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