Images: 1,2,3 channel support for transform function.
Added support for 1, 2, 3 float channel source images. Destination images must still be 4 channels.
This commit is contained in:
parent
67b657f07c
commit
723fb16343
|
@ -908,6 +908,25 @@ typedef enum eIMBTransformMode {
|
|||
IMB_TRANSFORM_MODE_WRAP_REPEAT = 2,
|
||||
} eIMBTransformMode;
|
||||
|
||||
/**
|
||||
* \brief Transform source image buffer onto destination image buffer using a transform matrix.
|
||||
*
|
||||
* \param src Image buffer to read from.
|
||||
* \param dst Image buffer to write to. rect or rect_float must already be initialized.
|
||||
* - dst buffer must be a 4 channel buffers.
|
||||
* - 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 transform_matrix Transformation matrix to use.
|
||||
* The given matrix should transform between dst texel space to src texel space.
|
||||
* One unit is one texel.
|
||||
* \param src_crop cropping region how to crop the source buffer. Should only be passed when mode
|
||||
* is set to IMB_TRANSFORM_MODE_CROP_SRC. For any other mode this should be empty.
|
||||
*
|
||||
* During transformation no data/color conversion will happens.
|
||||
* When transforming between float images the number of channels of the source buffer may be
|
||||
* between 1 and 4. When source buffer has one channel the data will be read as a grey scale value.
|
||||
*/
|
||||
void IMB_transform(const struct ImBuf *src,
|
||||
struct ImBuf *dst,
|
||||
const eIMBTransformMode mode,
|
||||
|
|
|
@ -21,6 +21,7 @@
|
|||
* \ingroup imbuf
|
||||
*/
|
||||
|
||||
#include <array>
|
||||
#include <type_traits>
|
||||
|
||||
#include "BLI_math.h"
|
||||
|
@ -32,13 +33,33 @@
|
|||
namespace blender::imbuf::transform {
|
||||
|
||||
struct TransformUserData {
|
||||
/** \brief Source image buffer to read from. */
|
||||
const ImBuf *src;
|
||||
/** \brief Destination image buffer to write to. */
|
||||
ImBuf *dst;
|
||||
/** \brief UV coordinates at the origin (0,0) in source image space. */
|
||||
float start_uv[2];
|
||||
|
||||
/**
|
||||
* \brief delta UV coordinates along the source image buffer, when moving a single texel in the X
|
||||
* axis of the dst image buffer.
|
||||
*/
|
||||
float add_x[2];
|
||||
|
||||
/**
|
||||
* \brief delta UV coordinate along the source image buffer, when moving a single texel in the Y
|
||||
* axes of the dst image buffer.
|
||||
*/
|
||||
float add_y[2];
|
||||
|
||||
/**
|
||||
* \brief Cropping region in source image texel space.
|
||||
*/
|
||||
rctf src_crop;
|
||||
|
||||
/**
|
||||
* \brief Initialize the start_uv, add_x and add_y fields based on the given transform matrix.
|
||||
*/
|
||||
void init(const float transform_matrix[4][4])
|
||||
{
|
||||
init_start_uv(transform_matrix);
|
||||
|
@ -85,24 +106,369 @@ struct TransformUserData {
|
|||
}
|
||||
};
|
||||
|
||||
template<eIMBTransformMode Mode, InterpolationColorFunction ColorInterpolation, int ChannelLen = 4>
|
||||
class ScanlineProcessor {
|
||||
private:
|
||||
void pixel_from_buffer(const struct ImBuf *ibuf, unsigned char **outI, float **outF, int y) const
|
||||
/**
|
||||
* \brief Base class for source discarding.
|
||||
*
|
||||
* The class decides if a specific uv coordinate from the source buffer should be ignored.
|
||||
* This is used to mix multiple images over a single output buffer. Discarded pixels will
|
||||
* not change the output buffer.
|
||||
*/
|
||||
class BaseDiscard {
|
||||
public:
|
||||
virtual ~BaseDiscard() = default;
|
||||
|
||||
/**
|
||||
* \brief Should the source pixel at the given uv coordinate be discarded.
|
||||
*/
|
||||
virtual bool should_discard(const TransformUserData &user_data, const float uv[2]) = 0;
|
||||
};
|
||||
|
||||
/**
|
||||
* \brief Crop uv-coordinates that are outside the user data src_crop rect.
|
||||
*/
|
||||
class CropSource : public BaseDiscard {
|
||||
public:
|
||||
/**
|
||||
* \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.
|
||||
*/
|
||||
bool should_discard(const TransformUserData &user_data, const float uv[2]) override
|
||||
{
|
||||
const size_t offset = ((size_t)ibuf->x) * y * ChannelLen;
|
||||
return uv[0] < user_data.src_crop.xmin || uv[0] >= user_data.src_crop.xmax ||
|
||||
uv[1] < user_data.src_crop.ymin || uv[1] >= user_data.src_crop.ymax;
|
||||
}
|
||||
};
|
||||
|
||||
if (ibuf->rect) {
|
||||
*outI = (unsigned char *)ibuf->rect + offset;
|
||||
/**
|
||||
* \brief Discard that does not discard anything.
|
||||
*/
|
||||
class NoDiscard : public BaseDiscard {
|
||||
public:
|
||||
/**
|
||||
* \brief Should the source pixel at the given uv coordinate be discarded.
|
||||
*
|
||||
* Will never discard any pixels.
|
||||
*/
|
||||
bool should_discard(const TransformUserData &UNUSED(user_data),
|
||||
const float UNUSED(uv[2])) override
|
||||
{
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* \brief Pointer to a texel to write to in serial.
|
||||
*/
|
||||
template<
|
||||
/**
|
||||
* \brief Kind of buffer.
|
||||
* Possible options: float, unsigned char.
|
||||
*/
|
||||
typename StorageType = float,
|
||||
|
||||
/**
|
||||
* \brief Number of channels of a single pixel.
|
||||
*/
|
||||
int NumChannels = 4>
|
||||
class TexelPointer {
|
||||
public:
|
||||
static const int ChannelLen = NumChannels;
|
||||
|
||||
private:
|
||||
StorageType *pointer;
|
||||
|
||||
public:
|
||||
void init_pixel_pointer(const ImBuf *image_buffer, int x, int y)
|
||||
{
|
||||
const size_t offset = (y * (size_t)image_buffer->x + x) * NumChannels;
|
||||
|
||||
if constexpr (std::is_same_v<StorageType, float>) {
|
||||
pointer = image_buffer->rect_float + offset;
|
||||
}
|
||||
|
||||
if (ibuf->rect_float) {
|
||||
*outF = ibuf->rect_float + offset;
|
||||
else if constexpr (std::is_same_v<StorageType, unsigned char>) {
|
||||
pointer = const_cast<unsigned char *>(
|
||||
static_cast<const unsigned char *>(static_cast<const void *>(image_buffer->rect)) +
|
||||
offset);
|
||||
}
|
||||
else {
|
||||
pointer = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* \brief Get pointer to the current texel to write to.
|
||||
*/
|
||||
StorageType *get_pointer()
|
||||
{
|
||||
return pointer;
|
||||
}
|
||||
|
||||
void increase_pixel_pointer()
|
||||
{
|
||||
pointer += NumChannels;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* \brief Wrapping mode for the uv coordinates.
|
||||
*
|
||||
* Subclasses have the ability to change the UV coordinates when sampling the source buffer.
|
||||
*/
|
||||
class BaseUVWrapping {
|
||||
public:
|
||||
/**
|
||||
* \brief modify the given u coordinate.
|
||||
*/
|
||||
virtual float modify_u(const ImBuf *source_buffer, float u) = 0;
|
||||
|
||||
/**
|
||||
* \brief modify the given v coordinate.
|
||||
*/
|
||||
virtual float modify_v(const ImBuf *source_buffer, float v) = 0;
|
||||
};
|
||||
|
||||
/**
|
||||
* \brief UVWrapping method that does not modify the UV coordinates.
|
||||
*/
|
||||
class PassThroughUV : public BaseUVWrapping {
|
||||
public:
|
||||
float modify_u(const ImBuf *UNUSED(source_buffer), float u) override
|
||||
{
|
||||
return u;
|
||||
}
|
||||
|
||||
float modify_v(const ImBuf *UNUSED(source_buffer), float v) override
|
||||
{
|
||||
return v;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* \brief UVWrapping method that wrap repeats the UV coordinates.
|
||||
*/
|
||||
class WrapRepeatUV : public BaseUVWrapping {
|
||||
public:
|
||||
float modify_u(const ImBuf *source_buffer, float u) override
|
||||
|
||||
{
|
||||
int x = (int)floor(u);
|
||||
x = x % source_buffer->x;
|
||||
if (x < 0) {
|
||||
x += source_buffer->x;
|
||||
}
|
||||
return x;
|
||||
}
|
||||
|
||||
float modify_v(const ImBuf *source_buffer, float v) override
|
||||
{
|
||||
int y = (int)floor(v);
|
||||
y = y % source_buffer->y;
|
||||
if (y < 0) {
|
||||
y += source_buffer->y;
|
||||
}
|
||||
return y;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* \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 texel channel (unsigned char 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 Wrapping method to perform
|
||||
*
|
||||
* Should be a subclass of BaseUVWrapper
|
||||
*/
|
||||
typename UVWrapping>
|
||||
class Sampler {
|
||||
UVWrapping uv_wrapper;
|
||||
|
||||
public:
|
||||
using ChannelType = StorageType;
|
||||
static const int ChannelLen = NumChannels;
|
||||
using SampleType = std::array<StorageType, NumChannels>;
|
||||
|
||||
void sample(const ImBuf *source, const float u, const float v, SampleType &r_sample)
|
||||
{
|
||||
const float wrapped_u = uv_wrapper.modify_u(source, u);
|
||||
const float wrapped_v = uv_wrapper.modify_v(source, v);
|
||||
|
||||
if constexpr (Filter == IMB_FILTER_BILINEAR && std::is_same_v<StorageType, float> &&
|
||||
NumChannels == 4) {
|
||||
bilinear_interpolation_color_fl(source, nullptr, r_sample.begin(), wrapped_u, wrapped_v);
|
||||
}
|
||||
else if constexpr (Filter == IMB_FILTER_NEAREST &&
|
||||
std::is_same_v<StorageType, unsigned char> && NumChannels == 4) {
|
||||
nearest_interpolation_color_char(source, r_sample.begin(), nullptr, wrapped_u, wrapped_v);
|
||||
}
|
||||
else if constexpr (Filter == IMB_FILTER_BILINEAR &&
|
||||
std::is_same_v<StorageType, unsigned char> && NumChannels == 4) {
|
||||
bilinear_interpolation_color_char(source, r_sample.begin(), nullptr, wrapped_u, wrapped_v);
|
||||
}
|
||||
else if constexpr (Filter == IMB_FILTER_BILINEAR && std::is_same_v<StorageType, float>) {
|
||||
if constexpr (std::is_same_v<UVWrapping, WrapRepeatUV>) {
|
||||
BLI_bilinear_interpolation_wrap_fl(source->rect_float,
|
||||
r_sample.begin(),
|
||||
source->x,
|
||||
source->y,
|
||||
NumChannels,
|
||||
u,
|
||||
v,
|
||||
true,
|
||||
true);
|
||||
}
|
||||
else {
|
||||
BLI_bilinear_interpolation_fl(source->rect_float,
|
||||
r_sample.begin(),
|
||||
source->x,
|
||||
source->y,
|
||||
NumChannels,
|
||||
wrapped_u,
|
||||
wrapped_v);
|
||||
}
|
||||
}
|
||||
else if constexpr (Filter == IMB_FILTER_NEAREST && std::is_same_v<StorageType, float>) {
|
||||
sample_nearest_float(source, wrapped_u, wrapped_v, r_sample);
|
||||
}
|
||||
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<StorageType, float>);
|
||||
|
||||
/* 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->rect_float + offset;
|
||||
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 TexelPointer.
|
||||
* It supports:
|
||||
* - 4 channel unsigned char -> 4 channel unsigned char.
|
||||
* - 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<typename StorageType, int SourceNumChannels, int DestinationNumChannels>
|
||||
class ChannelConverter {
|
||||
public:
|
||||
using SampleType = std::array<StorageType, SourceNumChannels>;
|
||||
using TexelType = TexelPointer<StorageType, DestinationNumChannels>;
|
||||
|
||||
/**
|
||||
* \brief Convert the number of channels of the given sample to match the texel pointer and store
|
||||
* it at the location the texel_pointer points at.
|
||||
*/
|
||||
void convert_and_store(const SampleType &sample, TexelType &texel_pointer)
|
||||
{
|
||||
if constexpr (std::is_same_v<StorageType, unsigned char>) {
|
||||
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(texel_pointer.get_pointer(), sample.begin());
|
||||
}
|
||||
else if constexpr (std::is_same_v<StorageType, float> && SourceNumChannels == 4 &&
|
||||
DestinationNumChannels == 4) {
|
||||
copy_v4_v4(texel_pointer.get_pointer(), sample.begin());
|
||||
}
|
||||
else if constexpr (std::is_same_v<StorageType, float> && SourceNumChannels == 3 &&
|
||||
DestinationNumChannels == 4) {
|
||||
copy_v4_fl4(texel_pointer.get_pointer(), sample[0], sample[1], sample[2], 1.0f);
|
||||
}
|
||||
else if constexpr (std::is_same_v<StorageType, float> && SourceNumChannels == 2 &&
|
||||
DestinationNumChannels == 4) {
|
||||
copy_v4_fl4(texel_pointer.get_pointer(), sample[0], sample[1], 0.0f, 1.0f);
|
||||
}
|
||||
else if constexpr (std::is_same_v<StorageType, float> && SourceNumChannels == 1 &&
|
||||
DestinationNumChannels == 4) {
|
||||
copy_v4_fl4(texel_pointer.get_pointer(), sample[0], sample[0], sample[0], 1.0f);
|
||||
}
|
||||
else {
|
||||
BLI_assert_unreachable();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* \brief Processor for a scanline.
|
||||
*/
|
||||
template<
|
||||
/**
|
||||
* \brief Discard function to use.
|
||||
*
|
||||
* \attention Should be a subclass of BaseDiscard.
|
||||
*/
|
||||
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 TexelPointer
|
||||
*/
|
||||
typename OutputTexelPointer>
|
||||
class ScanlineProcessor {
|
||||
Discard discarder;
|
||||
OutputTexelPointer output;
|
||||
Sampler sampler;
|
||||
|
||||
/**
|
||||
* \brief Channels sizzling logic to convert between the input image buffer and the output image
|
||||
* buffer.
|
||||
*/
|
||||
ChannelConverter<typename Sampler::ChannelType,
|
||||
Sampler::ChannelLen,
|
||||
OutputTexelPointer::ChannelLen>
|
||||
channel_converter;
|
||||
|
||||
public:
|
||||
/**
|
||||
* \brief Inner loop of the transformations, processing a full scanline.
|
||||
*/
|
||||
void process(const TransformUserData *user_data, int scanline)
|
||||
{
|
||||
const int width = user_data->dst->x;
|
||||
|
@ -110,31 +476,23 @@ class ScanlineProcessor {
|
|||
float uv[2];
|
||||
madd_v2_v2v2fl(uv, user_data->start_uv, user_data->add_y, scanline);
|
||||
|
||||
unsigned char *outI = nullptr;
|
||||
float *outF = nullptr;
|
||||
pixel_from_buffer(user_data->dst, &outI, &outF, scanline);
|
||||
|
||||
output.init_pixel_pointer(user_data->dst, 0, scanline);
|
||||
for (int xi = 0; xi < width; xi++) {
|
||||
if constexpr (Mode == IMB_TRANSFORM_MODE_CROP_SRC) {
|
||||
if (uv[0] >= user_data->src_crop.xmin && uv[0] < user_data->src_crop.xmax &&
|
||||
uv[1] >= user_data->src_crop.ymin && uv[1] < user_data->src_crop.ymax) {
|
||||
ColorInterpolation(user_data->src, outI, outF, uv[0], uv[1]);
|
||||
}
|
||||
}
|
||||
else {
|
||||
ColorInterpolation(user_data->src, outI, outF, uv[0], uv[1]);
|
||||
if (!discarder.should_discard(*user_data, uv)) {
|
||||
typename Sampler::SampleType sample;
|
||||
sampler.sample(user_data->src, uv[0], uv[1], sample);
|
||||
channel_converter.convert_and_store(sample, output);
|
||||
}
|
||||
|
||||
add_v2_v2(uv, user_data->add_x);
|
||||
if (outI) {
|
||||
outI += ChannelLen;
|
||||
}
|
||||
if (outF) {
|
||||
outF += ChannelLen;
|
||||
}
|
||||
output.increase_pixel_pointer();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* \brief callback function for threaded transformation.
|
||||
*/
|
||||
template<typename Processor> void transform_scanline_function(void *custom_data, int scanline)
|
||||
{
|
||||
const TransformUserData *user_data = static_cast<const TransformUserData *>(custom_data);
|
||||
|
@ -142,20 +500,29 @@ template<typename Processor> void transform_scanline_function(void *custom_data,
|
|||
processor.process(user_data, scanline);
|
||||
}
|
||||
|
||||
template<InterpolationColorFunction DefaultFunction, InterpolationColorFunction WrapRepeatFunction>
|
||||
template<eIMBInterpolationFilterMode Filter,
|
||||
typename StorageType,
|
||||
int SourceNumChannels,
|
||||
int DestinationNumChannels>
|
||||
ScanlineThreadFunc get_scanline_function(const eIMBTransformMode mode)
|
||||
|
||||
{
|
||||
switch (mode) {
|
||||
case IMB_TRANSFORM_MODE_REGULAR:
|
||||
return transform_scanline_function<
|
||||
ScanlineProcessor<IMB_TRANSFORM_MODE_REGULAR, DefaultFunction>>;
|
||||
ScanlineProcessor<NoDiscard,
|
||||
Sampler<Filter, StorageType, SourceNumChannels, PassThroughUV>,
|
||||
TexelPointer<StorageType, DestinationNumChannels>>>;
|
||||
case IMB_TRANSFORM_MODE_CROP_SRC:
|
||||
return transform_scanline_function<
|
||||
ScanlineProcessor<IMB_TRANSFORM_MODE_CROP_SRC, DefaultFunction>>;
|
||||
ScanlineProcessor<CropSource,
|
||||
Sampler<Filter, StorageType, SourceNumChannels, PassThroughUV>,
|
||||
TexelPointer<StorageType, DestinationNumChannels>>>;
|
||||
case IMB_TRANSFORM_MODE_WRAP_REPEAT:
|
||||
return transform_scanline_function<
|
||||
ScanlineProcessor<IMB_TRANSFORM_MODE_WRAP_REPEAT, WrapRepeatFunction>>;
|
||||
ScanlineProcessor<NoDiscard,
|
||||
Sampler<Filter, StorageType, SourceNumChannels, WrapRepeatUV>,
|
||||
TexelPointer<StorageType, DestinationNumChannels>>>;
|
||||
}
|
||||
|
||||
BLI_assert_unreachable();
|
||||
|
@ -163,23 +530,38 @@ ScanlineThreadFunc get_scanline_function(const eIMBTransformMode mode)
|
|||
}
|
||||
|
||||
template<eIMBInterpolationFilterMode Filter>
|
||||
static void transform(TransformUserData *user_data, const eIMBTransformMode mode)
|
||||
ScanlineThreadFunc get_scanline_function(const TransformUserData *user_data,
|
||||
const eIMBTransformMode mode)
|
||||
{
|
||||
const ImBuf *src = user_data->src;
|
||||
const ImBuf *dst = user_data->dst;
|
||||
|
||||
if (src->channels == 4 && dst->channels == 4) {
|
||||
return get_scanline_function<Filter, float, 4, 4>(mode);
|
||||
}
|
||||
if (src->channels == 3 && dst->channels == 4) {
|
||||
return get_scanline_function<Filter, float, 3, 4>(mode);
|
||||
}
|
||||
if (src->channels == 2 && dst->channels == 4) {
|
||||
return get_scanline_function<Filter, float, 2, 4>(mode);
|
||||
}
|
||||
if (src->channels == 1 && dst->channels == 4) {
|
||||
return get_scanline_function<Filter, float, 1, 4>(mode);
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
template<eIMBInterpolationFilterMode Filter>
|
||||
static void transform_threaded(TransformUserData *user_data, const eIMBTransformMode mode)
|
||||
{
|
||||
ScanlineThreadFunc scanline_func = nullptr;
|
||||
|
||||
if (user_data->dst->rect_float) {
|
||||
constexpr InterpolationColorFunction interpolation_function =
|
||||
Filter == IMB_FILTER_NEAREST ? nearest_interpolation_color_fl :
|
||||
bilinear_interpolation_color_fl;
|
||||
scanline_func =
|
||||
get_scanline_function<interpolation_function, nearest_interpolation_color_wrap>(mode);
|
||||
if (user_data->dst->rect_float && user_data->src->rect_float) {
|
||||
scanline_func = get_scanline_function<Filter>(user_data, mode);
|
||||
}
|
||||
else if (user_data->dst->rect) {
|
||||
constexpr InterpolationColorFunction interpolation_function =
|
||||
Filter == IMB_FILTER_NEAREST ? nearest_interpolation_color_char :
|
||||
bilinear_interpolation_color_char;
|
||||
scanline_func =
|
||||
get_scanline_function<interpolation_function, nearest_interpolation_color_wrap>(mode);
|
||||
else if (user_data->dst->rect && user_data->src->rect) {
|
||||
/* Number of channels is always 4 when using unsigned char buffers (sRGB + straight alpha). */
|
||||
scanline_func = get_scanline_function<Filter, unsigned char, 4, 4>(mode);
|
||||
}
|
||||
|
||||
if (scanline_func != nullptr) {
|
||||
|
@ -213,10 +595,10 @@ void IMB_transform(const struct ImBuf *src,
|
|||
user_data.init(transform_matrix);
|
||||
|
||||
if (filter == IMB_FILTER_NEAREST) {
|
||||
transform<IMB_FILTER_NEAREST>(&user_data, mode);
|
||||
transform_threaded<IMB_FILTER_NEAREST>(&user_data, mode);
|
||||
}
|
||||
else {
|
||||
transform<IMB_FILTER_BILINEAR>(&user_data, mode);
|
||||
transform_threaded<IMB_FILTER_BILINEAR>(&user_data, mode);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue