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