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-2019 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((double)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 register 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 register 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/=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 register 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 register 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 register 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 register 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((double) x)-
1505 cos((double) x)))-8.0/x*Q1(x)*(-1.0/sqrt(2.0)*(sin((double) x)+
1506 cos((double) x))));
1507 if (p < 0.0)
1508 q=(-q);
1509 return(q);
1510 }
1511
1512 /*
1513 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1514 % %
1515 % %
1516 % %
1517 + D e s t r o y R e s i z e F i l t e r %
1518 % %
1519 % %
1520 % %
1521 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1522 %
1523 % DestroyResizeFilter() destroy the resize filter.
1524 %
1525 % The format of the DestroyResizeFilter method is:
1526 %
1527 % ResizeFilter *DestroyResizeFilter(ResizeFilter *resize_filter)
1528 %
1529 % A description of each parameter follows:
1530 %
1531 % o resize_filter: the resize filter.
1532 %
1533 */
DestroyResizeFilter(ResizeFilter * resize_filter)1534 MagickPrivate ResizeFilter *DestroyResizeFilter(ResizeFilter *resize_filter)
1535 {
1536 assert(resize_filter != (ResizeFilter *) NULL);
1537 assert(resize_filter->signature == MagickCoreSignature);
1538 resize_filter->signature=(~MagickCoreSignature);
1539 resize_filter=(ResizeFilter *) RelinquishMagickMemory(resize_filter);
1540 return(resize_filter);
1541 }
1542
1543 /*
1544 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1545 % %
1546 % %
1547 % %
1548 + G e t R e s i z e F i l t e r S u p p o r t %
1549 % %
1550 % %
1551 % %
1552 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1553 %
1554 % GetResizeFilterSupport() return the current support window size for this
1555 % filter. Note that this may have been enlarged by filter:blur factor.
1556 %
1557 % The format of the GetResizeFilterSupport method is:
1558 %
1559 % double GetResizeFilterSupport(const ResizeFilter *resize_filter)
1560 %
1561 % A description of each parameter follows:
1562 %
1563 % o filter: Image filter to use.
1564 %
1565 */
1566
GetResizeFilterCoefficient(const ResizeFilter * resize_filter)1567 MagickPrivate double *GetResizeFilterCoefficient(
1568 const ResizeFilter *resize_filter)
1569 {
1570 assert(resize_filter != (ResizeFilter *) NULL);
1571 assert(resize_filter->signature == MagickCoreSignature);
1572 return((double *) resize_filter->coefficient);
1573 }
1574
GetResizeFilterBlur(const ResizeFilter * resize_filter)1575 MagickPrivate double GetResizeFilterBlur(const ResizeFilter *resize_filter)
1576 {
1577 assert(resize_filter != (ResizeFilter *) NULL);
1578 assert(resize_filter->signature == MagickCoreSignature);
1579 return(resize_filter->blur);
1580 }
1581
GetResizeFilterScale(const ResizeFilter * resize_filter)1582 MagickPrivate double GetResizeFilterScale(const ResizeFilter *resize_filter)
1583 {
1584 assert(resize_filter != (ResizeFilter *) NULL);
1585 assert(resize_filter->signature == MagickCoreSignature);
1586 return(resize_filter->scale);
1587 }
1588
GetResizeFilterWindowSupport(const ResizeFilter * resize_filter)1589 MagickPrivate double GetResizeFilterWindowSupport(
1590 const ResizeFilter *resize_filter)
1591 {
1592 assert(resize_filter != (ResizeFilter *) NULL);
1593 assert(resize_filter->signature == MagickCoreSignature);
1594 return(resize_filter->window_support);
1595 }
1596
GetResizeFilterWeightingType(const ResizeFilter * resize_filter)1597 MagickPrivate ResizeWeightingFunctionType GetResizeFilterWeightingType(
1598 const ResizeFilter *resize_filter)
1599 {
1600 assert(resize_filter != (ResizeFilter *) NULL);
1601 assert(resize_filter->signature == MagickCoreSignature);
1602 return(resize_filter->filterWeightingType);
1603 }
1604
GetResizeFilterWindowWeightingType(const ResizeFilter * resize_filter)1605 MagickPrivate ResizeWeightingFunctionType GetResizeFilterWindowWeightingType(
1606 const ResizeFilter *resize_filter)
1607 {
1608 assert(resize_filter != (ResizeFilter *) NULL);
1609 assert(resize_filter->signature == MagickCoreSignature);
1610 return(resize_filter->windowWeightingType);
1611 }
1612
GetResizeFilterSupport(const ResizeFilter * resize_filter)1613 MagickPrivate double GetResizeFilterSupport(const ResizeFilter *resize_filter)
1614 {
1615 assert(resize_filter != (ResizeFilter *) NULL);
1616 assert(resize_filter->signature == MagickCoreSignature);
1617 return(resize_filter->support*resize_filter->blur);
1618 }
1619
1620 /*
1621 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1622 % %
1623 % %
1624 % %
1625 + G e t R e s i z e F i l t e r W e i g h t %
1626 % %
1627 % %
1628 % %
1629 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1630 %
1631 % GetResizeFilterWeight evaluates the specified resize filter at the point x
1632 % which usally lies between zero and the filters current 'support' and
1633 % returns the weight of the filter function at that point.
1634 %
1635 % The format of the GetResizeFilterWeight method is:
1636 %
1637 % double GetResizeFilterWeight(const ResizeFilter *resize_filter,
1638 % const double x)
1639 %
1640 % A description of each parameter follows:
1641 %
1642 % o filter: the filter type.
1643 %
1644 % o x: the point.
1645 %
1646 */
GetResizeFilterWeight(const ResizeFilter * resize_filter,const double x)1647 MagickPrivate double GetResizeFilterWeight(const ResizeFilter *resize_filter,
1648 const double x)
1649 {
1650 double
1651 scale,
1652 weight,
1653 x_blur;
1654
1655 /*
1656 Windowing function - scale the weighting filter by this amount.
1657 */
1658 assert(resize_filter != (ResizeFilter *) NULL);
1659 assert(resize_filter->signature == MagickCoreSignature);
1660 x_blur=fabs((double) x)/resize_filter->blur; /* X offset with blur scaling */
1661 if ((resize_filter->window_support < MagickEpsilon) ||
1662 (resize_filter->window == Box))
1663 scale=1.0; /* Point or Box Filter -- avoid division by zero */
1664 else
1665 {
1666 scale=resize_filter->scale;
1667 scale=resize_filter->window(x_blur*scale,resize_filter);
1668 }
1669 weight=scale*resize_filter->filter(x_blur,resize_filter);
1670 return(weight);
1671 }
1672
1673 /*
1674 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1675 % %
1676 % %
1677 % %
1678 % I n t e r p o l a t i v e R e s i z e I m a g e %
1679 % %
1680 % %
1681 % %
1682 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1683 %
1684 % InterpolativeResizeImage() resizes an image using the specified
1685 % interpolation method.
1686 %
1687 % The format of the InterpolativeResizeImage method is:
1688 %
1689 % Image *InterpolativeResizeImage(const Image *image,const size_t columns,
1690 % const size_t rows,const PixelInterpolateMethod method,
1691 % ExceptionInfo *exception)
1692 %
1693 % A description of each parameter follows:
1694 %
1695 % o image: the image.
1696 %
1697 % o columns: the number of columns in the resized image.
1698 %
1699 % o rows: the number of rows in the resized image.
1700 %
1701 % o method: the pixel interpolation method.
1702 %
1703 % o exception: return any errors or warnings in this structure.
1704 %
1705 */
InterpolativeResizeImage(const Image * image,const size_t columns,const size_t rows,const PixelInterpolateMethod method,ExceptionInfo * exception)1706 MagickExport Image *InterpolativeResizeImage(const Image *image,
1707 const size_t columns,const size_t rows,const PixelInterpolateMethod method,
1708 ExceptionInfo *exception)
1709 {
1710 #define InterpolativeResizeImageTag "Resize/Image"
1711
1712 CacheView
1713 *image_view,
1714 *resize_view;
1715
1716 Image
1717 *resize_image;
1718
1719 MagickBooleanType
1720 status;
1721
1722 MagickOffsetType
1723 progress;
1724
1725 PointInfo
1726 scale;
1727
1728 ssize_t
1729 y;
1730
1731 /*
1732 Interpolatively resize image.
1733 */
1734 assert(image != (const Image *) NULL);
1735 assert(image->signature == MagickCoreSignature);
1736 if (image->debug != MagickFalse)
1737 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
1738 assert(exception != (ExceptionInfo *) NULL);
1739 assert(exception->signature == MagickCoreSignature);
1740 if ((columns == 0) || (rows == 0))
1741 ThrowImageException(ImageError,"NegativeOrZeroImageSize");
1742 if ((columns == image->columns) && (rows == image->rows))
1743 return(CloneImage(image,0,0,MagickTrue,exception));
1744 resize_image=CloneImage(image,columns,rows,MagickTrue,exception);
1745 if (resize_image == (Image *) NULL)
1746 return((Image *) NULL);
1747 if (SetImageStorageClass(resize_image,DirectClass,exception) == MagickFalse)
1748 {
1749 resize_image=DestroyImage(resize_image);
1750 return((Image *) NULL);
1751 }
1752 status=MagickTrue;
1753 progress=0;
1754 image_view=AcquireVirtualCacheView(image,exception);
1755 resize_view=AcquireAuthenticCacheView(resize_image,exception);
1756 scale.x=(double) image->columns/resize_image->columns;
1757 scale.y=(double) image->rows/resize_image->rows;
1758 #if defined(MAGICKCORE_OPENMP_SUPPORT)
1759 #pragma omp parallel for schedule(static) shared(progress,status) \
1760 magick_number_threads(image,resize_image,resize_image->rows,1)
1761 #endif
1762 for (y=0; y < (ssize_t) resize_image->rows; y++)
1763 {
1764 PointInfo
1765 offset;
1766
1767 register Quantum
1768 *magick_restrict q;
1769
1770 register ssize_t
1771 x;
1772
1773 if (status == MagickFalse)
1774 continue;
1775 q=QueueCacheViewAuthenticPixels(resize_view,0,y,resize_image->columns,1,
1776 exception);
1777 if (q == (Quantum *) NULL)
1778 continue;
1779 offset.y=((double) y+0.5)*scale.y-0.5;
1780 for (x=0; x < (ssize_t) resize_image->columns; x++)
1781 {
1782 register ssize_t
1783 i;
1784
1785 for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
1786 {
1787 PixelChannel
1788 channel;
1789
1790 PixelTrait
1791 resize_traits,
1792 traits;
1793
1794 channel=GetPixelChannelChannel(image,i);
1795 traits=GetPixelChannelTraits(image,channel);
1796 resize_traits=GetPixelChannelTraits(resize_image,channel);
1797 if ((traits == UndefinedPixelTrait) ||
1798 (resize_traits == UndefinedPixelTrait))
1799 continue;
1800 offset.x=((double) x+0.5)*scale.x-0.5;
1801 status=InterpolatePixelChannels(image,image_view,resize_image,method,
1802 offset.x,offset.y,q,exception);
1803 if (status == MagickFalse)
1804 break;
1805 }
1806 q+=GetPixelChannels(resize_image);
1807 }
1808 if (SyncCacheViewAuthenticPixels(resize_view,exception) == MagickFalse)
1809 status=MagickFalse;
1810 if (image->progress_monitor != (MagickProgressMonitor) NULL)
1811 {
1812 MagickBooleanType
1813 proceed;
1814
1815 #if defined(MAGICKCORE_OPENMP_SUPPORT)
1816 #pragma omp atomic
1817 #endif
1818 progress++;
1819 proceed=SetImageProgress(image,InterpolativeResizeImageTag,progress,
1820 image->rows);
1821 if (proceed == MagickFalse)
1822 status=MagickFalse;
1823 }
1824 }
1825 resize_view=DestroyCacheView(resize_view);
1826 image_view=DestroyCacheView(image_view);
1827 if (status == MagickFalse)
1828 resize_image=DestroyImage(resize_image);
1829 return(resize_image);
1830 }
1831 #if defined(MAGICKCORE_LQR_DELEGATE)
1832
1833 /*
1834 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1835 % %
1836 % %
1837 % %
1838 % L i q u i d R e s c a l e I m a g e %
1839 % %
1840 % %
1841 % %
1842 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1843 %
1844 % LiquidRescaleImage() rescales image with seam carving.
1845 %
1846 % The format of the LiquidRescaleImage method is:
1847 %
1848 % Image *LiquidRescaleImage(const Image *image,const size_t columns,
1849 % const size_t rows,const double delta_x,const double rigidity,
1850 % ExceptionInfo *exception)
1851 %
1852 % A description of each parameter follows:
1853 %
1854 % o image: the image.
1855 %
1856 % o columns: the number of columns in the rescaled image.
1857 %
1858 % o rows: the number of rows in the rescaled image.
1859 %
1860 % o delta_x: maximum seam transversal step (0 means straight seams).
1861 %
1862 % o rigidity: introduce a bias for non-straight seams (typically 0).
1863 %
1864 % o exception: return any errors or warnings in this structure.
1865 %
1866 */
LiquidRescaleImage(const Image * image,const size_t columns,const size_t rows,const double delta_x,const double rigidity,ExceptionInfo * exception)1867 MagickExport Image *LiquidRescaleImage(const Image *image,const size_t columns,
1868 const size_t rows,const double delta_x,const double rigidity,
1869 ExceptionInfo *exception)
1870 {
1871 #define LiquidRescaleImageTag "Rescale/Image"
1872
1873 CacheView
1874 *image_view,
1875 *rescale_view;
1876
1877 gfloat
1878 *packet,
1879 *pixels;
1880
1881 Image
1882 *rescale_image;
1883
1884 int
1885 x_offset,
1886 y_offset;
1887
1888 LqrCarver
1889 *carver;
1890
1891 LqrRetVal
1892 lqr_status;
1893
1894 MagickBooleanType
1895 status;
1896
1897 MemoryInfo
1898 *pixel_info;
1899
1900 register gfloat
1901 *q;
1902
1903 ssize_t
1904 y;
1905
1906 /*
1907 Liquid rescale image.
1908 */
1909 assert(image != (const Image *) NULL);
1910 assert(image->signature == MagickCoreSignature);
1911 if (image->debug != MagickFalse)
1912 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
1913 assert(exception != (ExceptionInfo *) NULL);
1914 assert(exception->signature == MagickCoreSignature);
1915 if ((columns == 0) || (rows == 0))
1916 ThrowImageException(ImageError,"NegativeOrZeroImageSize");
1917 if ((columns == image->columns) && (rows == image->rows))
1918 return(CloneImage(image,0,0,MagickTrue,exception));
1919 if ((columns <= 2) || (rows <= 2))
1920 return(ResizeImage(image,columns,rows,image->filter,exception));
1921 pixel_info=AcquireVirtualMemory(image->columns,image->rows*MaxPixelChannels*
1922 sizeof(*pixels));
1923 if (pixel_info == (MemoryInfo *) NULL)
1924 return((Image *) NULL);
1925 pixels=(gfloat *) GetVirtualMemoryBlob(pixel_info);
1926 status=MagickTrue;
1927 q=pixels;
1928 image_view=AcquireVirtualCacheView(image,exception);
1929 for (y=0; y < (ssize_t) image->rows; y++)
1930 {
1931 register const Quantum
1932 *magick_restrict p;
1933
1934 register ssize_t
1935 x;
1936
1937 if (status == MagickFalse)
1938 continue;
1939 p=GetCacheViewVirtualPixels(image_view,0,y,image->columns,1,exception);
1940 if (p == (const Quantum *) NULL)
1941 {
1942 status=MagickFalse;
1943 continue;
1944 }
1945 for (x=0; x < (ssize_t) image->columns; x++)
1946 {
1947 register ssize_t
1948 i;
1949
1950 for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
1951 *q++=QuantumScale*p[i];
1952 p+=GetPixelChannels(image);
1953 }
1954 }
1955 image_view=DestroyCacheView(image_view);
1956 carver=lqr_carver_new_ext(pixels,(int) image->columns,(int) image->rows,
1957 (int) GetPixelChannels(image),LQR_COLDEPTH_32F);
1958 if (carver == (LqrCarver *) NULL)
1959 {
1960 pixel_info=RelinquishVirtualMemory(pixel_info);
1961 ThrowImageException(ResourceLimitError,"MemoryAllocationFailed");
1962 }
1963 lqr_carver_set_preserve_input_image(carver);
1964 lqr_status=lqr_carver_init(carver,(int) delta_x,rigidity);
1965 lqr_status=lqr_carver_resize(carver,(int) columns,(int) rows);
1966 (void) lqr_status;
1967 rescale_image=CloneImage(image,lqr_carver_get_width(carver),
1968 lqr_carver_get_height(carver),MagickTrue,exception);
1969 if (rescale_image == (Image *) NULL)
1970 {
1971 pixel_info=RelinquishVirtualMemory(pixel_info);
1972 return((Image *) NULL);
1973 }
1974 if (SetImageStorageClass(rescale_image,DirectClass,exception) == MagickFalse)
1975 {
1976 pixel_info=RelinquishVirtualMemory(pixel_info);
1977 rescale_image=DestroyImage(rescale_image);
1978 return((Image *) NULL);
1979 }
1980 rescale_view=AcquireAuthenticCacheView(rescale_image,exception);
1981 (void) lqr_carver_scan_reset(carver);
1982 while (lqr_carver_scan_ext(carver,&x_offset,&y_offset,(void **) &packet) != 0)
1983 {
1984 register Quantum
1985 *magick_restrict p;
1986
1987 register ssize_t
1988 i;
1989
1990 p=QueueCacheViewAuthenticPixels(rescale_view,x_offset,y_offset,1,1,
1991 exception);
1992 if (p == (Quantum *) NULL)
1993 break;
1994 for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
1995 {
1996 PixelChannel
1997 channel;
1998
1999 PixelTrait
2000 rescale_traits,
2001 traits;
2002
2003 channel=GetPixelChannelChannel(image,i);
2004 traits=GetPixelChannelTraits(image,channel);
2005 rescale_traits=GetPixelChannelTraits(rescale_image,channel);
2006 if ((traits == UndefinedPixelTrait) ||
2007 (rescale_traits == UndefinedPixelTrait))
2008 continue;
2009 SetPixelChannel(rescale_image,channel,ClampToQuantum(QuantumRange*
2010 packet[i]),p);
2011 }
2012 if (SyncCacheViewAuthenticPixels(rescale_view,exception) == MagickFalse)
2013 break;
2014 }
2015 rescale_view=DestroyCacheView(rescale_view);
2016 pixel_info=RelinquishVirtualMemory(pixel_info);
2017 lqr_carver_destroy(carver);
2018 return(rescale_image);
2019 }
2020 #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)2021 MagickExport Image *LiquidRescaleImage(const Image *image,
2022 const size_t magick_unused(columns),const size_t magick_unused(rows),
2023 const double magick_unused(delta_x),const double magick_unused(rigidity),
2024 ExceptionInfo *exception)
2025 {
2026 assert(image != (const Image *) NULL);
2027 assert(image->signature == MagickCoreSignature);
2028 if (image->debug != MagickFalse)
2029 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
2030 assert(exception != (ExceptionInfo *) NULL);
2031 assert(exception->signature == MagickCoreSignature);
2032 (void) ThrowMagickException(exception,GetMagickModule(),MissingDelegateError,
2033 "DelegateLibrarySupportNotBuiltIn","'%s' (LQR)",image->filename);
2034 return((Image *) NULL);
2035 }
2036 #endif
2037
2038 /*
2039 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2040 % %
2041 % %
2042 % %
2043 % M a g n i f y I m a g e %
2044 % %
2045 % %
2046 % %
2047 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2048 %
2049 % MagnifyImage() doubles the size of the image with a pixel art scaling
2050 % algorithm.
2051 %
2052 % The format of the MagnifyImage method is:
2053 %
2054 % Image *MagnifyImage(const Image *image,ExceptionInfo *exception)
2055 %
2056 % A description of each parameter follows:
2057 %
2058 % o image: the image.
2059 %
2060 % o exception: return any errors or warnings in this structure.
2061 %
2062 */
MagnifyImage(const Image * image,ExceptionInfo * exception)2063 MagickExport Image *MagnifyImage(const Image *image,ExceptionInfo *exception)
2064 {
2065 #define MagnifyImageTag "Magnify/Image"
2066
2067 CacheView
2068 *image_view,
2069 *magnify_view;
2070
2071 Image
2072 *magnify_image;
2073
2074 MagickBooleanType
2075 status;
2076
2077 MagickOffsetType
2078 progress;
2079
2080 ssize_t
2081 y;
2082
2083 /*
2084 Initialize magnified image attributes.
2085 */
2086 assert(image != (const Image *) NULL);
2087 assert(image->signature == MagickCoreSignature);
2088 if (image->debug != MagickFalse)
2089 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
2090 assert(exception != (ExceptionInfo *) NULL);
2091 assert(exception->signature == MagickCoreSignature);
2092 magnify_image=CloneImage(image,2*image->columns,2*image->rows,MagickTrue,
2093 exception);
2094 if (magnify_image == (Image *) NULL)
2095 return((Image *) NULL);
2096 /*
2097 Magnify image.
2098 */
2099 status=MagickTrue;
2100 progress=0;
2101 image_view=AcquireVirtualCacheView(image,exception);
2102 magnify_view=AcquireAuthenticCacheView(magnify_image,exception);
2103 #if defined(MAGICKCORE_OPENMP_SUPPORT)
2104 #pragma omp parallel for schedule(static) shared(progress,status) \
2105 magick_number_threads(image,magnify_image,image->rows,1)
2106 #endif
2107 for (y=0; y < (ssize_t) image->rows; y++)
2108 {
2109 register Quantum
2110 *magick_restrict q;
2111
2112 register ssize_t
2113 x;
2114
2115 if (status == MagickFalse)
2116 continue;
2117 q=QueueCacheViewAuthenticPixels(magnify_view,0,2*y,magnify_image->columns,2,
2118 exception);
2119 if (q == (Quantum *) NULL)
2120 {
2121 status=MagickFalse;
2122 continue;
2123 }
2124 /*
2125 Magnify this row of pixels.
2126 */
2127 for (x=0; x < (ssize_t) image->columns; x++)
2128 {
2129 MagickRealType
2130 intensity[9];
2131
2132 register const Quantum
2133 *magick_restrict p;
2134
2135 register Quantum
2136 *magick_restrict r;
2137
2138 register ssize_t
2139 i;
2140
2141 size_t
2142 channels;
2143
2144 p=GetCacheViewVirtualPixels(image_view,x-1,y-1,3,3,exception);
2145 if (p == (const Quantum *) NULL)
2146 {
2147 status=MagickFalse;
2148 continue;
2149 }
2150 channels=GetPixelChannels(image);
2151 for (i=0; i < 9; i++)
2152 intensity[i]=GetPixelIntensity(image,p+i*channels);
2153 r=q;
2154 if ((fabs(intensity[1]-intensity[7]) < MagickEpsilon) ||
2155 (fabs(intensity[3]-intensity[5]) < MagickEpsilon))
2156 {
2157 /*
2158 Clone center pixel.
2159 */
2160 for (i=0; i < (ssize_t) channels; i++)
2161 r[i]=p[4*channels+i];
2162 r+=GetPixelChannels(magnify_image);
2163 for (i=0; i < (ssize_t) channels; i++)
2164 r[i]=p[4*channels+i];
2165 r+=GetPixelChannels(magnify_image)*(magnify_image->columns-1);
2166 for (i=0; i < (ssize_t) channels; i++)
2167 r[i]=p[4*channels+i];
2168 r+=GetPixelChannels(magnify_image);
2169 for (i=0; i < (ssize_t) channels; i++)
2170 r[i]=p[4*channels+i];
2171 }
2172 else
2173 {
2174 /*
2175 Selectively clone pixel.
2176 */
2177 if (fabs(intensity[1]-intensity[3]) < MagickEpsilon)
2178 for (i=0; i < (ssize_t) channels; i++)
2179 r[i]=p[3*channels+i];
2180 else
2181 for (i=0; i < (ssize_t) channels; i++)
2182 r[i]=p[4*channels+i];
2183 r+=GetPixelChannels(magnify_image);
2184 if (fabs(intensity[1]-intensity[5]) < MagickEpsilon)
2185 for (i=0; i < (ssize_t) channels; i++)
2186 r[i]=p[5*channels+i];
2187 else
2188 for (i=0; i < (ssize_t) channels; i++)
2189 r[i]=p[4*channels+i];
2190 r+=GetPixelChannels(magnify_image)*(magnify_image->columns-1);
2191 if (fabs(intensity[3]-intensity[7]) < MagickEpsilon)
2192 for (i=0; i < (ssize_t) channels; i++)
2193 r[i]=p[3*channels+i];
2194 else
2195 for (i=0; i < (ssize_t) channels; i++)
2196 r[i]=p[4*channels+i];
2197 r+=GetPixelChannels(magnify_image);
2198 if (fabs(intensity[5]-intensity[7]) < MagickEpsilon)
2199 for (i=0; i < (ssize_t) channels; i++)
2200 r[i]=p[5*channels+i];
2201 else
2202 for (i=0; i < (ssize_t) channels; i++)
2203 r[i]=p[4*channels+i];
2204 }
2205 q+=2*GetPixelChannels(magnify_image);
2206 }
2207 if (SyncCacheViewAuthenticPixels(magnify_view,exception) == MagickFalse)
2208 status=MagickFalse;
2209 if (image->progress_monitor != (MagickProgressMonitor) NULL)
2210 {
2211 MagickBooleanType
2212 proceed;
2213
2214 #if defined(MAGICKCORE_OPENMP_SUPPORT)
2215 #pragma omp atomic
2216 #endif
2217 progress++;
2218 proceed=SetImageProgress(image,MagnifyImageTag,progress,image->rows);
2219 if (proceed == MagickFalse)
2220 status=MagickFalse;
2221 }
2222 }
2223 magnify_view=DestroyCacheView(magnify_view);
2224 image_view=DestroyCacheView(image_view);
2225 if (status == MagickFalse)
2226 magnify_image=DestroyImage(magnify_image);
2227 return(magnify_image);
2228 }
2229
2230 /*
2231 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2232 % %
2233 % %
2234 % %
2235 % M i n i f y I m a g e %
2236 % %
2237 % %
2238 % %
2239 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2240 %
2241 % MinifyImage() is a convenience method that scales an image proportionally to
2242 % half its size.
2243 %
2244 % The format of the MinifyImage method is:
2245 %
2246 % Image *MinifyImage(const Image *image,ExceptionInfo *exception)
2247 %
2248 % A description of each parameter follows:
2249 %
2250 % o image: the image.
2251 %
2252 % o exception: return any errors or warnings in this structure.
2253 %
2254 */
MinifyImage(const Image * image,ExceptionInfo * exception)2255 MagickExport Image *MinifyImage(const Image *image,ExceptionInfo *exception)
2256 {
2257 Image
2258 *minify_image;
2259
2260 assert(image != (Image *) NULL);
2261 assert(image->signature == MagickCoreSignature);
2262 if (image->debug != MagickFalse)
2263 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
2264 assert(exception != (ExceptionInfo *) NULL);
2265 assert(exception->signature == MagickCoreSignature);
2266 minify_image=ResizeImage(image,image->columns/2,image->rows/2,SplineFilter,
2267 exception);
2268 return(minify_image);
2269 }
2270
2271 /*
2272 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2273 % %
2274 % %
2275 % %
2276 % R e s a m p l e I m a g e %
2277 % %
2278 % %
2279 % %
2280 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2281 %
2282 % ResampleImage() resize image in terms of its pixel size, so that when
2283 % displayed at the given resolution it will be the same size in terms of
2284 % real world units as the original image at the original resolution.
2285 %
2286 % The format of the ResampleImage method is:
2287 %
2288 % Image *ResampleImage(Image *image,const double x_resolution,
2289 % const double y_resolution,const FilterType filter,
2290 % ExceptionInfo *exception)
2291 %
2292 % A description of each parameter follows:
2293 %
2294 % o image: the image to be resized to fit the given resolution.
2295 %
2296 % o x_resolution: the new image x resolution.
2297 %
2298 % o y_resolution: the new image y resolution.
2299 %
2300 % o filter: Image filter to use.
2301 %
2302 % o exception: return any errors or warnings in this structure.
2303 %
2304 */
ResampleImage(const Image * image,const double x_resolution,const double y_resolution,const FilterType filter,ExceptionInfo * exception)2305 MagickExport Image *ResampleImage(const Image *image,const double x_resolution,
2306 const double y_resolution,const FilterType filter,ExceptionInfo *exception)
2307 {
2308 #define ResampleImageTag "Resample/Image"
2309
2310 Image
2311 *resample_image;
2312
2313 size_t
2314 height,
2315 width;
2316
2317 /*
2318 Initialize sampled image attributes.
2319 */
2320 assert(image != (const Image *) NULL);
2321 assert(image->signature == MagickCoreSignature);
2322 if (image->debug != MagickFalse)
2323 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
2324 assert(exception != (ExceptionInfo *) NULL);
2325 assert(exception->signature == MagickCoreSignature);
2326 width=(size_t) (x_resolution*image->columns/(image->resolution.x == 0.0 ?
2327 72.0 : image->resolution.x)+0.5);
2328 height=(size_t) (y_resolution*image->rows/(image->resolution.y == 0.0 ?
2329 72.0 : image->resolution.y)+0.5);
2330 resample_image=ResizeImage(image,width,height,filter,exception);
2331 if (resample_image != (Image *) NULL)
2332 {
2333 resample_image->resolution.x=x_resolution;
2334 resample_image->resolution.y=y_resolution;
2335 }
2336 return(resample_image);
2337 }
2338
2339 /*
2340 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2341 % %
2342 % %
2343 % %
2344 % R e s i z e I m a g e %
2345 % %
2346 % %
2347 % %
2348 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2349 %
2350 % ResizeImage() scales an image to the desired dimensions, using the given
2351 % filter (see AcquireFilterInfo()).
2352 %
2353 % If an undefined filter is given the filter defaults to Mitchell for a
2354 % colormapped image, a image with a matte channel, or if the image is
2355 % enlarged. Otherwise the filter defaults to a Lanczos.
2356 %
2357 % ResizeImage() was inspired by Paul Heckbert's "zoom" program.
2358 %
2359 % The format of the ResizeImage method is:
2360 %
2361 % Image *ResizeImage(Image *image,const size_t columns,const size_t rows,
2362 % const FilterType filter,ExceptionInfo *exception)
2363 %
2364 % A description of each parameter follows:
2365 %
2366 % o image: the image.
2367 %
2368 % o columns: the number of columns in the scaled image.
2369 %
2370 % o rows: the number of rows in the scaled image.
2371 %
2372 % o filter: Image filter to use.
2373 %
2374 % o exception: return any errors or warnings in this structure.
2375 %
2376 */
2377
2378 typedef struct _ContributionInfo
2379 {
2380 double
2381 weight;
2382
2383 ssize_t
2384 pixel;
2385 } ContributionInfo;
2386
DestroyContributionThreadSet(ContributionInfo ** contribution)2387 static ContributionInfo **DestroyContributionThreadSet(
2388 ContributionInfo **contribution)
2389 {
2390 register ssize_t
2391 i;
2392
2393 assert(contribution != (ContributionInfo **) NULL);
2394 for (i=0; i < (ssize_t) GetMagickResourceLimit(ThreadResource); i++)
2395 if (contribution[i] != (ContributionInfo *) NULL)
2396 contribution[i]=(ContributionInfo *) RelinquishAlignedMemory(
2397 contribution[i]);
2398 contribution=(ContributionInfo **) RelinquishMagickMemory(contribution);
2399 return(contribution);
2400 }
2401
AcquireContributionThreadSet(const size_t count)2402 static ContributionInfo **AcquireContributionThreadSet(const size_t count)
2403 {
2404 register ssize_t
2405 i;
2406
2407 ContributionInfo
2408 **contribution;
2409
2410 size_t
2411 number_threads;
2412
2413 number_threads=(size_t) GetMagickResourceLimit(ThreadResource);
2414 contribution=(ContributionInfo **) AcquireQuantumMemory(number_threads,
2415 sizeof(*contribution));
2416 if (contribution == (ContributionInfo **) NULL)
2417 return((ContributionInfo **) NULL);
2418 (void) memset(contribution,0,number_threads*sizeof(*contribution));
2419 for (i=0; i < (ssize_t) number_threads; i++)
2420 {
2421 contribution[i]=(ContributionInfo *) MagickAssumeAligned(
2422 AcquireAlignedMemory(count,sizeof(**contribution)));
2423 if (contribution[i] == (ContributionInfo *) NULL)
2424 return(DestroyContributionThreadSet(contribution));
2425 }
2426 return(contribution);
2427 }
2428
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)2429 static MagickBooleanType HorizontalFilter(
2430 const ResizeFilter *magick_restrict resize_filter,
2431 const Image *magick_restrict image,Image *magick_restrict resize_image,
2432 const double x_factor,const MagickSizeType span,
2433 MagickOffsetType *magick_restrict progress,ExceptionInfo *exception)
2434 {
2435 #define ResizeImageTag "Resize/Image"
2436
2437 CacheView
2438 *image_view,
2439 *resize_view;
2440
2441 ClassType
2442 storage_class;
2443
2444 ContributionInfo
2445 **magick_restrict contributions;
2446
2447 MagickBooleanType
2448 status;
2449
2450 double
2451 scale,
2452 support;
2453
2454 ssize_t
2455 x;
2456
2457 /*
2458 Apply filter to resize horizontally from image to resize image.
2459 */
2460 scale=MagickMax(1.0/x_factor+MagickEpsilon,1.0);
2461 support=scale*GetResizeFilterSupport(resize_filter);
2462 storage_class=support > 0.5 ? DirectClass : image->storage_class;
2463 if (SetImageStorageClass(resize_image,storage_class,exception) == MagickFalse)
2464 return(MagickFalse);
2465 if (support < 0.5)
2466 {
2467 /*
2468 Support too small even for nearest neighbour: Reduce to point sampling.
2469 */
2470 support=(double) 0.5;
2471 scale=1.0;
2472 }
2473 contributions=AcquireContributionThreadSet((size_t) (2.0*support+3.0));
2474 if (contributions == (ContributionInfo **) NULL)
2475 {
2476 (void) ThrowMagickException(exception,GetMagickModule(),
2477 ResourceLimitError,"MemoryAllocationFailed","`%s'",image->filename);
2478 return(MagickFalse);
2479 }
2480 status=MagickTrue;
2481 scale=PerceptibleReciprocal(scale);
2482 image_view=AcquireVirtualCacheView(image,exception);
2483 resize_view=AcquireAuthenticCacheView(resize_image,exception);
2484 #if defined(MAGICKCORE_OPENMP_SUPPORT)
2485 #pragma omp parallel for schedule(static) shared(progress,status) \
2486 magick_number_threads(image,resize_image,resize_image->columns,1)
2487 #endif
2488 for (x=0; x < (ssize_t) resize_image->columns; x++)
2489 {
2490 const int
2491 id = GetOpenMPThreadId();
2492
2493 double
2494 bisect,
2495 density;
2496
2497 register const Quantum
2498 *magick_restrict p;
2499
2500 register ContributionInfo
2501 *magick_restrict contribution;
2502
2503 register Quantum
2504 *magick_restrict q;
2505
2506 register ssize_t
2507 y;
2508
2509 ssize_t
2510 n,
2511 start,
2512 stop;
2513
2514 if (status == MagickFalse)
2515 continue;
2516 bisect=(double) (x+0.5)/x_factor+MagickEpsilon;
2517 start=(ssize_t) MagickMax(bisect-support+0.5,0.0);
2518 stop=(ssize_t) MagickMin(bisect+support+0.5,(double) image->columns);
2519 density=0.0;
2520 contribution=contributions[id];
2521 for (n=0; n < (stop-start); n++)
2522 {
2523 contribution[n].pixel=start+n;
2524 contribution[n].weight=GetResizeFilterWeight(resize_filter,scale*
2525 ((double) (start+n)-bisect+0.5));
2526 density+=contribution[n].weight;
2527 }
2528 if (n == 0)
2529 continue;
2530 if ((density != 0.0) && (density != 1.0))
2531 {
2532 register ssize_t
2533 i;
2534
2535 /*
2536 Normalize.
2537 */
2538 density=PerceptibleReciprocal(density);
2539 for (i=0; i < n; i++)
2540 contribution[i].weight*=density;
2541 }
2542 p=GetCacheViewVirtualPixels(image_view,contribution[0].pixel,0,(size_t)
2543 (contribution[n-1].pixel-contribution[0].pixel+1),image->rows,exception);
2544 q=QueueCacheViewAuthenticPixels(resize_view,x,0,1,resize_image->rows,
2545 exception);
2546 if ((p == (const Quantum *) NULL) || (q == (Quantum *) NULL))
2547 {
2548 status=MagickFalse;
2549 continue;
2550 }
2551 for (y=0; y < (ssize_t) resize_image->rows; y++)
2552 {
2553 register ssize_t
2554 i;
2555
2556 for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
2557 {
2558 double
2559 alpha,
2560 gamma,
2561 pixel;
2562
2563 PixelChannel
2564 channel;
2565
2566 PixelTrait
2567 resize_traits,
2568 traits;
2569
2570 register ssize_t
2571 j;
2572
2573 ssize_t
2574 k;
2575
2576 channel=GetPixelChannelChannel(image,i);
2577 traits=GetPixelChannelTraits(image,channel);
2578 resize_traits=GetPixelChannelTraits(resize_image,channel);
2579 if ((traits == UndefinedPixelTrait) ||
2580 (resize_traits == UndefinedPixelTrait))
2581 continue;
2582 if (((resize_traits & CopyPixelTrait) != 0) ||
2583 (GetPixelWriteMask(resize_image,q) <= (QuantumRange/2)))
2584 {
2585 j=(ssize_t) (MagickMin(MagickMax(bisect,(double) start),(double)
2586 stop-1.0)+0.5);
2587 k=y*(contribution[n-1].pixel-contribution[0].pixel+1)+
2588 (contribution[j-start].pixel-contribution[0].pixel);
2589 SetPixelChannel(resize_image,channel,p[k*GetPixelChannels(image)+i],
2590 q);
2591 continue;
2592 }
2593 pixel=0.0;
2594 if ((resize_traits & BlendPixelTrait) == 0)
2595 {
2596 /*
2597 No alpha blending.
2598 */
2599 for (j=0; j < n; j++)
2600 {
2601 k=y*(contribution[n-1].pixel-contribution[0].pixel+1)+
2602 (contribution[j].pixel-contribution[0].pixel);
2603 alpha=contribution[j].weight;
2604 pixel+=alpha*p[k*GetPixelChannels(image)+i];
2605 }
2606 SetPixelChannel(resize_image,channel,ClampToQuantum(pixel),q);
2607 continue;
2608 }
2609 /*
2610 Alpha blending.
2611 */
2612 gamma=0.0;
2613 for (j=0; j < n; j++)
2614 {
2615 k=y*(contribution[n-1].pixel-contribution[0].pixel+1)+
2616 (contribution[j].pixel-contribution[0].pixel);
2617 alpha=contribution[j].weight*QuantumScale*
2618 GetPixelAlpha(image,p+k*GetPixelChannels(image));
2619 pixel+=alpha*p[k*GetPixelChannels(image)+i];
2620 gamma+=alpha;
2621 }
2622 gamma=PerceptibleReciprocal(gamma);
2623 SetPixelChannel(resize_image,channel,ClampToQuantum(gamma*pixel),q);
2624 }
2625 q+=GetPixelChannels(resize_image);
2626 }
2627 if (SyncCacheViewAuthenticPixels(resize_view,exception) == MagickFalse)
2628 status=MagickFalse;
2629 if (image->progress_monitor != (MagickProgressMonitor) NULL)
2630 {
2631 MagickBooleanType
2632 proceed;
2633
2634 #if defined(MAGICKCORE_OPENMP_SUPPORT)
2635 #pragma omp atomic
2636 #endif
2637 (*progress)++;
2638 proceed=SetImageProgress(image,ResizeImageTag,*progress,span);
2639 if (proceed == MagickFalse)
2640 status=MagickFalse;
2641 }
2642 }
2643 resize_view=DestroyCacheView(resize_view);
2644 image_view=DestroyCacheView(image_view);
2645 contributions=DestroyContributionThreadSet(contributions);
2646 return(status);
2647 }
2648
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)2649 static MagickBooleanType VerticalFilter(
2650 const ResizeFilter *magick_restrict resize_filter,
2651 const Image *magick_restrict image,Image *magick_restrict resize_image,
2652 const double y_factor,const MagickSizeType span,
2653 MagickOffsetType *magick_restrict progress,ExceptionInfo *exception)
2654 {
2655 CacheView
2656 *image_view,
2657 *resize_view;
2658
2659 ClassType
2660 storage_class;
2661
2662 ContributionInfo
2663 **magick_restrict contributions;
2664
2665 double
2666 scale,
2667 support;
2668
2669 MagickBooleanType
2670 status;
2671
2672 ssize_t
2673 y;
2674
2675 /*
2676 Apply filter to resize vertically from image to resize image.
2677 */
2678 scale=MagickMax(1.0/y_factor+MagickEpsilon,1.0);
2679 support=scale*GetResizeFilterSupport(resize_filter);
2680 storage_class=support > 0.5 ? DirectClass : image->storage_class;
2681 if (SetImageStorageClass(resize_image,storage_class,exception) == MagickFalse)
2682 return(MagickFalse);
2683 if (support < 0.5)
2684 {
2685 /*
2686 Support too small even for nearest neighbour: Reduce to point sampling.
2687 */
2688 support=(double) 0.5;
2689 scale=1.0;
2690 }
2691 contributions=AcquireContributionThreadSet((size_t) (2.0*support+3.0));
2692 if (contributions == (ContributionInfo **) NULL)
2693 {
2694 (void) ThrowMagickException(exception,GetMagickModule(),
2695 ResourceLimitError,"MemoryAllocationFailed","`%s'",image->filename);
2696 return(MagickFalse);
2697 }
2698 status=MagickTrue;
2699 scale=PerceptibleReciprocal(scale);
2700 image_view=AcquireVirtualCacheView(image,exception);
2701 resize_view=AcquireAuthenticCacheView(resize_image,exception);
2702 #if defined(MAGICKCORE_OPENMP_SUPPORT)
2703 #pragma omp parallel for schedule(static) shared(progress,status) \
2704 magick_number_threads(image,resize_image,resize_image->rows,1)
2705 #endif
2706 for (y=0; y < (ssize_t) resize_image->rows; y++)
2707 {
2708 const int
2709 id = GetOpenMPThreadId();
2710
2711 double
2712 bisect,
2713 density;
2714
2715 register const Quantum
2716 *magick_restrict p;
2717
2718 register ContributionInfo
2719 *magick_restrict contribution;
2720
2721 register Quantum
2722 *magick_restrict q;
2723
2724 register ssize_t
2725 x;
2726
2727 ssize_t
2728 n,
2729 start,
2730 stop;
2731
2732 if (status == MagickFalse)
2733 continue;
2734 bisect=(double) (y+0.5)/y_factor+MagickEpsilon;
2735 start=(ssize_t) MagickMax(bisect-support+0.5,0.0);
2736 stop=(ssize_t) MagickMin(bisect+support+0.5,(double) image->rows);
2737 density=0.0;
2738 contribution=contributions[id];
2739 for (n=0; n < (stop-start); n++)
2740 {
2741 contribution[n].pixel=start+n;
2742 contribution[n].weight=GetResizeFilterWeight(resize_filter,scale*
2743 ((double) (start+n)-bisect+0.5));
2744 density+=contribution[n].weight;
2745 }
2746 if (n == 0)
2747 continue;
2748 if ((density != 0.0) && (density != 1.0))
2749 {
2750 register ssize_t
2751 i;
2752
2753 /*
2754 Normalize.
2755 */
2756 density=PerceptibleReciprocal(density);
2757 for (i=0; i < n; i++)
2758 contribution[i].weight*=density;
2759 }
2760 p=GetCacheViewVirtualPixels(image_view,0,contribution[0].pixel,
2761 image->columns,(size_t) (contribution[n-1].pixel-contribution[0].pixel+1),
2762 exception);
2763 q=QueueCacheViewAuthenticPixels(resize_view,0,y,resize_image->columns,1,
2764 exception);
2765 if ((p == (const Quantum *) NULL) || (q == (Quantum *) NULL))
2766 {
2767 status=MagickFalse;
2768 continue;
2769 }
2770 for (x=0; x < (ssize_t) resize_image->columns; x++)
2771 {
2772 register ssize_t
2773 i;
2774
2775 for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
2776 {
2777 double
2778 alpha,
2779 gamma,
2780 pixel;
2781
2782 PixelChannel
2783 channel;
2784
2785 PixelTrait
2786 resize_traits,
2787 traits;
2788
2789 register ssize_t
2790 j;
2791
2792 ssize_t
2793 k;
2794
2795 channel=GetPixelChannelChannel(image,i);
2796 traits=GetPixelChannelTraits(image,channel);
2797 resize_traits=GetPixelChannelTraits(resize_image,channel);
2798 if ((traits == UndefinedPixelTrait) ||
2799 (resize_traits == UndefinedPixelTrait))
2800 continue;
2801 if (((resize_traits & CopyPixelTrait) != 0) ||
2802 (GetPixelWriteMask(resize_image,q) <= (QuantumRange/2)))
2803 {
2804 j=(ssize_t) (MagickMin(MagickMax(bisect,(double) start),(double)
2805 stop-1.0)+0.5);
2806 k=(ssize_t) ((contribution[j-start].pixel-contribution[0].pixel)*
2807 image->columns+x);
2808 SetPixelChannel(resize_image,channel,p[k*GetPixelChannels(image)+i],
2809 q);
2810 continue;
2811 }
2812 pixel=0.0;
2813 if ((resize_traits & BlendPixelTrait) == 0)
2814 {
2815 /*
2816 No alpha blending.
2817 */
2818 for (j=0; j < n; j++)
2819 {
2820 k=(ssize_t) ((contribution[j].pixel-contribution[0].pixel)*
2821 image->columns+x);
2822 alpha=contribution[j].weight;
2823 pixel+=alpha*p[k*GetPixelChannels(image)+i];
2824 }
2825 SetPixelChannel(resize_image,channel,ClampToQuantum(pixel),q);
2826 continue;
2827 }
2828 gamma=0.0;
2829 for (j=0; j < n; j++)
2830 {
2831 k=(ssize_t) ((contribution[j].pixel-contribution[0].pixel)*
2832 image->columns+x);
2833 alpha=contribution[j].weight*QuantumScale*GetPixelAlpha(image,p+k*
2834 GetPixelChannels(image));
2835 pixel+=alpha*p[k*GetPixelChannels(image)+i];
2836 gamma+=alpha;
2837 }
2838 gamma=PerceptibleReciprocal(gamma);
2839 SetPixelChannel(resize_image,channel,ClampToQuantum(gamma*pixel),q);
2840 }
2841 q+=GetPixelChannels(resize_image);
2842 }
2843 if (SyncCacheViewAuthenticPixels(resize_view,exception) == MagickFalse)
2844 status=MagickFalse;
2845 if (image->progress_monitor != (MagickProgressMonitor) NULL)
2846 {
2847 MagickBooleanType
2848 proceed;
2849
2850 #if defined(MAGICKCORE_OPENMP_SUPPORT)
2851 #pragma omp atomic
2852 #endif
2853 (*progress)++;
2854 proceed=SetImageProgress(image,ResizeImageTag,*progress,span);
2855 if (proceed == MagickFalse)
2856 status=MagickFalse;
2857 }
2858 }
2859 resize_view=DestroyCacheView(resize_view);
2860 image_view=DestroyCacheView(image_view);
2861 contributions=DestroyContributionThreadSet(contributions);
2862 return(status);
2863 }
2864
ResizeImage(const Image * image,const size_t columns,const size_t rows,const FilterType filter,ExceptionInfo * exception)2865 MagickExport Image *ResizeImage(const Image *image,const size_t columns,
2866 const size_t rows,const FilterType filter,ExceptionInfo *exception)
2867 {
2868 double
2869 x_factor,
2870 y_factor;
2871
2872 FilterType
2873 filter_type;
2874
2875 Image
2876 *filter_image,
2877 *resize_image;
2878
2879 MagickOffsetType
2880 offset;
2881
2882 MagickSizeType
2883 span;
2884
2885 MagickStatusType
2886 status;
2887
2888 ResizeFilter
2889 *resize_filter;
2890
2891 /*
2892 Acquire resize image.
2893 */
2894 assert(image != (Image *) NULL);
2895 assert(image->signature == MagickCoreSignature);
2896 if (image->debug != MagickFalse)
2897 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
2898 assert(exception != (ExceptionInfo *) NULL);
2899 assert(exception->signature == MagickCoreSignature);
2900 if ((columns == 0) || (rows == 0))
2901 ThrowImageException(ImageError,"NegativeOrZeroImageSize");
2902 if ((columns == image->columns) && (rows == image->rows) &&
2903 (filter == UndefinedFilter))
2904 return(CloneImage(image,0,0,MagickTrue,exception));
2905 /*
2906 Acquire resize filter.
2907 */
2908 x_factor=(double) columns/(double) image->columns;
2909 y_factor=(double) rows/(double) image->rows;
2910 filter_type=LanczosFilter;
2911 if (filter != UndefinedFilter)
2912 filter_type=filter;
2913 else
2914 if ((x_factor == 1.0) && (y_factor == 1.0))
2915 filter_type=PointFilter;
2916 else
2917 if ((image->storage_class == PseudoClass) ||
2918 (image->alpha_trait != UndefinedPixelTrait) ||
2919 ((x_factor*y_factor) > 1.0))
2920 filter_type=MitchellFilter;
2921 resize_filter=AcquireResizeFilter(image,filter_type,MagickFalse,exception);
2922 #if defined(MAGICKCORE_OPENCL_SUPPORT)
2923 resize_image=AccelerateResizeImage(image,columns,rows,resize_filter,
2924 exception);
2925 if (resize_image != (Image *) NULL)
2926 {
2927 resize_filter=DestroyResizeFilter(resize_filter);
2928 return(resize_image);
2929 }
2930 #endif
2931 resize_image=CloneImage(image,columns,rows,MagickTrue,exception);
2932 if (resize_image == (Image *) NULL)
2933 {
2934 resize_filter=DestroyResizeFilter(resize_filter);
2935 return(resize_image);
2936 }
2937 if (x_factor > y_factor)
2938 filter_image=CloneImage(image,columns,image->rows,MagickTrue,exception);
2939 else
2940 filter_image=CloneImage(image,image->columns,rows,MagickTrue,exception);
2941 if (filter_image == (Image *) NULL)
2942 {
2943 resize_filter=DestroyResizeFilter(resize_filter);
2944 return(DestroyImage(resize_image));
2945 }
2946 /*
2947 Resize image.
2948 */
2949 offset=0;
2950 if (x_factor > y_factor)
2951 {
2952 span=(MagickSizeType) (filter_image->columns+rows);
2953 status=HorizontalFilter(resize_filter,image,filter_image,x_factor,span,
2954 &offset,exception);
2955 status&=VerticalFilter(resize_filter,filter_image,resize_image,y_factor,
2956 span,&offset,exception);
2957 }
2958 else
2959 {
2960 span=(MagickSizeType) (filter_image->rows+columns);
2961 status=VerticalFilter(resize_filter,image,filter_image,y_factor,span,
2962 &offset,exception);
2963 status&=HorizontalFilter(resize_filter,filter_image,resize_image,x_factor,
2964 span,&offset,exception);
2965 }
2966 /*
2967 Free resources.
2968 */
2969 filter_image=DestroyImage(filter_image);
2970 resize_filter=DestroyResizeFilter(resize_filter);
2971 if (status == MagickFalse)
2972 {
2973 resize_image=DestroyImage(resize_image);
2974 return((Image *) NULL);
2975 }
2976 resize_image->type=image->type;
2977 return(resize_image);
2978 }
2979
2980 /*
2981 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2982 % %
2983 % %
2984 % %
2985 % S a m p l e I m a g e %
2986 % %
2987 % %
2988 % %
2989 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2990 %
2991 % SampleImage() scales an image to the desired dimensions with pixel
2992 % sampling. Unlike other scaling methods, this method does not introduce
2993 % any additional color into the scaled image.
2994 %
2995 % The format of the SampleImage method is:
2996 %
2997 % Image *SampleImage(const Image *image,const size_t columns,
2998 % const size_t rows,ExceptionInfo *exception)
2999 %
3000 % A description of each parameter follows:
3001 %
3002 % o image: the image.
3003 %
3004 % o columns: the number of columns in the sampled image.
3005 %
3006 % o rows: the number of rows in the sampled image.
3007 %
3008 % o exception: return any errors or warnings in this structure.
3009 %
3010 */
SampleImage(const Image * image,const size_t columns,const size_t rows,ExceptionInfo * exception)3011 MagickExport Image *SampleImage(const Image *image,const size_t columns,
3012 const size_t rows,ExceptionInfo *exception)
3013 {
3014 #define SampleImageTag "Sample/Image"
3015
3016 CacheView
3017 *image_view,
3018 *sample_view;
3019
3020 Image
3021 *sample_image;
3022
3023 MagickBooleanType
3024 status;
3025
3026 MagickOffsetType
3027 progress;
3028
3029 register ssize_t
3030 x1;
3031
3032 ssize_t
3033 *x_offset,
3034 y;
3035
3036 PointInfo
3037 sample_offset;
3038
3039 /*
3040 Initialize sampled image attributes.
3041 */
3042 assert(image != (const Image *) NULL);
3043 assert(image->signature == MagickCoreSignature);
3044 if (image->debug != MagickFalse)
3045 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
3046 assert(exception != (ExceptionInfo *) NULL);
3047 assert(exception->signature == MagickCoreSignature);
3048 if ((columns == 0) || (rows == 0))
3049 ThrowImageException(ImageError,"NegativeOrZeroImageSize");
3050 if ((columns == image->columns) && (rows == image->rows))
3051 return(CloneImage(image,0,0,MagickTrue,exception));
3052 sample_image=CloneImage(image,columns,rows,MagickTrue,exception);
3053 if (sample_image == (Image *) NULL)
3054 return((Image *) NULL);
3055 /*
3056 Set the sampling offset, default is in the mid-point of sample regions.
3057 */
3058 sample_offset.x=sample_offset.y=0.5-MagickEpsilon;
3059 {
3060 const char
3061 *value;
3062
3063 value=GetImageArtifact(image,"sample:offset");
3064 if (value != (char *) NULL)
3065 {
3066 GeometryInfo
3067 geometry_info;
3068
3069 MagickStatusType
3070 flags;
3071
3072 (void) ParseGeometry(value,&geometry_info);
3073 flags=ParseGeometry(value,&geometry_info);
3074 sample_offset.x=sample_offset.y=geometry_info.rho/100.0-MagickEpsilon;
3075 if ((flags & SigmaValue) != 0)
3076 sample_offset.y=geometry_info.sigma/100.0-MagickEpsilon;
3077 }
3078 }
3079 /*
3080 Allocate scan line buffer and column offset buffers.
3081 */
3082 x_offset=(ssize_t *) AcquireQuantumMemory((size_t) sample_image->columns,
3083 sizeof(*x_offset));
3084 if (x_offset == (ssize_t *) NULL)
3085 {
3086 sample_image=DestroyImage(sample_image);
3087 ThrowImageException(ResourceLimitError,"MemoryAllocationFailed");
3088 }
3089 for (x1=0; x1 < (ssize_t) sample_image->columns; x1++)
3090 x_offset[x1]=(ssize_t) ((((double) x1+sample_offset.x)*image->columns)/
3091 sample_image->columns);
3092 /*
3093 Sample each row.
3094 */
3095 status=MagickTrue;
3096 progress=0;
3097 image_view=AcquireVirtualCacheView(image,exception);
3098 sample_view=AcquireAuthenticCacheView(sample_image,exception);
3099 #if defined(MAGICKCORE_OPENMP_SUPPORT)
3100 #pragma omp parallel for schedule(static) shared(status) \
3101 magick_number_threads(image,sample_image,sample_image->rows,1)
3102 #endif
3103 for (y=0; y < (ssize_t) sample_image->rows; y++)
3104 {
3105 register const Quantum
3106 *magick_restrict p;
3107
3108 register Quantum
3109 *magick_restrict q;
3110
3111 register ssize_t
3112 x;
3113
3114 ssize_t
3115 y_offset;
3116
3117 if (status == MagickFalse)
3118 continue;
3119 y_offset=(ssize_t) ((((double) y+sample_offset.y)*image->rows)/
3120 sample_image->rows);
3121 p=GetCacheViewVirtualPixels(image_view,0,y_offset,image->columns,1,
3122 exception);
3123 q=QueueCacheViewAuthenticPixels(sample_view,0,y,sample_image->columns,1,
3124 exception);
3125 if ((p == (const Quantum *) NULL) || (q == (Quantum *) NULL))
3126 {
3127 status=MagickFalse;
3128 continue;
3129 }
3130 /*
3131 Sample each column.
3132 */
3133 for (x=0; x < (ssize_t) sample_image->columns; x++)
3134 {
3135 register ssize_t
3136 i;
3137
3138 if (GetPixelWriteMask(sample_image,q) <= (QuantumRange/2))
3139 {
3140 q+=GetPixelChannels(sample_image);
3141 continue;
3142 }
3143 for (i=0; i < (ssize_t) GetPixelChannels(sample_image); i++)
3144 {
3145 PixelChannel
3146 channel;
3147
3148 PixelTrait
3149 image_traits,
3150 traits;
3151
3152 channel=GetPixelChannelChannel(sample_image,i);
3153 traits=GetPixelChannelTraits(sample_image,channel);
3154 image_traits=GetPixelChannelTraits(image,channel);
3155 if ((traits == UndefinedPixelTrait) ||
3156 (image_traits == UndefinedPixelTrait))
3157 continue;
3158 SetPixelChannel(sample_image,channel,p[x_offset[x]*GetPixelChannels(
3159 image)+i],q);
3160 }
3161 q+=GetPixelChannels(sample_image);
3162 }
3163 if (SyncCacheViewAuthenticPixels(sample_view,exception) == MagickFalse)
3164 status=MagickFalse;
3165 if (image->progress_monitor != (MagickProgressMonitor) NULL)
3166 {
3167 MagickBooleanType
3168 proceed;
3169
3170 proceed=SetImageProgress(image,SampleImageTag,progress++,image->rows);
3171 if (proceed == MagickFalse)
3172 status=MagickFalse;
3173 }
3174 }
3175 image_view=DestroyCacheView(image_view);
3176 sample_view=DestroyCacheView(sample_view);
3177 x_offset=(ssize_t *) RelinquishMagickMemory(x_offset);
3178 sample_image->type=image->type;
3179 if (status == MagickFalse)
3180 sample_image=DestroyImage(sample_image);
3181 return(sample_image);
3182 }
3183
3184 /*
3185 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3186 % %
3187 % %
3188 % %
3189 % S c a l e I m a g e %
3190 % %
3191 % %
3192 % %
3193 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3194 %
3195 % ScaleImage() changes the size of an image to the given dimensions.
3196 %
3197 % The format of the ScaleImage method is:
3198 %
3199 % Image *ScaleImage(const Image *image,const size_t columns,
3200 % const size_t rows,ExceptionInfo *exception)
3201 %
3202 % A description of each parameter follows:
3203 %
3204 % o image: the image.
3205 %
3206 % o columns: the number of columns in the scaled image.
3207 %
3208 % o rows: the number of rows in the scaled image.
3209 %
3210 % o exception: return any errors or warnings in this structure.
3211 %
3212 */
ScaleImage(const Image * image,const size_t columns,const size_t rows,ExceptionInfo * exception)3213 MagickExport Image *ScaleImage(const Image *image,const size_t columns,
3214 const size_t rows,ExceptionInfo *exception)
3215 {
3216 #define ScaleImageTag "Scale/Image"
3217
3218 CacheView
3219 *image_view,
3220 *scale_view;
3221
3222 double
3223 alpha,
3224 pixel[CompositePixelChannel],
3225 *scale_scanline,
3226 *scanline,
3227 *x_vector,
3228 *y_vector;
3229
3230 Image
3231 *scale_image;
3232
3233 MagickBooleanType
3234 next_column,
3235 next_row,
3236 proceed,
3237 status;
3238
3239 PixelTrait
3240 scale_traits;
3241
3242 PointInfo
3243 scale,
3244 span;
3245
3246 register ssize_t
3247 i;
3248
3249 ssize_t
3250 n,
3251 number_rows,
3252 y;
3253
3254 /*
3255 Initialize scaled image attributes.
3256 */
3257 assert(image != (const Image *) NULL);
3258 assert(image->signature == MagickCoreSignature);
3259 if (image->debug != MagickFalse)
3260 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
3261 assert(exception != (ExceptionInfo *) NULL);
3262 assert(exception->signature == MagickCoreSignature);
3263 if ((columns == 0) || (rows == 0))
3264 ThrowImageException(ImageError,"NegativeOrZeroImageSize");
3265 if ((columns == image->columns) && (rows == image->rows))
3266 return(CloneImage(image,0,0,MagickTrue,exception));
3267 scale_image=CloneImage(image,columns,rows,MagickTrue,exception);
3268 if (scale_image == (Image *) NULL)
3269 return((Image *) NULL);
3270 if (SetImageStorageClass(scale_image,DirectClass,exception) == MagickFalse)
3271 {
3272 scale_image=DestroyImage(scale_image);
3273 return((Image *) NULL);
3274 }
3275 /*
3276 Allocate memory.
3277 */
3278 x_vector=(double *) AcquireQuantumMemory((size_t) image->columns,
3279 MaxPixelChannels*sizeof(*x_vector));
3280 scanline=x_vector;
3281 if (image->rows != scale_image->rows)
3282 scanline=(double *) AcquireQuantumMemory((size_t) image->columns,
3283 MaxPixelChannels*sizeof(*scanline));
3284 scale_scanline=(double *) AcquireQuantumMemory((size_t) scale_image->columns,
3285 MaxPixelChannels*sizeof(*scale_scanline));
3286 y_vector=(double *) AcquireQuantumMemory((size_t) image->columns,
3287 MaxPixelChannels*sizeof(*y_vector));
3288 if ((scanline == (double *) NULL) || (scale_scanline == (double *) NULL) ||
3289 (x_vector == (double *) NULL) || (y_vector == (double *) NULL))
3290 {
3291 if ((image->rows != scale_image->rows) && (scanline != (double *) NULL))
3292 scanline=(double *) RelinquishMagickMemory(scanline);
3293 if (scale_scanline != (double *) NULL)
3294 scale_scanline=(double *) RelinquishMagickMemory(scale_scanline);
3295 if (x_vector != (double *) NULL)
3296 x_vector=(double *) RelinquishMagickMemory(x_vector);
3297 if (y_vector != (double *) NULL)
3298 y_vector=(double *) RelinquishMagickMemory(y_vector);
3299 scale_image=DestroyImage(scale_image);
3300 ThrowImageException(ResourceLimitError,"MemoryAllocationFailed");
3301 }
3302 /*
3303 Scale image.
3304 */
3305 number_rows=0;
3306 next_row=MagickTrue;
3307 span.y=1.0;
3308 scale.y=(double) scale_image->rows/(double) image->rows;
3309 (void) memset(y_vector,0,(size_t) MaxPixelChannels*image->columns*
3310 sizeof(*y_vector));
3311 n=0;
3312 status=MagickTrue;
3313 image_view=AcquireVirtualCacheView(image,exception);
3314 scale_view=AcquireAuthenticCacheView(scale_image,exception);
3315 for (y=0; y < (ssize_t) scale_image->rows; y++)
3316 {
3317 register const Quantum
3318 *magick_restrict p;
3319
3320 register Quantum
3321 *magick_restrict q;
3322
3323 register ssize_t
3324 x;
3325
3326 if (status == MagickFalse)
3327 break;
3328 q=QueueCacheViewAuthenticPixels(scale_view,0,y,scale_image->columns,1,
3329 exception);
3330 if (q == (Quantum *) NULL)
3331 {
3332 status=MagickFalse;
3333 break;
3334 }
3335 alpha=1.0;
3336 if (scale_image->rows == image->rows)
3337 {
3338 /*
3339 Read a new scanline.
3340 */
3341 p=GetCacheViewVirtualPixels(image_view,0,n++,image->columns,1,
3342 exception);
3343 if (p == (const Quantum *) NULL)
3344 {
3345 status=MagickFalse;
3346 break;
3347 }
3348 for (x=0; x < (ssize_t) image->columns; x++)
3349 {
3350 if (GetPixelWriteMask(image,p) <= (QuantumRange/2))
3351 {
3352 p+=GetPixelChannels(image);
3353 continue;
3354 }
3355 if (image->alpha_trait != UndefinedPixelTrait)
3356 alpha=QuantumScale*GetPixelAlpha(image,p);
3357 for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
3358 {
3359 PixelChannel channel = GetPixelChannelChannel(image,i);
3360 PixelTrait traits = GetPixelChannelTraits(image,channel);
3361 if ((traits & BlendPixelTrait) == 0)
3362 {
3363 x_vector[x*GetPixelChannels(image)+i]=(double) p[i];
3364 continue;
3365 }
3366 x_vector[x*GetPixelChannels(image)+i]=alpha*p[i];
3367 }
3368 p+=GetPixelChannels(image);
3369 }
3370 }
3371 else
3372 {
3373 /*
3374 Scale Y direction.
3375 */
3376 while (scale.y < span.y)
3377 {
3378 if ((next_row != MagickFalse) &&
3379 (number_rows < (ssize_t) image->rows))
3380 {
3381 /*
3382 Read a new scanline.
3383 */
3384 p=GetCacheViewVirtualPixels(image_view,0,n++,image->columns,1,
3385 exception);
3386 if (p == (const Quantum *) NULL)
3387 {
3388 status=MagickFalse;
3389 break;
3390 }
3391 for (x=0; x < (ssize_t) image->columns; x++)
3392 {
3393 if (GetPixelWriteMask(image,p) <= (QuantumRange/2))
3394 {
3395 p+=GetPixelChannels(image);
3396 continue;
3397 }
3398 if (image->alpha_trait != UndefinedPixelTrait)
3399 alpha=QuantumScale*GetPixelAlpha(image,p);
3400 for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
3401 {
3402 PixelChannel channel = GetPixelChannelChannel(image,i);
3403 PixelTrait traits = GetPixelChannelTraits(image,channel);
3404 if ((traits & BlendPixelTrait) == 0)
3405 {
3406 x_vector[x*GetPixelChannels(image)+i]=(double) p[i];
3407 continue;
3408 }
3409 x_vector[x*GetPixelChannels(image)+i]=alpha*p[i];
3410 }
3411 p+=GetPixelChannels(image);
3412 }
3413 number_rows++;
3414 }
3415 for (x=0; x < (ssize_t) image->columns; x++)
3416 for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
3417 y_vector[x*GetPixelChannels(image)+i]+=scale.y*
3418 x_vector[x*GetPixelChannels(image)+i];
3419 span.y-=scale.y;
3420 scale.y=(double) scale_image->rows/(double) image->rows;
3421 next_row=MagickTrue;
3422 }
3423 if ((next_row != MagickFalse) && (number_rows < (ssize_t) image->rows))
3424 {
3425 /*
3426 Read a new scanline.
3427 */
3428 p=GetCacheViewVirtualPixels(image_view,0,n++,image->columns,1,
3429 exception);
3430 if (p == (const Quantum *) NULL)
3431 {
3432 status=MagickFalse;
3433 break;
3434 }
3435 for (x=0; x < (ssize_t) image->columns; x++)
3436 {
3437 if (GetPixelWriteMask(image,p) <= (QuantumRange/2))
3438 {
3439 p+=GetPixelChannels(image);
3440 continue;
3441 }
3442 if (image->alpha_trait != UndefinedPixelTrait)
3443 alpha=QuantumScale*GetPixelAlpha(image,p);
3444 for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
3445 {
3446 PixelChannel channel = GetPixelChannelChannel(image,i);
3447 PixelTrait traits = GetPixelChannelTraits(image,channel);
3448 if ((traits & BlendPixelTrait) == 0)
3449 {
3450 x_vector[x*GetPixelChannels(image)+i]=(double) p[i];
3451 continue;
3452 }
3453 x_vector[x*GetPixelChannels(image)+i]=alpha*p[i];
3454 }
3455 p+=GetPixelChannels(image);
3456 }
3457 number_rows++;
3458 next_row=MagickFalse;
3459 }
3460 for (x=0; x < (ssize_t) image->columns; x++)
3461 {
3462 for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
3463 {
3464 pixel[i]=y_vector[x*GetPixelChannels(image)+i]+span.y*
3465 x_vector[x*GetPixelChannels(image)+i];
3466 scanline[x*GetPixelChannels(image)+i]=pixel[i];
3467 y_vector[x*GetPixelChannels(image)+i]=0.0;
3468 }
3469 }
3470 scale.y-=span.y;
3471 if (scale.y <= 0)
3472 {
3473 scale.y=(double) scale_image->rows/(double) image->rows;
3474 next_row=MagickTrue;
3475 }
3476 span.y=1.0;
3477 }
3478 if (scale_image->columns == image->columns)
3479 {
3480 /*
3481 Transfer scanline to scaled image.
3482 */
3483 for (x=0; x < (ssize_t) scale_image->columns; x++)
3484 {
3485 if (GetPixelWriteMask(scale_image,q) <= (QuantumRange/2))
3486 {
3487 q+=GetPixelChannels(scale_image);
3488 continue;
3489 }
3490 if (image->alpha_trait != UndefinedPixelTrait)
3491 {
3492 alpha=QuantumScale*scanline[x*GetPixelChannels(image)+
3493 GetPixelChannelOffset(image,AlphaPixelChannel)];
3494 alpha=PerceptibleReciprocal(alpha);
3495 }
3496 for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
3497 {
3498 PixelChannel channel = GetPixelChannelChannel(image,i);
3499 PixelTrait traits = GetPixelChannelTraits(image,channel);
3500 scale_traits=GetPixelChannelTraits(scale_image,channel);
3501 if ((traits == UndefinedPixelTrait) ||
3502 (scale_traits == UndefinedPixelTrait))
3503 continue;
3504 if ((traits & BlendPixelTrait) == 0)
3505 {
3506 SetPixelChannel(scale_image,channel,ClampToQuantum(
3507 scanline[x*GetPixelChannels(image)+i]),q);
3508 continue;
3509 }
3510 SetPixelChannel(scale_image,channel,ClampToQuantum(alpha*scanline[
3511 x*GetPixelChannels(image)+i]),q);
3512 }
3513 q+=GetPixelChannels(scale_image);
3514 }
3515 }
3516 else
3517 {
3518 ssize_t
3519 t;
3520
3521 /*
3522 Scale X direction.
3523 */
3524 for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
3525 pixel[i]=0.0;
3526 next_column=MagickFalse;
3527 span.x=1.0;
3528 t=0;
3529 for (x=0; x < (ssize_t) image->columns; x++)
3530 {
3531 scale.x=(double) scale_image->columns/(double) image->columns;
3532 while (scale.x >= span.x)
3533 {
3534 if (next_column != MagickFalse)
3535 {
3536 for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
3537 pixel[i]=0.0;
3538 t++;
3539 }
3540 for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
3541 {
3542 PixelChannel channel = GetPixelChannelChannel(image,i);
3543 PixelTrait traits = GetPixelChannelTraits(image,channel);
3544 if (traits == UndefinedPixelTrait)
3545 continue;
3546 pixel[i]+=span.x*scanline[x*GetPixelChannels(image)+i];
3547 scale_scanline[t*GetPixelChannels(image)+i]=pixel[i];
3548 }
3549 scale.x-=span.x;
3550 span.x=1.0;
3551 next_column=MagickTrue;
3552 }
3553 if (scale.x > 0)
3554 {
3555 if (next_column != MagickFalse)
3556 {
3557 for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
3558 pixel[i]=0.0;
3559 next_column=MagickFalse;
3560 t++;
3561 }
3562 for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
3563 pixel[i]+=scale.x*scanline[x*GetPixelChannels(image)+i];
3564 span.x-=scale.x;
3565 }
3566 }
3567 if (span.x > 0)
3568 {
3569 for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
3570 pixel[i]+=span.x*scanline[(x-1)*GetPixelChannels(image)+i];
3571 }
3572 if ((next_column == MagickFalse) &&
3573 (t < (ssize_t) scale_image->columns))
3574 for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
3575 scale_scanline[t*GetPixelChannels(image)+i]=pixel[i];
3576 /*
3577 Transfer scanline to scaled image.
3578 */
3579 for (x=0; x < (ssize_t) scale_image->columns; x++)
3580 {
3581 if (GetPixelWriteMask(scale_image,q) <= (QuantumRange/2))
3582 {
3583 q+=GetPixelChannels(scale_image);
3584 continue;
3585 }
3586 if (image->alpha_trait != UndefinedPixelTrait)
3587 {
3588 alpha=QuantumScale*scale_scanline[x*GetPixelChannels(image)+
3589 GetPixelChannelOffset(image,AlphaPixelChannel)];
3590 alpha=PerceptibleReciprocal(alpha);
3591 }
3592 for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
3593 {
3594 PixelChannel channel = GetPixelChannelChannel(image,i);
3595 PixelTrait traits = GetPixelChannelTraits(image,channel);
3596 scale_traits=GetPixelChannelTraits(scale_image,channel);
3597 if ((traits == UndefinedPixelTrait) ||
3598 (scale_traits == UndefinedPixelTrait))
3599 continue;
3600 if ((traits & BlendPixelTrait) == 0)
3601 {
3602 SetPixelChannel(scale_image,channel,ClampToQuantum(
3603 scale_scanline[x*GetPixelChannels(image)+i]),q);
3604 continue;
3605 }
3606 SetPixelChannel(scale_image,channel,ClampToQuantum(alpha*
3607 scale_scanline[x*GetPixelChannels(image)+i]),q);
3608 }
3609 q+=GetPixelChannels(scale_image);
3610 }
3611 }
3612 if (SyncCacheViewAuthenticPixels(scale_view,exception) == MagickFalse)
3613 {
3614 status=MagickFalse;
3615 break;
3616 }
3617 proceed=SetImageProgress(image,ScaleImageTag,(MagickOffsetType) y,
3618 image->rows);
3619 if (proceed == MagickFalse)
3620 {
3621 status=MagickFalse;
3622 break;
3623 }
3624 }
3625 scale_view=DestroyCacheView(scale_view);
3626 image_view=DestroyCacheView(image_view);
3627 /*
3628 Free allocated memory.
3629 */
3630 y_vector=(double *) RelinquishMagickMemory(y_vector);
3631 scale_scanline=(double *) RelinquishMagickMemory(scale_scanline);
3632 if (scale_image->rows != image->rows)
3633 scanline=(double *) RelinquishMagickMemory(scanline);
3634 x_vector=(double *) RelinquishMagickMemory(x_vector);
3635 scale_image->type=image->type;
3636 if (status == MagickFalse)
3637 scale_image=DestroyImage(scale_image);
3638 return(scale_image);
3639 }
3640
3641 /*
3642 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3643 % %
3644 % %
3645 % %
3646 % T h u m b n a i l I m a g e %
3647 % %
3648 % %
3649 % %
3650 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3651 %
3652 % ThumbnailImage() changes the size of an image to the given dimensions and
3653 % removes any associated profiles. The goal is to produce small low cost
3654 % thumbnail images suited for display on the Web.
3655 %
3656 % The format of the ThumbnailImage method is:
3657 %
3658 % Image *ThumbnailImage(const Image *image,const size_t columns,
3659 % const size_t rows,ExceptionInfo *exception)
3660 %
3661 % A description of each parameter follows:
3662 %
3663 % o image: the image.
3664 %
3665 % o columns: the number of columns in the scaled image.
3666 %
3667 % o rows: the number of rows in the scaled image.
3668 %
3669 % o exception: return any errors or warnings in this structure.
3670 %
3671 */
ThumbnailImage(const Image * image,const size_t columns,const size_t rows,ExceptionInfo * exception)3672 MagickExport Image *ThumbnailImage(const Image *image,const size_t columns,
3673 const size_t rows,ExceptionInfo *exception)
3674 {
3675 #define SampleFactor 5
3676
3677 char
3678 filename[MagickPathExtent],
3679 value[MagickPathExtent];
3680
3681 const char
3682 *name;
3683
3684 Image
3685 *thumbnail_image;
3686
3687 double
3688 x_factor,
3689 y_factor;
3690
3691 struct stat
3692 attributes;
3693
3694 assert(image != (Image *) NULL);
3695 assert(image->signature == MagickCoreSignature);
3696 if (image->debug != MagickFalse)
3697 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
3698 assert(exception != (ExceptionInfo *) NULL);
3699 assert(exception->signature == MagickCoreSignature);
3700 x_factor=(double) columns/(double) image->columns;
3701 y_factor=(double) rows/(double) image->rows;
3702 if ((x_factor*y_factor) > 0.1)
3703 thumbnail_image=ResizeImage(image,columns,rows,image->filter,exception);
3704 else
3705 if (((SampleFactor*columns) < 128) || ((SampleFactor*rows) < 128))
3706 thumbnail_image=ResizeImage(image,columns,rows,image->filter,exception);
3707 else
3708 {
3709 Image
3710 *sample_image;
3711
3712 sample_image=SampleImage(image,SampleFactor*columns,SampleFactor*rows,
3713 exception);
3714 if (sample_image == (Image *) NULL)
3715 return((Image *) NULL);
3716 thumbnail_image=ResizeImage(sample_image,columns,rows,image->filter,
3717 exception);
3718 sample_image=DestroyImage(sample_image);
3719 }
3720 if (thumbnail_image == (Image *) NULL)
3721 return(thumbnail_image);
3722 (void) ParseAbsoluteGeometry("0x0+0+0",&thumbnail_image->page);
3723 if (thumbnail_image->alpha_trait == UndefinedPixelTrait)
3724 (void) SetImageAlphaChannel(thumbnail_image,OpaqueAlphaChannel,exception);
3725 thumbnail_image->depth=8;
3726 thumbnail_image->interlace=NoInterlace;
3727 /*
3728 Strip all profiles except color profiles.
3729 */
3730 ResetImageProfileIterator(thumbnail_image);
3731 for (name=GetNextImageProfile(thumbnail_image); name != (const char *) NULL; )
3732 {
3733 if ((LocaleCompare(name,"icc") != 0) && (LocaleCompare(name,"icm") != 0))
3734 {
3735 (void) DeleteImageProfile(thumbnail_image,name);
3736 ResetImageProfileIterator(thumbnail_image);
3737 }
3738 name=GetNextImageProfile(thumbnail_image);
3739 }
3740 (void) DeleteImageProperty(thumbnail_image,"comment");
3741 (void) CopyMagickString(value,image->magick_filename,MagickPathExtent);
3742 if (strstr(image->magick_filename,"//") == (char *) NULL)
3743 (void) FormatLocaleString(value,MagickPathExtent,"file://%s",
3744 image->magick_filename);
3745 (void) SetImageProperty(thumbnail_image,"Thumb::URI",value,exception);
3746 GetPathComponent(image->magick_filename,TailPath,filename);
3747 (void) CopyMagickString(value,filename,MagickPathExtent);
3748 if ( GetPathAttributes(image->filename,&attributes) != MagickFalse )
3749 {
3750 (void) FormatLocaleString(value,MagickPathExtent,"%.20g",(double)
3751 attributes.st_mtime);
3752 (void) SetImageProperty(thumbnail_image,"Thumb::MTime",value,exception);
3753 }
3754 (void) FormatLocaleString(value,MagickPathExtent,"%.20g",(double)
3755 attributes.st_mtime);
3756 (void) FormatMagickSize(GetBlobSize(image),MagickFalse,"B",MagickPathExtent,
3757 value);
3758 (void) SetImageProperty(thumbnail_image,"Thumb::Size",value,exception);
3759 (void) FormatLocaleString(value,MagickPathExtent,"image/%s",image->magick);
3760 LocaleLower(value);
3761 (void) SetImageProperty(thumbnail_image,"Thumb::Mimetype",value,exception);
3762 (void) SetImageProperty(thumbnail_image,"software",MagickAuthoritativeURL,
3763 exception);
3764 (void) FormatLocaleString(value,MagickPathExtent,"%.20g",(double)
3765 image->magick_columns);
3766 (void) SetImageProperty(thumbnail_image,"Thumb::Image::Width",value,
3767 exception);
3768 (void) FormatLocaleString(value,MagickPathExtent,"%.20g",(double)
3769 image->magick_rows);
3770 (void) SetImageProperty(thumbnail_image,"Thumb::Image::Height",value,
3771 exception);
3772 (void) FormatLocaleString(value,MagickPathExtent,"%.20g",(double)
3773 GetImageListLength(image));
3774 (void) SetImageProperty(thumbnail_image,"Thumb::Document::Pages",value,
3775 exception);
3776 return(thumbnail_image);
3777 }
3778