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