1// [Blend2D]
2// 2D Vector Graphics Powered by a JIT Compiler.
3//
4// [License]
5// Zlib - See LICENSE.md file in the package.
6
7#include "./blapi-build_p.h"
8#include "./blimagescale_p.h"
9#include "./blmath_p.h"
10#include "./blformat_p.h"
11#include "./blgeometry_p.h"
12#include "./blrgba_p.h"
13#include "./blruntime_p.h"
14#include "./blsupport_p.h"
15
16// ============================================================================
17// [BLImageScale - Global Variables]
18// ============================================================================
19
20static constexpr const BLImageScaleOptions blImageScaleOptionsNone = {
21 nullptr, // UserFunc.
22 nullptr, // UserData.
23 2.0, // Radius.
24 {{
25 1.0 / 3.0, // Mitchell B.
26 1.0 / 3.0, // Mitchell C.
27 0.0 // Reserved.
28 }}
29};
30
31// ============================================================================
32// [BLImageScale - Ops]
33// ============================================================================
34
35struct BLImageScaleOps {
36 BLResult (BL_CDECL* weights)(BLImageScaleContext::Data* d, uint32_t dir, BLImageScaleUserFunc func, const void* data) BL_NOEXCEPT;
37 void (BL_CDECL* horz[BL_FORMAT_COUNT])(const BLImageScaleContext::Data* d, uint8_t* dstLine, intptr_t dstStride, const uint8_t* srcLine, intptr_t srcStride) BL_NOEXCEPT;
38 void (BL_CDECL* vert[BL_FORMAT_COUNT])(const BLImageScaleContext::Data* d, uint8_t* dstLine, intptr_t dstStride, const uint8_t* srcLine, intptr_t srcStride) BL_NOEXCEPT;
39};
40static BLImageScaleOps blImageScaleOps;
41
42// ============================================================================
43// [BLImageScale - BuiltInParams]
44// ============================================================================
45
46// Data needed by some functions that take additional parameters.
47struct BLImageScaleBuiltInParams {
48 double radius;
49
50 struct Mitchell {
51 double p0, p2, p3;
52 double q0, q1, q2, q3;
53 } mitchell;
54
55 BL_INLINE void initMitchell(double b, double c) noexcept {
56 constexpr double k1Div3 = 1.0 / 3.0;
57 constexpr double k1Div6 = 1.0 / 6.0;
58 constexpr double k4Div3 = 4.0 / 3.0;
59
60 mitchell.p0 = 1.0 - k1Div3 * b;
61 mitchell.p2 = -3.0 + 2.0 * b + c;
62 mitchell.p3 = 2.0 - 1.5 * b - c;
63
64 mitchell.q0 = k4Div3 * b + c * 4.0;
65 mitchell.q1 = -2.0 * b - c * 8.0;
66 mitchell.q2 = b + c * 5.0;
67 mitchell.q3 = -k1Div6 * b - c;
68 }
69};
70
71// ============================================================================
72// [BLImageScale - Utilities]
73// ============================================================================
74
75// Calculates a Bessel function of first kind of order `n`.
76//
77// Adapted for use in AGG library by Andy Wilk <castor.vulgaris@gmail.com>
78static BL_INLINE double blBessel(double x, int n) noexcept {
79 double d = 1e-6;
80 double b0 = 0.0;
81 double b1 = 0.0;
82
83 if (blAbs(x) <= d)
84 return n != 0 ? 0.0 : 1.0;
85
86 // Set up a starting order for recurrence.
87 int m1 = blAbs(x) > 5.0 ? int(blAbs(1.4 * x + 60.0 / x)) : int(blAbs(x) + 6);
88 int m2 = blMax(int(blAbs(x)) / 4 + 2 + n, m1);
89
90 for (;;) {
91 double c2 = blEpsilon<double>();
92 double c3 = 0.0;
93 double c4 = 0.0;
94
95 int m8 = m2 & 1;
96 for (int i = 1, iEnd = m2 - 1; i < iEnd; i++) {
97 double c6 = 2 * (m2 - i) * c2 / x - c3;
98 c3 = c2;
99 c2 = c6;
100
101 if (m2 - i - 1 == n)
102 b1 = c6;
103
104 m8 ^= 1;
105 if (m8)
106 c4 += c6 * 2.0;
107 }
108
109 double c6 = 2.0 * c2 / x - c3;
110 if (n == 0)
111 b1 = c6;
112
113 c4 += c6;
114 b1 /= c4;
115
116 if (blAbs(b1 - b0) < d)
117 return b1;
118
119 b0 = b1;
120 m2 += 3;
121 }
122}
123
124static BL_INLINE double blSinXDivX(double x) noexcept {
125 return blSin(x) / x;
126}
127
128static BL_INLINE double blLanczos(double x, double y) noexcept {
129 return blSinXDivX(x) * blSinXDivX(y);
130}
131
132static BL_INLINE double blBlackman(double x, double y) noexcept {
133 return blSinXDivX(x) * (0.42 + 0.5 * blCos(y) + 0.08 * blCos(y * 2.0));
134}
135
136// ============================================================================
137// [BLImageScale - Functions]
138// ============================================================================
139
140static BLResult BL_CDECL blImageScaleNearestFunc(double* dst, const double* tArray, size_t n, const void* data) noexcept {
141 BL_UNUSED(data);
142
143 for (size_t i = 0; i < n; i++) {
144 double t = tArray[i];
145 dst[i] = t <= 0.5 ? 1.0 : 0.0;
146 }
147
148 return BL_SUCCESS;
149}
150
151static BLResult BL_CDECL blImageScaleBilinearFunc(double* dst, const double* tArray, size_t n, const void* data) noexcept {
152 BL_UNUSED(data);
153
154 for (size_t i = 0; i < n; i++) {
155 double t = tArray[i];
156 dst[i] = t < 1.0 ? 1.0 - t : 0.0;
157 }
158
159 return BL_SUCCESS;
160}
161
162static BLResult BL_CDECL blImageScaleBicubicFunc(double* dst, const double* tArray, size_t n, const void* data) noexcept {
163 BL_UNUSED(data);
164 constexpr double k2Div3 = 2.0 / 3.0;
165
166 // 0.5t^3 - t^2 + 2/3 == (0.5t - 1.0) t^2 + 2/3
167 for (size_t i = 0; i < n; i++) {
168 double t = tArray[i];
169 dst[i] = t < 1.0 ? (t * 0.5 - 1.0) * blSquare(t) + k2Div3 :
170 t < 2.0 ? blPow3(2.0 - t) / 6.0 : 0.0;
171 }
172
173 return BL_SUCCESS;
174}
175
176static BLResult BL_CDECL blImageScaleBellFunc(double* dst, const double* tArray, size_t n, const void* data) noexcept {
177 BL_UNUSED(data);
178
179 for (size_t i = 0; i < n; i++) {
180 double t = tArray[i];
181 dst[i] = t < 0.5 ? 0.75 - blSquare(t) :
182 t < 1.5 ? 0.50 * blSquare(t - 1.5) : 0.0;
183 }
184
185 return BL_SUCCESS;
186}
187
188static BLResult BL_CDECL blImageScaleGaussFunc(double* dst, const double* tArray, size_t n, const void* data) noexcept {
189 BL_UNUSED(data);
190 constexpr double x = 0.7978845608; // sqrt(2 / PI);
191
192 for (size_t i = 0; i < n; i++) {
193 double t = tArray[i];
194 dst[i] = t <= 2.0 ? exp(blSquare(t) * -2.0) * x : 0.0;
195 }
196
197 return BL_SUCCESS;
198}
199
200static BLResult BL_CDECL blImageScaleHermiteFunc(double* dst, const double* tArray, size_t n, const void* data) noexcept {
201 BL_UNUSED(data);
202
203 for (size_t i = 0; i < n; i++) {
204 double t = tArray[i];
205 dst[i] = t < 1.0 ? (2.0 * t - 3.0) * blSquare(t) + 1.0 : 0.0;
206 }
207
208 return BL_SUCCESS;
209}
210
211static BLResult BL_CDECL blImageScaleHanningFunc(double* dst, const double* tArray, size_t n, const void* data) noexcept {
212 BL_UNUSED(data);
213
214 for (size_t i = 0; i < n; i++) {
215 double t = tArray[i];
216 dst[i] = t <= 1.0 ? 0.5 + 0.5 * blCos(t * BL_MATH_PI) : 0.0;
217 }
218
219 return BL_SUCCESS;
220}
221
222static BLResult BL_CDECL blImageScaleCatromFunc(double* dst, const double* tArray, size_t n, const void* data) noexcept {
223 BL_UNUSED(data);
224
225 for (size_t i = 0; i < n; i++) {
226 double t = tArray[i];
227 dst[i] = t < 1.0 ? 0.5 * (2.0 + t * t * (t * 3.0 - 5.0)) :
228 t < 2.0 ? 0.5 * (4.0 + t * (t * (5.0 - t) - 8.0)) : 0.0;
229 }
230
231 return BL_SUCCESS;
232}
233
234static BLResult BL_CDECL blImageScaleBesselFunc(double* dst, const double* tArray, size_t n, const void* data) noexcept {
235 BL_UNUSED(data);
236 constexpr double x = BL_MATH_PI * 0.25;
237
238 for (size_t i = 0; i < n; i++) {
239 double t = tArray[i];
240 dst[i] = t == 0.0 ? x : t <= 3.2383 ? blBessel(t * BL_MATH_PI, 1) / (2.0 * t) : 0.0;
241 }
242
243 return BL_SUCCESS;
244}
245
246static BLResult BL_CDECL blImageScaleSincFunc(double* dst, const double* tArray, size_t n, const void* data) noexcept {
247 const double r = static_cast<const BLImageScaleBuiltInParams*>(data)->radius;
248
249 for (size_t i = 0; i < n; i++) {
250 double t = tArray[i];
251 dst[i] = t == 0.0 ? 1.0 : t <= r ? blSinXDivX(t * BL_MATH_PI) : 0.0;
252 }
253
254 return BL_SUCCESS;
255}
256
257static BLResult BL_CDECL blImageScaleLanczosFunc(double* dst, const double* tArray, size_t n, const void* data) noexcept {
258 const double r = static_cast<const BLImageScaleBuiltInParams*>(data)->radius;
259 const double x = BL_MATH_PI;
260 const double y = BL_MATH_PI / r;
261
262 for (size_t i = 0; i < n; i++) {
263 double t = tArray[i];
264 dst[i] = t == 0.0 ? 1.0 : t <= r ? blLanczos(t * x, t * y) : 0.0;
265 }
266
267 return BL_SUCCESS;
268}
269
270static BLResult BL_CDECL blImageScaleBlackmanFunc(double* dst, const double* tArray, size_t n, const void* data) noexcept {
271 const double r = static_cast<const BLImageScaleBuiltInParams*>(data)->radius;
272 const double x = BL_MATH_PI;
273 const double y = BL_MATH_PI / r;
274
275 for (size_t i = 0; i < n; i++) {
276 double t = tArray[i];
277 dst[i] = t == 0.0 ? 1.0 : t <= r ? blBlackman(t * x, t * y) : 0.0;
278 }
279
280 return BL_SUCCESS;
281}
282
283static BLResult BL_CDECL blImageScaleMitchellFunc(double* dst, const double* tArray, size_t n, const void* data) noexcept {
284 const BLImageScaleBuiltInParams::Mitchell& p = static_cast<const BLImageScaleBuiltInParams*>(data)->mitchell;
285
286 for (size_t i = 0; i < n; i++) {
287 double t = tArray[i];
288 dst[i] = t < 1.0 ? p.p0 + blSquare(t) * (p.p2 + t * p.p3) :
289 t < 2.0 ? p.q0 + t * (p.q1 + t * (p.q2 + t * p.q3)) : 0.0;
290 }
291
292 return BL_SUCCESS;
293}
294
295// ============================================================================
296// [BLImageScale - Weights]
297// ============================================================================
298
299static BLResult BL_CDECL blImageScaleWeights(BLImageScaleContext::Data* d, uint32_t dir, BLImageScaleUserFunc userFunc, const void* userData) noexcept {
300 int32_t* weightList = d->weightList[dir];
301 BLImageScaleContext::Record* recordList = d->recordList[dir];
302
303 int dstSize = d->dstSize[dir];
304 int srcSize = d->srcSize[dir];
305 int kernelSize = d->kernelSize[dir];
306
307 double radius = d->radius[dir];
308 double factor = d->factor[dir];
309 double scale = d->scale[dir];
310 int32_t isUnbound = 0;
311
312 BLMemBufferTmp<512> wMem;
313 double* wData = static_cast<double*>(wMem.alloc(unsigned(kernelSize) * sizeof(double)));
314
315 if (BL_UNLIKELY(!wData))
316 return blTraceError(BL_ERROR_OUT_OF_MEMORY);
317
318 for (int i = 0; i < dstSize; i++) {
319 double wPos = (double(i) + 0.5) / scale - 0.5;
320 double wSum = 0.0;
321
322 int left = int(wPos - radius);
323 int right = left + kernelSize;
324 int wIndex;
325
326 // Calculate all weights for the destination pixel.
327 wPos -= left;
328 for (wIndex = 0; wIndex < kernelSize; wIndex++, wPos -= 1.0) {
329 wData[wIndex] = blAbs(wPos * factor);
330 }
331
332 // User function can fail.
333 BL_PROPAGATE(userFunc(wData, wData, unsigned(kernelSize), userData));
334
335 // Remove padded pixels from left and right.
336 wIndex = 0;
337 while (left < 0) {
338 double w = wData[wIndex];
339 wData[++wIndex] += w;
340 left++;
341 }
342
343 int wCount = kernelSize;
344 while (right > srcSize) {
345 BL_ASSERT(wCount > 0);
346 double w = wData[--wCount];
347 wData[wCount - 1] += w;
348 right--;
349 }
350
351 recordList[i].pos = 0;
352 recordList[i].count = 0;
353
354 if (wIndex < wCount) {
355 // Sum all weights.
356 int j;
357
358 for (j = wIndex; j < wCount; j++) {
359 double w = wData[j];
360 wSum += w;
361 }
362
363 int iStrongest = 0;
364 int32_t iSum = 0;
365 int32_t iMax = 0;
366
367 double wScale = 65535 / wSum;
368 for (j = wIndex; j < wCount; j++) {
369 int32_t w = int32_t(wData[j] * wScale) >> 8;
370
371 // Remove zero weight from the beginning of the list.
372 if (w == 0 && wIndex == j) {
373 wIndex++;
374 left++;
375 continue;
376 }
377
378 weightList[j - wIndex] = w;
379 iSum += w;
380 isUnbound |= w;
381
382 if (iMax < w) {
383 iMax = w;
384 iStrongest = j - wIndex;
385 }
386 }
387
388 // Normalize the strongest weight so the sum matches `0x100`.
389 if (iSum != 0x100)
390 weightList[iStrongest] += int32_t(0x100) - iSum;
391
392 // `wCount` is now absolute size of weights in `weightList`.
393 wCount -= wIndex;
394
395 // Remove all zero weights from the end of the weight array.
396 while (wCount > 0 && weightList[wCount - 1] == 0)
397 wCount--;
398
399 if (wCount) {
400 BL_ASSERT(left >= 0);
401 recordList[i].pos = uint32_t(left);
402 recordList[i].count = uint32_t(wCount);
403 }
404 }
405
406 weightList += kernelSize;
407 }
408
409 d->isUnbound[dir] = isUnbound < 0;
410 return BL_SUCCESS;
411}
412
413// ============================================================================
414// [BLImageScale - Horz]
415// ============================================================================
416
417static void BL_CDECL blImageScaleHorzPrgb32(const BLImageScaleContext::Data* d, uint8_t* dstLine, intptr_t dstStride, const uint8_t* srcLine, intptr_t srcStride) noexcept {
418 uint32_t dw = uint32_t(d->dstSize[0]);
419 uint32_t sh = uint32_t(d->srcSize[1]);
420 uint32_t kernelSize = uint32_t(d->kernelSize[0]);
421
422 if (!d->isUnbound[BLImageScaleContext::kDirHorz]) {
423 for (uint32_t y = 0; y < sh; y++) {
424 const BLImageScaleContext::Record* recordList = d->recordList[BLImageScaleContext::kDirHorz];
425 const int32_t* weightList = d->weightList[BLImageScaleContext::kDirHorz];
426
427 uint8_t* dp = dstLine;
428
429 for (uint32_t x = 0; x < dw; x++) {
430 const uint8_t* sp = srcLine + recordList->pos * 4;
431 const int32_t* wp = weightList;
432
433 uint32_t cr_cb = 0x00800080u;
434 uint32_t ca_cg = 0x00800080u;
435
436 for (uint32_t i = recordList->count; i; i--) {
437 uint32_t p0 = blMemReadU32a(sp);
438 uint32_t w0 = unsigned(wp[0]);
439
440 ca_cg += ((p0 >> 8) & 0x00FF00FFu) * w0;
441 cr_cb += ((p0 ) & 0x00FF00FFu) * w0;
442
443 sp += 4;
444 wp += 1;
445 }
446
447 blMemWriteU32a(dp, (ca_cg & 0xFF00FF00u) + ((cr_cb & 0xFF00FF00u) >> 8));
448 dp += 4;
449
450 recordList += 1;
451 weightList += kernelSize;
452 }
453
454 dstLine += dstStride;
455 srcLine += srcStride;
456 }
457 }
458 else {
459 for (uint32_t y = 0; y < sh; y++) {
460 const BLImageScaleContext::Record* recordList = d->recordList[BLImageScaleContext::kDirHorz];
461 const int32_t* weightList = d->weightList[BLImageScaleContext::kDirHorz];
462
463 uint8_t* dp = dstLine;
464
465 for (uint32_t x = 0; x < dw; x++) {
466 const uint8_t* sp = srcLine + recordList->pos * 4;
467 const int32_t* wp = weightList;
468
469 int32_t ca = 0x80;
470 int32_t cr = 0x80;
471 int32_t cg = 0x80;
472 int32_t cb = 0x80;
473
474 for (uint32_t i = recordList->count; i; i--) {
475 uint32_t p0 = blMemReadU32a(sp);
476 int32_t w0 = wp[0];
477
478 ca += int32_t((p0 >> 24) ) * w0;
479 cr += int32_t((p0 >> 16) & 0xFFu) * w0;
480 cg += int32_t((p0 >> 8) & 0xFFu) * w0;
481 cb += int32_t((p0 ) & 0xFFu) * w0;
482
483 sp += 4;
484 wp += 1;
485 }
486
487 ca = blClamp<int32_t>(ca >> 8, 0, 255);
488 cr = blClamp<int32_t>(cr >> 8, 0, ca);
489 cg = blClamp<int32_t>(cg >> 8, 0, ca);
490 cb = blClamp<int32_t>(cb >> 8, 0, ca);
491
492 blMemWriteU32a(dp, blRgba32Pack(uint32_t(cr), uint32_t(cg), uint32_t(cb), uint32_t(ca)));
493 dp += 4;
494
495 recordList += 1;
496 weightList += kernelSize;
497 }
498
499 dstLine += dstStride;
500 srcLine += srcStride;
501 }
502 }
503}
504
505static void BL_CDECL blImageScaleHorzXrgb32(const BLImageScaleContext::Data* d, uint8_t* dstLine, intptr_t dstStride, const uint8_t* srcLine, intptr_t srcStride) noexcept {
506 uint32_t dw = uint32_t(d->dstSize[0]);
507 uint32_t sh = uint32_t(d->srcSize[1]);
508 uint32_t kernelSize = uint32_t(d->kernelSize[0]);
509
510 if (!d->isUnbound[BLImageScaleContext::kDirHorz]) {
511 for (uint32_t y = 0; y < sh; y++) {
512 const BLImageScaleContext::Record* recordList = d->recordList[BLImageScaleContext::kDirHorz];
513 const int32_t* weightList = d->weightList[BLImageScaleContext::kDirHorz];
514
515 uint8_t* dp = dstLine;
516
517 for (uint32_t x = 0; x < dw; x++) {
518 const uint8_t* sp = srcLine + recordList->pos * 4;
519 const int32_t* wp = weightList;
520
521 uint32_t cx_cg = 0x00008000u;
522 uint32_t cr_cb = 0x00800080u;
523
524 for (uint32_t i = recordList->count; i; i--) {
525 uint32_t p0 = blMemReadU32a(sp);
526 uint32_t w0 = unsigned(wp[0]);
527
528 cx_cg += (p0 & 0x0000FF00u) * w0;
529 cr_cb += (p0 & 0x00FF00FFu) * w0;
530
531 sp += 4;
532 wp += 1;
533 }
534
535 blMemWriteU32a(dp, 0xFF000000u + (((cx_cg & 0x00FF0000u) | (cr_cb & 0xFF00FF00u)) >> 8));
536 dp += 4;
537
538 recordList += 1;
539 weightList += kernelSize;
540 }
541
542 dstLine += dstStride;
543 srcLine += srcStride;
544 }
545 }
546 else {
547 for (uint32_t y = 0; y < sh; y++) {
548 const BLImageScaleContext::Record* recordList = d->recordList[BLImageScaleContext::kDirHorz];
549 const int32_t* weightList = d->weightList[BLImageScaleContext::kDirHorz];
550
551 uint8_t* dp = dstLine;
552
553 for (uint32_t x = 0; x < dw; x++) {
554 const uint8_t* sp = srcLine + recordList->pos * 4;
555 const int32_t* wp = weightList;
556
557 int32_t cr = 0x80;
558 int32_t cg = 0x80;
559 int32_t cb = 0x80;
560
561 for (uint32_t i = recordList->count; i; i--) {
562 uint32_t p0 = blMemReadU32a(sp);
563 int32_t w0 = wp[0];
564
565 cr += int32_t((p0 >> 16) & 0xFF) * w0;
566 cg += int32_t((p0 >> 8) & 0xFF) * w0;
567 cb += int32_t((p0 ) & 0xFF) * w0;
568
569 sp += 4;
570 wp += 1;
571 }
572
573 cr = blClamp<int32_t>(cr >> 8, 0, 255);
574 cg = blClamp<int32_t>(cg >> 8, 0, 255);
575 cb = blClamp<int32_t>(cb >> 8, 0, 255);
576
577 blMemWriteU32a(dp, blRgba32Pack(uint32_t(cr), uint32_t(cg), uint32_t(cb), 0xFFu));
578 dp += 4;
579
580 recordList += 1;
581 weightList += kernelSize;
582 }
583
584 dstLine += dstStride;
585 srcLine += srcStride;
586 }
587 }
588}
589
590static void BL_CDECL blImageScaleHorzA8(const BLImageScaleContext::Data* d, uint8_t* dstLine, intptr_t dstStride, const uint8_t* srcLine, intptr_t srcStride) noexcept {
591 uint32_t dw = uint32_t(d->dstSize[0]);
592 uint32_t sh = uint32_t(d->srcSize[1]);
593 uint32_t kernelSize = uint32_t(d->kernelSize[0]);
594
595 if (!d->isUnbound[BLImageScaleContext::kDirHorz]) {
596 for (uint32_t y = 0; y < sh; y++) {
597 const BLImageScaleContext::Record* recordList = d->recordList[BLImageScaleContext::kDirHorz];
598 const int32_t* weightList = d->weightList[BLImageScaleContext::kDirHorz];
599
600 uint8_t* dp = dstLine;
601
602 for (uint32_t x = 0; x < dw; x++) {
603 const uint8_t* sp = srcLine + recordList->pos * 1;
604 const int32_t* wp = weightList;
605
606 uint32_t ca = 0x80;
607
608 for (uint32_t i = recordList->count; i; i--) {
609 uint32_t p0 = sp[0];
610 uint32_t w0 = unsigned(wp[0]);
611
612 ca += p0 * w0;
613
614 sp += 1;
615 wp += 1;
616 }
617
618 dp[0] = uint8_t(ca >> 8);
619
620 recordList += 1;
621 weightList += kernelSize;
622
623 dp += 1;
624 }
625
626 dstLine += dstStride;
627 srcLine += srcStride;
628 }
629 }
630 else {
631 for (uint32_t y = 0; y < sh; y++) {
632 const BLImageScaleContext::Record* recordList = d->recordList[BLImageScaleContext::kDirHorz];
633 const int32_t* weightList = d->weightList[BLImageScaleContext::kDirHorz];
634
635 uint8_t* dp = dstLine;
636
637 for (uint32_t x = 0; x < dw; x++) {
638 const uint8_t* sp = srcLine + recordList->pos * 1;
639 const int32_t* wp = weightList;
640
641 int32_t ca = 0x80;
642
643 for (uint32_t i = recordList->count; i; i--) {
644 uint32_t p0 = sp[0];
645 int32_t w0 = wp[0];
646
647 ca += (int32_t)p0 * w0;
648
649 sp += 1;
650 wp += 1;
651 }
652
653 dp[0] = blClampToByte(ca >> 8);
654
655 recordList += 1;
656 weightList += kernelSize;
657
658 dp += 1;
659 }
660
661 dstLine += dstStride;
662 srcLine += srcStride;
663 }
664 }
665}
666
667// ============================================================================
668// [BLImageScale - Vert]
669// ============================================================================
670
671static void BL_CDECL blImageScaleVertPrgb32(const BLImageScaleContext::Data* d, uint8_t* dstLine, intptr_t dstStride, const uint8_t* srcLine, intptr_t srcStride) noexcept {
672 uint32_t dw = uint32_t(d->dstSize[0]);
673 uint32_t dh = uint32_t(d->dstSize[1]);
674 uint32_t kernelSize = uint32_t(d->kernelSize[BLImageScaleContext::kDirVert]);
675
676 const BLImageScaleContext::Record* recordList = d->recordList[BLImageScaleContext::kDirVert];
677 const int32_t* weightList = d->weightList[BLImageScaleContext::kDirVert];
678
679 if (!d->isUnbound[BLImageScaleContext::kDirVert]) {
680 for (uint32_t y = 0; y < dh; y++) {
681 const uint8_t* srcData = srcLine + intptr_t(recordList->pos) * srcStride;
682 uint8_t* dp = dstLine;
683
684 uint32_t count = recordList->count;
685 for (uint32_t x = 0; x < dw; x++) {
686 const uint8_t* sp = srcData;
687 const int32_t* wp = weightList;
688
689 uint32_t cr_cb = 0x00800080;
690 uint32_t ca_cg = 0x00800080;
691
692 for (uint32_t i = count; i; i--) {
693 uint32_t p0 = blMemReadU32a(sp);
694 uint32_t w0 = unsigned(wp[0]);
695
696 ca_cg += ((p0 >> 8) & 0x00FF00FF) * w0;
697 cr_cb += ((p0 ) & 0x00FF00FF) * w0;
698
699 sp += srcStride;
700 wp += 1;
701 }
702
703 blMemWriteU32a(dp, (ca_cg & 0xFF00FF00) + ((cr_cb & 0xFF00FF00) >> 8));
704 dp += 4;
705 srcData += 4;
706 }
707
708 recordList += 1;
709 weightList += kernelSize;
710
711 dstLine += dstStride;
712 }
713 }
714 else {
715 for (uint32_t y = 0; y < dh; y++) {
716 const uint8_t* srcData = srcLine + intptr_t(recordList->pos) * srcStride;
717 uint8_t* dp = dstLine;
718
719 uint32_t count = recordList->count;
720 for (uint32_t x = 0; x < dw; x++) {
721 const uint8_t* sp = srcData;
722 const int32_t* wp = weightList;
723
724 int32_t ca = 0x80;
725 int32_t cr = 0x80;
726 int32_t cg = 0x80;
727 int32_t cb = 0x80;
728
729 for (uint32_t i = count; i; i--) {
730 uint32_t p0 = blMemReadU32a(sp);
731 int32_t w0 = wp[0];
732
733 ca += int32_t((p0 >> 24) ) * w0;
734 cr += int32_t((p0 >> 16) & 0xFFu) * w0;
735 cg += int32_t((p0 >> 8) & 0xFFu) * w0;
736 cb += int32_t((p0 ) & 0xFFu) * w0;
737
738 sp += srcStride;
739 wp += 1;
740 }
741
742 ca = blClamp<int32_t>(ca >> 8, 0, 255);
743 cr = blClamp<int32_t>(cr >> 8, 0, ca);
744 cg = blClamp<int32_t>(cg >> 8, 0, ca);
745 cb = blClamp<int32_t>(cb >> 8, 0, ca);
746
747 blMemWriteU32a(dp, blRgba32Pack(uint32_t(cr), uint32_t(cg), uint32_t(cb), uint32_t(ca)));
748 dp += 4;
749 srcData += 4;
750 }
751
752 recordList += 1;
753 weightList += kernelSize;
754
755 dstLine += dstStride;
756 }
757 }
758}
759
760static void BL_CDECL blImageScaleVertXrgb32(const BLImageScaleContext::Data* d, uint8_t* dstLine, intptr_t dstStride, const uint8_t* srcLine, intptr_t srcStride) noexcept {
761 uint32_t dw = uint32_t(d->dstSize[0]);
762 uint32_t dh = uint32_t(d->dstSize[1]);
763 uint32_t kernelSize = uint32_t(d->kernelSize[BLImageScaleContext::kDirVert]);
764
765 const BLImageScaleContext::Record* recordList = d->recordList[BLImageScaleContext::kDirVert];
766 const int32_t* weightList = d->weightList[BLImageScaleContext::kDirVert];
767
768 if (!d->isUnbound[BLImageScaleContext::kDirVert]) {
769 for (uint32_t y = 0; y < dh; y++) {
770 const uint8_t* srcData = srcLine + intptr_t(recordList->pos) * srcStride;
771 uint8_t* dp = dstLine;
772
773 uint32_t count = recordList->count;
774 for (uint32_t x = 0; x < dw; x++) {
775 const uint8_t* sp = srcData;
776 const int32_t* wp = weightList;
777
778 uint32_t cx_cg = 0x00008000u;
779 uint32_t cr_cb = 0x00800080u;
780
781 for (uint32_t i = count; i; i--) {
782 uint32_t p0 = blMemReadU32a(sp);
783 uint32_t w0 = unsigned(wp[0]);
784
785 cx_cg += (p0 & 0x0000FF00u) * w0;
786 cr_cb += (p0 & 0x00FF00FFu) * w0;
787
788 sp += srcStride;
789 wp += 1;
790 }
791
792 blMemWriteU32a(dp, 0xFF000000u + (((cx_cg & 0x00FF0000u) | (cr_cb & 0xFF00FF00u)) >> 8));
793 dp += 4;
794 srcData += 4;
795 }
796
797 recordList += 1;
798 weightList += kernelSize;
799
800 dstLine += dstStride;
801 }
802 }
803 else {
804 for (uint32_t y = 0; y < dh; y++) {
805 const uint8_t* srcData = srcLine + intptr_t(recordList->pos) * srcStride;
806 uint8_t* dp = dstLine;
807
808 uint32_t count = recordList->count;
809 for (uint32_t x = 0; x < dw; x++) {
810 const uint8_t* sp = srcData;
811 const int32_t* wp = weightList;
812
813 int32_t cr = 0x80;
814 int32_t cg = 0x80;
815 int32_t cb = 0x80;
816
817 for (uint32_t i = count; i; i--) {
818 uint32_t p0 = blMemReadU32a(sp);
819 int32_t w0 = wp[0];
820
821 cr += int32_t((p0 >> 16) & 0xFFu) * w0;
822 cg += int32_t((p0 >> 8) & 0xFFu) * w0;
823 cb += int32_t((p0 ) & 0xFFu) * w0;
824
825 sp += srcStride;
826 wp += 1;
827 }
828
829 cr = blClamp<int32_t>(cr >> 8, 0, 255);
830 cg = blClamp<int32_t>(cg >> 8, 0, 255);
831 cb = blClamp<int32_t>(cb >> 8, 0, 255);
832
833 blMemWriteU32a(dp, blRgba32Pack(uint32_t(cr), uint32_t(cg), uint32_t(cb), 0xFFu));
834 dp += 4;
835 srcData += 4;
836 }
837
838 recordList += 1;
839 weightList += kernelSize;
840
841 dstLine += dstStride;
842 }
843 }
844}
845
846static void BL_CDECL blImageScaleVertBytes(const BLImageScaleContext::Data* d, uint8_t* dstLine, intptr_t dstStride, const uint8_t* srcLine, intptr_t srcStride, uint32_t wScale) noexcept {
847 uint32_t dw = uint32_t(d->dstSize[0]) * wScale;
848 uint32_t dh = uint32_t(d->dstSize[1]);
849 uint32_t kernelSize = uint32_t(d->kernelSize[BLImageScaleContext::kDirVert]);
850
851 const BLImageScaleContext::Record* recordList = d->recordList[BLImageScaleContext::kDirVert];
852 const int32_t* weightList = d->weightList[BLImageScaleContext::kDirVert];
853
854 if (!d->isUnbound[BLImageScaleContext::kDirVert]) {
855 for (uint32_t y = 0; y < dh; y++) {
856 const uint8_t* srcData = srcLine + intptr_t(recordList->pos) * srcStride;
857 uint8_t* dp = dstLine;
858
859 uint32_t x = dw;
860 uint32_t i = 0;
861 uint32_t count = recordList->count;
862
863 if (((intptr_t)dp & 0x7) == 0)
864 goto BoundLarge;
865 i = 8u - uint32_t((uintptr_t)dp & 0x7u);
866
867BoundSmall:
868 x -= i;
869 do {
870 const uint8_t* sp = srcData;
871 const int32_t* wp = weightList;
872
873 uint32_t c0 = 0x80;
874
875 for (uint32_t j = count; j; j--) {
876 uint32_t p0 = sp[0];
877 uint32_t w0 = unsigned(wp[0]);
878
879 c0 += p0 * w0;
880
881 sp += srcStride;
882 wp += 1;
883 }
884
885 dp[0] = (uint8_t)(c0 >> 8);
886 dp += 1;
887 srcData += 1;
888 } while (--i);
889
890BoundLarge:
891 while (x >= 8) {
892 const uint8_t* sp = srcData;
893 const int32_t* wp = weightList;
894
895 uint32_t c0 = 0x00800080u;
896 uint32_t c1 = 0x00800080u;
897 uint32_t c2 = 0x00800080u;
898 uint32_t c3 = 0x00800080u;
899
900 for (uint32_t j = count; j; j--) {
901 uint32_t p0 = blMemReadU32a(sp + 0u);
902 uint32_t p1 = blMemReadU32a(sp + 4u);
903 uint32_t w0 = unsigned(wp[0]);
904
905 c0 += ((p0 ) & 0x00FF00FFu) * w0;
906 c1 += ((p0 >> 8) & 0x00FF00FFu) * w0;
907 c2 += ((p1 ) & 0x00FF00FFu) * w0;
908 c3 += ((p1 >> 8) & 0x00FF00FFu) * w0;
909
910 sp += srcStride;
911 wp += 1;
912 }
913
914 blMemWriteU32a(dp + 0u, ((c0 & 0xFF00FF00u) >> 8) + (c1 & 0xFF00FF00u));
915 blMemWriteU32a(dp + 4u, ((c2 & 0xFF00FF00u) >> 8) + (c3 & 0xFF00FF00u));
916
917 dp += 8;
918 srcData += 8;
919 x -= 8;
920 }
921
922 i = x;
923 if (i != 0)
924 goto BoundSmall;
925
926 recordList += 1;
927 weightList += kernelSize;
928
929 dstLine += dstStride;
930 }
931 }
932 else {
933 for (uint32_t y = 0; y < dh; y++) {
934 const uint8_t* srcData = srcLine + intptr_t(recordList->pos) * srcStride;
935 uint8_t* dp = dstLine;
936
937 uint32_t x = dw;
938 uint32_t i = 0;
939 uint32_t count = recordList->count;
940
941 if (((size_t)dp & 0x3) == 0)
942 goto UnboundLarge;
943 i = 4u - uint32_t((uintptr_t)dp & 0x3u);
944
945UnboundSmall:
946 x -= i;
947 do {
948 const uint8_t* sp = srcData;
949 const int32_t* wp = weightList;
950
951 int32_t c0 = 0x80;
952
953 for (uint32_t j = count; j; j--) {
954 uint32_t p0 = sp[0];
955 int32_t w0 = wp[0];
956
957 c0 += int32_t(p0) * w0;
958
959 sp += srcStride;
960 wp += 1;
961 }
962
963 dp[0] = (uint8_t)(uint32_t)blClamp<int32_t>(c0 >> 8, 0, 255);
964 dp += 1;
965 srcData += 1;
966 } while (--i);
967
968UnboundLarge:
969 while (x >= 4) {
970 const uint8_t* sp = srcData;
971 const int32_t* wp = weightList;
972
973 int32_t c0 = 0x80;
974 int32_t c1 = 0x80;
975 int32_t c2 = 0x80;
976 int32_t c3 = 0x80;
977
978 for (uint32_t j = count; j; j--) {
979 uint32_t p0 = blMemReadU32a(sp);
980 uint32_t w0 = unsigned(wp[0]);
981
982 c0 += ((p0 ) & 0xFF) * w0;
983 c1 += ((p0 >> 8) & 0xFF) * w0;
984 c2 += ((p0 >> 16) & 0xFF) * w0;
985 c3 += ((p0 >> 24) ) * w0;
986
987 sp += srcStride;
988 wp += 1;
989 }
990
991 blMemWriteU32a(dp, uint32_t(blClamp<int32_t>(c0 >> 8, 0, 255) ) |
992 uint32_t(blClamp<int32_t>(c1 >> 8, 0, 255) << 8) |
993 uint32_t(blClamp<int32_t>(c2 >> 8, 0, 255) << 16) |
994 uint32_t(blClamp<int32_t>(c3 >> 8, 0, 255) << 24));
995 dp += 4;
996 srcData += 4;
997 x -= 4;
998 }
999
1000 i = x;
1001 if (i != 0)
1002 goto UnboundSmall;
1003
1004 recordList += 1;
1005 weightList += kernelSize;
1006
1007 dstLine += dstStride;
1008 }
1009 }
1010}
1011
1012static void BL_CDECL blImageScaleVertA8(const BLImageScaleContext::Data* d, uint8_t* dstLine, intptr_t dstStride, const uint8_t* srcLine, intptr_t srcStride) noexcept {
1013 blImageScaleVertBytes(d, dstLine, dstStride, srcLine, srcStride, 1);
1014}
1015
1016// ============================================================================
1017// [BLImageScale - Reset]
1018// ============================================================================
1019
1020BLResult BLImageScaleContext::reset() noexcept {
1021 if (data != nullptr)
1022 free(data);
1023
1024 data = nullptr;
1025 return BL_SUCCESS;
1026}
1027
1028// ============================================================================
1029// [BLImageScale - Interface]
1030// ============================================================================
1031
1032BLResult BLImageScaleContext::create(const BLSizeI& to, const BLSizeI& from, uint32_t filter, const BLImageScaleOptions* options) noexcept {
1033 if (!options)
1034 options = &blImageScaleOptionsNone;
1035
1036 BLImageScaleBuiltInParams p;
1037 BLImageScaleUserFunc userFunc;
1038 const void* userData = &p;
1039
1040 // --------------------------------------------------------------------------
1041 // [Setup Parameters]
1042 // --------------------------------------------------------------------------
1043
1044 if (!blIsValid(to) || !blIsValid(from))
1045 return blTraceError(BL_ERROR_INVALID_VALUE);
1046
1047 switch (filter) {
1048 case BL_IMAGE_SCALE_FILTER_NEAREST : userFunc = blImageScaleNearestFunc ; p.radius = 1.0; break;
1049 case BL_IMAGE_SCALE_FILTER_BILINEAR: userFunc = blImageScaleBilinearFunc; p.radius = 1.0; break;
1050 case BL_IMAGE_SCALE_FILTER_BICUBIC : userFunc = blImageScaleBicubicFunc ; p.radius = 2.0; break;
1051 case BL_IMAGE_SCALE_FILTER_BELL : userFunc = blImageScaleBellFunc ; p.radius = 1.5; break;
1052 case BL_IMAGE_SCALE_FILTER_GAUSS : userFunc = blImageScaleGaussFunc ; p.radius = 2.0; break;
1053 case BL_IMAGE_SCALE_FILTER_HERMITE : userFunc = blImageScaleHermiteFunc ; p.radius = 1.0; break;
1054 case BL_IMAGE_SCALE_FILTER_HANNING : userFunc = blImageScaleHanningFunc ; p.radius = 1.0; break;
1055 case BL_IMAGE_SCALE_FILTER_CATROM : userFunc = blImageScaleCatromFunc ; p.radius = 2.0; break;
1056 case BL_IMAGE_SCALE_FILTER_BESSEL : userFunc = blImageScaleBesselFunc ; p.radius = 3.2383; break;
1057
1058 case BL_IMAGE_SCALE_FILTER_SINC : userFunc = blImageScaleSincFunc ; p.radius = options->radius; break;
1059 case BL_IMAGE_SCALE_FILTER_LANCZOS : userFunc = blImageScaleLanczosFunc ; p.radius = options->radius; break;
1060 case BL_IMAGE_SCALE_FILTER_BLACKMAN: userFunc = blImageScaleBlackmanFunc; p.radius = options->radius; break;
1061
1062 case BL_IMAGE_SCALE_FILTER_MITCHELL: {
1063 p.radius = 2.0;
1064 if (!blIsFinite(options->mitchell.b) || !blIsFinite(options->mitchell.c))
1065 return blTraceError(BL_ERROR_INVALID_VALUE);
1066
1067 p.initMitchell(options->mitchell.b, options->mitchell.c);
1068 userFunc = blImageScaleMitchellFunc;
1069 break;
1070 }
1071
1072 case BL_IMAGE_SCALE_FILTER_USER: {
1073 userFunc = options->userFunc;
1074 userData = options->userData;
1075
1076 if (!userFunc)
1077 return blTraceError(BL_ERROR_INVALID_VALUE);
1078 break;
1079 }
1080
1081 default:
1082 return blTraceError(BL_ERROR_INVALID_VALUE);
1083 }
1084
1085 if (!(p.radius >= 1.0 && p.radius <= 16.0))
1086 return blTraceError(BL_ERROR_INVALID_VALUE);
1087
1088 // --------------------------------------------------------------------------
1089 // [Setup Weights]
1090 // --------------------------------------------------------------------------
1091
1092 double scale[2];
1093 double factor[2];
1094 double radius[2];
1095 int kernelSize[2];
1096 int isUnbound[2];
1097
1098 scale[0] = double(to.w) / double(from.w);
1099 scale[1] = double(to.h) / double(from.h);
1100
1101 factor[0] = 1.0;
1102 factor[1] = 1.0;
1103
1104 radius[0] = p.radius;
1105 radius[1] = p.radius;
1106
1107 if (scale[0] < 1.0) { factor[0] = scale[0]; radius[0] = p.radius / scale[0]; }
1108 if (scale[1] < 1.0) { factor[1] = scale[1]; radius[1] = p.radius / scale[1]; }
1109
1110 kernelSize[0] = blCeilToInt(1.0 + 2.0 * radius[0]);
1111 kernelSize[1] = blCeilToInt(1.0 + 2.0 * radius[1]);
1112
1113 isUnbound[0] = false;
1114 isUnbound[1] = false;
1115
1116 size_t wWeightDataSize = size_t(to.w) * unsigned(kernelSize[0]) * sizeof(int32_t);
1117 size_t hWeightDataSize = size_t(to.h) * unsigned(kernelSize[1]) * sizeof(int32_t);
1118 size_t wRecordDataSize = size_t(to.w) * sizeof(Record);
1119 size_t hRecordDataSize = size_t(to.h) * sizeof(Record);
1120 size_t dataSize = sizeof(Data) + wWeightDataSize + hWeightDataSize + wRecordDataSize + hRecordDataSize;
1121
1122 if (this->data)
1123 free(this->data);
1124
1125 this->data = static_cast<Data*>(malloc(dataSize));
1126 if (BL_UNLIKELY(!this->data))
1127 return blTraceError(BL_ERROR_OUT_OF_MEMORY);
1128
1129 // Init data.
1130 Data* d = this->data;
1131 d->dstSize[0] = to.w;
1132 d->dstSize[1] = to.h;
1133 d->srcSize[0] = from.w;
1134 d->srcSize[1] = from.h;
1135 d->kernelSize[0] = kernelSize[0];
1136 d->kernelSize[1] = kernelSize[1];
1137 d->isUnbound[0] = isUnbound[0];
1138 d->isUnbound[1] = isUnbound[1];
1139
1140 d->scale[0] = scale[0];
1141 d->scale[1] = scale[1];
1142 d->factor[0] = factor[0];
1143 d->factor[1] = factor[1];
1144 d->radius[0] = radius[0];
1145 d->radius[1] = radius[1];
1146
1147 // Distribute the memory buffer.
1148 uint8_t* dataPtr = blOffsetPtr<uint8_t>(d, sizeof(Data));
1149
1150 d->weightList[kDirHorz] = reinterpret_cast<int32_t*>(dataPtr); dataPtr += wWeightDataSize;
1151 d->weightList[kDirVert] = reinterpret_cast<int32_t*>(dataPtr); dataPtr += hWeightDataSize;
1152 d->recordList[kDirHorz] = reinterpret_cast<Record*>(dataPtr); dataPtr += wRecordDataSize;
1153 d->recordList[kDirVert] = reinterpret_cast<Record*>(dataPtr);
1154
1155 // Built-in filters will probably never fail, however, custom filters can and
1156 // it wouldn't be safe to just continue.
1157 BL_PROPAGATE(blImageScaleOps.weights(d, kDirHorz, userFunc, userData));
1158 BL_PROPAGATE(blImageScaleOps.weights(d, kDirVert, userFunc, userData));
1159
1160 return BL_SUCCESS;
1161}
1162
1163BLResult BLImageScaleContext::processHorzData(uint8_t* dstLine, intptr_t dstStride, const uint8_t* srcLine, intptr_t srcStride, uint32_t format) const noexcept {
1164 BL_ASSERT(isInitialized());
1165 blImageScaleOps.horz[format](this->data, dstLine, dstStride, srcLine, srcStride);
1166 return BL_SUCCESS;
1167}
1168
1169BLResult BLImageScaleContext::processVertData(uint8_t* dstLine, intptr_t dstStride, const uint8_t* srcLine, intptr_t srcStride, uint32_t format) const noexcept {
1170 BL_ASSERT(isInitialized());
1171 blImageScaleOps.vert[format](this->data, dstLine, dstStride, srcLine, srcStride);
1172 return BL_SUCCESS;
1173}
1174
1175// ============================================================================
1176// [BLImageScale - Runtime Init]
1177// ============================================================================
1178
1179void blImageScalerRtInit(BLRuntimeContext* rt) noexcept {
1180 BL_UNUSED(rt);
1181
1182 blImageScaleOps.weights = blImageScaleWeights;
1183
1184 blImageScaleOps.horz[BL_FORMAT_PRGB32] = blImageScaleHorzPrgb32;
1185 blImageScaleOps.horz[BL_FORMAT_XRGB32] = blImageScaleHorzXrgb32;
1186 blImageScaleOps.horz[BL_FORMAT_A8 ] = blImageScaleHorzA8;
1187
1188 blImageScaleOps.vert[BL_FORMAT_PRGB32] = blImageScaleVertPrgb32;
1189 blImageScaleOps.vert[BL_FORMAT_XRGB32] = blImageScaleVertXrgb32;
1190 blImageScaleOps.vert[BL_FORMAT_A8 ] = blImageScaleVertA8;
1191}
1192