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