Blender V4.3
imbuf/intern/transform.cc
Go to the documentation of this file.
1/* SPDX-FileCopyrightText: 2001-2002 NaN Holding BV. All rights reserved.
2 * SPDX-FileCopyrightText: 2024 Blender Authors
3 *
4 * SPDX-License-Identifier: GPL-2.0-or-later */
5
9
10#include <type_traits>
11
13#include "BLI_math_interp.hh"
14#include "BLI_math_matrix.hh"
15#include "BLI_math_vector.h"
16#include "BLI_rect.h"
17#include "BLI_task.hh"
18
19#include "IMB_imbuf.hh"
20#include "IMB_interp.hh"
21
22using blender::float4;
23using blender::uchar4;
24
26
28 const ImBuf *src;
31
32 /* UV coordinates at the destination origin (0,0) in source image space. */
34
35 /* Source UV step delta, when moving along one destination pixel in X axis. */
37
38 /* Source UV step delta, when moving along one destination pixel in Y axis. */
40
41 /* Source corners in destination pixel space, counter-clockwise. */
43
46
47 /* Cropping region in source image pixel space. */
49
50 void init(const float4x4 &transform_matrix, const bool has_source_crop)
51 {
52 start_uv = transform_matrix.location().xy();
53 add_x = transform_matrix.x_axis().xy();
54 add_y = transform_matrix.y_axis().xy();
55 init_destination_region(transform_matrix, has_source_crop);
56 }
57
58 private:
59 void init_destination_region(const float4x4 &transform_matrix, const bool has_source_crop)
60 {
61 if (!has_source_crop) {
64 return;
65 }
66
67 /* Transform the src_crop to the destination buffer with a margin. */
68 const int2 margin(2);
69 rcti rect;
71 float4x4 inverse = math::invert(transform_matrix);
72 const int2 src_coords[4] = {int2(src_crop.xmin, src_crop.ymin),
76 for (int i = 0; i < 4; i++) {
77 int2 src_co = src_coords[i];
78 float3 dst_co = math::transform_point(inverse, float3(src_co.x, src_co.y, 0.0f));
79 src_corners[i] = float2(dst_co.x, dst_co.y);
80
81 BLI_rcti_do_minmax_v(&rect, int2(dst_co) + margin);
82 BLI_rcti_do_minmax_v(&rect, int2(dst_co) - margin);
83 }
84
85 /* Clamp rect to fit inside the image buffer. */
86 rcti dest_rect;
87 BLI_rcti_init(&dest_rect, 0, dst->x, 0, dst->y);
88 BLI_rcti_isect(&rect, &dest_rect, &rect);
91 }
92};
93
94/* Crop uv-coordinates that are outside the user data src_crop rect. */
95static bool should_discard(const TransformContext &ctx, const float2 &uv)
96{
97 return uv.x < ctx.src_crop.xmin || uv.x >= ctx.src_crop.xmax || uv.y < ctx.src_crop.ymin ||
98 uv.y >= ctx.src_crop.ymax;
99}
100
101template<typename T> static T *init_pixel_pointer(const ImBuf *image, int x, int y);
102template<> uchar *init_pixel_pointer(const ImBuf *image, int x, int y)
103{
104 return image->byte_buffer.data + (size_t(y) * image->x + x) * image->channels;
105}
106template<> float *init_pixel_pointer(const ImBuf *image, int x, int y)
107{
108 return image->float_buffer.data + (size_t(y) * image->x + x) * image->channels;
109}
110
111static float wrap_uv(float value, int size)
112{
113 int x = int(floorf(value));
114 if (UNLIKELY(x < 0 || x >= size)) {
115 x %= size;
116 if (x < 0) {
117 x += size;
118 }
119 }
120 return x;
121}
122
123/* Read a pixel from an image buffer, with filtering/wrapping parameters. */
124template<eIMBInterpolationFilterMode Filter, typename T, int NumChannels, bool WrapUV>
125static void sample_image(const ImBuf *source, float u, float v, T *r_sample)
126{
127 if constexpr (WrapUV) {
128 u = wrap_uv(u, source->x);
129 v = wrap_uv(v, source->y);
130 }
131 /* Bilinear/cubic interpolation functions use `floor(uv)` and `floor(uv)+1`
132 * texels. For proper mapping between pixel and texel spaces, need to
133 * subtract 0.5. */
134 if constexpr (Filter != IMB_FILTER_NEAREST) {
135 u -= 0.5f;
136 v -= 0.5f;
137 }
138 if constexpr (Filter == IMB_FILTER_BILINEAR && std::is_same_v<T, float> && NumChannels == 4) {
139 interpolate_bilinear_fl(source, r_sample, u, v);
140 }
141 else if constexpr (Filter == IMB_FILTER_NEAREST && std::is_same_v<T, uchar> && NumChannels == 4)
142 {
143 interpolate_nearest_border_byte(source, r_sample, u, v);
144 }
145 else if constexpr (Filter == IMB_FILTER_BILINEAR && std::is_same_v<T, uchar> && NumChannels == 4)
146 {
147 interpolate_bilinear_byte(source, r_sample, u, v);
148 }
149 else if constexpr (Filter == IMB_FILTER_BILINEAR && std::is_same_v<T, float>) {
150 if constexpr (WrapUV) {
152 r_sample,
153 source->x,
154 source->y,
155 NumChannels,
156 u,
157 v,
158 true,
159 true);
160 }
161 else {
163 source->float_buffer.data, r_sample, source->x, source->y, NumChannels, u, v);
164 }
165 }
166 else if constexpr (Filter == IMB_FILTER_NEAREST && std::is_same_v<T, float>) {
168 source->float_buffer.data, r_sample, source->x, source->y, NumChannels, u, v);
169 }
170 else if constexpr (Filter == IMB_FILTER_CUBIC_BSPLINE && std::is_same_v<T, float>) {
172 source->float_buffer.data, r_sample, source->x, source->y, NumChannels, u, v);
173 }
174 else if constexpr (Filter == IMB_FILTER_CUBIC_BSPLINE && std::is_same_v<T, uchar> &&
175 NumChannels == 4)
176 {
177 interpolate_cubic_bspline_byte(source, r_sample, u, v);
178 }
179 else if constexpr (Filter == IMB_FILTER_CUBIC_MITCHELL && std::is_same_v<T, float>) {
181 source->float_buffer.data, r_sample, source->x, source->y, NumChannels, u, v);
182 }
183 else if constexpr (Filter == IMB_FILTER_CUBIC_MITCHELL && std::is_same_v<T, uchar> &&
184 NumChannels == 4)
185 {
186 interpolate_cubic_mitchell_byte(source, r_sample, u, v);
187 }
188 else {
189 /* Unsupported sampler. */
191 }
192}
193
194static void add_subsample(const float src[4], float dst[4])
195{
196 add_v4_v4(dst, src);
197}
198
199static void add_subsample(const uchar src[4], float dst[4])
200{
201 float premul[4];
203 add_v4_v4(dst, premul);
204}
205
206static void store_premul_float_sample(const float sample[4], float dst[4])
207{
208 copy_v4_v4(dst, sample);
209}
210
211static void store_premul_float_sample(const float sample[4], uchar dst[4])
212{
214}
215
216template<int SrcChannels> static void store_sample(const uchar *sample, uchar *dst)
217{
218 BLI_STATIC_ASSERT(SrcChannels == 4, "Unsigned chars always have 4 channels.");
220}
221
222template<int SrcChannels> static void store_sample(const float *sample, float *dst)
223{
224 if constexpr (SrcChannels == 4) {
225 copy_v4_v4(dst, sample);
226 }
227 else if constexpr (SrcChannels == 3) {
228 copy_v4_fl4(dst, sample[0], sample[1], sample[2], 1.0f);
229 }
230 else if constexpr (SrcChannels == 2) {
231 copy_v4_fl4(dst, sample[0], sample[1], 0.0f, 1.0f);
232 }
233 else if constexpr (SrcChannels == 1) {
234 /* NOTE: single channel sample is stored as grayscale. */
235 copy_v4_fl4(dst, sample[0], sample[0], sample[0], 1.0f);
236 }
237 else {
239 }
240}
241
242/* Process a block of destination image scanlines. */
243template<eIMBInterpolationFilterMode Filter,
244 typename T,
245 int SrcChannels,
246 bool CropSource,
247 bool WrapUV>
248static void process_scanlines(const TransformContext &ctx, IndexRange y_range)
249{
250 if constexpr (Filter == IMB_FILTER_BOX) {
251
252 /* Multiple samples per pixel: accumulate them pre-multiplied,
253 * divide by sample count and write out (un-pre-multiplying if writing out
254 * to byte image).
255 *
256 * Do a box filter: for each destination pixel, accumulate XxY samples from source,
257 * based on scaling factors (length of X/Y pixel steps). Use at least 2 samples
258 * along each direction, so that in case of rotation the image gets
259 * some anti-aliasing. Use at most 100 samples along each direction,
260 * just as some way of clamping possible upper cost. Scaling something down by more
261 * than 100x should rarely if ever happen, worst case they will get some aliasing.
262 */
263 float2 uv_start = ctx.start_uv;
264 int sub_count_x = int(math::clamp(roundf(math::length(ctx.add_x)), 2.0f, 100.0f));
265 int sub_count_y = int(math::clamp(roundf(math::length(ctx.add_y)), 2.0f, 100.0f));
266 const float inv_count = 1.0f / (sub_count_x * sub_count_y);
267 const float2 sub_step_x = ctx.add_x / sub_count_x;
268 const float2 sub_step_y = ctx.add_y / sub_count_y;
269
270 for (int yi : y_range) {
272 float2 uv_row = uv_start + yi * ctx.add_y;
273 for (int xi : ctx.dst_region_x_range) {
274 const float2 uv = uv_row + xi * ctx.add_x;
275 float sample[4] = {};
276
277 for (int sub_y = 0; sub_y < sub_count_y; sub_y++) {
278 for (int sub_x = 0; sub_x < sub_count_x; sub_x++) {
279 float2 delta = (sub_x + 0.5f) * sub_step_x + (sub_y + 0.5f) * sub_step_y;
280 float2 sub_uv = uv + delta;
281 if (!CropSource || !should_discard(ctx, sub_uv)) {
282 T sub_sample[4];
284 T,
285 SrcChannels,
286 WrapUV>(ctx.src, sub_uv.x, sub_uv.y, sub_sample);
287 add_subsample(sub_sample, sample);
288 }
289 }
290 }
291
292 mul_v4_v4fl(sample, sample, inv_count);
294
295 output += 4;
296 }
297 }
298 }
299 else {
300 /* One sample per pixel.
301 * NOTE: sample at pixel center for proper filtering. */
302 float2 uv_start = ctx.start_uv + ctx.add_x * 0.5f + ctx.add_y * 0.5f;
303 for (int yi : y_range) {
305 float2 uv_row = uv_start + yi * ctx.add_y;
306 for (int xi : ctx.dst_region_x_range) {
307 float2 uv = uv_row + xi * ctx.add_x;
308 if (!CropSource || !should_discard(ctx, uv)) {
309 T sample[4];
312 }
313 output += 4;
314 }
315 }
316 }
317}
318
319template<eIMBInterpolationFilterMode Filter, typename T, int SrcChannels>
320static void transform_scanlines(const TransformContext &ctx, IndexRange y_range)
321{
322 switch (ctx.mode) {
325 break;
328 break;
331 break;
332 default:
334 break;
335 }
336}
337
338template<eIMBInterpolationFilterMode Filter>
340{
341 int channels = ctx.src->channels;
342
343 if (ctx.dst->float_buffer.data && ctx.src->float_buffer.data) {
344 /* Float pixels. */
345 if (channels == 4) {
347 }
348 else if (channels == 3) {
350 }
351 else if (channels == 2) {
353 }
354 else if (channels == 1) {
356 }
357 }
358
359 if (ctx.dst->byte_buffer.data && ctx.src->byte_buffer.data) {
360 /* Byte pixels. */
361 if (channels == 4) {
363 }
364 }
365}
366
367static float calc_coverage(float2 pos, int2 ipos, float2 delta, bool is_steep)
368{
369 /* Very approximate: just take difference from coordinate (x or y based on
370 * steepness) to the integer coordinate. Adjust based on directions
371 * of the edges. */
372 float cov;
373 if (is_steep) {
374 cov = fabsf(ipos.x - pos.x);
375 if (delta.y < 0) {
376 cov = 1.0f - cov;
377 }
378 }
379 else {
380 cov = fabsf(ipos.y - pos.y);
381 if (delta.x > 0) {
382 cov = 1.0f - cov;
383 }
384 }
385 cov = math::clamp(cov, 0.0f, 1.0f);
386 /* Resulting coverage is 0.5 .. 1.0 range, since we are only covering
387 * half of the pixels that should be AA'd (the other half is outside the
388 * quad and does not get rasterized). Square the coverage to get
389 * more range, and it looks a bit nicer that way. */
390 cov *= cov;
391 return cov;
392}
393
394static void edge_aa(const TransformContext &ctx)
395{
396 /* Rasterize along outer source edges into the destination image,
397 * reducing alpha based on pixel distance to the edge at each pixel.
398 * This is very approximate and not 100% correct "analytical AA",
399 * but simple to do and better than nothing. */
400 for (int line_idx = 0; line_idx < 4; line_idx++) {
401 float2 ptA = ctx.src_corners[line_idx];
402 float2 ptB = ctx.src_corners[(line_idx + 1) & 3];
403 float2 delta = ptB - ptA;
404 float2 abs_delta = math::abs(delta);
405 float length = math::max(abs_delta.x, abs_delta.y);
406 if (length < 1) {
407 continue;
408 }
409 bool is_steep = length == abs_delta.y;
410
411 /* It is very common to have non-rotated strips; check if edge line is
412 * horizontal or vertical and would not alter the coverage and can
413 * be skipped. */
414 constexpr float NO_ROTATION = 1.0e-6f;
415 constexpr float NO_AA_CONTRIB = 1.0e-2f;
416 if (is_steep) {
417 if ((abs_delta.x < NO_ROTATION) && (fabsf(ptA.x - roundf(ptA.x)) < NO_AA_CONTRIB)) {
418 continue;
419 }
420 }
421 else {
422 if ((abs_delta.y < NO_ROTATION) && (fabsf(ptA.y - roundf(ptA.y)) < NO_AA_CONTRIB)) {
423 continue;
424 }
425 }
426
427 /* DDA line raster: step one pixel along the longer direction. */
428 delta /= length;
429 if (ctx.dst->float_buffer.data != nullptr) {
430 /* Float pixels. */
431 float *dst = ctx.dst->float_buffer.data;
432 for (int i = 0; i < length; i++) {
433 float2 pos = ptA + i * delta;
434 int2 ipos = int2(pos);
435 if (ipos.x >= 0 && ipos.x < ctx.dst->x && ipos.y >= 0 && ipos.y < ctx.dst->y) {
436 float cov = calc_coverage(pos, ipos, delta, is_steep);
437 size_t idx = (size_t(ipos.y) * ctx.dst->x + ipos.x) * 4;
438 dst[idx + 0] *= cov;
439 dst[idx + 1] *= cov;
440 dst[idx + 2] *= cov;
441 dst[idx + 3] *= cov;
442 }
443 }
444 }
445 if (ctx.dst->byte_buffer.data != nullptr) {
446 /* Byte pixels. */
447 uchar *dst = ctx.dst->byte_buffer.data;
448 for (int i = 0; i < length; i++) {
449 float2 pos = ptA + i * delta;
450 int2 ipos = int2(pos);
451 if (ipos.x >= 0 && ipos.x < ctx.dst->x && ipos.y >= 0 && ipos.y < ctx.dst->y) {
452 float cov = calc_coverage(pos, ipos, delta, is_steep);
453 size_t idx = (size_t(ipos.y) * ctx.dst->x + ipos.x) * 4;
454 dst[idx + 3] *= cov;
455 }
456 }
457 }
458 }
459}
460
461} // namespace blender::imbuf::transform
462
463using namespace blender::imbuf::transform;
464using namespace blender;
465
466void IMB_transform(const ImBuf *src,
467 ImBuf *dst,
468 const eIMBTransformMode mode,
470 const float transform_matrix[4][4],
471 const rctf *src_crop)
472{
473 BLI_assert_msg(mode != IMB_TRANSFORM_MODE_CROP_SRC || src_crop != nullptr,
474 "No source crop rect given, but crop source is requested. Or source crop rect "
475 "was given, but crop source was not requested.");
476 BLI_assert_msg(dst->channels == 4, "Destination image must have 4 channels.");
477
479 ctx.src = src;
480 ctx.dst = dst;
481 ctx.mode = mode;
482 bool crop = mode == IMB_TRANSFORM_MODE_CROP_SRC;
483 if (crop) {
484 ctx.src_crop = *src_crop;
485 }
486 ctx.init(blender::float4x4(transform_matrix), crop);
487
489 if (filter == IMB_FILTER_NEAREST) {
490 transform_scanlines_filter<IMB_FILTER_NEAREST>(ctx, y_range);
491 }
492 else if (filter == IMB_FILTER_BILINEAR) {
494 }
495 else if (filter == IMB_FILTER_CUBIC_BSPLINE) {
497 }
498 else if (filter == IMB_FILTER_CUBIC_MITCHELL) {
500 }
501 else if (filter == IMB_FILTER_BOX) {
503 }
504 });
505
506 if (crop && (filter != IMB_FILTER_NEAREST)) {
507 edge_aa(ctx);
508 }
509}
#define BLI_assert_unreachable()
Definition BLI_assert.h:97
#define BLI_STATIC_ASSERT(a, msg)
Definition BLI_assert.h:87
#define BLI_assert_msg(a, msg)
Definition BLI_assert.h:57
MINLINE void straight_uchar_to_premul_float(float result[4], const unsigned char color[4])
MINLINE void premul_float_to_straight_uchar(unsigned char *result, const float color[4])
MINLINE void add_v4_v4(float r[4], const float a[4])
MINLINE void copy_v4_v4(float r[4], const float a[4])
MINLINE void copy_v4_fl4(float v[4], float x, float y, float z, float w)
MINLINE void copy_v4_v4_uchar(unsigned char r[4], const unsigned char a[4])
MINLINE void mul_v4_v4fl(float r[4], const float a[4], float f)
BLI_INLINE int BLI_rcti_size_y(const struct rcti *rct)
Definition BLI_rect.h:193
void BLI_rcti_init_minmax(struct rcti *rect)
Definition rct.c:478
void BLI_rcti_init(struct rcti *rect, int xmin, int xmax, int ymin, int ymax)
Definition rct.c:418
bool BLI_rcti_isect(const struct rcti *src1, const struct rcti *src2, struct rcti *dest)
BLI_INLINE int BLI_rcti_size_x(const struct rcti *rct)
Definition BLI_rect.h:189
void BLI_rcti_do_minmax_v(struct rcti *rect, const int xy[2])
Definition rct.c:490
unsigned char uchar
#define UNLIKELY(x)
eIMBTransformMode
Transform modes to use for IMB_transform function.
Definition IMB_imbuf.hh:676
@ IMB_TRANSFORM_MODE_WRAP_REPEAT
Wrap repeat the source buffer. Only supported in with nearest filtering.
Definition IMB_imbuf.hh:682
@ IMB_TRANSFORM_MODE_REGULAR
Do not crop or repeat.
Definition IMB_imbuf.hh:678
@ IMB_TRANSFORM_MODE_CROP_SRC
Crop the source buffer.
Definition IMB_imbuf.hh:680
eIMBInterpolationFilterMode
Definition IMB_imbuf.hh:288
@ IMB_FILTER_NEAREST
Definition IMB_imbuf.hh:289
@ IMB_FILTER_CUBIC_BSPLINE
Definition IMB_imbuf.hh:291
@ IMB_FILTER_CUBIC_MITCHELL
Definition IMB_imbuf.hh:292
@ IMB_FILTER_BILINEAR
Definition IMB_imbuf.hh:290
@ IMB_FILTER_BOX
Definition IMB_imbuf.hh:293
Group Output data from inside of a node group A color picker Mix two input colors RGB to Convert a color s luminance to a grayscale value Generate a normal vector and a dot product Brightness Control the brightness and contrast of the input color Vector Map input vector components with curves Camera Retrieve information about the camera and how it relates to the current shading point s position Clamp a value between a minimum and a maximum Vector Perform vector math operation Invert Invert a producing a negative Combine Generate a color from its and blue channels(Deprecated)") DefNode(ShaderNode
ATTR_WARN_UNUSED_RESULT const BMVert * v
static DBVT_INLINE btScalar size(const btDbvtVolume &a)
Definition btDbvt.cpp:52
btMatrix3x3 inverse() const
Return the inverse of the matrix.
#define output
SIMD_FORCE_INLINE btScalar length(const btQuaternion &q)
Return the length of a quaternion.
constexpr int64_t first() const
input_tx image(0, GPU_RGBA16F, Qualifier::WRITE, ImageType::FLOAT_2D, "preview_img") .compute_source("compositor_compute_preview.glsl") .do_static_compilation(true)
#define floorf(x)
#define fabsf(x)
draw_view push_constant(Type::INT, "radiance_src") .push_constant(Type capture_info_buf storage_buf(1, Qualifier::READ, "ObjectBounds", "bounds_buf[]") .push_constant(Type draw_view int
void IMB_transform(const ImBuf *src, ImBuf *dst, const eIMBTransformMode mode, const eIMBInterpolationFilterMode filter, const float transform_matrix[4][4], const rctf *src_crop)
Transform source image buffer onto destination image buffer using a transform matrix.
DO_INLINE void filter(lfVector *V, fmatrix3x3 *S)
static void store_sample(const uchar *sample, uchar *dst)
static void sample_image(const ImBuf *source, float u, float v, T *r_sample)
static void add_subsample(const float src[4], float dst[4])
static void edge_aa(const TransformContext &ctx)
static float calc_coverage(float2 pos, int2 ipos, float2 delta, bool is_steep)
static T * init_pixel_pointer(const ImBuf *image, int x, int y)
static void transform_scanlines_filter(const TransformContext &ctx, IndexRange y_range)
static void store_premul_float_sample(const float sample[4], float dst[4])
static void process_scanlines(const TransformContext &ctx, IndexRange y_range)
static bool should_discard(const TransformContext &ctx, const float2 &uv)
static float wrap_uv(float value, int size)
static void transform_scanlines(const TransformContext &ctx, IndexRange y_range)
float4 interpolate_bilinear_fl(const ImBuf *in, float u, float v)
Definition IMB_interp.hh:57
uchar4 interpolate_nearest_border_byte(const ImBuf *in, float u, float v)
Definition IMB_interp.hh:23
uchar4 interpolate_bilinear_byte(const ImBuf *in, float u, float v)
Definition IMB_interp.hh:53
uchar4 interpolate_cubic_mitchell_byte(const ImBuf *in, float u, float v)
uchar4 interpolate_cubic_bspline_byte(const ImBuf *in, float u, float v)
T clamp(const T &a, const T &min, const T &max)
void interpolate_nearest_border_fl(const float *buffer, float *output, int width, int height, int components, float u, float v)
T length(const VecBase< T, Size > &a)
float4 interpolate_bilinear_wrap_fl(const float *buffer, int width, int height, float u, float v)
float4 interpolate_cubic_bspline_fl(const float *buffer, int width, int height, float u, float v)
CartesianBasis invert(const CartesianBasis &basis)
float4 interpolate_bilinear_fl(const float *buffer, int width, int height, float u, float v)
float4 interpolate_cubic_mitchell_fl(const float *buffer, int width, int height, float u, float v)
T max(const T &a, const T &b)
T abs(const T &a)
VecBase< T, 3 > transform_point(const CartesianBasis &basis, const VecBase< T, 3 > &v)
void parallel_for(const IndexRange range, const int64_t grain_size, const Function &function, const TaskSizeHints &size_hints=detail::TaskSizeHints_Static(1))
Definition BLI_task.hh:95
MatBase< float, 4, 4 > float4x4
blender::VecBase< uint8_t, 4 > uchar4
VecBase< float, 4 > float4
VecBase< int32_t, 2 > int2
VecBase< float, 2 > float2
VecBase< float, 3 > float3
ImBufFloatBuffer float_buffer
ImBufByteBuffer byte_buffer
VecBase< T, 2 > xy() const
void init(const float4x4 &transform_matrix, const bool has_source_crop)
float xmax
float xmin
float ymax
float ymin
int ymin
int xmin