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