Blender V5.0
IMB_scaling_test.cc
Go to the documentation of this file.
1/* SPDX-FileCopyrightText: 2024 Blender Authors
2 *
3 * SPDX-License-Identifier: GPL-2.0-or-later */
4
5#include "testing/testing.h"
6
8#include "IMB_imbuf.hh"
9
11
13{
14 ImBuf *img = IMB_allocImBuf(6, 2, 32, IB_byte_data);
15 uchar4 *col = reinterpret_cast<uchar4 *>(img->byte_buffer.data);
16
17 /* Source pixels are spelled out in 2x2 blocks below:
18 * nearest filter results in corner pixel from each block, bilinear
19 * is average of each block. */
20 col[0] = uchar4(0, 0, 0, 255);
21 col[1] = uchar4(255, 0, 0, 255);
22 col[6] = uchar4(255, 255, 0, 255);
23 col[7] = uchar4(255, 255, 255, 255);
24
25 col[2] = uchar4(133, 55, 31, 13);
26 col[3] = uchar4(133, 55, 31, 15);
27 col[8] = uchar4(133, 55, 31, 17);
28 col[9] = uchar4(133, 55, 31, 19);
29
30 col[4] = uchar4(50, 200, 0, 255);
31 col[5] = uchar4(55, 0, 32, 254);
32 col[10] = uchar4(56, 0, 64, 253);
33 col[11] = uchar4(57, 0, 96, 252);
34
35 return img;
36}
37
38static ImBuf *create_6x2_test_image_fl(int channels)
39{
40 ImBuf *img = IMB_allocImBuf(6, 2, 32, IB_float_data);
41 img->channels = channels;
42 float *col = img->float_buffer.data;
43
44 for (int y = 0; y < img->y; y++) {
45 for (int x = 0; x < img->x; x++) {
46 for (int ch = 0; ch < channels; ch++) {
47 *col = x * 1.25f + y * 0.5f + ch * 0.125f;
48 col++;
49 }
50 }
51 }
52 return img;
53}
54
55static ImBuf *scale_2x_smaller(bool nearest, bool threaded, int float_channels = 0)
56{
57 ImBuf *img = float_channels > 0 ? create_6x2_test_image_fl(float_channels) :
59 int ww = 3, hh = 1;
60 if (threaded) {
61 IMB_scale(img, ww, hh, IMBScaleFilter::Bilinear, true);
62 }
63 else if (nearest) {
64 IMB_scale(img, ww, hh, IMBScaleFilter::Nearest, false);
65 }
66 else {
67 IMB_scale(img, ww, hh, IMBScaleFilter::Box, false);
68 }
69 return img;
70}
71
72static ImBuf *scale_to_1x1(bool nearest, bool threaded, int float_channels = 0)
73{
74 ImBuf *img = float_channels > 0 ? create_6x2_test_image_fl(float_channels) :
76 int ww = 1, hh = 1;
77 if (threaded) {
78 IMB_scale(img, ww, hh, IMBScaleFilter::Bilinear, true);
79 }
80 else if (nearest) {
81 IMB_scale(img, ww, hh, IMBScaleFilter::Nearest, false);
82 }
83 else {
84 IMB_scale(img, ww, hh, IMBScaleFilter::Box, false);
85 }
86 return img;
87}
88
89static ImBuf *scale_fractional_larger(bool nearest, bool threaded, int float_channels = 0)
90{
91 ImBuf *img = float_channels > 0 ? create_6x2_test_image_fl(float_channels) :
93 int ww = 9, hh = 7;
94 if (threaded) {
95 IMB_scale(img, ww, hh, IMBScaleFilter::Bilinear, true);
96 }
97 else if (nearest) {
98 IMB_scale(img, ww, hh, IMBScaleFilter::Nearest, false);
99 }
100 else {
101 IMB_scale(img, ww, hh, IMBScaleFilter::Box, false);
102 }
103 return img;
104}
105
106TEST(imbuf_scaling, nearest_2x_smaller)
107{
108 ImBuf *res = scale_2x_smaller(true, false);
109 const uchar4 *got = reinterpret_cast<uchar4 *>(res->byte_buffer.data);
110 EXPECT_EQ(uint4(got[0]), uint4(0, 0, 0, 255));
111 EXPECT_EQ(uint4(got[1]), uint4(133, 55, 31, 13));
112 EXPECT_EQ(uint4(got[2]), uint4(50, 200, 0, 255));
113 IMB_freeImBuf(res);
114}
115
116TEST(imbuf_scaling, threaded_2x_smaller)
117{
118 ImBuf *res = scale_2x_smaller(false, true);
119 const uchar4 *got = reinterpret_cast<uchar4 *>(res->byte_buffer.data);
120 EXPECT_EQ(uint4(got[0]), uint4(191, 128, 64, 255));
121 EXPECT_EQ(uint4(got[1]), uint4(133, 55, 31, 16));
122 EXPECT_EQ(uint4(got[2]), uint4(55, 50, 48, 254));
123 IMB_freeImBuf(res);
124}
125
126TEST(imbuf_scaling, bilinear_2x_smaller)
127{
128 ImBuf *res = scale_2x_smaller(false, false);
129 const uchar4 *got = reinterpret_cast<uchar4 *>(res->byte_buffer.data);
130 /* NOTE: #IMB_transform results in (191, 128, 64, 255), <same>,
131 * (55, 50, 48, 254) i.e. different rounding. */
132 EXPECT_EQ(uint4(got[0]), uint4(191, 127, 63, 255));
133 EXPECT_EQ(uint4(got[1]), uint4(133, 55, 31, 16));
134 EXPECT_EQ(uint4(got[2]), uint4(55, 50, 48, 253));
135 IMB_freeImBuf(res);
136}
137
138TEST(imbuf_scaling, nearest_to_1x1)
139{
140 ImBuf *res = scale_to_1x1(true, false);
141 const uchar4 *got = reinterpret_cast<uchar4 *>(res->byte_buffer.data);
142 EXPECT_EQ(uint4(got[0]), uint4(0, 0, 0, 255));
143 IMB_freeImBuf(res);
144}
145
146TEST(imbuf_scaling, threaded_to_1x1)
147{
148 ImBuf *res = scale_to_1x1(false, true);
149 const uchar4 *got = reinterpret_cast<uchar4 *>(res->byte_buffer.data);
150 EXPECT_EQ(uint4(got[0]), uint4(133, 55, 31, 16));
151 IMB_freeImBuf(res);
152}
153
154TEST(imbuf_scaling, bilinear_to_1x1)
155{
156 ImBuf *res = scale_to_1x1(false, false);
157 const uchar4 *got = reinterpret_cast<uchar4 *>(res->byte_buffer.data);
158 EXPECT_EQ(uint4(got[0]), uint4(126, 78, 47, 174));
159 IMB_freeImBuf(res);
160}
161
162TEST(imbuf_scaling, nearest_fractional_larger)
163{
164 ImBuf *res = scale_fractional_larger(true, false);
165 const uchar4 *got = reinterpret_cast<uchar4 *>(res->byte_buffer.data);
166 EXPECT_EQ(uint4(got[0 + 0 * res->x]), uint4(0, 0, 0, 255));
167 EXPECT_EQ(uint4(got[1 + 0 * res->x]), uint4(0, 0, 0, 255));
168 EXPECT_EQ(uint4(got[7 + 0 * res->x]), uint4(50, 200, 0, 255));
169 EXPECT_EQ(uint4(got[2 + 2 * res->x]), uint4(255, 0, 0, 255));
170 EXPECT_EQ(uint4(got[3 + 2 * res->x]), uint4(133, 55, 31, 13));
171 EXPECT_EQ(uint4(got[8 + 6 * res->x]), uint4(57, 0, 96, 252));
172 IMB_freeImBuf(res);
173}
174
175TEST(imbuf_scaling, bilinear_fractional_larger)
176{
177 ImBuf *res = scale_fractional_larger(false, false);
178 const uchar4 *got = reinterpret_cast<uchar4 *>(res->byte_buffer.data);
179 EXPECT_EQ(uint4(got[0 + 0 * res->x]), uint4(0, 0, 0, 255));
180 EXPECT_EQ(uint4(got[1 + 0 * res->x]), uint4(127, 0, 0, 255));
181 EXPECT_EQ(uint4(got[7 + 0 * res->x]), uint4(52, 100, 16, 255));
182 EXPECT_EQ(uint4(got[2 + 2 * res->x]), uint4(235, 55, 51, 215));
183 EXPECT_EQ(uint4(got[3 + 2 * res->x]), uint4(153, 55, 35, 54));
184 EXPECT_EQ(uint4(got[8 + 5 * res->x]), uint4(57, 0, 91, 252));
185 EXPECT_EQ(uint4(got[0 + 6 * res->x]), uint4(164, 164, 0, 255));
186 EXPECT_EQ(uint4(got[7 + 6 * res->x]), uint4(55, 36, 57, 254));
187 EXPECT_EQ(uint4(got[8 + 6 * res->x]), uint4(56, 0, 73, 253));
188 IMB_freeImBuf(res);
189}
190
191static constexpr float EPS = 0.0001f;
192
193TEST(imbuf_scaling, nearest_2x_smaller_fl1)
194{
195 ImBuf *res = scale_2x_smaller(true, false, 1);
196 const float *got = res->float_buffer.data;
197 EXPECT_NEAR(got[0], 0.0f, EPS);
198 EXPECT_NEAR(got[1], 2.5f, EPS);
199 EXPECT_NEAR(got[2], 5.0f, EPS);
200 IMB_freeImBuf(res);
201}
202
203TEST(imbuf_scaling, nearest_2x_smaller_fl2)
204{
205 ImBuf *res = scale_2x_smaller(true, false, 2);
206 const float2 *got = reinterpret_cast<float2 *>(res->float_buffer.data);
207 EXPECT_V2_NEAR(got[0], float2(0.0f, 0.125f), EPS);
208 EXPECT_V2_NEAR(got[1], float2(2.5f, 2.625f), EPS);
209 EXPECT_V2_NEAR(got[2], float2(5.0f, 5.125f), EPS);
210 IMB_freeImBuf(res);
211}
212
213TEST(imbuf_scaling, nearest_2x_smaller_fl3)
214{
215 ImBuf *res = scale_2x_smaller(true, false, 3);
216 const float3 *got = reinterpret_cast<float3 *>(res->float_buffer.data);
217 EXPECT_V3_NEAR(got[0], float3(0.0f, 0.125f, 0.25f), EPS);
218 EXPECT_V3_NEAR(got[1], float3(2.5f, 2.625f, 2.75f), EPS);
219 EXPECT_V3_NEAR(got[2], float3(5.0f, 5.125f, 5.25f), EPS);
220 IMB_freeImBuf(res);
221}
222
223TEST(imbuf_scaling, nearest_2x_smaller_fl4)
224{
225 ImBuf *res = scale_2x_smaller(true, false, 4);
226 const float4 *got = reinterpret_cast<float4 *>(res->float_buffer.data);
227 EXPECT_V4_NEAR(got[0], float4(0.0f, 0.125f, 0.25f, 0.375f), EPS);
228 EXPECT_V4_NEAR(got[1], float4(2.5f, 2.625f, 2.75f, 2.875f), EPS);
229 EXPECT_V4_NEAR(got[2], float4(5.0f, 5.125f, 5.25f, 5.375f), EPS);
230 IMB_freeImBuf(res);
231}
232
233TEST(imbuf_scaling, nearest_to_1x1_fl3)
234{
235 ImBuf *res = scale_to_1x1(true, false, 3);
236 const float3 *got = reinterpret_cast<float3 *>(res->float_buffer.data);
237 EXPECT_V3_NEAR(got[0], float3(0, 0.125f, 0.25f), EPS);
238 IMB_freeImBuf(res);
239}
240
241TEST(imbuf_scaling, threaded_to_1x1_fl3)
242{
243 ImBuf *res = scale_to_1x1(false, true, 3);
244 const float3 *got = reinterpret_cast<float3 *>(res->float_buffer.data);
245 EXPECT_V3_NEAR(got[0], float3(3.375f, 3.5f, 3.625f), EPS);
246 IMB_freeImBuf(res);
247}
248
249TEST(imbuf_scaling, bilinear_to_1x1_fl3)
250{
251 ImBuf *res = scale_to_1x1(false, false, 3);
252 const float3 *got = reinterpret_cast<float3 *>(res->float_buffer.data);
253 EXPECT_V3_NEAR(got[0], float3(3.36853f, 3.49353f, 3.61853f), EPS);
254 IMB_freeImBuf(res);
255}
256
257TEST(imbuf_scaling, bilinear_2x_smaller_fl3)
258{
259 ImBuf *res = scale_2x_smaller(false, false, 3);
260 const float3 *got = reinterpret_cast<float3 *>(res->float_buffer.data);
261 EXPECT_V3_NEAR(got[0], float3(0.87270f, 0.99770f, 1.12270f), EPS);
262 EXPECT_V3_NEAR(got[1], float3(3.36853f, 3.49353f, 3.61853f), EPS);
263 EXPECT_V3_NEAR(got[2], float3(5.86435f, 5.98935f, 6.11435f), EPS);
264 IMB_freeImBuf(res);
265}
266
267TEST(imbuf_scaling, bilinear_2x_smaller_fl4)
268{
269 ImBuf *res = scale_2x_smaller(false, false, 4);
270 const float4 *got = reinterpret_cast<float4 *>(res->float_buffer.data);
271 EXPECT_V4_NEAR(got[0], float4(0.87270f, 0.99770f, 1.12270f, 1.24770f), EPS);
272 EXPECT_V4_NEAR(got[1], float4(3.36853f, 3.49353f, 3.61853f, 3.74353f), EPS);
273 EXPECT_V4_NEAR(got[2], float4(5.86435f, 5.98935f, 6.11435f, 6.23935f), EPS);
274 IMB_freeImBuf(res);
275}
276
277TEST(imbuf_scaling, threaded_2x_smaller_fl3)
278{
279 ImBuf *res = scale_2x_smaller(false, true, 3);
280 const float3 *got = reinterpret_cast<float3 *>(res->float_buffer.data);
281 EXPECT_V3_NEAR(got[0], float3(0.875f, 1.0f, 1.125f), EPS);
282 EXPECT_V3_NEAR(got[1], float3(3.375f, 3.5f, 3.625f), EPS);
283 EXPECT_V3_NEAR(got[2], float3(5.875f, 6.0f, 6.125f), EPS);
284 IMB_freeImBuf(res);
285}
286
287TEST(imbuf_scaling, threaded_2x_smaller_fl4)
288{
289 ImBuf *res = scale_2x_smaller(false, true, 4);
290 const float4 *got = reinterpret_cast<float4 *>(res->float_buffer.data);
291 EXPECT_V4_NEAR(got[0], float4(0.875f, 1.0f, 1.125f, 1.25f), EPS);
292 EXPECT_V4_NEAR(got[1], float4(3.375f, 3.5f, 3.625f, 3.75f), EPS);
293 EXPECT_V4_NEAR(got[2], float4(5.875f, 6.0f, 6.125f, 6.25f), EPS);
294 IMB_freeImBuf(res);
295}
296
297} // namespace blender::imbuf::tests
EXPECT_EQ(BLI_expr_pylike_eval(expr, nullptr, 0, &result), EXPR_PYLIKE_INVALID)
void IMB_freeImBuf(ImBuf *ibuf)
ImBuf * IMB_allocImBuf(unsigned int x, unsigned int y, unsigned char planes, unsigned int flags)
bool IMB_scale(ImBuf *ibuf, unsigned int newx, unsigned int newy, IMBScaleFilter filter, bool threaded=true)
Definition scaling.cc:465
@ IB_float_data
@ IB_byte_data
uint col
TEST(imbuf_scaling, nearest_2x_smaller)
static constexpr float EPS
static ImBuf * create_6x2_test_image_fl(int channels)
static ImBuf * create_6x2_test_image()
static ImBuf * scale_fractional_larger(bool nearest, bool threaded, int float_channels=0)
static ImBuf * scale_2x_smaller(bool nearest, bool threaded, int float_channels=0)
static ImBuf * scale_to_1x1(bool nearest, bool threaded, int float_channels=0)
VecBase< uint32_t, 4 > uint4
blender::VecBase< uint8_t, 4 > uchar4
VecBase< float, 4 > float4
VecBase< float, 2 > float2
VecBase< float, 3 > float3
ImBufFloatBuffer float_buffer
ImBufByteBuffer byte_buffer