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
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) {
271 T *output = init_pixel_pointer<T>(ctx.dst, ctx.dst_region_x_range.first(), yi);
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) {
304 T *output = init_pixel_pointer<T>(ctx.dst, ctx.dst_region_x_range.first(), yi);
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,
469 const eIMBInterpolationFilterMode filter,
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
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.
SIMD_FORCE_INLINE btScalar length() const
Return the length of the vector.
Definition btVector3.h:257
constexpr int64_t first() const
#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.
#define T
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
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