MagickCore 7.1.1-39
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 magick_unreferenced(columns);
2026 magick_unreferenced(rows);
2027 magick_unreferenced(delta_x);
2028 magick_unreferenced(rigidity);
2029 if (IsEventLogging() != MagickFalse)
2030 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
2031 (void) ThrowMagickException(exception,GetMagickModule(),MissingDelegateError,
2032 "DelegateLibrarySupportNotBuiltIn","'%s' (LQR)",image->filename);
2033 return((Image *) NULL);
2034}
2035#endif
2036
2037/*
2038%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2039% %
2040% %
2041% %
2042% M a g n i f y I m a g e %
2043% %
2044% %
2045% %
2046%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2047%
2048% MagnifyImage() doubles the size of the image with a pixel art scaling
2049% algorithm.
2050%
2051% The format of the MagnifyImage method is:
2052%
2053% Image *MagnifyImage(const Image *image,ExceptionInfo *exception)
2054%
2055% A description of each parameter follows:
2056%
2057% o image: the image.
2058%
2059% o exception: return any errors or warnings in this structure.
2060%
2061*/
2062
2063static inline void CopyPixels(const Quantum *source,const ssize_t source_offset,
2064 Quantum *destination,const ssize_t destination_offset,const size_t channels)
2065{
2066 ssize_t
2067 i;
2068
2069 for (i=0; i < (ssize_t) channels; i++)
2070 destination[(ssize_t) channels*destination_offset+i]=
2071 source[source_offset*(ssize_t) channels+i];
2072}
2073
2074static inline void MixPixels(const Quantum *source,const ssize_t *source_offset,
2075 const size_t source_size,Quantum *destination,
2076 const ssize_t destination_offset,const size_t channels)
2077{
2078 ssize_t
2079 i;
2080
2081 for (i=0; i < (ssize_t) channels; i++)
2082 {
2083 ssize_t
2084 j,
2085 sum = 0;
2086
2087 for (j=0; j < (ssize_t) source_size; j++)
2088 sum+=source[source_offset[j]*(ssize_t) channels+i];
2089 destination[(ssize_t) channels*destination_offset+i]=(Quantum) (sum/
2090 (ssize_t) source_size);
2091 }
2092}
2093
2094static inline void Mix2Pixels(const Quantum *source,
2095 const ssize_t source_offset1,const ssize_t source_offset2,
2096 Quantum *destination,const ssize_t destination_offset,const size_t channels)
2097{
2098 const ssize_t
2099 offsets[2] = { source_offset1, source_offset2 };
2100
2101 MixPixels(source,offsets,2,destination,destination_offset,channels);
2102}
2103
2104static inline int PixelsEqual(const Quantum *source1,ssize_t offset1,
2105 const Quantum *source2,ssize_t offset2,const size_t channels)
2106{
2107 ssize_t
2108 i;
2109
2110 offset1*=(ssize_t) channels;
2111 offset2*=(ssize_t) channels;
2112 for (i=0; i < (ssize_t) channels; i++)
2113 if (source1[offset1+i] != source2[offset2+i])
2114 return(0);
2115 return(1);
2116}
2117
2118static inline void Eagle2X(const Image *source,const Quantum *pixels,
2119 Quantum *result,const size_t channels)
2120{
2121 ssize_t
2122 i;
2123
2124 (void) source;
2125 for (i=0; i < 4; i++)
2126 CopyPixels(pixels,4,result,i,channels);
2127 if (PixelsEqual(pixels,0,pixels,1,channels) &&
2128 PixelsEqual(pixels,1,pixels,3,channels))
2129 CopyPixels(pixels,0,result,0,channels);
2130 if (PixelsEqual(pixels,1,pixels,2,channels) &&
2131 PixelsEqual(pixels,2,pixels,5,channels))
2132 CopyPixels(pixels,2,result,1,channels);
2133 if (PixelsEqual(pixels,3,pixels,6,channels) &&
2134 PixelsEqual(pixels,6,pixels,7,channels))
2135 CopyPixels(pixels,6,result,2,channels);
2136 if (PixelsEqual(pixels,5,pixels,8,channels) &&
2137 PixelsEqual(pixels,8,pixels,7,channels))
2138 CopyPixels(pixels,8,result,3,channels);
2139}
2140
2141static void Hq2XHelper(const unsigned int rule,const Quantum *source,
2142 Quantum *destination,const ssize_t destination_offset,const size_t channels,
2143 const ssize_t e,const ssize_t a,const ssize_t b,const ssize_t d,
2144 const ssize_t f,const ssize_t h)
2145{
2146#define caseA(N,A,B,C,D) \
2147 case N: \
2148 { \
2149 const ssize_t \
2150 offsets[4] = { A, B, C, D }; \
2151 \
2152 MixPixels(source,offsets,4,destination,destination_offset,channels);\
2153 break; \
2154 }
2155#define caseB(N,A,B,C,D,E,F,G,H) \
2156 case N: \
2157 { \
2158 const ssize_t \
2159 offsets[8] = { A, B, C, D, E, F, G, H }; \
2160 \
2161 MixPixels(source,offsets,8,destination,destination_offset,channels);\
2162 break; \
2163 }
2164
2165 switch (rule)
2166 {
2167 case 0:
2168 {
2169 CopyPixels(source,e,destination,destination_offset,channels);
2170 break;
2171 }
2172 caseA(1,e,e,e,a)
2173 caseA(2,e,e,e,d)
2174 caseA(3,e,e,e,b)
2175 caseA(4,e,e,d,b)
2176 caseA(5,e,e,a,b)
2177 caseA(6,e,e,a,d)
2178 caseB(7,e,e,e,e,e,b,b,d)
2179 caseB(8,e,e,e,e,e,d,d,b)
2180 caseB(9,e,e,e,e,e,e,d,b)
2181 caseB(10,e,e,d,d,d,b,b,b)
2182 case 11:
2183 {
2184 const ssize_t
2185 offsets[16] = { e, e, e, e, e, e, e, e, e, e, e, e, e, e, d, b };
2186
2187 MixPixels(source,offsets,16,destination,destination_offset,channels);
2188 break;
2189 }
2190 case 12:
2191 {
2192 if (PixelsEqual(source,b,source,d,channels))
2193 {
2194 const ssize_t
2195 offsets[4] = { e, e, d, b };
2196
2197 MixPixels(source,offsets,4,destination,destination_offset,channels);
2198 }
2199 else
2200 CopyPixels(source,e,destination,destination_offset,channels);
2201 break;
2202 }
2203 case 13:
2204 {
2205 if (PixelsEqual(source,b,source,d,channels))
2206 {
2207 const ssize_t
2208 offsets[8] = { e, e, d, d, d, b, b, b };
2209
2210 MixPixels(source,offsets,8,destination,destination_offset,channels);
2211 }
2212 else
2213 CopyPixels(source,e,destination,destination_offset,channels);
2214 break;
2215 }
2216 case 14:
2217 {
2218 if (PixelsEqual(source,b,source,d,channels))
2219 {
2220 const ssize_t
2221 offsets[16] = { e, e, e, e, e, e, e, e, e, e, e, e, e, e, d, b };
2222
2223 MixPixels(source,offsets,16,destination,destination_offset,channels);
2224 }
2225 else
2226 CopyPixels(source,e,destination,destination_offset,channels);
2227 break;
2228 }
2229 case 15:
2230 {
2231 if (PixelsEqual(source,b,source,d,channels))
2232 {
2233 const ssize_t
2234 offsets[4] = { e, e, d, b };
2235
2236 MixPixels(source,offsets,4,destination,destination_offset,channels);
2237 }
2238 else
2239 {
2240 const ssize_t
2241 offsets[4] = { e, e, e, a };
2242
2243 MixPixels(source,offsets,4,destination,destination_offset,channels);
2244 }
2245 break;
2246 }
2247 case 16:
2248 {
2249 if (PixelsEqual(source,b,source,d,channels))
2250 {
2251 const ssize_t
2252 offsets[8] = { e, e, e, e, e, e, d, b };
2253
2254 MixPixels(source,offsets,8,destination,destination_offset,channels);
2255 }
2256 else
2257 {
2258 const ssize_t
2259 offsets[4] = { e, e, e, a };
2260
2261 MixPixels(source,offsets,4,destination,destination_offset,channels);
2262 }
2263 break;
2264 }
2265 case 17:
2266 {
2267 if (PixelsEqual(source,b,source,d,channels))
2268 {
2269 const ssize_t
2270 offsets[8] = { e, e, d, d, d, b, b, b };
2271
2272 MixPixels(source,offsets,8,destination,destination_offset,channels);
2273 }
2274 else
2275 {
2276 const ssize_t
2277 offsets[4] = { e, e, e, a };
2278
2279 MixPixels(source,offsets,4,destination,destination_offset,channels);
2280 }
2281 break;
2282 }
2283 case 18:
2284 {
2285 if (PixelsEqual(source,b,source,f,channels))
2286 {
2287 const ssize_t
2288 offsets[8] = { e, e, e, e, e, b, b, d };
2289
2290 MixPixels(source,offsets,8,destination,destination_offset,channels);
2291 }
2292 else
2293 {
2294 const ssize_t
2295 offsets[4] = { e, e, e, d };
2296
2297 MixPixels(source,offsets,4,destination,destination_offset,channels);
2298 }
2299 break;
2300 }
2301 default:
2302 {
2303 if (PixelsEqual(source,d,source,h,channels))
2304 {
2305 const ssize_t
2306 offsets[8] = { e, e, e, e, e, d, d, b };
2307
2308 MixPixels(source,offsets,8,destination,destination_offset,channels);
2309 }
2310 else
2311 {
2312 const ssize_t
2313 offsets[4] = { e, e, e, b };
2314
2315 MixPixels(source,offsets,4,destination,destination_offset,channels);
2316 }
2317 break;
2318 }
2319 }
2320 #undef caseA
2321 #undef caseB
2322}
2323
2324static inline unsigned int Hq2XPatternToNumber(const int *pattern)
2325{
2326 ssize_t
2327 i;
2328
2329 unsigned int
2330 result,
2331 order;
2332
2333 result=0;
2334 order=1;
2335 for (i=7; i >= 0; i--)
2336 {
2337 result+=order*(unsigned int) pattern[i];
2338 order*=2;
2339 }
2340 return(result);
2341}
2342
2343static inline void Hq2X(const Image *source,const Quantum *pixels,
2344 Quantum *result,const size_t channels)
2345{
2346 static const unsigned int
2347 Hq2XTable[] =
2348 {
2349 4, 4, 6, 2, 4, 4, 6, 2, 5, 3, 15, 12, 5, 3, 17, 13,
2350 4, 4, 6, 18, 4, 4, 6, 18, 5, 3, 12, 12, 5, 3, 1, 12,
2351 4, 4, 6, 2, 4, 4, 6, 2, 5, 3, 17, 13, 5, 3, 16, 14,
2352 4, 4, 6, 18, 4, 4, 6, 18, 5, 3, 16, 12, 5, 3, 1, 14,
2353 4, 4, 6, 2, 4, 4, 6, 2, 5, 19, 12, 12, 5, 19, 16, 12,
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, 19, 1, 12, 5, 19, 1, 14,
2356 4, 4, 6, 2, 4, 4, 6, 18, 5, 3, 16, 12, 5, 19, 1, 14,
2357 4, 4, 6, 2, 4, 4, 6, 2, 5, 3, 15, 12, 5, 3, 17, 13,
2358 4, 4, 6, 2, 4, 4, 6, 2, 5, 3, 16, 12, 5, 3, 16, 12,
2359 4, 4, 6, 2, 4, 4, 6, 2, 5, 3, 17, 13, 5, 3, 16, 14,
2360 4, 4, 6, 2, 4, 4, 6, 2, 5, 3, 16, 13, 5, 3, 1, 14,
2361 4, 4, 6, 2, 4, 4, 6, 2, 5, 3, 16, 12, 5, 3, 16, 13,
2362 4, 4, 6, 2, 4, 4, 6, 2, 5, 3, 16, 12, 5, 3, 1, 12,
2363 4, 4, 6, 2, 4, 4, 6, 2, 5, 3, 16, 12, 5, 3, 1, 14,
2364 4, 4, 6, 2, 4, 4, 6, 2, 5, 3, 1, 12, 5, 3, 1, 14
2365 };
2366
2367 const int
2368 pattern1[] =
2369 {
2370 !PixelsEqual(pixels,4,pixels,8,channels),
2371 !PixelsEqual(pixels,4,pixels,7,channels),
2372 !PixelsEqual(pixels,4,pixels,6,channels),
2373 !PixelsEqual(pixels,4,pixels,5,channels),
2374 !PixelsEqual(pixels,4,pixels,3,channels),
2375 !PixelsEqual(pixels,4,pixels,2,channels),
2376 !PixelsEqual(pixels,4,pixels,1,channels),
2377 !PixelsEqual(pixels,4,pixels,0,channels)
2378 };
2379
2380#define Rotated(p) p[2], p[4], p[7], p[1], p[6], p[0], p[3], p[5]
2381 const int pattern2[] = { Rotated(pattern1) };
2382 const int pattern3[] = { Rotated(pattern2) };
2383 const int pattern4[] = { Rotated(pattern3) };
2384#undef Rotated
2385 (void) source;
2386 Hq2XHelper(Hq2XTable[Hq2XPatternToNumber(pattern1)],pixels,result,0,
2387 channels,4,0,1,3,5,7);
2388 Hq2XHelper(Hq2XTable[Hq2XPatternToNumber(pattern2)],pixels,result,1,
2389 channels,4,2,5,1,7,3);
2390 Hq2XHelper(Hq2XTable[Hq2XPatternToNumber(pattern3)],pixels,result,3,
2391 channels,4,8,7,5,3,1);
2392 Hq2XHelper(Hq2XTable[Hq2XPatternToNumber(pattern4)],pixels,result,2,
2393 channels,4,6,3,7,1,5);
2394}
2395
2396static void Fish2X(const Image *source,const Quantum *pixels,Quantum *result,
2397 const size_t channels)
2398{
2399#define Corner(A,B,C,D) \
2400 { \
2401 if (intensities[B] > intensities[A]) \
2402 { \
2403 const ssize_t \
2404 offsets[3] = { B, C, D }; \
2405 \
2406 MixPixels(pixels,offsets,3,result,3,channels); \
2407 } \
2408 else \
2409 { \
2410 const ssize_t \
2411 offsets[3] = { A, B, C }; \
2412 \
2413 MixPixels(pixels,offsets,3,result,3,channels); \
2414 } \
2415 }
2416
2417#define Line(A,B,C,D) \
2418 { \
2419 if (intensities[C] > intensities[A]) \
2420 Mix2Pixels(pixels,C,D,result,3,channels); \
2421 else \
2422 Mix2Pixels(pixels,A,B,result,3,channels); \
2423 }
2424
2425 const ssize_t
2426 pixels_offsets[4] = { 0, 1, 3, 4 };
2427
2428 int
2429 ab,
2430 ad,
2431 ae,
2432 bd,
2433 be,
2434 de;
2435
2436 MagickFloatType
2437 intensities[9];
2438
2439 ssize_t
2440 i;
2441
2442 for (i=0; i < 9; i++)
2443 intensities[i]=GetPixelIntensity(source,pixels+i*(ssize_t) channels);
2444 CopyPixels(pixels,0,result,0,channels);
2445 CopyPixels(pixels,(ssize_t) (intensities[0] > intensities[1] ? 0 : 1),result,
2446 1,channels);
2447 CopyPixels(pixels,(ssize_t) (intensities[0] > intensities[3] ? 0 : 3),result,
2448 2,channels);
2449 ae=PixelsEqual(pixels,0,pixels,4,channels);
2450 bd=PixelsEqual(pixels,1,pixels,3,channels);
2451 ab=PixelsEqual(pixels,0,pixels,1,channels);
2452 de=PixelsEqual(pixels,3,pixels,4,channels);
2453 ad=PixelsEqual(pixels,0,pixels,3,channels);
2454 be=PixelsEqual(pixels,1,pixels,4,channels);
2455 if (ae && bd && ab)
2456 {
2457 CopyPixels(pixels,0,result,3,channels);
2458 return;
2459 }
2460 if (ad && de && !ab)
2461 {
2462 Corner(1,0,4,3)
2463 return;
2464 }
2465 if (be && de && !ab)
2466 {
2467 Corner(0,1,3,4)
2468 return;
2469 }
2470 if (ad && ab && !be)
2471 {
2472 Corner(4,3,1,0)
2473 return;
2474 }
2475 if (ab && be && !ad)
2476 {
2477 Corner(3,0,4,1)
2478 return;
2479 }
2480 if (ae && (!bd || intensities[1] > intensities[0]))
2481 {
2482 Mix2Pixels(pixels,0,4,result,3,channels);
2483 return;
2484 }
2485 if (bd && (!ae || intensities[0] > intensities[1]))
2486 {
2487 Mix2Pixels(pixels,1,3,result,3,channels);
2488 return;
2489 }
2490 if (ab)
2491 {
2492 Line(0,1,3,4)
2493 return;
2494 }
2495 if (de)
2496 {
2497 Line(3,4,0,1)
2498 return;
2499 }
2500 if (ad)
2501 {
2502 Line(0,3,1,4)
2503 return;
2504 }
2505 if (be)
2506 {
2507 Line(1,4,0,3)
2508 return;
2509 }
2510 MixPixels(pixels,pixels_offsets,4,result,3,channels);
2511#undef Corner
2512#undef Line
2513}
2514
2515static void Xbr2X(const Image *magick_unused(source),const Quantum *pixels,
2516 Quantum *result,const size_t channels)
2517{
2518#define WeightVar(M,N) const int w_##M##_##N = \
2519 PixelsEqual(pixels,M,pixels,N,channels) ? 0 : 1;
2520
2521 WeightVar(12,11)
2522 WeightVar(12,7)
2523 WeightVar(12,13)
2524 WeightVar(12,17)
2525 WeightVar(12,16)
2526 WeightVar(12,8)
2527 WeightVar(6,10)
2528 WeightVar(6,2)
2529 WeightVar(11,7)
2530 WeightVar(11,17)
2531 WeightVar(11,5)
2532 WeightVar(7,13)
2533 WeightVar(7,1)
2534 WeightVar(12,6)
2535 WeightVar(12,18)
2536 WeightVar(8,14)
2537 WeightVar(8,2)
2538 WeightVar(13,17)
2539 WeightVar(13,9)
2540 WeightVar(7,3)
2541 WeightVar(16,10)
2542 WeightVar(16,22)
2543 WeightVar(17,21)
2544 WeightVar(11,15)
2545 WeightVar(18,14)
2546 WeightVar(18,22)
2547 WeightVar(17,23)
2548 WeightVar(17,19)
2549#undef WeightVar
2550
2551 magick_unreferenced(source);
2552
2553 if (
2554 w_12_16 + w_12_8 + w_6_10 + w_6_2 + (4 * w_11_7) <
2555 w_11_17 + w_11_5 + w_7_13 + w_7_1 + (4 * w_12_6)
2556 )
2557 Mix2Pixels(pixels,(ssize_t) (w_12_11 <= w_12_7 ? 11 : 7),12,result,0,
2558 channels);
2559 else
2560 CopyPixels(pixels,12,result,0,channels);
2561 if (
2562 w_12_18 + w_12_6 + w_8_14 + w_8_2 + (4 * w_7_13) <
2563 w_13_17 + w_13_9 + w_11_7 + w_7_3 + (4 * w_12_8)
2564 )
2565 Mix2Pixels(pixels,(ssize_t) (w_12_7 <= w_12_13 ? 7 : 13),12,result,1,
2566 channels);
2567 else
2568 CopyPixels(pixels,12,result,1,channels);
2569 if (
2570 w_12_6 + w_12_18 + w_16_10 + w_16_22 + (4 * w_11_17) <
2571 w_11_7 + w_11_15 + w_13_17 + w_17_21 + (4 * w_12_16)
2572 )
2573 Mix2Pixels(pixels,(ssize_t) (w_12_11 <= w_12_17 ? 11 : 17),12,result,2,
2574 channels);
2575 else
2576 CopyPixels(pixels,12,result,2,channels);
2577 if (
2578 w_12_8 + w_12_16 + w_18_14 + w_18_22 + (4 * w_13_17) <
2579 w_11_17 + w_17_23 + w_17_19 + w_7_13 + (4 * w_12_18)
2580 )
2581 Mix2Pixels(pixels,(ssize_t) (w_12_13 <= w_12_17 ? 13 : 17),12,result,3,
2582 channels);
2583 else
2584 CopyPixels(pixels,12,result,3,channels);
2585}
2586
2587static void Scale2X(const Image *magick_unused(source),const Quantum *pixels,
2588 Quantum *result,const size_t channels)
2589{
2590 magick_unreferenced(source);
2591
2592 if (PixelsEqual(pixels,1,pixels,7,channels) ||
2593 PixelsEqual(pixels,3,pixels,5,channels))
2594 {
2595 ssize_t
2596 i;
2597
2598 for (i=0; i < 4; i++)
2599 CopyPixels(pixels,4,result,i,channels);
2600 return;
2601 }
2602 if (PixelsEqual(pixels,1,pixels,3,channels))
2603 CopyPixels(pixels,3,result,0,channels);
2604 else
2605 CopyPixels(pixels,4,result,0,channels);
2606 if (PixelsEqual(pixels,1,pixels,5,channels))
2607 CopyPixels(pixels,5,result,1,channels);
2608 else
2609 CopyPixels(pixels,4,result,1,channels);
2610 if (PixelsEqual(pixels,3,pixels,7,channels))
2611 CopyPixels(pixels,3,result,2,channels);
2612 else
2613 CopyPixels(pixels,4,result,2,channels);
2614 if (PixelsEqual(pixels,5,pixels,7,channels))
2615 CopyPixels(pixels,5,result,3,channels);
2616 else
2617 CopyPixels(pixels,4,result,3,channels);
2618}
2619
2620static void Epbx2X(const Image *magick_unused(source),const Quantum *pixels,
2621 Quantum *result,const size_t channels)
2622{
2623#define HelperCond(a,b,c,d,e,f,g) ( \
2624 PixelsEqual(pixels,a,pixels,b,channels) && ( \
2625 PixelsEqual(pixels,c,pixels,d,channels) || \
2626 PixelsEqual(pixels,c,pixels,e,channels) || \
2627 PixelsEqual(pixels,a,pixels,f,channels) || \
2628 PixelsEqual(pixels,b,pixels,g,channels) \
2629 ) \
2630 )
2631
2632 ssize_t
2633 i;
2634
2635 magick_unreferenced(source);
2636
2637 for (i=0; i < 4; i++)
2638 CopyPixels(pixels,4,result,i,channels);
2639 if (
2640 !PixelsEqual(pixels,3,pixels,5,channels) &&
2641 !PixelsEqual(pixels,1,pixels,7,channels) &&
2642 (
2643 PixelsEqual(pixels,4,pixels,3,channels) ||
2644 PixelsEqual(pixels,4,pixels,7,channels) ||
2645 PixelsEqual(pixels,4,pixels,5,channels) ||
2646 PixelsEqual(pixels,4,pixels,1,channels) ||
2647 (
2648 (
2649 !PixelsEqual(pixels,0,pixels,8,channels) ||
2650 PixelsEqual(pixels,4,pixels,6,channels) ||
2651 PixelsEqual(pixels,3,pixels,2,channels)
2652 ) &&
2653 (
2654 !PixelsEqual(pixels,6,pixels,2,channels) ||
2655 PixelsEqual(pixels,4,pixels,0,channels) ||
2656 PixelsEqual(pixels,4,pixels,8,channels)
2657 )
2658 )
2659 )
2660 )
2661 {
2662 if (HelperCond(1,3,4,0,8,2,6))
2663 Mix2Pixels(pixels,1,3,result,0,channels);
2664 if (HelperCond(5,1,4,2,6,8,0))
2665 Mix2Pixels(pixels,5,1,result,1,channels);
2666 if (HelperCond(3,7,4,6,2,0,8))
2667 Mix2Pixels(pixels,3,7,result,2,channels);
2668 if (HelperCond(7,5,4,8,0,6,2))
2669 Mix2Pixels(pixels,7,5,result,3,channels);
2670 }
2671
2672#undef HelperCond
2673}
2674
2675static inline void Eagle3X(const Image *magick_unused(source),
2676 const Quantum *pixels,Quantum *result,const size_t channels)
2677{
2678 ssize_t
2679 corner_tl,
2680 corner_tr,
2681 corner_bl,
2682 corner_br;
2683
2684 magick_unreferenced(source);
2685
2686 corner_tl=PixelsEqual(pixels,0,pixels,1,channels) &&
2687 PixelsEqual(pixels,0,pixels,3,channels);
2688 corner_tr=PixelsEqual(pixels,1,pixels,2,channels) &&
2689 PixelsEqual(pixels,2,pixels,5,channels);
2690 corner_bl=PixelsEqual(pixels,3,pixels,6,channels) &&
2691 PixelsEqual(pixels,6,pixels,7,channels);
2692 corner_br=PixelsEqual(pixels,5,pixels,7,channels) &&
2693 PixelsEqual(pixels,7,pixels,8,channels);
2694 CopyPixels(pixels,(ssize_t) (corner_tl ? 0 : 4),result,0,channels);
2695 if (corner_tl && corner_tr)
2696 Mix2Pixels(pixels,0,2,result,1,channels);
2697 else
2698 CopyPixels(pixels,4,result,1,channels);
2699 CopyPixels(pixels,(ssize_t) (corner_tr ? 1 : 4),result,2,channels);
2700 if (corner_tl && corner_bl)
2701 Mix2Pixels(pixels,0,6,result,3,channels);
2702 else
2703 CopyPixels(pixels,4,result,3,channels);
2704 CopyPixels(pixels,4,result,4,channels);
2705 if (corner_tr && corner_br)
2706 Mix2Pixels(pixels,2,8,result,5,channels);
2707 else
2708 CopyPixels(pixels,4,result,5,channels);
2709 CopyPixels(pixels,(ssize_t) (corner_bl ? 3 : 4),result,6,channels);
2710 if (corner_bl && corner_br)
2711 Mix2Pixels(pixels,6,8,result,7,channels);
2712 else
2713 CopyPixels(pixels,4,result,7,channels);
2714 CopyPixels(pixels,(ssize_t) (corner_br ? 5 : 4),result,8,channels);
2715}
2716
2717static inline void Eagle3XB(const Image *magick_unused(source),
2718 const Quantum *pixels,Quantum *result,const size_t channels)
2719{
2720 ssize_t
2721 corner_tl,
2722 corner_tr,
2723 corner_bl,
2724 corner_br;
2725
2726 magick_unreferenced(source);
2727
2728 corner_tl=PixelsEqual(pixels,0,pixels,1,channels) &&
2729 PixelsEqual(pixels,0,pixels,3,channels);
2730 corner_tr=PixelsEqual(pixels,1,pixels,2,channels) &&
2731 PixelsEqual(pixels,2,pixels,5,channels);
2732 corner_bl=PixelsEqual(pixels,3,pixels,6,channels) &&
2733 PixelsEqual(pixels,6,pixels,7,channels);
2734 corner_br=PixelsEqual(pixels,5,pixels,7,channels) &&
2735 PixelsEqual(pixels,7,pixels,8,channels);
2736 CopyPixels(pixels,(ssize_t) (corner_tl ? 0 : 4),result,0,channels);
2737 CopyPixels(pixels,4,result,1,channels);
2738 CopyPixels(pixels,(ssize_t) (corner_tr ? 1 : 4),result,2,channels);
2739 CopyPixels(pixels,4,result,3,channels);
2740 CopyPixels(pixels,4,result,4,channels);
2741 CopyPixels(pixels,4,result,5,channels);
2742 CopyPixels(pixels,(ssize_t) (corner_bl ? 3 : 4),result,6,channels);
2743 CopyPixels(pixels,4,result,7,channels);
2744 CopyPixels(pixels,(ssize_t) (corner_br ? 5 : 4),result,8,channels);
2745}
2746
2747static inline void Scale3X(const Image *magick_unused(source),
2748 const Quantum *pixels,Quantum *result,const size_t channels)
2749{
2750 magick_unreferenced(source);
2751
2752 if (!PixelsEqual(pixels,1,pixels,7,channels) &&
2753 !PixelsEqual(pixels,3,pixels,5,channels))
2754 {
2755 if (PixelsEqual(pixels,3,pixels,1,channels))
2756 CopyPixels(pixels,3,result,0,channels);
2757 else
2758 CopyPixels(pixels,4,result,0,channels);
2759
2760 if (
2761 (
2762 PixelsEqual(pixels,3,pixels,1,channels) &&
2763 !PixelsEqual(pixels,4,pixels,2,channels)
2764 ) ||
2765 (
2766 PixelsEqual(pixels,5,pixels,1,channels) &&
2767 !PixelsEqual(pixels,4,pixels,0,channels)
2768 )
2769 )
2770 CopyPixels(pixels,1,result,1,channels);
2771 else
2772 CopyPixels(pixels,4,result,1,channels);
2773 if (PixelsEqual(pixels,5,pixels,1,channels))
2774 CopyPixels(pixels,5,result,2,channels);
2775 else
2776 CopyPixels(pixels,4,result,2,channels);
2777 if (
2778 (
2779 PixelsEqual(pixels,3,pixels,1,channels) &&
2780 !PixelsEqual(pixels,4,pixels,6,channels)
2781 ) ||
2782 (
2783 PixelsEqual(pixels,3,pixels,7,channels) &&
2784 !PixelsEqual(pixels,4,pixels,0,channels)
2785 )
2786 )
2787 CopyPixels(pixels,3,result,3,channels);
2788 else
2789 CopyPixels(pixels,4,result,3,channels);
2790 CopyPixels(pixels,4,result,4,channels);
2791 if (
2792 (
2793 PixelsEqual(pixels,5,pixels,1,channels) &&
2794 !PixelsEqual(pixels,4,pixels,8,channels)
2795 ) ||
2796 (
2797 PixelsEqual(pixels,5,pixels,7,channels) &&
2798 !PixelsEqual(pixels,4,pixels,2,channels)
2799 )
2800 )
2801 CopyPixels(pixels,5,result,5,channels);
2802 else
2803 CopyPixels(pixels,4,result,5,channels);
2804 if (PixelsEqual(pixels,3,pixels,7,channels))
2805 CopyPixels(pixels,3,result,6,channels);
2806 else
2807 CopyPixels(pixels,4,result,6,channels);
2808 if (
2809 (
2810 PixelsEqual(pixels,3,pixels,7,channels) &&
2811 !PixelsEqual(pixels,4,pixels,8,channels)
2812 ) ||
2813 (
2814 PixelsEqual(pixels,5,pixels,7,channels) &&
2815 !PixelsEqual(pixels,4,pixels,6,channels)
2816 )
2817 )
2818 CopyPixels(pixels,7,result,7,channels);
2819 else
2820 CopyPixels(pixels,4,result,7,channels);
2821 if (PixelsEqual(pixels,5,pixels,7,channels))
2822 CopyPixels(pixels,5,result,8,channels);
2823 else
2824 CopyPixels(pixels,4,result,8,channels);
2825 }
2826 else
2827 {
2828 ssize_t
2829 i;
2830
2831 for (i=0; i < 9; i++)
2832 CopyPixels(pixels,4,result,i,channels);
2833 }
2834}
2835
2836MagickExport Image *MagnifyImage(const Image *image,ExceptionInfo *exception)
2837{
2838#define MagnifyImageTag "Magnify/Image"
2839
2840 CacheView
2841 *image_view,
2842 *magnify_view;
2843
2844 const char
2845 *option;
2846
2847 Image
2848 *source_image,
2849 *magnify_image;
2850
2851 MagickBooleanType
2852 status;
2853
2854 MagickOffsetType
2855 progress;
2856
2858 offset;
2859
2861 rectangle;
2862
2863 ssize_t
2864 y;
2865
2866 unsigned char
2867 magnification,
2868 width;
2869
2870 void
2871 (*scaling_method)(const Image *,const Quantum *,Quantum *,size_t);
2872
2873 /*
2874 Initialize magnified image attributes.
2875 */
2876 assert(image != (const Image *) NULL);
2877 assert(image->signature == MagickCoreSignature);
2878 assert(exception != (ExceptionInfo *) NULL);
2879 assert(exception->signature == MagickCoreSignature);
2880 if (IsEventLogging() != MagickFalse)
2881 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
2882 option=GetImageOption(image->image_info,"magnify:method");
2883 if (option == (char *) NULL)
2884 option="scale2x";
2885 scaling_method=Scale2X;
2886 magnification=1;
2887 width=1;
2888 switch (*option)
2889 {
2890 case 'e':
2891 {
2892 if (LocaleCompare(option,"eagle2x") == 0)
2893 {
2894 scaling_method=Eagle2X;
2895 magnification=2;
2896 width=3;
2897 break;
2898 }
2899 if (LocaleCompare(option,"eagle3x") == 0)
2900 {
2901 scaling_method=Eagle3X;
2902 magnification=3;
2903 width=3;
2904 break;
2905 }
2906 if (LocaleCompare(option,"eagle3xb") == 0)
2907 {
2908 scaling_method=Eagle3XB;
2909 magnification=3;
2910 width=3;
2911 break;
2912 }
2913 if (LocaleCompare(option,"epbx2x") == 0)
2914 {
2915 scaling_method=Epbx2X;
2916 magnification=2;
2917 width=3;
2918 break;
2919 }
2920 break;
2921 }
2922 case 'f':
2923 {
2924 if (LocaleCompare(option,"fish2x") == 0)
2925 {
2926 scaling_method=Fish2X;
2927 magnification=2;
2928 width=3;
2929 break;
2930 }
2931 break;
2932 }
2933 case 'h':
2934 {
2935 if (LocaleCompare(option,"hq2x") == 0)
2936 {
2937 scaling_method=Hq2X;
2938 magnification=2;
2939 width=3;
2940 break;
2941 }
2942 break;
2943 }
2944 case 's':
2945 {
2946 if (LocaleCompare(option,"scale2x") == 0)
2947 {
2948 scaling_method=Scale2X;
2949 magnification=2;
2950 width=3;
2951 break;
2952 }
2953 if (LocaleCompare(option,"scale3x") == 0)
2954 {
2955 scaling_method=Scale3X;
2956 magnification=3;
2957 width=3;
2958 break;
2959 }
2960 break;
2961 }
2962 case 'x':
2963 {
2964 if (LocaleCompare(option,"xbr2x") == 0)
2965 {
2966 scaling_method=Xbr2X;
2967 magnification=2;
2968 width=5;
2969 }
2970 break;
2971 }
2972 default:
2973 break;
2974 }
2975 /*
2976 Make a working copy of the source image and convert it to RGB colorspace.
2977 */
2978 source_image=CloneImage(image,image->columns,image->rows,MagickTrue,
2979 exception);
2980 if (source_image == (Image *) NULL)
2981 return((Image *) NULL);
2982 offset.x=0;
2983 offset.y=0;
2984 rectangle.x=0;
2985 rectangle.y=0;
2986 rectangle.width=image->columns;
2987 rectangle.height=image->rows;
2988 (void) CopyImagePixels(source_image,image,&rectangle,&offset,exception);
2989 if (IssRGBCompatibleColorspace(source_image->colorspace) == MagickFalse)
2990 (void) TransformImageColorspace(source_image,sRGBColorspace,exception);
2991 magnify_image=CloneImage(source_image,magnification*source_image->columns,
2992 magnification*source_image->rows,MagickTrue,exception);
2993 if (magnify_image == (Image *) NULL)
2994 {
2995 source_image=DestroyImage(source_image);
2996 return((Image *) NULL);
2997 }
2998 /*
2999 Magnify the image.
3000 */
3001 status=MagickTrue;
3002 progress=0;
3003 image_view=AcquireVirtualCacheView(source_image,exception);
3004 magnify_view=AcquireAuthenticCacheView(magnify_image,exception);
3005#if defined(MAGICKCORE_OPENMP_SUPPORT)
3006 #pragma omp parallel for schedule(static) shared(progress,status) \
3007 magick_number_threads(source_image,magnify_image,source_image->rows,1)
3008#endif
3009 for (y=0; y < (ssize_t) source_image->rows; y++)
3010 {
3011 Quantum
3012 r[128]; /* to hold result pixels */
3013
3014 Quantum
3015 *magick_restrict q;
3016
3017 ssize_t
3018 x;
3019
3020 if (status == MagickFalse)
3021 continue;
3022 q=QueueCacheViewAuthenticPixels(magnify_view,0,magnification*y,
3023 magnify_image->columns,magnification,exception);
3024 if (q == (Quantum *) NULL)
3025 {
3026 status=MagickFalse;
3027 continue;
3028 }
3029 /*
3030 Magnify this row of pixels.
3031 */
3032 for (x=0; x < (ssize_t) source_image->columns; x++)
3033 {
3034 const Quantum
3035 *magick_restrict p;
3036
3037 size_t
3038 channels;
3039
3040 ssize_t
3041 i,
3042 j;
3043
3044 p=GetCacheViewVirtualPixels(image_view,x-width/2,y-width/2,width,width,
3045 exception);
3046 if (p == (Quantum *) NULL)
3047 {
3048 status=MagickFalse;
3049 continue;
3050 }
3051 channels=GetPixelChannels(source_image);
3052 scaling_method(source_image,p,r,channels);
3053 /*
3054 Copy the result pixels into the final image.
3055 */
3056 for (j=0; j < (ssize_t) magnification; j++)
3057 for (i=0; i < (ssize_t) (channels*magnification); i++)
3058 q[j*(ssize_t) channels*(ssize_t) magnify_image->columns+i]=
3059 r[j*magnification*(ssize_t) channels+i];
3060 q+=magnification*GetPixelChannels(magnify_image);
3061 }
3062 if (SyncCacheViewAuthenticPixels(magnify_view,exception) == MagickFalse)
3063 status=MagickFalse;
3064 if (image->progress_monitor != (MagickProgressMonitor) NULL)
3065 {
3066 MagickBooleanType
3067 proceed;
3068
3069#if defined(MAGICKCORE_OPENMP_SUPPORT)
3070 #pragma omp atomic
3071#endif
3072 progress++;
3073 proceed=SetImageProgress(image,MagnifyImageTag,progress,image->rows);
3074 if (proceed == MagickFalse)
3075 status=MagickFalse;
3076 }
3077 }
3078 magnify_view=DestroyCacheView(magnify_view);
3079 image_view=DestroyCacheView(image_view);
3080 source_image=DestroyImage(source_image);
3081 if (status == MagickFalse)
3082 magnify_image=DestroyImage(magnify_image);
3083 return(magnify_image);
3084}
3085
3086/*
3087%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3088% %
3089% %
3090% %
3091% M i n i f y I m a g e %
3092% %
3093% %
3094% %
3095%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3096%
3097% MinifyImage() is a convenience method that scales an image proportionally to
3098% half its size.
3099%
3100% The format of the MinifyImage method is:
3101%
3102% Image *MinifyImage(const Image *image,ExceptionInfo *exception)
3103%
3104% A description of each parameter follows:
3105%
3106% o image: the image.
3107%
3108% o exception: return any errors or warnings in this structure.
3109%
3110*/
3111MagickExport Image *MinifyImage(const Image *image,ExceptionInfo *exception)
3112{
3113 Image
3114 *minify_image;
3115
3116 assert(image != (Image *) NULL);
3117 assert(image->signature == MagickCoreSignature);
3118 assert(exception != (ExceptionInfo *) NULL);
3119 assert(exception->signature == MagickCoreSignature);
3120 if (IsEventLogging() != MagickFalse)
3121 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
3122 minify_image=ResizeImage(image,image->columns/2,image->rows/2,SplineFilter,
3123 exception);
3124 return(minify_image);
3125}
3126
3127/*
3128%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3129% %
3130% %
3131% %
3132% R e s a m p l e I m a g e %
3133% %
3134% %
3135% %
3136%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3137%
3138% ResampleImage() resize image in terms of its pixel size, so that when
3139% displayed at the given resolution it will be the same size in terms of
3140% real world units as the original image at the original resolution.
3141%
3142% The format of the ResampleImage method is:
3143%
3144% Image *ResampleImage(Image *image,const double x_resolution,
3145% const double y_resolution,const FilterType filter,
3146% ExceptionInfo *exception)
3147%
3148% A description of each parameter follows:
3149%
3150% o image: the image to be resized to fit the given resolution.
3151%
3152% o x_resolution: the new image x resolution.
3153%
3154% o y_resolution: the new image y resolution.
3155%
3156% o filter: Image filter to use.
3157%
3158% o exception: return any errors or warnings in this structure.
3159%
3160*/
3161MagickExport Image *ResampleImage(const Image *image,const double x_resolution,
3162 const double y_resolution,const FilterType filter,ExceptionInfo *exception)
3163{
3164#define ResampleImageTag "Resample/Image"
3165
3166 Image
3167 *resample_image;
3168
3169 size_t
3170 height,
3171 width;
3172
3173 /*
3174 Initialize sampled image attributes.
3175 */
3176 assert(image != (const Image *) NULL);
3177 assert(image->signature == MagickCoreSignature);
3178 assert(exception != (ExceptionInfo *) NULL);
3179 assert(exception->signature == MagickCoreSignature);
3180 if (IsEventLogging() != MagickFalse)
3181 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
3182 width=(size_t) (x_resolution*image->columns/(image->resolution.x == 0.0 ?
3183 DefaultResolution : image->resolution.x)+0.5);
3184 height=(size_t) (y_resolution*image->rows/(image->resolution.y == 0.0 ?
3185 DefaultResolution : image->resolution.y)+0.5);
3186 resample_image=ResizeImage(image,width,height,filter,exception);
3187 if (resample_image != (Image *) NULL)
3188 {
3189 resample_image->resolution.x=x_resolution;
3190 resample_image->resolution.y=y_resolution;
3191 }
3192 return(resample_image);
3193}
3194
3195/*
3196%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3197% %
3198% %
3199% %
3200% R e s i z e I m a g e %
3201% %
3202% %
3203% %
3204%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3205%
3206% ResizeImage() scales an image to the desired dimensions, using the given
3207% filter (see AcquireFilterInfo()).
3208%
3209% If an undefined filter is given the filter defaults to Mitchell for a
3210% colormapped image, a image with a matte channel, or if the image is
3211% enlarged. Otherwise the filter defaults to a Lanczos.
3212%
3213% ResizeImage() was inspired by Paul Heckbert's "zoom" program.
3214%
3215% The format of the ResizeImage method is:
3216%
3217% Image *ResizeImage(Image *image,const size_t columns,const size_t rows,
3218% const FilterType filter,ExceptionInfo *exception)
3219%
3220% A description of each parameter follows:
3221%
3222% o image: the image.
3223%
3224% o columns: the number of columns in the scaled image.
3225%
3226% o rows: the number of rows in the scaled image.
3227%
3228% o filter: Image filter to use.
3229%
3230% o exception: return any errors or warnings in this structure.
3231%
3232*/
3233
3234typedef struct _ContributionInfo
3235{
3236 double
3237 weight;
3238
3239 ssize_t
3240 pixel;
3242
3243static ContributionInfo **DestroyContributionTLS(
3244 ContributionInfo **contribution)
3245{
3246 ssize_t
3247 i;
3248
3249 assert(contribution != (ContributionInfo **) NULL);
3250 for (i=0; i < (ssize_t) GetMagickResourceLimit(ThreadResource); i++)
3251 if (contribution[i] != (ContributionInfo *) NULL)
3252 contribution[i]=(ContributionInfo *) RelinquishAlignedMemory(
3253 contribution[i]);
3254 contribution=(ContributionInfo **) RelinquishMagickMemory(contribution);
3255 return(contribution);
3256}
3257
3258static ContributionInfo **AcquireContributionTLS(const size_t count)
3259{
3260 ssize_t
3261 i;
3262
3264 **contribution;
3265
3266 size_t
3267 number_threads;
3268
3269 number_threads=(size_t) GetMagickResourceLimit(ThreadResource);
3270 contribution=(ContributionInfo **) AcquireQuantumMemory(number_threads,
3271 sizeof(*contribution));
3272 if (contribution == (ContributionInfo **) NULL)
3273 return((ContributionInfo **) NULL);
3274 (void) memset(contribution,0,number_threads*sizeof(*contribution));
3275 for (i=0; i < (ssize_t) number_threads; i++)
3276 {
3277 contribution[i]=(ContributionInfo *) MagickAssumeAligned(
3278 AcquireAlignedMemory(count,sizeof(**contribution)));
3279 if (contribution[i] == (ContributionInfo *) NULL)
3280 return(DestroyContributionTLS(contribution));
3281 }
3282 return(contribution);
3283}
3284
3285static MagickBooleanType HorizontalFilter(
3286 const ResizeFilter *magick_restrict resize_filter,
3287 const Image *magick_restrict image,Image *magick_restrict resize_image,
3288 const double x_factor,const MagickSizeType span,
3289 MagickOffsetType *magick_restrict progress,ExceptionInfo *exception)
3290{
3291#define ResizeImageTag "Resize/Image"
3292
3293 CacheView
3294 *image_view,
3295 *resize_view;
3296
3297 ClassType
3298 storage_class;
3299
3301 **magick_restrict contributions;
3302
3303 double
3304 scale,
3305 support;
3306
3307 MagickBooleanType
3308 status;
3309
3310 ssize_t
3311 x;
3312
3313 /*
3314 Apply filter to resize horizontally from image to resize image.
3315 */
3316 scale=MagickMax(1.0/x_factor+MagickEpsilon,1.0);
3317 support=scale*GetResizeFilterSupport(resize_filter);
3318 storage_class=support > 0.5 ? DirectClass : image->storage_class;
3319 if (SetImageStorageClass(resize_image,storage_class,exception) == MagickFalse)
3320 return(MagickFalse);
3321 if (support < 0.5)
3322 {
3323 /*
3324 Support too small even for nearest neighbour: Reduce to point sampling.
3325 */
3326 support=(double) 0.5;
3327 scale=1.0;
3328 }
3329 contributions=AcquireContributionTLS((size_t) (2.0*support+3.0));
3330 if (contributions == (ContributionInfo **) NULL)
3331 {
3332 (void) ThrowMagickException(exception,GetMagickModule(),
3333 ResourceLimitError,"MemoryAllocationFailed","`%s'",image->filename);
3334 return(MagickFalse);
3335 }
3336 status=MagickTrue;
3337 scale=PerceptibleReciprocal(scale);
3338 image_view=AcquireVirtualCacheView(image,exception);
3339 resize_view=AcquireAuthenticCacheView(resize_image,exception);
3340#if defined(MAGICKCORE_OPENMP_SUPPORT)
3341 #pragma omp parallel for schedule(static) shared(progress,status) \
3342 magick_number_threads(image,resize_image,resize_image->columns,1)
3343#endif
3344 for (x=0; x < (ssize_t) resize_image->columns; x++)
3345 {
3346 const int
3347 id = GetOpenMPThreadId();
3348
3349 const Quantum
3350 *magick_restrict p;
3351
3353 *magick_restrict contribution;
3354
3355 double
3356 bisect,
3357 density;
3358
3359 Quantum
3360 *magick_restrict q;
3361
3362 ssize_t
3363 n,
3364 start,
3365 stop,
3366 y;
3367
3368 if (status == MagickFalse)
3369 continue;
3370 bisect=(double) (x+0.5)/x_factor+MagickEpsilon;
3371 start=(ssize_t) MagickMax(bisect-support+0.5,0.0);
3372 stop=(ssize_t) MagickMin(bisect+support+0.5,(double) image->columns);
3373 density=0.0;
3374 contribution=contributions[id];
3375 for (n=0; n < (stop-start); n++)
3376 {
3377 contribution[n].pixel=start+n;
3378 contribution[n].weight=GetResizeFilterWeight(resize_filter,scale*
3379 ((double) (start+n)-bisect+0.5));
3380 density+=contribution[n].weight;
3381 }
3382 if (n == 0)
3383 continue;
3384 if ((density != 0.0) && (density != 1.0))
3385 {
3386 ssize_t
3387 i;
3388
3389 /*
3390 Normalize.
3391 */
3392 density=PerceptibleReciprocal(density);
3393 for (i=0; i < n; i++)
3394 contribution[i].weight*=density;
3395 }
3396 p=GetCacheViewVirtualPixels(image_view,contribution[0].pixel,0,(size_t)
3397 (contribution[n-1].pixel-contribution[0].pixel+1),image->rows,exception);
3398 q=QueueCacheViewAuthenticPixels(resize_view,x,0,1,resize_image->rows,
3399 exception);
3400 if ((p == (const Quantum *) NULL) || (q == (Quantum *) NULL))
3401 {
3402 status=MagickFalse;
3403 continue;
3404 }
3405 for (y=0; y < (ssize_t) resize_image->rows; y++)
3406 {
3407 ssize_t
3408 i;
3409
3410 for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
3411 {
3412 double
3413 alpha,
3414 gamma,
3415 pixel;
3416
3417 PixelChannel
3418 channel;
3419
3420 PixelTrait
3421 resize_traits,
3422 traits;
3423
3424 ssize_t
3425 j,
3426 k;
3427
3428 channel=GetPixelChannelChannel(image,i);
3429 traits=GetPixelChannelTraits(image,channel);
3430 resize_traits=GetPixelChannelTraits(resize_image,channel);
3431 if ((traits == UndefinedPixelTrait) ||
3432 (resize_traits == UndefinedPixelTrait))
3433 continue;
3434 if (((resize_traits & CopyPixelTrait) != 0) ||
3435 (GetPixelWriteMask(resize_image,q) <= (QuantumRange/2)))
3436 {
3437 j=(ssize_t) (MagickMin(MagickMax(bisect,(double) start),(double)
3438 stop-1.0)+0.5);
3439 k=y*(contribution[n-1].pixel-contribution[0].pixel+1)+
3440 (contribution[j-start].pixel-contribution[0].pixel);
3441 SetPixelChannel(resize_image,channel,
3442 p[k*(ssize_t) GetPixelChannels(image)+i],q);
3443 continue;
3444 }
3445 pixel=0.0;
3446 if ((resize_traits & BlendPixelTrait) == 0)
3447 {
3448 /*
3449 No alpha blending.
3450 */
3451 for (j=0; j < n; j++)
3452 {
3453 k=y*(contribution[n-1].pixel-contribution[0].pixel+1)+
3454 (contribution[j].pixel-contribution[0].pixel);
3455 alpha=contribution[j].weight;
3456 pixel+=alpha*(double) p[k*(ssize_t) GetPixelChannels(image)+i];
3457 }
3458 SetPixelChannel(resize_image,channel,ClampToQuantum(pixel),q);
3459 continue;
3460 }
3461 /*
3462 Alpha blending.
3463 */
3464 gamma=0.0;
3465 for (j=0; j < n; j++)
3466 {
3467 k=y*(contribution[n-1].pixel-contribution[0].pixel+1)+
3468 (contribution[j].pixel-contribution[0].pixel);
3469 alpha=contribution[j].weight*QuantumScale*
3470 (double) GetPixelAlpha(image,p+k*(ssize_t) GetPixelChannels(image));
3471 pixel+=alpha*(double) p[k*(ssize_t) GetPixelChannels(image)+i];
3472 gamma+=alpha;
3473 }
3474 gamma=PerceptibleReciprocal(gamma);
3475 SetPixelChannel(resize_image,channel,ClampToQuantum(gamma*pixel),q);
3476 }
3477 q+=GetPixelChannels(resize_image);
3478 }
3479 if (SyncCacheViewAuthenticPixels(resize_view,exception) == MagickFalse)
3480 status=MagickFalse;
3481 if (image->progress_monitor != (MagickProgressMonitor) NULL)
3482 {
3483 MagickBooleanType
3484 proceed;
3485
3486#if defined(MAGICKCORE_OPENMP_SUPPORT)
3487 #pragma omp atomic
3488#endif
3489 (*progress)++;
3490 proceed=SetImageProgress(image,ResizeImageTag,*progress,span);
3491 if (proceed == MagickFalse)
3492 status=MagickFalse;
3493 }
3494 }
3495 resize_view=DestroyCacheView(resize_view);
3496 image_view=DestroyCacheView(image_view);
3497 contributions=DestroyContributionTLS(contributions);
3498 return(status);
3499}
3500
3501static MagickBooleanType VerticalFilter(
3502 const ResizeFilter *magick_restrict resize_filter,
3503 const Image *magick_restrict image,Image *magick_restrict resize_image,
3504 const double y_factor,const MagickSizeType span,
3505 MagickOffsetType *magick_restrict progress,ExceptionInfo *exception)
3506{
3507 CacheView
3508 *image_view,
3509 *resize_view;
3510
3511 ClassType
3512 storage_class;
3513
3515 **magick_restrict contributions;
3516
3517 double
3518 scale,
3519 support;
3520
3521 MagickBooleanType
3522 status;
3523
3524 ssize_t
3525 y;
3526
3527 /*
3528 Apply filter to resize vertically from image to resize image.
3529 */
3530 scale=MagickMax(1.0/y_factor+MagickEpsilon,1.0);
3531 support=scale*GetResizeFilterSupport(resize_filter);
3532 storage_class=support > 0.5 ? DirectClass : image->storage_class;
3533 if (SetImageStorageClass(resize_image,storage_class,exception) == MagickFalse)
3534 return(MagickFalse);
3535 if (support < 0.5)
3536 {
3537 /*
3538 Support too small even for nearest neighbour: Reduce to point sampling.
3539 */
3540 support=(double) 0.5;
3541 scale=1.0;
3542 }
3543 contributions=AcquireContributionTLS((size_t) (2.0*support+3.0));
3544 if (contributions == (ContributionInfo **) NULL)
3545 {
3546 (void) ThrowMagickException(exception,GetMagickModule(),
3547 ResourceLimitError,"MemoryAllocationFailed","`%s'",image->filename);
3548 return(MagickFalse);
3549 }
3550 status=MagickTrue;
3551 scale=PerceptibleReciprocal(scale);
3552 image_view=AcquireVirtualCacheView(image,exception);
3553 resize_view=AcquireAuthenticCacheView(resize_image,exception);
3554#if defined(MAGICKCORE_OPENMP_SUPPORT)
3555 #pragma omp parallel for schedule(static) shared(progress,status) \
3556 magick_number_threads(image,resize_image,resize_image->rows,1)
3557#endif
3558 for (y=0; y < (ssize_t) resize_image->rows; y++)
3559 {
3560 const int
3561 id = GetOpenMPThreadId();
3562
3563 const Quantum
3564 *magick_restrict p;
3565
3567 *magick_restrict contribution;
3568
3569 double
3570 bisect,
3571 density;
3572
3573 Quantum
3574 *magick_restrict q;
3575
3576 ssize_t
3577 n,
3578 start,
3579 stop,
3580 x;
3581
3582 if (status == MagickFalse)
3583 continue;
3584 bisect=(double) (y+0.5)/y_factor+MagickEpsilon;
3585 start=(ssize_t) MagickMax(bisect-support+0.5,0.0);
3586 stop=(ssize_t) MagickMin(bisect+support+0.5,(double) image->rows);
3587 density=0.0;
3588 contribution=contributions[id];
3589 for (n=0; n < (stop-start); n++)
3590 {
3591 contribution[n].pixel=start+n;
3592 contribution[n].weight=GetResizeFilterWeight(resize_filter,scale*
3593 ((double) (start+n)-bisect+0.5));
3594 density+=contribution[n].weight;
3595 }
3596 if (n == 0)
3597 continue;
3598 if ((density != 0.0) && (density != 1.0))
3599 {
3600 ssize_t
3601 i;
3602
3603 /*
3604 Normalize.
3605 */
3606 density=PerceptibleReciprocal(density);
3607 for (i=0; i < n; i++)
3608 contribution[i].weight*=density;
3609 }
3610 p=GetCacheViewVirtualPixels(image_view,0,contribution[0].pixel,
3611 image->columns,(size_t) (contribution[n-1].pixel-contribution[0].pixel+1),
3612 exception);
3613 q=QueueCacheViewAuthenticPixels(resize_view,0,y,resize_image->columns,1,
3614 exception);
3615 if ((p == (const Quantum *) NULL) || (q == (Quantum *) NULL))
3616 {
3617 status=MagickFalse;
3618 continue;
3619 }
3620 for (x=0; x < (ssize_t) resize_image->columns; x++)
3621 {
3622 ssize_t
3623 i;
3624
3625 for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
3626 {
3627 double
3628 alpha,
3629 gamma,
3630 pixel;
3631
3632 PixelChannel
3633 channel;
3634
3635 PixelTrait
3636 resize_traits,
3637 traits;
3638
3639 ssize_t
3640 j,
3641 k;
3642
3643 channel=GetPixelChannelChannel(image,i);
3644 traits=GetPixelChannelTraits(image,channel);
3645 resize_traits=GetPixelChannelTraits(resize_image,channel);
3646 if ((traits == UndefinedPixelTrait) ||
3647 (resize_traits == UndefinedPixelTrait))
3648 continue;
3649 if (((resize_traits & CopyPixelTrait) != 0) ||
3650 (GetPixelWriteMask(resize_image,q) <= (QuantumRange/2)))
3651 {
3652 j=(ssize_t) (MagickMin(MagickMax(bisect,(double) start),(double)
3653 stop-1.0)+0.5);
3654 k=(ssize_t) ((contribution[j-start].pixel-contribution[0].pixel)*
3655 (ssize_t) image->columns+x);
3656 SetPixelChannel(resize_image,channel,p[k*(ssize_t)
3657 GetPixelChannels(image)+i],q);
3658 continue;
3659 }
3660 pixel=0.0;
3661 if ((resize_traits & BlendPixelTrait) == 0)
3662 {
3663 /*
3664 No alpha blending.
3665 */
3666 for (j=0; j < n; j++)
3667 {
3668 k=(ssize_t) ((contribution[j].pixel-contribution[0].pixel)*
3669 (ssize_t) image->columns+x);
3670 alpha=contribution[j].weight;
3671 pixel+=alpha*(double) p[k*(ssize_t) GetPixelChannels(image)+i];
3672 }
3673 SetPixelChannel(resize_image,channel,ClampToQuantum(pixel),q);
3674 continue;
3675 }
3676 gamma=0.0;
3677 for (j=0; j < n; j++)
3678 {
3679 k=(ssize_t) ((contribution[j].pixel-contribution[0].pixel)*
3680 (ssize_t) image->columns+x);
3681 alpha=contribution[j].weight*QuantumScale*(double)
3682 GetPixelAlpha(image,p+k*(ssize_t) GetPixelChannels(image));
3683 pixel+=alpha*(double) p[k*(ssize_t) GetPixelChannels(image)+i];
3684 gamma+=alpha;
3685 }
3686 gamma=PerceptibleReciprocal(gamma);
3687 SetPixelChannel(resize_image,channel,ClampToQuantum(gamma*pixel),q);
3688 }
3689 q+=GetPixelChannels(resize_image);
3690 }
3691 if (SyncCacheViewAuthenticPixels(resize_view,exception) == MagickFalse)
3692 status=MagickFalse;
3693 if (image->progress_monitor != (MagickProgressMonitor) NULL)
3694 {
3695 MagickBooleanType
3696 proceed;
3697
3698#if defined(MAGICKCORE_OPENMP_SUPPORT)
3699 #pragma omp atomic
3700#endif
3701 (*progress)++;
3702 proceed=SetImageProgress(image,ResizeImageTag,*progress,span);
3703 if (proceed == MagickFalse)
3704 status=MagickFalse;
3705 }
3706 }
3707 resize_view=DestroyCacheView(resize_view);
3708 image_view=DestroyCacheView(image_view);
3709 contributions=DestroyContributionTLS(contributions);
3710 return(status);
3711}
3712
3713MagickExport Image *ResizeImage(const Image *image,const size_t columns,
3714 const size_t rows,const FilterType filter,ExceptionInfo *exception)
3715{
3716 double
3717 x_factor,
3718 y_factor;
3719
3720 FilterType
3721 filter_type;
3722
3723 Image
3724 *filter_image,
3725 *resize_image;
3726
3727 MagickOffsetType
3728 offset;
3729
3730 MagickSizeType
3731 span;
3732
3733 MagickStatusType
3734 status;
3735
3737 *resize_filter;
3738
3739 /*
3740 Acquire resize image.
3741 */
3742 assert(image != (Image *) NULL);
3743 assert(image->signature == MagickCoreSignature);
3744 assert(exception != (ExceptionInfo *) NULL);
3745 assert(exception->signature == MagickCoreSignature);
3746 if (IsEventLogging() != MagickFalse)
3747 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
3748 if ((columns == 0) || (rows == 0))
3749 ThrowImageException(ImageError,"NegativeOrZeroImageSize");
3750 if ((columns == image->columns) && (rows == image->rows) &&
3751 (filter == UndefinedFilter))
3752 return(CloneImage(image,0,0,MagickTrue,exception));
3753 /*
3754 Acquire resize filter.
3755 */
3756 x_factor=(double) (columns*PerceptibleReciprocal((double) image->columns));
3757 y_factor=(double) (rows*PerceptibleReciprocal((double) image->rows));
3758 filter_type=LanczosFilter;
3759 if (filter != UndefinedFilter)
3760 filter_type=filter;
3761 else
3762 if ((x_factor == 1.0) && (y_factor == 1.0))
3763 filter_type=PointFilter;
3764 else
3765 if ((image->storage_class == PseudoClass) ||
3766 (image->alpha_trait != UndefinedPixelTrait) ||
3767 ((x_factor*y_factor) > 1.0))
3768 filter_type=MitchellFilter;
3769 resize_filter=AcquireResizeFilter(image,filter_type,MagickFalse,exception);
3770#if defined(MAGICKCORE_OPENCL_SUPPORT)
3771 resize_image=AccelerateResizeImage(image,columns,rows,resize_filter,
3772 exception);
3773 if (resize_image != (Image *) NULL)
3774 {
3775 resize_filter=DestroyResizeFilter(resize_filter);
3776 return(resize_image);
3777 }
3778#endif
3779 resize_image=CloneImage(image,columns,rows,MagickTrue,exception);
3780 if (resize_image == (Image *) NULL)
3781 {
3782 resize_filter=DestroyResizeFilter(resize_filter);
3783 return(resize_image);
3784 }
3785 if (x_factor > y_factor)
3786 filter_image=CloneImage(image,columns,image->rows,MagickTrue,exception);
3787 else
3788 filter_image=CloneImage(image,image->columns,rows,MagickTrue,exception);
3789 if (filter_image == (Image *) NULL)
3790 {
3791 resize_filter=DestroyResizeFilter(resize_filter);
3792 return(DestroyImage(resize_image));
3793 }
3794 /*
3795 Resize image.
3796 */
3797 offset=0;
3798 if (x_factor > y_factor)
3799 {
3800 span=(MagickSizeType) (filter_image->columns+rows);
3801 status=HorizontalFilter(resize_filter,image,filter_image,x_factor,span,
3802 &offset,exception);
3803 status&=(MagickStatusType) VerticalFilter(resize_filter,filter_image,
3804 resize_image,y_factor,span,&offset,exception);
3805 }
3806 else
3807 {
3808 span=(MagickSizeType) (filter_image->rows+columns);
3809 status=VerticalFilter(resize_filter,image,filter_image,y_factor,span,
3810 &offset,exception);
3811 status&=(MagickStatusType) HorizontalFilter(resize_filter,filter_image,
3812 resize_image,x_factor,span,&offset,exception);
3813 }
3814 /*
3815 Free resources.
3816 */
3817 filter_image=DestroyImage(filter_image);
3818 resize_filter=DestroyResizeFilter(resize_filter);
3819 if (status == MagickFalse)
3820 {
3821 resize_image=DestroyImage(resize_image);
3822 return((Image *) NULL);
3823 }
3824 resize_image->type=image->type;
3825 return(resize_image);
3826}
3827
3828/*
3829%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3830% %
3831% %
3832% %
3833% S a m p l e I m a g e %
3834% %
3835% %
3836% %
3837%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3838%
3839% SampleImage() scales an image to the desired dimensions with pixel
3840% sampling. Unlike other scaling methods, this method does not introduce
3841% any additional color into the scaled image.
3842%
3843% The format of the SampleImage method is:
3844%
3845% Image *SampleImage(const Image *image,const size_t columns,
3846% const size_t rows,ExceptionInfo *exception)
3847%
3848% A description of each parameter follows:
3849%
3850% o image: the image.
3851%
3852% o columns: the number of columns in the sampled image.
3853%
3854% o rows: the number of rows in the sampled image.
3855%
3856% o exception: return any errors or warnings in this structure.
3857%
3858*/
3859MagickExport Image *SampleImage(const Image *image,const size_t columns,
3860 const size_t rows,ExceptionInfo *exception)
3861{
3862#define SampleImageTag "Sample/Image"
3863
3864 CacheView
3865 *image_view,
3866 *sample_view;
3867
3868 Image
3869 *sample_image;
3870
3871 MagickBooleanType
3872 status;
3873
3874 MagickOffsetType
3875 progress;
3876
3877 PointInfo
3878 sample_offset;
3879
3880 ssize_t
3881 j,
3882 *x_offset,
3883 y;
3884
3885 /*
3886 Initialize sampled image attributes.
3887 */
3888 assert(image != (const Image *) NULL);
3889 assert(image->signature == MagickCoreSignature);
3890 assert(exception != (ExceptionInfo *) NULL);
3891 assert(exception->signature == MagickCoreSignature);
3892 if (IsEventLogging() != MagickFalse)
3893 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
3894 if ((columns == 0) || (rows == 0))
3895 ThrowImageException(ImageError,"NegativeOrZeroImageSize");
3896 if ((columns == image->columns) && (rows == image->rows))
3897 return(CloneImage(image,0,0,MagickTrue,exception));
3898 sample_image=CloneImage(image,columns,rows,MagickTrue,exception);
3899 if (sample_image == (Image *) NULL)
3900 return((Image *) NULL);
3901 /*
3902 Set the sampling offset, default is in the mid-point of sample regions.
3903 */
3904 sample_offset.x=0.5-MagickEpsilon;
3905 sample_offset.y=sample_offset.x;
3906 {
3907 const char
3908 *value;
3909
3910 value=GetImageArtifact(image,"sample:offset");
3911 if (value != (char *) NULL)
3912 {
3914 geometry_info;
3915
3916 MagickStatusType
3917 flags;
3918
3919 (void) ParseGeometry(value,&geometry_info);
3920 flags=ParseGeometry(value,&geometry_info);
3921 sample_offset.x=sample_offset.y=geometry_info.rho/100.0-MagickEpsilon;
3922 if ((flags & SigmaValue) != 0)
3923 sample_offset.y=geometry_info.sigma/100.0-MagickEpsilon;
3924 }
3925 }
3926 /*
3927 Allocate scan line buffer and column offset buffers.
3928 */
3929 x_offset=(ssize_t *) AcquireQuantumMemory((size_t) sample_image->columns,
3930 sizeof(*x_offset));
3931 if (x_offset == (ssize_t *) NULL)
3932 {
3933 sample_image=DestroyImage(sample_image);
3934 ThrowImageException(ResourceLimitError,"MemoryAllocationFailed");
3935 }
3936 for (j=0; j < (ssize_t) sample_image->columns; j++)
3937 x_offset[j]=(ssize_t) ((((double) j+sample_offset.x)*image->columns)/
3938 sample_image->columns);
3939 /*
3940 Sample each row.
3941 */
3942 status=MagickTrue;
3943 progress=0;
3944 image_view=AcquireVirtualCacheView(image,exception);
3945 sample_view=AcquireAuthenticCacheView(sample_image,exception);
3946#if defined(MAGICKCORE_OPENMP_SUPPORT)
3947 #pragma omp parallel for schedule(static) shared(status) \
3948 magick_number_threads(image,sample_image,sample_image->rows,2)
3949#endif
3950 for (y=0; y < (ssize_t) sample_image->rows; y++)
3951 {
3952 const Quantum
3953 *magick_restrict p;
3954
3955 Quantum
3956 *magick_restrict q;
3957
3958 ssize_t
3959 x,
3960 y_offset;
3961
3962 if (status == MagickFalse)
3963 continue;
3964 y_offset=(ssize_t) ((((double) y+sample_offset.y)*image->rows)/
3965 sample_image->rows);
3966 p=GetCacheViewVirtualPixels(image_view,0,y_offset,image->columns,1,
3967 exception);
3968 q=QueueCacheViewAuthenticPixels(sample_view,0,y,sample_image->columns,1,
3969 exception);
3970 if ((p == (const Quantum *) NULL) || (q == (Quantum *) NULL))
3971 {
3972 status=MagickFalse;
3973 continue;
3974 }
3975 /*
3976 Sample each column.
3977 */
3978 for (x=0; x < (ssize_t) sample_image->columns; x++)
3979 {
3980 ssize_t
3981 i;
3982
3983 if (GetPixelWriteMask(sample_image,q) <= (QuantumRange/2))
3984 {
3985 q+=GetPixelChannels(sample_image);
3986 continue;
3987 }
3988 for (i=0; i < (ssize_t) GetPixelChannels(sample_image); i++)
3989 {
3990 PixelChannel
3991 channel;
3992
3993 PixelTrait
3994 image_traits,
3995 traits;
3996
3997 channel=GetPixelChannelChannel(sample_image,i);
3998 traits=GetPixelChannelTraits(sample_image,channel);
3999 image_traits=GetPixelChannelTraits(image,channel);
4000 if ((traits == UndefinedPixelTrait) ||
4001 (image_traits == UndefinedPixelTrait))
4002 continue;
4003 SetPixelChannel(sample_image,channel,p[x_offset[x]*(ssize_t)
4004 GetPixelChannels(image)+i],q);
4005 }
4006 q+=GetPixelChannels(sample_image);
4007 }
4008 if (SyncCacheViewAuthenticPixels(sample_view,exception) == MagickFalse)
4009 status=MagickFalse;
4010 if (image->progress_monitor != (MagickProgressMonitor) NULL)
4011 {
4012 MagickBooleanType
4013 proceed;
4014
4015 proceed=SetImageProgress(image,SampleImageTag,progress++,image->rows);
4016 if (proceed == MagickFalse)
4017 status=MagickFalse;
4018 }
4019 }
4020 image_view=DestroyCacheView(image_view);
4021 sample_view=DestroyCacheView(sample_view);
4022 x_offset=(ssize_t *) RelinquishMagickMemory(x_offset);
4023 sample_image->type=image->type;
4024 if (status == MagickFalse)
4025 sample_image=DestroyImage(sample_image);
4026 return(sample_image);
4027}
4028
4029/*
4030%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
4031% %
4032% %
4033% %
4034% S c a l e I m a g e %
4035% %
4036% %
4037% %
4038%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
4039%
4040% ScaleImage() changes the size of an image to the given dimensions.
4041%
4042% The format of the ScaleImage method is:
4043%
4044% Image *ScaleImage(const Image *image,const size_t columns,
4045% const size_t rows,ExceptionInfo *exception)
4046%
4047% A description of each parameter follows:
4048%
4049% o image: the image.
4050%
4051% o columns: the number of columns in the scaled image.
4052%
4053% o rows: the number of rows in the scaled image.
4054%
4055% o exception: return any errors or warnings in this structure.
4056%
4057*/
4058MagickExport Image *ScaleImage(const Image *image,const size_t columns,
4059 const size_t rows,ExceptionInfo *exception)
4060{
4061#define ScaleImageTag "Scale/Image"
4062
4063 CacheView
4064 *image_view,
4065 *scale_view;
4066
4067 double
4068 alpha,
4069 pixel[CompositePixelChannel],
4070 *scale_scanline,
4071 *scanline,
4072 *x_vector,
4073 *y_vector;
4074
4075 Image
4076 *scale_image;
4077
4078 MagickBooleanType
4079 next_column,
4080 next_row,
4081 proceed,
4082 status;
4083
4084 PixelTrait
4085 scale_traits;
4086
4087 PointInfo
4088 scale,
4089 span;
4090
4091 ssize_t
4092 i,
4093 n,
4094 number_rows,
4095 y;
4096
4097 /*
4098 Initialize scaled image attributes.
4099 */
4100 assert(image != (const Image *) NULL);
4101 assert(image->signature == MagickCoreSignature);
4102 assert(exception != (ExceptionInfo *) NULL);
4103 assert(exception->signature == MagickCoreSignature);
4104 if (IsEventLogging() != MagickFalse)
4105 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
4106 if ((columns == 0) || (rows == 0))
4107 ThrowImageException(ImageError,"NegativeOrZeroImageSize");
4108 if ((columns == image->columns) && (rows == image->rows))
4109 return(CloneImage(image,0,0,MagickTrue,exception));
4110 scale_image=CloneImage(image,columns,rows,MagickTrue,exception);
4111 if (scale_image == (Image *) NULL)
4112 return((Image *) NULL);
4113 if (SetImageStorageClass(scale_image,DirectClass,exception) == MagickFalse)
4114 {
4115 scale_image=DestroyImage(scale_image);
4116 return((Image *) NULL);
4117 }
4118 /*
4119 Allocate memory.
4120 */
4121 x_vector=(double *) AcquireQuantumMemory((size_t) image->columns,
4122 MaxPixelChannels*sizeof(*x_vector));
4123 scanline=x_vector;
4124 if (image->rows != scale_image->rows)
4125 scanline=(double *) AcquireQuantumMemory((size_t) image->columns,
4126 MaxPixelChannels*sizeof(*scanline));
4127 scale_scanline=(double *) AcquireQuantumMemory((size_t) scale_image->columns,
4128 MaxPixelChannels*sizeof(*scale_scanline));
4129 y_vector=(double *) AcquireQuantumMemory((size_t) image->columns,
4130 MaxPixelChannels*sizeof(*y_vector));
4131 if ((scanline == (double *) NULL) || (scale_scanline == (double *) NULL) ||
4132 (x_vector == (double *) NULL) || (y_vector == (double *) NULL))
4133 {
4134 if ((image->rows != scale_image->rows) && (scanline != (double *) NULL))
4135 scanline=(double *) RelinquishMagickMemory(scanline);
4136 if (scale_scanline != (double *) NULL)
4137 scale_scanline=(double *) RelinquishMagickMemory(scale_scanline);
4138 if (x_vector != (double *) NULL)
4139 x_vector=(double *) RelinquishMagickMemory(x_vector);
4140 if (y_vector != (double *) NULL)
4141 y_vector=(double *) RelinquishMagickMemory(y_vector);
4142 scale_image=DestroyImage(scale_image);
4143 ThrowImageException(ResourceLimitError,"MemoryAllocationFailed");
4144 }
4145 /*
4146 Scale image.
4147 */
4148 number_rows=0;
4149 next_row=MagickTrue;
4150 span.y=1.0;
4151 scale.y=(double) scale_image->rows/(double) image->rows;
4152 (void) memset(y_vector,0,(size_t) MaxPixelChannels*image->columns*
4153 sizeof(*y_vector));
4154 n=0;
4155 status=MagickTrue;
4156 image_view=AcquireVirtualCacheView(image,exception);
4157 scale_view=AcquireAuthenticCacheView(scale_image,exception);
4158 for (y=0; y < (ssize_t) scale_image->rows; y++)
4159 {
4160 const Quantum
4161 *magick_restrict p;
4162
4163 Quantum
4164 *magick_restrict q;
4165
4166 ssize_t
4167 x;
4168
4169 if (status == MagickFalse)
4170 break;
4171 q=QueueCacheViewAuthenticPixels(scale_view,0,y,scale_image->columns,1,
4172 exception);
4173 if (q == (Quantum *) NULL)
4174 {
4175 status=MagickFalse;
4176 break;
4177 }
4178 alpha=1.0;
4179 if (scale_image->rows == image->rows)
4180 {
4181 /*
4182 Read a new scanline.
4183 */
4184 p=GetCacheViewVirtualPixels(image_view,0,n++,image->columns,1,
4185 exception);
4186 if (p == (const Quantum *) NULL)
4187 {
4188 status=MagickFalse;
4189 break;
4190 }
4191 for (x=0; x < (ssize_t) image->columns; x++)
4192 {
4193 if (GetPixelWriteMask(image,p) <= (QuantumRange/2))
4194 {
4195 p+=GetPixelChannels(image);
4196 continue;
4197 }
4198 if (image->alpha_trait != UndefinedPixelTrait)
4199 alpha=QuantumScale*(double) GetPixelAlpha(image,p);
4200 for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
4201 {
4202 PixelChannel channel = GetPixelChannelChannel(image,i);
4203 PixelTrait traits = GetPixelChannelTraits(image,channel);
4204 if ((traits & BlendPixelTrait) == 0)
4205 {
4206 x_vector[x*(ssize_t) GetPixelChannels(image)+i]=(double) p[i];
4207 continue;
4208 }
4209 x_vector[x*(ssize_t) GetPixelChannels(image)+i]=alpha*(double) p[i];
4210 }
4211 p+=GetPixelChannels(image);
4212 }
4213 }
4214 else
4215 {
4216 /*
4217 Scale Y direction.
4218 */
4219 while (scale.y < span.y)
4220 {
4221 if ((next_row != MagickFalse) &&
4222 (number_rows < (ssize_t) image->rows))
4223 {
4224 /*
4225 Read a new scanline.
4226 */
4227 p=GetCacheViewVirtualPixels(image_view,0,n++,image->columns,1,
4228 exception);
4229 if (p == (const Quantum *) NULL)
4230 {
4231 status=MagickFalse;
4232 break;
4233 }
4234 for (x=0; x < (ssize_t) image->columns; x++)
4235 {
4236 if (GetPixelWriteMask(image,p) <= (QuantumRange/2))
4237 {
4238 p+=GetPixelChannels(image);
4239 continue;
4240 }
4241 if (image->alpha_trait != UndefinedPixelTrait)
4242 alpha=QuantumScale*(double) GetPixelAlpha(image,p);
4243 for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
4244 {
4245 PixelChannel channel = GetPixelChannelChannel(image,i);
4246 PixelTrait traits = GetPixelChannelTraits(image,channel);
4247 if ((traits & BlendPixelTrait) == 0)
4248 {
4249 x_vector[x*(ssize_t) GetPixelChannels(image)+i]=
4250 (double) p[i];
4251 continue;
4252 }
4253 x_vector[x*(ssize_t) GetPixelChannels(image)+i]=alpha*
4254 (double) p[i];
4255 }
4256 p+=GetPixelChannels(image);
4257 }
4258 number_rows++;
4259 }
4260 for (x=0; x < (ssize_t) image->columns; x++)
4261 for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
4262 y_vector[x*(ssize_t) GetPixelChannels(image)+i]+=scale.y*
4263 x_vector[x*(ssize_t) GetPixelChannels(image)+i];
4264 span.y-=scale.y;
4265 scale.y=(double) scale_image->rows/(double) image->rows;
4266 next_row=MagickTrue;
4267 }
4268 if ((next_row != MagickFalse) && (number_rows < (ssize_t) image->rows))
4269 {
4270 /*
4271 Read a new scanline.
4272 */
4273 p=GetCacheViewVirtualPixels(image_view,0,n++,image->columns,1,
4274 exception);
4275 if (p == (const Quantum *) NULL)
4276 {
4277 status=MagickFalse;
4278 break;
4279 }
4280 for (x=0; x < (ssize_t) image->columns; x++)
4281 {
4282 if (GetPixelWriteMask(image,p) <= (QuantumRange/2))
4283 {
4284 p+=GetPixelChannels(image);
4285 continue;
4286 }
4287 if (image->alpha_trait != UndefinedPixelTrait)
4288 alpha=QuantumScale*(double) GetPixelAlpha(image,p);
4289 for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
4290 {
4291 PixelChannel channel = GetPixelChannelChannel(image,i);
4292 PixelTrait traits = GetPixelChannelTraits(image,channel);
4293 if ((traits & BlendPixelTrait) == 0)
4294 {
4295 x_vector[x*(ssize_t) GetPixelChannels(image)+i]=
4296 (double) p[i];
4297 continue;
4298 }
4299 x_vector[x*(ssize_t) GetPixelChannels(image)+i]=alpha*
4300 (double) p[i];
4301 }
4302 p+=GetPixelChannels(image);
4303 }
4304 number_rows++;
4305 next_row=MagickFalse;
4306 }
4307 for (x=0; x < (ssize_t) image->columns; x++)
4308 {
4309 for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
4310 {
4311 pixel[i]=y_vector[x*(ssize_t) GetPixelChannels(image)+i]+span.y*
4312 x_vector[x*(ssize_t) GetPixelChannels(image)+i];
4313 scanline[x*(ssize_t) GetPixelChannels(image)+i]=pixel[i];
4314 y_vector[x*(ssize_t) GetPixelChannels(image)+i]=0.0;
4315 }
4316 }
4317 scale.y-=span.y;
4318 if (scale.y <= 0)
4319 {
4320 scale.y=(double) scale_image->rows/(double) image->rows;
4321 next_row=MagickTrue;
4322 }
4323 span.y=1.0;
4324 }
4325 if (scale_image->columns == image->columns)
4326 {
4327 /*
4328 Transfer scanline to scaled image.
4329 */
4330 for (x=0; x < (ssize_t) scale_image->columns; x++)
4331 {
4332 if (GetPixelWriteMask(scale_image,q) <= (QuantumRange/2))
4333 {
4334 q+=GetPixelChannels(scale_image);
4335 continue;
4336 }
4337 if (image->alpha_trait != UndefinedPixelTrait)
4338 {
4339 alpha=QuantumScale*scanline[x*(ssize_t) GetPixelChannels(image)+
4340 GetPixelChannelOffset(image,AlphaPixelChannel)];
4341 alpha=PerceptibleReciprocal(alpha);
4342 }
4343 for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
4344 {
4345 PixelChannel channel = GetPixelChannelChannel(image,i);
4346 PixelTrait traits = GetPixelChannelTraits(image,channel);
4347 scale_traits=GetPixelChannelTraits(scale_image,channel);
4348 if ((traits == UndefinedPixelTrait) ||
4349 (scale_traits == UndefinedPixelTrait))
4350 continue;
4351 if ((traits & BlendPixelTrait) == 0)
4352 {
4353 SetPixelChannel(scale_image,channel,ClampToQuantum(
4354 scanline[x*(ssize_t) GetPixelChannels(image)+i]),q);
4355 continue;
4356 }
4357 SetPixelChannel(scale_image,channel,ClampToQuantum(alpha*scanline[
4358 x*(ssize_t) GetPixelChannels(image)+i]),q);
4359 }
4360 q+=GetPixelChannels(scale_image);
4361 }
4362 }
4363 else
4364 {
4365 ssize_t
4366 t;
4367
4368 /*
4369 Scale X direction.
4370 */
4371 for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
4372 pixel[i]=0.0;
4373 next_column=MagickFalse;
4374 span.x=1.0;
4375 t=0;
4376 for (x=0; x < (ssize_t) image->columns; x++)
4377 {
4378 scale.x=(double) scale_image->columns/(double) image->columns;
4379 while (scale.x >= span.x)
4380 {
4381 if (next_column != MagickFalse)
4382 {
4383 for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
4384 pixel[i]=0.0;
4385 t++;
4386 }
4387 for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
4388 {
4389 PixelChannel channel = GetPixelChannelChannel(image,i);
4390 PixelTrait traits = GetPixelChannelTraits(image,channel);
4391 if (traits == UndefinedPixelTrait)
4392 continue;
4393 pixel[i]+=span.x*scanline[x*(ssize_t) GetPixelChannels(image)+i];
4394 scale_scanline[t*(ssize_t) GetPixelChannels(image)+i]=pixel[i];
4395 }
4396 scale.x-=span.x;
4397 span.x=1.0;
4398 next_column=MagickTrue;
4399 }
4400 if (scale.x > 0)
4401 {
4402 if (next_column != MagickFalse)
4403 {
4404 for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
4405 pixel[i]=0.0;
4406 next_column=MagickFalse;
4407 t++;
4408 }
4409 for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
4410 pixel[i]+=scale.x*scanline[x*(ssize_t)
4411 GetPixelChannels(image)+i];
4412 span.x-=scale.x;
4413 }
4414 }
4415 if (span.x > 0)
4416 {
4417 for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
4418 pixel[i]+=span.x*
4419 scanline[(x-1)*(ssize_t) GetPixelChannels(image)+i];
4420 }
4421 if ((next_column == MagickFalse) && (t < (ssize_t) scale_image->columns))
4422 for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
4423 scale_scanline[t*(ssize_t) GetPixelChannels(image)+i]=pixel[i];
4424 /*
4425 Transfer scanline to scaled image.
4426 */
4427 for (x=0; x < (ssize_t) scale_image->columns; x++)
4428 {
4429 if (GetPixelWriteMask(scale_image,q) <= (QuantumRange/2))
4430 {
4431 q+=GetPixelChannels(scale_image);
4432 continue;
4433 }
4434 if (image->alpha_trait != UndefinedPixelTrait)
4435 {
4436 alpha=QuantumScale*scale_scanline[x*(ssize_t)
4437 GetPixelChannels(image)+
4438 GetPixelChannelOffset(image,AlphaPixelChannel)];
4439 alpha=PerceptibleReciprocal(alpha);
4440 }
4441 for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
4442 {
4443 PixelChannel channel = GetPixelChannelChannel(image,i);
4444 PixelTrait traits = GetPixelChannelTraits(image,channel);
4445 scale_traits=GetPixelChannelTraits(scale_image,channel);
4446 if ((traits == UndefinedPixelTrait) ||
4447 (scale_traits == UndefinedPixelTrait))
4448 continue;
4449 if ((traits & BlendPixelTrait) == 0)
4450 {
4451 SetPixelChannel(scale_image,channel,ClampToQuantum(
4452 scale_scanline[x*(ssize_t) GetPixelChannels(image)+i]),q);
4453 continue;
4454 }
4455 SetPixelChannel(scale_image,channel,ClampToQuantum(alpha*
4456 scale_scanline[x*(ssize_t) GetPixelChannels(image)+i]),q);
4457 }
4458 q+=GetPixelChannels(scale_image);
4459 }
4460 }
4461 if (SyncCacheViewAuthenticPixels(scale_view,exception) == MagickFalse)
4462 {
4463 status=MagickFalse;
4464 break;
4465 }
4466 proceed=SetImageProgress(image,ScaleImageTag,(MagickOffsetType) y,
4467 image->rows);
4468 if (proceed == MagickFalse)
4469 {
4470 status=MagickFalse;
4471 break;
4472 }
4473 }
4474 scale_view=DestroyCacheView(scale_view);
4475 image_view=DestroyCacheView(image_view);
4476 /*
4477 Free allocated memory.
4478 */
4479 y_vector=(double *) RelinquishMagickMemory(y_vector);
4480 scale_scanline=(double *) RelinquishMagickMemory(scale_scanline);
4481 if (scale_image->rows != image->rows)
4482 scanline=(double *) RelinquishMagickMemory(scanline);
4483 x_vector=(double *) RelinquishMagickMemory(x_vector);
4484 scale_image->type=image->type;
4485 if (status == MagickFalse)
4486 scale_image=DestroyImage(scale_image);
4487 return(scale_image);
4488}
4489
4490/*
4491%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
4492% %
4493% %
4494% %
4495% T h u m b n a i l I m a g e %
4496% %
4497% %
4498% %
4499%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
4500%
4501% ThumbnailImage() changes the size of an image to the given dimensions and
4502% removes any associated profiles. The goal is to produce small low cost
4503% thumbnail images suited for display on the Web.
4504%
4505% The format of the ThumbnailImage method is:
4506%
4507% Image *ThumbnailImage(const Image *image,const size_t columns,
4508% const size_t rows,ExceptionInfo *exception)
4509%
4510% A description of each parameter follows:
4511%
4512% o image: the image.
4513%
4514% o columns: the number of columns in the scaled image.
4515%
4516% o rows: the number of rows in the scaled image.
4517%
4518% o exception: return any errors or warnings in this structure.
4519%
4520*/
4521MagickExport Image *ThumbnailImage(const Image *image,const size_t columns,
4522 const size_t rows,ExceptionInfo *exception)
4523{
4524#define SampleFactor 5
4525
4526 char
4527 filename[MagickPathExtent],
4528 value[MagickPathExtent];
4529
4530 const char
4531 *name;
4532
4533 Image
4534 *thumbnail_image;
4535
4536 struct stat
4537 attributes;
4538
4539 assert(image != (Image *) NULL);
4540 assert(image->signature == MagickCoreSignature);
4541 assert(exception != (ExceptionInfo *) NULL);
4542 assert(exception->signature == MagickCoreSignature);
4543 if (IsEventLogging() != MagickFalse)
4544 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
4545 thumbnail_image=CloneImage(image,0,0,MagickTrue,exception);
4546 if (thumbnail_image == (Image *) NULL)
4547 return(thumbnail_image);
4548 if ((columns != image->columns) || (rows != image->rows))
4549 {
4550 Image
4551 *clone_image = thumbnail_image;
4552
4553 ssize_t
4554 x_factor,
4555 y_factor;
4556
4557 x_factor=(ssize_t) image->columns/(ssize_t) columns;
4558 y_factor=(ssize_t) image->rows/(ssize_t) rows;
4559 if ((x_factor > 4) && (y_factor > 4))
4560 {
4561 thumbnail_image=SampleImage(clone_image,4*columns,4*rows,exception);
4562 if (thumbnail_image != (Image *) NULL)
4563 {
4564 clone_image=DestroyImage(clone_image);
4565 clone_image=thumbnail_image;
4566 }
4567 }
4568 if ((x_factor > 2) && (y_factor > 2))
4569 {
4570 thumbnail_image=ResizeImage(clone_image,2*columns,2*rows,BoxFilter,
4571 exception);
4572 if (thumbnail_image != (Image *) NULL)
4573 {
4574 clone_image=DestroyImage(clone_image);
4575 clone_image=thumbnail_image;
4576 }
4577 }
4578 thumbnail_image=ResizeImage(clone_image,columns,rows,image->filter ==
4579 UndefinedFilter ? LanczosSharpFilter : image->filter,exception);
4580 clone_image=DestroyImage(clone_image);
4581 if (thumbnail_image == (Image *) NULL)
4582 return(thumbnail_image);
4583 }
4584 (void) ParseAbsoluteGeometry("0x0+0+0",&thumbnail_image->page);
4585 thumbnail_image->depth=8;
4586 thumbnail_image->interlace=NoInterlace;
4587 /*
4588 Strip all profiles except color profiles.
4589 */
4590 ResetImageProfileIterator(thumbnail_image);
4591 for (name=GetNextImageProfile(thumbnail_image); name != (const char *) NULL; )
4592 {
4593 if ((LocaleCompare(name,"icc") != 0) && (LocaleCompare(name,"icm") != 0))
4594 {
4595 (void) DeleteImageProfile(thumbnail_image,name);
4596 ResetImageProfileIterator(thumbnail_image);
4597 }
4598 name=GetNextImageProfile(thumbnail_image);
4599 }
4600 (void) DeleteImageProperty(thumbnail_image,"comment");
4601 (void) CopyMagickString(value,image->magick_filename,MagickPathExtent);
4602 if (strstr(image->magick_filename,"//") == (char *) NULL)
4603 (void) FormatLocaleString(value,MagickPathExtent,"file://%s",
4604 image->magick_filename);
4605 (void) SetImageProperty(thumbnail_image,"Thumb::URI",value,exception);
4606 GetPathComponent(image->magick_filename,TailPath,filename);
4607 (void) CopyMagickString(value,filename,MagickPathExtent);
4608 if ( GetPathAttributes(image->filename,&attributes) != MagickFalse )
4609 (void) FormatImageProperty(thumbnail_image,"Thumb::MTime","%.20g",(double)
4610 attributes.st_mtime);
4611 (void) FormatLocaleString(value,MagickPathExtent,"%.20g",(double)
4612 attributes.st_mtime);
4613 (void) FormatMagickSize(GetBlobSize(image),MagickFalse,"B",MagickPathExtent,
4614 value);
4615 (void) SetImageProperty(thumbnail_image,"Thumb::Size",value,exception);
4616 (void) FormatLocaleString(value,MagickPathExtent,"image/%s",image->magick);
4617 LocaleLower(value);
4618 (void) SetImageProperty(thumbnail_image,"Thumb::Mimetype",value,exception);
4619 (void) SetImageProperty(thumbnail_image,"software",MagickAuthoritativeURL,
4620 exception);
4621 (void) FormatImageProperty(thumbnail_image,"Thumb::Image::Width","%.20g",
4622 (double) image->magick_columns);
4623 (void) FormatImageProperty(thumbnail_image,"Thumb::Image::Height","%.20g",
4624 (double) image->magick_rows);
4625 (void) FormatImageProperty(thumbnail_image,"Thumb::Document::Pages","%.20g",
4626 (double) GetImageListLength(image));
4627 return(thumbnail_image);
4628}