2 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
6 % RRRR EEEEE SSSSS IIIII ZZZZZ EEEEE %
8 % RRRR EEE SSS I ZZZ EEE %
10 % R R EEEEE SSSSS IIIII ZZZZZ EEEEE %
13 % MagickCore Image Resize Methods %
20 % Copyright 1999-2017 ImageMagick Studio LLC, a non-profit organization %
21 % dedicated to making software imaging solutions freely available. %
23 % You may not use this file except in compliance with the License. You may %
24 % obtain a copy of the License at %
26 % https://www.imagemagick.org/script/license.php %
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. %
34 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
42 #include "MagickCore/studio.h"
43 #include "MagickCore/accelerate-private.h"
44 #include "MagickCore/artifact.h"
45 #include "MagickCore/blob.h"
46 #include "MagickCore/cache.h"
47 #include "MagickCore/cache-view.h"
48 #include "MagickCore/channel.h"
49 #include "MagickCore/color.h"
50 #include "MagickCore/color-private.h"
51 #include "MagickCore/draw.h"
52 #include "MagickCore/exception.h"
53 #include "MagickCore/exception-private.h"
54 #include "MagickCore/gem.h"
55 #include "MagickCore/image.h"
56 #include "MagickCore/image-private.h"
57 #include "MagickCore/list.h"
58 #include "MagickCore/memory_.h"
59 #include "MagickCore/memory-private.h"
60 #include "MagickCore/magick.h"
61 #include "MagickCore/pixel-accessor.h"
62 #include "MagickCore/property.h"
63 #include "MagickCore/monitor.h"
64 #include "MagickCore/monitor-private.h"
65 #include "MagickCore/nt-base-private.h"
66 #include "MagickCore/option.h"
67 #include "MagickCore/pixel.h"
68 #include "MagickCore/pixel-private.h"
69 #include "MagickCore/quantum-private.h"
70 #include "MagickCore/resample.h"
71 #include "MagickCore/resample-private.h"
72 #include "MagickCore/resize.h"
73 #include "MagickCore/resize-private.h"
74 #include "MagickCore/resource_.h"
75 #include "MagickCore/string_.h"
76 #include "MagickCore/string-private.h"
77 #include "MagickCore/thread-private.h"
78 #include "MagickCore/token.h"
79 #include "MagickCore/utility.h"
80 #include "MagickCore/utility-private.h"
81 #include "MagickCore/version.h"
82 #if defined(MAGICKCORE_LQR_DELEGATE)
92 (*filter)(const double,const ResizeFilter *),
93 (*window)(const double,const ResizeFilter *),
94 support, /* filter region of support - the filter support limit */
95 window_support, /* window support, usally equal to support (expert only) */
96 scale, /* dimension scaling to fit window support (usally 1.0) */
97 blur, /* x-scale (blur-sharpen) */
98 coefficient[7]; /* cubic coefficents for BC-cubic filters */
100 ResizeWeightingFunctionType
109 Forward declaractions.
113 BesselOrderOne(double),
114 Sinc(const double, const ResizeFilter *),
115 SincFast(const double, const ResizeFilter *);
118 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
122 + F i l t e r F u n c t i o n s %
126 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
128 % These are the various filter and windowing functions that are provided.
130 % They are internal to this module only. See AcquireResizeFilterInfo() for
131 % details of the access to these functions, via the GetResizeFilterSupport()
132 % and GetResizeFilterWeight() API interface.
134 % The individual filter functions have this format...
136 % static MagickRealtype *FilterName(const double x,const double support)
138 % A description of each parameter follows:
140 % o x: the distance from the sampling point generally in the range of 0 to
141 % support. The GetResizeFilterWeight() ensures this a positive value.
143 % o resize_filter: current filter information. This allows function to
144 % access support, and possibly other pre-calculated information defining
149 static double Blackman(const double x,
150 const ResizeFilter *magick_unused(resize_filter))
153 Blackman: 2nd order cosine windowing function:
154 0.42 + 0.5 cos(pi x) + 0.08 cos(2pi x)
156 Refactored by Chantal Racette and Nicolas Robidoux to one trig call and
159 const double cosine=cos((double) (MagickPI*x));
160 magick_unreferenced(resize_filter);
161 return(0.34+cosine*(0.5+cosine*0.16));
164 static double Bohman(const double x,
165 const ResizeFilter *magick_unused(resize_filter))
168 Bohman: 2rd Order cosine windowing function:
169 (1-x) cos(pi x) + sin(pi x) / pi.
171 Refactored by Nicolas Robidoux to one trig call, one sqrt call, and 7 flops,
172 taking advantage of the fact that the support of Bohman is 1.0 (so that we
173 know that sin(pi x) >= 0).
175 const double cosine=cos((double) (MagickPI*x));
176 const double sine=sqrt(1.0-cosine*cosine);
177 magick_unreferenced(resize_filter);
178 return((1.0-x)*cosine+(1.0/MagickPI)*sine);
181 static double Box(const double magick_unused(x),
182 const ResizeFilter *magick_unused(resize_filter))
184 magick_unreferenced(x);
185 magick_unreferenced(resize_filter);
188 A Box filter is a equal weighting function (all weights equal).
189 DO NOT LIMIT results by support or resize point sampling will work
190 as it requests points beyond its normal 0.0 support size.
195 static double Cosine(const double x,
196 const ResizeFilter *magick_unused(resize_filter))
198 magick_unreferenced(resize_filter);
201 Cosine window function:
204 return((double)cos((double) (MagickPI2*x)));
207 static double CubicBC(const double x,const ResizeFilter *resize_filter)
210 Cubic Filters using B,C determined values:
211 Mitchell-Netravali B = 1/3 C = 1/3 "Balanced" cubic spline filter
212 Catmull-Rom B = 0 C = 1/2 Interpolatory and exact on linears
213 Spline B = 1 C = 0 B-Spline Gaussian approximation
214 Hermite B = 0 C = 0 B-Spline interpolator
216 See paper by Mitchell and Netravali, Reconstruction Filters in Computer
217 Graphics Computer Graphics, Volume 22, Number 4, August 1988
218 http://www.cs.utexas.edu/users/fussell/courses/cs384g/lectures/mitchell/
221 Coefficents are determined from B,C values:
222 P0 = ( 6 - 2*B )/6 = coeff[0]
224 P2 = (-18 +12*B + 6*C )/6 = coeff[1]
225 P3 = ( 12 - 9*B - 6*C )/6 = coeff[2]
226 Q0 = ( 8*B +24*C )/6 = coeff[3]
227 Q1 = ( -12*B -48*C )/6 = coeff[4]
228 Q2 = ( 6*B +30*C )/6 = coeff[5]
229 Q3 = ( - 1*B - 6*C )/6 = coeff[6]
231 which are used to define the filter:
233 P0 + P1*x + P2*x^2 + P3*x^3 0 <= x < 1
234 Q0 + Q1*x + Q2*x^2 + Q3*x^3 1 <= x < 2
236 which ensures function is continuous in value and derivative (slope).
239 return(resize_filter->coefficient[0]+x*(x*
240 (resize_filter->coefficient[1]+x*resize_filter->coefficient[2])));
242 return(resize_filter->coefficient[3]+x*(resize_filter->coefficient[4]+x*
243 (resize_filter->coefficient[5]+x*resize_filter->coefficient[6])));
247 static double Gaussian(const double x,const ResizeFilter *resize_filter)
250 Gaussian with a sigma = 1/2 (or as user specified)
252 Gaussian Formula (1D) ...
253 exp( -(x^2)/((2.0*sigma^2) ) / (sqrt(2*PI)*sigma^2))
255 Gaussian Formula (2D) ...
256 exp( -(x^2+y^2)/(2.0*sigma^2) ) / (PI*sigma^2) )
258 exp( -(r^2)/(2.0*sigma^2) ) / (PI*sigma^2) )
260 Note that it is only a change from 1-d to radial form is in the
261 normalization multiplier which is not needed or used when Gaussian is used
264 The constants are pre-calculated...
267 coeff[1]=1.0/(2.0*sigma^2);
268 coeff[2]=1.0/(sqrt(2*PI)*sigma^2);
270 exp( -coeff[1]*(x^2)) ) * coeff[2];
272 However the multiplier coeff[1] is need, the others are informative only.
274 This separates the gaussian 'sigma' value from the 'blur/support'
275 settings allowing for its use in special 'small sigma' gaussians,
276 without the filter 'missing' pixels because the support becomes too
279 return(exp((double)(-resize_filter->coefficient[1]*x*x)));
282 static double Hann(const double x,
283 const ResizeFilter *magick_unused(resize_filter))
286 Cosine window function:
289 const double cosine=cos((double) (MagickPI*x));
290 magick_unreferenced(resize_filter);
291 return(0.5+0.5*cosine);
294 static double Hamming(const double x,
295 const ResizeFilter *magick_unused(resize_filter))
298 Offset cosine window function:
301 const double cosine=cos((double) (MagickPI*x));
302 magick_unreferenced(resize_filter);
303 return(0.54+0.46*cosine);
306 static double Jinc(const double x,
307 const ResizeFilter *magick_unused(resize_filter))
309 magick_unreferenced(resize_filter);
312 See Pratt "Digital Image Processing" p.97 for Jinc/Bessel functions.
313 http://mathworld.wolfram.com/JincFunction.html and page 11 of
314 http://www.ph.ed.ac.uk/%7ewjh/teaching/mo/slides/lens/lens.pdf
316 The original "zoom" program by Paul Heckbert called this "Bessel". But
317 really it is more accurately named "Jinc".
320 return(0.5*MagickPI);
321 return(BesselOrderOne(MagickPI*x)/x);
324 static double Kaiser(const double x,const ResizeFilter *resize_filter)
327 Kaiser Windowing Function (bessel windowing)
329 I0( beta * sqrt( 1-x^2) ) / IO(0)
331 Beta (coeff[0]) is a free value from 5 to 8 (defaults to 6.5).
332 However it is typically defined in terms of Alpha*PI
334 The normalization factor (coeff[1]) is not actually needed,
335 but without it the filters has a large value at x=0 making it
336 difficult to compare the function with other windowing functions.
338 return(resize_filter->coefficient[1]*I0(resize_filter->coefficient[0]*
339 sqrt((double) (1.0-x*x))));
342 static double Lagrange(const double x,const ResizeFilter *resize_filter)
355 Lagrange piecewise polynomial fit of sinc: N is the 'order' of the lagrange
356 function and depends on the overall support window size of the filter. That
357 is: for a support of 2, it gives a lagrange-4 (piecewise cubic function).
359 "n" identifies the piece of the piecewise polynomial.
361 See Survey: Interpolation Methods, IEEE Transactions on Medical Imaging,
362 Vol 18, No 11, November 1999, p1049-1075, -- Equation 27 on p1064.
364 if (x > resize_filter->support)
366 order=(ssize_t) (2.0*resize_filter->window_support); /* number of pieces */
367 n=(ssize_t) (resize_filter->window_support+x);
369 for (i=0; i < order; i++)
371 value*=(n-i-x)/(n-i);
375 static double Quadratic(const double x,
376 const ResizeFilter *magick_unused(resize_filter))
378 magick_unreferenced(resize_filter);
381 2rd order (quadratic) B-Spline approximation of Gaussian.
386 return(0.5*(x-1.5)*(x-1.5));
390 static double Sinc(const double x,
391 const ResizeFilter *magick_unused(resize_filter))
393 magick_unreferenced(resize_filter);
396 Scaled sinc(x) function using a trig call:
397 sinc(x) == sin(pi x)/(pi x).
401 const double alpha=(double) (MagickPI*x);
402 return(sin((double) alpha)/alpha);
404 return((double) 1.0);
407 static double SincFast(const double x,
408 const ResizeFilter *magick_unused(resize_filter))
410 magick_unreferenced(resize_filter);
413 Approximations of the sinc function sin(pi x)/(pi x) over the interval
414 [-4,4] constructed by Nicolas Robidoux and Chantal Racette with funding
415 from the Natural Sciences and Engineering Research Council of Canada.
417 Although the approximations are polynomials (for low order of
418 approximation) and quotients of polynomials (for higher order of
419 approximation) and consequently are similar in form to Taylor polynomials /
420 Pade approximants, the approximations are computed with a completely
423 Summary: These approximations are "the best" in terms of bang (accuracy)
424 for the buck (flops). More specifically: Among the polynomial quotients
425 that can be computed using a fixed number of flops (with a given "+ - * /
426 budget"), the chosen polynomial quotient is the one closest to the
427 approximated function with respect to maximum absolute relative error over
430 The Remez algorithm, as implemented in the boost library's minimax package,
431 is the key to the construction: http://www.boost.org/doc/libs/1_36_0/libs/
432 math/doc/sf_and_dist/html/math_toolkit/backgrounders/remez.html
434 If outside of the interval of approximation, use the standard trig formula.
438 const double alpha=(double) (MagickPI*x);
439 return(sin((double) alpha)/alpha);
443 The approximations only depend on x^2 (sinc is an even function).
445 const double xx = x*x;
446 #if MAGICKCORE_QUANTUM_DEPTH <= 8
448 Maximum absolute relative error 6.3e-6 < 1/2^17.
450 const double c0 = 0.173610016489197553621906385078711564924e-2L;
451 const double c1 = -0.384186115075660162081071290162149315834e-3L;
452 const double c2 = 0.393684603287860108352720146121813443561e-4L;
453 const double c3 = -0.248947210682259168029030370205389323899e-5L;
454 const double c4 = 0.107791837839662283066379987646635416692e-6L;
455 const double c5 = -0.324874073895735800961260474028013982211e-8L;
456 const double c6 = 0.628155216606695311524920882748052490116e-10L;
457 const double c7 = -0.586110644039348333520104379959307242711e-12L;
459 c0+xx*(c1+xx*(c2+xx*(c3+xx*(c4+xx*(c5+xx*(c6+xx*c7))))));
460 return((xx-1.0)*(xx-4.0)*(xx-9.0)*(xx-16.0)*p);
461 #elif MAGICKCORE_QUANTUM_DEPTH <= 16
463 Max. abs. rel. error 2.2e-8 < 1/2^25.
465 const double c0 = 0.173611107357320220183368594093166520811e-2L;
466 const double c1 = -0.384240921114946632192116762889211361285e-3L;
467 const double c2 = 0.394201182359318128221229891724947048771e-4L;
468 const double c3 = -0.250963301609117217660068889165550534856e-5L;
469 const double c4 = 0.111902032818095784414237782071368805120e-6L;
470 const double c5 = -0.372895101408779549368465614321137048875e-8L;
471 const double c6 = 0.957694196677572570319816780188718518330e-10L;
472 const double c7 = -0.187208577776590710853865174371617338991e-11L;
473 const double c8 = 0.253524321426864752676094495396308636823e-13L;
474 const double c9 = -0.177084805010701112639035485248501049364e-15L;
476 c0+xx*(c1+xx*(c2+xx*(c3+xx*(c4+xx*(c5+xx*(c6+xx*(c7+xx*(c8+xx*c9))))))));
477 return((xx-1.0)*(xx-4.0)*(xx-9.0)*(xx-16.0)*p);
480 Max. abs. rel. error 1.2e-12 < 1/2^39.
482 const double c0 = 0.173611111110910715186413700076827593074e-2L;
483 const double c1 = -0.289105544717893415815859968653611245425e-3L;
484 const double c2 = 0.206952161241815727624413291940849294025e-4L;
485 const double c3 = -0.834446180169727178193268528095341741698e-6L;
486 const double c4 = 0.207010104171026718629622453275917944941e-7L;
487 const double c5 = -0.319724784938507108101517564300855542655e-9L;
488 const double c6 = 0.288101675249103266147006509214934493930e-11L;
489 const double c7 = -0.118218971804934245819960233886876537953e-13L;
491 c0+xx*(c1+xx*(c2+xx*(c3+xx*(c4+xx*(c5+xx*(c6+xx*c7))))));
492 const double d0 = 1.0L;
493 const double d1 = 0.547981619622284827495856984100563583948e-1L;
494 const double d2 = 0.134226268835357312626304688047086921806e-2L;
495 const double d3 = 0.178994697503371051002463656833597608689e-4L;
496 const double d4 = 0.114633394140438168641246022557689759090e-6L;
497 const double q = d0+xx*(d1+xx*(d2+xx*(d3+xx*d4)));
498 return((xx-1.0)*(xx-4.0)*(xx-9.0)*(xx-16.0)/q*p);
503 static double Triangle(const double x,
504 const ResizeFilter *magick_unused(resize_filter))
506 magick_unreferenced(resize_filter);
509 1st order (linear) B-Spline, bilinear interpolation, Tent 1D filter, or
510 a Bartlett 2D Cone filter. Also used as a Bartlett Windowing function
518 static double Welch(const double x,
519 const ResizeFilter *magick_unused(resize_filter))
521 magick_unreferenced(resize_filter);
524 Welch parabolic windowing filter.
532 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
536 + A c q u i r e R e s i z e F i l t e r %
540 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
542 % AcquireResizeFilter() allocates the ResizeFilter structure. Choose from
545 % FIR (Finite impulse Response) Filters
546 % Box Triangle Quadratic
547 % Spline Hermite Catrom
550 % IIR (Infinite impulse Response) Filters
551 % Gaussian Sinc Jinc (Bessel)
553 % Windowed Sinc/Jinc Filters
554 % Blackman Bohman Lanczos
555 % Hann Hamming Cosine
556 % Kaiser Welch Parzen
559 % Special Purpose Filters
560 % Cubic SincFast LanczosSharp Lanczos2 Lanczos2Sharp
561 % Robidoux RobidouxSharp
563 % The users "-filter" selection is used to lookup the default 'expert'
564 % settings for that filter from a internal table. However any provided
565 % 'expert' settings (see below) may override this selection.
567 % FIR filters are used as is, and are limited to that filters support window
568 % (unless over-ridden). 'Gaussian' while classed as an IIR filter, is also
569 % simply clipped by its support size (currently 1.5 or approximately 3*sigma
570 % as recommended by many references)
572 % The special a 'cylindrical' filter flag will promote the default 4-lobed
573 % Windowed Sinc filter to a 3-lobed Windowed Jinc equivalent, which is better
574 % suited to this style of image resampling. This typically happens when using
575 % such a filter for images distortions.
579 % Directly requesting 'Sinc', 'Jinc' function as a filter will force the use
580 % of function without any windowing, or promotion for cylindrical usage. This
581 % is not recommended, except by image processing experts, especially as part
582 % of expert option filter function selection.
584 % Two forms of the 'Sinc' function are available: Sinc and SincFast. Sinc is
585 % computed using the traditional sin(pi*x)/(pi*x); it is selected if the user
586 % specifically specifies the use of a Sinc filter. SincFast uses highly
587 % accurate (and fast) polynomial (low Q) and rational (high Q) approximations,
588 % and will be used by default in most cases.
590 % The Lanczos filter is a special 3-lobed Sinc-windowed Sinc filter (promoted
591 % to Jinc-windowed Jinc for cylindrical (Elliptical Weighted Average) use).
592 % The Sinc version is the most popular windowed filter.
594 % LanczosSharp is a slightly sharpened (blur=0.9812505644269356 < 1) form of
595 % the Lanczos filter, specifically designed for EWA distortion (as a
596 % Jinc-Jinc); it can also be used as a slightly sharper orthogonal Lanczos
597 % (Sinc-Sinc) filter. The chosen blur value comes as close as possible to
598 % satisfying the following condition without changing the character of the
599 % corresponding EWA filter:
601 % 'No-Op' Vertical and Horizontal Line Preservation Condition: Images with
602 % only vertical or horizontal features are preserved when performing 'no-op"
603 % with EWA distortion.
605 % The Lanczos2 and Lanczos2Sharp filters are 2-lobe versions of the Lanczos
606 % filters. The 'sharp' version uses a blur factor of 0.9549963639785485,
607 % again chosen because the resulting EWA filter comes as close as possible to
608 % satisfying the above condition.
610 % Robidoux is another filter tuned for EWA. It is the Keys cubic filter
611 % defined by B=(228 - 108 sqrt(2))/199. Robidoux satisfies the "'No-Op'
612 % Vertical and Horizontal Line Preservation Condition" exactly, and it
613 % moderately blurs high frequency 'pixel-hash' patterns under no-op. It turns
614 % out to be close to both Mitchell and Lanczos2Sharp. For example, its first
615 % crossing is at (36 sqrt(2) + 123)/(72 sqrt(2) + 47), almost the same as the
616 % first crossing of Mitchell and Lanczos2Sharp.
618 % RodidouxSharp is a slightly sharper version of Rodidoux, some believe it
619 % is too sharp. It is designed to minimize the maximum possible change in
620 % a pixel value which is at one of the extremes (e.g., 0 or 255) under no-op
621 % conditions. Amazingly Mitchell falls roughly between Rodidoux and
622 % RodidouxSharp, though this seems to have been pure coincidence.
626 % These artifact "defines" are not recommended for production use without
627 % expert knowledge of resampling, filtering, and the effects they have on the
628 % resulting resampled (resized or distorted) image.
630 % They can be used to override any and all filter default, and it is
631 % recommended you make good use of "filter:verbose" to make sure that the
632 % overall effect of your selection (before and after) is as expected.
634 % "filter:verbose" controls whether to output the exact results of the
635 % filter selections made, as well as plotting data for graphing the
636 % resulting filter over the filters support range.
638 % "filter:filter" select the main function associated with this filter
639 % name, as the weighting function of the filter. This can be used to
640 % set a windowing function as a weighting function, for special
641 % purposes, such as graphing.
643 % If a "filter:window" operation has not been provided, a 'Box'
644 % windowing function will be set to denote that no windowing function is
647 % "filter:window" Select this windowing function for the filter. While any
648 % filter could be used as a windowing function, using the 'first lobe' of
649 % that filter over the whole support window, using a non-windowing
650 % function is not advisible. If no weighting filter function is specified
651 % a 'SincFast' filter is used.
653 % "filter:lobes" Number of lobes to use for the Sinc/Jinc filter. This a
654 % simpler method of setting filter support size that will correctly
655 % handle the Sinc/Jinc switch for an operators filtering requirements.
656 % Only integers should be given.
658 % "filter:support" Set the support size for filtering to the size given.
659 % This not recommended for Sinc/Jinc windowed filters (lobes should be
660 % used instead). This will override any 'filter:lobes' option.
662 % "filter:win-support" Scale windowing function to this size instead. This
663 % causes the windowing (or self-windowing Lagrange filter) to act is if
664 % the support window it much much larger than what is actually supplied
665 % to the calling operator. The filter however is still clipped to the
666 % real support size given, by the support range supplied to the caller.
667 % If unset this will equal the normal filter support size.
669 % "filter:blur" Scale the filter and support window by this amount. A value
670 % of > 1 will generally result in a more blurred image with more ringing
671 % effects, while a value <1 will sharpen the resulting image with more
674 % "filter:sigma" The sigma value to use for the Gaussian filter only.
675 % Defaults to '1/2'. Using a different sigma effectively provides a
676 % method of using the filter as a 'blur' convolution. Particularly when
677 % using it for Distort.
680 % "filter:c" Override the preset B,C values for a Cubic filter.
681 % If only one of these are given it is assumes to be a 'Keys' type of
682 % filter such that B+2C=1, where Keys 'alpha' value = C.
686 % Set a true un-windowed Sinc filter with 10 lobes (very slow):
687 % -define filter:filter=Sinc
688 % -define filter:lobes=8
690 % Set an 8 lobe Lanczos (Sinc or Jinc) filter:
692 % -define filter:lobes=8
694 % The format of the AcquireResizeFilter method is:
696 % ResizeFilter *AcquireResizeFilter(const Image *image,
697 % const FilterType filter_type,const MagickBooleanType cylindrical,
698 % ExceptionInfo *exception)
700 % A description of each parameter follows:
702 % o image: the image.
704 % o filter: the filter type, defining a preset filter, window and support.
705 % The artifact settings listed above will override those selections.
707 % o blur: blur the filter by this amount, use 1.0 if unknown. Image
708 % artifact "filter:blur" will override this API call usage, including any
709 % internal change (such as for cylindrical usage).
711 % o radial: use a 1D orthogonal filter (Sinc) or 2D cylindrical (radial)
714 % o exception: return any errors or warnings in this structure.
717 MagickPrivate ResizeFilter *AcquireResizeFilter(const Image *image,
718 const FilterType filter,const MagickBooleanType cylindrical,
719 ExceptionInfo *exception)
733 register ResizeFilter
737 Table Mapping given Filter, into Weighting and Windowing functions.
738 A 'Box' windowing function means its a simble non-windowed filter.
739 An 'SincFast' filter function could be upgraded to a 'Jinc' filter if a
740 "cylindrical" is requested, unless a 'Sinc' or 'SincFast' filter was
741 specifically requested by the user.
743 WARNING: The order of this table must match the order of the FilterType
744 enumeration specified in "resample.h", or the filter names will not match
745 the filter being setup.
747 You can check filter setups with the "filter:verbose" expert setting.
754 } const mapping[SentinelFilter] =
756 { UndefinedFilter, BoxFilter }, /* Undefined (default to Box) */
757 { PointFilter, BoxFilter }, /* SPECIAL: Nearest neighbour */
758 { BoxFilter, BoxFilter }, /* Box averaging filter */
759 { TriangleFilter, BoxFilter }, /* Linear interpolation filter */
760 { HermiteFilter, BoxFilter }, /* Hermite interpolation filter */
761 { SincFastFilter, HannFilter }, /* Hann -- cosine-sinc */
762 { SincFastFilter, HammingFilter }, /* Hamming -- '' variation */
763 { SincFastFilter, BlackmanFilter }, /* Blackman -- 2*cosine-sinc */
764 { GaussianFilter, BoxFilter }, /* Gaussian blur filter */
765 { QuadraticFilter, BoxFilter }, /* Quadratic Gaussian approx */
766 { CubicFilter, BoxFilter }, /* General Cubic Filter, Spline */
767 { CatromFilter, BoxFilter }, /* Cubic-Keys interpolator */
768 { MitchellFilter, BoxFilter }, /* 'Ideal' Cubic-Keys filter */
769 { JincFilter, BoxFilter }, /* Raw 3-lobed Jinc function */
770 { SincFilter, BoxFilter }, /* Raw 4-lobed Sinc function */
771 { SincFastFilter, BoxFilter }, /* Raw fast sinc ("Pade"-type) */
772 { SincFastFilter, KaiserFilter }, /* Kaiser -- square root-sinc */
773 { LanczosFilter, WelchFilter }, /* Welch -- parabolic (3 lobe) */
774 { SincFastFilter, CubicFilter }, /* Parzen -- cubic-sinc */
775 { SincFastFilter, BohmanFilter }, /* Bohman -- 2*cosine-sinc */
776 { SincFastFilter, TriangleFilter }, /* Bartlett -- triangle-sinc */
777 { LagrangeFilter, BoxFilter }, /* Lagrange self-windowing */
778 { LanczosFilter, LanczosFilter }, /* Lanczos Sinc-Sinc filters */
779 { LanczosSharpFilter, LanczosSharpFilter }, /* | these require */
780 { Lanczos2Filter, Lanczos2Filter }, /* | special handling */
781 { Lanczos2SharpFilter, Lanczos2SharpFilter },
782 { RobidouxFilter, BoxFilter }, /* Cubic Keys tuned for EWA */
783 { RobidouxSharpFilter, BoxFilter }, /* Sharper Cubic Keys for EWA */
784 { LanczosFilter, CosineFilter }, /* Cosine window (3 lobes) */
785 { SplineFilter, BoxFilter }, /* Spline Cubic Filter */
786 { LanczosRadiusFilter, LanczosFilter }, /* Lanczos with integer radius */
789 Table mapping the filter/window from the above table to an actual function.
790 The default support size for that filter as a weighting function, the range
791 to scale with to use that function as a sinc windowing function, (typ 1.0).
793 Note that the filter_type -> function is 1 to 1 except for Sinc(),
794 SincFast(), and CubicBC() functions, which may have multiple filter to
795 function associations.
797 See "filter:verbose" handling below for the function -> filter mapping.
802 (*function)(const double,const ResizeFilter*),
803 support, /* Default lobes/support size of the weighting filter. */
804 scale, /* Support when function used as a windowing function
805 Typically equal to the location of the first zero crossing. */
806 B,C; /* BC-spline coefficients, ignored if not a CubicBC filter. */
807 ResizeWeightingFunctionType weightingFunctionType;
808 } const filters[SentinelFilter] =
810 /* .--- support window (if used as a Weighting Function)
811 | .--- first crossing (if used as a Windowing Function)
812 | | .--- B value for Cubic Function
813 | | | .---- C value for Cubic Function
815 { Box, 0.5, 0.5, 0.0, 0.0, BoxWeightingFunction }, /* Undefined (default to Box) */
816 { Box, 0.0, 0.5, 0.0, 0.0, BoxWeightingFunction }, /* Point (special handling) */
817 { Box, 0.5, 0.5, 0.0, 0.0, BoxWeightingFunction }, /* Box */
818 { Triangle, 1.0, 1.0, 0.0, 0.0, TriangleWeightingFunction }, /* Triangle */
819 { CubicBC, 1.0, 1.0, 0.0, 0.0, CubicBCWeightingFunction }, /* Hermite (cubic B=C=0) */
820 { Hann, 1.0, 1.0, 0.0, 0.0, HannWeightingFunction }, /* Hann, cosine window */
821 { Hamming, 1.0, 1.0, 0.0, 0.0, HammingWeightingFunction }, /* Hamming, '' variation */
822 { Blackman, 1.0, 1.0, 0.0, 0.0, BlackmanWeightingFunction }, /* Blackman, 2*cosine window */
823 { Gaussian, 2.0, 1.5, 0.0, 0.0, GaussianWeightingFunction }, /* Gaussian */
824 { Quadratic, 1.5, 1.5, 0.0, 0.0, QuadraticWeightingFunction },/* Quadratic gaussian */
825 { CubicBC, 2.0, 2.0, 1.0, 0.0, CubicBCWeightingFunction }, /* General Cubic Filter */
826 { CubicBC, 2.0, 1.0, 0.0, 0.5, CubicBCWeightingFunction }, /* Catmull-Rom (B=0,C=1/2) */
827 { CubicBC, 2.0, 8.0/7.0, 1./3., 1./3., CubicBCWeightingFunction }, /* Mitchell (B=C=1/3) */
828 { Jinc, 3.0, 1.2196698912665045, 0.0, 0.0, JincWeightingFunction }, /* Raw 3-lobed Jinc */
829 { Sinc, 4.0, 1.0, 0.0, 0.0, SincWeightingFunction }, /* Raw 4-lobed Sinc */
830 { SincFast, 4.0, 1.0, 0.0, 0.0, SincFastWeightingFunction }, /* Raw fast sinc ("Pade"-type) */
831 { Kaiser, 1.0, 1.0, 0.0, 0.0, KaiserWeightingFunction }, /* Kaiser (square root window) */
832 { Welch, 1.0, 1.0, 0.0, 0.0, WelchWeightingFunction }, /* Welch (parabolic window) */
833 { CubicBC, 2.0, 2.0, 1.0, 0.0, CubicBCWeightingFunction }, /* Parzen (B-Spline window) */
834 { Bohman, 1.0, 1.0, 0.0, 0.0, BohmanWeightingFunction }, /* Bohman, 2*Cosine window */
835 { Triangle, 1.0, 1.0, 0.0, 0.0, TriangleWeightingFunction }, /* Bartlett (triangle window) */
836 { Lagrange, 2.0, 1.0, 0.0, 0.0, LagrangeWeightingFunction }, /* Lagrange sinc approximation */
837 { SincFast, 3.0, 1.0, 0.0, 0.0, SincFastWeightingFunction }, /* Lanczos, 3-lobed Sinc-Sinc */
838 { SincFast, 3.0, 1.0, 0.0, 0.0, SincFastWeightingFunction }, /* Lanczos, Sharpened */
839 { SincFast, 2.0, 1.0, 0.0, 0.0, SincFastWeightingFunction }, /* Lanczos, 2-lobed */
840 { SincFast, 2.0, 1.0, 0.0, 0.0, SincFastWeightingFunction }, /* Lanczos2, sharpened */
841 /* Robidoux: Keys cubic close to Lanczos2D sharpened */
842 { CubicBC, 2.0, 1.1685777620836932,
843 0.37821575509399867, 0.31089212245300067, CubicBCWeightingFunction },
844 /* RobidouxSharp: Sharper version of Robidoux */
845 { CubicBC, 2.0, 1.105822933719019,
846 0.2620145123990142, 0.3689927438004929, CubicBCWeightingFunction },
847 { Cosine, 1.0, 1.0, 0.0, 0.0, CosineWeightingFunction }, /* Low level cosine window */
848 { CubicBC, 2.0, 2.0, 1.0, 0.0, CubicBCWeightingFunction }, /* Cubic B-Spline (B=1,C=0) */
849 { SincFast, 3.0, 1.0, 0.0, 0.0, SincFastWeightingFunction }, /* Lanczos, Interger Radius */
852 The known zero crossings of the Jinc() or more accurately the Jinc(x*PI)
853 function being used as a filter. It is used by the "filter:lobes" expert
854 setting and for 'lobes' for Jinc functions in the previous table. This way
855 users do not have to deal with the highly irrational lobe sizes of the Jinc
859 http://cose.math.bas.bg/webMathematica/webComputing/BesselZeros.jsp
860 using Jv-function with v=1, then dividing by PI.
884 Allocate resize filter.
886 assert(image != (const Image *) NULL);
887 assert(image->signature == MagickCoreSignature);
888 if (image->debug != MagickFalse)
889 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
890 assert(UndefinedFilter < filter && filter < SentinelFilter);
891 assert(exception != (ExceptionInfo *) NULL);
892 assert(exception->signature == MagickCoreSignature);
893 resize_filter=(ResizeFilter *) AcquireMagickMemory(sizeof(*resize_filter));
894 if (resize_filter == (ResizeFilter *) NULL)
895 ThrowFatalException(ResourceLimitFatalError,"MemoryAllocationFailed");
896 (void) ResetMagickMemory(resize_filter,0,sizeof(*resize_filter));
898 Defaults for the requested filter.
900 filter_type=mapping[filter].filter;
901 window_type=mapping[filter].window;
902 resize_filter->blur=1.0;
903 /* Promote 1D Windowed Sinc Filters to a 2D Windowed Jinc filters */
904 if ( cylindrical != MagickFalse && (filter_type == SincFastFilter) &&
905 (filter != SincFastFilter))
906 filter_type=JincFilter; /* 1D Windowed Sinc => 2D Windowed Jinc filters */
908 /* Expert filter setting override */
909 artifact=GetImageArtifact(image,"filter:filter");
910 if (IsStringTrue(artifact) != MagickFalse)
915 option=ParseCommandOption(MagickFilterOptions,MagickFalse,artifact);
916 if ((UndefinedFilter < option) && (option < SentinelFilter))
917 { /* Raw filter request - no window function. */
918 filter_type=(FilterType) option;
919 window_type=BoxFilter;
921 /* Filter override with a specific window function. */
922 artifact=GetImageArtifact(image,"filter:window");
923 if (artifact != (const char *) NULL)
925 option=ParseCommandOption(MagickFilterOptions,MagickFalse,artifact);
926 if ((UndefinedFilter < option) && (option < SentinelFilter))
927 window_type=(FilterType) option;
932 /* Window specified, but no filter function? Assume Sinc/Jinc. */
933 artifact=GetImageArtifact(image,"filter:window");
934 if (artifact != (const char *) NULL)
939 option=ParseCommandOption(MagickFilterOptions,MagickFalse,artifact);
940 if ((UndefinedFilter < option) && (option < SentinelFilter))
942 filter_type= cylindrical != MagickFalse ? JincFilter
944 window_type=(FilterType) option;
949 /* Assign the real functions to use for the filters selected. */
950 resize_filter->filter=filters[filter_type].function;
951 resize_filter->support=filters[filter_type].support;
952 resize_filter->filterWeightingType=filters[filter_type].weightingFunctionType;
953 resize_filter->window=filters[window_type].function;
954 resize_filter->windowWeightingType=filters[window_type].weightingFunctionType;
955 resize_filter->scale=filters[window_type].scale;
956 resize_filter->signature=MagickCoreSignature;
958 /* Filter Modifications for orthogonal/cylindrical usage */
959 if (cylindrical != MagickFalse)
963 /* Support for Cylindrical Box should be sqrt(2)/2 */
964 resize_filter->support=(double) MagickSQ1_2;
967 case LanczosSharpFilter:
969 case Lanczos2SharpFilter:
970 case LanczosRadiusFilter:
971 resize_filter->filter=filters[JincFilter].function;
972 resize_filter->window=filters[JincFilter].function;
973 resize_filter->scale=filters[JincFilter].scale;
974 /* number of lobes (support window size) remain unchanged */
979 /* Global Sharpening (regardless of orthoginal/cylindrical) */
982 case LanczosSharpFilter:
983 resize_filter->blur *= 0.9812505644269356;
985 case Lanczos2SharpFilter:
986 resize_filter->blur *= 0.9549963639785485;
988 /* case LanczosRadius: blur adjust is done after lobes */
994 Expert Option Modifications.
997 /* User Gaussian Sigma Override - no support change */
998 if ((resize_filter->filter == Gaussian) ||
999 (resize_filter->window == Gaussian) ) {
1000 value=0.5; /* guassian sigma default, half pixel */
1001 artifact=GetImageArtifact(image,"filter:sigma");
1002 if (artifact != (const char *) NULL)
1003 value=StringToDouble(artifact,(char **) NULL);
1004 /* Define coefficents for Gaussian */
1005 resize_filter->coefficient[0]=value; /* note sigma too */
1006 resize_filter->coefficient[1]=PerceptibleReciprocal(2.0*value*value); /* sigma scaling */
1007 resize_filter->coefficient[2]=PerceptibleReciprocal(Magick2PI*value*value);
1008 /* normalization - not actually needed or used! */
1010 resize_filter->support *= 2*value; /* increase support linearly */
1013 /* User Kaiser Alpha Override - no support change */
1014 if ((resize_filter->filter == Kaiser) ||
1015 (resize_filter->window == Kaiser) ) {
1016 value=6.5; /* default beta value for Kaiser bessel windowing function */
1017 artifact=GetImageArtifact(image,"filter:alpha"); /* FUTURE: depreciate */
1018 if (artifact != (const char *) NULL)
1019 value=StringToDouble(artifact,(char **) NULL);
1020 artifact=GetImageArtifact(image,"filter:kaiser-beta");
1021 if (artifact != (const char *) NULL)
1022 value=StringToDouble(artifact,(char **) NULL);
1023 artifact=GetImageArtifact(image,"filter:kaiser-alpha");
1024 if (artifact != (const char *) NULL)
1025 value=StringToDouble(artifact,(char **) NULL)*MagickPI;
1026 /* Define coefficents for Kaiser Windowing Function */
1027 resize_filter->coefficient[0]=value; /* alpha */
1028 resize_filter->coefficient[1]=PerceptibleReciprocal(I0(value));
1032 /* Support Overrides */
1033 artifact=GetImageArtifact(image,"filter:lobes");
1034 if (artifact != (const char *) NULL)
1039 lobes=(ssize_t) StringToLong(artifact);
1042 resize_filter->support=(double) lobes;
1044 if (resize_filter->filter == Jinc)
1047 Convert a Jinc function lobes value to a real support value.
1049 if (resize_filter->support > 16)
1050 resize_filter->support=jinc_zeros[15]; /* largest entry in table */
1052 resize_filter->support=jinc_zeros[((long) resize_filter->support)-1];
1054 Blur this filter so support is a integer value (lobes dependant).
1056 if (filter_type == LanczosRadiusFilter)
1057 resize_filter->blur*=floor(resize_filter->support)/
1058 resize_filter->support;
1061 Expert blur override.
1063 artifact=GetImageArtifact(image,"filter:blur");
1064 if (artifact != (const char *) NULL)
1065 resize_filter->blur*=StringToDouble(artifact,(char **) NULL);
1066 if (resize_filter->blur < MagickEpsilon)
1067 resize_filter->blur=(double) MagickEpsilon;
1069 Expert override of the support setting.
1071 artifact=GetImageArtifact(image,"filter:support");
1072 if (artifact != (const char *) NULL)
1073 resize_filter->support=fabs(StringToDouble(artifact,(char **) NULL));
1075 Scale windowing function separately to the support 'clipping' window
1076 that calling operator is planning to actually use. (Expert override)
1078 resize_filter->window_support=resize_filter->support; /* default */
1079 artifact=GetImageArtifact(image,"filter:win-support");
1080 if (artifact != (const char *) NULL)
1081 resize_filter->window_support=fabs(StringToDouble(artifact,(char **) NULL));
1083 Adjust window function scaling to match windowing support for weighting
1084 function. This avoids a division on every filter call.
1086 resize_filter->scale/=resize_filter->window_support;
1088 * Set Cubic Spline B,C values, calculate Cubic coefficients.
1092 if ((resize_filter->filter == CubicBC) ||
1093 (resize_filter->window == CubicBC) )
1095 B=filters[filter_type].B;
1096 C=filters[filter_type].C;
1097 if (filters[window_type].function == CubicBC)
1099 B=filters[window_type].B;
1100 C=filters[window_type].C;
1102 artifact=GetImageArtifact(image,"filter:b");
1103 if (artifact != (const char *) NULL)
1105 B=StringToDouble(artifact,(char **) NULL);
1106 C=(1.0-B)/2.0; /* Calculate C to get a Keys cubic filter. */
1107 artifact=GetImageArtifact(image,"filter:c"); /* user C override */
1108 if (artifact != (const char *) NULL)
1109 C=StringToDouble(artifact,(char **) NULL);
1113 artifact=GetImageArtifact(image,"filter:c");
1114 if (artifact != (const char *) NULL)
1116 C=StringToDouble(artifact,(char **) NULL);
1117 B=1.0-2.0*C; /* Calculate B to get a Keys cubic filter. */
1125 Convert B,C values into Cubic Coefficents. See CubicBC().
1127 resize_filter->coefficient[0]=1.0-(1.0/3.0)*B;
1128 resize_filter->coefficient[1]=-3.0+twoB+C;
1129 resize_filter->coefficient[2]=2.0-1.5*B-C;
1130 resize_filter->coefficient[3]=(4.0/3.0)*B+4.0*C;
1131 resize_filter->coefficient[4]=-8.0*C-twoB;
1132 resize_filter->coefficient[5]=B+5.0*C;
1133 resize_filter->coefficient[6]=(-1.0/6.0)*B-C;
1138 Expert Option Request for verbose details of the resulting filter.
1140 #if defined(MAGICKCORE_OPENMP_SUPPORT)
1144 if (IsStringTrue(GetImageArtifact(image,"filter:verbose")) != MagickFalse)
1151 Set the weighting function properly when the weighting function
1152 may not exactly match the filter of the same name. EG: a Point
1153 filter is really uses a Box weighting function with a different
1154 support than is typically used.
1156 if (resize_filter->filter == Box) filter_type=BoxFilter;
1157 if (resize_filter->filter == Sinc) filter_type=SincFilter;
1158 if (resize_filter->filter == SincFast) filter_type=SincFastFilter;
1159 if (resize_filter->filter == Jinc) filter_type=JincFilter;
1160 if (resize_filter->filter == CubicBC) filter_type=CubicFilter;
1161 if (resize_filter->window == Box) window_type=BoxFilter;
1162 if (resize_filter->window == Sinc) window_type=SincFilter;
1163 if (resize_filter->window == SincFast) window_type=SincFastFilter;
1164 if (resize_filter->window == Jinc) window_type=JincFilter;
1165 if (resize_filter->window == CubicBC) window_type=CubicFilter;
1167 Report Filter Details.
1169 support=GetResizeFilterSupport(resize_filter); /* practical_support */
1170 (void) FormatLocaleFile(stdout,
1171 "# Resampling Filter (for graphing)\n#\n");
1172 (void) FormatLocaleFile(stdout,"# filter = %s\n",
1173 CommandOptionToMnemonic(MagickFilterOptions,filter_type));
1174 (void) FormatLocaleFile(stdout,"# window = %s\n",
1175 CommandOptionToMnemonic(MagickFilterOptions,window_type));
1176 (void) FormatLocaleFile(stdout,"# support = %.*g\n",
1177 GetMagickPrecision(),(double) resize_filter->support);
1178 (void) FormatLocaleFile(stdout,"# window-support = %.*g\n",
1179 GetMagickPrecision(),(double) resize_filter->window_support);
1180 (void) FormatLocaleFile(stdout,"# scale-blur = %.*g\n",
1181 GetMagickPrecision(),(double)resize_filter->blur);
1182 if ((filter_type == GaussianFilter) || (window_type == GaussianFilter))
1183 (void) FormatLocaleFile(stdout,"# gaussian-sigma = %.*g\n",
1184 GetMagickPrecision(),(double)resize_filter->coefficient[0]);
1185 if ( filter_type == KaiserFilter || window_type == KaiserFilter )
1186 (void) FormatLocaleFile(stdout,"# kaiser-beta = %.*g\n",
1187 GetMagickPrecision(),(double)resize_filter->coefficient[0]);
1188 (void) FormatLocaleFile(stdout,"# practical-support = %.*g\n",
1189 GetMagickPrecision(), (double)support);
1190 if ( filter_type == CubicFilter || window_type == CubicFilter )
1191 (void) FormatLocaleFile(stdout,"# B,C = %.*g,%.*g\n",
1192 GetMagickPrecision(),(double)B, GetMagickPrecision(),(double)C);
1193 (void) FormatLocaleFile(stdout,"\n");
1195 Output values of resulting filter graph -- for graphing filter result.
1197 for (x=0.0; x <= support; x+=0.01f)
1198 (void) FormatLocaleFile(stdout,"%5.2lf\t%.*g\n",x,
1199 GetMagickPrecision(),(double)
1200 GetResizeFilterWeight(resize_filter,x));
1202 A final value so gnuplot can graph the 'stop' properly.
1204 (void) FormatLocaleFile(stdout,"%5.2lf\t%.*g\n",support,
1205 GetMagickPrecision(),0.0);
1207 /* Output the above once only for each image - remove setting */
1208 (void) DeleteImageArtifact((Image *) image,"filter:verbose");
1209 #if defined(MAGICKCORE_OPENMP_SUPPORT)
1212 return(resize_filter);
1216 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1220 % A d a p t i v e R e s i z e I m a g e %
1224 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1226 % AdaptiveResizeImage() adaptively resize image with pixel resampling.
1228 % This is shortcut function for a fast interpolative resize using mesh
1229 % interpolation. It works well for small resizes of less than +/- 50%
1230 % of the original image size. For larger resizing on images a full
1231 % filtered and slower resize function should be used instead.
1233 % The format of the AdaptiveResizeImage method is:
1235 % Image *AdaptiveResizeImage(const Image *image,const size_t columns,
1236 % const size_t rows,ExceptionInfo *exception)
1238 % A description of each parameter follows:
1240 % o image: the image.
1242 % o columns: the number of columns in the resized image.
1244 % o rows: the number of rows in the resized image.
1246 % o exception: return any errors or warnings in this structure.
1249 MagickExport Image *AdaptiveResizeImage(const Image *image,
1250 const size_t columns,const size_t rows,ExceptionInfo *exception)
1255 resize_image=InterpolativeResizeImage(image,columns,rows,MeshInterpolatePixel,
1257 return(resize_image);
1261 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1265 + B e s s e l O r d e r O n e %
1269 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1271 % BesselOrderOne() computes the Bessel function of x of the first kind of
1272 % order 0. This is used to create the Jinc() filter function below.
1274 % Reduce x to |x| since j1(x)= -j1(-x), and for x in (0,8]
1280 % j1(x) = sqrt(2/(pi*x))*(p1(x)*cos(x1)-q1(x)*sin(x1))
1282 % where x1 = x-3*pi/4. Compute sin(x1) and cos(x1) as follow:
1284 % cos(x1) = cos(x)cos(3pi/4)+sin(x)sin(3pi/4)
1285 % = 1/sqrt(2) * (sin(x) - cos(x))
1286 % sin(x1) = sin(x)cos(3pi/4)-cos(x)sin(3pi/4)
1287 % = -1/sqrt(2) * (sin(x) + cos(x))
1289 % The format of the BesselOrderOne method is:
1291 % double BesselOrderOne(double x)
1293 % A description of each parameter follows:
1295 % o x: double value.
1300 static double I0(double x)
1311 Zeroth order Bessel function of the first kind.
1316 for (i=2; t > MagickEpsilon; i++)
1319 t*=y/((double) i*i);
1325 static double J1(double x)
1337 0.581199354001606143928050809e+21,
1338 -0.6672106568924916298020941484e+20,
1339 0.2316433580634002297931815435e+19,
1340 -0.3588817569910106050743641413e+17,
1341 0.2908795263834775409737601689e+15,
1342 -0.1322983480332126453125473247e+13,
1343 0.3413234182301700539091292655e+10,
1344 -0.4695753530642995859767162166e+7,
1345 0.270112271089232341485679099e+4
1349 0.11623987080032122878585294e+22,
1350 0.1185770712190320999837113348e+20,
1351 0.6092061398917521746105196863e+17,
1352 0.2081661221307607351240184229e+15,
1353 0.5243710262167649715406728642e+12,
1354 0.1013863514358673989967045588e+10,
1355 0.1501793594998585505921097578e+7,
1356 0.1606931573481487801970916749e+4,
1362 for (i=7; i >= 0; i--)
1371 static double P1(double x)
1383 0.352246649133679798341724373e+5,
1384 0.62758845247161281269005675e+5,
1385 0.313539631109159574238669888e+5,
1386 0.49854832060594338434500455e+4,
1387 0.2111529182853962382105718e+3,
1388 0.12571716929145341558495e+1
1392 0.352246649133679798068390431e+5,
1393 0.626943469593560511888833731e+5,
1394 0.312404063819041039923015703e+5,
1395 0.4930396490181088979386097e+4,
1396 0.2030775189134759322293574e+3,
1402 for (i=4; i >= 0; i--)
1404 p=p*(8.0/x)*(8.0/x)+Pone[i];
1405 q=q*(8.0/x)*(8.0/x)+Qone[i];
1411 static double Q1(double x)
1423 0.3511751914303552822533318e+3,
1424 0.7210391804904475039280863e+3,
1425 0.4259873011654442389886993e+3,
1426 0.831898957673850827325226e+2,
1427 0.45681716295512267064405e+1,
1428 0.3532840052740123642735e-1
1432 0.74917374171809127714519505e+4,
1433 0.154141773392650970499848051e+5,
1434 0.91522317015169922705904727e+4,
1435 0.18111867005523513506724158e+4,
1436 0.1038187585462133728776636e+3,
1442 for (i=4; i >= 0; i--)
1444 p=p*(8.0/x)*(8.0/x)+Pone[i];
1445 q=q*(8.0/x)*(8.0/x)+Qone[i];
1450 static double BesselOrderOne(double x)
1463 q=sqrt((double) (2.0/(MagickPI*x)))*(P1(x)*(1.0/sqrt(2.0)*(sin((double) x)-
1464 cos((double) x)))-8.0/x*Q1(x)*(-1.0/sqrt(2.0)*(sin((double) x)+
1472 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1476 + D e s t r o y R e s i z e F i l t e r %
1480 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1482 % DestroyResizeFilter() destroy the resize filter.
1484 % The format of the DestroyResizeFilter method is:
1486 % ResizeFilter *DestroyResizeFilter(ResizeFilter *resize_filter)
1488 % A description of each parameter follows:
1490 % o resize_filter: the resize filter.
1493 MagickPrivate ResizeFilter *DestroyResizeFilter(ResizeFilter *resize_filter)
1495 assert(resize_filter != (ResizeFilter *) NULL);
1496 assert(resize_filter->signature == MagickCoreSignature);
1497 resize_filter->signature=(~MagickCoreSignature);
1498 resize_filter=(ResizeFilter *) RelinquishMagickMemory(resize_filter);
1499 return(resize_filter);
1503 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1507 + G e t R e s i z e F i l t e r S u p p o r t %
1511 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1513 % GetResizeFilterSupport() return the current support window size for this
1514 % filter. Note that this may have been enlarged by filter:blur factor.
1516 % The format of the GetResizeFilterSupport method is:
1518 % double GetResizeFilterSupport(const ResizeFilter *resize_filter)
1520 % A description of each parameter follows:
1522 % o filter: Image filter to use.
1526 MagickPrivate double *GetResizeFilterCoefficient(
1527 const ResizeFilter *resize_filter)
1529 assert(resize_filter != (ResizeFilter *) NULL);
1530 assert(resize_filter->signature == MagickCoreSignature);
1531 return((double *) resize_filter->coefficient);
1534 MagickPrivate double GetResizeFilterBlur(const ResizeFilter *resize_filter)
1536 assert(resize_filter != (ResizeFilter *) NULL);
1537 assert(resize_filter->signature == MagickCoreSignature);
1538 return(resize_filter->blur);
1541 MagickPrivate double GetResizeFilterScale(const ResizeFilter *resize_filter)
1543 assert(resize_filter != (ResizeFilter *) NULL);
1544 assert(resize_filter->signature == MagickCoreSignature);
1545 return(resize_filter->scale);
1548 MagickPrivate double GetResizeFilterWindowSupport(
1549 const ResizeFilter *resize_filter)
1551 assert(resize_filter != (ResizeFilter *) NULL);
1552 assert(resize_filter->signature == MagickCoreSignature);
1553 return(resize_filter->window_support);
1556 MagickPrivate ResizeWeightingFunctionType GetResizeFilterWeightingType(
1557 const ResizeFilter *resize_filter)
1559 assert(resize_filter != (ResizeFilter *) NULL);
1560 assert(resize_filter->signature == MagickCoreSignature);
1561 return(resize_filter->filterWeightingType);
1564 MagickPrivate ResizeWeightingFunctionType GetResizeFilterWindowWeightingType(
1565 const ResizeFilter *resize_filter)
1567 assert(resize_filter != (ResizeFilter *) NULL);
1568 assert(resize_filter->signature == MagickCoreSignature);
1569 return(resize_filter->windowWeightingType);
1572 MagickPrivate double GetResizeFilterSupport(const ResizeFilter *resize_filter)
1574 assert(resize_filter != (ResizeFilter *) NULL);
1575 assert(resize_filter->signature == MagickCoreSignature);
1576 return(resize_filter->support*resize_filter->blur);
1580 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1584 + G e t R e s i z e F i l t e r W e i g h t %
1588 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1590 % GetResizeFilterWeight evaluates the specified resize filter at the point x
1591 % which usally lies between zero and the filters current 'support' and
1592 % returns the weight of the filter function at that point.
1594 % The format of the GetResizeFilterWeight method is:
1596 % double GetResizeFilterWeight(const ResizeFilter *resize_filter,
1599 % A description of each parameter follows:
1601 % o filter: the filter type.
1606 MagickPrivate double GetResizeFilterWeight(const ResizeFilter *resize_filter,
1615 Windowing function - scale the weighting filter by this amount.
1617 assert(resize_filter != (ResizeFilter *) NULL);
1618 assert(resize_filter->signature == MagickCoreSignature);
1619 x_blur=fabs((double) x)/resize_filter->blur; /* X offset with blur scaling */
1620 if ((resize_filter->window_support < MagickEpsilon) ||
1621 (resize_filter->window == Box))
1622 scale=1.0; /* Point or Box Filter -- avoid division by zero */
1625 scale=resize_filter->scale;
1626 scale=resize_filter->window(x_blur*scale,resize_filter);
1628 weight=scale*resize_filter->filter(x_blur,resize_filter);
1633 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1637 % I n t e r p o l a t i v e R e s i z e I m a g e %
1641 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1643 % InterpolativeResizeImage() resizes an image using the specified
1644 % interpolation method.
1646 % The format of the InterpolativeResizeImage method is:
1648 % Image *InterpolativeResizeImage(const Image *image,const size_t columns,
1649 % const size_t rows,const PixelInterpolateMethod method,
1650 % ExceptionInfo *exception)
1652 % A description of each parameter follows:
1654 % o image: the image.
1656 % o columns: the number of columns in the resized image.
1658 % o rows: the number of rows in the resized image.
1660 % o method: the pixel interpolation method.
1662 % o exception: return any errors or warnings in this structure.
1665 MagickExport Image *InterpolativeResizeImage(const Image *image,
1666 const size_t columns,const size_t rows,const PixelInterpolateMethod method,
1667 ExceptionInfo *exception)
1669 #define InterpolativeResizeImageTag "Resize/Image"
1691 Interpolatively resize image.
1693 assert(image != (const Image *) NULL);
1694 assert(image->signature == MagickCoreSignature);
1695 if (image->debug != MagickFalse)
1696 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
1697 assert(exception != (ExceptionInfo *) NULL);
1698 assert(exception->signature == MagickCoreSignature);
1699 if ((columns == 0) || (rows == 0))
1700 ThrowImageException(ImageError,"NegativeOrZeroImageSize");
1701 if ((columns == image->columns) && (rows == image->rows))
1702 return(CloneImage(image,0,0,MagickTrue,exception));
1703 resize_image=CloneImage(image,columns,rows,MagickTrue,exception);
1704 if (resize_image == (Image *) NULL)
1705 return((Image *) NULL);
1706 if (SetImageStorageClass(resize_image,DirectClass,exception) == MagickFalse)
1708 resize_image=DestroyImage(resize_image);
1709 return((Image *) NULL);
1713 image_view=AcquireVirtualCacheView(image,exception);
1714 resize_view=AcquireAuthenticCacheView(resize_image,exception);
1715 scale.x=(double) image->columns/resize_image->columns;
1716 scale.y=(double) image->rows/resize_image->rows;
1717 #if defined(MAGICKCORE_OPENMP_SUPPORT)
1718 #pragma omp parallel for schedule(static,4) shared(progress,status) \
1719 magick_threads(image,resize_image,resize_image->rows,1)
1721 for (y=0; y < (ssize_t) resize_image->rows; y++)
1732 if (status == MagickFalse)
1734 q=QueueCacheViewAuthenticPixels(resize_view,0,y,resize_image->columns,1,
1736 if (q == (Quantum *) NULL)
1738 offset.y=((double) y+0.5)*scale.y-0.5;
1739 for (x=0; x < (ssize_t) resize_image->columns; x++)
1744 if (GetPixelWriteMask(resize_image,q) == 0)
1746 q+=GetPixelChannels(resize_image);
1749 for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
1758 channel=GetPixelChannelChannel(image,i);
1759 traits=GetPixelChannelTraits(image,channel);
1760 resize_traits=GetPixelChannelTraits(resize_image,channel);
1761 if ((traits == UndefinedPixelTrait) ||
1762 (resize_traits == UndefinedPixelTrait))
1764 offset.x=((double) x+0.5)*scale.x-0.5;
1765 status=InterpolatePixelChannels(image,image_view,resize_image,method,
1766 offset.x,offset.y,q,exception);
1768 q+=GetPixelChannels(resize_image);
1770 if (SyncCacheViewAuthenticPixels(resize_view,exception) == MagickFalse)
1772 if (image->progress_monitor != (MagickProgressMonitor) NULL)
1777 #if defined(MAGICKCORE_OPENMP_SUPPORT)
1778 #pragma omp critical (MagickCore_InterpolativeResizeImage)
1780 proceed=SetImageProgress(image,InterpolativeResizeImageTag,progress++,
1782 if (proceed == MagickFalse)
1786 resize_view=DestroyCacheView(resize_view);
1787 image_view=DestroyCacheView(image_view);
1788 if (status == MagickFalse)
1789 resize_image=DestroyImage(resize_image);
1790 return(resize_image);
1792 #if defined(MAGICKCORE_LQR_DELEGATE)
1795 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1799 % L i q u i d R e s c a l e I m a g e %
1803 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1805 % LiquidRescaleImage() rescales image with seam carving.
1807 % The format of the LiquidRescaleImage method is:
1809 % Image *LiquidRescaleImage(const Image *image,const size_t columns,
1810 % const size_t rows,const double delta_x,const double rigidity,
1811 % ExceptionInfo *exception)
1813 % A description of each parameter follows:
1815 % o image: the image.
1817 % o columns: the number of columns in the rescaled image.
1819 % o rows: the number of rows in the rescaled image.
1821 % o delta_x: maximum seam transversal step (0 means straight seams).
1823 % o rigidity: introduce a bias for non-straight seams (typically 0).
1825 % o exception: return any errors or warnings in this structure.
1828 MagickExport Image *LiquidRescaleImage(const Image *image,const size_t columns,
1829 const size_t rows,const double delta_x,const double rigidity,
1830 ExceptionInfo *exception)
1832 #define LiquidRescaleImageTag "Rescale/Image"
1868 Liquid rescale image.
1870 assert(image != (const Image *) NULL);
1871 assert(image->signature == MagickCoreSignature);
1872 if (image->debug != MagickFalse)
1873 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
1874 assert(exception != (ExceptionInfo *) NULL);
1875 assert(exception->signature == MagickCoreSignature);
1876 if ((columns == 0) || (rows == 0))
1877 ThrowImageException(ImageError,"NegativeOrZeroImageSize");
1878 if ((columns == image->columns) && (rows == image->rows))
1879 return(CloneImage(image,0,0,MagickTrue,exception));
1880 if ((columns <= 2) || (rows <= 2))
1881 return(ResizeImage(image,columns,rows,image->filter,exception));
1882 pixel_info=AcquireVirtualMemory(image->columns,image->rows*
1883 GetPixelChannels(image)*sizeof(*pixels));
1884 if (pixel_info == (MemoryInfo *) NULL)
1885 return((Image *) NULL);
1886 pixels=(gfloat *) GetVirtualMemoryBlob(pixel_info);
1889 image_view=AcquireVirtualCacheView(image,exception);
1890 for (y=0; y < (ssize_t) image->rows; y++)
1892 register const Quantum
1898 if (status == MagickFalse)
1900 p=GetCacheViewVirtualPixels(image_view,0,y,image->columns,1,exception);
1901 if (p == (const Quantum *) NULL)
1906 for (x=0; x < (ssize_t) image->columns; x++)
1911 for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
1912 *q++=QuantumScale*p[i];
1913 p+=GetPixelChannels(image);
1916 image_view=DestroyCacheView(image_view);
1917 carver=lqr_carver_new_ext(pixels,(int) image->columns,(int) image->rows,
1918 (int) GetPixelChannels(image),LQR_COLDEPTH_32F);
1919 if (carver == (LqrCarver *) NULL)
1921 pixel_info=RelinquishVirtualMemory(pixel_info);
1922 ThrowImageException(ResourceLimitError,"MemoryAllocationFailed");
1924 lqr_carver_set_preserve_input_image(carver);
1925 lqr_status=lqr_carver_init(carver,(int) delta_x,rigidity);
1926 lqr_status=lqr_carver_resize(carver,(int) columns,(int) rows);
1928 rescale_image=CloneImage(image,lqr_carver_get_width(carver),
1929 lqr_carver_get_height(carver),MagickTrue,exception);
1930 if (rescale_image == (Image *) NULL)
1932 pixel_info=RelinquishVirtualMemory(pixel_info);
1933 return((Image *) NULL);
1935 if (SetImageStorageClass(rescale_image,DirectClass,exception) == MagickFalse)
1937 pixel_info=RelinquishVirtualMemory(pixel_info);
1938 rescale_image=DestroyImage(rescale_image);
1939 return((Image *) NULL);
1941 rescale_view=AcquireAuthenticCacheView(rescale_image,exception);
1942 (void) lqr_carver_scan_reset(carver);
1943 while (lqr_carver_scan_ext(carver,&x_offset,&y_offset,(void **) &packet) != 0)
1951 q=QueueCacheViewAuthenticPixels(rescale_view,x_offset,y_offset,1,1,
1953 if (q == (Quantum *) NULL)
1955 for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
1964 channel=GetPixelChannelChannel(image,i);
1965 traits=GetPixelChannelTraits(image,channel);
1966 rescale_traits=GetPixelChannelTraits(rescale_image,channel);
1967 if ((traits == UndefinedPixelTrait) ||
1968 (rescale_traits == UndefinedPixelTrait))
1970 SetPixelChannel(rescale_image,channel,ClampToQuantum(QuantumRange*
1973 if (SyncCacheViewAuthenticPixels(rescale_view,exception) == MagickFalse)
1976 rescale_view=DestroyCacheView(rescale_view);
1977 pixel_info=RelinquishVirtualMemory(pixel_info);
1978 lqr_carver_destroy(carver);
1979 return(rescale_image);
1982 MagickExport Image *LiquidRescaleImage(const Image *image,
1983 const size_t magick_unused(columns),const size_t magick_unused(rows),
1984 const double magick_unused(delta_x),const double magick_unused(rigidity),
1985 ExceptionInfo *exception)
1987 assert(image != (const Image *) NULL);
1988 assert(image->signature == MagickCoreSignature);
1989 if (image->debug != MagickFalse)
1990 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
1991 assert(exception != (ExceptionInfo *) NULL);
1992 assert(exception->signature == MagickCoreSignature);
1993 (void) ThrowMagickException(exception,GetMagickModule(),MissingDelegateError,
1994 "DelegateLibrarySupportNotBuiltIn","'%s' (LQR)",image->filename);
1995 return((Image *) NULL);
2000 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2004 % M a g n i f y I m a g e %
2008 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2010 % MagnifyImage() doubles the size of the image with a pixel art scaling
2013 % The format of the MagnifyImage method is:
2015 % Image *MagnifyImage(const Image *image,ExceptionInfo *exception)
2017 % A description of each parameter follows:
2019 % o image: the image.
2021 % o exception: return any errors or warnings in this structure.
2024 MagickExport Image *MagnifyImage(const Image *image,ExceptionInfo *exception)
2026 #define MagnifyImageTag "Magnify/Image"
2045 Initialize magnified image attributes.
2047 assert(image != (const Image *) NULL);
2048 assert(image->signature == MagickCoreSignature);
2049 if (image->debug != MagickFalse)
2050 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
2051 assert(exception != (ExceptionInfo *) NULL);
2052 assert(exception->signature == MagickCoreSignature);
2053 magnify_image=CloneImage(image,2*image->columns,2*image->rows,MagickTrue,
2055 if (magnify_image == (Image *) NULL)
2056 return((Image *) NULL);
2062 image_view=AcquireVirtualCacheView(image,exception);
2063 magnify_view=AcquireAuthenticCacheView(magnify_image,exception);
2064 #if defined(MAGICKCORE_OPENMP_SUPPORT)
2065 #pragma omp parallel for schedule(static,4) shared(progress,status) \
2066 magick_threads(image,magnify_image,image->rows,1)
2068 for (y=0; y < (ssize_t) image->rows; y++)
2076 if (status == MagickFalse)
2078 q=QueueCacheViewAuthenticPixels(magnify_view,0,2*y,magnify_image->columns,2,
2080 if (q == (Quantum *) NULL)
2086 Magnify this row of pixels.
2088 for (x=0; x < (ssize_t) image->columns; x++)
2093 register const Quantum
2105 p=GetCacheViewVirtualPixels(image_view,x-1,y-1,3,3,exception);
2106 if (p == (const Quantum *) NULL)
2111 channels=GetPixelChannels(image);
2112 for (i=0; i < 9; i++)
2113 intensity[i]=GetPixelIntensity(image,p+i*channels);
2115 if ((fabs(intensity[1]-intensity[7]) < MagickEpsilon) ||
2116 (fabs(intensity[3]-intensity[5]) < MagickEpsilon))
2121 for (i=0; i < (ssize_t) channels; i++)
2122 r[i]=p[4*channels+i];
2123 r+=GetPixelChannels(magnify_image);
2124 for (i=0; i < (ssize_t) channels; i++)
2125 r[i]=p[4*channels+i];
2126 r+=GetPixelChannels(magnify_image)*(magnify_image->columns-1);
2127 for (i=0; i < (ssize_t) channels; i++)
2128 r[i]=p[4*channels+i];
2129 r+=GetPixelChannels(magnify_image);
2130 for (i=0; i < (ssize_t) channels; i++)
2131 r[i]=p[4*channels+i];
2136 Selectively clone pixel.
2138 if (fabs(intensity[1]-intensity[3]) < MagickEpsilon)
2139 for (i=0; i < (ssize_t) channels; i++)
2140 r[i]=p[3*channels+i];
2142 for (i=0; i < (ssize_t) channels; i++)
2143 r[i]=p[4*channels+i];
2144 r+=GetPixelChannels(magnify_image);
2145 if (fabs(intensity[1]-intensity[5]) < MagickEpsilon)
2146 for (i=0; i < (ssize_t) channels; i++)
2147 r[i]=p[5*channels+i];
2149 for (i=0; i < (ssize_t) channels; i++)
2150 r[i]=p[4*channels+i];
2151 r+=GetPixelChannels(magnify_image)*(magnify_image->columns-1);
2152 if (fabs(intensity[3]-intensity[7]) < MagickEpsilon)
2153 for (i=0; i < (ssize_t) channels; i++)
2154 r[i]=p[3*channels+i];
2156 for (i=0; i < (ssize_t) channels; i++)
2157 r[i]=p[4*channels+i];
2158 r+=GetPixelChannels(magnify_image);
2159 if (fabs(intensity[5]-intensity[7]) < MagickEpsilon)
2160 for (i=0; i < (ssize_t) channels; i++)
2161 r[i]=p[5*channels+i];
2163 for (i=0; i < (ssize_t) channels; i++)
2164 r[i]=p[4*channels+i];
2166 q+=2*GetPixelChannels(magnify_image);
2168 if (SyncCacheViewAuthenticPixels(magnify_view,exception) == MagickFalse)
2170 if (image->progress_monitor != (MagickProgressMonitor) NULL)
2175 #if defined(MAGICKCORE_OPENMP_SUPPORT)
2176 #pragma omp critical (MagickCore_MagnifyImage)
2178 proceed=SetImageProgress(image,MagnifyImageTag,progress++,image->rows);
2179 if (proceed == MagickFalse)
2183 magnify_view=DestroyCacheView(magnify_view);
2184 image_view=DestroyCacheView(image_view);
2185 if (status == MagickFalse)
2186 magnify_image=DestroyImage(magnify_image);
2187 return(magnify_image);
2191 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2195 % M i n i f y I m a g e %
2199 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2201 % MinifyImage() is a convenience method that scales an image proportionally to
2204 % The format of the MinifyImage method is:
2206 % Image *MinifyImage(const Image *image,ExceptionInfo *exception)
2208 % A description of each parameter follows:
2210 % o image: the image.
2212 % o exception: return any errors or warnings in this structure.
2215 MagickExport Image *MinifyImage(const Image *image,ExceptionInfo *exception)
2220 assert(image != (Image *) NULL);
2221 assert(image->signature == MagickCoreSignature);
2222 if (image->debug != MagickFalse)
2223 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
2224 assert(exception != (ExceptionInfo *) NULL);
2225 assert(exception->signature == MagickCoreSignature);
2226 minify_image=ResizeImage(image,image->columns/2,image->rows/2,SplineFilter,
2228 return(minify_image);
2232 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2236 % R e s a m p l e I m a g e %
2240 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2242 % ResampleImage() resize image in terms of its pixel size, so that when
2243 % displayed at the given resolution it will be the same size in terms of
2244 % real world units as the original image at the original resolution.
2246 % The format of the ResampleImage method is:
2248 % Image *ResampleImage(Image *image,const double x_resolution,
2249 % const double y_resolution,const FilterType filter,
2250 % ExceptionInfo *exception)
2252 % A description of each parameter follows:
2254 % o image: the image to be resized to fit the given resolution.
2256 % o x_resolution: the new image x resolution.
2258 % o y_resolution: the new image y resolution.
2260 % o filter: Image filter to use.
2262 % o exception: return any errors or warnings in this structure.
2265 MagickExport Image *ResampleImage(const Image *image,const double x_resolution,
2266 const double y_resolution,const FilterType filter,ExceptionInfo *exception)
2268 #define ResampleImageTag "Resample/Image"
2278 Initialize sampled image attributes.
2280 assert(image != (const Image *) NULL);
2281 assert(image->signature == MagickCoreSignature);
2282 if (image->debug != MagickFalse)
2283 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
2284 assert(exception != (ExceptionInfo *) NULL);
2285 assert(exception->signature == MagickCoreSignature);
2286 width=(size_t) (x_resolution*image->columns/(image->resolution.x == 0.0 ?
2287 72.0 : image->resolution.x)+0.5);
2288 height=(size_t) (y_resolution*image->rows/(image->resolution.y == 0.0 ?
2289 72.0 : image->resolution.y)+0.5);
2290 resample_image=ResizeImage(image,width,height,filter,exception);
2291 if (resample_image != (Image *) NULL)
2293 resample_image->resolution.x=x_resolution;
2294 resample_image->resolution.y=y_resolution;
2296 return(resample_image);
2300 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2304 % R e s i z e I m a g e %
2308 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2310 % ResizeImage() scales an image to the desired dimensions, using the given
2311 % filter (see AcquireFilterInfo()).
2313 % If an undefined filter is given the filter defaults to Mitchell for a
2314 % colormapped image, a image with a matte channel, or if the image is
2315 % enlarged. Otherwise the filter defaults to a Lanczos.
2317 % ResizeImage() was inspired by Paul Heckbert's "zoom" program.
2319 % The format of the ResizeImage method is:
2321 % Image *ResizeImage(Image *image,const size_t columns,const size_t rows,
2322 % const FilterType filter,ExceptionInfo *exception)
2324 % A description of each parameter follows:
2326 % o image: the image.
2328 % o columns: the number of columns in the scaled image.
2330 % o rows: the number of rows in the scaled image.
2332 % o filter: Image filter to use.
2334 % o exception: return any errors or warnings in this structure.
2338 typedef struct _ContributionInfo
2347 static ContributionInfo **DestroyContributionThreadSet(
2348 ContributionInfo **contribution)
2353 assert(contribution != (ContributionInfo **) NULL);
2354 for (i=0; i < (ssize_t) GetMagickResourceLimit(ThreadResource); i++)
2355 if (contribution[i] != (ContributionInfo *) NULL)
2356 contribution[i]=(ContributionInfo *) RelinquishAlignedMemory(
2358 contribution=(ContributionInfo **) RelinquishMagickMemory(contribution);
2359 return(contribution);
2362 static ContributionInfo **AcquireContributionThreadSet(const size_t count)
2373 number_threads=(size_t) GetMagickResourceLimit(ThreadResource);
2374 contribution=(ContributionInfo **) AcquireQuantumMemory(number_threads,
2375 sizeof(*contribution));
2376 if (contribution == (ContributionInfo **) NULL)
2377 return((ContributionInfo **) NULL);
2378 (void) ResetMagickMemory(contribution,0,number_threads*sizeof(*contribution));
2379 for (i=0; i < (ssize_t) number_threads; i++)
2381 contribution[i]=(ContributionInfo *) MagickAssumeAligned(
2382 AcquireAlignedMemory(count,sizeof(**contribution)));
2383 if (contribution[i] == (ContributionInfo *) NULL)
2384 return(DestroyContributionThreadSet(contribution));
2386 return(contribution);
2389 static MagickBooleanType HorizontalFilter(const ResizeFilter *resize_filter,
2390 const Image *image,Image *resize_image,const double x_factor,
2391 const MagickSizeType span,MagickOffsetType *offset,ExceptionInfo *exception)
2393 #define ResizeImageTag "Resize/Image"
2403 **magick_restrict contributions;
2416 Apply filter to resize horizontally from image to resize image.
2418 scale=MagickMax(1.0/x_factor+MagickEpsilon,1.0);
2419 support=scale*GetResizeFilterSupport(resize_filter);
2420 storage_class=support > 0.5 ? DirectClass : image->storage_class;
2421 if (SetImageStorageClass(resize_image,storage_class,exception) == MagickFalse)
2422 return(MagickFalse);
2426 Support too small even for nearest neighbour: Reduce to point sampling.
2428 support=(double) 0.5;
2431 contributions=AcquireContributionThreadSet((size_t) (2.0*support+3.0));
2432 if (contributions == (ContributionInfo **) NULL)
2434 (void) ThrowMagickException(exception,GetMagickModule(),
2435 ResourceLimitError,"MemoryAllocationFailed","`%s'",image->filename);
2436 return(MagickFalse);
2439 scale=PerceptibleReciprocal(scale);
2440 image_view=AcquireVirtualCacheView(image,exception);
2441 resize_view=AcquireAuthenticCacheView(resize_image,exception);
2442 #if defined(MAGICKCORE_OPENMP_SUPPORT)
2443 #pragma omp parallel for schedule(static,4) shared(status) \
2444 magick_threads(image,resize_image,resize_image->columns,1)
2446 for (x=0; x < (ssize_t) resize_image->columns; x++)
2449 id = GetOpenMPThreadId();
2455 register const Quantum
2458 register ContributionInfo
2459 *magick_restrict contribution;
2472 if (status == MagickFalse)
2474 bisect=(double) (x+0.5)/x_factor+MagickEpsilon;
2475 start=(ssize_t) MagickMax(bisect-support+0.5,0.0);
2476 stop=(ssize_t) MagickMin(bisect+support+0.5,(double) image->columns);
2478 contribution=contributions[id];
2479 for (n=0; n < (stop-start); n++)
2481 contribution[n].pixel=start+n;
2482 contribution[n].weight=GetResizeFilterWeight(resize_filter,scale*
2483 ((double) (start+n)-bisect+0.5));
2484 density+=contribution[n].weight;
2488 if ((density != 0.0) && (density != 1.0))
2496 density=PerceptibleReciprocal(density);
2497 for (i=0; i < n; i++)
2498 contribution[i].weight*=density;
2500 p=GetCacheViewVirtualPixels(image_view,contribution[0].pixel,0,(size_t)
2501 (contribution[n-1].pixel-contribution[0].pixel+1),image->rows,exception);
2502 q=QueueCacheViewAuthenticPixels(resize_view,x,0,1,resize_image->rows,
2504 if ((p == (const Quantum *) NULL) || (q == (Quantum *) NULL))
2509 for (y=0; y < (ssize_t) resize_image->rows; y++)
2514 for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
2534 channel=GetPixelChannelChannel(image,i);
2535 traits=GetPixelChannelTraits(image,channel);
2536 resize_traits=GetPixelChannelTraits(resize_image,channel);
2537 if ((traits == UndefinedPixelTrait) ||
2538 (resize_traits == UndefinedPixelTrait))
2540 if (((resize_traits & CopyPixelTrait) != 0) ||
2541 (GetPixelWriteMask(resize_image,q) == 0))
2543 j=(ssize_t) (MagickMin(MagickMax(bisect,(double) start),(double)
2545 k=y*(contribution[n-1].pixel-contribution[0].pixel+1)+
2546 (contribution[j-start].pixel-contribution[0].pixel);
2547 SetPixelChannel(resize_image,channel,p[k*GetPixelChannels(image)+i],
2552 if ((resize_traits & BlendPixelTrait) == 0)
2557 for (j=0; j < n; j++)
2559 k=y*(contribution[n-1].pixel-contribution[0].pixel+1)+
2560 (contribution[j].pixel-contribution[0].pixel);
2561 alpha=contribution[j].weight;
2562 pixel+=alpha*p[k*GetPixelChannels(image)+i];
2564 SetPixelChannel(resize_image,channel,ClampToQuantum(pixel),q);
2571 for (j=0; j < n; j++)
2573 k=y*(contribution[n-1].pixel-contribution[0].pixel+1)+
2574 (contribution[j].pixel-contribution[0].pixel);
2575 alpha=contribution[j].weight*QuantumScale*
2576 GetPixelAlpha(image,p+k*GetPixelChannels(image));
2577 pixel+=alpha*p[k*GetPixelChannels(image)+i];
2580 gamma=PerceptibleReciprocal(gamma);
2581 SetPixelChannel(resize_image,channel,ClampToQuantum(gamma*pixel),q);
2583 q+=GetPixelChannels(resize_image);
2585 if (SyncCacheViewAuthenticPixels(resize_view,exception) == MagickFalse)
2587 if (image->progress_monitor != (MagickProgressMonitor) NULL)
2592 #if defined(MAGICKCORE_OPENMP_SUPPORT)
2593 #pragma omp critical (MagickCore_HorizontalFilter)
2595 proceed=SetImageProgress(image,ResizeImageTag,(*offset)++,span);
2596 if (proceed == MagickFalse)
2600 resize_view=DestroyCacheView(resize_view);
2601 image_view=DestroyCacheView(image_view);
2602 contributions=DestroyContributionThreadSet(contributions);
2606 static MagickBooleanType VerticalFilter(const ResizeFilter *resize_filter,
2607 const Image *image,Image *resize_image,const double y_factor,
2608 const MagickSizeType span,MagickOffsetType *offset,ExceptionInfo *exception)
2618 **magick_restrict contributions;
2631 Apply filter to resize vertically from image to resize image.
2633 scale=MagickMax(1.0/y_factor+MagickEpsilon,1.0);
2634 support=scale*GetResizeFilterSupport(resize_filter);
2635 storage_class=support > 0.5 ? DirectClass : image->storage_class;
2636 if (SetImageStorageClass(resize_image,storage_class,exception) == MagickFalse)
2637 return(MagickFalse);
2641 Support too small even for nearest neighbour: Reduce to point sampling.
2643 support=(double) 0.5;
2646 contributions=AcquireContributionThreadSet((size_t) (2.0*support+3.0));
2647 if (contributions == (ContributionInfo **) NULL)
2649 (void) ThrowMagickException(exception,GetMagickModule(),
2650 ResourceLimitError,"MemoryAllocationFailed","`%s'",image->filename);
2651 return(MagickFalse);
2654 scale=PerceptibleReciprocal(scale);
2655 image_view=AcquireVirtualCacheView(image,exception);
2656 resize_view=AcquireAuthenticCacheView(resize_image,exception);
2657 #if defined(MAGICKCORE_OPENMP_SUPPORT)
2658 #pragma omp parallel for schedule(static,4) shared(status) \
2659 magick_threads(image,resize_image,resize_image->rows,1)
2661 for (y=0; y < (ssize_t) resize_image->rows; y++)
2664 id = GetOpenMPThreadId();
2670 register const Quantum
2673 register ContributionInfo
2674 *magick_restrict contribution;
2687 if (status == MagickFalse)
2689 bisect=(double) (y+0.5)/y_factor+MagickEpsilon;
2690 start=(ssize_t) MagickMax(bisect-support+0.5,0.0);
2691 stop=(ssize_t) MagickMin(bisect+support+0.5,(double) image->rows);
2693 contribution=contributions[id];
2694 for (n=0; n < (stop-start); n++)
2696 contribution[n].pixel=start+n;
2697 contribution[n].weight=GetResizeFilterWeight(resize_filter,scale*
2698 ((double) (start+n)-bisect+0.5));
2699 density+=contribution[n].weight;
2703 if ((density != 0.0) && (density != 1.0))
2711 density=PerceptibleReciprocal(density);
2712 for (i=0; i < n; i++)
2713 contribution[i].weight*=density;
2715 p=GetCacheViewVirtualPixels(image_view,0,contribution[0].pixel,
2716 image->columns,(size_t) (contribution[n-1].pixel-contribution[0].pixel+1),
2718 q=QueueCacheViewAuthenticPixels(resize_view,0,y,resize_image->columns,1,
2720 if ((p == (const Quantum *) NULL) || (q == (Quantum *) NULL))
2725 for (x=0; x < (ssize_t) resize_image->columns; x++)
2730 for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
2750 channel=GetPixelChannelChannel(image,i);
2751 traits=GetPixelChannelTraits(image,channel);
2752 resize_traits=GetPixelChannelTraits(resize_image,channel);
2753 if ((traits == UndefinedPixelTrait) ||
2754 (resize_traits == UndefinedPixelTrait))
2756 if (((resize_traits & CopyPixelTrait) != 0) ||
2757 (GetPixelWriteMask(resize_image,q) == 0))
2759 j=(ssize_t) (MagickMin(MagickMax(bisect,(double) start),(double)
2761 k=(ssize_t) ((contribution[j-start].pixel-contribution[0].pixel)*
2763 SetPixelChannel(resize_image,channel,p[k*GetPixelChannels(image)+i],
2768 if ((resize_traits & BlendPixelTrait) == 0)
2773 for (j=0; j < n; j++)
2775 k=(ssize_t) ((contribution[j].pixel-contribution[0].pixel)*
2777 alpha=contribution[j].weight;
2778 pixel+=alpha*p[k*GetPixelChannels(image)+i];
2780 SetPixelChannel(resize_image,channel,ClampToQuantum(pixel),q);
2784 for (j=0; j < n; j++)
2786 k=(ssize_t) ((contribution[j].pixel-contribution[0].pixel)*
2788 alpha=contribution[j].weight*QuantumScale*GetPixelAlpha(image,p+k*
2789 GetPixelChannels(image));
2790 pixel+=alpha*p[k*GetPixelChannels(image)+i];
2793 gamma=PerceptibleReciprocal(gamma);
2794 SetPixelChannel(resize_image,channel,ClampToQuantum(gamma*pixel),q);
2796 q+=GetPixelChannels(resize_image);
2798 if (SyncCacheViewAuthenticPixels(resize_view,exception) == MagickFalse)
2800 if (image->progress_monitor != (MagickProgressMonitor) NULL)
2805 #if defined(MAGICKCORE_OPENMP_SUPPORT)
2806 #pragma omp critical (MagickCore_VerticalFilter)
2808 proceed=SetImageProgress(image,ResizeImageTag,(*offset)++,span);
2809 if (proceed == MagickFalse)
2813 resize_view=DestroyCacheView(resize_view);
2814 image_view=DestroyCacheView(image_view);
2815 contributions=DestroyContributionThreadSet(contributions);
2819 MagickExport Image *ResizeImage(const Image *image,const size_t columns,
2820 const size_t rows,const FilterType filter,ExceptionInfo *exception)
2846 Acquire resize image.
2848 assert(image != (Image *) NULL);
2849 assert(image->signature == MagickCoreSignature);
2850 if (image->debug != MagickFalse)
2851 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
2852 assert(exception != (ExceptionInfo *) NULL);
2853 assert(exception->signature == MagickCoreSignature);
2854 if ((columns == 0) || (rows == 0))
2855 ThrowImageException(ImageError,"NegativeOrZeroImageSize");
2856 if ((columns == image->columns) && (rows == image->rows) &&
2857 (filter == UndefinedFilter))
2858 return(CloneImage(image,0,0,MagickTrue,exception));
2860 Acquire resize filter.
2862 x_factor=(double) columns/(double) image->columns;
2863 y_factor=(double) rows/(double) image->rows;
2864 filter_type=LanczosFilter;
2865 if (filter != UndefinedFilter)
2868 if ((x_factor == 1.0) && (y_factor == 1.0))
2869 filter_type=PointFilter;
2871 if ((image->storage_class == PseudoClass) ||
2872 (image->alpha_trait != UndefinedPixelTrait) ||
2873 ((x_factor*y_factor) > 1.0))
2874 filter_type=MitchellFilter;
2875 resize_filter=AcquireResizeFilter(image,filter_type,MagickFalse,exception);
2876 #if defined(MAGICKCORE_OPENCL_SUPPORT)
2877 resize_image=AccelerateResizeImage(image,columns,rows,resize_filter,
2879 if (resize_image != (Image *) NULL)
2881 resize_filter=DestroyResizeFilter(resize_filter);
2882 return(resize_image);
2885 resize_image=CloneImage(image,columns,rows,MagickTrue,exception);
2886 if (resize_image == (Image *) NULL)
2888 resize_filter=DestroyResizeFilter(resize_filter);
2889 return(resize_image);
2891 if (x_factor > y_factor)
2892 filter_image=CloneImage(image,columns,image->rows,MagickTrue,exception);
2894 filter_image=CloneImage(image,image->columns,rows,MagickTrue,exception);
2895 if (filter_image == (Image *) NULL)
2897 resize_filter=DestroyResizeFilter(resize_filter);
2898 return(DestroyImage(resize_image));
2904 if (x_factor > y_factor)
2906 span=(MagickSizeType) (filter_image->columns+rows);
2907 status=HorizontalFilter(resize_filter,image,filter_image,x_factor,span,
2909 status&=VerticalFilter(resize_filter,filter_image,resize_image,y_factor,
2910 span,&offset,exception);
2914 span=(MagickSizeType) (filter_image->rows+columns);
2915 status=VerticalFilter(resize_filter,image,filter_image,y_factor,span,
2917 status&=HorizontalFilter(resize_filter,filter_image,resize_image,x_factor,
2918 span,&offset,exception);
2923 filter_image=DestroyImage(filter_image);
2924 resize_filter=DestroyResizeFilter(resize_filter);
2925 if (status == MagickFalse)
2927 resize_image=DestroyImage(resize_image);
2928 return((Image *) NULL);
2930 resize_image->type=image->type;
2931 return(resize_image);
2935 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2939 % S a m p l e I m a g e %
2943 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2945 % SampleImage() scales an image to the desired dimensions with pixel
2946 % sampling. Unlike other scaling methods, this method does not introduce
2947 % any additional color into the scaled image.
2949 % The format of the SampleImage method is:
2951 % Image *SampleImage(const Image *image,const size_t columns,
2952 % const size_t rows,ExceptionInfo *exception)
2954 % A description of each parameter follows:
2956 % o image: the image.
2958 % o columns: the number of columns in the sampled image.
2960 % o rows: the number of rows in the sampled image.
2962 % o exception: return any errors or warnings in this structure.
2965 MagickExport Image *SampleImage(const Image *image,const size_t columns,
2966 const size_t rows,ExceptionInfo *exception)
2968 #define SampleImageTag "Sample/Image"
2994 Initialize sampled image attributes.
2996 assert(image != (const Image *) NULL);
2997 assert(image->signature == MagickCoreSignature);
2998 if (image->debug != MagickFalse)
2999 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
3000 assert(exception != (ExceptionInfo *) NULL);
3001 assert(exception->signature == MagickCoreSignature);
3002 if ((columns == 0) || (rows == 0))
3003 ThrowImageException(ImageError,"NegativeOrZeroImageSize");
3004 if ((columns == image->columns) && (rows == image->rows))
3005 return(CloneImage(image,0,0,MagickTrue,exception));
3006 sample_image=CloneImage(image,columns,rows,MagickTrue,exception);
3007 if (sample_image == (Image *) NULL)
3008 return((Image *) NULL);
3010 Set the sampling offset, default is in the mid-point of sample regions.
3012 sample_offset.x=sample_offset.y=0.5-MagickEpsilon;
3017 value=GetImageArtifact(image,"sample:offset");
3018 if (value != (char *) NULL)
3026 (void) ParseGeometry(value,&geometry_info);
3027 flags=ParseGeometry(value,&geometry_info);
3028 sample_offset.x=sample_offset.y=geometry_info.rho/100.0-MagickEpsilon;
3029 if ((flags & SigmaValue) != 0)
3030 sample_offset.y=geometry_info.sigma/100.0-MagickEpsilon;
3034 Allocate scan line buffer and column offset buffers.
3036 x_offset=(ssize_t *) AcquireQuantumMemory((size_t) sample_image->columns,
3038 if (x_offset == (ssize_t *) NULL)
3040 sample_image=DestroyImage(sample_image);
3041 ThrowImageException(ResourceLimitError,"MemoryAllocationFailed");
3043 for (x=0; x < (ssize_t) sample_image->columns; x++)
3044 x_offset[x]=(ssize_t) ((((double) x+sample_offset.x)*image->columns)/
3045 sample_image->columns);
3051 image_view=AcquireVirtualCacheView(image,exception);
3052 sample_view=AcquireAuthenticCacheView(sample_image,exception);
3053 #if defined(MAGICKCORE_OPENMP_SUPPORT)
3054 #pragma omp parallel for schedule(static,4) shared(status) \
3055 magick_threads(image,sample_image,1,1)
3057 for (y=0; y < (ssize_t) sample_image->rows; y++)
3059 register const Quantum
3071 if (status == MagickFalse)
3073 y_offset=(ssize_t) ((((double) y+sample_offset.y)*image->rows)/
3074 sample_image->rows);
3075 p=GetCacheViewVirtualPixels(image_view,0,y_offset,image->columns,1,
3077 q=QueueCacheViewAuthenticPixels(sample_view,0,y,sample_image->columns,1,
3079 if ((p == (const Quantum *) NULL) || (q == (Quantum *) NULL))
3087 for (x=0; x < (ssize_t) sample_image->columns; x++)
3092 if (GetPixelWriteMask(sample_image,q) == 0)
3094 q+=GetPixelChannels(sample_image);
3097 for (i=0; i < (ssize_t) GetPixelChannels(sample_image); i++)
3106 channel=GetPixelChannelChannel(image,i);
3107 traits=GetPixelChannelTraits(image,channel);
3108 sample_traits=GetPixelChannelTraits(sample_image,channel);
3109 if ((traits == UndefinedPixelTrait) ||
3110 (sample_traits == UndefinedPixelTrait))
3112 SetPixelChannel(sample_image,channel,p[x_offset[x]*GetPixelChannels(
3115 q+=GetPixelChannels(sample_image);
3117 if (SyncCacheViewAuthenticPixels(sample_view,exception) == MagickFalse)
3119 if (image->progress_monitor != (MagickProgressMonitor) NULL)
3124 #if defined(MAGICKCORE_OPENMP_SUPPORT)
3125 #pragma omp critical (MagickCore_SampleImage)
3127 proceed=SetImageProgress(image,SampleImageTag,progress++,image->rows);
3128 if (proceed == MagickFalse)
3132 image_view=DestroyCacheView(image_view);
3133 sample_view=DestroyCacheView(sample_view);
3134 x_offset=(ssize_t *) RelinquishMagickMemory(x_offset);
3135 sample_image->type=image->type;
3136 if (status == MagickFalse)
3137 sample_image=DestroyImage(sample_image);
3138 return(sample_image);
3142 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3146 % S c a l e I m a g e %
3150 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3152 % ScaleImage() changes the size of an image to the given dimensions.
3154 % The format of the ScaleImage method is:
3156 % Image *ScaleImage(const Image *image,const size_t columns,
3157 % const size_t rows,ExceptionInfo *exception)
3159 % A description of each parameter follows:
3161 % o image: the image.
3163 % o columns: the number of columns in the scaled image.
3165 % o rows: the number of rows in the scaled image.
3167 % o exception: return any errors or warnings in this structure.
3170 MagickExport Image *ScaleImage(const Image *image,const size_t columns,
3171 const size_t rows,ExceptionInfo *exception)
3173 #define ScaleImageTag "Scale/Image"
3181 pixel[CompositePixelChannel],
3216 Initialize scaled image attributes.
3218 assert(image != (const Image *) NULL);
3219 assert(image->signature == MagickCoreSignature);
3220 if (image->debug != MagickFalse)
3221 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
3222 assert(exception != (ExceptionInfo *) NULL);
3223 assert(exception->signature == MagickCoreSignature);
3224 if ((columns == 0) || (rows == 0))
3225 ThrowImageException(ImageError,"NegativeOrZeroImageSize");
3226 if ((columns == image->columns) && (rows == image->rows))
3227 return(CloneImage(image,0,0,MagickTrue,exception));
3228 scale_image=CloneImage(image,columns,rows,MagickTrue,exception);
3229 if (scale_image == (Image *) NULL)
3230 return((Image *) NULL);
3231 if (SetImageStorageClass(scale_image,DirectClass,exception) == MagickFalse)
3233 scale_image=DestroyImage(scale_image);
3234 return((Image *) NULL);
3239 x_vector=(double *) AcquireQuantumMemory((size_t) image->columns,
3240 GetPixelChannels(image)*sizeof(*x_vector));
3242 if (image->rows != scale_image->rows)
3243 scanline=(double *) AcquireQuantumMemory((size_t) image->columns,
3244 GetPixelChannels(image)*sizeof(*scanline));
3245 scale_scanline=(double *) AcquireQuantumMemory((size_t)
3246 scale_image->columns,GetPixelChannels(image)*sizeof(*scale_scanline));
3247 y_vector=(double *) AcquireQuantumMemory((size_t) image->columns,
3248 GetPixelChannels(image)*sizeof(*y_vector));
3249 if ((scanline == (double *) NULL) ||
3250 (scale_scanline == (double *) NULL) ||
3251 (x_vector == (double *) NULL) ||
3252 (y_vector == (double *) NULL))
3254 scale_image=DestroyImage(scale_image);
3255 ThrowImageException(ResourceLimitError,"MemoryAllocationFailed");
3261 next_row=MagickTrue;
3263 scale.y=(double) scale_image->rows/(double) image->rows;
3264 (void) ResetMagickMemory(y_vector,0,(size_t) GetPixelChannels(image)*
3265 image->columns*sizeof(*y_vector));
3268 image_view=AcquireVirtualCacheView(image,exception);
3269 scale_view=AcquireAuthenticCacheView(scale_image,exception);
3270 for (y=0; y < (ssize_t) scale_image->rows; y++)
3272 register const Quantum
3281 if (status == MagickFalse)
3283 q=QueueCacheViewAuthenticPixels(scale_view,0,y,scale_image->columns,1,
3285 if (q == (Quantum *) NULL)
3291 if (scale_image->rows == image->rows)
3294 Read a new scanline.
3296 p=GetCacheViewVirtualPixels(image_view,0,n++,image->columns,1,
3298 if (p == (const Quantum *) NULL)
3303 for (x=0; x < (ssize_t) image->columns; x++)
3305 if (GetPixelWriteMask(image,p) == 0)
3307 p+=GetPixelChannels(image);
3310 if (image->alpha_trait != UndefinedPixelTrait)
3311 alpha=QuantumScale*GetPixelAlpha(image,p);
3312 for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
3314 PixelChannel channel=GetPixelChannelChannel(image,i);
3315 PixelTrait traits=GetPixelChannelTraits(image,channel);
3316 if ((traits & BlendPixelTrait) == 0)
3318 x_vector[x*GetPixelChannels(image)+i]=(double) p[i];
3321 x_vector[x*GetPixelChannels(image)+i]=alpha*p[i];
3323 p+=GetPixelChannels(image);
3331 while (scale.y < span.y)
3333 if ((next_row != MagickFalse) &&
3334 (number_rows < (ssize_t) image->rows))
3337 Read a new scanline.
3339 p=GetCacheViewVirtualPixels(image_view,0,n++,image->columns,1,
3341 if (p == (const Quantum *) NULL)
3346 for (x=0; x < (ssize_t) image->columns; x++)
3348 if (GetPixelWriteMask(image,p) == 0)
3350 p+=GetPixelChannels(image);
3353 if (image->alpha_trait != UndefinedPixelTrait)
3354 alpha=QuantumScale*GetPixelAlpha(image,p);
3355 for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
3357 PixelChannel channel=GetPixelChannelChannel(image,i);
3358 PixelTrait traits=GetPixelChannelTraits(image,channel);
3359 if ((traits & BlendPixelTrait) == 0)
3361 x_vector[x*GetPixelChannels(image)+i]=(double) p[i];
3364 x_vector[x*GetPixelChannels(image)+i]=alpha*p[i];
3366 p+=GetPixelChannels(image);
3370 for (x=0; x < (ssize_t) image->columns; x++)
3371 for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
3372 y_vector[x*GetPixelChannels(image)+i]+=scale.y*
3373 x_vector[x*GetPixelChannels(image)+i];
3375 scale.y=(double) scale_image->rows/(double) image->rows;
3376 next_row=MagickTrue;
3378 if ((next_row != MagickFalse) && (number_rows < (ssize_t) image->rows))
3381 Read a new scanline.
3383 p=GetCacheViewVirtualPixels(image_view,0,n++,image->columns,1,
3385 if (p == (const Quantum *) NULL)
3390 for (x=0; x < (ssize_t) image->columns; x++)
3392 if (GetPixelWriteMask(image,p) == 0)
3394 p+=GetPixelChannels(image);
3397 if (image->alpha_trait != UndefinedPixelTrait)
3398 alpha=QuantumScale*GetPixelAlpha(image,p);
3399 for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
3401 PixelChannel channel=GetPixelChannelChannel(image,i);
3402 PixelTrait traits=GetPixelChannelTraits(image,channel);
3403 if ((traits & BlendPixelTrait) == 0)
3405 x_vector[x*GetPixelChannels(image)+i]=(double) p[i];
3408 x_vector[x*GetPixelChannels(image)+i]=alpha*p[i];
3410 p+=GetPixelChannels(image);
3413 next_row=MagickFalse;
3415 for (x=0; x < (ssize_t) image->columns; x++)
3417 for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
3419 pixel[i]=y_vector[x*GetPixelChannels(image)+i]+span.y*
3420 x_vector[x*GetPixelChannels(image)+i];
3421 scanline[x*GetPixelChannels(image)+i]=pixel[i];
3422 y_vector[x*GetPixelChannels(image)+i]=0.0;
3428 scale.y=(double) scale_image->rows/(double) image->rows;
3429 next_row=MagickTrue;
3433 if (scale_image->columns == image->columns)
3436 Transfer scanline to scaled image.
3438 for (x=0; x < (ssize_t) scale_image->columns; x++)
3440 if (GetPixelWriteMask(scale_image,q) == 0)
3442 q+=GetPixelChannels(scale_image);
3445 if (image->alpha_trait != UndefinedPixelTrait)
3447 alpha=QuantumScale*scanline[x*GetPixelChannels(image)+
3448 GetPixelChannelOffset(image,AlphaPixelChannel)];
3449 alpha=PerceptibleReciprocal(alpha);
3451 for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
3453 channel=GetPixelChannelChannel(image,i);
3454 traits=GetPixelChannelTraits(image,channel);
3455 scale_traits=GetPixelChannelTraits(scale_image,channel);
3456 if ((traits == UndefinedPixelTrait) ||
3457 (scale_traits == UndefinedPixelTrait))
3459 if ((traits & BlendPixelTrait) == 0)
3461 SetPixelChannel(scale_image,channel,ClampToQuantum(
3462 scanline[x*GetPixelChannels(image)+i]),q);
3465 SetPixelChannel(scale_image,channel,ClampToQuantum(alpha*scanline[
3466 x*GetPixelChannels(image)+i]),q);
3468 q+=GetPixelChannels(scale_image);
3479 for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
3481 next_column=MagickFalse;
3484 for (x=0; x < (ssize_t) image->columns; x++)
3486 scale.x=(double) scale_image->columns/(double) image->columns;
3487 while (scale.x >= span.x)
3489 if (next_column != MagickFalse)
3491 for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
3495 for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
3497 PixelChannel channel=GetPixelChannelChannel(image,i);
3498 PixelTrait traits=GetPixelChannelTraits(image,channel);
3499 if (traits == UndefinedPixelTrait)
3501 pixel[i]+=span.x*scanline[x*GetPixelChannels(image)+i];
3502 scale_scanline[t*GetPixelChannels(image)+i]=pixel[i];
3506 next_column=MagickTrue;
3510 if (next_column != MagickFalse)
3512 for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
3514 next_column=MagickFalse;
3517 for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
3518 pixel[i]+=scale.x*scanline[x*GetPixelChannels(image)+i];
3524 for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
3525 pixel[i]+=span.x*scanline[(x-1)*GetPixelChannels(image)+i];
3527 if ((next_column == MagickFalse) &&
3528 (t < (ssize_t) scale_image->columns))
3529 for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
3530 scale_scanline[t*GetPixelChannels(image)+i]=pixel[i];
3532 Transfer scanline to scaled image.
3534 for (x=0; x < (ssize_t) scale_image->columns; x++)
3536 if (GetPixelWriteMask(scale_image,q) == 0)
3538 q+=GetPixelChannels(scale_image);
3541 if (image->alpha_trait != UndefinedPixelTrait)
3543 alpha=QuantumScale*scale_scanline[x*GetPixelChannels(image)+
3544 GetPixelChannelOffset(image,AlphaPixelChannel)];
3545 alpha=PerceptibleReciprocal(alpha);
3547 for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
3549 PixelChannel channel=GetPixelChannelChannel(image,i);
3550 PixelTrait traits=GetPixelChannelTraits(image,channel);
3551 scale_traits=GetPixelChannelTraits(scale_image,channel);
3552 if ((traits == UndefinedPixelTrait) ||
3553 (scale_traits == UndefinedPixelTrait))
3555 if ((traits & BlendPixelTrait) == 0)
3557 SetPixelChannel(scale_image,channel,ClampToQuantum(
3558 scale_scanline[x*GetPixelChannels(image)+i]),q);
3561 SetPixelChannel(scale_image,channel,ClampToQuantum(alpha*
3562 scale_scanline[x*GetPixelChannels(image)+i]),q);
3564 q+=GetPixelChannels(scale_image);
3567 if (SyncCacheViewAuthenticPixels(scale_view,exception) == MagickFalse)
3572 proceed=SetImageProgress(image,ScaleImageTag,(MagickOffsetType) y,
3574 if (proceed == MagickFalse)
3580 scale_view=DestroyCacheView(scale_view);
3581 image_view=DestroyCacheView(image_view);
3583 Free allocated memory.
3585 y_vector=(double *) RelinquishMagickMemory(y_vector);
3586 scale_scanline=(double *) RelinquishMagickMemory(scale_scanline);
3587 if (scale_image->rows != image->rows)
3588 scanline=(double *) RelinquishMagickMemory(scanline);
3589 x_vector=(double *) RelinquishMagickMemory(x_vector);
3590 scale_image->type=image->type;
3591 if (status == MagickFalse)
3592 scale_image=DestroyImage(scale_image);
3593 return(scale_image);
3597 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3601 % T h u m b n a i l I m a g e %
3605 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3607 % ThumbnailImage() changes the size of an image to the given dimensions and
3608 % removes any associated profiles. The goal is to produce small low cost
3609 % thumbnail images suited for display on the Web.
3611 % The format of the ThumbnailImage method is:
3613 % Image *ThumbnailImage(const Image *image,const size_t columns,
3614 % const size_t rows,ExceptionInfo *exception)
3616 % A description of each parameter follows:
3618 % o image: the image.
3620 % o columns: the number of columns in the scaled image.
3622 % o rows: the number of rows in the scaled image.
3624 % o exception: return any errors or warnings in this structure.
3627 MagickExport Image *ThumbnailImage(const Image *image,const size_t columns,
3628 const size_t rows,ExceptionInfo *exception)
3630 #define SampleFactor 5
3634 value[MagickPathExtent];
3649 assert(image != (Image *) NULL);
3650 assert(image->signature == MagickCoreSignature);
3651 if (image->debug != MagickFalse)
3652 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
3653 assert(exception != (ExceptionInfo *) NULL);
3654 assert(exception->signature == MagickCoreSignature);
3655 x_factor=(double) columns/(double) image->columns;
3656 y_factor=(double) rows/(double) image->rows;
3657 if ((x_factor*y_factor) > 0.1)
3658 thumbnail_image=ResizeImage(image,columns,rows,image->filter,exception);
3660 if (((SampleFactor*columns) < 128) || ((SampleFactor*rows) < 128))
3661 thumbnail_image=ResizeImage(image,columns,rows,image->filter,exception);
3667 sample_image=SampleImage(image,SampleFactor*columns,SampleFactor*rows,
3669 if (sample_image == (Image *) NULL)
3670 return((Image *) NULL);
3671 thumbnail_image=ResizeImage(sample_image,columns,rows,image->filter,
3673 sample_image=DestroyImage(sample_image);
3675 if (thumbnail_image == (Image *) NULL)
3676 return(thumbnail_image);
3677 (void) ParseAbsoluteGeometry("0x0+0+0",&thumbnail_image->page);
3678 if (thumbnail_image->alpha_trait == UndefinedPixelTrait)
3679 (void) SetImageAlphaChannel(thumbnail_image,OpaqueAlphaChannel,exception);
3680 thumbnail_image->depth=8;
3681 thumbnail_image->interlace=NoInterlace;
3683 Strip all profiles except color profiles.
3685 ResetImageProfileIterator(thumbnail_image);
3686 for (name=GetNextImageProfile(thumbnail_image); name != (const char *) NULL; )
3688 if ((LocaleCompare(name,"icc") != 0) && (LocaleCompare(name,"icm") != 0))
3690 (void) DeleteImageProfile(thumbnail_image,name);
3691 ResetImageProfileIterator(thumbnail_image);
3693 name=GetNextImageProfile(thumbnail_image);
3695 (void) DeleteImageProperty(thumbnail_image,"comment");
3696 (void) CopyMagickString(value,image->magick_filename,MagickPathExtent);
3697 if (strstr(image->magick_filename,"//") == (char *) NULL)
3698 (void) FormatLocaleString(value,MagickPathExtent,"file://%s",
3699 image->magick_filename);
3700 (void) SetImageProperty(thumbnail_image,"Thumb::URI",value,exception);
3701 (void) CopyMagickString(value,image->magick_filename,MagickPathExtent);
3702 if ( GetPathAttributes(image->filename,&attributes) != MagickFalse )
3704 (void) FormatLocaleString(value,MagickPathExtent,"%.20g",(double)
3705 attributes.st_mtime);
3706 (void) SetImageProperty(thumbnail_image,"Thumb::MTime",value,exception);
3708 (void) FormatLocaleString(value,MagickPathExtent,"%.20g",(double)
3709 attributes.st_mtime);
3710 (void) FormatMagickSize(GetBlobSize(image),MagickFalse,"B",MagickPathExtent,
3712 (void) SetImageProperty(thumbnail_image,"Thumb::Size",value,exception);
3713 (void) FormatLocaleString(value,MagickPathExtent,"image/%s",image->magick);
3715 (void) SetImageProperty(thumbnail_image,"Thumb::Mimetype",value,exception);
3716 url=GetMagickHomeURL();
3717 (void) SetImageProperty(thumbnail_image,"software",url,exception);
3718 url=DestroyString(url);
3719 (void) FormatLocaleString(value,MagickPathExtent,"%.20g",(double)
3720 image->magick_columns);
3721 (void) SetImageProperty(thumbnail_image,"Thumb::Image::Width",value,
3723 (void) FormatLocaleString(value,MagickPathExtent,"%.20g",(double)
3724 image->magick_rows);
3725 (void) SetImageProperty(thumbnail_image,"Thumb::Image::Height",value,
3727 (void) FormatLocaleString(value,MagickPathExtent,"%.20g",(double)
3728 GetImageListLength(image));
3729 (void) SetImageProperty(thumbnail_image,"Thumb::Document::Pages",value,
3731 return(thumbnail_image);