1 /*
2 * The two pass scaling function is based on:
3 * Filtered Image Rescaling
4 * Based on Gems III
5 * - Schumacher general filtered image rescaling
6 * (pp. 414-424)
7 * by Dale Schumacher
8 *
9 * Additional changes by Ray Gardener, Daylon Graphics Ltd.
10 * December 4, 1999
11 *
12 * Ported to libgd by Pierre Joye. Support for multiple channels
13 * added (argb for now).
14 *
15 * Initial sources code is avaibable in the Gems Source Code Packages:
16 * http://www.acm.org/pubs/tog/GraphicsGems/GGemsIII.tar.gz
17 *
18 */
19
20 /*
21 Summary:
22
23 - Horizontal filter contributions are calculated on the fly,
24 as each column is mapped from src to dst image. This lets
25 us omit having to allocate a temporary full horizontal stretch
26 of the src image.
27
28 - If none of the src pixels within a sampling region differ,
29 then the output pixel is forced to equal (any of) the source pixel.
30 This ensures that filters do not corrupt areas of constant color.
31
32 - Filter weight contribution results, after summing, are
33 rounded to the nearest pixel color value instead of
34 being casted to ILubyte (usually an int or char). Otherwise,
35 artifacting occurs.
36
37 */
38
39 /*
40 Additional functions are available for simple rotation or up/downscaling.
41 downscaling using the fixed point implementations are usually much faster
42 than the existing gdImageCopyResampled while having a similar or better
43 quality.
44
45 For image rotations, the optimized versions have a lazy antialiasing for
46 the edges of the images. For a much better antialiased result, the affine
47 function is recommended.
48 */
49
50 /*
51 TODO:
52 - Optimize pixel accesses and loops once we have continuous buffer
53 - Add scale support for a portion only of an image (equivalent of copyresized/resampled)
54 */
55
56 #ifdef HAVE_CONFIG_H
57 #include "config.h"
58 #endif /* HAVE_CONFIG_H */
59
60 #include <stdio.h>
61 #include <stdlib.h>
62 #include <string.h>
63 #include <math.h>
64
65 #undef NDEBUG
66 /* Comment out this line to enable asserts.
67 * TODO: This logic really belongs in cmake and configure.
68 */
69 #define NDEBUG 1
70 #include <assert.h>
71
72 #include "gd.h"
73 #include "gdhelpers.h"
74 #include "gd_intern.h"
75
76 #ifdef _MSC_VER
77 # pragma optimize("t", on)
78 # include <emmintrin.h>
79 #endif
80
81 static gdImagePtr gdImageScaleBilinear(gdImagePtr im,
82 const unsigned int new_width,
83 const unsigned int new_height);
84 static gdImagePtr gdImageScaleBicubicFixed(gdImagePtr src,
85 const unsigned int width,
86 const unsigned int height);
87 static gdImagePtr gdImageScaleNearestNeighbour(gdImagePtr im,
88 const unsigned int width,
89 const unsigned int height);
90 static gdImagePtr gdImageRotateNearestNeighbour(gdImagePtr src,
91 const float degrees,
92 const int bgColor);
93 static gdImagePtr gdImageRotateGeneric(gdImagePtr src, const float degrees,
94 const int bgColor);
95
96
97 #define CLAMP(x, low, high) (((x) > (high)) ? (high) : (((x) < (low)) ? (low) : (x)))
98
99 /* only used here, let do a generic fixed point integers later if required by other
100 part of GD */
101 typedef long gdFixed;
102 /* Integer to fixed point */
103 #define gd_itofx(x) ((x) << 8)
104
105 /* Float to fixed point */
106 #define gd_ftofx(x) (long)((x) * 256)
107
108 /* Double to fixed point */
109 #define gd_dtofx(x) (long)((x) * 256)
110
111 /* Fixed point to integer */
112 #define gd_fxtoi(x) ((x) >> 8)
113
114 /* Fixed point to float */
115 # define gd_fxtof(x) ((float)(x) / 256)
116
117 /* Fixed point to double */
118 #define gd_fxtod(x) ((double)(x) / 256)
119
120 /* Multiply a fixed by a fixed */
121 #define gd_mulfx(x,y) (((x) * (y)) >> 8)
122
123 /* Divide a fixed by a fixed */
124 #define gd_divfx(x,y) (((x) << 8) / (y))
125
126 typedef struct
127 {
128 double *Weights; /* Normalized weights of neighboring pixels */
129 int Left,Right; /* Bounds of source pixels window */
130 } ContributionType; /* Contirbution information for a single pixel */
131
132 typedef struct
133 {
134 ContributionType *ContribRow; /* Row (or column) of contribution weights */
135 unsigned int WindowSize, /* Filter window size (of affecting source pixels) */
136 LineLength; /* Length of line (no. or rows / cols) */
137 } LineContribType;
138
139 /* Each core filter has its own radius */
140 #define DEFAULT_FILTER_LINEAR 1.0f
141 #define DEFAULT_FILTER_BICUBIC 3.0f
142 #define DEFAULT_FILTER_BOX 0.5f
143 #define DEFAULT_FILTER_GENERALIZED_CUBIC 0.5f
144 #define DEFAULT_FILTER_RADIUS 1.0f
145 #define DEFAULT_LANCZOS8_RADIUS 8.0f
146 #define DEFAULT_LANCZOS3_RADIUS 3.0f
147 #define DEFAULT_HERMITE_RADIUS 1.0f
148 #define DEFAULT_BOX_RADIUS 0.5f
149 #define DEFAULT_TRIANGLE_RADIUS 1.0f
150 #define DEFAULT_BELL_RADIUS 1.5f
151 #define DEFAULT_CUBICSPLINE_RADIUS 2.0f
152 #define DEFAULT_MITCHELL_RADIUS 2.0f
153 #define DEFAULT_COSINE_RADIUS 1.0f
154 #define DEFAULT_CATMULLROM_RADIUS 2.0f
155 #define DEFAULT_QUADRATIC_RADIUS 1.5f
156 #define DEFAULT_QUADRATICBSPLINE_RADIUS 1.5f
157 #define DEFAULT_CUBICCONVOLUTION_RADIUS 3.0f
158 #define DEFAULT_GAUSSIAN_RADIUS 1.0f
159 #define DEFAULT_HANNING_RADIUS 1.0f
160 #define DEFAULT_HAMMING_RADIUS 1.0f
161 #define DEFAULT_SINC_RADIUS 1.0f
162 #define DEFAULT_WELSH_RADIUS 1.0f
163
KernelBessel_J1(const double x)164 static double KernelBessel_J1(const double x)
165 {
166 double p, q;
167
168 register long i;
169
170 static const double
171 Pone[] =
172 {
173 0.581199354001606143928050809e+21,
174 -0.6672106568924916298020941484e+20,
175 0.2316433580634002297931815435e+19,
176 -0.3588817569910106050743641413e+17,
177 0.2908795263834775409737601689e+15,
178 -0.1322983480332126453125473247e+13,
179 0.3413234182301700539091292655e+10,
180 -0.4695753530642995859767162166e+7,
181 0.270112271089232341485679099e+4
182 },
183 Qone[] =
184 {
185 0.11623987080032122878585294e+22,
186 0.1185770712190320999837113348e+20,
187 0.6092061398917521746105196863e+17,
188 0.2081661221307607351240184229e+15,
189 0.5243710262167649715406728642e+12,
190 0.1013863514358673989967045588e+10,
191 0.1501793594998585505921097578e+7,
192 0.1606931573481487801970916749e+4,
193 0.1e+1
194 };
195
196 p = Pone[8];
197 q = Qone[8];
198 for (i=7; i >= 0; i--)
199 {
200 p = p*x*x+Pone[i];
201 q = q*x*x+Qone[i];
202 }
203 return (double)(p/q);
204 }
205
KernelBessel_P1(const double x)206 static double KernelBessel_P1(const double x)
207 {
208 double p, q;
209
210 register long i;
211
212 static const double
213 Pone[] =
214 {
215 0.352246649133679798341724373e+5,
216 0.62758845247161281269005675e+5,
217 0.313539631109159574238669888e+5,
218 0.49854832060594338434500455e+4,
219 0.2111529182853962382105718e+3,
220 0.12571716929145341558495e+1
221 },
222 Qone[] =
223 {
224 0.352246649133679798068390431e+5,
225 0.626943469593560511888833731e+5,
226 0.312404063819041039923015703e+5,
227 0.4930396490181088979386097e+4,
228 0.2030775189134759322293574e+3,
229 0.1e+1
230 };
231
232 p = Pone[5];
233 q = Qone[5];
234 for (i=4; i >= 0; i--)
235 {
236 p = p*(8.0/x)*(8.0/x)+Pone[i];
237 q = q*(8.0/x)*(8.0/x)+Qone[i];
238 }
239 return (double)(p/q);
240 }
241
KernelBessel_Q1(const double x)242 static double KernelBessel_Q1(const double x)
243 {
244 double p, q;
245
246 register long i;
247
248 static const double
249 Pone[] =
250 {
251 0.3511751914303552822533318e+3,
252 0.7210391804904475039280863e+3,
253 0.4259873011654442389886993e+3,
254 0.831898957673850827325226e+2,
255 0.45681716295512267064405e+1,
256 0.3532840052740123642735e-1
257 },
258 Qone[] =
259 {
260 0.74917374171809127714519505e+4,
261 0.154141773392650970499848051e+5,
262 0.91522317015169922705904727e+4,
263 0.18111867005523513506724158e+4,
264 0.1038187585462133728776636e+3,
265 0.1e+1
266 };
267
268 p = Pone[5];
269 q = Qone[5];
270 for (i=4; i >= 0; i--)
271 {
272 p = p*(8.0/x)*(8.0/x)+Pone[i];
273 q = q*(8.0/x)*(8.0/x)+Qone[i];
274 }
275 return (double)(p/q);
276 }
277
KernelBessel_Order1(double x)278 static double KernelBessel_Order1(double x)
279 {
280 double p, q;
281
282 if (x == 0.0)
283 return (0.0f);
284 p = x;
285 if (x < 0.0)
286 x=(-x);
287 if (x < 8.0)
288 return (p*KernelBessel_J1(x));
289 q = (double)sqrt(2.0f/(M_PI*x))*(double)(KernelBessel_P1(x)*(1.0f/sqrt(2.0f)*(sin(x)-cos(x)))-8.0f/x*KernelBessel_Q1(x)*
290 (-1.0f/sqrt(2.0f)*(sin(x)+cos(x))));
291 if (p < 0.0f)
292 q = (-q);
293 return (q);
294 }
295
filter_bessel(const double x)296 static double filter_bessel(const double x)
297 {
298 if (x == 0.0f)
299 return (double)(M_PI/4.0f);
300 return (KernelBessel_Order1((double)M_PI*x)/(2.0f*x));
301 }
302
303
filter_blackman(const double x)304 static double filter_blackman(const double x)
305 {
306 return (0.42f+0.5f*(double)cos(M_PI*x)+0.08f*(double)cos(2.0f*M_PI*x));
307 }
308
filter_linear(const double x)309 double filter_linear(const double x) {
310 double ax = fabs(x);
311 if (ax < 1.0f) {
312 return (1.0f - ax);
313 }
314 return 0.0f;
315 }
316
317
318 /**
319 * Bicubic interpolation kernel (a=-1):
320 \verbatim
321 /
322 | 1-2|t|**2+|t|**3 , if |t| < 1
323 h(t) = | 4-8|t|+5|t|**2-|t|**3 , if 1<=|t|<2
324 | 0 , otherwise
325 \
326 \endverbatim
327 * ***bd*** 2.2004
328 */
filter_bicubic(const double t)329 static double filter_bicubic(const double t)
330 {
331 const double abs_t = (double)fabs(t);
332 const double abs_t_sq = abs_t * abs_t;
333 if (abs_t<1) return 1-2*abs_t_sq+abs_t_sq*abs_t;
334 if (abs_t<2) return 4 - 8*abs_t +5*abs_t_sq - abs_t_sq*abs_t;
335 return 0;
336 }
337
338 /**
339 * Generalized cubic kernel (for a=-1 it is the same as BicubicKernel):
340 \verbatim
341 /
342 | (a+2)|t|**3 - (a+3)|t|**2 + 1 , |t| <= 1
343 h(t) = | a|t|**3 - 5a|t|**2 + 8a|t| - 4a , 1 < |t| <= 2
344 | 0 , otherwise
345 \
346 \endverbatim
347 * Often used values for a are -1 and -1/2.
348 */
filter_generalized_cubic(const double t)349 static double filter_generalized_cubic(const double t)
350 {
351 const double a = -DEFAULT_FILTER_GENERALIZED_CUBIC;
352 double abs_t = (double)fabs(t);
353 double abs_t_sq = abs_t * abs_t;
354 if (abs_t < 1) return (a + 2) * abs_t_sq * abs_t - (a + 3) * abs_t_sq + 1;
355 if (abs_t < 2) return a * abs_t_sq * abs_t - 5 * a * abs_t_sq + 8 * a * abs_t - 4 * a;
356 return 0;
357 }
358
359 #ifdef FUNCTION_NOT_USED_YET
360 /* CubicSpline filter, default radius 2 */
filter_cubic_spline(const double x1)361 static double filter_cubic_spline(const double x1)
362 {
363 const double x = x1 < 0.0 ? -x1 : x1;
364
365 if (x < 1.0 ) {
366 const double x2 = x*x;
367
368 return (0.5 * x2 * x - x2 + 2.0 / 3.0);
369 }
370 if (x < 2.0) {
371 return (pow(2.0 - x, 3.0)/6.0);
372 }
373 return 0;
374 }
375 #endif
376
377 #ifdef FUNCTION_NOT_USED_YET
378 /* CubicConvolution filter, default radius 3 */
filter_cubic_convolution(const double x1)379 static double filter_cubic_convolution(const double x1)
380 {
381 const double x = x1 < 0.0 ? -x1 : x1;
382 const double x2 = x1 * x1;
383 const double x2_x = x2 * x;
384
385 if (x <= 1.0) return ((4.0 / 3.0)* x2_x - (7.0 / 3.0) * x2 + 1.0);
386 if (x <= 2.0) return (- (7.0 / 12.0) * x2_x + 3 * x2 - (59.0 / 12.0) * x + 2.5);
387 if (x <= 3.0) return ( (1.0/12.0) * x2_x - (2.0 / 3.0) * x2 + 1.75 * x - 1.5);
388 return 0;
389 }
390 #endif
391
filter_box(double x)392 static double filter_box(double x) {
393 if (x < - DEFAULT_FILTER_BOX)
394 return 0.0f;
395 if (x < DEFAULT_FILTER_BOX)
396 return 1.0f;
397 return 0.0f;
398 }
399
filter_catmullrom(const double x)400 static double filter_catmullrom(const double x)
401 {
402 if (x < -2.0)
403 return(0.0f);
404 if (x < -1.0)
405 return(0.5f*(4.0f+x*(8.0f+x*(5.0f+x))));
406 if (x < 0.0)
407 return(0.5f*(2.0f+x*x*(-5.0f-3.0f*x)));
408 if (x < 1.0)
409 return(0.5f*(2.0f+x*x*(-5.0f+3.0f*x)));
410 if (x < 2.0)
411 return(0.5f*(4.0f+x*(-8.0f+x*(5.0f-x))));
412 return(0.0f);
413 }
414
415 #ifdef FUNCTION_NOT_USED_YET
filter_filter(double t)416 static double filter_filter(double t)
417 {
418 /* f(t) = 2|t|^3 - 3|t|^2 + 1, -1 <= t <= 1 */
419 if(t < 0.0) t = -t;
420 if(t < 1.0) return((2.0 * t - 3.0) * t * t + 1.0);
421 return(0.0);
422 }
423 #endif
424
425 #ifdef FUNCTION_NOT_USED_YET
426 /* Lanczos8 filter, default radius 8 */
filter_lanczos8(const double x1)427 static double filter_lanczos8(const double x1)
428 {
429 const double x = x1 < 0.0 ? -x1 : x1;
430 #define R DEFAULT_LANCZOS8_RADIUS
431
432 if ( x == 0.0) return 1;
433
434 if ( x < R) {
435 return R * sin(x*M_PI) * sin(x * M_PI/ R) / (x * M_PI * x * M_PI);
436 }
437 return 0.0;
438 #undef R
439 }
440 #endif
441
442 #ifdef FUNCTION_NOT_USED_YET
443 /* Lanczos3 filter, default radius 3 */
filter_lanczos3(const double x1)444 static double filter_lanczos3(const double x1)
445 {
446 const double x = x1 < 0.0 ? -x1 : x1;
447 #define R DEFAULT_LANCZOS3_RADIUS
448
449 if ( x == 0.0) return 1;
450
451 if ( x < R)
452 {
453 return R * sin(x*M_PI) * sin(x * M_PI / R) / (x * M_PI * x * M_PI);
454 }
455 return 0.0;
456 #undef R
457 }
458 #endif
459
460 /* Hermite filter, default radius 1 */
filter_hermite(const double x1)461 static double filter_hermite(const double x1)
462 {
463 const double x = x1 < 0.0 ? -x1 : x1;
464
465 if (x < 1.0) return ((2.0 * x - 3) * x * x + 1.0 );
466
467 return 0.0;
468 }
469
470 /* Trangle filter, default radius 1 */
filter_triangle(const double x1)471 static double filter_triangle(const double x1)
472 {
473 const double x = x1 < 0.0 ? -x1 : x1;
474 if (x < 1.0) return (1.0 - x);
475 return 0.0;
476 }
477
478 /* Bell filter, default radius 1.5 */
filter_bell(const double x1)479 static double filter_bell(const double x1)
480 {
481 const double x = x1 < 0.0 ? -x1 : x1;
482
483 if (x < 0.5) return (0.75 - x*x);
484 if (x < 1.5) return (0.5 * pow(x - 1.5, 2.0));
485 return 0.0;
486 }
487
488 /* Mitchell filter, default radius 2.0 */
filter_mitchell(const double x)489 static double filter_mitchell(const double x)
490 {
491 #define KM_B (1.0f/3.0f)
492 #define KM_C (1.0f/3.0f)
493 #define KM_P0 (( 6.0f - 2.0f * KM_B ) / 6.0f)
494 #define KM_P2 ((-18.0f + 12.0f * KM_B + 6.0f * KM_C) / 6.0f)
495 #define KM_P3 (( 12.0f - 9.0f * KM_B - 6.0f * KM_C) / 6.0f)
496 #define KM_Q0 (( 8.0f * KM_B + 24.0f * KM_C) / 6.0f)
497 #define KM_Q1 ((-12.0f * KM_B - 48.0f * KM_C) / 6.0f)
498 #define KM_Q2 (( 6.0f * KM_B + 30.0f * KM_C) / 6.0f)
499 #define KM_Q3 (( -1.0f * KM_B - 6.0f * KM_C) / 6.0f)
500
501 if (x < -2.0)
502 return(0.0f);
503 if (x < -1.0)
504 return(KM_Q0-x*(KM_Q1-x*(KM_Q2-x*KM_Q3)));
505 if (x < 0.0f)
506 return(KM_P0+x*x*(KM_P2-x*KM_P3));
507 if (x < 1.0f)
508 return(KM_P0+x*x*(KM_P2+x*KM_P3));
509 if (x < 2.0f)
510 return(KM_Q0+x*(KM_Q1+x*(KM_Q2+x*KM_Q3)));
511 return(0.0f);
512 }
513
514
515
516 #ifdef FUNCTION_NOT_USED_YET
517 /* Cosine filter, default radius 1 */
filter_cosine(const double x)518 static double filter_cosine(const double x)
519 {
520 if ((x >= -1.0) && (x <= 1.0)) return ((cos(x * M_PI) + 1.0)/2.0);
521
522 return 0;
523 }
524 #endif
525
526 /* Quadratic filter, default radius 1.5 */
filter_quadratic(const double x1)527 static double filter_quadratic(const double x1)
528 {
529 const double x = x1 < 0.0 ? -x1 : x1;
530
531 if (x <= 0.5) return (- 2.0 * x * x + 1);
532 if (x <= 1.5) return (x * x - 2.5* x + 1.5);
533 return 0.0;
534 }
535
filter_bspline(const double x)536 static double filter_bspline(const double x)
537 {
538 if (x>2.0f) {
539 return 0.0f;
540 } else {
541 double a, b, c, d;
542 /* Was calculated anyway cause the "if((x-1.0f) < 0)" */
543 const double xm1 = x - 1.0f;
544 const double xp1 = x + 1.0f;
545 const double xp2 = x + 2.0f;
546
547 if ((xp2) <= 0.0f) a = 0.0f; else a = xp2*xp2*xp2;
548 if ((xp1) <= 0.0f) b = 0.0f; else b = xp1*xp1*xp1;
549 if (x <= 0) c = 0.0f; else c = x*x*x;
550 if ((xm1) <= 0.0f) d = 0.0f; else d = xm1*xm1*xm1;
551
552 return (0.16666666666666666667f * (a - (4.0f * b) + (6.0f * c) - (4.0f * d)));
553 }
554 }
555
556 #ifdef FUNCTION_NOT_USED_YET
557 /* QuadraticBSpline filter, default radius 1.5 */
filter_quadratic_bspline(const double x1)558 static double filter_quadratic_bspline(const double x1)
559 {
560 const double x = x1 < 0.0 ? -x1 : x1;
561
562 if (x <= 0.5) return (- x * x + 0.75);
563 if (x <= 1.5) return (0.5 * x * x - 1.5 * x + 1.125);
564 return 0.0;
565 }
566 #endif
567
filter_gaussian(const double x)568 static double filter_gaussian(const double x)
569 {
570 /* return(exp((double) (-2.0 * x * x)) * sqrt(2.0 / M_PI)); */
571 return (double)(exp(-2.0f * x * x) * 0.79788456080287f);
572 }
573
filter_hanning(const double x)574 static double filter_hanning(const double x)
575 {
576 /* A Cosine windowing function */
577 return(0.5 + 0.5 * cos(M_PI * x));
578 }
579
filter_hamming(const double x)580 static double filter_hamming(const double x)
581 {
582 /* should be
583 (0.54+0.46*cos(M_PI*(double) x));
584 but this approximation is sufficient */
585 if (x < -1.0f)
586 return 0.0f;
587 if (x < 0.0f)
588 return 0.92f*(-2.0f*x-3.0f)*x*x+1.0f;
589 if (x < 1.0f)
590 return 0.92f*(2.0f*x-3.0f)*x*x+1.0f;
591 return 0.0f;
592 }
593
filter_power(const double x)594 static double filter_power(const double x)
595 {
596 const double a = 2.0f;
597 if (fabs(x)>1) return 0.0f;
598 return (1.0f - (double)fabs(pow(x,a)));
599 }
600
filter_sinc(const double x)601 static double filter_sinc(const double x)
602 {
603 /* X-scaled Sinc(x) function. */
604 if (x == 0.0) return(1.0);
605 return (sin(M_PI * (double) x) / (M_PI * (double) x));
606 }
607
608 #ifdef FUNCTION_NOT_USED_YET
filter_welsh(const double x)609 static double filter_welsh(const double x)
610 {
611 /* Welsh parabolic windowing filter */
612 if (x < 1.0)
613 return(1 - x*x);
614 return(0.0);
615 }
616 #endif
617
618 #if defined(_MSC_VER) && !defined(inline)
619 # define inline __inline
620 #endif
621
622 /* keep it for future usage for affine copy over an existing image, targetting fix for 2.2.2 */
623 #ifdef FUNCTION_NOT_USED_YET
624 /* Copied from upstream's libgd */
_color_blend(const int dst,const int src)625 static inline int _color_blend (const int dst, const int src)
626 {
627 const int src_alpha = gdTrueColorGetAlpha(src);
628
629 if( src_alpha == gdAlphaOpaque ) {
630 return src;
631 } else {
632 const int dst_alpha = gdTrueColorGetAlpha(dst);
633
634 if( src_alpha == gdAlphaTransparent ) return dst;
635 if( dst_alpha == gdAlphaTransparent ) {
636 return src;
637 } else {
638 register int alpha, red, green, blue;
639 const int src_weight = gdAlphaTransparent - src_alpha;
640 const int dst_weight = (gdAlphaTransparent - dst_alpha) * src_alpha / gdAlphaMax;
641 const int tot_weight = src_weight + dst_weight;
642
643 alpha = src_alpha * dst_alpha / gdAlphaMax;
644
645 red = (gdTrueColorGetRed(src) * src_weight
646 + gdTrueColorGetRed(dst) * dst_weight) / tot_weight;
647 green = (gdTrueColorGetGreen(src) * src_weight
648 + gdTrueColorGetGreen(dst) * dst_weight) / tot_weight;
649 blue = (gdTrueColorGetBlue(src) * src_weight
650 + gdTrueColorGetBlue(dst) * dst_weight) / tot_weight;
651
652 return ((alpha << 24) + (red << 16) + (green << 8) + blue);
653 }
654 }
655 }
656
_setEdgePixel(const gdImagePtr src,unsigned int x,unsigned int y,gdFixed coverage,const int bgColor)657 static inline int _setEdgePixel(const gdImagePtr src, unsigned int x, unsigned int y, gdFixed coverage, const int bgColor)
658 {
659 const gdFixed f_127 = gd_itofx(127);
660 register int c = src->tpixels[y][x];
661 c = c | (( (int) (gd_fxtof(gd_mulfx(coverage, f_127)) + 50.5f)) << 24);
662 return _color_blend(bgColor, c);
663 }
664 #endif
665
getPixelOverflowTC(gdImagePtr im,const int x,const int y,const int bgColor)666 static inline int getPixelOverflowTC(gdImagePtr im, const int x, const int y, const int bgColor)
667 {
668 if (gdImageBoundsSafe(im, x, y)) {
669 const int c = im->tpixels[y][x];
670 if (c == im->transparent) {
671 return bgColor == -1 ? gdTrueColorAlpha(0, 0, 0, 127) : bgColor;
672 }
673 return c;
674 } else {
675 return bgColor;
676 }
677 }
678
679 #define colorIndex2RGBA(c) gdTrueColorAlpha(im->red[(c)], im->green[(c)], im->blue[(c)], im->alpha[(c)])
680 #define colorIndex2RGBcustomA(c, a) gdTrueColorAlpha(im->red[(c)], im->green[(c)], im->blue[(c)], im->alpha[(a)])
getPixelOverflowPalette(gdImagePtr im,const int x,const int y,const int bgColor)681 static inline int getPixelOverflowPalette(gdImagePtr im, const int x, const int y, const int bgColor)
682 {
683 if (gdImageBoundsSafe(im, x, y)) {
684 const int c = im->pixels[y][x];
685 if (c == im->transparent) {
686 return bgColor == -1 ? gdTrueColorAlpha(0, 0, 0, 127) : bgColor;
687 }
688 return colorIndex2RGBA(c);
689 } else {
690 return bgColor;
691 }
692 }
693
getPixelInterpolateWeight(gdImagePtr im,const double x,const double y,const int bgColor)694 static int getPixelInterpolateWeight(gdImagePtr im, const double x, const double y, const int bgColor)
695 {
696 /* Closest pixel <= (xf,yf) */
697 int sx = (int)(x);
698 int sy = (int)(y);
699 const double xf = x - (double)sx;
700 const double yf = y - (double)sy;
701 const double nxf = (double) 1.0 - xf;
702 const double nyf = (double) 1.0 - yf;
703 const double m1 = xf * yf;
704 const double m2 = nxf * yf;
705 const double m3 = xf * nyf;
706 const double m4 = nxf * nyf;
707
708 /* get color values of neighbouring pixels */
709 const int c1 = im->trueColor == 1 ? getPixelOverflowTC(im, sx, sy, bgColor) : getPixelOverflowPalette(im, sx, sy, bgColor);
710 const int c2 = im->trueColor == 1 ? getPixelOverflowTC(im, sx - 1, sy, bgColor) : getPixelOverflowPalette(im, sx - 1, sy, bgColor);
711 const int c3 = im->trueColor == 1 ? getPixelOverflowTC(im, sx, sy - 1, bgColor) : getPixelOverflowPalette(im, sx, sy - 1, bgColor);
712 const int c4 = im->trueColor == 1 ? getPixelOverflowTC(im, sx - 1, sy - 1, bgColor) : getPixelOverflowPalette(im, sx, sy - 1, bgColor);
713 int r, g, b, a;
714
715 if (x < 0) sx--;
716 if (y < 0) sy--;
717
718 /* component-wise summing-up of color values */
719 if (im->trueColor) {
720 r = (int)(m1*gdTrueColorGetRed(c1) + m2*gdTrueColorGetRed(c2) + m3*gdTrueColorGetRed(c3) + m4*gdTrueColorGetRed(c4));
721 g = (int)(m1*gdTrueColorGetGreen(c1) + m2*gdTrueColorGetGreen(c2) + m3*gdTrueColorGetGreen(c3) + m4*gdTrueColorGetGreen(c4));
722 b = (int)(m1*gdTrueColorGetBlue(c1) + m2*gdTrueColorGetBlue(c2) + m3*gdTrueColorGetBlue(c3) + m4*gdTrueColorGetBlue(c4));
723 a = (int)(m1*gdTrueColorGetAlpha(c1) + m2*gdTrueColorGetAlpha(c2) + m3*gdTrueColorGetAlpha(c3) + m4*gdTrueColorGetAlpha(c4));
724 } else {
725 r = (int)(m1*im->red[(c1)] + m2*im->red[(c2)] + m3*im->red[(c3)] + m4*im->red[(c4)]);
726 g = (int)(m1*im->green[(c1)] + m2*im->green[(c2)] + m3*im->green[(c3)] + m4*im->green[(c4)]);
727 b = (int)(m1*im->blue[(c1)] + m2*im->blue[(c2)] + m3*im->blue[(c3)] + m4*im->blue[(c4)]);
728 a = (int)(m1*im->alpha[(c1)] + m2*im->alpha[(c2)] + m3*im->alpha[(c3)] + m4*im->alpha[(c4)]);
729 }
730
731 r = CLAMP(r, 0, 255);
732 g = CLAMP(g, 0, 255);
733 b = CLAMP(b, 0, 255);
734 a = CLAMP(a, 0, gdAlphaMax);
735 return gdTrueColorAlpha(r, g, b, a);
736 }
737
738 /**
739 * InternalFunction: getPixelInterpolated
740 * Returns the interpolated color value using the default interpolation
741 * method. The returned color is always in the ARGB format (truecolor).
742 *
743 * Parameters:
744 * im - Image to set the default interpolation method
745 * y - X value of the ideal position
746 * y - Y value of the ideal position
747 * method - Interpolation method <gdInterpolationMethod>
748 *
749 * Returns:
750 * GD_TRUE if the affine is rectilinear or GD_FALSE
751 *
752 * See also:
753 * <gdSetInterpolationMethod>
754 */
getPixelInterpolated(gdImagePtr im,const double x,const double y,const int bgColor)755 int getPixelInterpolated(gdImagePtr im, const double x, const double y, const int bgColor)
756 {
757 const int xi=(int)(x);
758 const int yi=(int)(y);
759 int yii;
760 int i;
761 double kernel, kernel_cache_y;
762 double kernel_x[12], kernel_y[4];
763 double new_r = 0.0f, new_g = 0.0f, new_b = 0.0f, new_a = 0.0f;
764
765 /* These methods use special implementations */
766 if (im->interpolation_id == GD_NEAREST_NEIGHBOUR) {
767 return -1;
768 }
769
770 if (im->interpolation_id == GD_WEIGHTED4) {
771 return getPixelInterpolateWeight(im, x, y, bgColor);
772 }
773
774 if (im->interpolation_id == GD_NEAREST_NEIGHBOUR) {
775 if (im->trueColor == 1) {
776 return getPixelOverflowTC(im, xi, yi, bgColor);
777 } else {
778 return getPixelOverflowPalette(im, xi, yi, bgColor);
779 }
780 }
781 if (im->interpolation) {
782 for (i=0; i<4; i++) {
783 kernel_x[i] = (double) im->interpolation((double)(xi+i-1-x));
784 kernel_y[i] = (double) im->interpolation((double)(yi+i-1-y));
785 }
786 } else {
787 return -1;
788 }
789
790 /*
791 * TODO: use the known fast rgba multiplication implementation once
792 * the new formats are in place
793 */
794 for (yii = yi-1; yii < yi+3; yii++) {
795 int xii;
796 kernel_cache_y = kernel_y[yii-(yi-1)];
797 if (im->trueColor) {
798 for (xii=xi-1; xii<xi+3; xii++) {
799 const int rgbs = getPixelOverflowTC(im, xii, yii, bgColor);
800
801 kernel = kernel_cache_y * kernel_x[xii-(xi-1)];
802 new_r += kernel * gdTrueColorGetRed(rgbs);
803 new_g += kernel * gdTrueColorGetGreen(rgbs);
804 new_b += kernel * gdTrueColorGetBlue(rgbs);
805 new_a += kernel * gdTrueColorGetAlpha(rgbs);
806 }
807 } else {
808 for (xii=xi-1; xii<xi+3; xii++) {
809 const int rgbs = getPixelOverflowPalette(im, xii, yii, bgColor);
810
811 kernel = kernel_cache_y * kernel_x[xii-(xi-1)];
812 new_r += kernel * gdTrueColorGetRed(rgbs);
813 new_g += kernel * gdTrueColorGetGreen(rgbs);
814 new_b += kernel * gdTrueColorGetBlue(rgbs);
815 new_a += kernel * gdTrueColorGetAlpha(rgbs);
816 }
817 }
818 }
819
820 new_r = CLAMP(new_r, 0, 255);
821 new_g = CLAMP(new_g, 0, 255);
822 new_b = CLAMP(new_b, 0, 255);
823 new_a = CLAMP(new_a, 0, gdAlphaMax);
824
825 return gdTrueColorAlpha(((int)new_r), ((int)new_g), ((int)new_b), ((int)new_a));
826 }
827
_gdContributionsAlloc(unsigned int line_length,unsigned int windows_size)828 static inline LineContribType * _gdContributionsAlloc(unsigned int line_length, unsigned int windows_size)
829 {
830 unsigned int u = 0;
831 LineContribType *res;
832 size_t weights_size;
833
834 if (overflow2(windows_size, sizeof(double))) {
835 return NULL;
836 } else {
837 weights_size = windows_size * sizeof(double);
838 }
839 res = (LineContribType *) gdMalloc(sizeof(LineContribType));
840 if (!res) {
841 return NULL;
842 }
843 res->WindowSize = windows_size;
844 res->LineLength = line_length;
845 if (overflow2(line_length, sizeof(ContributionType))) {
846 gdFree(res);
847 return NULL;
848 }
849 res->ContribRow = (ContributionType *) gdMalloc(line_length * sizeof(ContributionType));
850 if (res->ContribRow == NULL) {
851 gdFree(res);
852 return NULL;
853 }
854 for (u = 0 ; u < line_length ; u++) {
855 res->ContribRow[u].Weights = (double *) gdMalloc(weights_size);
856 if (res->ContribRow[u].Weights == NULL) {
857 unsigned int i;
858
859 for (i=0;i<u;i++) {
860 gdFree(res->ContribRow[i].Weights);
861 }
862 gdFree(res->ContribRow);
863 gdFree(res);
864 return NULL;
865 }
866 }
867 return res;
868 }
869
_gdContributionsFree(LineContribType * p)870 static inline void _gdContributionsFree(LineContribType * p)
871 {
872 unsigned int u;
873 for (u = 0; u < p->LineLength; u++) {
874 gdFree(p->ContribRow[u].Weights);
875 }
876 gdFree(p->ContribRow);
877 gdFree(p);
878 }
879
_gdContributionsCalc(unsigned int line_size,unsigned int src_size,double scale_d,const interpolation_method pFilter)880 static inline LineContribType *_gdContributionsCalc(unsigned int line_size, unsigned int src_size, double scale_d, const interpolation_method pFilter)
881 {
882 double width_d;
883 double scale_f_d = 1.0;
884 const double filter_width_d = DEFAULT_BOX_RADIUS;
885 int windows_size;
886 unsigned int u;
887 LineContribType *res;
888
889 if (scale_d < 1.0) {
890 width_d = filter_width_d / scale_d;
891 scale_f_d = scale_d;
892 } else {
893 width_d= filter_width_d;
894 }
895
896 windows_size = 2 * (int)ceil(width_d) + 1;
897 res = _gdContributionsAlloc(line_size, windows_size);
898 if (res == NULL) {
899 return NULL;
900 }
901 for (u = 0; u < line_size; u++) {
902 const double dCenter = (double)u / scale_d;
903 /* get the significant edge points affecting the pixel */
904 register int iLeft = MAX(0, (int)floor (dCenter - width_d));
905 int iRight = MIN((int)ceil(dCenter + width_d), (int)src_size - 1);
906 double dTotalWeight = 0.0;
907 int iSrc;
908
909 /* Cut edge points to fit in filter window in case of spill-off */
910 if (iRight - iLeft + 1 > windows_size) {
911 if (iLeft < ((int)src_size - 1 / 2)) {
912 iLeft++;
913 } else {
914 iRight--;
915 }
916 }
917
918 res->ContribRow[u].Left = iLeft;
919 res->ContribRow[u].Right = iRight;
920
921 for (iSrc = iLeft; iSrc <= iRight; iSrc++) {
922 dTotalWeight += (res->ContribRow[u].Weights[iSrc-iLeft] = scale_f_d * (*pFilter)(scale_f_d * (dCenter - (double)iSrc)));
923 }
924
925 if (dTotalWeight < 0.0) {
926 _gdContributionsFree(res);
927 return NULL;
928 }
929
930 if (dTotalWeight > 0.0) {
931 for (iSrc = iLeft; iSrc <= iRight; iSrc++) {
932 res->ContribRow[u].Weights[iSrc-iLeft] /= dTotalWeight;
933 }
934 }
935 }
936 return res;
937 }
938
939
940 static inline void
_gdScaleOneAxis(gdImagePtr pSrc,gdImagePtr dst,unsigned int dst_len,unsigned int row,LineContribType * contrib,gdAxis axis)941 _gdScaleOneAxis(gdImagePtr pSrc, gdImagePtr dst,
942 unsigned int dst_len, unsigned int row, LineContribType *contrib,
943 gdAxis axis)
944 {
945 unsigned int ndx;
946
947 for (ndx = 0; ndx < dst_len; ndx++) {
948 double r = 0, g = 0, b = 0, a = 0;
949 const int left = contrib->ContribRow[ndx].Left;
950 const int right = contrib->ContribRow[ndx].Right;
951 int *dest = (axis == HORIZONTAL) ?
952 &dst->tpixels[row][ndx] :
953 &dst->tpixels[ndx][row];
954
955 int i;
956
957 /* Accumulate each channel */
958 for (i = left; i <= right; i++) {
959 const int left_channel = i - left;
960 const int srcpx = (axis == HORIZONTAL) ?
961 pSrc->tpixels[row][i] :
962 pSrc->tpixels[i][row];
963
964 r += contrib->ContribRow[ndx].Weights[left_channel]
965 * (double)(gdTrueColorGetRed(srcpx));
966 g += contrib->ContribRow[ndx].Weights[left_channel]
967 * (double)(gdTrueColorGetGreen(srcpx));
968 b += contrib->ContribRow[ndx].Weights[left_channel]
969 * (double)(gdTrueColorGetBlue(srcpx));
970 a += contrib->ContribRow[ndx].Weights[left_channel]
971 * (double)(gdTrueColorGetAlpha(srcpx));
972 }/* for */
973
974 *dest = gdTrueColorAlpha(uchar_clamp(r, 0xFF), uchar_clamp(g, 0xFF),
975 uchar_clamp(b, 0xFF),
976 uchar_clamp(a, 0x7F)); /* alpha is 0..127 */
977 }/* for */
978 }/* _gdScaleOneAxis*/
979
980
981 static inline int
_gdScalePass(const gdImagePtr pSrc,const unsigned int src_len,const gdImagePtr pDst,const unsigned int dst_len,const unsigned int num_lines,const gdAxis axis)982 _gdScalePass(const gdImagePtr pSrc, const unsigned int src_len,
983 const gdImagePtr pDst, const unsigned int dst_len,
984 const unsigned int num_lines,
985 const gdAxis axis)
986 {
987 unsigned int line_ndx;
988 LineContribType * contrib;
989
990 /* Same dim, just copy it. */
991 assert(dst_len != src_len); // TODO: caller should handle this.
992
993 contrib = _gdContributionsCalc(dst_len, src_len,
994 (double)dst_len / (double)src_len,
995 pSrc->interpolation);
996 if (contrib == NULL) {
997 return 0;
998 }
999
1000 /* Scale each line */
1001 for (line_ndx = 0; line_ndx < num_lines; line_ndx++) {
1002 _gdScaleOneAxis(pSrc, pDst, dst_len, line_ndx, contrib, axis);
1003 }
1004 _gdContributionsFree (contrib);
1005 return 1;
1006 }/* _gdScalePass*/
1007
1008
1009 static gdImagePtr
gdImageScaleTwoPass(const gdImagePtr src,const unsigned int new_width,const unsigned int new_height)1010 gdImageScaleTwoPass(const gdImagePtr src, const unsigned int new_width,
1011 const unsigned int new_height)
1012 {
1013 const unsigned int src_width = src->sx;
1014 const unsigned int src_height = src->sy;
1015 gdImagePtr tmp_im = NULL;
1016 gdImagePtr dst = NULL;
1017 int scale_pass_res;
1018
1019 assert(src != NULL);
1020
1021 /* First, handle the trivial case. */
1022 if (src_width == new_width && src_height == new_height) {
1023 return gdImageClone(src);
1024 }/* if */
1025
1026 /* Convert to truecolor if it isn't; this code requires it. */
1027 if (!src->trueColor) {
1028 gdImagePaletteToTrueColor(src);
1029 }/* if */
1030
1031 /* Scale horizontally unless sizes are the same. */
1032 if (src_width == new_width) {
1033 tmp_im = src;
1034 } else {
1035 tmp_im = gdImageCreateTrueColor(new_width, src_height);
1036 if (tmp_im == NULL) {
1037 return NULL;
1038 }
1039 gdImageSetInterpolationMethod(tmp_im, src->interpolation_id);
1040
1041 scale_pass_res = _gdScalePass(src, src_width, tmp_im, new_width, src_height, HORIZONTAL);
1042 if (scale_pass_res != 1) {
1043 gdImageDestroy(tmp_im);
1044 return NULL;
1045 }
1046 }/* if .. else*/
1047
1048 /* If vertical sizes match, we're done. */
1049 if (src_height == new_height) {
1050 assert(tmp_im != src);
1051 return tmp_im;
1052 }/* if */
1053
1054 /* Otherwise, we need to scale vertically. */
1055 dst = gdImageCreateTrueColor(new_width, new_height);
1056 if (dst != NULL) {
1057 gdImageSetInterpolationMethod(dst, src->interpolation_id);
1058 scale_pass_res = _gdScalePass(tmp_im, src_height, dst, new_height, new_width, VERTICAL);
1059 if (scale_pass_res != 1) {
1060 gdImageDestroy(dst);
1061 if (src != tmp_im) {
1062 gdImageDestroy(tmp_im);
1063 }
1064 return NULL;
1065 }
1066 }/* if */
1067
1068
1069 if (src != tmp_im) {
1070 gdImageDestroy(tmp_im);
1071 }/* if */
1072
1073 return dst;
1074 }/* gdImageScaleTwoPass*/
1075
1076
1077 /*
1078 BilinearFixed, BicubicFixed and nearest implementations are
1079 rewamped versions of the implementation in CBitmapEx
1080
1081 http://www.codeproject.com/Articles/29121/CBitmapEx-Free-C-Bitmap-Manipulation-Class
1082
1083 Integer only implementation, good to have for common usages like
1084 pre scale very large images before using another interpolation
1085 methods for the last step.
1086 */
1087 static gdImagePtr
gdImageScaleNearestNeighbour(gdImagePtr im,const unsigned int width,const unsigned int height)1088 gdImageScaleNearestNeighbour(gdImagePtr im, const unsigned int width, const unsigned int height)
1089 {
1090 const unsigned long new_width = MAX(1, width);
1091 const unsigned long new_height = MAX(1, height);
1092 const float dx = (float)im->sx / (float)new_width;
1093 const float dy = (float)im->sy / (float)new_height;
1094 const gdFixed f_dx = gd_ftofx(dx);
1095 const gdFixed f_dy = gd_ftofx(dy);
1096
1097 gdImagePtr dst_img;
1098 unsigned long dst_offset_x;
1099 unsigned long dst_offset_y = 0;
1100 unsigned int i;
1101
1102 dst_img = gdImageCreateTrueColor(new_width, new_height);
1103
1104 if (dst_img == NULL) {
1105 return NULL;
1106 }
1107
1108 for (i=0; i<new_height; i++) {
1109 unsigned int j;
1110 dst_offset_x = 0;
1111 if (im->trueColor) {
1112 for (j=0; j<new_width; j++) {
1113 const gdFixed f_i = gd_itofx(i);
1114 const gdFixed f_j = gd_itofx(j);
1115 const gdFixed f_a = gd_mulfx(f_i, f_dy);
1116 const gdFixed f_b = gd_mulfx(f_j, f_dx);
1117 const long m = gd_fxtoi(f_a);
1118 const long n = gd_fxtoi(f_b);
1119
1120 dst_img->tpixels[dst_offset_y][dst_offset_x++] = im->tpixels[m][n];
1121 }
1122 } else {
1123 for (j=0; j<new_width; j++) {
1124 const gdFixed f_i = gd_itofx(i);
1125 const gdFixed f_j = gd_itofx(j);
1126 const gdFixed f_a = gd_mulfx(f_i, f_dy);
1127 const gdFixed f_b = gd_mulfx(f_j, f_dx);
1128 const long m = gd_fxtoi(f_a);
1129 const long n = gd_fxtoi(f_b);
1130
1131 dst_img->tpixels[dst_offset_y][dst_offset_x++] = colorIndex2RGBA(im->pixels[m][n]);
1132 }
1133 }
1134 dst_offset_y++;
1135 }
1136 return dst_img;
1137 }
1138
1139 #if 0
1140 static inline int getPixelOverflowColorTC(gdImagePtr im, const int x, const int y, const int color)
1141 {
1142 if (gdImageBoundsSafe(im, x, y)) {
1143 const int c = im->tpixels[y][x];
1144 if (c == im->transparent) {
1145 return gdTrueColorAlpha(0, 0, 0, 127);
1146 }
1147 return c;
1148 } else {
1149 register int border = 0;
1150 if (y < im->cy1) {
1151 border = im->tpixels[0][im->cx1];
1152 goto processborder;
1153 }
1154
1155 if (y < im->cy1) {
1156 border = im->tpixels[0][im->cx1];
1157 goto processborder;
1158 }
1159
1160 if (y > im->cy2) {
1161 if (x >= im->cx1 && x <= im->cx1) {
1162 border = im->tpixels[im->cy2][x];
1163 goto processborder;
1164 } else {
1165 return gdTrueColorAlpha(0, 0, 0, 127);
1166 }
1167 }
1168
1169 /* y is bound safe at this point */
1170 if (x < im->cx1) {
1171 border = im->tpixels[y][im->cx1];
1172 goto processborder;
1173 }
1174
1175 if (x > im->cx2) {
1176 border = im->tpixels[y][im->cx2];
1177 }
1178
1179 processborder:
1180 if (border == im->transparent) {
1181 return gdTrueColorAlpha(0, 0, 0, 127);
1182 } else{
1183 return gdTrueColorAlpha(gdTrueColorGetRed(border), gdTrueColorGetGreen(border), gdTrueColorGetBlue(border), 127);
1184 }
1185 }
1186 }
1187 #endif
1188
gdImageScaleBilinearPalette(gdImagePtr im,const unsigned int new_width,const unsigned int new_height)1189 static gdImagePtr gdImageScaleBilinearPalette(gdImagePtr im, const unsigned int new_width, const unsigned int new_height)
1190 {
1191 long _width = MAX(1, new_width);
1192 long _height = MAX(1, new_height);
1193 float dx = (float)gdImageSX(im) / (float)_width;
1194 float dy = (float)gdImageSY(im) / (float)_height;
1195 gdFixed f_dx = gd_ftofx(dx);
1196 gdFixed f_dy = gd_ftofx(dy);
1197 gdFixed f_1 = gd_itofx(1);
1198
1199 int dst_offset_h;
1200 int dst_offset_v = 0;
1201 long i;
1202 gdImagePtr new_img;
1203 const int transparent = im->transparent;
1204
1205 new_img = gdImageCreateTrueColor(new_width, new_height);
1206 if (new_img == NULL) {
1207 return NULL;
1208 }
1209
1210 if (transparent < 0) {
1211 /* uninitialized */
1212 new_img->transparent = -1;
1213 } else {
1214 new_img->transparent = gdTrueColorAlpha(im->red[transparent], im->green[transparent], im->blue[transparent], im->alpha[transparent]);
1215 }
1216
1217 for (i=0; i < _height; i++) {
1218 long j;
1219 const gdFixed f_i = gd_itofx(i);
1220 const gdFixed f_a = gd_mulfx(f_i, f_dy);
1221 register long m = gd_fxtoi(f_a);
1222
1223 dst_offset_h = 0;
1224
1225 for (j=0; j < _width; j++) {
1226 /* Update bitmap */
1227 gdFixed f_j = gd_itofx(j);
1228 gdFixed f_b = gd_mulfx(f_j, f_dx);
1229
1230 const long n = gd_fxtoi(f_b);
1231 gdFixed f_f = f_a - gd_itofx(m);
1232 gdFixed f_g = f_b - gd_itofx(n);
1233
1234 const gdFixed f_w1 = gd_mulfx(f_1-f_f, f_1-f_g);
1235 const gdFixed f_w2 = gd_mulfx(f_1-f_f, f_g);
1236 const gdFixed f_w3 = gd_mulfx(f_f, f_1-f_g);
1237 const gdFixed f_w4 = gd_mulfx(f_f, f_g);
1238 unsigned int pixel1;
1239 unsigned int pixel2;
1240 unsigned int pixel3;
1241 unsigned int pixel4;
1242 register gdFixed f_r1, f_r2, f_r3, f_r4,
1243 f_g1, f_g2, f_g3, f_g4,
1244 f_b1, f_b2, f_b3, f_b4,
1245 f_a1, f_a2, f_a3, f_a4;
1246
1247 /* 0 for bgColor; (n,m) is supposed to be valid anyway */
1248 pixel1 = getPixelOverflowPalette(im, n, m, 0);
1249 pixel2 = getPixelOverflowPalette(im, n + 1, m, pixel1);
1250 pixel3 = getPixelOverflowPalette(im, n, m + 1, pixel1);
1251 pixel4 = getPixelOverflowPalette(im, n + 1, m + 1, pixel1);
1252
1253 f_r1 = gd_itofx(gdTrueColorGetRed(pixel1));
1254 f_r2 = gd_itofx(gdTrueColorGetRed(pixel2));
1255 f_r3 = gd_itofx(gdTrueColorGetRed(pixel3));
1256 f_r4 = gd_itofx(gdTrueColorGetRed(pixel4));
1257 f_g1 = gd_itofx(gdTrueColorGetGreen(pixel1));
1258 f_g2 = gd_itofx(gdTrueColorGetGreen(pixel2));
1259 f_g3 = gd_itofx(gdTrueColorGetGreen(pixel3));
1260 f_g4 = gd_itofx(gdTrueColorGetGreen(pixel4));
1261 f_b1 = gd_itofx(gdTrueColorGetBlue(pixel1));
1262 f_b2 = gd_itofx(gdTrueColorGetBlue(pixel2));
1263 f_b3 = gd_itofx(gdTrueColorGetBlue(pixel3));
1264 f_b4 = gd_itofx(gdTrueColorGetBlue(pixel4));
1265 f_a1 = gd_itofx(gdTrueColorGetAlpha(pixel1));
1266 f_a2 = gd_itofx(gdTrueColorGetAlpha(pixel2));
1267 f_a3 = gd_itofx(gdTrueColorGetAlpha(pixel3));
1268 f_a4 = gd_itofx(gdTrueColorGetAlpha(pixel4));
1269
1270 {
1271 const unsigned char red = (unsigned char) gd_fxtoi(gd_mulfx(f_w1, f_r1) + gd_mulfx(f_w2, f_r2) + gd_mulfx(f_w3, f_r3) + gd_mulfx(f_w4, f_r4));
1272 const unsigned char green = (unsigned char) gd_fxtoi(gd_mulfx(f_w1, f_g1) + gd_mulfx(f_w2, f_g2) + gd_mulfx(f_w3, f_g3) + gd_mulfx(f_w4, f_g4));
1273 const unsigned char blue = (unsigned char) gd_fxtoi(gd_mulfx(f_w1, f_b1) + gd_mulfx(f_w2, f_b2) + gd_mulfx(f_w3, f_b3) + gd_mulfx(f_w4, f_b4));
1274 const unsigned char alpha = (unsigned char) gd_fxtoi(gd_mulfx(f_w1, f_a1) + gd_mulfx(f_w2, f_a2) + gd_mulfx(f_w3, f_a3) + gd_mulfx(f_w4, f_a4));
1275
1276 new_img->tpixels[dst_offset_v][dst_offset_h] = gdTrueColorAlpha(red, green, blue, alpha);
1277 }
1278
1279 dst_offset_h++;
1280 }
1281
1282 dst_offset_v++;
1283 }
1284 return new_img;
1285 }
1286
gdImageScaleBilinearTC(gdImagePtr im,const unsigned int new_width,const unsigned int new_height)1287 static gdImagePtr gdImageScaleBilinearTC(gdImagePtr im, const unsigned int new_width, const unsigned int new_height)
1288 {
1289 long dst_w = MAX(1, new_width);
1290 long dst_h = MAX(1, new_height);
1291 float dx = (float)gdImageSX(im) / (float)dst_w;
1292 float dy = (float)gdImageSY(im) / (float)dst_h;
1293 gdFixed f_dx = gd_ftofx(dx);
1294 gdFixed f_dy = gd_ftofx(dy);
1295 gdFixed f_1 = gd_itofx(1);
1296
1297 int dst_offset_h;
1298 int dst_offset_v = 0;
1299 long i;
1300 gdImagePtr new_img;
1301
1302 new_img = gdImageCreateTrueColor(new_width, new_height);
1303 if (!new_img){
1304 return NULL;
1305 }
1306
1307 for (i=0; i < dst_h; i++) {
1308 long j;
1309 dst_offset_h = 0;
1310 for (j=0; j < dst_w; j++) {
1311 /* Update bitmap */
1312 gdFixed f_i = gd_itofx(i);
1313 gdFixed f_j = gd_itofx(j);
1314 gdFixed f_a = gd_mulfx(f_i, f_dy);
1315 gdFixed f_b = gd_mulfx(f_j, f_dx);
1316 const long m = gd_fxtoi(f_a);
1317 const long n = gd_fxtoi(f_b);
1318 gdFixed f_f = f_a - gd_itofx(m);
1319 gdFixed f_g = f_b - gd_itofx(n);
1320
1321 const gdFixed f_w1 = gd_mulfx(f_1-f_f, f_1-f_g);
1322 const gdFixed f_w2 = gd_mulfx(f_1-f_f, f_g);
1323 const gdFixed f_w3 = gd_mulfx(f_f, f_1-f_g);
1324 const gdFixed f_w4 = gd_mulfx(f_f, f_g);
1325 unsigned int pixel1;
1326 unsigned int pixel2;
1327 unsigned int pixel3;
1328 unsigned int pixel4;
1329 register gdFixed f_r1, f_r2, f_r3, f_r4,
1330 f_g1, f_g2, f_g3, f_g4,
1331 f_b1, f_b2, f_b3, f_b4,
1332 f_a1, f_a2, f_a3, f_a4;
1333 /* 0 for bgColor; (n,m) is supposed to be valid anyway */
1334 pixel1 = getPixelOverflowTC(im, n, m, 0);
1335 pixel2 = getPixelOverflowTC(im, n + 1, m, pixel1);
1336 pixel3 = getPixelOverflowTC(im, n, m + 1, pixel1);
1337 pixel4 = getPixelOverflowTC(im, n + 1, m + 1, pixel1);
1338
1339 f_r1 = gd_itofx(gdTrueColorGetRed(pixel1));
1340 f_r2 = gd_itofx(gdTrueColorGetRed(pixel2));
1341 f_r3 = gd_itofx(gdTrueColorGetRed(pixel3));
1342 f_r4 = gd_itofx(gdTrueColorGetRed(pixel4));
1343 f_g1 = gd_itofx(gdTrueColorGetGreen(pixel1));
1344 f_g2 = gd_itofx(gdTrueColorGetGreen(pixel2));
1345 f_g3 = gd_itofx(gdTrueColorGetGreen(pixel3));
1346 f_g4 = gd_itofx(gdTrueColorGetGreen(pixel4));
1347 f_b1 = gd_itofx(gdTrueColorGetBlue(pixel1));
1348 f_b2 = gd_itofx(gdTrueColorGetBlue(pixel2));
1349 f_b3 = gd_itofx(gdTrueColorGetBlue(pixel3));
1350 f_b4 = gd_itofx(gdTrueColorGetBlue(pixel4));
1351 f_a1 = gd_itofx(gdTrueColorGetAlpha(pixel1));
1352 f_a2 = gd_itofx(gdTrueColorGetAlpha(pixel2));
1353 f_a3 = gd_itofx(gdTrueColorGetAlpha(pixel3));
1354 f_a4 = gd_itofx(gdTrueColorGetAlpha(pixel4));
1355 {
1356 const unsigned char red = (unsigned char) gd_fxtoi(gd_mulfx(f_w1, f_r1) + gd_mulfx(f_w2, f_r2) + gd_mulfx(f_w3, f_r3) + gd_mulfx(f_w4, f_r4));
1357 const unsigned char green = (unsigned char) gd_fxtoi(gd_mulfx(f_w1, f_g1) + gd_mulfx(f_w2, f_g2) + gd_mulfx(f_w3, f_g3) + gd_mulfx(f_w4, f_g4));
1358 const unsigned char blue = (unsigned char) gd_fxtoi(gd_mulfx(f_w1, f_b1) + gd_mulfx(f_w2, f_b2) + gd_mulfx(f_w3, f_b3) + gd_mulfx(f_w4, f_b4));
1359 const unsigned char alpha = (unsigned char) gd_fxtoi(gd_mulfx(f_w1, f_a1) + gd_mulfx(f_w2, f_a2) + gd_mulfx(f_w3, f_a3) + gd_mulfx(f_w4, f_a4));
1360
1361 new_img->tpixels[dst_offset_v][dst_offset_h] = gdTrueColorAlpha(red, green, blue, alpha);
1362 }
1363
1364 dst_offset_h++;
1365 }
1366
1367 dst_offset_v++;
1368 }
1369 return new_img;
1370 }
1371
1372 static gdImagePtr
gdImageScaleBilinear(gdImagePtr im,const unsigned int new_width,const unsigned int new_height)1373 gdImageScaleBilinear(gdImagePtr im, const unsigned int new_width,
1374 const unsigned int new_height)
1375 {
1376 if (im->trueColor) {
1377 return gdImageScaleBilinearTC(im, new_width, new_height);
1378 } else {
1379 return gdImageScaleBilinearPalette(im, new_width, new_height);
1380 }
1381 }
1382
1383 static gdImagePtr
gdImageScaleBicubicFixed(gdImagePtr src,const unsigned int width,const unsigned int height)1384 gdImageScaleBicubicFixed(gdImagePtr src, const unsigned int width,
1385 const unsigned int height)
1386 {
1387 const long new_width = MAX(1, width);
1388 const long new_height = MAX(1, height);
1389 const int src_w = gdImageSX(src);
1390 const int src_h = gdImageSY(src);
1391 const gdFixed f_dx = gd_ftofx((float)src_w / (float)new_width);
1392 const gdFixed f_dy = gd_ftofx((float)src_h / (float)new_height);
1393 const gdFixed f_1 = gd_itofx(1);
1394 const gdFixed f_2 = gd_itofx(2);
1395 const gdFixed f_4 = gd_itofx(4);
1396 const gdFixed f_6 = gd_itofx(6);
1397 const gdFixed f_gamma = gd_ftofx(1.04f);
1398 gdImagePtr dst;
1399
1400 unsigned int dst_offset_x;
1401 unsigned int dst_offset_y = 0;
1402 long i;
1403
1404 /* impact perf a bit, but not that much. Implementation for palette
1405 images can be done at a later point.
1406 */
1407 if (src->trueColor == 0) {
1408 gdImagePaletteToTrueColor(src);
1409 }
1410
1411 dst = gdImageCreateTrueColor(new_width, new_height);
1412 if (!dst) {
1413 return NULL;
1414 }
1415
1416 dst->saveAlphaFlag = 1;
1417
1418 for (i=0; i < new_height; i++) {
1419 long j;
1420 dst_offset_x = 0;
1421
1422 for (j=0; j < new_width; j++) {
1423 const gdFixed f_a = gd_mulfx(gd_itofx(i), f_dy);
1424 const gdFixed f_b = gd_mulfx(gd_itofx(j), f_dx);
1425 const long m = gd_fxtoi(f_a);
1426 const long n = gd_fxtoi(f_b);
1427 const gdFixed f_f = f_a - gd_itofx(m);
1428 const gdFixed f_g = f_b - gd_itofx(n);
1429 unsigned int src_offset_x[16], src_offset_y[16];
1430 long k;
1431 register gdFixed f_red = 0, f_green = 0, f_blue = 0, f_alpha = 0;
1432 unsigned char red, green, blue, alpha = 0;
1433 int *dst_row = dst->tpixels[dst_offset_y];
1434
1435 if ((m < 1) || (n < 1)) {
1436 src_offset_x[0] = n;
1437 src_offset_y[0] = m;
1438 } else {
1439 src_offset_x[0] = n - 1;
1440 src_offset_y[0] = m;
1441 }
1442
1443 src_offset_x[1] = n;
1444 src_offset_y[1] = m;
1445
1446 if ((m < 1) || (n >= src_w - 1)) {
1447 src_offset_x[2] = n;
1448 src_offset_y[2] = m;
1449 } else {
1450 src_offset_x[2] = n + 1;
1451 src_offset_y[2] = m;
1452 }
1453
1454 if ((m < 1) || (n >= src_w - 2)) {
1455 src_offset_x[3] = n;
1456 src_offset_y[3] = m;
1457 } else {
1458 src_offset_x[3] = n + 1 + 1;
1459 src_offset_y[3] = m;
1460 }
1461
1462 if (n < 1) {
1463 src_offset_x[4] = n;
1464 src_offset_y[4] = m;
1465 } else {
1466 src_offset_x[4] = n - 1;
1467 src_offset_y[4] = m;
1468 }
1469
1470 src_offset_x[5] = n;
1471 src_offset_y[5] = m;
1472 if (n >= src_w-1) {
1473 src_offset_x[6] = n;
1474 src_offset_y[6] = m;
1475 } else {
1476 src_offset_x[6] = n + 1;
1477 src_offset_y[6] = m;
1478 }
1479
1480 if (n >= src_w - 2) {
1481 src_offset_x[7] = n;
1482 src_offset_y[7] = m;
1483 } else {
1484 src_offset_x[7] = n + 1 + 1;
1485 src_offset_y[7] = m;
1486 }
1487
1488 if ((m >= src_h - 1) || (n < 1)) {
1489 src_offset_x[8] = n;
1490 src_offset_y[8] = m;
1491 } else {
1492 src_offset_x[8] = n - 1;
1493 src_offset_y[8] = m;
1494 }
1495
1496 src_offset_x[9] = n;
1497 src_offset_y[9] = m;
1498
1499 if ((m >= src_h-1) || (n >= src_w-1)) {
1500 src_offset_x[10] = n;
1501 src_offset_y[10] = m;
1502 } else {
1503 src_offset_x[10] = n + 1;
1504 src_offset_y[10] = m;
1505 }
1506
1507 if ((m >= src_h - 1) || (n >= src_w - 2)) {
1508 src_offset_x[11] = n;
1509 src_offset_y[11] = m;
1510 } else {
1511 src_offset_x[11] = n + 1 + 1;
1512 src_offset_y[11] = m;
1513 }
1514
1515 if ((m >= src_h - 2) || (n < 1)) {
1516 src_offset_x[12] = n;
1517 src_offset_y[12] = m;
1518 } else {
1519 src_offset_x[12] = n - 1;
1520 src_offset_y[12] = m;
1521 }
1522
1523 if (!(m >= src_h - 2)) {
1524 src_offset_x[13] = n;
1525 src_offset_y[13] = m;
1526 }
1527
1528 if ((m >= src_h - 2) || (n >= src_w - 1)) {
1529 src_offset_x[14] = n;
1530 src_offset_y[14] = m;
1531 } else {
1532 src_offset_x[14] = n + 1;
1533 src_offset_y[14] = m;
1534 }
1535
1536 if ((m >= src_h - 2) || (n >= src_w - 2)) {
1537 src_offset_x[15] = n;
1538 src_offset_y[15] = m;
1539 } else {
1540 src_offset_x[15] = n + 1 + 1;
1541 src_offset_y[15] = m;
1542 }
1543
1544 for (k = -1; k < 3; k++) {
1545 const gdFixed f = gd_itofx(k)-f_f;
1546 const gdFixed f_fm1 = f - f_1;
1547 const gdFixed f_fp1 = f + f_1;
1548 const gdFixed f_fp2 = f + f_2;
1549 register gdFixed f_a = 0, f_b = 0, f_d = 0, f_c = 0;
1550 register gdFixed f_RY;
1551 int l;
1552
1553 if (f_fp2 > 0) f_a = gd_mulfx(f_fp2, gd_mulfx(f_fp2,f_fp2));
1554 if (f_fp1 > 0) f_b = gd_mulfx(f_fp1, gd_mulfx(f_fp1,f_fp1));
1555 if (f > 0) f_c = gd_mulfx(f, gd_mulfx(f,f));
1556 if (f_fm1 > 0) f_d = gd_mulfx(f_fm1, gd_mulfx(f_fm1,f_fm1));
1557
1558 f_RY = gd_divfx((f_a - gd_mulfx(f_4,f_b) + gd_mulfx(f_6,f_c) - gd_mulfx(f_4,f_d)),f_6);
1559
1560 for (l = -1; l < 3; l++) {
1561 const gdFixed f = gd_itofx(l) - f_g;
1562 const gdFixed f_fm1 = f - f_1;
1563 const gdFixed f_fp1 = f + f_1;
1564 const gdFixed f_fp2 = f + f_2;
1565 register gdFixed f_a = 0, f_b = 0, f_c = 0, f_d = 0;
1566 register gdFixed f_RX, f_R, f_rs, f_gs, f_bs, f_ba;
1567 register int c;
1568 const int _k = ((k+1)*4) + (l+1);
1569
1570 if (f_fp2 > 0) f_a = gd_mulfx(f_fp2,gd_mulfx(f_fp2,f_fp2));
1571
1572 if (f_fp1 > 0) f_b = gd_mulfx(f_fp1,gd_mulfx(f_fp1,f_fp1));
1573
1574 if (f > 0) f_c = gd_mulfx(f,gd_mulfx(f,f));
1575
1576 if (f_fm1 > 0) f_d = gd_mulfx(f_fm1,gd_mulfx(f_fm1,f_fm1));
1577
1578 f_RX = gd_divfx((f_a-gd_mulfx(f_4,f_b)+gd_mulfx(f_6,f_c)-gd_mulfx(f_4,f_d)),f_6);
1579 f_R = gd_mulfx(f_RY,f_RX);
1580
1581 c = src->tpixels[*(src_offset_y + _k)][*(src_offset_x + _k)];
1582 f_rs = gd_itofx(gdTrueColorGetRed(c));
1583 f_gs = gd_itofx(gdTrueColorGetGreen(c));
1584 f_bs = gd_itofx(gdTrueColorGetBlue(c));
1585 f_ba = gd_itofx(gdTrueColorGetAlpha(c));
1586
1587 f_red += gd_mulfx(f_rs,f_R);
1588 f_green += gd_mulfx(f_gs,f_R);
1589 f_blue += gd_mulfx(f_bs,f_R);
1590 f_alpha += gd_mulfx(f_ba,f_R);
1591 }
1592 }
1593
1594 red = (unsigned char) CLAMP(gd_fxtoi(gd_mulfx(f_red, f_gamma)), 0, 255);
1595 green = (unsigned char) CLAMP(gd_fxtoi(gd_mulfx(f_green, f_gamma)), 0, 255);
1596 blue = (unsigned char) CLAMP(gd_fxtoi(gd_mulfx(f_blue, f_gamma)), 0, 255);
1597 alpha = (unsigned char) CLAMP(gd_fxtoi(gd_mulfx(f_alpha, f_gamma)), 0, 127);
1598
1599 *(dst_row + dst_offset_x) = gdTrueColorAlpha(red, green, blue, alpha);
1600
1601 dst_offset_x++;
1602 }
1603 dst_offset_y++;
1604 }
1605 return dst;
1606 }
1607
1608 /**
1609 * Function: gdImageScale
1610 *
1611 * Scale an image
1612 *
1613 * Creates a new image, scaled to the requested size using the current
1614 * <gdInterpolationMethod>.
1615 *
1616 * Note that GD_WEIGHTED4 is not yet supported by this function.
1617 *
1618 * Parameters:
1619 * src - The source image.
1620 * new_width - The new width.
1621 * new_height - The new height.
1622 *
1623 * Returns:
1624 * The scaled image on success, NULL on failure.
1625 *
1626 * See also:
1627 * - <gdImageCopyResized>
1628 * - <gdImageCopyResampled>
1629 */
gdImageScale(const gdImagePtr src,const unsigned int new_width,const unsigned int new_height)1630 BGD_DECLARE(gdImagePtr) gdImageScale(const gdImagePtr src, const unsigned int new_width, const unsigned int new_height)
1631 {
1632 gdImagePtr im_scaled = NULL;
1633
1634 if (src == NULL || (uintmax_t)src->interpolation_id >= GD_METHOD_COUNT) {
1635 return NULL;
1636 }
1637
1638 if (new_width == 0 || new_height == 0) {
1639 return NULL;
1640 }
1641 if (new_width == gdImageSX(src) && new_height == gdImageSY(src)) {
1642 return gdImageClone(src);
1643 }
1644 switch (src->interpolation_id) {
1645 /*Special cases, optimized implementations */
1646 case GD_NEAREST_NEIGHBOUR:
1647 im_scaled = gdImageScaleNearestNeighbour(src, new_width, new_height);
1648 break;
1649
1650 case GD_BILINEAR_FIXED:
1651 case GD_LINEAR:
1652 im_scaled = gdImageScaleBilinear(src, new_width, new_height);
1653 break;
1654
1655 case GD_BICUBIC_FIXED:
1656 case GD_BICUBIC:
1657 im_scaled = gdImageScaleBicubicFixed(src, new_width, new_height);
1658 break;
1659
1660 /* generic */
1661 default:
1662 if (src->interpolation == NULL) {
1663 return NULL;
1664 }
1665 im_scaled = gdImageScaleTwoPass(src, new_width, new_height);
1666 break;
1667 }
1668
1669 return im_scaled;
1670 }
1671
gdRotatedImageSize(gdImagePtr src,const float angle,gdRectPtr bbox)1672 static int gdRotatedImageSize(gdImagePtr src, const float angle, gdRectPtr bbox)
1673 {
1674 gdRect src_area;
1675 double m[6];
1676
1677 gdAffineRotate(m, angle);
1678 src_area.x = 0;
1679 src_area.y = 0;
1680 src_area.width = gdImageSX(src);
1681 src_area.height = gdImageSY(src);
1682 if (gdTransformAffineBoundingBox(&src_area, m, bbox) != GD_TRUE) {
1683 return GD_FALSE;
1684 }
1685
1686 return GD_TRUE;
1687 }
1688
1689 static gdImagePtr
gdImageRotateNearestNeighbour(gdImagePtr src,const float degrees,const int bgColor)1690 gdImageRotateNearestNeighbour(gdImagePtr src, const float degrees,
1691 const int bgColor)
1692 {
1693 float _angle = ((float) (-degrees / 180.0f) * (float)M_PI);
1694 const int src_w = gdImageSX(src);
1695 const int src_h = gdImageSY(src);
1696 const gdFixed f_0_5 = gd_ftofx(0.5f);
1697 const gdFixed f_H = gd_itofx(src_h/2);
1698 const gdFixed f_W = gd_itofx(src_w/2);
1699 const gdFixed f_cos = gd_ftofx(cos(-_angle));
1700 const gdFixed f_sin = gd_ftofx(sin(-_angle));
1701
1702 unsigned int dst_offset_x;
1703 unsigned int dst_offset_y = 0;
1704 unsigned int i;
1705 gdImagePtr dst;
1706 gdRect bbox;
1707 int new_height, new_width;
1708
1709 gdRotatedImageSize(src, degrees, &bbox);
1710 new_width = bbox.width;
1711 new_height = bbox.height;
1712
1713 dst = gdImageCreateTrueColor(new_width, new_height);
1714 if (!dst) {
1715 return NULL;
1716 }
1717 dst->saveAlphaFlag = 1;
1718 for (i = 0; i < new_height; i++) {
1719 unsigned int j;
1720 dst_offset_x = 0;
1721 for (j = 0; j < new_width; j++) {
1722 gdFixed f_i = gd_itofx((int)i - (int)new_height / 2);
1723 gdFixed f_j = gd_itofx((int)j - (int)new_width / 2);
1724 gdFixed f_m = gd_mulfx(f_j,f_sin) + gd_mulfx(f_i,f_cos) + f_0_5 + f_H;
1725 gdFixed f_n = gd_mulfx(f_j,f_cos) - gd_mulfx(f_i,f_sin) + f_0_5 + f_W;
1726 long m = gd_fxtoi(f_m);
1727 long n = gd_fxtoi(f_n);
1728
1729 if ((m > 0) && (m < src_h-1) && (n > 0) && (n < src_w-1)) {
1730 if (dst_offset_y < new_height) {
1731 dst->tpixels[dst_offset_y][dst_offset_x++] = src->tpixels[m][n];
1732 }
1733 } else {
1734 if (dst_offset_y < new_height) {
1735 dst->tpixels[dst_offset_y][dst_offset_x++] = bgColor;
1736 }
1737 }
1738 }
1739 dst_offset_y++;
1740 }
1741 return dst;
1742 }
1743
1744 static gdImagePtr
gdImageRotateGeneric(gdImagePtr src,const float degrees,const int bgColor)1745 gdImageRotateGeneric(gdImagePtr src, const float degrees, const int bgColor)
1746 {
1747 float _angle = ((float) (-degrees / 180.0f) * (float)M_PI);
1748 const int src_w = gdImageSX(src);
1749 const int src_h = gdImageSY(src);
1750 const gdFixed f_H = gd_itofx(src_h/2);
1751 const gdFixed f_W = gd_itofx(src_w/2);
1752 const gdFixed f_cos = gd_ftofx(cos(-_angle));
1753 const gdFixed f_sin = gd_ftofx(sin(-_angle));
1754
1755 unsigned int dst_offset_x;
1756 unsigned int dst_offset_y = 0;
1757 unsigned int i;
1758 gdImagePtr dst;
1759 int new_width, new_height;
1760 gdRect bbox;
1761
1762 if (bgColor < 0) {
1763 return NULL;
1764 }
1765
1766 if (src->interpolation == NULL) {
1767 gdImageSetInterpolationMethod(src, GD_DEFAULT);
1768 }
1769
1770 gdRotatedImageSize(src, degrees, &bbox);
1771 new_width = bbox.width;
1772 new_height = bbox.height;
1773
1774 dst = gdImageCreateTrueColor(new_width, new_height);
1775 if (!dst) {
1776 return NULL;
1777 }
1778 dst->saveAlphaFlag = 1;
1779
1780 for (i = 0; i < new_height; i++) {
1781 unsigned int j;
1782 dst_offset_x = 0;
1783 for (j = 0; j < new_width; j++) {
1784 gdFixed f_i = gd_itofx((int)i - (int)new_height / 2);
1785 gdFixed f_j = gd_itofx((int)j - (int)new_width / 2);
1786 gdFixed f_m = gd_mulfx(f_j,f_sin) + gd_mulfx(f_i,f_cos) + f_H;
1787 gdFixed f_n = gd_mulfx(f_j,f_cos) - gd_mulfx(f_i,f_sin) + f_W;
1788 long m = gd_fxtoi(f_m);
1789 long n = gd_fxtoi(f_n);
1790
1791 if (m < -1 || n < -1 || m >= src_h || n >= src_w ) {
1792 dst->tpixels[dst_offset_y][dst_offset_x++] = bgColor;
1793 } else {
1794 dst->tpixels[dst_offset_y][dst_offset_x++] = getPixelInterpolated(src, gd_fxtod(f_n), gd_fxtod(f_m), bgColor);
1795 }
1796 }
1797 dst_offset_y++;
1798 }
1799 return dst;
1800 }
1801
1802 /**
1803 * Function: gdImageRotateInterpolated
1804 *
1805 * Rotate an image
1806 *
1807 * Creates a new image, counter-clockwise rotated by the requested angle
1808 * using the current <gdInterpolationMethod>. Non-square angles will add a
1809 * border with bgcolor.
1810 *
1811 * Parameters:
1812 * src - The source image.
1813 * angle - The angle in degrees.
1814 * bgcolor - The color to fill the added background with.
1815 *
1816 * Returns:
1817 * The rotated image on success, NULL on failure.
1818 *
1819 * See also:
1820 * - <gdImageCopyRotated>
1821 */
gdImageRotateInterpolated(const gdImagePtr src,const float angle,int bgcolor)1822 BGD_DECLARE(gdImagePtr) gdImageRotateInterpolated(const gdImagePtr src, const float angle, int bgcolor)
1823 {
1824 /* round to two decimals and keep the 100x multiplication to use it in the common square angles
1825 case later. Keep the two decimal precisions so smaller rotation steps can be done, useful for
1826 slow animations, f.e. */
1827 const int angle_rounded = fmod((int) floorf(angle * 100), 360 * 100);
1828
1829 if (src == NULL || bgcolor < 0) {
1830 return NULL;
1831 }
1832
1833 /* impact perf a bit, but not that much. Implementation for palette
1834 images can be done at a later point.
1835 */
1836 if (src->trueColor == 0) {
1837 if (bgcolor < gdMaxColors) {
1838 bgcolor = gdTrueColorAlpha(src->red[bgcolor], src->green[bgcolor], src->blue[bgcolor], src->alpha[bgcolor]);
1839 }
1840 gdImagePaletteToTrueColor(src);
1841 }
1842
1843 /* 0 && 90 degrees multiple rotation, 0 rotation simply clones the return image and convert it
1844 to truecolor, as we must return truecolor image. */
1845 switch (angle_rounded) {
1846 case 0: {
1847 gdImagePtr dst = gdImageClone(src);
1848
1849 if (dst == NULL) {
1850 return NULL;
1851 }
1852 if (dst->trueColor == 0) {
1853 gdImagePaletteToTrueColor(dst);
1854 }
1855 return dst;
1856 }
1857
1858 case -27000:
1859 case 9000:
1860 return gdImageRotate90(src, 0);
1861
1862 case -18000:
1863 case 18000:
1864 return gdImageRotate180(src, 0);
1865
1866 case -9000:
1867 case 27000:
1868 return gdImageRotate270(src, 0);
1869 }
1870
1871 if (src->interpolation_id < 1 || src->interpolation_id > GD_METHOD_COUNT) {
1872 return NULL;
1873 }
1874
1875 switch (src->interpolation_id) {
1876 case GD_NEAREST_NEIGHBOUR:
1877 return gdImageRotateNearestNeighbour(src, angle, bgcolor);
1878 break;
1879
1880 case GD_BILINEAR_FIXED:
1881 case GD_BICUBIC_FIXED:
1882 default:
1883 return gdImageRotateGeneric(src, angle, bgcolor);
1884 }
1885 return NULL;
1886 }
1887
1888 /**
1889 * Group: Affine Transformation
1890 **/
1891
gdImageClipRectangle(gdImagePtr im,gdRectPtr r)1892 static void gdImageClipRectangle(gdImagePtr im, gdRectPtr r)
1893 {
1894 int c1x, c1y, c2x, c2y;
1895 int x1,y1;
1896
1897 gdImageGetClip(im, &c1x, &c1y, &c2x, &c2y);
1898 x1 = r->x + r->width - 1;
1899 y1 = r->y + r->height - 1;
1900 r->x = CLAMP(r->x, c1x, c2x);
1901 r->y = CLAMP(r->y, c1y, c2y);
1902 r->width = CLAMP(x1, c1x, c2x) - r->x + 1;
1903 r->height = CLAMP(y1, c1y, c2y) - r->y + 1;
1904 }
1905
gdDumpRect(const char * msg,gdRectPtr r)1906 void gdDumpRect(const char *msg, gdRectPtr r)
1907 {
1908 printf("%s (%i, %i) (%i, %i)\n", msg, r->x, r->y, r->width, r->height);
1909 }
1910
1911 /**
1912 * Function: gdTransformAffineGetImage
1913 * Applies an affine transformation to a region and return an image
1914 * containing the complete transformation.
1915 *
1916 * Parameters:
1917 * dst - Pointer to a gdImagePtr to store the created image, NULL when
1918 * the creation or the transformation failed
1919 * src - Source image
1920 * src_area - rectangle defining the source region to transform
1921 * dstY - Y position in the destination image
1922 * affine - The desired affine transformation
1923 *
1924 * Returns:
1925 * GD_TRUE if the affine is rectilinear or GD_FALSE
1926 */
gdTransformAffineGetImage(gdImagePtr * dst,const gdImagePtr src,gdRectPtr src_area,const double affine[6])1927 BGD_DECLARE(int) gdTransformAffineGetImage(gdImagePtr *dst,
1928 const gdImagePtr src,
1929 gdRectPtr src_area,
1930 const double affine[6])
1931 {
1932 int res;
1933 double m[6];
1934 gdRect bbox;
1935 gdRect area_full;
1936
1937 if (src_area == NULL) {
1938 area_full.x = 0;
1939 area_full.y = 0;
1940 area_full.width = gdImageSX(src);
1941 area_full.height = gdImageSY(src);
1942 src_area = &area_full;
1943 }
1944
1945 gdTransformAffineBoundingBox(src_area, affine, &bbox);
1946
1947 *dst = gdImageCreateTrueColor(bbox.width, bbox.height);
1948 if (*dst == NULL) {
1949 return GD_FALSE;
1950 }
1951 (*dst)->saveAlphaFlag = 1;
1952
1953 if (!src->trueColor) {
1954 gdImagePaletteToTrueColor(src);
1955 }
1956
1957 /* Translate to dst origin (0,0) */
1958 gdAffineTranslate(m, -bbox.x, -bbox.y);
1959 gdAffineConcat(m, affine, m);
1960
1961 gdImageAlphaBlending(*dst, 0);
1962
1963 res = gdTransformAffineCopy(*dst,
1964 0,0,
1965 src,
1966 src_area,
1967 m);
1968
1969 if (res != GD_TRUE) {
1970 gdImageDestroy(*dst);
1971 *dst = NULL;
1972 return GD_FALSE;
1973 } else {
1974 return GD_TRUE;
1975 }
1976 }
1977
1978 /** Function: getPixelRgbInterpolated
1979 * get the index of the image's colors
1980 *
1981 * Parameters:
1982 * im - Image to draw the transformed image
1983 * tcolor - TrueColor
1984 *
1985 * Return:
1986 * index of colors
1987 */
getPixelRgbInterpolated(gdImagePtr im,const int tcolor)1988 static int getPixelRgbInterpolated(gdImagePtr im, const int tcolor)
1989 {
1990 unsigned char r, g, b, a;
1991 int ct;
1992 int i;
1993
1994 b = (unsigned char)tcolor;
1995 g = (unsigned char)tcolor >> 8;
1996 r = (unsigned char)tcolor >> 16;
1997 a = (unsigned char)tcolor >> 24;
1998
1999 b = CLAMP(b, 0, 255);
2000 g = CLAMP(g, 0, 255);
2001 r = CLAMP(r, 0, 255);
2002 a = CLAMP(a, 0, 127);
2003
2004 for (i = 0; i < im->colorsTotal; i++) {
2005 if (im->red[i] == r && im->green[i] == g && im->blue[i] == b && im->alpha[i] == a) {
2006 return i;
2007 }
2008 }
2009
2010 ct = im->colorsTotal;
2011 if (ct == gdMaxColors) {
2012 return -1;
2013 }
2014
2015 im->colorsTotal++;
2016 im->red[ct] = r;
2017 im->green[ct] = g;
2018 im->blue[ct] = b;
2019 im->alpha[ct] = a;
2020 im->open[ct] = 0;
2021
2022 return ct;
2023 }
2024 /**
2025 * Function: gdTransformAffineCopy
2026 * Applies an affine transformation to a region and copy the result
2027 * in a destination to the given position.
2028 *
2029 * Parameters:
2030 * dst - Image to draw the transformed image
2031 * src - Source image
2032 * dstX - X position in the destination image
2033 * dstY - Y position in the destination image
2034 * src_area - Rectangular region to rotate in the src image
2035 *
2036 * Returns:
2037 * GD_TRUE on success or GD_FALSE on failure
2038 */
gdTransformAffineCopy(gdImagePtr dst,int dst_x,int dst_y,const gdImagePtr src,gdRectPtr src_region,const double affine[6])2039 BGD_DECLARE(int) gdTransformAffineCopy(gdImagePtr dst,
2040 int dst_x, int dst_y,
2041 const gdImagePtr src,
2042 gdRectPtr src_region,
2043 const double affine[6])
2044 {
2045 int c1x,c1y,c2x,c2y;
2046 int backclip = 0;
2047 int backup_clipx1, backup_clipy1, backup_clipx2, backup_clipy2;
2048 register int x, y, src_offset_x, src_offset_y;
2049 double inv[6];
2050 gdPointF pt, src_pt;
2051 gdRect bbox;
2052 int end_x, end_y;
2053 gdInterpolationMethod interpolation_id_bak = src->interpolation_id;
2054
2055 /* These methods use special implementations */
2056 if (src->interpolation_id == GD_BILINEAR_FIXED || src->interpolation_id == GD_BICUBIC_FIXED || src->interpolation_id == GD_NEAREST_NEIGHBOUR) {
2057 gdImageSetInterpolationMethod(src, GD_BICUBIC);
2058 }
2059
2060 gdImageClipRectangle(src, src_region);
2061 c1x = src_region->x;
2062 c1y = src_region->y;
2063 c2x = src_region->x + src_region->width -1;
2064 c2y = src_region->y + src_region->height -1;
2065
2066 if (src_region->x > 0 || src_region->y > 0
2067 || src_region->width < gdImageSX(src)
2068 || src_region->height < gdImageSY(src)) {
2069 backclip = 1;
2070
2071 gdImageGetClip(src, &backup_clipx1, &backup_clipy1,
2072 &backup_clipx2, &backup_clipy2);
2073
2074 gdImageSetClip(src, src_region->x, src_region->y,
2075 src_region->x + src_region->width - 1,
2076 src_region->y + src_region->height - 1);
2077 }
2078
2079 if (!gdTransformAffineBoundingBox(src_region, affine, &bbox)) {
2080 if (backclip) {
2081 gdImageSetClip(src, backup_clipx1, backup_clipy1,
2082 backup_clipx2, backup_clipy2);
2083 }
2084 gdImageSetInterpolationMethod(src, interpolation_id_bak);
2085 return GD_FALSE;
2086 }
2087
2088 end_x = bbox.width + abs(bbox.x);
2089 end_y = bbox.height + abs(bbox.y);
2090
2091 /* Get inverse affine to let us work with destination -> source */
2092 if (gdAffineInvert(inv, affine) == GD_FALSE) {
2093 gdImageSetInterpolationMethod(src, interpolation_id_bak);
2094 return GD_FALSE;
2095 }
2096
2097 src_offset_x = src_region->x;
2098 src_offset_y = src_region->y;
2099
2100 if (dst->alphaBlendingFlag) {
2101 for (y = bbox.y; y <= end_y; y++) {
2102 pt.y = y + 0.5;
2103 for (x = bbox.x; x <= end_x; x++) {
2104 pt.x = x + 0.5;
2105 gdAffineApplyToPointF(&src_pt, &pt, inv);
2106 if (floor(src_offset_x + src_pt.x) < c1x
2107 || floor(src_offset_x + src_pt.x) > c2x
2108 || floor(src_offset_y + src_pt.y) < c1y
2109 || floor(src_offset_y + src_pt.y) > c2y) {
2110 continue;
2111 }
2112 gdImageSetPixel(dst, dst_x + x, dst_y + y, getPixelInterpolated(src, (int)(src_offset_x + src_pt.x), (int)(src_offset_y + src_pt.y), 0));
2113 }
2114 }
2115 } else {
2116 for (y = bbox.y; y <= end_y; y++) {
2117 unsigned char *dst_p = NULL;
2118 int *tdst_p = NULL;
2119
2120 pt.y = y + 0.5;
2121 if ((dst_y + y) < 0 || ((dst_y + y) > gdImageSY(dst) -1)) {
2122 continue;
2123 }
2124 if (dst->trueColor) {
2125 tdst_p = dst->tpixels[dst_y + y] + dst_x;
2126 } else {
2127 dst_p = dst->pixels[dst_y + y] + dst_x;
2128 }
2129
2130 for (x = bbox.x; x <= end_x; x++) {
2131 pt.x = x + 0.5;
2132 gdAffineApplyToPointF(&src_pt, &pt, inv);
2133
2134 if ((dst_x + x) < 0 || (dst_x + x) > (gdImageSX(dst) - 1)) {
2135 break;
2136 }
2137 if (floor(src_offset_x + src_pt.x) < c1x
2138 || floor(src_offset_x + src_pt.x) > c2x
2139 || floor(src_offset_y + src_pt.y) < c1y
2140 || floor(src_offset_y + src_pt.y) > c2y) {
2141 continue;
2142 }
2143 if (dst->trueColor) {
2144 *(tdst_p + dst_x + x) = getPixelInterpolated(src, (int)(src_offset_x + src_pt.x), (int)(src_offset_y + src_pt.y), -1);
2145 } else {
2146 *(dst_p + dst_x + x) = getPixelRgbInterpolated(dst, getPixelInterpolated(src, (int)(src_offset_x + src_pt.x), (int)(src_offset_y + src_pt.y), -1));
2147 }
2148 }
2149 }
2150 }
2151
2152 /* Restore clip if required */
2153 if (backclip) {
2154 gdImageSetClip(src, backup_clipx1, backup_clipy1,
2155 backup_clipx2, backup_clipy2);
2156 }
2157
2158 gdImageSetInterpolationMethod(src, interpolation_id_bak);
2159 return GD_TRUE;
2160 }
2161
2162 /**
2163 * Function: gdTransformAffineBoundingBox
2164 * Returns the bounding box of an affine transformation applied to a
2165 * rectangular area <gdRect>
2166 *
2167 * Parameters:
2168 * src - Rectangular source area for the affine transformation
2169 * affine - the affine transformation
2170 * bbox - the resulting bounding box
2171 *
2172 * Returns:
2173 * GD_TRUE if the affine is rectilinear or GD_FALSE
2174 */
gdTransformAffineBoundingBox(gdRectPtr src,const double affine[6],gdRectPtr bbox)2175 BGD_DECLARE(int) gdTransformAffineBoundingBox(gdRectPtr src, const double affine[6], gdRectPtr bbox)
2176 {
2177 gdPointF extent[4], min, max, point;
2178 int i;
2179
2180 extent[0].x=0.0;
2181 extent[0].y=0.0;
2182 extent[1].x=(double) src->width;
2183 extent[1].y=0.0;
2184 extent[2].x=(double) src->width;
2185 extent[2].y=(double) src->height;
2186 extent[3].x=0.0;
2187 extent[3].y=(double) src->height;
2188
2189 for (i=0; i < 4; i++) {
2190 point=extent[i];
2191 if (gdAffineApplyToPointF(&extent[i], &point, affine) != GD_TRUE) {
2192 return GD_FALSE;
2193 }
2194 }
2195 min=extent[0];
2196 max=extent[0];
2197
2198 for (i=1; i < 4; i++) {
2199 if (min.x > extent[i].x)
2200 min.x=extent[i].x;
2201 if (min.y > extent[i].y)
2202 min.y=extent[i].y;
2203 if (max.x < extent[i].x)
2204 max.x=extent[i].x;
2205 if (max.y < extent[i].y)
2206 max.y=extent[i].y;
2207 }
2208 bbox->x = (int) min.x;
2209 bbox->y = (int) min.y;
2210 bbox->width = (int) ceil((max.x - min.x));
2211 bbox->height = (int) ceil(max.y - min.y);
2212
2213 return GD_TRUE;
2214 }
2215
2216 /**
2217 * Group: Interpolation Method
2218 */
2219
2220 /**
2221 * Function: gdImageSetInterpolationMethod
2222 *
2223 * Set the interpolation method for subsequent operations
2224 *
2225 * Parameters:
2226 * im - The image.
2227 * id - The interpolation method.
2228 *
2229 * Returns:
2230 * Non-zero on success, zero on failure.
2231 *
2232 * See also:
2233 * - <gdInterpolationMethod>
2234 * - <gdImageGetInterpolationMethod>
2235 */
gdImageSetInterpolationMethod(gdImagePtr im,gdInterpolationMethod id)2236 BGD_DECLARE(int) gdImageSetInterpolationMethod(gdImagePtr im, gdInterpolationMethod id)
2237 {
2238 if (im == NULL || (uintmax_t)id > GD_METHOD_COUNT) {
2239 return 0;
2240 }
2241
2242 switch (id) {
2243 case GD_NEAREST_NEIGHBOUR:
2244 case GD_WEIGHTED4:
2245 im->interpolation = NULL;
2246 break;
2247
2248 /* generic versions*/
2249 /* GD_BILINEAR_FIXED and GD_BICUBIC_FIXED are kept for BC reasons */
2250 case GD_BILINEAR_FIXED:
2251 case GD_LINEAR:
2252 im->interpolation = filter_linear;
2253 break;
2254 case GD_BELL:
2255 im->interpolation = filter_bell;
2256 break;
2257 case GD_BESSEL:
2258 im->interpolation = filter_bessel;
2259 break;
2260 case GD_BICUBIC_FIXED:
2261 case GD_BICUBIC:
2262 im->interpolation = filter_bicubic;
2263 break;
2264 case GD_BLACKMAN:
2265 im->interpolation = filter_blackman;
2266 break;
2267 case GD_BOX:
2268 im->interpolation = filter_box;
2269 break;
2270 case GD_BSPLINE:
2271 im->interpolation = filter_bspline;
2272 break;
2273 case GD_CATMULLROM:
2274 im->interpolation = filter_catmullrom;
2275 break;
2276 case GD_GAUSSIAN:
2277 im->interpolation = filter_gaussian;
2278 break;
2279 case GD_GENERALIZED_CUBIC:
2280 im->interpolation = filter_generalized_cubic;
2281 break;
2282 case GD_HERMITE:
2283 im->interpolation = filter_hermite;
2284 break;
2285 case GD_HAMMING:
2286 im->interpolation = filter_hamming;
2287 break;
2288 case GD_HANNING:
2289 im->interpolation = filter_hanning;
2290 break;
2291 case GD_MITCHELL:
2292 im->interpolation = filter_mitchell;
2293 break;
2294 case GD_POWER:
2295 im->interpolation = filter_power;
2296 break;
2297 case GD_QUADRATIC:
2298 im->interpolation = filter_quadratic;
2299 break;
2300 case GD_SINC:
2301 im->interpolation = filter_sinc;
2302 break;
2303 case GD_TRIANGLE:
2304 im->interpolation = filter_triangle;
2305 break;
2306 case GD_DEFAULT:
2307 id = GD_LINEAR;
2308 im->interpolation = filter_linear;
2309 break;
2310 default:
2311 return 0;
2312 }
2313 im->interpolation_id = id;
2314 return 1;
2315 }
2316
2317
2318 /**
2319 * Function: gdImageGetInterpolationMethod
2320 *
2321 * Get the current interpolation method
2322 *
2323 * This is here so that the value can be read via a language or VM with an FFI
2324 * but no (portable) way to extract the value from the struct.
2325 *
2326 * Parameters:
2327 * im - The image.
2328 *
2329 * Returns:
2330 * The current interpolation method.
2331 *
2332 * See also:
2333 * - <gdInterpolationMethod>
2334 * - <gdImageSetInterpolationMethod>
2335 */
gdImageGetInterpolationMethod(gdImagePtr im)2336 BGD_DECLARE(gdInterpolationMethod) gdImageGetInterpolationMethod(gdImagePtr im)
2337 {
2338 return im->interpolation_id;
2339 }
2340
2341 #ifdef _MSC_VER
2342 # pragma optimize("", on)
2343 #endif
2344