1 /*
2 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3 % %
4 % %
5 % %
6 % RRRR EEEEE SSSSS IIIII ZZZZZ EEEEE %
7 % R R E SS I ZZ E %
8 % RRRR EEE SSS I ZZZ EEE %
9 % R R E SS I ZZ E %
10 % R R EEEEE SSSSS IIIII ZZZZZ EEEEE %
11 % %
12 % %
13 % MagickCore Image Resize Methods %
14 % %
15 % Software Design %
16 % Cristy %
17 % July 1992 %
18 % %
19 % %
20 % Copyright 1999-2021 ImageMagick Studio LLC, a non-profit organization %
21 % dedicated to making software imaging solutions freely available. %
22 % %
23 % You may not use this file except in compliance with the License. You may %
24 % obtain a copy of the License at %
25 % %
26 % https://imagemagick.org/script/license.php %
27 % %
28 % Unless required by applicable law or agreed to in writing, software %
29 % distributed under the License is distributed on an "AS IS" BASIS, %
30 % WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. %
31 % See the License for the specific language governing permissions and %
32 % limitations under the License. %
33 % %
34 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
35 %
36 %
37 */
38
39 /*
40 Include declarations.
41 */
42 #include "MagickCore/studio.h"
43 #include "MagickCore/accelerate-private.h"
44 #include "MagickCore/artifact.h"
45 #include "MagickCore/blob.h"
46 #include "MagickCore/cache.h"
47 #include "MagickCore/cache-view.h"
48 #include "MagickCore/channel.h"
49 #include "MagickCore/color.h"
50 #include "MagickCore/color-private.h"
51 #include "MagickCore/draw.h"
52 #include "MagickCore/exception.h"
53 #include "MagickCore/exception-private.h"
54 #include "MagickCore/gem.h"
55 #include "MagickCore/image.h"
56 #include "MagickCore/image-private.h"
57 #include "MagickCore/list.h"
58 #include "MagickCore/memory_.h"
59 #include "MagickCore/memory-private.h"
60 #include "MagickCore/magick.h"
61 #include "MagickCore/pixel-accessor.h"
62 #include "MagickCore/property.h"
63 #include "MagickCore/monitor.h"
64 #include "MagickCore/monitor-private.h"
65 #include "MagickCore/nt-base-private.h"
66 #include "MagickCore/option.h"
67 #include "MagickCore/pixel.h"
68 #include "MagickCore/pixel-private.h"
69 #include "MagickCore/quantum-private.h"
70 #include "MagickCore/resample.h"
71 #include "MagickCore/resample-private.h"
72 #include "MagickCore/resize.h"
73 #include "MagickCore/resize-private.h"
74 #include "MagickCore/resource_.h"
75 #include "MagickCore/string_.h"
76 #include "MagickCore/string-private.h"
77 #include "MagickCore/thread-private.h"
78 #include "MagickCore/token.h"
79 #include "MagickCore/utility.h"
80 #include "MagickCore/utility-private.h"
81 #include "MagickCore/version.h"
82 #if defined(MAGICKCORE_LQR_DELEGATE)
83 #include <lqr.h>
84 #endif
85
86 /*
87 Typedef declarations.
88 */
89 struct _ResizeFilter
90 {
91 double
92 (*filter)(const double,const ResizeFilter *),
93 (*window)(const double,const ResizeFilter *),
94 support, /* filter region of support - the filter support limit */
95 window_support, /* window support, usally equal to support (expert only) */
96 scale, /* dimension scaling to fit window support (usally 1.0) */
97 blur, /* x-scale (blur-sharpen) */
98 coefficient[7]; /* cubic coefficents for BC-cubic filters */
99
100 ResizeWeightingFunctionType
101 filterWeightingType,
102 windowWeightingType;
103
104 size_t
105 signature;
106 };
107
108 /*
109 Forward declaractions.
110 */
111 static double
112 I0(double x),
113 BesselOrderOne(double),
114 Sinc(const double, const ResizeFilter *),
115 SincFast(const double, const ResizeFilter *);
116
117 /*
118 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
119 % %
120 % %
121 % %
122 + F i l t e r F u n c t i o n s %
123 % %
124 % %
125 % %
126 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
127 %
128 % These are the various filter and windowing functions that are provided.
129 %
130 % They are internal to this module only. See AcquireResizeFilterInfo() for
131 % details of the access to these functions, via the GetResizeFilterSupport()
132 % and GetResizeFilterWeight() API interface.
133 %
134 % The individual filter functions have this format...
135 %
136 % static MagickRealtype *FilterName(const double x,const double support)
137 %
138 % A description of each parameter follows:
139 %
140 % o x: the distance from the sampling point generally in the range of 0 to
141 % support. The GetResizeFilterWeight() ensures this a positive value.
142 %
143 % o resize_filter: current filter information. This allows function to
144 % access support, and possibly other pre-calculated information defining
145 % the functions.
146 %
147 */
148
Blackman(const double x,const ResizeFilter * magick_unused (resize_filter))149 static double Blackman(const double x,
150 const ResizeFilter *magick_unused(resize_filter))
151 {
152 /*
153 Blackman: 2nd order cosine windowing function:
154 0.42 + 0.5 cos(pi x) + 0.08 cos(2pi x)
155
156 Refactored by Chantal Racette and Nicolas Robidoux to one trig call and
157 five flops.
158 */
159 const double cosine = cos((double) (MagickPI*x));
160 magick_unreferenced(resize_filter);
161 return(0.34+cosine*(0.5+cosine*0.16));
162 }
163
Bohman(const double x,const ResizeFilter * magick_unused (resize_filter))164 static double Bohman(const double x,
165 const ResizeFilter *magick_unused(resize_filter))
166 {
167 /*
168 Bohman: 2rd Order cosine windowing function:
169 (1-x) cos(pi x) + sin(pi x) / pi.
170
171 Refactored by Nicolas Robidoux to one trig call, one sqrt call, and 7 flops,
172 taking advantage of the fact that the support of Bohman is 1.0 (so that we
173 know that sin(pi x) >= 0).
174 */
175 const double cosine = cos((double) (MagickPI*x));
176 const double sine=sqrt(1.0-cosine*cosine);
177 magick_unreferenced(resize_filter);
178 return((1.0-x)*cosine+(1.0/MagickPI)*sine);
179 }
180
Box(const double magick_unused (x),const ResizeFilter * magick_unused (resize_filter))181 static double Box(const double magick_unused(x),
182 const ResizeFilter *magick_unused(resize_filter))
183 {
184 magick_unreferenced(x);
185 magick_unreferenced(resize_filter);
186
187 /*
188 A Box filter is a equal weighting function (all weights equal).
189 DO NOT LIMIT results by support or resize point sampling will work
190 as it requests points beyond its normal 0.0 support size.
191 */
192 return(1.0);
193 }
194
Cosine(const double x,const ResizeFilter * magick_unused (resize_filter))195 static double Cosine(const double x,
196 const ResizeFilter *magick_unused(resize_filter))
197 {
198 magick_unreferenced(resize_filter);
199
200 /*
201 Cosine window function:
202 cos((pi/2)*x).
203 */
204 return(cos((double) (MagickPI2*x)));
205 }
206
CubicBC(const double x,const ResizeFilter * resize_filter)207 static double CubicBC(const double x,const ResizeFilter *resize_filter)
208 {
209 /*
210 Cubic Filters using B,C determined values:
211 Mitchell-Netravali B = 1/3 C = 1/3 "Balanced" cubic spline filter
212 Catmull-Rom B = 0 C = 1/2 Interpolatory and exact on linears
213 Spline B = 1 C = 0 B-Spline Gaussian approximation
214 Hermite B = 0 C = 0 B-Spline interpolator
215
216 See paper by Mitchell and Netravali, Reconstruction Filters in Computer
217 Graphics Computer Graphics, Volume 22, Number 4, August 1988
218 http://www.cs.utexas.edu/users/fussell/courses/cs384g/lectures/mitchell/
219 Mitchell.pdf.
220
221 Coefficents are determined from B,C values:
222 P0 = ( 6 - 2*B )/6 = coeff[0]
223 P1 = 0
224 P2 = (-18 +12*B + 6*C )/6 = coeff[1]
225 P3 = ( 12 - 9*B - 6*C )/6 = coeff[2]
226 Q0 = ( 8*B +24*C )/6 = coeff[3]
227 Q1 = ( -12*B -48*C )/6 = coeff[4]
228 Q2 = ( 6*B +30*C )/6 = coeff[5]
229 Q3 = ( - 1*B - 6*C )/6 = coeff[6]
230
231 which are used to define the filter:
232
233 P0 + P1*x + P2*x^2 + P3*x^3 0 <= x < 1
234 Q0 + Q1*x + Q2*x^2 + Q3*x^3 1 <= x < 2
235
236 which ensures function is continuous in value and derivative (slope).
237 */
238 if (x < 1.0)
239 return(resize_filter->coefficient[0]+x*(x*
240 (resize_filter->coefficient[1]+x*resize_filter->coefficient[2])));
241 if (x < 2.0)
242 return(resize_filter->coefficient[3]+x*(resize_filter->coefficient[4]+x*
243 (resize_filter->coefficient[5]+x*resize_filter->coefficient[6])));
244 return(0.0);
245 }
246
CubicSpline(const double x,const ResizeFilter * resize_filter)247 static double CubicSpline(const double x,const ResizeFilter *resize_filter)
248 {
249 if (resize_filter->support <= 2.0)
250 {
251 /*
252 2-lobe Spline filter.
253 */
254 if (x < 1.0)
255 return(((x-9.0/5.0)*x-1.0/5.0)*x+1.0);
256 if (x < 2.0)
257 return(((-1.0/3.0*(x-1.0)+4.0/5.0)*(x-1.0)-7.0/15.0)*(x-1.0));
258 return(0.0);
259 }
260 if (resize_filter->support <= 3.0)
261 {
262 /*
263 3-lobe Spline filter.
264 */
265 if (x < 1.0)
266 return(((13.0/11.0*x-453.0/209.0)*x-3.0/209.0)*x+1.0);
267 if (x < 2.0)
268 return(((-6.0/11.0*(x-1.0)+270.0/209.0)*(x-1.0)-156.0/209.0)*(x-1.0));
269 if (x < 3.0)
270 return(((1.0/11.0*(x-2.0)-45.0/209.0)*(x-2.0)+26.0/209.0)*(x-2.0));
271 return(0.0);
272 }
273 /*
274 4-lobe Spline filter.
275 */
276 if (x < 1.0)
277 return(((49.0/41.0*x-6387.0/2911.0)*x-3.0/2911.0)*x+1.0);
278 if (x < 2.0)
279 return(((-24.0/41.0*(x-1.0)+4032.0/2911.0)*(x-1.0)-2328.0/2911.0)*(x-1.0));
280 if (x < 3.0)
281 return(((6.0/41.0*(x-2.0)-1008.0/2911.0)*(x-2.0)+582.0/2911.0)*(x-2.0));
282 if (x < 4.0)
283 return(((-1.0/41.0*(x-3.0)+168.0/2911.0)*(x-3.0)-97.0/2911.0)*(x-3.0));
284 return(0.0);
285 }
286
Gaussian(const double x,const ResizeFilter * resize_filter)287 static double Gaussian(const double x,const ResizeFilter *resize_filter)
288 {
289 /*
290 Gaussian with a sigma = 1/2 (or as user specified)
291
292 Gaussian Formula (1D) ...
293 exp( -(x^2)/((2.0*sigma^2) ) / (sqrt(2*PI)*sigma^2))
294
295 Gaussian Formula (2D) ...
296 exp( -(x^2+y^2)/(2.0*sigma^2) ) / (PI*sigma^2) )
297 or for radius
298 exp( -(r^2)/(2.0*sigma^2) ) / (PI*sigma^2) )
299
300 Note that it is only a change from 1-d to radial form is in the
301 normalization multiplier which is not needed or used when Gaussian is used
302 as a filter.
303
304 The constants are pre-calculated...
305
306 coeff[0]=sigma;
307 coeff[1]=1.0/(2.0*sigma^2);
308 coeff[2]=1.0/(sqrt(2*PI)*sigma^2);
309
310 exp( -coeff[1]*(x^2)) ) * coeff[2];
311
312 However the multiplier coeff[1] is need, the others are informative only.
313
314 This separates the gaussian 'sigma' value from the 'blur/support'
315 settings allowing for its use in special 'small sigma' gaussians,
316 without the filter 'missing' pixels because the support becomes too
317 small.
318 */
319 return(exp((double)(-resize_filter->coefficient[1]*x*x)));
320 }
321
Hann(const double x,const ResizeFilter * magick_unused (resize_filter))322 static double Hann(const double x,
323 const ResizeFilter *magick_unused(resize_filter))
324 {
325 /*
326 Cosine window function:
327 0.5+0.5*cos(pi*x).
328 */
329 const double cosine = cos((double) (MagickPI*x));
330 magick_unreferenced(resize_filter);
331 return(0.5+0.5*cosine);
332 }
333
Hamming(const double x,const ResizeFilter * magick_unused (resize_filter))334 static double Hamming(const double x,
335 const ResizeFilter *magick_unused(resize_filter))
336 {
337 /*
338 Offset cosine window function:
339 .54 + .46 cos(pi x).
340 */
341 const double cosine = cos((double) (MagickPI*x));
342 magick_unreferenced(resize_filter);
343 return(0.54+0.46*cosine);
344 }
345
Jinc(const double x,const ResizeFilter * magick_unused (resize_filter))346 static double Jinc(const double x,
347 const ResizeFilter *magick_unused(resize_filter))
348 {
349 magick_unreferenced(resize_filter);
350
351 /*
352 See Pratt "Digital Image Processing" p.97 for Jinc/Bessel functions.
353 http://mathworld.wolfram.com/JincFunction.html and page 11 of
354 http://www.ph.ed.ac.uk/%7ewjh/teaching/mo/slides/lens/lens.pdf
355
356 The original "zoom" program by Paul Heckbert called this "Bessel". But
357 really it is more accurately named "Jinc".
358 */
359 if (x == 0.0)
360 return(0.5*MagickPI);
361 return(BesselOrderOne(MagickPI*x)/x);
362 }
363
Kaiser(const double x,const ResizeFilter * resize_filter)364 static double Kaiser(const double x,const ResizeFilter *resize_filter)
365 {
366 /*
367 Kaiser Windowing Function (bessel windowing)
368
369 I0( beta * sqrt( 1-x^2) ) / IO(0)
370
371 Beta (coeff[0]) is a free value from 5 to 8 (defaults to 6.5).
372 However it is typically defined in terms of Alpha*PI
373
374 The normalization factor (coeff[1]) is not actually needed,
375 but without it the filters has a large value at x=0 making it
376 difficult to compare the function with other windowing functions.
377 */
378 return(resize_filter->coefficient[1]*I0(resize_filter->coefficient[0]*
379 sqrt((double) (1.0-x*x))));
380 }
381
Lagrange(const double x,const ResizeFilter * resize_filter)382 static double Lagrange(const double x,const ResizeFilter *resize_filter)
383 {
384 double
385 value;
386
387 ssize_t
388 i;
389
390 ssize_t
391 n,
392 order;
393
394 /*
395 Lagrange piecewise polynomial fit of sinc: N is the 'order' of the lagrange
396 function and depends on the overall support window size of the filter. That
397 is: for a support of 2, it gives a lagrange-4 (piecewise cubic function).
398
399 "n" identifies the piece of the piecewise polynomial.
400
401 See Survey: Interpolation Methods, IEEE Transactions on Medical Imaging,
402 Vol 18, No 11, November 1999, p1049-1075, -- Equation 27 on p1064.
403 */
404 if (x > resize_filter->support)
405 return(0.0);
406 order=(ssize_t) (2.0*resize_filter->window_support); /* number of pieces */
407 n=(ssize_t) (resize_filter->window_support+x);
408 value=1.0f;
409 for (i=0; i < order; i++)
410 if (i != n)
411 value*=(n-i-x)/(n-i);
412 return(value);
413 }
414
Quadratic(const double x,const ResizeFilter * magick_unused (resize_filter))415 static double Quadratic(const double x,
416 const ResizeFilter *magick_unused(resize_filter))
417 {
418 magick_unreferenced(resize_filter);
419
420 /*
421 2rd order (quadratic) B-Spline approximation of Gaussian.
422 */
423 if (x < 0.5)
424 return(0.75-x*x);
425 if (x < 1.5)
426 return(0.5*(x-1.5)*(x-1.5));
427 return(0.0);
428 }
429
Sinc(const double x,const ResizeFilter * magick_unused (resize_filter))430 static double Sinc(const double x,
431 const ResizeFilter *magick_unused(resize_filter))
432 {
433 magick_unreferenced(resize_filter);
434
435 /*
436 Scaled sinc(x) function using a trig call:
437 sinc(x) == sin(pi x)/(pi x).
438 */
439 if (x != 0.0)
440 {
441 const double alpha=(double) (MagickPI*x);
442 return(sin((double) alpha)/alpha);
443 }
444 return((double) 1.0);
445 }
446
SincFast(const double x,const ResizeFilter * magick_unused (resize_filter))447 static double SincFast(const double x,
448 const ResizeFilter *magick_unused(resize_filter))
449 {
450 magick_unreferenced(resize_filter);
451
452 /*
453 Approximations of the sinc function sin(pi x)/(pi x) over the interval
454 [-4,4] constructed by Nicolas Robidoux and Chantal Racette with funding
455 from the Natural Sciences and Engineering Research Council of Canada.
456
457 Although the approximations are polynomials (for low order of
458 approximation) and quotients of polynomials (for higher order of
459 approximation) and consequently are similar in form to Taylor polynomials /
460 Pade approximants, the approximations are computed with a completely
461 different technique.
462
463 Summary: These approximations are "the best" in terms of bang (accuracy)
464 for the buck (flops). More specifically: Among the polynomial quotients
465 that can be computed using a fixed number of flops (with a given "+ - * /
466 budget"), the chosen polynomial quotient is the one closest to the
467 approximated function with respect to maximum absolute relative error over
468 the given interval.
469
470 The Remez algorithm, as implemented in the boost library's minimax package,
471 is the key to the construction: http://www.boost.org/doc/libs/1_36_0/libs/
472 math/doc/sf_and_dist/html/math_toolkit/backgrounders/remez.html
473
474 If outside of the interval of approximation, use the standard trig formula.
475 */
476 if (x > 4.0)
477 {
478 const double alpha=(double) (MagickPI*x);
479 return(sin((double) alpha)/alpha);
480 }
481 {
482 /*
483 The approximations only depend on x^2 (sinc is an even function).
484 */
485 const double xx = x*x;
486 #if MAGICKCORE_QUANTUM_DEPTH <= 8
487 /*
488 Maximum absolute relative error 6.3e-6 < 1/2^17.
489 */
490 const double c0 = 0.173610016489197553621906385078711564924e-2L;
491 const double c1 = -0.384186115075660162081071290162149315834e-3L;
492 const double c2 = 0.393684603287860108352720146121813443561e-4L;
493 const double c3 = -0.248947210682259168029030370205389323899e-5L;
494 const double c4 = 0.107791837839662283066379987646635416692e-6L;
495 const double c5 = -0.324874073895735800961260474028013982211e-8L;
496 const double c6 = 0.628155216606695311524920882748052490116e-10L;
497 const double c7 = -0.586110644039348333520104379959307242711e-12L;
498 const double p =
499 c0+xx*(c1+xx*(c2+xx*(c3+xx*(c4+xx*(c5+xx*(c6+xx*c7))))));
500 return((xx-1.0)*(xx-4.0)*(xx-9.0)*(xx-16.0)*p);
501 #elif MAGICKCORE_QUANTUM_DEPTH <= 16
502 /*
503 Max. abs. rel. error 2.2e-8 < 1/2^25.
504 */
505 const double c0 = 0.173611107357320220183368594093166520811e-2L;
506 const double c1 = -0.384240921114946632192116762889211361285e-3L;
507 const double c2 = 0.394201182359318128221229891724947048771e-4L;
508 const double c3 = -0.250963301609117217660068889165550534856e-5L;
509 const double c4 = 0.111902032818095784414237782071368805120e-6L;
510 const double c5 = -0.372895101408779549368465614321137048875e-8L;
511 const double c6 = 0.957694196677572570319816780188718518330e-10L;
512 const double c7 = -0.187208577776590710853865174371617338991e-11L;
513 const double c8 = 0.253524321426864752676094495396308636823e-13L;
514 const double c9 = -0.177084805010701112639035485248501049364e-15L;
515 const double p =
516 c0+xx*(c1+xx*(c2+xx*(c3+xx*(c4+xx*(c5+xx*(c6+xx*(c7+xx*(c8+xx*c9))))))));
517 return((xx-1.0)*(xx-4.0)*(xx-9.0)*(xx-16.0)*p);
518 #else
519 /*
520 Max. abs. rel. error 1.2e-12 < 1/2^39.
521 */
522 const double c0 = 0.173611111110910715186413700076827593074e-2L;
523 const double c1 = -0.289105544717893415815859968653611245425e-3L;
524 const double c2 = 0.206952161241815727624413291940849294025e-4L;
525 const double c3 = -0.834446180169727178193268528095341741698e-6L;
526 const double c4 = 0.207010104171026718629622453275917944941e-7L;
527 const double c5 = -0.319724784938507108101517564300855542655e-9L;
528 const double c6 = 0.288101675249103266147006509214934493930e-11L;
529 const double c7 = -0.118218971804934245819960233886876537953e-13L;
530 const double p =
531 c0+xx*(c1+xx*(c2+xx*(c3+xx*(c4+xx*(c5+xx*(c6+xx*c7))))));
532 const double d0 = 1.0L;
533 const double d1 = 0.547981619622284827495856984100563583948e-1L;
534 const double d2 = 0.134226268835357312626304688047086921806e-2L;
535 const double d3 = 0.178994697503371051002463656833597608689e-4L;
536 const double d4 = 0.114633394140438168641246022557689759090e-6L;
537 const double q = d0+xx*(d1+xx*(d2+xx*(d3+xx*d4)));
538 return((xx-1.0)*(xx-4.0)*(xx-9.0)*(xx-16.0)/q*p);
539 #endif
540 }
541 }
542
Triangle(const double x,const ResizeFilter * magick_unused (resize_filter))543 static double Triangle(const double x,
544 const ResizeFilter *magick_unused(resize_filter))
545 {
546 magick_unreferenced(resize_filter);
547
548 /*
549 1st order (linear) B-Spline, bilinear interpolation, Tent 1D filter, or
550 a Bartlett 2D Cone filter. Also used as a Bartlett Windowing function
551 for Sinc().
552 */
553 if (x < 1.0)
554 return(1.0-x);
555 return(0.0);
556 }
557
Welch(const double x,const ResizeFilter * magick_unused (resize_filter))558 static double Welch(const double x,
559 const ResizeFilter *magick_unused(resize_filter))
560 {
561 magick_unreferenced(resize_filter);
562
563 /*
564 Welch parabolic windowing filter.
565 */
566 if (x < 1.0)
567 return(1.0-x*x);
568 return(0.0);
569 }
570
571 /*
572 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
573 % %
574 % %
575 % %
576 + A c q u i r e R e s i z e F i l t e r %
577 % %
578 % %
579 % %
580 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
581 %
582 % AcquireResizeFilter() allocates the ResizeFilter structure. Choose from
583 % these filters:
584 %
585 % FIR (Finite impulse Response) Filters
586 % Box Triangle Quadratic
587 % Spline Hermite Catrom
588 % Mitchell
589 %
590 % IIR (Infinite impulse Response) Filters
591 % Gaussian Sinc Jinc (Bessel)
592 %
593 % Windowed Sinc/Jinc Filters
594 % Blackman Bohman Lanczos
595 % Hann Hamming Cosine
596 % Kaiser Welch Parzen
597 % Bartlett
598 %
599 % Special Purpose Filters
600 % Cubic SincFast LanczosSharp Lanczos2 Lanczos2Sharp
601 % Robidoux RobidouxSharp
602 %
603 % The users "-filter" selection is used to lookup the default 'expert'
604 % settings for that filter from a internal table. However any provided
605 % 'expert' settings (see below) may override this selection.
606 %
607 % FIR filters are used as is, and are limited to that filters support window
608 % (unless over-ridden). 'Gaussian' while classed as an IIR filter, is also
609 % simply clipped by its support size (currently 1.5 or approximately 3*sigma
610 % as recommended by many references)
611 %
612 % The special a 'cylindrical' filter flag will promote the default 4-lobed
613 % Windowed Sinc filter to a 3-lobed Windowed Jinc equivalent, which is better
614 % suited to this style of image resampling. This typically happens when using
615 % such a filter for images distortions.
616 %
617 % SPECIFIC FILTERS:
618 %
619 % Directly requesting 'Sinc', 'Jinc' function as a filter will force the use
620 % of function without any windowing, or promotion for cylindrical usage. This
621 % is not recommended, except by image processing experts, especially as part
622 % of expert option filter function selection.
623 %
624 % Two forms of the 'Sinc' function are available: Sinc and SincFast. Sinc is
625 % computed using the traditional sin(pi*x)/(pi*x); it is selected if the user
626 % specifically specifies the use of a Sinc filter. SincFast uses highly
627 % accurate (and fast) polynomial (low Q) and rational (high Q) approximations,
628 % and will be used by default in most cases.
629 %
630 % The Lanczos filter is a special 3-lobed Sinc-windowed Sinc filter (promoted
631 % to Jinc-windowed Jinc for cylindrical (Elliptical Weighted Average) use).
632 % The Sinc version is the most popular windowed filter.
633 %
634 % LanczosSharp is a slightly sharpened (blur=0.9812505644269356 < 1) form of
635 % the Lanczos filter, specifically designed for EWA distortion (as a
636 % Jinc-Jinc); it can also be used as a slightly sharper orthogonal Lanczos
637 % (Sinc-Sinc) filter. The chosen blur value comes as close as possible to
638 % satisfying the following condition without changing the character of the
639 % corresponding EWA filter:
640 %
641 % 'No-Op' Vertical and Horizontal Line Preservation Condition: Images with
642 % only vertical or horizontal features are preserved when performing 'no-op"
643 % with EWA distortion.
644 %
645 % The Lanczos2 and Lanczos2Sharp filters are 2-lobe versions of the Lanczos
646 % filters. The 'sharp' version uses a blur factor of 0.9549963639785485,
647 % again chosen because the resulting EWA filter comes as close as possible to
648 % satisfying the above condition.
649 %
650 % Robidoux is another filter tuned for EWA. It is the Keys cubic filter
651 % defined by B=(228 - 108 sqrt(2))/199. Robidoux satisfies the "'No-Op'
652 % Vertical and Horizontal Line Preservation Condition" exactly, and it
653 % moderately blurs high frequency 'pixel-hash' patterns under no-op. It turns
654 % out to be close to both Mitchell and Lanczos2Sharp. For example, its first
655 % crossing is at (36 sqrt(2) + 123)/(72 sqrt(2) + 47), almost the same as the
656 % first crossing of Mitchell and Lanczos2Sharp.
657 %
658 % RodidouxSharp is a slightly sharper version of Rodidoux, some believe it
659 % is too sharp. It is designed to minimize the maximum possible change in
660 % a pixel value which is at one of the extremes (e.g., 0 or 255) under no-op
661 % conditions. Amazingly Mitchell falls roughly between Rodidoux and
662 % RodidouxSharp, though this seems to have been pure coincidence.
663 %
664 % 'EXPERT' OPTIONS:
665 %
666 % These artifact "defines" are not recommended for production use without
667 % expert knowledge of resampling, filtering, and the effects they have on the
668 % resulting resampled (resized or distorted) image.
669 %
670 % They can be used to override any and all filter default, and it is
671 % recommended you make good use of "filter:verbose" to make sure that the
672 % overall effect of your selection (before and after) is as expected.
673 %
674 % "filter:verbose" controls whether to output the exact results of the
675 % filter selections made, as well as plotting data for graphing the
676 % resulting filter over the filters support range.
677 %
678 % "filter:filter" select the main function associated with this filter
679 % name, as the weighting function of the filter. This can be used to
680 % set a windowing function as a weighting function, for special
681 % purposes, such as graphing.
682 %
683 % If a "filter:window" operation has not been provided, a 'Box'
684 % windowing function will be set to denote that no windowing function is
685 % being used.
686 %
687 % "filter:window" Select this windowing function for the filter. While any
688 % filter could be used as a windowing function, using the 'first lobe' of
689 % that filter over the whole support window, using a non-windowing
690 % function is not advisible. If no weighting filter function is specified
691 % a 'SincFast' filter is used.
692 %
693 % "filter:lobes" Number of lobes to use for the Sinc/Jinc filter. This a
694 % simpler method of setting filter support size that will correctly
695 % handle the Sinc/Jinc switch for an operators filtering requirements.
696 % Only integers should be given.
697 %
698 % "filter:support" Set the support size for filtering to the size given.
699 % This not recommended for Sinc/Jinc windowed filters (lobes should be
700 % used instead). This will override any 'filter:lobes' option.
701 %
702 % "filter:win-support" Scale windowing function to this size instead. This
703 % causes the windowing (or self-windowing Lagrange filter) to act is if
704 % the support window it much much larger than what is actually supplied
705 % to the calling operator. The filter however is still clipped to the
706 % real support size given, by the support range supplied to the caller.
707 % If unset this will equal the normal filter support size.
708 %
709 % "filter:blur" Scale the filter and support window by this amount. A value
710 % of > 1 will generally result in a more blurred image with more ringing
711 % effects, while a value <1 will sharpen the resulting image with more
712 % aliasing effects.
713 %
714 % "filter:sigma" The sigma value to use for the Gaussian filter only.
715 % Defaults to '1/2'. Using a different sigma effectively provides a
716 % method of using the filter as a 'blur' convolution. Particularly when
717 % using it for Distort.
718 %
719 % "filter:b"
720 % "filter:c" Override the preset B,C values for a Cubic filter.
721 % If only one of these are given it is assumes to be a 'Keys' type of
722 % filter such that B+2C=1, where Keys 'alpha' value = C.
723 %
724 % Examples:
725 %
726 % Set a true un-windowed Sinc filter with 10 lobes (very slow):
727 % -define filter:filter=Sinc
728 % -define filter:lobes=8
729 %
730 % Set an 8 lobe Lanczos (Sinc or Jinc) filter:
731 % -filter Lanczos
732 % -define filter:lobes=8
733 %
734 % The format of the AcquireResizeFilter method is:
735 %
736 % ResizeFilter *AcquireResizeFilter(const Image *image,
737 % const FilterType filter_type,const MagickBooleanType cylindrical,
738 % ExceptionInfo *exception)
739 %
740 % A description of each parameter follows:
741 %
742 % o image: the image.
743 %
744 % o filter: the filter type, defining a preset filter, window and support.
745 % The artifact settings listed above will override those selections.
746 %
747 % o blur: blur the filter by this amount, use 1.0 if unknown. Image
748 % artifact "filter:blur" will override this API call usage, including any
749 % internal change (such as for cylindrical usage).
750 %
751 % o radial: use a 1D orthogonal filter (Sinc) or 2D cylindrical (radial)
752 % filter (Jinc).
753 %
754 % o exception: return any errors or warnings in this structure.
755 %
756 */
AcquireResizeFilter(const Image * image,const FilterType filter,const MagickBooleanType cylindrical,ExceptionInfo * exception)757 MagickPrivate ResizeFilter *AcquireResizeFilter(const Image *image,
758 const FilterType filter,const MagickBooleanType cylindrical,
759 ExceptionInfo *exception)
760 {
761 const char
762 *artifact;
763
764 FilterType
765 filter_type,
766 window_type;
767
768 double
769 B,
770 C,
771 value;
772
773 ResizeFilter
774 *resize_filter;
775
776 /*
777 Table Mapping given Filter, into Weighting and Windowing functions.
778 A 'Box' windowing function means its a simble non-windowed filter.
779 An 'SincFast' filter function could be upgraded to a 'Jinc' filter if a
780 "cylindrical" is requested, unless a 'Sinc' or 'SincFast' filter was
781 specifically requested by the user.
782
783 WARNING: The order of this table must match the order of the FilterType
784 enumeration specified in "resample.h", or the filter names will not match
785 the filter being setup.
786
787 You can check filter setups with the "filter:verbose" expert setting.
788 */
789 static struct
790 {
791 FilterType
792 filter,
793 window;
794 } const mapping[SentinelFilter] =
795 {
796 { UndefinedFilter, BoxFilter }, /* Undefined (default to Box) */
797 { PointFilter, BoxFilter }, /* SPECIAL: Nearest neighbour */
798 { BoxFilter, BoxFilter }, /* Box averaging filter */
799 { TriangleFilter, BoxFilter }, /* Linear interpolation filter */
800 { HermiteFilter, BoxFilter }, /* Hermite interpolation filter */
801 { SincFastFilter, HannFilter }, /* Hann -- cosine-sinc */
802 { SincFastFilter, HammingFilter }, /* Hamming -- '' variation */
803 { SincFastFilter, BlackmanFilter }, /* Blackman -- 2*cosine-sinc */
804 { GaussianFilter, BoxFilter }, /* Gaussian blur filter */
805 { QuadraticFilter, BoxFilter }, /* Quadratic Gaussian approx */
806 { CubicFilter, BoxFilter }, /* General Cubic Filter, Spline */
807 { CatromFilter, BoxFilter }, /* Cubic-Keys interpolator */
808 { MitchellFilter, BoxFilter }, /* 'Ideal' Cubic-Keys filter */
809 { JincFilter, BoxFilter }, /* Raw 3-lobed Jinc function */
810 { SincFilter, BoxFilter }, /* Raw 4-lobed Sinc function */
811 { SincFastFilter, BoxFilter }, /* Raw fast sinc ("Pade"-type) */
812 { SincFastFilter, KaiserFilter }, /* Kaiser -- square root-sinc */
813 { LanczosFilter, WelchFilter }, /* Welch -- parabolic (3 lobe) */
814 { SincFastFilter, CubicFilter }, /* Parzen -- cubic-sinc */
815 { SincFastFilter, BohmanFilter }, /* Bohman -- 2*cosine-sinc */
816 { SincFastFilter, TriangleFilter }, /* Bartlett -- triangle-sinc */
817 { LagrangeFilter, BoxFilter }, /* Lagrange self-windowing */
818 { LanczosFilter, LanczosFilter }, /* Lanczos Sinc-Sinc filters */
819 { LanczosSharpFilter, LanczosSharpFilter }, /* | these require */
820 { Lanczos2Filter, Lanczos2Filter }, /* | special handling */
821 { Lanczos2SharpFilter, Lanczos2SharpFilter },
822 { RobidouxFilter, BoxFilter }, /* Cubic Keys tuned for EWA */
823 { RobidouxSharpFilter, BoxFilter }, /* Sharper Cubic Keys for EWA */
824 { LanczosFilter, CosineFilter }, /* Cosine window (3 lobes) */
825 { SplineFilter, BoxFilter }, /* Spline Cubic Filter */
826 { LanczosRadiusFilter, LanczosFilter }, /* Lanczos with integer radius */
827 { CubicSplineFilter, BoxFilter }, /* CubicSpline (2/3/4 lobes) */
828 };
829 /*
830 Table mapping the filter/window from the above table to an actual function.
831 The default support size for that filter as a weighting function, the range
832 to scale with to use that function as a sinc windowing function, (typ 1.0).
833
834 Note that the filter_type -> function is 1 to 1 except for Sinc(),
835 SincFast(), and CubicBC() functions, which may have multiple filter to
836 function associations.
837
838 See "filter:verbose" handling below for the function -> filter mapping.
839 */
840 static struct
841 {
842 double
843 (*function)(const double,const ResizeFilter*),
844 support, /* Default lobes/support size of the weighting filter. */
845 scale, /* Support when function used as a windowing function
846 Typically equal to the location of the first zero crossing. */
847 B,C; /* BC-spline coefficients, ignored if not a CubicBC filter. */
848 ResizeWeightingFunctionType weightingFunctionType;
849 } const filters[SentinelFilter] =
850 {
851 /* .--- support window (if used as a Weighting Function)
852 | .--- first crossing (if used as a Windowing Function)
853 | | .--- B value for Cubic Function
854 | | | .---- C value for Cubic Function
855 | | | | */
856 { Box, 0.5, 0.5, 0.0, 0.0, BoxWeightingFunction }, /* Undefined (default to Box) */
857 { Box, 0.0, 0.5, 0.0, 0.0, BoxWeightingFunction }, /* Point (special handling) */
858 { Box, 0.5, 0.5, 0.0, 0.0, BoxWeightingFunction }, /* Box */
859 { Triangle, 1.0, 1.0, 0.0, 0.0, TriangleWeightingFunction }, /* Triangle */
860 { CubicBC, 1.0, 1.0, 0.0, 0.0, CubicBCWeightingFunction }, /* Hermite (cubic B=C=0) */
861 { Hann, 1.0, 1.0, 0.0, 0.0, HannWeightingFunction }, /* Hann, cosine window */
862 { Hamming, 1.0, 1.0, 0.0, 0.0, HammingWeightingFunction }, /* Hamming, '' variation */
863 { Blackman, 1.0, 1.0, 0.0, 0.0, BlackmanWeightingFunction }, /* Blackman, 2*cosine window */
864 { Gaussian, 2.0, 1.5, 0.0, 0.0, GaussianWeightingFunction }, /* Gaussian */
865 { Quadratic, 1.5, 1.5, 0.0, 0.0, QuadraticWeightingFunction },/* Quadratic gaussian */
866 { CubicBC, 2.0, 2.0, 1.0, 0.0, CubicBCWeightingFunction }, /* General Cubic Filter */
867 { CubicBC, 2.0, 1.0, 0.0, 0.5, CubicBCWeightingFunction }, /* Catmull-Rom (B=0,C=1/2) */
868 { CubicBC, 2.0, 8.0/7.0, 1./3., 1./3., CubicBCWeightingFunction }, /* Mitchell (B=C=1/3) */
869 { Jinc, 3.0, 1.2196698912665045, 0.0, 0.0, JincWeightingFunction }, /* Raw 3-lobed Jinc */
870 { Sinc, 4.0, 1.0, 0.0, 0.0, SincWeightingFunction }, /* Raw 4-lobed Sinc */
871 { SincFast, 4.0, 1.0, 0.0, 0.0, SincFastWeightingFunction }, /* Raw fast sinc ("Pade"-type) */
872 { Kaiser, 1.0, 1.0, 0.0, 0.0, KaiserWeightingFunction }, /* Kaiser (square root window) */
873 { Welch, 1.0, 1.0, 0.0, 0.0, WelchWeightingFunction }, /* Welch (parabolic window) */
874 { CubicBC, 2.0, 2.0, 1.0, 0.0, CubicBCWeightingFunction }, /* Parzen (B-Spline window) */
875 { Bohman, 1.0, 1.0, 0.0, 0.0, BohmanWeightingFunction }, /* Bohman, 2*Cosine window */
876 { Triangle, 1.0, 1.0, 0.0, 0.0, TriangleWeightingFunction }, /* Bartlett (triangle window) */
877 { Lagrange, 2.0, 1.0, 0.0, 0.0, LagrangeWeightingFunction }, /* Lagrange sinc approximation */
878 { SincFast, 3.0, 1.0, 0.0, 0.0, SincFastWeightingFunction }, /* Lanczos, 3-lobed Sinc-Sinc */
879 { SincFast, 3.0, 1.0, 0.0, 0.0, SincFastWeightingFunction }, /* Lanczos, Sharpened */
880 { SincFast, 2.0, 1.0, 0.0, 0.0, SincFastWeightingFunction }, /* Lanczos, 2-lobed */
881 { SincFast, 2.0, 1.0, 0.0, 0.0, SincFastWeightingFunction }, /* Lanczos2, sharpened */
882 /* Robidoux: Keys cubic close to Lanczos2D sharpened */
883 { CubicBC, 2.0, 1.1685777620836932,
884 0.37821575509399867, 0.31089212245300067, CubicBCWeightingFunction },
885 /* RobidouxSharp: Sharper version of Robidoux */
886 { CubicBC, 2.0, 1.105822933719019,
887 0.2620145123990142, 0.3689927438004929, CubicBCWeightingFunction },
888 { Cosine, 1.0, 1.0, 0.0, 0.0, CosineWeightingFunction }, /* Low level cosine window */
889 { CubicBC, 2.0, 2.0, 1.0, 0.0, CubicBCWeightingFunction }, /* Cubic B-Spline (B=1,C=0) */
890 { SincFast, 3.0, 1.0, 0.0, 0.0, SincFastWeightingFunction }, /* Lanczos, Interger Radius */
891 { CubicSpline,2.0, 0.5, 0.0, 0.0, BoxWeightingFunction }, /* Spline Lobes 2-lobed */
892 };
893 /*
894 The known zero crossings of the Jinc() or more accurately the Jinc(x*PI)
895 function being used as a filter. It is used by the "filter:lobes" expert
896 setting and for 'lobes' for Jinc functions in the previous table. This way
897 users do not have to deal with the highly irrational lobe sizes of the Jinc
898 filter.
899
900 Values taken from
901 http://cose.math.bas.bg/webMathematica/webComputing/BesselZeros.jsp
902 using Jv-function with v=1, then dividing by PI.
903 */
904 static double
905 jinc_zeros[16] =
906 {
907 1.2196698912665045,
908 2.2331305943815286,
909 3.2383154841662362,
910 4.2410628637960699,
911 5.2427643768701817,
912 6.2439216898644877,
913 7.2447598687199570,
914 8.2453949139520427,
915 9.2458926849494673,
916 10.246293348754916,
917 11.246622794877883,
918 12.246898461138105,
919 13.247132522181061,
920 14.247333735806849,
921 15.247508563037300,
922 16.247661874700962
923 };
924
925 /*
926 Allocate resize filter.
927 */
928 assert(image != (const Image *) NULL);
929 assert(image->signature == MagickCoreSignature);
930 if (image->debug != MagickFalse)
931 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
932 assert(UndefinedFilter < filter && filter < SentinelFilter);
933 assert(exception != (ExceptionInfo *) NULL);
934 assert(exception->signature == MagickCoreSignature);
935 (void) exception;
936 resize_filter=(ResizeFilter *) AcquireCriticalMemory(sizeof(*resize_filter));
937 (void) memset(resize_filter,0,sizeof(*resize_filter));
938 /*
939 Defaults for the requested filter.
940 */
941 filter_type=mapping[filter].filter;
942 window_type=mapping[filter].window;
943 resize_filter->blur=1.0;
944 /* Promote 1D Windowed Sinc Filters to a 2D Windowed Jinc filters */
945 if ((cylindrical != MagickFalse) && (filter_type == SincFastFilter) &&
946 (filter != SincFastFilter))
947 filter_type=JincFilter; /* 1D Windowed Sinc => 2D Windowed Jinc filters */
948
949 /* Expert filter setting override */
950 artifact=GetImageArtifact(image,"filter:filter");
951 if (IsStringTrue(artifact) != MagickFalse)
952 {
953 ssize_t
954 option;
955
956 option=ParseCommandOption(MagickFilterOptions,MagickFalse,artifact);
957 if ((UndefinedFilter < option) && (option < SentinelFilter))
958 { /* Raw filter request - no window function. */
959 filter_type=(FilterType) option;
960 window_type=BoxFilter;
961 }
962 /* Filter override with a specific window function. */
963 artifact=GetImageArtifact(image,"filter:window");
964 if (artifact != (const char *) NULL)
965 {
966 option=ParseCommandOption(MagickFilterOptions,MagickFalse,artifact);
967 if ((UndefinedFilter < option) && (option < SentinelFilter))
968 window_type=(FilterType) option;
969 }
970 }
971 else
972 {
973 /* Window specified, but no filter function? Assume Sinc/Jinc. */
974 artifact=GetImageArtifact(image,"filter:window");
975 if (artifact != (const char *) NULL)
976 {
977 ssize_t
978 option;
979
980 option=ParseCommandOption(MagickFilterOptions,MagickFalse,artifact);
981 if ((UndefinedFilter < option) && (option < SentinelFilter))
982 {
983 filter_type= cylindrical != MagickFalse ? JincFilter
984 : SincFastFilter;
985 window_type=(FilterType) option;
986 }
987 }
988 }
989
990 /* Assign the real functions to use for the filters selected. */
991 resize_filter->filter=filters[filter_type].function;
992 resize_filter->support=filters[filter_type].support;
993 resize_filter->filterWeightingType=filters[filter_type].weightingFunctionType;
994 resize_filter->window=filters[window_type].function;
995 resize_filter->windowWeightingType=filters[window_type].weightingFunctionType;
996 resize_filter->scale=filters[window_type].scale;
997 resize_filter->signature=MagickCoreSignature;
998
999 /* Filter Modifications for orthogonal/cylindrical usage */
1000 if (cylindrical != MagickFalse)
1001 switch (filter_type)
1002 {
1003 case BoxFilter:
1004 /* Support for Cylindrical Box should be sqrt(2)/2 */
1005 resize_filter->support=(double) MagickSQ1_2;
1006 break;
1007 case LanczosFilter:
1008 case LanczosSharpFilter:
1009 case Lanczos2Filter:
1010 case Lanczos2SharpFilter:
1011 case LanczosRadiusFilter:
1012 resize_filter->filter=filters[JincFilter].function;
1013 resize_filter->window=filters[JincFilter].function;
1014 resize_filter->scale=filters[JincFilter].scale;
1015 /* number of lobes (support window size) remain unchanged */
1016 break;
1017 default:
1018 break;
1019 }
1020 /* Global Sharpening (regardless of orthoginal/cylindrical) */
1021 switch (filter_type)
1022 {
1023 case LanczosSharpFilter:
1024 resize_filter->blur *= 0.9812505644269356;
1025 break;
1026 case Lanczos2SharpFilter:
1027 resize_filter->blur *= 0.9549963639785485;
1028 break;
1029 /* case LanczosRadius: blur adjust is done after lobes */
1030 default:
1031 break;
1032 }
1033
1034 /*
1035 Expert Option Modifications.
1036 */
1037
1038 /* User Gaussian Sigma Override - no support change */
1039 if ((resize_filter->filter == Gaussian) ||
1040 (resize_filter->window == Gaussian) ) {
1041 value=0.5; /* guassian sigma default, half pixel */
1042 artifact=GetImageArtifact(image,"filter:sigma");
1043 if (artifact != (const char *) NULL)
1044 value=StringToDouble(artifact,(char **) NULL);
1045 /* Define coefficents for Gaussian */
1046 resize_filter->coefficient[0]=value; /* note sigma too */
1047 resize_filter->coefficient[1]=PerceptibleReciprocal(2.0*value*value); /* sigma scaling */
1048 resize_filter->coefficient[2]=PerceptibleReciprocal(Magick2PI*value*value);
1049 /* normalization - not actually needed or used! */
1050 if ( value > 0.5 )
1051 resize_filter->support *= 2*value; /* increase support linearly */
1052 }
1053
1054 /* User Kaiser Alpha Override - no support change */
1055 if ((resize_filter->filter == Kaiser) ||
1056 (resize_filter->window == Kaiser) ) {
1057 value=6.5; /* default beta value for Kaiser bessel windowing function */
1058 artifact=GetImageArtifact(image,"filter:alpha"); /* FUTURE: depreciate */
1059 if (artifact != (const char *) NULL)
1060 value=StringToDouble(artifact,(char **) NULL);
1061 artifact=GetImageArtifact(image,"filter:kaiser-beta");
1062 if (artifact != (const char *) NULL)
1063 value=StringToDouble(artifact,(char **) NULL);
1064 artifact=GetImageArtifact(image,"filter:kaiser-alpha");
1065 if (artifact != (const char *) NULL)
1066 value=StringToDouble(artifact,(char **) NULL)*MagickPI;
1067 /* Define coefficents for Kaiser Windowing Function */
1068 resize_filter->coefficient[0]=value; /* alpha */
1069 resize_filter->coefficient[1]=PerceptibleReciprocal(I0(value));
1070 /* normalization */
1071 }
1072
1073 /* Support Overrides */
1074 artifact=GetImageArtifact(image,"filter:lobes");
1075 if (artifact != (const char *) NULL)
1076 {
1077 ssize_t
1078 lobes;
1079
1080 lobes=(ssize_t) StringToLong(artifact);
1081 if (lobes < 1)
1082 lobes=1;
1083 resize_filter->support=(double) lobes;
1084 }
1085 if (resize_filter->filter == Jinc)
1086 {
1087 /*
1088 Convert a Jinc function lobes value to a real support value.
1089 */
1090 if (resize_filter->support > 16)
1091 resize_filter->support=jinc_zeros[15]; /* largest entry in table */
1092 else
1093 resize_filter->support=jinc_zeros[((long) resize_filter->support)-1];
1094 /*
1095 Blur this filter so support is a integer value (lobes dependant).
1096 */
1097 if (filter_type == LanczosRadiusFilter)
1098 resize_filter->blur*=floor(resize_filter->support)/
1099 resize_filter->support;
1100 }
1101 /*
1102 Expert blur override.
1103 */
1104 artifact=GetImageArtifact(image,"filter:blur");
1105 if (artifact != (const char *) NULL)
1106 resize_filter->blur*=StringToDouble(artifact,(char **) NULL);
1107 if (resize_filter->blur < MagickEpsilon)
1108 resize_filter->blur=(double) MagickEpsilon;
1109 /*
1110 Expert override of the support setting.
1111 */
1112 artifact=GetImageArtifact(image,"filter:support");
1113 if (artifact != (const char *) NULL)
1114 resize_filter->support=fabs(StringToDouble(artifact,(char **) NULL));
1115 /*
1116 Scale windowing function separately to the support 'clipping' window
1117 that calling operator is planning to actually use. (Expert override)
1118 */
1119 resize_filter->window_support=resize_filter->support; /* default */
1120 artifact=GetImageArtifact(image,"filter:win-support");
1121 if (artifact != (const char *) NULL)
1122 resize_filter->window_support=fabs(StringToDouble(artifact,(char **) NULL));
1123 /*
1124 Adjust window function scaling to match windowing support for weighting
1125 function. This avoids a division on every filter call.
1126 */
1127 resize_filter->scale*=PerceptibleReciprocal(resize_filter->window_support);
1128 /*
1129 Set Cubic Spline B,C values, calculate Cubic coefficients.
1130 */
1131 B=0.0;
1132 C=0.0;
1133 if ((resize_filter->filter == CubicBC) ||
1134 (resize_filter->window == CubicBC) )
1135 {
1136 B=filters[filter_type].B;
1137 C=filters[filter_type].C;
1138 if (filters[window_type].function == CubicBC)
1139 {
1140 B=filters[window_type].B;
1141 C=filters[window_type].C;
1142 }
1143 artifact=GetImageArtifact(image,"filter:b");
1144 if (artifact != (const char *) NULL)
1145 {
1146 B=StringToDouble(artifact,(char **) NULL);
1147 C=(1.0-B)/2.0; /* Calculate C to get a Keys cubic filter. */
1148 artifact=GetImageArtifact(image,"filter:c"); /* user C override */
1149 if (artifact != (const char *) NULL)
1150 C=StringToDouble(artifact,(char **) NULL);
1151 }
1152 else
1153 {
1154 artifact=GetImageArtifact(image,"filter:c");
1155 if (artifact != (const char *) NULL)
1156 {
1157 C=StringToDouble(artifact,(char **) NULL);
1158 B=1.0-2.0*C; /* Calculate B to get a Keys cubic filter. */
1159 }
1160 }
1161 {
1162 const double
1163 twoB = B+B;
1164
1165 /*
1166 Convert B,C values into Cubic Coefficents. See CubicBC().
1167 */
1168 resize_filter->coefficient[0]=1.0-(1.0/3.0)*B;
1169 resize_filter->coefficient[1]=-3.0+twoB+C;
1170 resize_filter->coefficient[2]=2.0-1.5*B-C;
1171 resize_filter->coefficient[3]=(4.0/3.0)*B+4.0*C;
1172 resize_filter->coefficient[4]=-8.0*C-twoB;
1173 resize_filter->coefficient[5]=B+5.0*C;
1174 resize_filter->coefficient[6]=(-1.0/6.0)*B-C;
1175 }
1176 }
1177
1178 /*
1179 Expert Option Request for verbose details of the resulting filter.
1180 */
1181 #if defined(MAGICKCORE_OPENMP_SUPPORT)
1182 #pragma omp master
1183 {
1184 #endif
1185 if (IsStringTrue(GetImageArtifact(image,"filter:verbose")) != MagickFalse)
1186 {
1187 double
1188 support,
1189 x;
1190
1191 /*
1192 Set the weighting function properly when the weighting function
1193 may not exactly match the filter of the same name. EG: a Point
1194 filter is really uses a Box weighting function with a different
1195 support than is typically used.
1196 */
1197 if (resize_filter->filter == Box) filter_type=BoxFilter;
1198 if (resize_filter->filter == Sinc) filter_type=SincFilter;
1199 if (resize_filter->filter == SincFast) filter_type=SincFastFilter;
1200 if (resize_filter->filter == Jinc) filter_type=JincFilter;
1201 if (resize_filter->filter == CubicBC) filter_type=CubicFilter;
1202 if (resize_filter->window == Box) window_type=BoxFilter;
1203 if (resize_filter->window == Sinc) window_type=SincFilter;
1204 if (resize_filter->window == SincFast) window_type=SincFastFilter;
1205 if (resize_filter->window == Jinc) window_type=JincFilter;
1206 if (resize_filter->window == CubicBC) window_type=CubicFilter;
1207 /*
1208 Report Filter Details.
1209 */
1210 support=GetResizeFilterSupport(resize_filter); /* practical_support */
1211 (void) FormatLocaleFile(stdout,
1212 "# Resampling Filter (for graphing)\n#\n");
1213 (void) FormatLocaleFile(stdout,"# filter = %s\n",
1214 CommandOptionToMnemonic(MagickFilterOptions,filter_type));
1215 (void) FormatLocaleFile(stdout,"# window = %s\n",
1216 CommandOptionToMnemonic(MagickFilterOptions,window_type));
1217 (void) FormatLocaleFile(stdout,"# support = %.*g\n",
1218 GetMagickPrecision(),(double) resize_filter->support);
1219 (void) FormatLocaleFile(stdout,"# window-support = %.*g\n",
1220 GetMagickPrecision(),(double) resize_filter->window_support);
1221 (void) FormatLocaleFile(stdout,"# scale-blur = %.*g\n",
1222 GetMagickPrecision(),(double) resize_filter->blur);
1223 if ((filter_type == GaussianFilter) || (window_type == GaussianFilter))
1224 (void) FormatLocaleFile(stdout,"# gaussian-sigma = %.*g\n",
1225 GetMagickPrecision(),(double) resize_filter->coefficient[0]);
1226 if ( filter_type == KaiserFilter || window_type == KaiserFilter )
1227 (void) FormatLocaleFile(stdout,"# kaiser-beta = %.*g\n",
1228 GetMagickPrecision(),(double) resize_filter->coefficient[0]);
1229 (void) FormatLocaleFile(stdout,"# practical-support = %.*g\n",
1230 GetMagickPrecision(), (double) support);
1231 if ((filter_type == CubicFilter) || (window_type == CubicFilter))
1232 (void) FormatLocaleFile(stdout,"# B,C = %.*g,%.*g\n",
1233 GetMagickPrecision(),(double) B,GetMagickPrecision(),(double) C);
1234 (void) FormatLocaleFile(stdout,"\n");
1235 /*
1236 Output values of resulting filter graph -- for graphing filter result.
1237 */
1238 for (x=0.0; x <= support; x+=0.01f)
1239 (void) FormatLocaleFile(stdout,"%5.2lf\t%.*g\n",x,
1240 GetMagickPrecision(),(double)
1241 GetResizeFilterWeight(resize_filter,x));
1242 /*
1243 A final value so gnuplot can graph the 'stop' properly.
1244 */
1245 (void) FormatLocaleFile(stdout,"%5.2lf\t%.*g\n",support,
1246 GetMagickPrecision(),0.0);
1247 }
1248 /* Output the above once only for each image - remove setting */
1249 (void) DeleteImageArtifact((Image *) image,"filter:verbose");
1250 #if defined(MAGICKCORE_OPENMP_SUPPORT)
1251 }
1252 #endif
1253 return(resize_filter);
1254 }
1255
1256 /*
1257 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1258 % %
1259 % %
1260 % %
1261 % A d a p t i v e R e s i z e I m a g e %
1262 % %
1263 % %
1264 % %
1265 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1266 %
1267 % AdaptiveResizeImage() adaptively resize image with pixel resampling.
1268 %
1269 % This is shortcut function for a fast interpolative resize using mesh
1270 % interpolation. It works well for small resizes of less than +/- 50%
1271 % of the original image size. For larger resizing on images a full
1272 % filtered and slower resize function should be used instead.
1273 %
1274 % The format of the AdaptiveResizeImage method is:
1275 %
1276 % Image *AdaptiveResizeImage(const Image *image,const size_t columns,
1277 % const size_t rows,ExceptionInfo *exception)
1278 %
1279 % A description of each parameter follows:
1280 %
1281 % o image: the image.
1282 %
1283 % o columns: the number of columns in the resized image.
1284 %
1285 % o rows: the number of rows in the resized image.
1286 %
1287 % o exception: return any errors or warnings in this structure.
1288 %
1289 */
AdaptiveResizeImage(const Image * image,const size_t columns,const size_t rows,ExceptionInfo * exception)1290 MagickExport Image *AdaptiveResizeImage(const Image *image,
1291 const size_t columns,const size_t rows,ExceptionInfo *exception)
1292 {
1293 Image
1294 *resize_image;
1295
1296 resize_image=InterpolativeResizeImage(image,columns,rows,MeshInterpolatePixel,
1297 exception);
1298 return(resize_image);
1299 }
1300
1301 /*
1302 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1303 % %
1304 % %
1305 % %
1306 + B e s s e l O r d e r O n e %
1307 % %
1308 % %
1309 % %
1310 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1311 %
1312 % BesselOrderOne() computes the Bessel function of x of the first kind of
1313 % order 0. This is used to create the Jinc() filter function below.
1314 %
1315 % Reduce x to |x| since j1(x)= -j1(-x), and for x in (0,8]
1316 %
1317 % j1(x) = x*j1(x);
1318 %
1319 % For x in (8,inf)
1320 %
1321 % j1(x) = sqrt(2/(pi*x))*(p1(x)*cos(x1)-q1(x)*sin(x1))
1322 %
1323 % where x1 = x-3*pi/4. Compute sin(x1) and cos(x1) as follow:
1324 %
1325 % cos(x1) = cos(x)cos(3pi/4)+sin(x)sin(3pi/4)
1326 % = 1/sqrt(2) * (sin(x) - cos(x))
1327 % sin(x1) = sin(x)cos(3pi/4)-cos(x)sin(3pi/4)
1328 % = -1/sqrt(2) * (sin(x) + cos(x))
1329 %
1330 % The format of the BesselOrderOne method is:
1331 %
1332 % double BesselOrderOne(double x)
1333 %
1334 % A description of each parameter follows:
1335 %
1336 % o x: double value.
1337 %
1338 */
1339
1340 #undef I0
I0(double x)1341 static double I0(double x)
1342 {
1343 double
1344 sum,
1345 t,
1346 y;
1347
1348 ssize_t
1349 i;
1350
1351 /*
1352 Zeroth order Bessel function of the first kind.
1353 */
1354 sum=1.0;
1355 y=x*x/4.0;
1356 t=y;
1357 for (i=2; t > MagickEpsilon; i++)
1358 {
1359 sum+=t;
1360 t*=y/((double) i*i);
1361 }
1362 return(sum);
1363 }
1364
1365 #undef J1
J1(double x)1366 static double J1(double x)
1367 {
1368 double
1369 p,
1370 q;
1371
1372 ssize_t
1373 i;
1374
1375 static const double
1376 Pone[] =
1377 {
1378 0.581199354001606143928050809e+21,
1379 -0.6672106568924916298020941484e+20,
1380 0.2316433580634002297931815435e+19,
1381 -0.3588817569910106050743641413e+17,
1382 0.2908795263834775409737601689e+15,
1383 -0.1322983480332126453125473247e+13,
1384 0.3413234182301700539091292655e+10,
1385 -0.4695753530642995859767162166e+7,
1386 0.270112271089232341485679099e+4
1387 },
1388 Qone[] =
1389 {
1390 0.11623987080032122878585294e+22,
1391 0.1185770712190320999837113348e+20,
1392 0.6092061398917521746105196863e+17,
1393 0.2081661221307607351240184229e+15,
1394 0.5243710262167649715406728642e+12,
1395 0.1013863514358673989967045588e+10,
1396 0.1501793594998585505921097578e+7,
1397 0.1606931573481487801970916749e+4,
1398 0.1e+1
1399 };
1400
1401 p=Pone[8];
1402 q=Qone[8];
1403 for (i=7; i >= 0; i--)
1404 {
1405 p=p*x*x+Pone[i];
1406 q=q*x*x+Qone[i];
1407 }
1408 return(p/q);
1409 }
1410
1411 #undef P1
P1(double x)1412 static double P1(double x)
1413 {
1414 double
1415 p,
1416 q;
1417
1418 ssize_t
1419 i;
1420
1421 static const double
1422 Pone[] =
1423 {
1424 0.352246649133679798341724373e+5,
1425 0.62758845247161281269005675e+5,
1426 0.313539631109159574238669888e+5,
1427 0.49854832060594338434500455e+4,
1428 0.2111529182853962382105718e+3,
1429 0.12571716929145341558495e+1
1430 },
1431 Qone[] =
1432 {
1433 0.352246649133679798068390431e+5,
1434 0.626943469593560511888833731e+5,
1435 0.312404063819041039923015703e+5,
1436 0.4930396490181088979386097e+4,
1437 0.2030775189134759322293574e+3,
1438 0.1e+1
1439 };
1440
1441 p=Pone[5];
1442 q=Qone[5];
1443 for (i=4; i >= 0; i--)
1444 {
1445 p=p*(8.0/x)*(8.0/x)+Pone[i];
1446 q=q*(8.0/x)*(8.0/x)+Qone[i];
1447 }
1448 return(p/q);
1449 }
1450
1451 #undef Q1
Q1(double x)1452 static double Q1(double x)
1453 {
1454 double
1455 p,
1456 q;
1457
1458 ssize_t
1459 i;
1460
1461 static const double
1462 Pone[] =
1463 {
1464 0.3511751914303552822533318e+3,
1465 0.7210391804904475039280863e+3,
1466 0.4259873011654442389886993e+3,
1467 0.831898957673850827325226e+2,
1468 0.45681716295512267064405e+1,
1469 0.3532840052740123642735e-1
1470 },
1471 Qone[] =
1472 {
1473 0.74917374171809127714519505e+4,
1474 0.154141773392650970499848051e+5,
1475 0.91522317015169922705904727e+4,
1476 0.18111867005523513506724158e+4,
1477 0.1038187585462133728776636e+3,
1478 0.1e+1
1479 };
1480
1481 p=Pone[5];
1482 q=Qone[5];
1483 for (i=4; i >= 0; i--)
1484 {
1485 p=p*(8.0/x)*(8.0/x)+Pone[i];
1486 q=q*(8.0/x)*(8.0/x)+Qone[i];
1487 }
1488 return(p/q);
1489 }
1490
BesselOrderOne(double x)1491 static double BesselOrderOne(double x)
1492 {
1493 double
1494 p,
1495 q;
1496
1497 if (x == 0.0)
1498 return(0.0);
1499 p=x;
1500 if (x < 0.0)
1501 x=(-x);
1502 if (x < 8.0)
1503 return(p*J1(x));
1504 q=sqrt((double) (2.0/(MagickPI*x)))*(P1(x)*(1.0/sqrt(2.0)*(sin(x)-
1505 cos(x)))-8.0/x*Q1(x)*(-1.0/sqrt(2.0)*(sin(x)+cos(x))));
1506 if (p < 0.0)
1507 q=(-q);
1508 return(q);
1509 }
1510
1511 /*
1512 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1513 % %
1514 % %
1515 % %
1516 + D e s t r o y R e s i z e F i l t e r %
1517 % %
1518 % %
1519 % %
1520 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1521 %
1522 % DestroyResizeFilter() destroy the resize filter.
1523 %
1524 % The format of the DestroyResizeFilter method is:
1525 %
1526 % ResizeFilter *DestroyResizeFilter(ResizeFilter *resize_filter)
1527 %
1528 % A description of each parameter follows:
1529 %
1530 % o resize_filter: the resize filter.
1531 %
1532 */
DestroyResizeFilter(ResizeFilter * resize_filter)1533 MagickPrivate ResizeFilter *DestroyResizeFilter(ResizeFilter *resize_filter)
1534 {
1535 assert(resize_filter != (ResizeFilter *) NULL);
1536 assert(resize_filter->signature == MagickCoreSignature);
1537 resize_filter->signature=(~MagickCoreSignature);
1538 resize_filter=(ResizeFilter *) RelinquishMagickMemory(resize_filter);
1539 return(resize_filter);
1540 }
1541
1542 /*
1543 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1544 % %
1545 % %
1546 % %
1547 + G e t R e s i z e F i l t e r S u p p o r t %
1548 % %
1549 % %
1550 % %
1551 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1552 %
1553 % GetResizeFilterSupport() return the current support window size for this
1554 % filter. Note that this may have been enlarged by filter:blur factor.
1555 %
1556 % The format of the GetResizeFilterSupport method is:
1557 %
1558 % double GetResizeFilterSupport(const ResizeFilter *resize_filter)
1559 %
1560 % A description of each parameter follows:
1561 %
1562 % o filter: Image filter to use.
1563 %
1564 */
1565
GetResizeFilterCoefficient(const ResizeFilter * resize_filter)1566 MagickPrivate double *GetResizeFilterCoefficient(
1567 const ResizeFilter *resize_filter)
1568 {
1569 assert(resize_filter != (ResizeFilter *) NULL);
1570 assert(resize_filter->signature == MagickCoreSignature);
1571 return((double *) resize_filter->coefficient);
1572 }
1573
GetResizeFilterBlur(const ResizeFilter * resize_filter)1574 MagickPrivate double GetResizeFilterBlur(const ResizeFilter *resize_filter)
1575 {
1576 assert(resize_filter != (ResizeFilter *) NULL);
1577 assert(resize_filter->signature == MagickCoreSignature);
1578 return(resize_filter->blur);
1579 }
1580
GetResizeFilterScale(const ResizeFilter * resize_filter)1581 MagickPrivate double GetResizeFilterScale(const ResizeFilter *resize_filter)
1582 {
1583 assert(resize_filter != (ResizeFilter *) NULL);
1584 assert(resize_filter->signature == MagickCoreSignature);
1585 return(resize_filter->scale);
1586 }
1587
GetResizeFilterWindowSupport(const ResizeFilter * resize_filter)1588 MagickPrivate double GetResizeFilterWindowSupport(
1589 const ResizeFilter *resize_filter)
1590 {
1591 assert(resize_filter != (ResizeFilter *) NULL);
1592 assert(resize_filter->signature == MagickCoreSignature);
1593 return(resize_filter->window_support);
1594 }
1595
GetResizeFilterWeightingType(const ResizeFilter * resize_filter)1596 MagickPrivate ResizeWeightingFunctionType GetResizeFilterWeightingType(
1597 const ResizeFilter *resize_filter)
1598 {
1599 assert(resize_filter != (ResizeFilter *) NULL);
1600 assert(resize_filter->signature == MagickCoreSignature);
1601 return(resize_filter->filterWeightingType);
1602 }
1603
GetResizeFilterWindowWeightingType(const ResizeFilter * resize_filter)1604 MagickPrivate ResizeWeightingFunctionType GetResizeFilterWindowWeightingType(
1605 const ResizeFilter *resize_filter)
1606 {
1607 assert(resize_filter != (ResizeFilter *) NULL);
1608 assert(resize_filter->signature == MagickCoreSignature);
1609 return(resize_filter->windowWeightingType);
1610 }
1611
GetResizeFilterSupport(const ResizeFilter * resize_filter)1612 MagickPrivate double GetResizeFilterSupport(const ResizeFilter *resize_filter)
1613 {
1614 assert(resize_filter != (ResizeFilter *) NULL);
1615 assert(resize_filter->signature == MagickCoreSignature);
1616 return(resize_filter->support*resize_filter->blur);
1617 }
1618
1619 /*
1620 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1621 % %
1622 % %
1623 % %
1624 + G e t R e s i z e F i l t e r W e i g h t %
1625 % %
1626 % %
1627 % %
1628 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1629 %
1630 % GetResizeFilterWeight evaluates the specified resize filter at the point x
1631 % which usally lies between zero and the filters current 'support' and
1632 % returns the weight of the filter function at that point.
1633 %
1634 % The format of the GetResizeFilterWeight method is:
1635 %
1636 % double GetResizeFilterWeight(const ResizeFilter *resize_filter,
1637 % const double x)
1638 %
1639 % A description of each parameter follows:
1640 %
1641 % o filter: the filter type.
1642 %
1643 % o x: the point.
1644 %
1645 */
GetResizeFilterWeight(const ResizeFilter * resize_filter,const double x)1646 MagickPrivate double GetResizeFilterWeight(const ResizeFilter *resize_filter,
1647 const double x)
1648 {
1649 double
1650 scale,
1651 weight,
1652 x_blur;
1653
1654 /*
1655 Windowing function - scale the weighting filter by this amount.
1656 */
1657 assert(resize_filter != (ResizeFilter *) NULL);
1658 assert(resize_filter->signature == MagickCoreSignature);
1659 x_blur=fabs((double) x)*PerceptibleReciprocal(resize_filter->blur); /* X offset with blur scaling */
1660 if ((resize_filter->window_support < MagickEpsilon) ||
1661 (resize_filter->window == Box))
1662 scale=1.0; /* Point or Box Filter -- avoid division by zero */
1663 else
1664 {
1665 scale=resize_filter->scale;
1666 scale=resize_filter->window(x_blur*scale,resize_filter);
1667 }
1668 weight=scale*resize_filter->filter(x_blur,resize_filter);
1669 return(weight);
1670 }
1671
1672 /*
1673 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1674 % %
1675 % %
1676 % %
1677 % I n t e r p o l a t i v e R e s i z e I m a g e %
1678 % %
1679 % %
1680 % %
1681 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1682 %
1683 % InterpolativeResizeImage() resizes an image using the specified
1684 % interpolation method.
1685 %
1686 % The format of the InterpolativeResizeImage method is:
1687 %
1688 % Image *InterpolativeResizeImage(const Image *image,const size_t columns,
1689 % const size_t rows,const PixelInterpolateMethod method,
1690 % ExceptionInfo *exception)
1691 %
1692 % A description of each parameter follows:
1693 %
1694 % o image: the image.
1695 %
1696 % o columns: the number of columns in the resized image.
1697 %
1698 % o rows: the number of rows in the resized image.
1699 %
1700 % o method: the pixel interpolation method.
1701 %
1702 % o exception: return any errors or warnings in this structure.
1703 %
1704 */
InterpolativeResizeImage(const Image * image,const size_t columns,const size_t rows,const PixelInterpolateMethod method,ExceptionInfo * exception)1705 MagickExport Image *InterpolativeResizeImage(const Image *image,
1706 const size_t columns,const size_t rows,const PixelInterpolateMethod method,
1707 ExceptionInfo *exception)
1708 {
1709 #define InterpolativeResizeImageTag "Resize/Image"
1710
1711 CacheView
1712 *image_view,
1713 *resize_view;
1714
1715 Image
1716 *resize_image;
1717
1718 MagickBooleanType
1719 status;
1720
1721 MagickOffsetType
1722 progress;
1723
1724 PointInfo
1725 scale;
1726
1727 ssize_t
1728 y;
1729
1730 /*
1731 Interpolatively resize image.
1732 */
1733 assert(image != (const Image *) NULL);
1734 assert(image->signature == MagickCoreSignature);
1735 if (image->debug != MagickFalse)
1736 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
1737 assert(exception != (ExceptionInfo *) NULL);
1738 assert(exception->signature == MagickCoreSignature);
1739 if ((columns == 0) || (rows == 0))
1740 ThrowImageException(ImageError,"NegativeOrZeroImageSize");
1741 if ((columns == image->columns) && (rows == image->rows))
1742 return(CloneImage(image,0,0,MagickTrue,exception));
1743 resize_image=CloneImage(image,columns,rows,MagickTrue,exception);
1744 if (resize_image == (Image *) NULL)
1745 return((Image *) NULL);
1746 if (SetImageStorageClass(resize_image,DirectClass,exception) == MagickFalse)
1747 {
1748 resize_image=DestroyImage(resize_image);
1749 return((Image *) NULL);
1750 }
1751 status=MagickTrue;
1752 progress=0;
1753 image_view=AcquireVirtualCacheView(image,exception);
1754 resize_view=AcquireAuthenticCacheView(resize_image,exception);
1755 scale.x=(double) image->columns/resize_image->columns;
1756 scale.y=(double) image->rows/resize_image->rows;
1757 #if defined(MAGICKCORE_OPENMP_SUPPORT)
1758 #pragma omp parallel for schedule(static) shared(progress,status) \
1759 magick_number_threads(image,resize_image,resize_image->rows,1)
1760 #endif
1761 for (y=0; y < (ssize_t) resize_image->rows; y++)
1762 {
1763 PointInfo
1764 offset;
1765
1766 Quantum
1767 *magick_restrict q;
1768
1769 ssize_t
1770 x;
1771
1772 if (status == MagickFalse)
1773 continue;
1774 q=QueueCacheViewAuthenticPixels(resize_view,0,y,resize_image->columns,1,
1775 exception);
1776 if (q == (Quantum *) NULL)
1777 continue;
1778 offset.y=((double) y+0.5)*scale.y-0.5;
1779 for (x=0; x < (ssize_t) resize_image->columns; x++)
1780 {
1781 ssize_t
1782 i;
1783
1784 for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
1785 {
1786 PixelChannel
1787 channel;
1788
1789 PixelTrait
1790 resize_traits,
1791 traits;
1792
1793 channel=GetPixelChannelChannel(image,i);
1794 traits=GetPixelChannelTraits(image,channel);
1795 resize_traits=GetPixelChannelTraits(resize_image,channel);
1796 if ((traits == UndefinedPixelTrait) ||
1797 (resize_traits == UndefinedPixelTrait))
1798 continue;
1799 offset.x=((double) x+0.5)*scale.x-0.5;
1800 status=InterpolatePixelChannels(image,image_view,resize_image,method,
1801 offset.x,offset.y,q,exception);
1802 if (status == MagickFalse)
1803 break;
1804 }
1805 q+=GetPixelChannels(resize_image);
1806 }
1807 if (SyncCacheViewAuthenticPixels(resize_view,exception) == MagickFalse)
1808 status=MagickFalse;
1809 if (image->progress_monitor != (MagickProgressMonitor) NULL)
1810 {
1811 MagickBooleanType
1812 proceed;
1813
1814 #if defined(MAGICKCORE_OPENMP_SUPPORT)
1815 #pragma omp atomic
1816 #endif
1817 progress++;
1818 proceed=SetImageProgress(image,InterpolativeResizeImageTag,progress,
1819 image->rows);
1820 if (proceed == MagickFalse)
1821 status=MagickFalse;
1822 }
1823 }
1824 resize_view=DestroyCacheView(resize_view);
1825 image_view=DestroyCacheView(image_view);
1826 if (status == MagickFalse)
1827 resize_image=DestroyImage(resize_image);
1828 return(resize_image);
1829 }
1830 #if defined(MAGICKCORE_LQR_DELEGATE)
1831
1832 /*
1833 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1834 % %
1835 % %
1836 % %
1837 % L i q u i d R e s c a l e I m a g e %
1838 % %
1839 % %
1840 % %
1841 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1842 %
1843 % LiquidRescaleImage() rescales image with seam carving.
1844 %
1845 % The format of the LiquidRescaleImage method is:
1846 %
1847 % Image *LiquidRescaleImage(const Image *image,const size_t columns,
1848 % const size_t rows,const double delta_x,const double rigidity,
1849 % ExceptionInfo *exception)
1850 %
1851 % A description of each parameter follows:
1852 %
1853 % o image: the image.
1854 %
1855 % o columns: the number of columns in the rescaled image.
1856 %
1857 % o rows: the number of rows in the rescaled image.
1858 %
1859 % o delta_x: maximum seam transversal step (0 means straight seams).
1860 %
1861 % o rigidity: introduce a bias for non-straight seams (typically 0).
1862 %
1863 % o exception: return any errors or warnings in this structure.
1864 %
1865 */
LiquidRescaleImage(const Image * image,const size_t columns,const size_t rows,const double delta_x,const double rigidity,ExceptionInfo * exception)1866 MagickExport Image *LiquidRescaleImage(const Image *image,const size_t columns,
1867 const size_t rows,const double delta_x,const double rigidity,
1868 ExceptionInfo *exception)
1869 {
1870 #define LiquidRescaleImageTag "Rescale/Image"
1871
1872 CacheView
1873 *image_view,
1874 *rescale_view;
1875
1876 gfloat
1877 *packet,
1878 *pixels;
1879
1880 Image
1881 *rescale_image;
1882
1883 int
1884 x_offset,
1885 y_offset;
1886
1887 LqrCarver
1888 *carver;
1889
1890 LqrRetVal
1891 lqr_status;
1892
1893 MagickBooleanType
1894 status;
1895
1896 MemoryInfo
1897 *pixel_info;
1898
1899 gfloat
1900 *q;
1901
1902 ssize_t
1903 y;
1904
1905 /*
1906 Liquid rescale image.
1907 */
1908 assert(image != (const Image *) NULL);
1909 assert(image->signature == MagickCoreSignature);
1910 if (image->debug != MagickFalse)
1911 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
1912 assert(exception != (ExceptionInfo *) NULL);
1913 assert(exception->signature == MagickCoreSignature);
1914 if ((columns == 0) || (rows == 0))
1915 ThrowImageException(ImageError,"NegativeOrZeroImageSize");
1916 if ((columns == image->columns) && (rows == image->rows))
1917 return(CloneImage(image,0,0,MagickTrue,exception));
1918 if ((columns <= 2) || (rows <= 2))
1919 return(ResizeImage(image,columns,rows,image->filter,exception));
1920 pixel_info=AcquireVirtualMemory(image->columns,image->rows*MaxPixelChannels*
1921 sizeof(*pixels));
1922 if (pixel_info == (MemoryInfo *) NULL)
1923 return((Image *) NULL);
1924 pixels=(gfloat *) GetVirtualMemoryBlob(pixel_info);
1925 status=MagickTrue;
1926 q=pixels;
1927 image_view=AcquireVirtualCacheView(image,exception);
1928 for (y=0; y < (ssize_t) image->rows; y++)
1929 {
1930 const Quantum
1931 *magick_restrict p;
1932
1933 ssize_t
1934 x;
1935
1936 if (status == MagickFalse)
1937 continue;
1938 p=GetCacheViewVirtualPixels(image_view,0,y,image->columns,1,exception);
1939 if (p == (const Quantum *) NULL)
1940 {
1941 status=MagickFalse;
1942 continue;
1943 }
1944 for (x=0; x < (ssize_t) image->columns; x++)
1945 {
1946 ssize_t
1947 i;
1948
1949 for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
1950 *q++=QuantumScale*p[i];
1951 p+=GetPixelChannels(image);
1952 }
1953 }
1954 image_view=DestroyCacheView(image_view);
1955 carver=lqr_carver_new_ext(pixels,(int) image->columns,(int) image->rows,
1956 (int) GetPixelChannels(image),LQR_COLDEPTH_32F);
1957 if (carver == (LqrCarver *) NULL)
1958 {
1959 pixel_info=RelinquishVirtualMemory(pixel_info);
1960 ThrowImageException(ResourceLimitError,"MemoryAllocationFailed");
1961 }
1962 lqr_carver_set_preserve_input_image(carver);
1963 lqr_status=lqr_carver_init(carver,(int) delta_x,rigidity);
1964 lqr_status=lqr_carver_resize(carver,(int) columns,(int) rows);
1965 (void) lqr_status;
1966 rescale_image=CloneImage(image,lqr_carver_get_width(carver),
1967 lqr_carver_get_height(carver),MagickTrue,exception);
1968 if (rescale_image == (Image *) NULL)
1969 {
1970 pixel_info=RelinquishVirtualMemory(pixel_info);
1971 return((Image *) NULL);
1972 }
1973 if (SetImageStorageClass(rescale_image,DirectClass,exception) == MagickFalse)
1974 {
1975 pixel_info=RelinquishVirtualMemory(pixel_info);
1976 rescale_image=DestroyImage(rescale_image);
1977 return((Image *) NULL);
1978 }
1979 rescale_view=AcquireAuthenticCacheView(rescale_image,exception);
1980 (void) lqr_carver_scan_reset(carver);
1981 while (lqr_carver_scan_ext(carver,&x_offset,&y_offset,(void **) &packet) != 0)
1982 {
1983 Quantum
1984 *magick_restrict p;
1985
1986 ssize_t
1987 i;
1988
1989 p=QueueCacheViewAuthenticPixels(rescale_view,x_offset,y_offset,1,1,
1990 exception);
1991 if (p == (Quantum *) NULL)
1992 break;
1993 for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
1994 {
1995 PixelChannel
1996 channel;
1997
1998 PixelTrait
1999 rescale_traits,
2000 traits;
2001
2002 channel=GetPixelChannelChannel(image,i);
2003 traits=GetPixelChannelTraits(image,channel);
2004 rescale_traits=GetPixelChannelTraits(rescale_image,channel);
2005 if ((traits == UndefinedPixelTrait) ||
2006 (rescale_traits == UndefinedPixelTrait))
2007 continue;
2008 SetPixelChannel(rescale_image,channel,ClampToQuantum(QuantumRange*
2009 packet[i]),p);
2010 }
2011 if (SyncCacheViewAuthenticPixels(rescale_view,exception) == MagickFalse)
2012 break;
2013 }
2014 rescale_view=DestroyCacheView(rescale_view);
2015 pixel_info=RelinquishVirtualMemory(pixel_info);
2016 lqr_carver_destroy(carver);
2017 return(rescale_image);
2018 }
2019 #else
LiquidRescaleImage(const Image * image,const size_t magick_unused (columns),const size_t magick_unused (rows),const double magick_unused (delta_x),const double magick_unused (rigidity),ExceptionInfo * exception)2020 MagickExport Image *LiquidRescaleImage(const Image *image,
2021 const size_t magick_unused(columns),const size_t magick_unused(rows),
2022 const double magick_unused(delta_x),const double magick_unused(rigidity),
2023 ExceptionInfo *exception)
2024 {
2025 assert(image != (const Image *) NULL);
2026 assert(image->signature == MagickCoreSignature);
2027 if (image->debug != MagickFalse)
2028 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
2029 assert(exception != (ExceptionInfo *) NULL);
2030 assert(exception->signature == MagickCoreSignature);
2031 (void) ThrowMagickException(exception,GetMagickModule(),MissingDelegateError,
2032 "DelegateLibrarySupportNotBuiltIn","'%s' (LQR)",image->filename);
2033 return((Image *) NULL);
2034 }
2035 #endif
2036
2037 /*
2038 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2039 % %
2040 % %
2041 % %
2042 % M a g n i f y I m a g e %
2043 % %
2044 % %
2045 % %
2046 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2047 %
2048 % MagnifyImage() doubles the size of the image with a pixel art scaling
2049 % algorithm.
2050 %
2051 % The format of the MagnifyImage method is:
2052 %
2053 % Image *MagnifyImage(const Image *image,ExceptionInfo *exception)
2054 %
2055 % A description of each parameter follows:
2056 %
2057 % o image: the image.
2058 %
2059 % o exception: return any errors or warnings in this structure.
2060 %
2061 */
2062
CopyPixels(const Quantum * source,const ssize_t source_offset,Quantum * destination,const ssize_t destination_offset,const size_t channels)2063 static inline void CopyPixels(const Quantum *source,const ssize_t source_offset,
2064 Quantum *destination,const ssize_t destination_offset,const size_t channels)
2065 {
2066 ssize_t
2067 i;
2068
2069 for (i=0; i < (ssize_t) channels; i++)
2070 destination[channels*destination_offset+i]=source[source_offset*channels+i];
2071 }
2072
MixPixels(const Quantum * source,const ssize_t * source_offset,const size_t source_size,Quantum * destination,const ssize_t destination_offset,const size_t channels)2073 static inline void MixPixels(const Quantum *source,const ssize_t *source_offset,
2074 const size_t source_size,Quantum *destination,
2075 const ssize_t destination_offset,const size_t channels)
2076 {
2077 ssize_t
2078 sum;
2079
2080 ssize_t
2081 i;
2082
2083 for (i=0; i < (ssize_t) channels; i++)
2084 {
2085 ssize_t
2086 j;
2087
2088 sum=0;
2089 for (j=0; j < (ssize_t) source_size; j++)
2090 sum+=source[source_offset[j]*channels+i];
2091 destination[channels*destination_offset+i]=(Quantum) (sum/source_size);
2092 }
2093 }
2094
Mix2Pixels(const Quantum * source,const ssize_t source_offset1,const ssize_t source_offset2,Quantum * destination,const ssize_t destination_offset,const size_t channels)2095 static inline void Mix2Pixels(const Quantum *source,
2096 const ssize_t source_offset1,const ssize_t source_offset2,
2097 Quantum *destination,const ssize_t destination_offset,const size_t channels)
2098 {
2099 const ssize_t
2100 offsets[2] = { source_offset1, source_offset2 };
2101
2102 MixPixels(source,offsets,2,destination,destination_offset,channels);
2103 }
2104
PixelsEqual(const Quantum * source1,ssize_t offset1,const Quantum * source2,ssize_t offset2,const size_t channels)2105 static inline int PixelsEqual(const Quantum *source1,ssize_t offset1,
2106 const Quantum *source2,ssize_t offset2,const size_t channels)
2107 {
2108 ssize_t
2109 i;
2110
2111 offset1*=channels;
2112 offset2*=channels;
2113 for (i=0; i < (ssize_t) channels; i++)
2114 if (source1[offset1+i] != source2[offset2+i])
2115 return(0);
2116 return(1);
2117 }
2118
Eagle2X(const Image * source,const Quantum * pixels,Quantum * result,const size_t channels)2119 static inline void Eagle2X(const Image *source,const Quantum *pixels,
2120 Quantum *result,const size_t channels)
2121 {
2122 ssize_t
2123 i;
2124
2125 (void) source;
2126 for (i=0; i < 4; i++)
2127 CopyPixels(pixels,4,result,i,channels);
2128 if (PixelsEqual(pixels,0,pixels,1,channels) &&
2129 PixelsEqual(pixels,1,pixels,3,channels))
2130 CopyPixels(pixels,0,result,0,channels);
2131 if (PixelsEqual(pixels,1,pixels,2,channels) &&
2132 PixelsEqual(pixels,2,pixels,5,channels))
2133 CopyPixels(pixels,2,result,1,channels);
2134 if (PixelsEqual(pixels,3,pixels,6,channels) &&
2135 PixelsEqual(pixels,6,pixels,7,channels))
2136 CopyPixels(pixels,6,result,2,channels);
2137 if (PixelsEqual(pixels,5,pixels,8,channels) &&
2138 PixelsEqual(pixels,8,pixels,7,channels))
2139 CopyPixels(pixels,8,result,3,channels);
2140 }
2141
Hq2XHelper(const unsigned int rule,const Quantum * source,Quantum * destination,const ssize_t destination_offset,const size_t channels,const ssize_t e,const ssize_t a,const ssize_t b,const ssize_t d,const ssize_t f,const ssize_t h)2142 static void Hq2XHelper(const unsigned int rule,const Quantum *source,
2143 Quantum *destination,const ssize_t destination_offset,const size_t channels,
2144 const ssize_t e,const ssize_t a,const ssize_t b,const ssize_t d,
2145 const ssize_t f,const ssize_t h)
2146 {
2147 #define caseA(N,A,B,C,D) \
2148 case N: \
2149 { \
2150 const ssize_t \
2151 offsets[4] = { A, B, C, D }; \
2152 \
2153 MixPixels(source,offsets,4,destination,destination_offset,channels);\
2154 break; \
2155 }
2156 #define caseB(N,A,B,C,D,E,F,G,H) \
2157 case N: \
2158 { \
2159 const ssize_t \
2160 offsets[8] = { A, B, C, D, E, F, G, H }; \
2161 \
2162 MixPixels(source,offsets,8,destination,destination_offset,channels);\
2163 break; \
2164 }
2165
2166 switch (rule)
2167 {
2168 case 0:
2169 {
2170 CopyPixels(source,e,destination,destination_offset,channels);
2171 break;
2172 }
2173 caseA(1,e,e,e,a)
2174 caseA(2,e,e,e,d)
2175 caseA(3,e,e,e,b)
2176 caseA(4,e,e,d,b)
2177 caseA(5,e,e,a,b)
2178 caseA(6,e,e,a,d)
2179 caseB(7,e,e,e,e,e,b,b,d)
2180 caseB(8,e,e,e,e,e,d,d,b)
2181 caseB(9,e,e,e,e,e,e,d,b)
2182 caseB(10,e,e,d,d,d,b,b,b)
2183 case 11:
2184 {
2185 const ssize_t
2186 offsets[16] = { e, e, e, e, e, e, e, e, e, e, e, e, e, e, d, b };
2187
2188 MixPixels(source,offsets,16,destination,destination_offset,channels);
2189 break;
2190 }
2191 case 12:
2192 {
2193 if (PixelsEqual(source,b,source,d,channels))
2194 {
2195 const ssize_t
2196 offsets[4] = { e, e, d, b };
2197
2198 MixPixels(source,offsets,4,destination,destination_offset,channels);
2199 }
2200 else
2201 CopyPixels(source,e,destination,destination_offset,channels);
2202 break;
2203 }
2204 case 13:
2205 {
2206 if (PixelsEqual(source,b,source,d,channels))
2207 {
2208 const ssize_t
2209 offsets[8] = { e, e, d, d, d, b, b, b };
2210
2211 MixPixels(source,offsets,8,destination,destination_offset,channels);
2212 }
2213 else
2214 CopyPixels(source,e,destination,destination_offset,channels);
2215 break;
2216 }
2217 case 14:
2218 {
2219 if (PixelsEqual(source,b,source,d,channels))
2220 {
2221 const ssize_t
2222 offsets[16] = { e, e, e, e, e, e, e, e, e, e, e, e, e, e, d, b };
2223
2224 MixPixels(source,offsets,16,destination,destination_offset,channels);
2225 }
2226 else
2227 CopyPixels(source,e,destination,destination_offset,channels);
2228 break;
2229 }
2230 case 15:
2231 {
2232 if (PixelsEqual(source,b,source,d,channels))
2233 {
2234 const ssize_t
2235 offsets[4] = { e, e, d, b };
2236
2237 MixPixels(source,offsets,4,destination,destination_offset,channels);
2238 }
2239 else
2240 {
2241 const ssize_t
2242 offsets[4] = { e, e, e, a };
2243
2244 MixPixels(source,offsets,4,destination,destination_offset,channels);
2245 }
2246 break;
2247 }
2248 case 16:
2249 {
2250 if (PixelsEqual(source,b,source,d,channels))
2251 {
2252 const ssize_t
2253 offsets[8] = { e, e, e, e, e, e, d, b };
2254
2255 MixPixels(source,offsets,8,destination,destination_offset,channels);
2256 }
2257 else
2258 {
2259 const ssize_t
2260 offsets[4] = { e, e, e, a };
2261
2262 MixPixels(source,offsets,4,destination,destination_offset,channels);
2263 }
2264 break;
2265 }
2266 case 17:
2267 {
2268 if (PixelsEqual(source,b,source,d,channels))
2269 {
2270 const ssize_t
2271 offsets[8] = { e, e, d, d, d, b, b, b };
2272
2273 MixPixels(source,offsets,8,destination,destination_offset,channels);
2274 }
2275 else
2276 {
2277 const ssize_t
2278 offsets[4] = { e, e, e, a };
2279
2280 MixPixels(source,offsets,4,destination,destination_offset,channels);
2281 }
2282 break;
2283 }
2284 case 18:
2285 {
2286 if (PixelsEqual(source,b,source,f,channels))
2287 {
2288 const ssize_t
2289 offsets[8] = { e, e, e, e, e, b, b, d };
2290
2291 MixPixels(source,offsets,8,destination,destination_offset,channels);
2292 }
2293 else
2294 {
2295 const ssize_t
2296 offsets[4] = { e, e, e, d };
2297
2298 MixPixels(source,offsets,4,destination,destination_offset,channels);
2299 }
2300 break;
2301 }
2302 default:
2303 {
2304 if (PixelsEqual(source,d,source,h,channels))
2305 {
2306 const ssize_t
2307 offsets[8] = { e, e, e, e, e, d, d, b };
2308
2309 MixPixels(source,offsets,8,destination,destination_offset,channels);
2310 }
2311 else
2312 {
2313 const ssize_t
2314 offsets[4] = { e, e, e, b };
2315
2316 MixPixels(source,offsets,4,destination,destination_offset,channels);
2317 }
2318 break;
2319 }
2320 }
2321 #undef caseA
2322 #undef caseB
2323 }
2324
Hq2XPatternToNumber(const int * pattern)2325 static inline unsigned int Hq2XPatternToNumber(const int *pattern)
2326 {
2327 ssize_t
2328 i;
2329
2330 unsigned int
2331 result,
2332 order;
2333
2334 result=0;
2335 order=1;
2336 for (i=7; i >= 0; i--)
2337 {
2338 result+=order*pattern[i];
2339 order*=2;
2340 }
2341 return(result);
2342 }
2343
Hq2X(const Image * source,const Quantum * pixels,Quantum * result,const size_t channels)2344 static inline void Hq2X(const Image *source,const Quantum *pixels,
2345 Quantum *result,const size_t channels)
2346 {
2347 static const unsigned int
2348 Hq2XTable[] =
2349 {
2350 4, 4, 6, 2, 4, 4, 6, 2, 5, 3, 15, 12, 5, 3, 17, 13,
2351 4, 4, 6, 18, 4, 4, 6, 18, 5, 3, 12, 12, 5, 3, 1, 12,
2352 4, 4, 6, 2, 4, 4, 6, 2, 5, 3, 17, 13, 5, 3, 16, 14,
2353 4, 4, 6, 18, 4, 4, 6, 18, 5, 3, 16, 12, 5, 3, 1, 14,
2354 4, 4, 6, 2, 4, 4, 6, 2, 5, 19, 12, 12, 5, 19, 16, 12,
2355 4, 4, 6, 2, 4, 4, 6, 2, 5, 3, 16, 12, 5, 3, 16, 12,
2356 4, 4, 6, 2, 4, 4, 6, 2, 5, 19, 1, 12, 5, 19, 1, 14,
2357 4, 4, 6, 2, 4, 4, 6, 18, 5, 3, 16, 12, 5, 19, 1, 14,
2358 4, 4, 6, 2, 4, 4, 6, 2, 5, 3, 15, 12, 5, 3, 17, 13,
2359 4, 4, 6, 2, 4, 4, 6, 2, 5, 3, 16, 12, 5, 3, 16, 12,
2360 4, 4, 6, 2, 4, 4, 6, 2, 5, 3, 17, 13, 5, 3, 16, 14,
2361 4, 4, 6, 2, 4, 4, 6, 2, 5, 3, 16, 13, 5, 3, 1, 14,
2362 4, 4, 6, 2, 4, 4, 6, 2, 5, 3, 16, 12, 5, 3, 16, 13,
2363 4, 4, 6, 2, 4, 4, 6, 2, 5, 3, 16, 12, 5, 3, 1, 12,
2364 4, 4, 6, 2, 4, 4, 6, 2, 5, 3, 16, 12, 5, 3, 1, 14,
2365 4, 4, 6, 2, 4, 4, 6, 2, 5, 3, 1, 12, 5, 3, 1, 14
2366 };
2367
2368 const int
2369 pattern1[] =
2370 {
2371 !PixelsEqual(pixels,4,pixels,8,channels),
2372 !PixelsEqual(pixels,4,pixels,7,channels),
2373 !PixelsEqual(pixels,4,pixels,6,channels),
2374 !PixelsEqual(pixels,4,pixels,5,channels),
2375 !PixelsEqual(pixels,4,pixels,3,channels),
2376 !PixelsEqual(pixels,4,pixels,2,channels),
2377 !PixelsEqual(pixels,4,pixels,1,channels),
2378 !PixelsEqual(pixels,4,pixels,0,channels)
2379 };
2380
2381 #define Rotated(p) p[2], p[4], p[7], p[1], p[6], p[0], p[3], p[5]
2382 const int pattern2[] = { Rotated(pattern1) };
2383 const int pattern3[] = { Rotated(pattern2) };
2384 const int pattern4[] = { Rotated(pattern3) };
2385 #undef Rotated
2386 (void) source;
2387 Hq2XHelper(Hq2XTable[Hq2XPatternToNumber(pattern1)],pixels,result,0,
2388 channels,4,0,1,3,5,7);
2389 Hq2XHelper(Hq2XTable[Hq2XPatternToNumber(pattern2)],pixels,result,1,
2390 channels,4,2,5,1,7,3);
2391 Hq2XHelper(Hq2XTable[Hq2XPatternToNumber(pattern3)],pixels,result,3,
2392 channels,4,8,7,5,3,1);
2393 Hq2XHelper(Hq2XTable[Hq2XPatternToNumber(pattern4)],pixels,result,2,
2394 channels,4,6,3,7,1,5);
2395 }
2396
Fish2X(const Image * source,const Quantum * pixels,Quantum * result,const size_t channels)2397 static void Fish2X(const Image *source,const Quantum *pixels,Quantum *result,
2398 const size_t channels)
2399 {
2400 #define Corner(A,B,C,D) \
2401 { \
2402 if (intensities[B] > intensities[A]) \
2403 { \
2404 ssize_t \
2405 offsets[3] = { B, C, D }; \
2406 \
2407 MixPixels(pixels,offsets,3,result,3,channels); \
2408 } \
2409 else \
2410 { \
2411 ssize_t \
2412 offsets[3] = { A, B, C }; \
2413 \
2414 MixPixels(pixels,offsets,3,result,3,channels); \
2415 } \
2416 }
2417
2418 #define Line(A,B,C,D) \
2419 { \
2420 if (intensities[C] > intensities[A]) \
2421 Mix2Pixels(pixels,C,D,result,3,channels); \
2422 else \
2423 Mix2Pixels(pixels,A,B,result,3,channels); \
2424 }
2425
2426 MagickFloatType
2427 intensities[9];
2428
2429 int
2430 ae,
2431 bd,
2432 ab,
2433 ad,
2434 be,
2435 de;
2436
2437 ssize_t
2438 i;
2439
2440 ssize_t
2441 offsets[4] = { 0, 1, 3, 4 };
2442
2443 for (i=0; i < 9; i++)
2444 intensities[i]=GetPixelIntensity(source,pixels + i*channels);
2445 CopyPixels(pixels,0,result,0,channels);
2446 CopyPixels(pixels,(ssize_t) (intensities[0] > intensities[1] ? 0 : 1),result,
2447 1,channels);
2448 CopyPixels(pixels,(ssize_t) (intensities[0] > intensities[3] ? 0 : 3),result,
2449 2,channels);
2450 ae=PixelsEqual(pixels,0,pixels,4,channels);
2451 bd=PixelsEqual(pixels,1,pixels,3,channels);
2452 ab=PixelsEqual(pixels,0,pixels,1,channels);
2453 de=PixelsEqual(pixels,3,pixels,4,channels);
2454 ad=PixelsEqual(pixels,0,pixels,3,channels);
2455 be=PixelsEqual(pixels,1,pixels,4,channels);
2456 if (ae && bd && ab)
2457 {
2458 CopyPixels(pixels,0,result,3,channels);
2459 return;
2460 }
2461 if (ad && de && !ab)
2462 {
2463 Corner(1,0,4,3)
2464 return;
2465 }
2466 if (be && de && !ab)
2467 {
2468 Corner(0,1,3,4)
2469 return;
2470 }
2471 if (ad && ab && !be)
2472 {
2473 Corner(4,3,1,0)
2474 return;
2475 }
2476 if (ab && be && !ad)
2477 {
2478 Corner(3,0,4,1)
2479 return;
2480 }
2481 if (ae && (!bd || intensities[1] > intensities[0]))
2482 {
2483 Mix2Pixels(pixels,0,4,result,3,channels);
2484 return;
2485 }
2486 if (bd && (!ae || intensities[0] > intensities[1]))
2487 {
2488 Mix2Pixels(pixels,1,3,result,3,channels);
2489 return;
2490 }
2491 if (ab)
2492 {
2493 Line(0,1,3,4)
2494 return;
2495 }
2496 if (de)
2497 {
2498 Line(3,4,0,1)
2499 return;
2500 }
2501 if (ad)
2502 {
2503 Line(0,3,1,4)
2504 return;
2505 }
2506 if (be)
2507 {
2508 Line(1,4,0,3)
2509 return;
2510 }
2511 MixPixels(pixels,offsets,4,result,3,channels);
2512 #undef Corner
2513 #undef Line
2514 }
2515
Xbr2X(const Image * source,const Quantum * pixels,Quantum * result,const size_t channels)2516 static void Xbr2X(const Image *source,const Quantum *pixels,Quantum *result,
2517 const size_t channels)
2518 {
2519 #define WeightVar(M,N) const int w_##M##_##N = \
2520 PixelsEqual(pixels,M,pixels,N,channels) ? 0 : 1;
2521
2522 WeightVar(12,11)
2523 WeightVar(12,7)
2524 WeightVar(12,13)
2525 WeightVar(12,17)
2526 WeightVar(12,16)
2527 WeightVar(12,8)
2528 WeightVar(6,10)
2529 WeightVar(6,2)
2530 WeightVar(11,7)
2531 WeightVar(11,17)
2532 WeightVar(11,5)
2533 WeightVar(7,13)
2534 WeightVar(7,1)
2535 WeightVar(12,6)
2536 WeightVar(12,18)
2537 WeightVar(8,14)
2538 WeightVar(8,2)
2539 WeightVar(13,17)
2540 WeightVar(13,9)
2541 WeightVar(7,3)
2542 WeightVar(16,10)
2543 WeightVar(16,22)
2544 WeightVar(17,21)
2545 WeightVar(11,15)
2546 WeightVar(18,14)
2547 WeightVar(18,22)
2548 WeightVar(17,23)
2549 WeightVar(17,19)
2550 #undef WeightVar
2551
2552 if (
2553 w_12_16 + w_12_8 + w_6_10 + w_6_2 + (4 * w_11_7) <
2554 w_11_17 + w_11_5 + w_7_13 + w_7_1 + (4 * w_12_6)
2555 )
2556 Mix2Pixels(pixels,(ssize_t) (w_12_11 <= w_12_7 ? 11 : 7),12,result,0,
2557 channels);
2558 else
2559 CopyPixels(pixels,12,result,0,channels);
2560 if (
2561 w_12_18 + w_12_6 + w_8_14 + w_8_2 + (4 * w_7_13) <
2562 w_13_17 + w_13_9 + w_11_7 + w_7_3 + (4 * w_12_8)
2563 )
2564 Mix2Pixels(pixels,(ssize_t) (w_12_7 <= w_12_13 ? 7 : 13),12,result,1,
2565 channels);
2566 else
2567 CopyPixels(pixels,12,result,1,channels);
2568 if (
2569 w_12_6 + w_12_18 + w_16_10 + w_16_22 + (4 * w_11_17) <
2570 w_11_7 + w_11_15 + w_13_17 + w_17_21 + (4 * w_12_16)
2571 )
2572 Mix2Pixels(pixels,(ssize_t) (w_12_11 <= w_12_17 ? 11 : 17),12,result,2,
2573 channels);
2574 else
2575 CopyPixels(pixels,12,result,2,channels);
2576 if (
2577 w_12_8 + w_12_16 + w_18_14 + w_18_22 + (4 * w_13_17) <
2578 w_11_17 + w_17_23 + w_17_19 + w_7_13 + (4 * w_12_18)
2579 )
2580 Mix2Pixels(pixels,(ssize_t) (w_12_13 <= w_12_17 ? 13 : 17),12,result,3,
2581 channels);
2582 else
2583 CopyPixels(pixels,12,result,3,channels);
2584 }
2585
Scale2X(const Image * source,const Quantum * pixels,Quantum * result,const size_t channels)2586 static void Scale2X(const Image *source,const Quantum *pixels,Quantum *result,
2587 const size_t channels)
2588 {
2589 if (PixelsEqual(pixels,1,pixels,7,channels) ||
2590 PixelsEqual(pixels,3,pixels,5,channels))
2591 {
2592 ssize_t
2593 i;
2594
2595 for (i=0; i < 4; i++)
2596 CopyPixels(pixels,4,result,i,channels);
2597 return;
2598 }
2599 if (PixelsEqual(pixels,1,pixels,3,channels))
2600 CopyPixels(pixels,3,result,0,channels);
2601 else
2602 CopyPixels(pixels,4,result,0,channels);
2603 if (PixelsEqual(pixels,1,pixels,5,channels))
2604 CopyPixels(pixels,5,result,1,channels);
2605 else
2606 CopyPixels(pixels,4,result,1,channels);
2607 if (PixelsEqual(pixels,3,pixels,7,channels))
2608 CopyPixels(pixels,3,result,2,channels);
2609 else
2610 CopyPixels(pixels,4,result,2,channels);
2611 if (PixelsEqual(pixels,5,pixels,7,channels))
2612 CopyPixels(pixels,5,result,3,channels);
2613 else
2614 CopyPixels(pixels,4,result,3,channels);
2615 }
2616
Epbx2X(const Image * source,const Quantum * pixels,Quantum * result,const size_t channels)2617 static void Epbx2X(const Image *source,const Quantum *pixels,
2618 Quantum *result,const size_t channels)
2619 {
2620 #define HelperCond(a,b,c,d,e,f,g) ( \
2621 PixelsEqual(pixels,a,pixels,b,channels) && ( \
2622 PixelsEqual(pixels,c,pixels,d,channels) || \
2623 PixelsEqual(pixels,c,pixels,e,channels) || \
2624 PixelsEqual(pixels,a,pixels,f,channels) || \
2625 PixelsEqual(pixels,b,pixels,g,channels) \
2626 ) \
2627 )
2628
2629 ssize_t
2630 i;
2631
2632 for (i=0; i < 4; i++)
2633 CopyPixels(pixels,4,result,i,channels);
2634 if (
2635 !PixelsEqual(pixels,3,pixels,5,channels) &&
2636 !PixelsEqual(pixels,1,pixels,7,channels) &&
2637 (
2638 PixelsEqual(pixels,4,pixels,3,channels) ||
2639 PixelsEqual(pixels,4,pixels,7,channels) ||
2640 PixelsEqual(pixels,4,pixels,5,channels) ||
2641 PixelsEqual(pixels,4,pixels,1,channels) ||
2642 (
2643 (
2644 !PixelsEqual(pixels,0,pixels,8,channels) ||
2645 PixelsEqual(pixels,4,pixels,6,channels) ||
2646 PixelsEqual(pixels,3,pixels,2,channels)
2647 ) &&
2648 (
2649 !PixelsEqual(pixels,6,pixels,2,channels) ||
2650 PixelsEqual(pixels,4,pixels,0,channels) ||
2651 PixelsEqual(pixels,4,pixels,8,channels)
2652 )
2653 )
2654 )
2655 )
2656 {
2657 if (HelperCond(1,3,4,0,8,2,6))
2658 Mix2Pixels(pixels,1,3,result,0,channels);
2659 if (HelperCond(5,1,4,2,6,8,0))
2660 Mix2Pixels(pixels,5,1,result,1,channels);
2661 if (HelperCond(3,7,4,6,2,0,8))
2662 Mix2Pixels(pixels,3,7,result,2,channels);
2663 if (HelperCond(7,5,4,8,0,6,2))
2664 Mix2Pixels(pixels,7,5,result,3,channels);
2665 }
2666
2667 #undef HelperCond
2668 }
2669
Eagle3X(const Image * source,const Quantum * pixels,Quantum * result,const size_t channels)2670 static inline void Eagle3X(const Image *source,const Quantum *pixels,
2671 Quantum *result,const size_t channels)
2672 {
2673 ssize_t
2674 corner_tl,
2675 corner_tr,
2676 corner_bl,
2677 corner_br;
2678
2679 corner_tl=PixelsEqual(pixels,0,pixels,1,channels) &&
2680 PixelsEqual(pixels,0,pixels,3,channels);
2681 corner_tr=PixelsEqual(pixels,1,pixels,2,channels) &&
2682 PixelsEqual(pixels,2,pixels,5,channels);
2683 corner_bl=PixelsEqual(pixels,3,pixels,6,channels) &&
2684 PixelsEqual(pixels,6,pixels,7,channels);
2685 corner_br=PixelsEqual(pixels,5,pixels,7,channels) &&
2686 PixelsEqual(pixels,7,pixels,8,channels);
2687 CopyPixels(pixels,(ssize_t) (corner_tl ? 0 : 4),result,0,channels);
2688 if (corner_tl && corner_tr)
2689 Mix2Pixels(pixels,0,2,result,1,channels);
2690 else
2691 CopyPixels(pixels,4,result,1,channels);
2692 CopyPixels(pixels,(ssize_t) (corner_tr ? 1 : 4),result,2,channels);
2693 if (corner_tl && corner_bl)
2694 Mix2Pixels(pixels,0,6,result,3,channels);
2695 else
2696 CopyPixels(pixels,4,result,3,channels);
2697 CopyPixels(pixels,4,result,4,channels);
2698 if (corner_tr && corner_br)
2699 Mix2Pixels(pixels,2,8,result,5,channels);
2700 else
2701 CopyPixels(pixels,4,result,5,channels);
2702 CopyPixels(pixels,(ssize_t) (corner_bl ? 3 : 4),result,6,channels);
2703 if (corner_bl && corner_br)
2704 Mix2Pixels(pixels,6,8,result,7,channels);
2705 else
2706 CopyPixels(pixels,4,result,7,channels);
2707 CopyPixels(pixels,(ssize_t) (corner_br ? 5 : 4),result,8,channels);
2708 }
2709
Eagle3XB(const Image * source,const Quantum * pixels,Quantum * result,const size_t channels)2710 static inline void Eagle3XB(const Image *source,const Quantum *pixels,
2711 Quantum *result,const size_t channels)
2712 {
2713 ssize_t
2714 corner_tl,
2715 corner_tr,
2716 corner_bl,
2717 corner_br;
2718
2719 corner_tl=PixelsEqual(pixels,0,pixels,1,channels) &&
2720 PixelsEqual(pixels,0,pixels,3,channels);
2721 corner_tr=PixelsEqual(pixels,1,pixels,2,channels) &&
2722 PixelsEqual(pixels,2,pixels,5,channels);
2723 corner_bl=PixelsEqual(pixels,3,pixels,6,channels) &&
2724 PixelsEqual(pixels,6,pixels,7,channels);
2725 corner_br=PixelsEqual(pixels,5,pixels,7,channels) &&
2726 PixelsEqual(pixels,7,pixels,8,channels);
2727 CopyPixels(pixels,(ssize_t) (corner_tl ? 0 : 4),result,0,channels);
2728 CopyPixels(pixels,4,result,1,channels);
2729 CopyPixels(pixels,(ssize_t) (corner_tr ? 1 : 4),result,2,channels);
2730 CopyPixels(pixels,4,result,3,channels);
2731 CopyPixels(pixels,4,result,4,channels);
2732 CopyPixels(pixels,4,result,5,channels);
2733 CopyPixels(pixels,(ssize_t) (corner_bl ? 3 : 4),result,6,channels);
2734 CopyPixels(pixels,4,result,7,channels);
2735 CopyPixels(pixels,(ssize_t) (corner_br ? 5 : 4),result,8,channels);
2736 }
2737
Scale3X(const Image * source,const Quantum * pixels,Quantum * result,const size_t channels)2738 static inline void Scale3X(const Image *source,const Quantum *pixels,
2739 Quantum *result,const size_t channels)
2740 {
2741 if (!PixelsEqual(pixels,1,pixels,7,channels) &&
2742 !PixelsEqual(pixels,3,pixels,5,channels))
2743 {
2744 if (PixelsEqual(pixels,3,pixels,1,channels))
2745 CopyPixels(pixels,3,result,0,channels);
2746 else
2747 CopyPixels(pixels,4,result,0,channels);
2748
2749 if (
2750 (
2751 PixelsEqual(pixels,3,pixels,1,channels) &&
2752 !PixelsEqual(pixels,4,pixels,2,channels)
2753 ) ||
2754 (
2755 PixelsEqual(pixels,5,pixels,1,channels) &&
2756 !PixelsEqual(pixels,4,pixels,0,channels)
2757 )
2758 )
2759 CopyPixels(pixels,1,result,1,channels);
2760 else
2761 CopyPixels(pixels,4,result,1,channels);
2762 if (PixelsEqual(pixels,5,pixels,1,channels))
2763 CopyPixels(pixels,5,result,2,channels);
2764 else
2765 CopyPixels(pixels,4,result,2,channels);
2766 if (
2767 (
2768 PixelsEqual(pixels,3,pixels,1,channels) &&
2769 !PixelsEqual(pixels,4,pixels,6,channels)
2770 ) ||
2771 (
2772 PixelsEqual(pixels,3,pixels,7,channels) &&
2773 !PixelsEqual(pixels,4,pixels,0,channels)
2774 )
2775 )
2776 CopyPixels(pixels,3,result,3,channels);
2777 else
2778 CopyPixels(pixels,4,result,3,channels);
2779 CopyPixels(pixels,4,result,4,channels);
2780 if (
2781 (
2782 PixelsEqual(pixels,5,pixels,1,channels) &&
2783 !PixelsEqual(pixels,4,pixels,8,channels)
2784 ) ||
2785 (
2786 PixelsEqual(pixels,5,pixels,7,channels) &&
2787 !PixelsEqual(pixels,4,pixels,2,channels)
2788 )
2789 )
2790 CopyPixels(pixels,5,result,5,channels);
2791 else
2792 CopyPixels(pixels,4,result,5,channels);
2793 if (PixelsEqual(pixels,3,pixels,7,channels))
2794 CopyPixels(pixels,3,result,6,channels);
2795 else
2796 CopyPixels(pixels,4,result,6,channels);
2797 if (
2798 (
2799 PixelsEqual(pixels,3,pixels,7,channels) &&
2800 !PixelsEqual(pixels,4,pixels,8,channels)
2801 ) ||
2802 (
2803 PixelsEqual(pixels,5,pixels,7,channels) &&
2804 !PixelsEqual(pixels,4,pixels,6,channels)
2805 )
2806 )
2807 CopyPixels(pixels,7,result,7,channels);
2808 else
2809 CopyPixels(pixels,4,result,7,channels);
2810 if (PixelsEqual(pixels,5,pixels,7,channels))
2811 CopyPixels(pixels,5,result,8,channels);
2812 else
2813 CopyPixels(pixels,4,result,8,channels);
2814 }
2815 else
2816 {
2817 ssize_t
2818 i;
2819
2820 for (i=0; i < 9; i++)
2821 CopyPixels(pixels,4,result,i,channels);
2822 }
2823 }
2824
MagnifyImage(const Image * image,ExceptionInfo * exception)2825 MagickExport Image *MagnifyImage(const Image *image,ExceptionInfo *exception)
2826 {
2827 #define MagnifyImageTag "Magnify/Image"
2828
2829 CacheView
2830 *image_view,
2831 *magnify_view;
2832
2833 const char
2834 *option;
2835
2836 Image
2837 *source_image,
2838 *magnify_image;
2839
2840 MagickBooleanType
2841 status;
2842
2843 MagickOffsetType
2844 progress;
2845
2846 OffsetInfo
2847 offset;
2848
2849 RectangleInfo
2850 rectangle;
2851
2852 ssize_t
2853 y;
2854
2855 unsigned char
2856 magnification,
2857 width;
2858
2859 void
2860 (*scaling_method)(const Image *,const Quantum *,Quantum *,size_t);
2861
2862 /*
2863 Initialize magnified image attributes.
2864 */
2865 assert(image != (const Image *) NULL);
2866 assert(image->signature == MagickCoreSignature);
2867 if (image->debug != MagickFalse)
2868 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
2869 assert(exception != (ExceptionInfo *) NULL);
2870 assert(exception->signature == MagickCoreSignature);
2871 option=GetImageOption(image->image_info,"magnify:method");
2872 if (option == (char *) NULL)
2873 option="scale2x";
2874 scaling_method=Scale2X;
2875 magnification=1;
2876 width=1;
2877 switch (*option)
2878 {
2879 case 'e':
2880 {
2881 if (LocaleCompare(option,"eagle2x") == 0)
2882 {
2883 scaling_method=Eagle2X;
2884 magnification=2;
2885 width=3;
2886 break;
2887 }
2888 if (LocaleCompare(option,"eagle3x") == 0)
2889 {
2890 scaling_method=Eagle3X;
2891 magnification=3;
2892 width=3;
2893 break;
2894 }
2895 if (LocaleCompare(option,"eagle3xb") == 0)
2896 {
2897 scaling_method=Eagle3XB;
2898 magnification=3;
2899 width=3;
2900 break;
2901 }
2902 if (LocaleCompare(option,"epbx2x") == 0)
2903 {
2904 scaling_method=Epbx2X;
2905 magnification=2;
2906 width=3;
2907 break;
2908 }
2909 break;
2910 }
2911 case 'f':
2912 {
2913 if (LocaleCompare(option,"fish2x") == 0)
2914 {
2915 scaling_method=Fish2X;
2916 magnification=2;
2917 width=3;
2918 break;
2919 }
2920 break;
2921 }
2922 case 'h':
2923 {
2924 if (LocaleCompare(option,"hq2x") == 0)
2925 {
2926 scaling_method=Hq2X;
2927 magnification=2;
2928 width=3;
2929 break;
2930 }
2931 break;
2932 }
2933 case 's':
2934 {
2935 if (LocaleCompare(option,"scale2x") == 0)
2936 {
2937 scaling_method=Scale2X;
2938 magnification=2;
2939 width=3;
2940 break;
2941 }
2942 if (LocaleCompare(option,"scale3x") == 0)
2943 {
2944 scaling_method=Scale3X;
2945 magnification=3;
2946 width=3;
2947 break;
2948 }
2949 break;
2950 }
2951 case 'x':
2952 {
2953 if (LocaleCompare(option,"xbr2x") == 0)
2954 {
2955 scaling_method=Xbr2X;
2956 magnification=2;
2957 width=5;
2958 }
2959 break;
2960 }
2961 default:
2962 break;
2963 }
2964 /*
2965 Make a working copy of the source image and convert it to RGB colorspace.
2966 */
2967 source_image=CloneImage(image,image->columns,image->rows,MagickTrue,
2968 exception);
2969 if (source_image == (Image *) NULL)
2970 return((Image *) NULL);
2971 offset.x=0;
2972 offset.y=0;
2973 rectangle.x=0;
2974 rectangle.y=0;
2975 rectangle.width=image->columns;
2976 rectangle.height=image->rows;
2977 (void) CopyImagePixels(source_image,image,&rectangle,&offset,exception);
2978 (void) SetImageColorspace(source_image,RGBColorspace,exception);
2979 magnify_image=CloneImage(source_image,magnification*source_image->columns,
2980 magnification*source_image->rows,MagickTrue,exception);
2981 if (magnify_image == (Image *) NULL)
2982 {
2983 source_image=DestroyImage(source_image);
2984 return((Image *) NULL);
2985 }
2986 /*
2987 Magnify the image.
2988 */
2989 status=MagickTrue;
2990 progress=0;
2991 image_view=AcquireVirtualCacheView(source_image,exception);
2992 magnify_view=AcquireAuthenticCacheView(magnify_image,exception);
2993 #if defined(MAGICKCORE_OPENMP_SUPPORT)
2994 #pragma omp parallel for schedule(static) shared(progress,status) \
2995 magick_number_threads(source_image,magnify_image,source_image->rows,1)
2996 #endif
2997 for (y=0; y < (ssize_t) source_image->rows; y++)
2998 {
2999 Quantum
3000 r[128]; /* to hold result pixels */
3001
3002 Quantum
3003 *magick_restrict q;
3004
3005 ssize_t
3006 x;
3007
3008 if (status == MagickFalse)
3009 continue;
3010 q=QueueCacheViewAuthenticPixels(magnify_view,0,magnification*y,
3011 magnify_image->columns,magnification,exception);
3012 if (q == (Quantum *) NULL)
3013 {
3014 status=MagickFalse;
3015 continue;
3016 }
3017 /*
3018 Magnify this row of pixels.
3019 */
3020 for (x=0; x < (ssize_t) source_image->columns; x++)
3021 {
3022 const Quantum
3023 *magick_restrict p;
3024
3025 size_t
3026 channels;
3027
3028 ssize_t
3029 i;
3030
3031 ssize_t
3032 j;
3033
3034 p=GetCacheViewVirtualPixels(image_view,x-width/2,y-width/2,width,width,
3035 exception);
3036 channels=GetPixelChannels(source_image);
3037 scaling_method(source_image,p,r,channels);
3038 /*
3039 Copy the result pixels into the final image.
3040 */
3041 for (j=0; j < (ssize_t) magnification; j++)
3042 for (i=0; i < (ssize_t) (channels*magnification); i++)
3043 q[j*channels*magnify_image->columns+i]=r[j*magnification*channels+i];
3044 q+=magnification*GetPixelChannels(magnify_image);
3045 }
3046 if (SyncCacheViewAuthenticPixels(magnify_view,exception) == MagickFalse)
3047 status=MagickFalse;
3048 if (image->progress_monitor != (MagickProgressMonitor) NULL)
3049 {
3050 MagickBooleanType
3051 proceed;
3052
3053 #if defined(MAGICKCORE_OPENMP_SUPPORT)
3054 #pragma omp atomic
3055 #endif
3056 progress++;
3057 proceed=SetImageProgress(image,MagnifyImageTag,progress,image->rows);
3058 if (proceed == MagickFalse)
3059 status=MagickFalse;
3060 }
3061 }
3062 magnify_view=DestroyCacheView(magnify_view);
3063 image_view=DestroyCacheView(image_view);
3064 source_image=DestroyImage(source_image);
3065 if (status == MagickFalse)
3066 magnify_image=DestroyImage(magnify_image);
3067 return(magnify_image);
3068 }
3069
3070 /*
3071 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3072 % %
3073 % %
3074 % %
3075 % M i n i f y I m a g e %
3076 % %
3077 % %
3078 % %
3079 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3080 %
3081 % MinifyImage() is a convenience method that scales an image proportionally to
3082 % half its size.
3083 %
3084 % The format of the MinifyImage method is:
3085 %
3086 % Image *MinifyImage(const Image *image,ExceptionInfo *exception)
3087 %
3088 % A description of each parameter follows:
3089 %
3090 % o image: the image.
3091 %
3092 % o exception: return any errors or warnings in this structure.
3093 %
3094 */
MinifyImage(const Image * image,ExceptionInfo * exception)3095 MagickExport Image *MinifyImage(const Image *image,ExceptionInfo *exception)
3096 {
3097 Image
3098 *minify_image;
3099
3100 assert(image != (Image *) NULL);
3101 assert(image->signature == MagickCoreSignature);
3102 if (image->debug != MagickFalse)
3103 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
3104 assert(exception != (ExceptionInfo *) NULL);
3105 assert(exception->signature == MagickCoreSignature);
3106 minify_image=ResizeImage(image,image->columns/2,image->rows/2,SplineFilter,
3107 exception);
3108 return(minify_image);
3109 }
3110
3111 /*
3112 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3113 % %
3114 % %
3115 % %
3116 % R e s a m p l e I m a g e %
3117 % %
3118 % %
3119 % %
3120 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3121 %
3122 % ResampleImage() resize image in terms of its pixel size, so that when
3123 % displayed at the given resolution it will be the same size in terms of
3124 % real world units as the original image at the original resolution.
3125 %
3126 % The format of the ResampleImage method is:
3127 %
3128 % Image *ResampleImage(Image *image,const double x_resolution,
3129 % const double y_resolution,const FilterType filter,
3130 % ExceptionInfo *exception)
3131 %
3132 % A description of each parameter follows:
3133 %
3134 % o image: the image to be resized to fit the given resolution.
3135 %
3136 % o x_resolution: the new image x resolution.
3137 %
3138 % o y_resolution: the new image y resolution.
3139 %
3140 % o filter: Image filter to use.
3141 %
3142 % o exception: return any errors or warnings in this structure.
3143 %
3144 */
ResampleImage(const Image * image,const double x_resolution,const double y_resolution,const FilterType filter,ExceptionInfo * exception)3145 MagickExport Image *ResampleImage(const Image *image,const double x_resolution,
3146 const double y_resolution,const FilterType filter,ExceptionInfo *exception)
3147 {
3148 #define ResampleImageTag "Resample/Image"
3149
3150 Image
3151 *resample_image;
3152
3153 size_t
3154 height,
3155 width;
3156
3157 /*
3158 Initialize sampled image attributes.
3159 */
3160 assert(image != (const Image *) NULL);
3161 assert(image->signature == MagickCoreSignature);
3162 if (image->debug != MagickFalse)
3163 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
3164 assert(exception != (ExceptionInfo *) NULL);
3165 assert(exception->signature == MagickCoreSignature);
3166 width=(size_t) (x_resolution*image->columns/(image->resolution.x == 0.0 ?
3167 DefaultResolution : image->resolution.x)+0.5);
3168 height=(size_t) (y_resolution*image->rows/(image->resolution.y == 0.0 ?
3169 DefaultResolution : image->resolution.y)+0.5);
3170 resample_image=ResizeImage(image,width,height,filter,exception);
3171 if (resample_image != (Image *) NULL)
3172 {
3173 resample_image->resolution.x=x_resolution;
3174 resample_image->resolution.y=y_resolution;
3175 }
3176 return(resample_image);
3177 }
3178
3179 /*
3180 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3181 % %
3182 % %
3183 % %
3184 % R e s i z e I m a g e %
3185 % %
3186 % %
3187 % %
3188 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3189 %
3190 % ResizeImage() scales an image to the desired dimensions, using the given
3191 % filter (see AcquireFilterInfo()).
3192 %
3193 % If an undefined filter is given the filter defaults to Mitchell for a
3194 % colormapped image, a image with a matte channel, or if the image is
3195 % enlarged. Otherwise the filter defaults to a Lanczos.
3196 %
3197 % ResizeImage() was inspired by Paul Heckbert's "zoom" program.
3198 %
3199 % The format of the ResizeImage method is:
3200 %
3201 % Image *ResizeImage(Image *image,const size_t columns,const size_t rows,
3202 % const FilterType filter,ExceptionInfo *exception)
3203 %
3204 % A description of each parameter follows:
3205 %
3206 % o image: the image.
3207 %
3208 % o columns: the number of columns in the scaled image.
3209 %
3210 % o rows: the number of rows in the scaled image.
3211 %
3212 % o filter: Image filter to use.
3213 %
3214 % o exception: return any errors or warnings in this structure.
3215 %
3216 */
3217
3218 typedef struct _ContributionInfo
3219 {
3220 double
3221 weight;
3222
3223 ssize_t
3224 pixel;
3225 } ContributionInfo;
3226
DestroyContributionThreadSet(ContributionInfo ** contribution)3227 static ContributionInfo **DestroyContributionThreadSet(
3228 ContributionInfo **contribution)
3229 {
3230 ssize_t
3231 i;
3232
3233 assert(contribution != (ContributionInfo **) NULL);
3234 for (i=0; i < (ssize_t) GetMagickResourceLimit(ThreadResource); i++)
3235 if (contribution[i] != (ContributionInfo *) NULL)
3236 contribution[i]=(ContributionInfo *) RelinquishAlignedMemory(
3237 contribution[i]);
3238 contribution=(ContributionInfo **) RelinquishMagickMemory(contribution);
3239 return(contribution);
3240 }
3241
AcquireContributionThreadSet(const size_t count)3242 static ContributionInfo **AcquireContributionThreadSet(const size_t count)
3243 {
3244 ssize_t
3245 i;
3246
3247 ContributionInfo
3248 **contribution;
3249
3250 size_t
3251 number_threads;
3252
3253 number_threads=(size_t) GetMagickResourceLimit(ThreadResource);
3254 contribution=(ContributionInfo **) AcquireQuantumMemory(number_threads,
3255 sizeof(*contribution));
3256 if (contribution == (ContributionInfo **) NULL)
3257 return((ContributionInfo **) NULL);
3258 (void) memset(contribution,0,number_threads*sizeof(*contribution));
3259 for (i=0; i < (ssize_t) number_threads; i++)
3260 {
3261 contribution[i]=(ContributionInfo *) MagickAssumeAligned(
3262 AcquireAlignedMemory(count,sizeof(**contribution)));
3263 if (contribution[i] == (ContributionInfo *) NULL)
3264 return(DestroyContributionThreadSet(contribution));
3265 }
3266 return(contribution);
3267 }
3268
HorizontalFilter(const ResizeFilter * magick_restrict resize_filter,const Image * magick_restrict image,Image * magick_restrict resize_image,const double x_factor,const MagickSizeType span,MagickOffsetType * magick_restrict progress,ExceptionInfo * exception)3269 static MagickBooleanType HorizontalFilter(
3270 const ResizeFilter *magick_restrict resize_filter,
3271 const Image *magick_restrict image,Image *magick_restrict resize_image,
3272 const double x_factor,const MagickSizeType span,
3273 MagickOffsetType *magick_restrict progress,ExceptionInfo *exception)
3274 {
3275 #define ResizeImageTag "Resize/Image"
3276
3277 CacheView
3278 *image_view,
3279 *resize_view;
3280
3281 ClassType
3282 storage_class;
3283
3284 ContributionInfo
3285 **magick_restrict contributions;
3286
3287 MagickBooleanType
3288 status;
3289
3290 double
3291 scale,
3292 support;
3293
3294 ssize_t
3295 x;
3296
3297 /*
3298 Apply filter to resize horizontally from image to resize image.
3299 */
3300 scale=MagickMax(1.0/x_factor+MagickEpsilon,1.0);
3301 support=scale*GetResizeFilterSupport(resize_filter);
3302 storage_class=support > 0.5 ? DirectClass : image->storage_class;
3303 if (SetImageStorageClass(resize_image,storage_class,exception) == MagickFalse)
3304 return(MagickFalse);
3305 if (support < 0.5)
3306 {
3307 /*
3308 Support too small even for nearest neighbour: Reduce to point sampling.
3309 */
3310 support=(double) 0.5;
3311 scale=1.0;
3312 }
3313 contributions=AcquireContributionThreadSet((size_t) (2.0*support+3.0));
3314 if (contributions == (ContributionInfo **) NULL)
3315 {
3316 (void) ThrowMagickException(exception,GetMagickModule(),
3317 ResourceLimitError,"MemoryAllocationFailed","`%s'",image->filename);
3318 return(MagickFalse);
3319 }
3320 status=MagickTrue;
3321 scale=PerceptibleReciprocal(scale);
3322 image_view=AcquireVirtualCacheView(image,exception);
3323 resize_view=AcquireAuthenticCacheView(resize_image,exception);
3324 #if defined(MAGICKCORE_OPENMP_SUPPORT)
3325 #pragma omp parallel for schedule(static) shared(progress,status) \
3326 magick_number_threads(image,resize_image,resize_image->columns,1)
3327 #endif
3328 for (x=0; x < (ssize_t) resize_image->columns; x++)
3329 {
3330 const int
3331 id = GetOpenMPThreadId();
3332
3333 double
3334 bisect,
3335 density;
3336
3337 const Quantum
3338 *magick_restrict p;
3339
3340 ContributionInfo
3341 *magick_restrict contribution;
3342
3343 Quantum
3344 *magick_restrict q;
3345
3346 ssize_t
3347 y;
3348
3349 ssize_t
3350 n,
3351 start,
3352 stop;
3353
3354 if (status == MagickFalse)
3355 continue;
3356 bisect=(double) (x+0.5)/x_factor+MagickEpsilon;
3357 start=(ssize_t) MagickMax(bisect-support+0.5,0.0);
3358 stop=(ssize_t) MagickMin(bisect+support+0.5,(double) image->columns);
3359 density=0.0;
3360 contribution=contributions[id];
3361 for (n=0; n < (stop-start); n++)
3362 {
3363 contribution[n].pixel=start+n;
3364 contribution[n].weight=GetResizeFilterWeight(resize_filter,scale*
3365 ((double) (start+n)-bisect+0.5));
3366 density+=contribution[n].weight;
3367 }
3368 if (n == 0)
3369 continue;
3370 if ((density != 0.0) && (density != 1.0))
3371 {
3372 ssize_t
3373 i;
3374
3375 /*
3376 Normalize.
3377 */
3378 density=PerceptibleReciprocal(density);
3379 for (i=0; i < n; i++)
3380 contribution[i].weight*=density;
3381 }
3382 p=GetCacheViewVirtualPixels(image_view,contribution[0].pixel,0,(size_t)
3383 (contribution[n-1].pixel-contribution[0].pixel+1),image->rows,exception);
3384 q=QueueCacheViewAuthenticPixels(resize_view,x,0,1,resize_image->rows,
3385 exception);
3386 if ((p == (const Quantum *) NULL) || (q == (Quantum *) NULL))
3387 {
3388 status=MagickFalse;
3389 continue;
3390 }
3391 for (y=0; y < (ssize_t) resize_image->rows; y++)
3392 {
3393 ssize_t
3394 i;
3395
3396 for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
3397 {
3398 double
3399 alpha,
3400 gamma,
3401 pixel;
3402
3403 PixelChannel
3404 channel;
3405
3406 PixelTrait
3407 resize_traits,
3408 traits;
3409
3410 ssize_t
3411 j;
3412
3413 ssize_t
3414 k;
3415
3416 channel=GetPixelChannelChannel(image,i);
3417 traits=GetPixelChannelTraits(image,channel);
3418 resize_traits=GetPixelChannelTraits(resize_image,channel);
3419 if ((traits == UndefinedPixelTrait) ||
3420 (resize_traits == UndefinedPixelTrait))
3421 continue;
3422 if (((resize_traits & CopyPixelTrait) != 0) ||
3423 (GetPixelWriteMask(resize_image,q) <= (QuantumRange/2)))
3424 {
3425 j=(ssize_t) (MagickMin(MagickMax(bisect,(double) start),(double)
3426 stop-1.0)+0.5);
3427 k=y*(contribution[n-1].pixel-contribution[0].pixel+1)+
3428 (contribution[j-start].pixel-contribution[0].pixel);
3429 SetPixelChannel(resize_image,channel,p[k*GetPixelChannels(image)+i],
3430 q);
3431 continue;
3432 }
3433 pixel=0.0;
3434 if ((resize_traits & BlendPixelTrait) == 0)
3435 {
3436 /*
3437 No alpha blending.
3438 */
3439 for (j=0; j < n; j++)
3440 {
3441 k=y*(contribution[n-1].pixel-contribution[0].pixel+1)+
3442 (contribution[j].pixel-contribution[0].pixel);
3443 alpha=contribution[j].weight;
3444 pixel+=alpha*p[k*GetPixelChannels(image)+i];
3445 }
3446 SetPixelChannel(resize_image,channel,ClampToQuantum(pixel),q);
3447 continue;
3448 }
3449 /*
3450 Alpha blending.
3451 */
3452 gamma=0.0;
3453 for (j=0; j < n; j++)
3454 {
3455 k=y*(contribution[n-1].pixel-contribution[0].pixel+1)+
3456 (contribution[j].pixel-contribution[0].pixel);
3457 alpha=contribution[j].weight*QuantumScale*
3458 GetPixelAlpha(image,p+k*GetPixelChannels(image));
3459 pixel+=alpha*p[k*GetPixelChannels(image)+i];
3460 gamma+=alpha;
3461 }
3462 gamma=PerceptibleReciprocal(gamma);
3463 SetPixelChannel(resize_image,channel,ClampToQuantum(gamma*pixel),q);
3464 }
3465 q+=GetPixelChannels(resize_image);
3466 }
3467 if (SyncCacheViewAuthenticPixels(resize_view,exception) == MagickFalse)
3468 status=MagickFalse;
3469 if (image->progress_monitor != (MagickProgressMonitor) NULL)
3470 {
3471 MagickBooleanType
3472 proceed;
3473
3474 #if defined(MAGICKCORE_OPENMP_SUPPORT)
3475 #pragma omp atomic
3476 #endif
3477 (*progress)++;
3478 proceed=SetImageProgress(image,ResizeImageTag,*progress,span);
3479 if (proceed == MagickFalse)
3480 status=MagickFalse;
3481 }
3482 }
3483 resize_view=DestroyCacheView(resize_view);
3484 image_view=DestroyCacheView(image_view);
3485 contributions=DestroyContributionThreadSet(contributions);
3486 return(status);
3487 }
3488
VerticalFilter(const ResizeFilter * magick_restrict resize_filter,const Image * magick_restrict image,Image * magick_restrict resize_image,const double y_factor,const MagickSizeType span,MagickOffsetType * magick_restrict progress,ExceptionInfo * exception)3489 static MagickBooleanType VerticalFilter(
3490 const ResizeFilter *magick_restrict resize_filter,
3491 const Image *magick_restrict image,Image *magick_restrict resize_image,
3492 const double y_factor,const MagickSizeType span,
3493 MagickOffsetType *magick_restrict progress,ExceptionInfo *exception)
3494 {
3495 CacheView
3496 *image_view,
3497 *resize_view;
3498
3499 ClassType
3500 storage_class;
3501
3502 ContributionInfo
3503 **magick_restrict contributions;
3504
3505 double
3506 scale,
3507 support;
3508
3509 MagickBooleanType
3510 status;
3511
3512 ssize_t
3513 y;
3514
3515 /*
3516 Apply filter to resize vertically from image to resize image.
3517 */
3518 scale=MagickMax(1.0/y_factor+MagickEpsilon,1.0);
3519 support=scale*GetResizeFilterSupport(resize_filter);
3520 storage_class=support > 0.5 ? DirectClass : image->storage_class;
3521 if (SetImageStorageClass(resize_image,storage_class,exception) == MagickFalse)
3522 return(MagickFalse);
3523 if (support < 0.5)
3524 {
3525 /*
3526 Support too small even for nearest neighbour: Reduce to point sampling.
3527 */
3528 support=(double) 0.5;
3529 scale=1.0;
3530 }
3531 contributions=AcquireContributionThreadSet((size_t) (2.0*support+3.0));
3532 if (contributions == (ContributionInfo **) NULL)
3533 {
3534 (void) ThrowMagickException(exception,GetMagickModule(),
3535 ResourceLimitError,"MemoryAllocationFailed","`%s'",image->filename);
3536 return(MagickFalse);
3537 }
3538 status=MagickTrue;
3539 scale=PerceptibleReciprocal(scale);
3540 image_view=AcquireVirtualCacheView(image,exception);
3541 resize_view=AcquireAuthenticCacheView(resize_image,exception);
3542 #if defined(MAGICKCORE_OPENMP_SUPPORT)
3543 #pragma omp parallel for schedule(static) shared(progress,status) \
3544 magick_number_threads(image,resize_image,resize_image->rows,1)
3545 #endif
3546 for (y=0; y < (ssize_t) resize_image->rows; y++)
3547 {
3548 const int
3549 id = GetOpenMPThreadId();
3550
3551 double
3552 bisect,
3553 density;
3554
3555 const Quantum
3556 *magick_restrict p;
3557
3558 ContributionInfo
3559 *magick_restrict contribution;
3560
3561 Quantum
3562 *magick_restrict q;
3563
3564 ssize_t
3565 x;
3566
3567 ssize_t
3568 n,
3569 start,
3570 stop;
3571
3572 if (status == MagickFalse)
3573 continue;
3574 bisect=(double) (y+0.5)/y_factor+MagickEpsilon;
3575 start=(ssize_t) MagickMax(bisect-support+0.5,0.0);
3576 stop=(ssize_t) MagickMin(bisect+support+0.5,(double) image->rows);
3577 density=0.0;
3578 contribution=contributions[id];
3579 for (n=0; n < (stop-start); n++)
3580 {
3581 contribution[n].pixel=start+n;
3582 contribution[n].weight=GetResizeFilterWeight(resize_filter,scale*
3583 ((double) (start+n)-bisect+0.5));
3584 density+=contribution[n].weight;
3585 }
3586 if (n == 0)
3587 continue;
3588 if ((density != 0.0) && (density != 1.0))
3589 {
3590 ssize_t
3591 i;
3592
3593 /*
3594 Normalize.
3595 */
3596 density=PerceptibleReciprocal(density);
3597 for (i=0; i < n; i++)
3598 contribution[i].weight*=density;
3599 }
3600 p=GetCacheViewVirtualPixels(image_view,0,contribution[0].pixel,
3601 image->columns,(size_t) (contribution[n-1].pixel-contribution[0].pixel+1),
3602 exception);
3603 q=QueueCacheViewAuthenticPixels(resize_view,0,y,resize_image->columns,1,
3604 exception);
3605 if ((p == (const Quantum *) NULL) || (q == (Quantum *) NULL))
3606 {
3607 status=MagickFalse;
3608 continue;
3609 }
3610 for (x=0; x < (ssize_t) resize_image->columns; x++)
3611 {
3612 ssize_t
3613 i;
3614
3615 for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
3616 {
3617 double
3618 alpha,
3619 gamma,
3620 pixel;
3621
3622 PixelChannel
3623 channel;
3624
3625 PixelTrait
3626 resize_traits,
3627 traits;
3628
3629 ssize_t
3630 j;
3631
3632 ssize_t
3633 k;
3634
3635 channel=GetPixelChannelChannel(image,i);
3636 traits=GetPixelChannelTraits(image,channel);
3637 resize_traits=GetPixelChannelTraits(resize_image,channel);
3638 if ((traits == UndefinedPixelTrait) ||
3639 (resize_traits == UndefinedPixelTrait))
3640 continue;
3641 if (((resize_traits & CopyPixelTrait) != 0) ||
3642 (GetPixelWriteMask(resize_image,q) <= (QuantumRange/2)))
3643 {
3644 j=(ssize_t) (MagickMin(MagickMax(bisect,(double) start),(double)
3645 stop-1.0)+0.5);
3646 k=(ssize_t) ((contribution[j-start].pixel-contribution[0].pixel)*
3647 image->columns+x);
3648 SetPixelChannel(resize_image,channel,p[k*GetPixelChannels(image)+i],
3649 q);
3650 continue;
3651 }
3652 pixel=0.0;
3653 if ((resize_traits & BlendPixelTrait) == 0)
3654 {
3655 /*
3656 No alpha blending.
3657 */
3658 for (j=0; j < n; j++)
3659 {
3660 k=(ssize_t) ((contribution[j].pixel-contribution[0].pixel)*
3661 image->columns+x);
3662 alpha=contribution[j].weight;
3663 pixel+=alpha*p[k*GetPixelChannels(image)+i];
3664 }
3665 SetPixelChannel(resize_image,channel,ClampToQuantum(pixel),q);
3666 continue;
3667 }
3668 gamma=0.0;
3669 for (j=0; j < n; j++)
3670 {
3671 k=(ssize_t) ((contribution[j].pixel-contribution[0].pixel)*
3672 image->columns+x);
3673 alpha=contribution[j].weight*QuantumScale*GetPixelAlpha(image,p+k*
3674 GetPixelChannels(image));
3675 pixel+=alpha*p[k*GetPixelChannels(image)+i];
3676 gamma+=alpha;
3677 }
3678 gamma=PerceptibleReciprocal(gamma);
3679 SetPixelChannel(resize_image,channel,ClampToQuantum(gamma*pixel),q);
3680 }
3681 q+=GetPixelChannels(resize_image);
3682 }
3683 if (SyncCacheViewAuthenticPixels(resize_view,exception) == MagickFalse)
3684 status=MagickFalse;
3685 if (image->progress_monitor != (MagickProgressMonitor) NULL)
3686 {
3687 MagickBooleanType
3688 proceed;
3689
3690 #if defined(MAGICKCORE_OPENMP_SUPPORT)
3691 #pragma omp atomic
3692 #endif
3693 (*progress)++;
3694 proceed=SetImageProgress(image,ResizeImageTag,*progress,span);
3695 if (proceed == MagickFalse)
3696 status=MagickFalse;
3697 }
3698 }
3699 resize_view=DestroyCacheView(resize_view);
3700 image_view=DestroyCacheView(image_view);
3701 contributions=DestroyContributionThreadSet(contributions);
3702 return(status);
3703 }
3704
ResizeImage(const Image * image,const size_t columns,const size_t rows,const FilterType filter,ExceptionInfo * exception)3705 MagickExport Image *ResizeImage(const Image *image,const size_t columns,
3706 const size_t rows,const FilterType filter,ExceptionInfo *exception)
3707 {
3708 double
3709 x_factor,
3710 y_factor;
3711
3712 FilterType
3713 filter_type;
3714
3715 Image
3716 *filter_image,
3717 *resize_image;
3718
3719 MagickOffsetType
3720 offset;
3721
3722 MagickSizeType
3723 span;
3724
3725 MagickStatusType
3726 status;
3727
3728 ResizeFilter
3729 *resize_filter;
3730
3731 /*
3732 Acquire resize image.
3733 */
3734 assert(image != (Image *) NULL);
3735 assert(image->signature == MagickCoreSignature);
3736 if (image->debug != MagickFalse)
3737 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
3738 assert(exception != (ExceptionInfo *) NULL);
3739 assert(exception->signature == MagickCoreSignature);
3740 if ((columns == 0) || (rows == 0))
3741 ThrowImageException(ImageError,"NegativeOrZeroImageSize");
3742 if ((columns == image->columns) && (rows == image->rows) &&
3743 (filter == UndefinedFilter))
3744 return(CloneImage(image,0,0,MagickTrue,exception));
3745 /*
3746 Acquire resize filter.
3747 */
3748 x_factor=(double) columns/(double) image->columns;
3749 y_factor=(double) rows/(double) image->rows;
3750 filter_type=LanczosFilter;
3751 if (filter != UndefinedFilter)
3752 filter_type=filter;
3753 else
3754 if ((x_factor == 1.0) && (y_factor == 1.0))
3755 filter_type=PointFilter;
3756 else
3757 if ((image->storage_class == PseudoClass) ||
3758 (image->alpha_trait != UndefinedPixelTrait) ||
3759 ((x_factor*y_factor) > 1.0))
3760 filter_type=MitchellFilter;
3761 resize_filter=AcquireResizeFilter(image,filter_type,MagickFalse,exception);
3762 #if defined(MAGICKCORE_OPENCL_SUPPORT)
3763 resize_image=AccelerateResizeImage(image,columns,rows,resize_filter,
3764 exception);
3765 if (resize_image != (Image *) NULL)
3766 {
3767 resize_filter=DestroyResizeFilter(resize_filter);
3768 return(resize_image);
3769 }
3770 #endif
3771 resize_image=CloneImage(image,columns,rows,MagickTrue,exception);
3772 if (resize_image == (Image *) NULL)
3773 {
3774 resize_filter=DestroyResizeFilter(resize_filter);
3775 return(resize_image);
3776 }
3777 if (x_factor > y_factor)
3778 filter_image=CloneImage(image,columns,image->rows,MagickTrue,exception);
3779 else
3780 filter_image=CloneImage(image,image->columns,rows,MagickTrue,exception);
3781 if (filter_image == (Image *) NULL)
3782 {
3783 resize_filter=DestroyResizeFilter(resize_filter);
3784 return(DestroyImage(resize_image));
3785 }
3786 /*
3787 Resize image.
3788 */
3789 offset=0;
3790 if (x_factor > y_factor)
3791 {
3792 span=(MagickSizeType) (filter_image->columns+rows);
3793 status=HorizontalFilter(resize_filter,image,filter_image,x_factor,span,
3794 &offset,exception);
3795 status&=VerticalFilter(resize_filter,filter_image,resize_image,y_factor,
3796 span,&offset,exception);
3797 }
3798 else
3799 {
3800 span=(MagickSizeType) (filter_image->rows+columns);
3801 status=VerticalFilter(resize_filter,image,filter_image,y_factor,span,
3802 &offset,exception);
3803 status&=HorizontalFilter(resize_filter,filter_image,resize_image,x_factor,
3804 span,&offset,exception);
3805 }
3806 /*
3807 Free resources.
3808 */
3809 filter_image=DestroyImage(filter_image);
3810 resize_filter=DestroyResizeFilter(resize_filter);
3811 if (status == MagickFalse)
3812 {
3813 resize_image=DestroyImage(resize_image);
3814 return((Image *) NULL);
3815 }
3816 resize_image->type=image->type;
3817 return(resize_image);
3818 }
3819
3820 /*
3821 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3822 % %
3823 % %
3824 % %
3825 % S a m p l e I m a g e %
3826 % %
3827 % %
3828 % %
3829 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3830 %
3831 % SampleImage() scales an image to the desired dimensions with pixel
3832 % sampling. Unlike other scaling methods, this method does not introduce
3833 % any additional color into the scaled image.
3834 %
3835 % The format of the SampleImage method is:
3836 %
3837 % Image *SampleImage(const Image *image,const size_t columns,
3838 % const size_t rows,ExceptionInfo *exception)
3839 %
3840 % A description of each parameter follows:
3841 %
3842 % o image: the image.
3843 %
3844 % o columns: the number of columns in the sampled image.
3845 %
3846 % o rows: the number of rows in the sampled image.
3847 %
3848 % o exception: return any errors or warnings in this structure.
3849 %
3850 */
SampleImage(const Image * image,const size_t columns,const size_t rows,ExceptionInfo * exception)3851 MagickExport Image *SampleImage(const Image *image,const size_t columns,
3852 const size_t rows,ExceptionInfo *exception)
3853 {
3854 #define SampleImageTag "Sample/Image"
3855
3856 CacheView
3857 *image_view,
3858 *sample_view;
3859
3860 Image
3861 *sample_image;
3862
3863 MagickBooleanType
3864 status;
3865
3866 MagickOffsetType
3867 progress;
3868
3869 ssize_t
3870 x1;
3871
3872 ssize_t
3873 *x_offset,
3874 y;
3875
3876 PointInfo
3877 sample_offset;
3878
3879 /*
3880 Initialize sampled image attributes.
3881 */
3882 assert(image != (const Image *) NULL);
3883 assert(image->signature == MagickCoreSignature);
3884 if (image->debug != MagickFalse)
3885 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
3886 assert(exception != (ExceptionInfo *) NULL);
3887 assert(exception->signature == MagickCoreSignature);
3888 if ((columns == 0) || (rows == 0))
3889 ThrowImageException(ImageError,"NegativeOrZeroImageSize");
3890 if ((columns == image->columns) && (rows == image->rows))
3891 return(CloneImage(image,0,0,MagickTrue,exception));
3892 sample_image=CloneImage(image,columns,rows,MagickTrue,exception);
3893 if (sample_image == (Image *) NULL)
3894 return((Image *) NULL);
3895 /*
3896 Set the sampling offset, default is in the mid-point of sample regions.
3897 */
3898 sample_offset.x=sample_offset.y=0.5-MagickEpsilon;
3899 {
3900 const char
3901 *value;
3902
3903 value=GetImageArtifact(image,"sample:offset");
3904 if (value != (char *) NULL)
3905 {
3906 GeometryInfo
3907 geometry_info;
3908
3909 MagickStatusType
3910 flags;
3911
3912 (void) ParseGeometry(value,&geometry_info);
3913 flags=ParseGeometry(value,&geometry_info);
3914 sample_offset.x=sample_offset.y=geometry_info.rho/100.0-MagickEpsilon;
3915 if ((flags & SigmaValue) != 0)
3916 sample_offset.y=geometry_info.sigma/100.0-MagickEpsilon;
3917 }
3918 }
3919 /*
3920 Allocate scan line buffer and column offset buffers.
3921 */
3922 x_offset=(ssize_t *) AcquireQuantumMemory((size_t) sample_image->columns,
3923 sizeof(*x_offset));
3924 if (x_offset == (ssize_t *) NULL)
3925 {
3926 sample_image=DestroyImage(sample_image);
3927 ThrowImageException(ResourceLimitError,"MemoryAllocationFailed");
3928 }
3929 for (x1=0; x1 < (ssize_t) sample_image->columns; x1++)
3930 x_offset[x1]=(ssize_t) ((((double) x1+sample_offset.x)*image->columns)/
3931 sample_image->columns);
3932 /*
3933 Sample each row.
3934 */
3935 status=MagickTrue;
3936 progress=0;
3937 image_view=AcquireVirtualCacheView(image,exception);
3938 sample_view=AcquireAuthenticCacheView(sample_image,exception);
3939 #if defined(MAGICKCORE_OPENMP_SUPPORT)
3940 #pragma omp parallel for schedule(static) shared(status) \
3941 magick_number_threads(image,sample_image,sample_image->rows,1)
3942 #endif
3943 for (y=0; y < (ssize_t) sample_image->rows; y++)
3944 {
3945 const Quantum
3946 *magick_restrict p;
3947
3948 Quantum
3949 *magick_restrict q;
3950
3951 ssize_t
3952 x;
3953
3954 ssize_t
3955 y_offset;
3956
3957 if (status == MagickFalse)
3958 continue;
3959 y_offset=(ssize_t) ((((double) y+sample_offset.y)*image->rows)/
3960 sample_image->rows);
3961 p=GetCacheViewVirtualPixels(image_view,0,y_offset,image->columns,1,
3962 exception);
3963 q=QueueCacheViewAuthenticPixels(sample_view,0,y,sample_image->columns,1,
3964 exception);
3965 if ((p == (const Quantum *) NULL) || (q == (Quantum *) NULL))
3966 {
3967 status=MagickFalse;
3968 continue;
3969 }
3970 /*
3971 Sample each column.
3972 */
3973 for (x=0; x < (ssize_t) sample_image->columns; x++)
3974 {
3975 ssize_t
3976 i;
3977
3978 if (GetPixelWriteMask(sample_image,q) <= (QuantumRange/2))
3979 {
3980 q+=GetPixelChannels(sample_image);
3981 continue;
3982 }
3983 for (i=0; i < (ssize_t) GetPixelChannels(sample_image); i++)
3984 {
3985 PixelChannel
3986 channel;
3987
3988 PixelTrait
3989 image_traits,
3990 traits;
3991
3992 channel=GetPixelChannelChannel(sample_image,i);
3993 traits=GetPixelChannelTraits(sample_image,channel);
3994 image_traits=GetPixelChannelTraits(image,channel);
3995 if ((traits == UndefinedPixelTrait) ||
3996 (image_traits == UndefinedPixelTrait))
3997 continue;
3998 SetPixelChannel(sample_image,channel,p[x_offset[x]*GetPixelChannels(
3999 image)+i],q);
4000 }
4001 q+=GetPixelChannels(sample_image);
4002 }
4003 if (SyncCacheViewAuthenticPixels(sample_view,exception) == MagickFalse)
4004 status=MagickFalse;
4005 if (image->progress_monitor != (MagickProgressMonitor) NULL)
4006 {
4007 MagickBooleanType
4008 proceed;
4009
4010 proceed=SetImageProgress(image,SampleImageTag,progress++,image->rows);
4011 if (proceed == MagickFalse)
4012 status=MagickFalse;
4013 }
4014 }
4015 image_view=DestroyCacheView(image_view);
4016 sample_view=DestroyCacheView(sample_view);
4017 x_offset=(ssize_t *) RelinquishMagickMemory(x_offset);
4018 sample_image->type=image->type;
4019 if (status == MagickFalse)
4020 sample_image=DestroyImage(sample_image);
4021 return(sample_image);
4022 }
4023
4024 /*
4025 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
4026 % %
4027 % %
4028 % %
4029 % S c a l e I m a g e %
4030 % %
4031 % %
4032 % %
4033 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
4034 %
4035 % ScaleImage() changes the size of an image to the given dimensions.
4036 %
4037 % The format of the ScaleImage method is:
4038 %
4039 % Image *ScaleImage(const Image *image,const size_t columns,
4040 % const size_t rows,ExceptionInfo *exception)
4041 %
4042 % A description of each parameter follows:
4043 %
4044 % o image: the image.
4045 %
4046 % o columns: the number of columns in the scaled image.
4047 %
4048 % o rows: the number of rows in the scaled image.
4049 %
4050 % o exception: return any errors or warnings in this structure.
4051 %
4052 */
ScaleImage(const Image * image,const size_t columns,const size_t rows,ExceptionInfo * exception)4053 MagickExport Image *ScaleImage(const Image *image,const size_t columns,
4054 const size_t rows,ExceptionInfo *exception)
4055 {
4056 #define ScaleImageTag "Scale/Image"
4057
4058 CacheView
4059 *image_view,
4060 *scale_view;
4061
4062 double
4063 alpha,
4064 pixel[CompositePixelChannel],
4065 *scale_scanline,
4066 *scanline,
4067 *x_vector,
4068 *y_vector;
4069
4070 Image
4071 *scale_image;
4072
4073 MagickBooleanType
4074 next_column,
4075 next_row,
4076 proceed,
4077 status;
4078
4079 PixelTrait
4080 scale_traits;
4081
4082 PointInfo
4083 scale,
4084 span;
4085
4086 ssize_t
4087 i;
4088
4089 ssize_t
4090 n,
4091 number_rows,
4092 y;
4093
4094 /*
4095 Initialize scaled image attributes.
4096 */
4097 assert(image != (const Image *) NULL);
4098 assert(image->signature == MagickCoreSignature);
4099 if (image->debug != MagickFalse)
4100 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
4101 assert(exception != (ExceptionInfo *) NULL);
4102 assert(exception->signature == MagickCoreSignature);
4103 if ((columns == 0) || (rows == 0))
4104 ThrowImageException(ImageError,"NegativeOrZeroImageSize");
4105 if ((columns == image->columns) && (rows == image->rows))
4106 return(CloneImage(image,0,0,MagickTrue,exception));
4107 scale_image=CloneImage(image,columns,rows,MagickTrue,exception);
4108 if (scale_image == (Image *) NULL)
4109 return((Image *) NULL);
4110 if (SetImageStorageClass(scale_image,DirectClass,exception) == MagickFalse)
4111 {
4112 scale_image=DestroyImage(scale_image);
4113 return((Image *) NULL);
4114 }
4115 /*
4116 Allocate memory.
4117 */
4118 x_vector=(double *) AcquireQuantumMemory((size_t) image->columns,
4119 MaxPixelChannels*sizeof(*x_vector));
4120 scanline=x_vector;
4121 if (image->rows != scale_image->rows)
4122 scanline=(double *) AcquireQuantumMemory((size_t) image->columns,
4123 MaxPixelChannels*sizeof(*scanline));
4124 scale_scanline=(double *) AcquireQuantumMemory((size_t) scale_image->columns,
4125 MaxPixelChannels*sizeof(*scale_scanline));
4126 y_vector=(double *) AcquireQuantumMemory((size_t) image->columns,
4127 MaxPixelChannels*sizeof(*y_vector));
4128 if ((scanline == (double *) NULL) || (scale_scanline == (double *) NULL) ||
4129 (x_vector == (double *) NULL) || (y_vector == (double *) NULL))
4130 {
4131 if ((image->rows != scale_image->rows) && (scanline != (double *) NULL))
4132 scanline=(double *) RelinquishMagickMemory(scanline);
4133 if (scale_scanline != (double *) NULL)
4134 scale_scanline=(double *) RelinquishMagickMemory(scale_scanline);
4135 if (x_vector != (double *) NULL)
4136 x_vector=(double *) RelinquishMagickMemory(x_vector);
4137 if (y_vector != (double *) NULL)
4138 y_vector=(double *) RelinquishMagickMemory(y_vector);
4139 scale_image=DestroyImage(scale_image);
4140 ThrowImageException(ResourceLimitError,"MemoryAllocationFailed");
4141 }
4142 /*
4143 Scale image.
4144 */
4145 number_rows=0;
4146 next_row=MagickTrue;
4147 span.y=1.0;
4148 scale.y=(double) scale_image->rows/(double) image->rows;
4149 (void) memset(y_vector,0,(size_t) MaxPixelChannels*image->columns*
4150 sizeof(*y_vector));
4151 n=0;
4152 status=MagickTrue;
4153 image_view=AcquireVirtualCacheView(image,exception);
4154 scale_view=AcquireAuthenticCacheView(scale_image,exception);
4155 for (y=0; y < (ssize_t) scale_image->rows; y++)
4156 {
4157 const Quantum
4158 *magick_restrict p;
4159
4160 Quantum
4161 *magick_restrict q;
4162
4163 ssize_t
4164 x;
4165
4166 if (status == MagickFalse)
4167 break;
4168 q=QueueCacheViewAuthenticPixels(scale_view,0,y,scale_image->columns,1,
4169 exception);
4170 if (q == (Quantum *) NULL)
4171 {
4172 status=MagickFalse;
4173 break;
4174 }
4175 alpha=1.0;
4176 if (scale_image->rows == image->rows)
4177 {
4178 /*
4179 Read a new scanline.
4180 */
4181 p=GetCacheViewVirtualPixels(image_view,0,n++,image->columns,1,
4182 exception);
4183 if (p == (const Quantum *) NULL)
4184 {
4185 status=MagickFalse;
4186 break;
4187 }
4188 for (x=0; x < (ssize_t) image->columns; x++)
4189 {
4190 if (GetPixelWriteMask(image,p) <= (QuantumRange/2))
4191 {
4192 p+=GetPixelChannels(image);
4193 continue;
4194 }
4195 if (image->alpha_trait != UndefinedPixelTrait)
4196 alpha=QuantumScale*GetPixelAlpha(image,p);
4197 for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
4198 {
4199 PixelChannel channel = GetPixelChannelChannel(image,i);
4200 PixelTrait traits = GetPixelChannelTraits(image,channel);
4201 if ((traits & BlendPixelTrait) == 0)
4202 {
4203 x_vector[x*GetPixelChannels(image)+i]=(double) p[i];
4204 continue;
4205 }
4206 x_vector[x*GetPixelChannels(image)+i]=alpha*p[i];
4207 }
4208 p+=GetPixelChannels(image);
4209 }
4210 }
4211 else
4212 {
4213 /*
4214 Scale Y direction.
4215 */
4216 while (scale.y < span.y)
4217 {
4218 if ((next_row != MagickFalse) &&
4219 (number_rows < (ssize_t) image->rows))
4220 {
4221 /*
4222 Read a new scanline.
4223 */
4224 p=GetCacheViewVirtualPixels(image_view,0,n++,image->columns,1,
4225 exception);
4226 if (p == (const Quantum *) NULL)
4227 {
4228 status=MagickFalse;
4229 break;
4230 }
4231 for (x=0; x < (ssize_t) image->columns; x++)
4232 {
4233 if (GetPixelWriteMask(image,p) <= (QuantumRange/2))
4234 {
4235 p+=GetPixelChannels(image);
4236 continue;
4237 }
4238 if (image->alpha_trait != UndefinedPixelTrait)
4239 alpha=QuantumScale*GetPixelAlpha(image,p);
4240 for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
4241 {
4242 PixelChannel channel = GetPixelChannelChannel(image,i);
4243 PixelTrait traits = GetPixelChannelTraits(image,channel);
4244 if ((traits & BlendPixelTrait) == 0)
4245 {
4246 x_vector[x*GetPixelChannels(image)+i]=(double) p[i];
4247 continue;
4248 }
4249 x_vector[x*GetPixelChannels(image)+i]=alpha*p[i];
4250 }
4251 p+=GetPixelChannels(image);
4252 }
4253 number_rows++;
4254 }
4255 for (x=0; x < (ssize_t) image->columns; x++)
4256 for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
4257 y_vector[x*GetPixelChannels(image)+i]+=scale.y*
4258 x_vector[x*GetPixelChannels(image)+i];
4259 span.y-=scale.y;
4260 scale.y=(double) scale_image->rows/(double) image->rows;
4261 next_row=MagickTrue;
4262 }
4263 if ((next_row != MagickFalse) && (number_rows < (ssize_t) image->rows))
4264 {
4265 /*
4266 Read a new scanline.
4267 */
4268 p=GetCacheViewVirtualPixels(image_view,0,n++,image->columns,1,
4269 exception);
4270 if (p == (const Quantum *) NULL)
4271 {
4272 status=MagickFalse;
4273 break;
4274 }
4275 for (x=0; x < (ssize_t) image->columns; x++)
4276 {
4277 if (GetPixelWriteMask(image,p) <= (QuantumRange/2))
4278 {
4279 p+=GetPixelChannels(image);
4280 continue;
4281 }
4282 if (image->alpha_trait != UndefinedPixelTrait)
4283 alpha=QuantumScale*GetPixelAlpha(image,p);
4284 for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
4285 {
4286 PixelChannel channel = GetPixelChannelChannel(image,i);
4287 PixelTrait traits = GetPixelChannelTraits(image,channel);
4288 if ((traits & BlendPixelTrait) == 0)
4289 {
4290 x_vector[x*GetPixelChannels(image)+i]=(double) p[i];
4291 continue;
4292 }
4293 x_vector[x*GetPixelChannels(image)+i]=alpha*p[i];
4294 }
4295 p+=GetPixelChannels(image);
4296 }
4297 number_rows++;
4298 next_row=MagickFalse;
4299 }
4300 for (x=0; x < (ssize_t) image->columns; x++)
4301 {
4302 for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
4303 {
4304 pixel[i]=y_vector[x*GetPixelChannels(image)+i]+span.y*
4305 x_vector[x*GetPixelChannels(image)+i];
4306 scanline[x*GetPixelChannels(image)+i]=pixel[i];
4307 y_vector[x*GetPixelChannels(image)+i]=0.0;
4308 }
4309 }
4310 scale.y-=span.y;
4311 if (scale.y <= 0)
4312 {
4313 scale.y=(double) scale_image->rows/(double) image->rows;
4314 next_row=MagickTrue;
4315 }
4316 span.y=1.0;
4317 }
4318 if (scale_image->columns == image->columns)
4319 {
4320 /*
4321 Transfer scanline to scaled image.
4322 */
4323 for (x=0; x < (ssize_t) scale_image->columns; x++)
4324 {
4325 if (GetPixelWriteMask(scale_image,q) <= (QuantumRange/2))
4326 {
4327 q+=GetPixelChannels(scale_image);
4328 continue;
4329 }
4330 if (image->alpha_trait != UndefinedPixelTrait)
4331 {
4332 alpha=QuantumScale*scanline[x*GetPixelChannels(image)+
4333 GetPixelChannelOffset(image,AlphaPixelChannel)];
4334 alpha=PerceptibleReciprocal(alpha);
4335 }
4336 for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
4337 {
4338 PixelChannel channel = GetPixelChannelChannel(image,i);
4339 PixelTrait traits = GetPixelChannelTraits(image,channel);
4340 scale_traits=GetPixelChannelTraits(scale_image,channel);
4341 if ((traits == UndefinedPixelTrait) ||
4342 (scale_traits == UndefinedPixelTrait))
4343 continue;
4344 if ((traits & BlendPixelTrait) == 0)
4345 {
4346 SetPixelChannel(scale_image,channel,ClampToQuantum(
4347 scanline[x*GetPixelChannels(image)+i]),q);
4348 continue;
4349 }
4350 SetPixelChannel(scale_image,channel,ClampToQuantum(alpha*scanline[
4351 x*GetPixelChannels(image)+i]),q);
4352 }
4353 q+=GetPixelChannels(scale_image);
4354 }
4355 }
4356 else
4357 {
4358 ssize_t
4359 t;
4360
4361 /*
4362 Scale X direction.
4363 */
4364 for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
4365 pixel[i]=0.0;
4366 next_column=MagickFalse;
4367 span.x=1.0;
4368 t=0;
4369 for (x=0; x < (ssize_t) image->columns; x++)
4370 {
4371 scale.x=(double) scale_image->columns/(double) image->columns;
4372 while (scale.x >= span.x)
4373 {
4374 if (next_column != MagickFalse)
4375 {
4376 for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
4377 pixel[i]=0.0;
4378 t++;
4379 }
4380 for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
4381 {
4382 PixelChannel channel = GetPixelChannelChannel(image,i);
4383 PixelTrait traits = GetPixelChannelTraits(image,channel);
4384 if (traits == UndefinedPixelTrait)
4385 continue;
4386 pixel[i]+=span.x*scanline[x*GetPixelChannels(image)+i];
4387 scale_scanline[t*GetPixelChannels(image)+i]=pixel[i];
4388 }
4389 scale.x-=span.x;
4390 span.x=1.0;
4391 next_column=MagickTrue;
4392 }
4393 if (scale.x > 0)
4394 {
4395 if (next_column != MagickFalse)
4396 {
4397 for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
4398 pixel[i]=0.0;
4399 next_column=MagickFalse;
4400 t++;
4401 }
4402 for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
4403 pixel[i]+=scale.x*scanline[x*GetPixelChannels(image)+i];
4404 span.x-=scale.x;
4405 }
4406 }
4407 if (span.x > 0)
4408 {
4409 for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
4410 pixel[i]+=span.x*scanline[(x-1)*GetPixelChannels(image)+i];
4411 }
4412 if ((next_column == MagickFalse) && (t < (ssize_t) scale_image->columns))
4413 for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
4414 scale_scanline[t*GetPixelChannels(image)+i]=pixel[i];
4415 /*
4416 Transfer scanline to scaled image.
4417 */
4418 for (x=0; x < (ssize_t) scale_image->columns; x++)
4419 {
4420 if (GetPixelWriteMask(scale_image,q) <= (QuantumRange/2))
4421 {
4422 q+=GetPixelChannels(scale_image);
4423 continue;
4424 }
4425 if (image->alpha_trait != UndefinedPixelTrait)
4426 {
4427 alpha=QuantumScale*scale_scanline[x*GetPixelChannels(image)+
4428 GetPixelChannelOffset(image,AlphaPixelChannel)];
4429 alpha=PerceptibleReciprocal(alpha);
4430 }
4431 for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
4432 {
4433 PixelChannel channel = GetPixelChannelChannel(image,i);
4434 PixelTrait traits = GetPixelChannelTraits(image,channel);
4435 scale_traits=GetPixelChannelTraits(scale_image,channel);
4436 if ((traits == UndefinedPixelTrait) ||
4437 (scale_traits == UndefinedPixelTrait))
4438 continue;
4439 if ((traits & BlendPixelTrait) == 0)
4440 {
4441 SetPixelChannel(scale_image,channel,ClampToQuantum(
4442 scale_scanline[x*GetPixelChannels(image)+i]),q);
4443 continue;
4444 }
4445 SetPixelChannel(scale_image,channel,ClampToQuantum(alpha*
4446 scale_scanline[x*GetPixelChannels(image)+i]),q);
4447 }
4448 q+=GetPixelChannels(scale_image);
4449 }
4450 }
4451 if (SyncCacheViewAuthenticPixels(scale_view,exception) == MagickFalse)
4452 {
4453 status=MagickFalse;
4454 break;
4455 }
4456 proceed=SetImageProgress(image,ScaleImageTag,(MagickOffsetType) y,
4457 image->rows);
4458 if (proceed == MagickFalse)
4459 {
4460 status=MagickFalse;
4461 break;
4462 }
4463 }
4464 scale_view=DestroyCacheView(scale_view);
4465 image_view=DestroyCacheView(image_view);
4466 /*
4467 Free allocated memory.
4468 */
4469 y_vector=(double *) RelinquishMagickMemory(y_vector);
4470 scale_scanline=(double *) RelinquishMagickMemory(scale_scanline);
4471 if (scale_image->rows != image->rows)
4472 scanline=(double *) RelinquishMagickMemory(scanline);
4473 x_vector=(double *) RelinquishMagickMemory(x_vector);
4474 scale_image->type=image->type;
4475 if (status == MagickFalse)
4476 scale_image=DestroyImage(scale_image);
4477 return(scale_image);
4478 }
4479
4480 /*
4481 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
4482 % %
4483 % %
4484 % %
4485 % T h u m b n a i l I m a g e %
4486 % %
4487 % %
4488 % %
4489 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
4490 %
4491 % ThumbnailImage() changes the size of an image to the given dimensions and
4492 % removes any associated profiles. The goal is to produce small low cost
4493 % thumbnail images suited for display on the Web.
4494 %
4495 % The format of the ThumbnailImage method is:
4496 %
4497 % Image *ThumbnailImage(const Image *image,const size_t columns,
4498 % const size_t rows,ExceptionInfo *exception)
4499 %
4500 % A description of each parameter follows:
4501 %
4502 % o image: the image.
4503 %
4504 % o columns: the number of columns in the scaled image.
4505 %
4506 % o rows: the number of rows in the scaled image.
4507 %
4508 % o exception: return any errors or warnings in this structure.
4509 %
4510 */
ThumbnailImage(const Image * image,const size_t columns,const size_t rows,ExceptionInfo * exception)4511 MagickExport Image *ThumbnailImage(const Image *image,const size_t columns,
4512 const size_t rows,ExceptionInfo *exception)
4513 {
4514 #define SampleFactor 5
4515
4516 char
4517 filename[MagickPathExtent],
4518 value[MagickPathExtent];
4519
4520 const char
4521 *name;
4522
4523 Image
4524 *thumbnail_image;
4525
4526 double
4527 x_factor,
4528 y_factor;
4529
4530 struct stat
4531 attributes;
4532
4533 assert(image != (Image *) NULL);
4534 assert(image->signature == MagickCoreSignature);
4535 if (image->debug != MagickFalse)
4536 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
4537 assert(exception != (ExceptionInfo *) NULL);
4538 assert(exception->signature == MagickCoreSignature);
4539 x_factor=(double) columns/(double) image->columns;
4540 y_factor=(double) rows/(double) image->rows;
4541 if ((x_factor*y_factor) > 0.1)
4542 thumbnail_image=ResizeImage(image,columns,rows,image->filter,exception);
4543 else
4544 if (((SampleFactor*columns) < 128) || ((SampleFactor*rows) < 128))
4545 thumbnail_image=ResizeImage(image,columns,rows,image->filter,exception);
4546 else
4547 {
4548 Image
4549 *sample_image;
4550
4551 sample_image=SampleImage(image,SampleFactor*columns,SampleFactor*rows,
4552 exception);
4553 if (sample_image == (Image *) NULL)
4554 return((Image *) NULL);
4555 thumbnail_image=ResizeImage(sample_image,columns,rows,image->filter,
4556 exception);
4557 sample_image=DestroyImage(sample_image);
4558 }
4559 if (thumbnail_image == (Image *) NULL)
4560 return(thumbnail_image);
4561 (void) ParseAbsoluteGeometry("0x0+0+0",&thumbnail_image->page);
4562 if (thumbnail_image->alpha_trait == UndefinedPixelTrait)
4563 (void) SetImageAlphaChannel(thumbnail_image,OpaqueAlphaChannel,exception);
4564 thumbnail_image->depth=8;
4565 thumbnail_image->interlace=NoInterlace;
4566 /*
4567 Strip all profiles except color profiles.
4568 */
4569 ResetImageProfileIterator(thumbnail_image);
4570 for (name=GetNextImageProfile(thumbnail_image); name != (const char *) NULL; )
4571 {
4572 if ((LocaleCompare(name,"icc") != 0) && (LocaleCompare(name,"icm") != 0))
4573 {
4574 (void) DeleteImageProfile(thumbnail_image,name);
4575 ResetImageProfileIterator(thumbnail_image);
4576 }
4577 name=GetNextImageProfile(thumbnail_image);
4578 }
4579 (void) DeleteImageProperty(thumbnail_image,"comment");
4580 (void) CopyMagickString(value,image->magick_filename,MagickPathExtent);
4581 if (strstr(image->magick_filename,"//") == (char *) NULL)
4582 (void) FormatLocaleString(value,MagickPathExtent,"file://%s",
4583 image->magick_filename);
4584 (void) SetImageProperty(thumbnail_image,"Thumb::URI",value,exception);
4585 GetPathComponent(image->magick_filename,TailPath,filename);
4586 (void) CopyMagickString(value,filename,MagickPathExtent);
4587 if ( GetPathAttributes(image->filename,&attributes) != MagickFalse )
4588 (void) FormatImageProperty(thumbnail_image,"Thumb::MTime","%.20g",(double)
4589 attributes.st_mtime);
4590 (void) FormatLocaleString(value,MagickPathExtent,"%.20g",(double)
4591 attributes.st_mtime);
4592 (void) FormatMagickSize(GetBlobSize(image),MagickFalse,"B",MagickPathExtent,
4593 value);
4594 (void) SetImageProperty(thumbnail_image,"Thumb::Size",value,exception);
4595 (void) FormatLocaleString(value,MagickPathExtent,"image/%s",image->magick);
4596 LocaleLower(value);
4597 (void) SetImageProperty(thumbnail_image,"Thumb::Mimetype",value,exception);
4598 (void) SetImageProperty(thumbnail_image,"software",MagickAuthoritativeURL,
4599 exception);
4600 (void) FormatImageProperty(thumbnail_image,"Thumb::Image::Width","%.20g",
4601 (double) image->magick_columns);
4602 (void) FormatImageProperty(thumbnail_image,"Thumb::Image::Height","%.20g",
4603 (double) image->magick_rows);
4604 (void) FormatImageProperty(thumbnail_image,"Thumb::Document::Pages","%.20g",
4605 (double) GetImageListLength(image));
4606 return(thumbnail_image);
4607 }
4608