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