VSE: replace Subsampled3x3 filter by a general Box filter #117584
|
@ -616,7 +616,6 @@ template<typename TextureMethod> class ScreenSpaceDrawingMode : public AbstractD
|
|||
&texture_buffer,
|
||||
transform_mode,
|
||||
IMB_FILTER_NEAREST,
|
||||
1,
|
||||
uv_to_texel.ptr(),
|
||||
crop_rect_ptr);
|
||||
}
|
||||
|
|
|
@ -266,6 +266,7 @@ enum eIMBInterpolationFilterMode {
|
|||
IMB_FILTER_BILINEAR,
|
||||
IMB_FILTER_CUBIC_BSPLINE,
|
||||
IMB_FILTER_CUBIC_MITCHELL,
|
||||
IMB_FILTER_BOX,
|
||||
};
|
||||
|
||||
/**
|
||||
|
@ -651,8 +652,6 @@ enum eIMBTransformMode {
|
|||
* - Only one data type buffer will be used (rect_float has priority over rect)
|
||||
* \param mode: Cropping/Wrap repeat effect to apply during transformation.
|
||||
* \param filter: Interpolation to use during sampling.
|
||||
* \param num_subsamples: Number of subsamples to use. Increasing this would improve the quality,
|
||||
* but reduces the performance.
|
||||
* \param transform_matrix: Transformation matrix to use.
|
||||
* The given matrix should transform between dst pixel space to src pixel space.
|
||||
* One unit is one pixel.
|
||||
|
@ -667,7 +666,6 @@ void IMB_transform(const ImBuf *src,
|
|||
ImBuf *dst,
|
||||
eIMBTransformMode mode,
|
||||
eIMBInterpolationFilterMode filter,
|
||||
const int num_subsamples,
|
||||
const float transform_matrix[4][4],
|
||||
const rctf *src_crop);
|
||||
|
||||
|
|
|
@ -15,7 +15,6 @@
|
|||
#include "BLI_math_vector.h"
|
||||
#include "BLI_rect.h"
|
||||
#include "BLI_task.hh"
|
||||
#include "BLI_vector.hh"
|
||||
|
||||
#include "IMB_imbuf.hh"
|
||||
#include "IMB_interp.hh"
|
||||
|
@ -39,42 +38,21 @@ struct TransformContext {
|
|||
/* Source UV step delta, when moving along one destination pixel in Y axis. */
|
||||
float2 add_y;
|
||||
|
||||
/* Per-subsample source image delta UVs. */
|
||||
Vector<float2, 9> subsampling_deltas;
|
||||
|
||||
IndexRange dst_region_x_range;
|
||||
IndexRange dst_region_y_range;
|
||||
|
||||
/* Cropping region in source image pixel space. */
|
||||
rctf src_crop;
|
||||
|
||||
void init(const float4x4 &transform_matrix, const int num_subsamples, const bool has_source_crop)
|
||||
void init(const float4x4 &transform_matrix, const bool has_source_crop)
|
||||
{
|
||||
start_uv = transform_matrix.location().xy();
|
||||
add_x = transform_matrix.x_axis().xy();
|
||||
add_y = transform_matrix.y_axis().xy();
|
||||
init_subsampling(num_subsamples);
|
||||
init_destination_region(transform_matrix, has_source_crop);
|
||||
}
|
||||
|
||||
private:
|
||||
void init_subsampling(const int num_subsamples)
|
||||
{
|
||||
float2 subsample_add_x = add_x / num_subsamples;
|
||||
float2 subsample_add_y = add_y / num_subsamples;
|
||||
float2 offset_x = -add_x * 0.5f + subsample_add_x * 0.5f;
|
||||
float2 offset_y = -add_y * 0.5f + subsample_add_y * 0.5f;
|
||||
|
||||
for (int y : IndexRange(0, num_subsamples)) {
|
||||
for (int x : IndexRange(0, num_subsamples)) {
|
||||
float2 delta_uv = offset_x + offset_y;
|
||||
delta_uv += x * subsample_add_x;
|
||||
delta_uv += y * subsample_add_y;
|
||||
subsampling_deltas.append(delta_uv);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void init_destination_region(const float4x4 &transform_matrix, const bool has_source_crop)
|
||||
{
|
||||
if (!has_source_crop) {
|
||||
|
@ -265,27 +243,47 @@ template<eIMBInterpolationFilterMode Filter,
|
|||
bool WrapUV>
|
||||
static void process_scanlines(const TransformContext &ctx, IndexRange y_range)
|
||||
{
|
||||
/* Note: sample at pixel center for proper filtering. */
|
||||
float2 uv_start = ctx.start_uv + ctx.add_x * 0.5f + ctx.add_y * 0.5f;
|
||||
if constexpr (Filter == IMB_FILTER_BOX) {
|
||||
|
||||
if (ctx.subsampling_deltas.size() > 1) {
|
||||
/* Multiple samples per pixel: accumulate them pre-multiplied,
|
||||
* divide by sample count and write out (un-pre-multiplying if writing out
|
||||
* to byte image). */
|
||||
const float inv_count = 1.0f / ctx.subsampling_deltas.size();
|
||||
* to byte image).
|
||||
*
|
||||
* Do a box filter: for each destination pixel, accumulate XxY samples from source,
|
||||
* based on scaling factors (length of X/Y pixel steps). Use at least 2 samples
|
||||
* along each direction, so that in case of rotation the resulting edges get
|
||||
* some anti-aliasing, to match previous Subsampled3x3 filter behavior. The
|
||||
* "at least 2" can be removed once/if transform edge anti-aliasing is implemented
|
||||
* in general way for all filters. Use at most 100 samples along each direction,
|
||||
* just as some way of clamping possible upper cost. Scaling something down by more
|
||||
* than 100x should rarely if ever happen, worst case they will get some aliasing.
|
||||
*/
|
||||
float2 uv_start = ctx.start_uv;
|
||||
int sub_count_x = int(math::clamp(roundf(math::length(ctx.add_x)), 2.0f, 100.0f));
|
||||
int sub_count_y = int(math::clamp(roundf(math::length(ctx.add_y)), 2.0f, 100.0f));
|
||||
const float inv_count = 1.0f / (sub_count_x * sub_count_y);
|
||||
const float2 sub_step_x = ctx.add_x / sub_count_x;
|
||||
const float2 sub_step_y = ctx.add_y / sub_count_y;
|
||||
|
||||
for (int yi : y_range) {
|
||||
T *output = init_pixel_pointer<T>(ctx.dst, ctx.dst_region_x_range.first(), yi);
|
||||
float2 uv_row = uv_start + yi * ctx.add_y;
|
||||
for (int xi : ctx.dst_region_x_range) {
|
||||
float2 uv = uv_row + xi * ctx.add_x;
|
||||
const float2 uv = uv_row + xi * ctx.add_x;
|
||||
float sample[4] = {};
|
||||
|
||||
for (const float2 &delta_uv : ctx.subsampling_deltas) {
|
||||
const float2 sub_uv = uv + delta_uv;
|
||||
if (!CropSource || !should_discard(ctx, sub_uv)) {
|
||||
T sub_sample[4];
|
||||
sample_image<Filter, T, SrcChannels, WrapUV>(ctx.src, sub_uv.x, sub_uv.y, sub_sample);
|
||||
add_subsample(sub_sample, sample);
|
||||
for (int sub_y = 0; sub_y < sub_count_y; sub_y++) {
|
||||
for (int sub_x = 0; sub_x < sub_count_x; sub_x++) {
|
||||
float2 delta = (sub_x + 0.5f) * sub_step_x + (sub_y + 0.5f) * sub_step_y;
|
||||
float2 sub_uv = uv + delta;
|
||||
if (!CropSource || !should_discard(ctx, sub_uv)) {
|
||||
T sub_sample[4];
|
||||
sample_image<eIMBInterpolationFilterMode::IMB_FILTER_NEAREST,
|
||||
T,
|
||||
SrcChannels,
|
||||
WrapUV>(ctx.src, sub_uv.x, sub_uv.y, sub_sample);
|
||||
add_subsample(sub_sample, sample);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -297,7 +295,8 @@ static void process_scanlines(const TransformContext &ctx, IndexRange y_range)
|
|||
}
|
||||
}
|
||||
else {
|
||||
/* One sample per pixel. */
|
||||
/* One sample per pixel. Note: sample at pixel center for proper filtering. */
|
||||
float2 uv_start = ctx.start_uv + ctx.add_x * 0.5f + ctx.add_y * 0.5f;
|
||||
for (int yi : y_range) {
|
||||
T *output = init_pixel_pointer<T>(ctx.dst, ctx.dst_region_x_range.first(), yi);
|
||||
float2 uv_row = uv_start + yi * ctx.add_y;
|
||||
|
@ -369,7 +368,6 @@ void IMB_transform(const ImBuf *src,
|
|||
ImBuf *dst,
|
||||
const eIMBTransformMode mode,
|
||||
const eIMBInterpolationFilterMode filter,
|
||||
const int num_subsamples,
|
||||
const float transform_matrix[4][4],
|
||||
const rctf *src_crop)
|
||||
{
|
||||
|
@ -386,7 +384,7 @@ void IMB_transform(const ImBuf *src,
|
|||
if (crop) {
|
||||
ctx.src_crop = *src_crop;
|
||||
}
|
||||
ctx.init(blender::float4x4(transform_matrix), num_subsamples, crop);
|
||||
ctx.init(blender::float4x4(transform_matrix), crop);
|
||||
|
||||
threading::parallel_for(ctx.dst_region_y_range, 8, [&](IndexRange y_range) {
|
||||
if (filter == IMB_FILTER_NEAREST) {
|
||||
|
@ -401,5 +399,8 @@ void IMB_transform(const ImBuf *src,
|
|||
else if (filter == IMB_FILTER_CUBIC_MITCHELL) {
|
||||
transform_scanlines_filter<IMB_FILTER_CUBIC_MITCHELL>(ctx, y_range);
|
||||
}
|
||||
else if (filter == IMB_FILTER_BOX) {
|
||||
transform_scanlines_filter<IMB_FILTER_BOX>(ctx, y_range);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
|
|
@ -37,29 +37,29 @@ static ImBuf *create_6x2_test_image()
|
|||
return img;
|
||||
}
|
||||
|
||||
static ImBuf *transform_2x_smaller(eIMBInterpolationFilterMode filter, int subsamples)
|
||||
static ImBuf *transform_2x_smaller(eIMBInterpolationFilterMode filter)
|
||||
{
|
||||
ImBuf *src = create_6x2_test_image();
|
||||
ImBuf *dst = IMB_allocImBuf(3, 1, 32, IB_rect);
|
||||
float4x4 matrix = math::from_scale<float4x4>(float4(2.0f));
|
||||
IMB_transform(src, dst, IMB_TRANSFORM_MODE_REGULAR, filter, subsamples, matrix.ptr(), nullptr);
|
||||
IMB_transform(src, dst, IMB_TRANSFORM_MODE_REGULAR, filter, matrix.ptr(), nullptr);
|
||||
IMB_freeImBuf(src);
|
||||
return dst;
|
||||
}
|
||||
|
||||
static ImBuf *transform_fractional_larger(eIMBInterpolationFilterMode filter, int subsamples)
|
||||
static ImBuf *transform_fractional_larger(eIMBInterpolationFilterMode filter)
|
||||
{
|
||||
ImBuf *src = create_6x2_test_image();
|
||||
ImBuf *dst = IMB_allocImBuf(9, 7, 32, IB_rect);
|
||||
float4x4 matrix = math::from_scale<float4x4>(float4(6.0f / 9.0f, 2.0f / 7.0f, 1.0f, 1.0f));
|
||||
IMB_transform(src, dst, IMB_TRANSFORM_MODE_REGULAR, filter, subsamples, matrix.ptr(), nullptr);
|
||||
IMB_transform(src, dst, IMB_TRANSFORM_MODE_REGULAR, filter, matrix.ptr(), nullptr);
|
||||
IMB_freeImBuf(src);
|
||||
return dst;
|
||||
}
|
||||
|
||||
TEST(imbuf_transform, nearest_2x_smaller)
|
||||
{
|
||||
ImBuf *res = transform_2x_smaller(IMB_FILTER_NEAREST, 1);
|
||||
ImBuf *res = transform_2x_smaller(IMB_FILTER_NEAREST);
|
||||
const ColorTheme4b *got = reinterpret_cast<ColorTheme4b *>(res->byte_buffer.data);
|
||||
EXPECT_EQ(got[0], ColorTheme4b(255, 255, 255, 255));
|
||||
EXPECT_EQ(got[1], ColorTheme4b(133, 55, 31, 19));
|
||||
|
@ -67,19 +67,20 @@ TEST(imbuf_transform, nearest_2x_smaller)
|
|||
IMB_freeImBuf(res);
|
||||
}
|
||||
|
||||
TEST(imbuf_transform, nearest_subsample3_2x_smaller)
|
||||
TEST(imbuf_transform, box_2x_smaller)
|
||||
{
|
||||
ImBuf *res = transform_2x_smaller(IMB_FILTER_NEAREST, 3);
|
||||
ImBuf *res = transform_2x_smaller(IMB_FILTER_BOX);
|
||||
const ColorTheme4b *got = reinterpret_cast<ColorTheme4b *>(res->byte_buffer.data);
|
||||
EXPECT_EQ(got[0], ColorTheme4b(227, 170, 113, 255));
|
||||
EXPECT_EQ(got[1], ColorTheme4b(133, 55, 31, 17));
|
||||
EXPECT_EQ(got[2], ColorTheme4b(56, 22, 64, 253));
|
||||
/* At 2x reduction should be same as bilinear, save for some rounding errors. */
|
||||
EXPECT_EQ(got[0], ColorTheme4b(191, 128, 64, 255));
|
||||
EXPECT_EQ(got[1], ColorTheme4b(133, 55, 31, 16));
|
||||
EXPECT_EQ(got[2], ColorTheme4b(54, 50, 48, 254));
|
||||
IMB_freeImBuf(res);
|
||||
}
|
||||
|
||||
TEST(imbuf_transform, bilinear_2x_smaller)
|
||||
{
|
||||
ImBuf *res = transform_2x_smaller(IMB_FILTER_BILINEAR, 1);
|
||||
ImBuf *res = transform_2x_smaller(IMB_FILTER_BILINEAR);
|
||||
const ColorTheme4b *got = reinterpret_cast<ColorTheme4b *>(res->byte_buffer.data);
|
||||
EXPECT_EQ(got[0], ColorTheme4b(191, 128, 64, 255));
|
||||
EXPECT_EQ(got[1], ColorTheme4b(133, 55, 31, 16));
|
||||
|
@ -89,7 +90,7 @@ TEST(imbuf_transform, bilinear_2x_smaller)
|
|||
|
||||
TEST(imbuf_transform, cubic_bspline_2x_smaller)
|
||||
{
|
||||
ImBuf *res = transform_2x_smaller(IMB_FILTER_CUBIC_BSPLINE, 1);
|
||||
ImBuf *res = transform_2x_smaller(IMB_FILTER_CUBIC_BSPLINE);
|
||||
const ColorTheme4b *got = reinterpret_cast<ColorTheme4b *>(res->byte_buffer.data);
|
||||
EXPECT_EQ(got[0], ColorTheme4b(189, 126, 62, 250));
|
||||
EXPECT_EQ(got[1], ColorTheme4b(134, 57, 33, 26));
|
||||
|
@ -99,7 +100,7 @@ TEST(imbuf_transform, cubic_bspline_2x_smaller)
|
|||
|
||||
TEST(imbuf_transform, cubic_mitchell_2x_smaller)
|
||||
{
|
||||
ImBuf *res = transform_2x_smaller(IMB_FILTER_CUBIC_MITCHELL, 1);
|
||||
ImBuf *res = transform_2x_smaller(IMB_FILTER_CUBIC_MITCHELL);
|
||||
const ColorTheme4b *got = reinterpret_cast<ColorTheme4b *>(res->byte_buffer.data);
|
||||
EXPECT_EQ(got[0], ColorTheme4b(195, 130, 67, 255));
|
||||
EXPECT_EQ(got[1], ColorTheme4b(132, 51, 28, 0));
|
||||
|
@ -109,7 +110,7 @@ TEST(imbuf_transform, cubic_mitchell_2x_smaller)
|
|||
|
||||
TEST(imbuf_transform, cubic_mitchell_fractional_larger)
|
||||
{
|
||||
ImBuf *res = transform_fractional_larger(IMB_FILTER_CUBIC_MITCHELL, 1);
|
||||
ImBuf *res = transform_fractional_larger(IMB_FILTER_CUBIC_MITCHELL);
|
||||
const ColorTheme4b *got = reinterpret_cast<ColorTheme4b *>(res->byte_buffer.data);
|
||||
EXPECT_EQ(got[0 + 0 * res->x], ColorTheme4b(0, 0, 0, 255));
|
||||
EXPECT_EQ(got[1 + 0 * res->x], ColorTheme4b(127, 0, 0, 255));
|
||||
|
@ -138,8 +139,7 @@ TEST(imbuf_transform, nearest_very_large_scale)
|
|||
ImBuf *res = IMB_allocImBuf(3841, 1, 32, IB_rect);
|
||||
float4x4 matrix = math::from_loc_rot_scale<float4x4>(
|
||||
float3(254, 0, 0), math::Quaternion::identity(), float3(3.0f / 3840.0f, 1, 1));
|
||||
IMB_transform(
|
||||
src, res, IMB_TRANSFORM_MODE_REGULAR, IMB_FILTER_NEAREST, 1, matrix.ptr(), nullptr);
|
||||
IMB_transform(src, res, IMB_TRANSFORM_MODE_REGULAR, IMB_FILTER_NEAREST, matrix.ptr(), nullptr);
|
||||
|
||||
/* Check result: leftmost red, middle green, two rightmost pixels blue and black.
|
||||
* If the transform code internally does not have enough precision while stepping
|
||||
|
|
|
@ -838,7 +838,7 @@ typedef enum SequenceColorTag {
|
|||
enum {
|
||||
SEQ_TRANSFORM_FILTER_NEAREST = 0,
|
||||
SEQ_TRANSFORM_FILTER_BILINEAR = 1,
|
||||
SEQ_TRANSFORM_FILTER_NEAREST_3x3 = 2,
|
||||
SEQ_TRANSFORM_FILTER_BOX = 2,
|
||||
SEQ_TRANSFORM_FILTER_CUBIC_BSPLINE = 3,
|
||||
SEQ_TRANSFORM_FILTER_CUBIC_MITCHELL = 4,
|
||||
};
|
||||
|
|
|
@ -1726,11 +1726,11 @@ static const EnumPropertyItem transform_filter_items[] = {
|
|||
"Cubic B-Spline",
|
||||
"Cubic B-Spline filter (blurry but no ringing) on 4" BLI_STR_UTF8_MULTIPLICATION_SIGN
|
||||
"4 samples"},
|
||||
{SEQ_TRANSFORM_FILTER_NEAREST_3x3,
|
||||
"SUBSAMPLING_3x3",
|
||||
{SEQ_TRANSFORM_FILTER_BOX,
|
||||
"BOX",
|
||||
0,
|
||||
"Subsampling (3" BLI_STR_UTF8_MULTIPLICATION_SIGN "3)",
|
||||
"Use nearest with 3" BLI_STR_UTF8_MULTIPLICATION_SIGN "3 subsamples"},
|
||||
"Box",
|
||||
"Averages source image samples that fall under destination pixel"},
|
||||
{0, nullptr, 0, nullptr, nullptr},
|
||||
};
|
||||
|
||||
|
|
|
@ -465,14 +465,8 @@ static void sequencer_thumbnail_transform(ImBuf *in, ImBuf *out)
|
|||
blender::float3{scale_x, scale_y, 1.0f});
|
||||
transform_pivot_set_m4(transform_matrix, pivot);
|
||||
invert_m4(transform_matrix);
|
||||
const int num_subsamples = 1;
|
||||
IMB_transform(in,
|
||||
out,
|
||||
IMB_TRANSFORM_MODE_REGULAR,
|
||||
IMB_FILTER_NEAREST,
|
||||
num_subsamples,
|
||||
transform_matrix,
|
||||
nullptr);
|
||||
IMB_transform(
|
||||
in, out, IMB_TRANSFORM_MODE_REGULAR, IMB_FILTER_NEAREST, transform_matrix, nullptr);
|
||||
}
|
||||
|
||||
/* Check whether transform introduces transparent ares in the result (happens when the transformed
|
||||
|
@ -537,7 +531,6 @@ static void sequencer_preprocess_transform_crop(
|
|||
|
||||
const StripTransform *transform = seq->strip->transform;
|
||||
eIMBInterpolationFilterMode filter = IMB_FILTER_NEAREST;
|
||||
int num_subsamples = 1;
|
||||
switch (transform->filter) {
|
||||
case SEQ_TRANSFORM_FILTER_NEAREST:
|
||||
filter = IMB_FILTER_NEAREST;
|
||||
|
@ -551,19 +544,12 @@ static void sequencer_preprocess_transform_crop(
|
|||
case SEQ_TRANSFORM_FILTER_CUBIC_MITCHELL:
|
||||
filter = IMB_FILTER_CUBIC_MITCHELL;
|
||||
break;
|
||||
case SEQ_TRANSFORM_FILTER_NEAREST_3x3:
|
||||
filter = IMB_FILTER_NEAREST;
|
||||
num_subsamples = 3;
|
||||
case SEQ_TRANSFORM_FILTER_BOX:
|
||||
filter = IMB_FILTER_BOX;
|
||||
break;
|
||||
}
|
||||
|
||||
IMB_transform(in,
|
||||
out,
|
||||
IMB_TRANSFORM_MODE_CROP_SRC,
|
||||
filter,
|
||||
num_subsamples,
|
||||
transform_matrix,
|
||||
&source_crop);
|
||||
IMB_transform(in, out, IMB_TRANSFORM_MODE_CROP_SRC, filter, transform_matrix, &source_crop);
|
||||
|
||||
if (!seq_image_transform_transparency_gained(context, seq)) {
|
||||
out->planes = in->planes;
|
||||
|
|
Loading…
Reference in New Issue