diff --git a/source/blender/imbuf/intern/transform.cc b/source/blender/imbuf/intern/transform.cc index 9eeca79a0ea..f531f527df4 100644 --- a/source/blender/imbuf/intern/transform.cc +++ b/source/blender/imbuf/intern/transform.cc @@ -6,7 +6,6 @@ * \ingroup imbuf */ -#include #include #include "BLI_math_color_blend.h" @@ -22,73 +21,39 @@ namespace blender::imbuf::transform { -struct TransformUserData { - /** \brief Source image buffer to read from. */ +struct TransformContext { const ImBuf *src; - /** \brief Destination image buffer to write to. */ ImBuf *dst; - /** \brief UV coordinates at the origin (0,0) in source image space. */ + eIMBTransformMode mode; + + /* UV coordinates at the destination origin (0,0) in source image space. */ float2 start_uv; - /** - * \brief delta UV coordinates along the source image buffer, when moving a single pixel in the X - * axis of the dst image buffer. - */ + /* Source UV step delta, when moving along one destination pixel in X axis. */ float2 add_x; - /** - * \brief delta UV coordinate along the source image buffer, when moving a single pixel in the Y - * axes of the dst image buffer. - */ + /* Source UV step delta, when moving along one destination pixel in Y axis. */ float2 add_y; - struct { - /** - * Contains per sub-sample a delta to be added to the uv of the source image buffer. - */ - Vector delta_uvs; - } subsampling; + /* Per-subsample source image delta UVs. */ + Vector subsampling_deltas; - struct { - IndexRange x_range; - IndexRange y_range; - } destination_region; + IndexRange dst_region_x_range; + IndexRange dst_region_y_range; - /** - * \brief Cropping region in source image pixel space. - */ + /* Cropping region in source image pixel space. */ rctf src_crop; - /** - * \brief Initialize the start_uv, add_x and add_y fields based on the given transform matrix. - */ - void init(const float4x4 &transform_matrix, - const int num_subsamples, - const bool do_crop_destination_region) + void init(const float4x4 &transform_matrix, const int num_subsamples, const bool has_source_crop) { - init_start_uv(transform_matrix); - init_add_x(transform_matrix); - init_add_y(transform_matrix); + 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, do_crop_destination_region); + init_destination_region(transform_matrix, has_source_crop); } private: - void init_start_uv(const float4x4 &transform_matrix) - { - start_uv = transform_matrix.location().xy(); - } - - void init_add_x(const float4x4 &transform_matrix) - { - add_x = transform_matrix.x_axis().xy(); - } - - void init_add_y(const float4x4 &transform_matrix) - { - add_y = transform_matrix.y_axis().xy(); - } - void init_subsampling(const int num_subsamples) { float2 subsample_add_x = add_x / num_subsamples; @@ -101,17 +66,16 @@ struct TransformUserData { float2 delta_uv = offset_x + offset_y; delta_uv += x * subsample_add_x; delta_uv += y * subsample_add_y; - subsampling.delta_uvs.append(delta_uv); + subsampling_deltas.append(delta_uv); } } } - void init_destination_region(const float4x4 &transform_matrix, - const bool do_crop_destination_region) + void init_destination_region(const float4x4 &transform_matrix, const bool has_source_crop) { - if (!do_crop_destination_region) { - destination_region.x_range = IndexRange(dst->x); - destination_region.y_range = IndexRange(dst->y); + if (!has_source_crop) { + dst_region_x_range = IndexRange(dst->x); + dst_region_y_range = IndexRange(dst->y); return; } @@ -136,99 +100,28 @@ struct TransformUserData { rcti dest_rect; BLI_rcti_init(&dest_rect, 0, dst->x, 0, dst->y); BLI_rcti_isect(&rect, &dest_rect, &rect); - destination_region.x_range = IndexRange(rect.xmin, BLI_rcti_size_x(&rect)); - destination_region.y_range = IndexRange(rect.ymin, BLI_rcti_size_y(&rect)); + dst_region_x_range = IndexRange(rect.xmin, BLI_rcti_size_x(&rect)); + dst_region_y_range = IndexRange(rect.ymin, BLI_rcti_size_y(&rect)); } }; -/** - * \brief Crop uv-coordinates that are outside the user data src_crop rect. - */ -struct CropSource { - /** - * \brief Should the source pixel at the given uv coordinate be discarded. - * - * Uses user_data.src_crop to determine if the uv coordinate should be skipped. - */ - static bool should_discard(const TransformUserData &user_data, const float2 &uv) - { - return uv.x < user_data.src_crop.xmin || uv.x >= user_data.src_crop.xmax || - uv.y < user_data.src_crop.ymin || uv.y >= user_data.src_crop.ymax; - } -}; +/* Crop uv-coordinates that are outside the user data src_crop rect. */ +static bool should_discard(const TransformContext &ctx, const float2 &uv) +{ + return uv.x < ctx.src_crop.xmin || uv.x >= ctx.src_crop.xmax || uv.y < ctx.src_crop.ymin || + uv.y >= ctx.src_crop.ymax; +} -/** - * \brief Discard that does not discard anything. - */ -struct NoDiscard { - /** - * \brief Should the source pixel at the given uv coordinate be discarded. - * - * Will never discard any pixels. - */ - static bool should_discard(const TransformUserData & /*user_data*/, const float2 & /*uv*/) - { - return false; - } -}; +template static T *init_pixel_pointer(const ImBuf *image, int x, int y); +template<> uchar *init_pixel_pointer(const ImBuf *image, int x, int y) +{ + return image->byte_buffer.data + (size_t(y) * image->x + x) * image->channels; +} +template<> float *init_pixel_pointer(const ImBuf *image, int x, int y) +{ + return image->float_buffer.data + (size_t(y) * image->x + x) * image->channels; +} -/** - * \brief Pointer to a pixel to write to in serial. - */ -template< - /** - * \brief Kind of buffer. - * Possible options: float, uchar. - */ - typename StorageType = float, - - /** - * \brief Number of channels of a single pixel. - */ - int NumChannels = 4> -class PixelPointer { - public: - static const int ChannelLen = NumChannels; - - private: - StorageType *pointer; - - public: - void init_pixel_pointer(const ImBuf *image_buffer, int2 start_coordinate) - { - const size_t offset = (start_coordinate.y * size_t(image_buffer->x) + start_coordinate.x) * - NumChannels; - - if constexpr (std::is_same_v) { - pointer = image_buffer->float_buffer.data + offset; - } - else if constexpr (std::is_same_v) { - pointer = const_cast( - static_cast(static_cast(image_buffer->byte_buffer.data)) + - offset); - } - else { - pointer = nullptr; - } - } - - /** - * \brief Get pointer to the current pixel to write to. - */ - StorageType *get_pointer() - { - return pointer; - } - - void increase_pixel_pointer() - { - pointer += NumChannels; - } -}; - -/** - * \brief Repeats UV coordinate. - */ static float wrap_uv(float value, int size) { int x = int(floorf(value)); @@ -241,416 +134,246 @@ static float wrap_uv(float value, int size) return x; } -/* TODO: should we use math_vectors for this. */ -template -class Pixel : public std::array { - public: - void clear() - { - for (int channel_index : IndexRange(NumChannels)) { - (*this)[channel_index] = 0; +template +static void add_subsample(const T *src, T *dst, int sample_number) +{ + BLI_STATIC_ASSERT((is_same_any_v), "Only uchar and float channels supported."); + + float factor = 1.0 / (sample_number + 1); + if constexpr (std::is_same_v) { + BLI_STATIC_ASSERT(NumChannels == 4, "Pixels using uchar requires to have 4 channels."); + blend_color_interpolate_byte(dst, dst, src, factor); + } + else if constexpr (std::is_same_v && NumChannels == 4) { + blend_color_interpolate_float(dst, dst, src, factor); + } + else if constexpr (std::is_same_v) { + for (int i : IndexRange(NumChannels)) { + dst[i] = dst[i] * (1.0f - factor) + src[i] * factor; } } +} - void add_subsample(const Pixel other, int sample_number) - { - BLI_STATIC_ASSERT((std::is_same_v) || (std::is_same_v), - "Only uchar and float channels supported."); +template +static void sample_nearest_float(const ImBuf *source, float u, float v, float *r_sample) +{ + int x1 = int(u); + int y1 = int(v); - float factor = 1.0 / (sample_number + 1); - if constexpr (std::is_same_v) { - BLI_STATIC_ASSERT(NumChannels == 4, "Pixels using uchar requires to have 4 channels."); - blend_color_interpolate_byte(this->data(), this->data(), other.data(), factor); - } - else if constexpr (std::is_same_v && NumChannels == 4) { - blend_color_interpolate_float(this->data(), this->data(), other.data(), factor); - } - else if constexpr (std::is_same_v) { - for (int channel_index : IndexRange(NumChannels)) { - (*this)[channel_index] = (*this)[channel_index] * (1.0 - factor) + - other[channel_index] * factor; - } - } - } -}; - -/** - * \brief Read a sample from an image buffer. - * - * A sampler can read from an image buffer. - */ -template< - /** \brief Interpolation mode to use when sampling. */ - eIMBInterpolationFilterMode Filter, - - /** \brief storage type of a single pixel channel (uchar or float). */ - typename StorageType, - /** - * \brief number of channels if the image to read. - * - * Must match the actual channels of the image buffer that is sampled. - */ - int NumChannels, - /** - * \brief Should UVs wrap - */ - bool UVWrapping> -class Sampler { - public: - using ChannelType = StorageType; - static const int ChannelLen = NumChannels; - using SampleType = Pixel; - - void sample(const ImBuf *source, const float2 &uv, SampleType &r_sample) - { - float u = uv.x; - float v = uv.y; - if constexpr (UVWrapping) { - u = wrap_uv(u, source->x); - v = wrap_uv(v, source->y); - } - /* BLI_bilinear_interpolation functions use `floor(uv)` and `floor(uv)+1` - * texels. For proper mapping between pixel and texel spaces, need to - * subtract 0.5. Same for bicubic. */ - if constexpr (Filter == IMB_FILTER_BILINEAR || Filter == IMB_FILTER_BICUBIC) { - u -= 0.5f; - v -= 0.5f; - } - if constexpr (Filter == IMB_FILTER_BILINEAR && std::is_same_v && - NumChannels == 4) - { - bilinear_interpolation_color_fl(source, r_sample.data(), u, v); - } - else if constexpr (Filter == IMB_FILTER_NEAREST && std::is_same_v && - NumChannels == 4) - { - nearest_interpolation_color_char(source, r_sample.data(), nullptr, u, v); - } - else if constexpr (Filter == IMB_FILTER_BILINEAR && std::is_same_v && - NumChannels == 4) - { - bilinear_interpolation_color_char(source, r_sample.data(), u, v); - } - else if constexpr (Filter == IMB_FILTER_BILINEAR && std::is_same_v) { - if constexpr (UVWrapping) { - BLI_bilinear_interpolation_wrap_fl(source->float_buffer.data, - r_sample.data(), - source->x, - source->y, - NumChannels, - UNPACK2(uv), - true, - true); - } - else { - BLI_bilinear_interpolation_fl( - source->float_buffer.data, r_sample.data(), source->x, source->y, NumChannels, u, v); - } - } - else if constexpr (Filter == IMB_FILTER_NEAREST && std::is_same_v) { - sample_nearest_float(source, u, v, r_sample); - } - else if constexpr (Filter == IMB_FILTER_BICUBIC && std::is_same_v) { - BLI_bicubic_interpolation_fl( - source->float_buffer.data, r_sample.data(), source->x, source->y, NumChannels, u, v); - } - else if constexpr (Filter == IMB_FILTER_BICUBIC && std::is_same_v && - NumChannels == 4) - { - BLI_bicubic_interpolation_char( - source->byte_buffer.data, r_sample.data(), source->x, source->y, u, v); - } - else { - /* Unsupported sampler. */ - BLI_assert_unreachable(); - } - } - - private: - void sample_nearest_float(const ImBuf *source, - const float u, - const float v, - SampleType &r_sample) - { - BLI_STATIC_ASSERT(std::is_same_v); - - /* ImBuf in must have a valid rect or rect_float, assume this is already checked */ - int x1 = int(u); - int y1 = int(v); - - /* Break when sample outside image is requested. */ - if (x1 < 0 || x1 >= source->x || y1 < 0 || y1 >= source->y) { - for (int i = 0; i < NumChannels; i++) { - r_sample[i] = 0.0f; - } - return; - } - - const size_t offset = (size_t(source->x) * y1 + x1) * NumChannels; - const float *dataF = source->float_buffer.data + offset; + /* Break when sample outside image is requested. */ + if (x1 < 0 || x1 >= source->x || y1 < 0 || y1 >= source->y) { for (int i = 0; i < NumChannels; i++) { - r_sample[i] = dataF[i]; - } - } -}; - -/** - * \brief Change the number of channels and store it. - * - * Template class to convert and store a sample in a PixelPointer. - * It supports: - * - 4 channel uchar -> 4 channel uchar. - * - 4 channel float -> 4 channel float. - * - 3 channel float -> 4 channel float. - * - 2 channel float -> 4 channel float. - * - 1 channel float -> 4 channel float. - */ -template -class ChannelConverter { - public: - using SampleType = Pixel; - using PixelType = PixelPointer; - - /** - * \brief Convert the number of channels of the given sample to match the pixel pointer and - * store it at the location the pixel_pointer points at. - */ - void convert_and_store(const SampleType &sample, PixelType &pixel_pointer) - { - if constexpr (std::is_same_v) { - BLI_STATIC_ASSERT(SourceNumChannels == 4, "Unsigned chars always have 4 channels."); - BLI_STATIC_ASSERT(DestinationNumChannels == 4, "Unsigned chars always have 4 channels."); - - copy_v4_v4_uchar(pixel_pointer.get_pointer(), sample.data()); - } - else if constexpr (std::is_same_v && SourceNumChannels == 4 && - DestinationNumChannels == 4) - { - copy_v4_v4(pixel_pointer.get_pointer(), sample.data()); - } - else if constexpr (std::is_same_v && SourceNumChannels == 3 && - DestinationNumChannels == 4) - { - copy_v4_fl4(pixel_pointer.get_pointer(), sample[0], sample[1], sample[2], 1.0f); - } - else if constexpr (std::is_same_v && SourceNumChannels == 2 && - DestinationNumChannels == 4) - { - copy_v4_fl4(pixel_pointer.get_pointer(), sample[0], sample[1], 0.0f, 1.0f); - } - else if constexpr (std::is_same_v && SourceNumChannels == 1 && - DestinationNumChannels == 4) - { - copy_v4_fl4(pixel_pointer.get_pointer(), sample[0], sample[0], sample[0], 1.0f); - } - else { - BLI_assert_unreachable(); + r_sample[i] = 0.0f; } + return; } - void mix_and_store(const SampleType &sample, PixelType &pixel_pointer, const float mix_factor) - { - if constexpr (std::is_same_v) { - BLI_STATIC_ASSERT(SourceNumChannels == 4, "Unsigned chars always have 4 channels."); - BLI_STATIC_ASSERT(DestinationNumChannels == 4, "Unsigned chars always have 4 channels."); - blend_color_interpolate_byte( - pixel_pointer.get_pointer(), pixel_pointer.get_pointer(), sample.data(), mix_factor); - } - else if constexpr (std::is_same_v && SourceNumChannels == 4 && - DestinationNumChannels == 4) - { - blend_color_interpolate_float( - pixel_pointer.get_pointer(), pixel_pointer.get_pointer(), sample.data(), mix_factor); - } - else { - BLI_assert_unreachable(); - } + size_t offset = (size_t(source->x) * y1 + x1) * NumChannels; + const float *dataF = source->float_buffer.data + offset; + for (int i = 0; i < NumChannels; i++) { + r_sample[i] = dataF[i]; } -}; - -/** - * \brief Processor for a scanline. - */ -template< - /** - * \brief Discard functor that implements `should_discard`. - */ - typename Discard, - - /** - * \brief Color interpolation function to read from the source buffer. - */ - typename Sampler, - - /** - * \brief Kernel to store to the destination buffer. - * Should be an PixelPointer - */ - typename OutputPixelPointer> -class ScanlineProcessor { - Discard discarder; - OutputPixelPointer output; - Sampler sampler; - - /** - * \brief Channels sizzling logic to convert between the input image buffer and the output - * image buffer. - */ - ChannelConverter - channel_converter; - - public: - /** - * \brief Inner loop of the transformations, processing a full scanline. - */ - void process(const TransformUserData *user_data, int scanline) - { - if (user_data->subsampling.delta_uvs.size() > 1) { - process_with_subsampling(user_data, scanline); - } - else { - process_one_sample_per_pixel(user_data, scanline); - } - } - - private: - void process_one_sample_per_pixel(const TransformUserData *user_data, int scanline) - { - /* Note: sample at pixel center for proper filtering. */ - float pixel_x = 0.5f; - float pixel_y = scanline + 0.5f; - float2 uv0 = user_data->start_uv + user_data->add_x * pixel_x + user_data->add_y * pixel_y; - - output.init_pixel_pointer(user_data->dst, - int2(user_data->destination_region.x_range.first(), scanline)); - for (int xi : user_data->destination_region.x_range) { - float2 uv = uv0 + xi * user_data->add_x; - if (!discarder.should_discard(*user_data, uv)) { - typename Sampler::SampleType sample; - sampler.sample(user_data->src, uv, sample); - channel_converter.convert_and_store(sample, output); - } - output.increase_pixel_pointer(); - } - } - - void process_with_subsampling(const TransformUserData *user_data, int scanline) - { - /* Note: sample at pixel center for proper filtering. */ - float pixel_x = 0.5f; - float pixel_y = scanline + 0.5f; - float2 uv0 = user_data->start_uv + user_data->add_x * pixel_x + user_data->add_y * pixel_y; - - output.init_pixel_pointer(user_data->dst, - int2(user_data->destination_region.x_range.first(), scanline)); - for (int xi : user_data->destination_region.x_range) { - float2 uv = uv0 + xi * user_data->add_x; - typename Sampler::SampleType sample; - sample.clear(); - int num_subsamples_added = 0; - - for (const float2 &delta_uv : user_data->subsampling.delta_uvs) { - const float2 subsample_uv = uv + delta_uv; - if (!discarder.should_discard(*user_data, subsample_uv)) { - typename Sampler::SampleType sub_sample; - sampler.sample(user_data->src, subsample_uv, sub_sample); - sample.add_subsample(sub_sample, num_subsamples_added); - num_subsamples_added += 1; - } - } - - if (num_subsamples_added != 0) { - const float mix_weight = float(num_subsamples_added) / - user_data->subsampling.delta_uvs.size(); - channel_converter.mix_and_store(sample, output, mix_weight); - } - output.increase_pixel_pointer(); - } - } -}; - -/** - * \brief callback function for threaded transformation. - */ -template void transform_scanline_function(void *custom_data, int scanline) -{ - const TransformUserData *user_data = static_cast(custom_data); - Processor processor; - processor.process(user_data, scanline); } +/* Read a pixel from an image buffer, with filtering/wrapping parameters. */ +template +static void sample_image(const ImBuf *source, float u, float v, T *r_sample) +{ + if constexpr (WrapUV) { + u = wrap_uv(u, source->x); + v = wrap_uv(v, source->y); + } + /* BLI_bilinear_interpolation functions use `floor(uv)` and `floor(uv)+1` + * texels. For proper mapping between pixel and texel spaces, need to + * subtract 0.5. Same for bicubic. */ + if constexpr (Filter == IMB_FILTER_BILINEAR || Filter == IMB_FILTER_BICUBIC) { + u -= 0.5f; + v -= 0.5f; + } + if constexpr (Filter == IMB_FILTER_BILINEAR && std::is_same_v && NumChannels == 4) { + bilinear_interpolation_color_fl(source, r_sample, u, v); + } + else if constexpr (Filter == IMB_FILTER_NEAREST && std::is_same_v && NumChannels == 4) + { + nearest_interpolation_color_char(source, r_sample, nullptr, u, v); + } + else if constexpr (Filter == IMB_FILTER_BILINEAR && std::is_same_v && NumChannels == 4) + { + bilinear_interpolation_color_char(source, r_sample, u, v); + } + else if constexpr (Filter == IMB_FILTER_BILINEAR && std::is_same_v) { + if constexpr (WrapUV) { + BLI_bilinear_interpolation_wrap_fl(source->float_buffer.data, + r_sample, + source->x, + source->y, + NumChannels, + u, + v, + true, + true); + } + else { + BLI_bilinear_interpolation_fl( + source->float_buffer.data, r_sample, source->x, source->y, NumChannels, u, v); + } + } + else if constexpr (Filter == IMB_FILTER_NEAREST && std::is_same_v) { + sample_nearest_float(source, u, v, r_sample); + } + else if constexpr (Filter == IMB_FILTER_BICUBIC && std::is_same_v) { + BLI_bicubic_interpolation_fl( + source->float_buffer.data, r_sample, source->x, source->y, NumChannels, u, v); + } + else if constexpr (Filter == IMB_FILTER_BICUBIC && std::is_same_v && NumChannels == 4) + { + BLI_bicubic_interpolation_char(source->byte_buffer.data, r_sample, source->x, source->y, u, v); + } + else { + /* Unsupported sampler. */ + BLI_assert_unreachable(); + } +} + +template static void store_sample(const T *sample, T *dst) +{ + if constexpr (std::is_same_v) { + BLI_STATIC_ASSERT(SrcChannels == 4, "Unsigned chars always have 4 channels."); + copy_v4_v4_uchar(dst, sample); + } + else if constexpr (std::is_same_v && SrcChannels == 4) { + copy_v4_v4(dst, sample); + } + else if constexpr (std::is_same_v && SrcChannels == 3) { + copy_v4_fl4(dst, sample[0], sample[1], sample[2], 1.0f); + } + else if constexpr (std::is_same_v && SrcChannels == 2) { + copy_v4_fl4(dst, sample[0], sample[1], 0.0f, 1.0f); + } + else if constexpr (std::is_same_v && SrcChannels == 1) { + /* Note: single channel sample is stored as grayscale. */ + copy_v4_fl4(dst, sample[0], sample[0], sample[0], 1.0f); + } + else { + BLI_assert_unreachable(); + } +} + +template +static void mix_and_store_sample(const T *sample, T *dst, const float mix_factor) +{ + if constexpr (std::is_same_v) { + BLI_STATIC_ASSERT(SrcChannels == 4, "Unsigned chars always have 4 channels."); + blend_color_interpolate_byte(dst, dst, sample, mix_factor); + } + else if constexpr (std::is_same_v && SrcChannels == 4) { + blend_color_interpolate_float(dst, dst, sample, mix_factor); + } + else { + BLI_assert_unreachable(); + } +} + +/* Process a block of destination image scanlines. */ template -ScanlineThreadFunc get_scanline_function(const eIMBTransformMode mode) - + typename T, + int SrcChannels, + bool CropSource, + bool WrapUV> +static void process_scanlines(const TransformContext &ctx, IndexRange y_range) { - switch (mode) { - case IMB_TRANSFORM_MODE_REGULAR: - return transform_scanline_function< - ScanlineProcessor, - PixelPointer>>; - case IMB_TRANSFORM_MODE_CROP_SRC: - return transform_scanline_function< - ScanlineProcessor, - PixelPointer>>; - case IMB_TRANSFORM_MODE_WRAP_REPEAT: - return transform_scanline_function< - ScanlineProcessor, - PixelPointer>>; - } + /* Note: sample at pixel center for proper filtering. */ + float2 uv_start = ctx.start_uv + ctx.add_x * 0.5f + ctx.add_y * 0.5f; - BLI_assert_unreachable(); - return nullptr; -} + if (ctx.subsampling_deltas.size() > 1) { + /* Multiple samples per pixel. */ + for (int yi : y_range) { + T *output = init_pixel_pointer(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; + T sample[4] = {}; + int num_subsamples_added = 0; -template -ScanlineThreadFunc get_scanline_function(const TransformUserData *user_data, - const eIMBTransformMode mode) -{ - const ImBuf *src = user_data->src; - const ImBuf *dst = user_data->dst; + 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(ctx.src, sub_uv.x, sub_uv.y, sub_sample); + add_subsample(sub_sample, sample, num_subsamples_added); + num_subsamples_added += 1; + } + } - if (src->channels == 4 && dst->channels == 4) { - return get_scanline_function(mode); - } - if (src->channels == 3 && dst->channels == 4) { - return get_scanline_function(mode); - } - if (src->channels == 2 && dst->channels == 4) { - return get_scanline_function(mode); - } - if (src->channels == 1 && dst->channels == 4) { - return get_scanline_function(mode); - } - return nullptr; -} - -template -static void transform_threaded(TransformUserData *user_data, const eIMBTransformMode mode) -{ - ScanlineThreadFunc scanline_func = nullptr; - - if (user_data->dst->float_buffer.data && user_data->src->float_buffer.data) { - scanline_func = get_scanline_function(user_data, mode); - } - else if (user_data->dst->byte_buffer.data && user_data->src->byte_buffer.data) { - /* Number of channels is always 4 when using uchar buffers (sRGB + straight alpha). */ - scanline_func = get_scanline_function(mode); - } - - if (scanline_func != nullptr) { - threading::parallel_for(user_data->destination_region.y_range, 8, [&](IndexRange range) { - for (int scanline : range) { - scanline_func(user_data, scanline); + if (num_subsamples_added != 0) { + const float mix_weight = float(num_subsamples_added) / ctx.subsampling_deltas.size(); + mix_and_store_sample(sample, output, mix_weight); + } + output += 4; } - }); + } + } + else { + /* One sample per pixel. */ + for (int yi : y_range) { + T *output = init_pixel_pointer(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; + if (!CropSource || !should_discard(ctx, uv)) { + T sample[4]; + sample_image(ctx.src, uv.x, uv.y, sample); + store_sample(sample, output); + } + output += 4; + } + } + } +} + +template +static void transform_scanlines(const TransformContext &ctx, IndexRange y_range) +{ + switch (ctx.mode) { + case IMB_TRANSFORM_MODE_REGULAR: + process_scanlines(ctx, y_range); + break; + case IMB_TRANSFORM_MODE_CROP_SRC: + process_scanlines(ctx, y_range); + break; + case IMB_TRANSFORM_MODE_WRAP_REPEAT: + process_scanlines(ctx, y_range); + break; + default: + BLI_assert_unreachable(); + break; + } +} + +template +static void transform_scanlines_filter(const TransformContext &ctx, IndexRange y_range) +{ + int channels = ctx.src->channels; + if (ctx.dst->float_buffer.data && ctx.src->float_buffer.data) { + /* Float images. */ + if (channels == 4) { + transform_scanlines(ctx, y_range); + } + else if (channels == 3) { + transform_scanlines(ctx, y_range); + } + else if (channels == 2) { + transform_scanlines(ctx, y_range); + } + else if (channels == 1) { + transform_scanlines(ctx, y_range); + } + } + else if (ctx.dst->byte_buffer.data && ctx.src->byte_buffer.data) { + /* Byte images. */ + if (channels == 4) { + transform_scanlines(ctx, y_range); + } } } @@ -659,6 +382,7 @@ static void transform_threaded(TransformUserData *user_data, const eIMBTransform extern "C" { using namespace blender::imbuf::transform; +using namespace blender; void IMB_transform(const ImBuf *src, ImBuf *dst, @@ -671,25 +395,28 @@ void IMB_transform(const ImBuf *src, BLI_assert_msg(mode != IMB_TRANSFORM_MODE_CROP_SRC || src_crop != nullptr, "No source crop rect given, but crop source is requested. Or source crop rect " "was given, but crop source was not requested."); + BLI_assert_msg(dst->channels == 4, "Destination image must have 4 channels."); - TransformUserData user_data; - user_data.src = src; - user_data.dst = dst; - if (mode == IMB_TRANSFORM_MODE_CROP_SRC) { - user_data.src_crop = *src_crop; + TransformContext ctx; + ctx.src = src; + ctx.dst = dst; + ctx.mode = mode; + bool crop = mode == IMB_TRANSFORM_MODE_CROP_SRC; + if (crop) { + ctx.src_crop = *src_crop; } - user_data.init(blender::float4x4(transform_matrix), - num_subsamples, - ELEM(mode, IMB_TRANSFORM_MODE_CROP_SRC)); + ctx.init(blender::float4x4(transform_matrix), num_subsamples, crop); - if (filter == IMB_FILTER_NEAREST) { - transform_threaded(&user_data, mode); - } - else if (filter == IMB_FILTER_BILINEAR) { - transform_threaded(&user_data, mode); - } - else if (filter == IMB_FILTER_BICUBIC) { - transform_threaded(&user_data, mode); - } + threading::parallel_for(ctx.dst_region_y_range, 8, [&](IndexRange y_range) { + if (filter == IMB_FILTER_NEAREST) { + transform_scanlines_filter(ctx, y_range); + } + else if (filter == IMB_FILTER_BILINEAR) { + transform_scanlines_filter(ctx, y_range); + } + else if (filter == IMB_FILTER_BICUBIC) { + transform_scanlines_filter(ctx, y_range); + } + }); } }