MagickCore 7.1.2-26
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 return(resize_image);
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 {
3878 char
3879 transform[MagickPathExtent];
3880
3881 (void) FormatLocaleString(transform,MagickPathExtent,
3882 "resize %.20gx%.20g %.20gx%.20g",(double) image->columns,
3883 (double) image->rows,(double) resize_image->columns,
3884 (double) resize_image->rows);
3885 AppendImageProfileProperty(resize_image,"hdrgm","hdrgm:Transform",
3886 transform,exception);
3887 }
3888 return(resize_image);
3889}
3890
3891/*
3892%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3893% %
3894% %
3895% %
3896% S a m p l e I m a g e %
3897% %
3898% %
3899% %
3900%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3901%
3902% SampleImage() scales an image to the desired dimensions with pixel
3903% sampling. Unlike other scaling methods, this method does not introduce
3904% any additional color into the scaled image.
3905%
3906% The format of the SampleImage method is:
3907%
3908% Image *SampleImage(const Image *image,const size_t columns,
3909% const size_t rows,ExceptionInfo *exception)
3910%
3911% A description of each parameter follows:
3912%
3913% o image: the image.
3914%
3915% o columns: the number of columns in the sampled image.
3916%
3917% o rows: the number of rows in the sampled image.
3918%
3919% o exception: return any errors or warnings in this structure.
3920%
3921*/
3922MagickExport Image *SampleImage(const Image *image,const size_t columns,
3923 const size_t rows,ExceptionInfo *exception)
3924{
3925#define SampleImageTag "Sample/Image"
3926
3927 CacheView
3928 *image_view,
3929 *sample_view;
3930
3931 Image
3932 *sample_image;
3933
3934 MagickBooleanType
3935 status;
3936
3937 MagickOffsetType
3938 progress;
3939
3940 PointInfo
3941 sample_offset;
3942
3943 ssize_t
3944 y;
3945
3946 /*
3947 Initialize sampled image attributes.
3948 */
3949 assert(image != (const Image *) NULL);
3950 assert(image->signature == MagickCoreSignature);
3951 assert(exception != (ExceptionInfo *) NULL);
3952 assert(exception->signature == MagickCoreSignature);
3953 if (IsEventLogging() != MagickFalse)
3954 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
3955 if ((columns == 0) || (rows == 0))
3956 ThrowImageException(ImageError,"NegativeOrZeroImageSize");
3957 if ((columns == image->columns) && (rows == image->rows))
3958 return(CloneImage(image,0,0,MagickTrue,exception));
3959 sample_image=CloneImage(image,columns,rows,MagickTrue,exception);
3960 if (sample_image == (Image *) NULL)
3961 return((Image *) NULL);
3962 /*
3963 Set the sampling offset, default is in the mid-point of sample regions.
3964 */
3965 sample_offset.x=0.5-MagickEpsilon;
3966 sample_offset.y=sample_offset.x;
3967 {
3968 const char
3969 *value;
3970
3971 value=GetImageArtifact(image,"sample:offset");
3972 if (value != (char *) NULL)
3973 {
3974 GeometryInfo
3975 geometry_info;
3976
3977 MagickStatusType
3978 flags;
3979
3980 (void) ParseGeometry(value,&geometry_info);
3981 flags=ParseGeometry(value,&geometry_info);
3982 sample_offset.x=sample_offset.y=geometry_info.rho/100.0-MagickEpsilon;
3983 if ((flags & SigmaValue) != 0)
3984 sample_offset.y=geometry_info.sigma/100.0-MagickEpsilon;
3985 }
3986 }
3987 /*
3988 Sample each row.
3989 */
3990 status=MagickTrue;
3991 progress=0;
3992 image_view=AcquireVirtualCacheView(image,exception);
3993 sample_view=AcquireAuthenticCacheView(sample_image,exception);
3994#if defined(MAGICKCORE_OPENMP_SUPPORT)
3995 #pragma omp parallel for schedule(static) shared(status) \
3996 magick_number_threads(image,sample_image,sample_image->rows,2)
3997#endif
3998 for (y=0; y < (ssize_t) sample_image->rows; y++)
3999 {
4000 Quantum
4001 *magick_restrict q;
4002
4003 ssize_t
4004 x;
4005
4006 if (status == MagickFalse)
4007 continue;
4008 q=QueueCacheViewAuthenticPixels(sample_view,0,y,sample_image->columns,1,
4009 exception);
4010 if (q == (Quantum *) NULL)
4011 {
4012 status=MagickFalse;
4013 continue;
4014 }
4015 /*
4016 Sample each column.
4017 */
4018 for (x=0; x < (ssize_t) sample_image->columns; x++)
4019 {
4020 const Quantum
4021 *magick_restrict p;
4022
4023 ssize_t
4024 i,
4025 x_offset,
4026 y_offset;
4027
4028 if (GetPixelWriteMask(sample_image,q) <= (QuantumRange/2))
4029 {
4030 q+=(ptrdiff_t) GetPixelChannels(sample_image);
4031 continue;
4032 }
4033 x_offset=(ssize_t) ((((double) x+sample_offset.x)*image->columns)/
4034 sample_image->columns);
4035 y_offset=(ssize_t) ((((double) y+sample_offset.y)*image->rows)/
4036 sample_image->rows);
4037 p=GetCacheViewVirtualPixels(image_view,x_offset,y_offset,1,1,exception);
4038 if (p == (const Quantum *) NULL)
4039 {
4040 status=MagickFalse;
4041 break;
4042 }
4043 for (i=0; i < (ssize_t) GetPixelChannels(sample_image); i++)
4044 {
4045 PixelChannel
4046 channel;
4047
4048 PixelTrait
4049 image_traits,
4050 traits;
4051
4052 channel=GetPixelChannelChannel(sample_image,i);
4053 traits=GetPixelChannelTraits(sample_image,channel);
4054 image_traits=GetPixelChannelTraits(image,channel);
4055 if ((traits == UndefinedPixelTrait) ||
4056 (image_traits == UndefinedPixelTrait))
4057 continue;
4058 SetPixelChannel(sample_image,channel,p[i],q);
4059 }
4060 q+=(ptrdiff_t) GetPixelChannels(sample_image);
4061 }
4062 if (SyncCacheViewAuthenticPixels(sample_view,exception) == MagickFalse)
4063 status=MagickFalse;
4064 if (image->progress_monitor != (MagickProgressMonitor) NULL)
4065 {
4066 MagickBooleanType
4067 proceed;
4068
4069 proceed=SetImageProgress(image,SampleImageTag,progress++,image->rows);
4070 if (proceed == MagickFalse)
4071 status=MagickFalse;
4072 }
4073 }
4074 image_view=DestroyCacheView(image_view);
4075 sample_view=DestroyCacheView(sample_view);
4076 sample_image->type=image->type;
4077 if (status == MagickFalse)
4078 sample_image=DestroyImage(sample_image);
4079 return(sample_image);
4080}
4081
4082/*
4083%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
4084% %
4085% %
4086% %
4087% S c a l e I m a g e %
4088% %
4089% %
4090% %
4091%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
4092%
4093% ScaleImage() changes the size of an image to the given dimensions.
4094%
4095% The format of the ScaleImage method is:
4096%
4097% Image *ScaleImage(const Image *image,const size_t columns,
4098% const size_t rows,ExceptionInfo *exception)
4099%
4100% A description of each parameter follows:
4101%
4102% o image: the image.
4103%
4104% o columns: the number of columns in the scaled image.
4105%
4106% o rows: the number of rows in the scaled image.
4107%
4108% o exception: return any errors or warnings in this structure.
4109%
4110*/
4111MagickExport Image *ScaleImage(const Image *image,const size_t columns,
4112 const size_t rows,ExceptionInfo *exception)
4113{
4114#define ScaleImageTag "Scale/Image"
4115
4116 CacheView
4117 *image_view,
4118 *scale_view;
4119
4120 double
4121 alpha,
4122 pixel[CompositePixelChannel],
4123 *scale_scanline,
4124 *scanline,
4125 *x_vector,
4126 *y_vector;
4127
4128 Image
4129 *scale_image;
4130
4131 MagickBooleanType
4132 next_column,
4133 next_row,
4134 proceed,
4135 status;
4136
4137 PixelTrait
4138 scale_traits;
4139
4140 PointInfo
4141 scale,
4142 span;
4143
4144 ssize_t
4145 i,
4146 n,
4147 number_rows,
4148 y;
4149
4150 /*
4151 Initialize scaled image attributes.
4152 */
4153 assert(image != (const Image *) NULL);
4154 assert(image->signature == MagickCoreSignature);
4155 assert(exception != (ExceptionInfo *) NULL);
4156 assert(exception->signature == MagickCoreSignature);
4157 if (IsEventLogging() != MagickFalse)
4158 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
4159 if ((columns == 0) || (rows == 0))
4160 ThrowImageException(ImageError,"NegativeOrZeroImageSize");
4161 if ((columns == image->columns) && (rows == image->rows))
4162 return(CloneImage(image,0,0,MagickTrue,exception));
4163 scale_image=CloneImage(image,columns,rows,MagickTrue,exception);
4164 if (scale_image == (Image *) NULL)
4165 return((Image *) NULL);
4166 if (SetImageStorageClass(scale_image,DirectClass,exception) == MagickFalse)
4167 {
4168 scale_image=DestroyImage(scale_image);
4169 return((Image *) NULL);
4170 }
4171 /*
4172 Allocate memory.
4173 */
4174 x_vector=(double *) AcquireQuantumMemory((size_t) image->columns,
4175 MaxPixelChannels*sizeof(*x_vector));
4176 scanline=x_vector;
4177 if (image->rows != scale_image->rows)
4178 scanline=(double *) AcquireQuantumMemory((size_t) image->columns,
4179 MaxPixelChannels*sizeof(*scanline));
4180 scale_scanline=(double *) AcquireQuantumMemory((size_t) scale_image->columns,
4181 MaxPixelChannels*sizeof(*scale_scanline));
4182 y_vector=(double *) AcquireQuantumMemory((size_t) image->columns,
4183 MaxPixelChannels*sizeof(*y_vector));
4184 if ((scanline == (double *) NULL) || (scale_scanline == (double *) NULL) ||
4185 (x_vector == (double *) NULL) || (y_vector == (double *) NULL))
4186 {
4187 if ((image->rows != scale_image->rows) && (scanline != (double *) NULL))
4188 scanline=(double *) RelinquishMagickMemory(scanline);
4189 if (scale_scanline != (double *) NULL)
4190 scale_scanline=(double *) RelinquishMagickMemory(scale_scanline);
4191 if (x_vector != (double *) NULL)
4192 x_vector=(double *) RelinquishMagickMemory(x_vector);
4193 if (y_vector != (double *) NULL)
4194 y_vector=(double *) RelinquishMagickMemory(y_vector);
4195 scale_image=DestroyImage(scale_image);
4196 ThrowImageException(ResourceLimitError,"MemoryAllocationFailed");
4197 }
4198 /*
4199 Scale image.
4200 */
4201 number_rows=0;
4202 next_row=MagickTrue;
4203 span.y=1.0;
4204 scale.y=(double) scale_image->rows/(double) image->rows;
4205 (void) memset(y_vector,0,(size_t) MaxPixelChannels*image->columns*
4206 sizeof(*y_vector));
4207 n=0;
4208 status=MagickTrue;
4209 image_view=AcquireVirtualCacheView(image,exception);
4210 scale_view=AcquireAuthenticCacheView(scale_image,exception);
4211 for (y=0; y < (ssize_t) scale_image->rows; y++)
4212 {
4213 const Quantum
4214 *magick_restrict p;
4215
4216 Quantum
4217 *magick_restrict q;
4218
4219 ssize_t
4220 x;
4221
4222 if (status == MagickFalse)
4223 break;
4224 q=QueueCacheViewAuthenticPixels(scale_view,0,y,scale_image->columns,1,
4225 exception);
4226 if (q == (Quantum *) NULL)
4227 {
4228 status=MagickFalse;
4229 break;
4230 }
4231 alpha=1.0;
4232 if (scale_image->rows == image->rows)
4233 {
4234 /*
4235 Read a new scanline.
4236 */
4237 p=GetCacheViewVirtualPixels(image_view,0,n++,image->columns,1,
4238 exception);
4239 if (p == (const Quantum *) NULL)
4240 {
4241 status=MagickFalse;
4242 break;
4243 }
4244 for (x=0; x < (ssize_t) image->columns; x++)
4245 {
4246 if (GetPixelWriteMask(image,p) <= (QuantumRange/2))
4247 {
4248 p+=(ptrdiff_t) GetPixelChannels(image);
4249 continue;
4250 }
4251 if (image->alpha_trait != UndefinedPixelTrait)
4252 alpha=QuantumScale*(double) GetPixelAlpha(image,p);
4253 for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
4254 {
4255 PixelChannel channel = GetPixelChannelChannel(image,i);
4256 PixelTrait traits = GetPixelChannelTraits(image,channel);
4257 if ((traits & BlendPixelTrait) == 0)
4258 {
4259 x_vector[x*(ssize_t) GetPixelChannels(image)+i]=(double) p[i];
4260 continue;
4261 }
4262 x_vector[x*(ssize_t) GetPixelChannels(image)+i]=alpha*(double) p[i];
4263 }
4264 p+=(ptrdiff_t) GetPixelChannels(image);
4265 }
4266 }
4267 else
4268 {
4269 /*
4270 Scale Y direction.
4271 */
4272 while (scale.y < span.y)
4273 {
4274 if ((next_row != MagickFalse) &&
4275 (number_rows < (ssize_t) image->rows))
4276 {
4277 /*
4278 Read a new scanline.
4279 */
4280 p=GetCacheViewVirtualPixels(image_view,0,n++,image->columns,1,
4281 exception);
4282 if (p == (const Quantum *) NULL)
4283 {
4284 status=MagickFalse;
4285 break;
4286 }
4287 for (x=0; x < (ssize_t) image->columns; x++)
4288 {
4289 if (GetPixelWriteMask(image,p) <= (QuantumRange/2))
4290 {
4291 p+=(ptrdiff_t) GetPixelChannels(image);
4292 continue;
4293 }
4294 if (image->alpha_trait != UndefinedPixelTrait)
4295 alpha=QuantumScale*(double) GetPixelAlpha(image,p);
4296 for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
4297 {
4298 PixelChannel channel = GetPixelChannelChannel(image,i);
4299 PixelTrait traits = GetPixelChannelTraits(image,channel);
4300 if ((traits & BlendPixelTrait) == 0)
4301 {
4302 x_vector[x*(ssize_t) GetPixelChannels(image)+i]=
4303 (double) p[i];
4304 continue;
4305 }
4306 x_vector[x*(ssize_t) GetPixelChannels(image)+i]=alpha*
4307 (double) p[i];
4308 }
4309 p+=(ptrdiff_t) GetPixelChannels(image);
4310 }
4311 number_rows++;
4312 }
4313 for (x=0; x < (ssize_t) image->columns; x++)
4314 for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
4315 y_vector[x*(ssize_t) GetPixelChannels(image)+i]+=scale.y*
4316 x_vector[x*(ssize_t) GetPixelChannels(image)+i];
4317 span.y-=scale.y;
4318 scale.y=(double) scale_image->rows/(double) image->rows;
4319 next_row=MagickTrue;
4320 }
4321 if ((next_row != MagickFalse) && (number_rows < (ssize_t) image->rows))
4322 {
4323 /*
4324 Read a new scanline.
4325 */
4326 p=GetCacheViewVirtualPixels(image_view,0,n++,image->columns,1,
4327 exception);
4328 if (p == (const Quantum *) NULL)
4329 {
4330 status=MagickFalse;
4331 break;
4332 }
4333 for (x=0; x < (ssize_t) image->columns; x++)
4334 {
4335 if (GetPixelWriteMask(image,p) <= (QuantumRange/2))
4336 {
4337 p+=(ptrdiff_t) GetPixelChannels(image);
4338 continue;
4339 }
4340 if (image->alpha_trait != UndefinedPixelTrait)
4341 alpha=QuantumScale*(double) GetPixelAlpha(image,p);
4342 for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
4343 {
4344 PixelChannel channel = GetPixelChannelChannel(image,i);
4345 PixelTrait traits = GetPixelChannelTraits(image,channel);
4346 if ((traits & BlendPixelTrait) == 0)
4347 {
4348 x_vector[x*(ssize_t) GetPixelChannels(image)+i]=
4349 (double) p[i];
4350 continue;
4351 }
4352 x_vector[x*(ssize_t) GetPixelChannels(image)+i]=alpha*
4353 (double) p[i];
4354 }
4355 p+=(ptrdiff_t) GetPixelChannels(image);
4356 }
4357 number_rows++;
4358 next_row=MagickFalse;
4359 }
4360 for (x=0; x < (ssize_t) image->columns; x++)
4361 {
4362 for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
4363 {
4364 pixel[i]=y_vector[x*(ssize_t) GetPixelChannels(image)+i]+span.y*
4365 x_vector[x*(ssize_t) GetPixelChannels(image)+i];
4366 scanline[x*(ssize_t) GetPixelChannels(image)+i]=pixel[i];
4367 y_vector[x*(ssize_t) GetPixelChannels(image)+i]=0.0;
4368 }
4369 }
4370 scale.y-=span.y;
4371 if (scale.y <= 0)
4372 {
4373 scale.y=(double) scale_image->rows/(double) image->rows;
4374 next_row=MagickTrue;
4375 }
4376 span.y=1.0;
4377 }
4378 if (scale_image->columns == image->columns)
4379 {
4380 /*
4381 Transfer scanline to scaled image.
4382 */
4383 for (x=0; x < (ssize_t) scale_image->columns; x++)
4384 {
4385 if (GetPixelWriteMask(scale_image,q) <= (QuantumRange/2))
4386 {
4387 q+=(ptrdiff_t) GetPixelChannels(scale_image);
4388 continue;
4389 }
4390 if (image->alpha_trait != UndefinedPixelTrait)
4391 {
4392 alpha=QuantumScale*scanline[x*(ssize_t) GetPixelChannels(image)+
4393 GetPixelChannelOffset(image,AlphaPixelChannel)];
4394 alpha=MagickSafeReciprocal(alpha);
4395 }
4396 for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
4397 {
4398 PixelChannel channel = GetPixelChannelChannel(image,i);
4399 PixelTrait traits = GetPixelChannelTraits(image,channel);
4400 scale_traits=GetPixelChannelTraits(scale_image,channel);
4401 if ((traits == UndefinedPixelTrait) ||
4402 (scale_traits == UndefinedPixelTrait))
4403 continue;
4404 if ((traits & BlendPixelTrait) == 0)
4405 {
4406 SetPixelChannel(scale_image,channel,ClampToQuantum(
4407 scanline[x*(ssize_t) GetPixelChannels(image)+i]),q);
4408 continue;
4409 }
4410 SetPixelChannel(scale_image,channel,ClampToQuantum(alpha*scanline[
4411 x*(ssize_t) GetPixelChannels(image)+i]),q);
4412 }
4413 q+=(ptrdiff_t) GetPixelChannels(scale_image);
4414 }
4415 }
4416 else
4417 {
4418 ssize_t
4419 t;
4420
4421 /*
4422 Scale X direction.
4423 */
4424 for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
4425 pixel[i]=0.0;
4426 next_column=MagickFalse;
4427 span.x=1.0;
4428 t=0;
4429 for (x=0; x < (ssize_t) image->columns; x++)
4430 {
4431 scale.x=(double) scale_image->columns/(double) image->columns;
4432 while (scale.x >= span.x)
4433 {
4434 if (next_column != MagickFalse)
4435 {
4436 for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
4437 pixel[i]=0.0;
4438 t++;
4439 }
4440 for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
4441 {
4442 PixelChannel channel = GetPixelChannelChannel(image,i);
4443 PixelTrait traits = GetPixelChannelTraits(image,channel);
4444 if (traits == UndefinedPixelTrait)
4445 continue;
4446 pixel[i]+=span.x*scanline[x*(ssize_t) GetPixelChannels(image)+i];
4447 scale_scanline[t*(ssize_t) GetPixelChannels(image)+i]=pixel[i];
4448 }
4449 scale.x-=span.x;
4450 span.x=1.0;
4451 next_column=MagickTrue;
4452 }
4453 if (scale.x > 0)
4454 {
4455 if (next_column != MagickFalse)
4456 {
4457 for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
4458 pixel[i]=0.0;
4459 next_column=MagickFalse;
4460 t++;
4461 }
4462 for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
4463 pixel[i]+=scale.x*scanline[x*(ssize_t)
4464 GetPixelChannels(image)+i];
4465 span.x-=scale.x;
4466 }
4467 }
4468 if (span.x > 0)
4469 {
4470 for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
4471 pixel[i]+=span.x*
4472 scanline[(x-1)*(ssize_t) GetPixelChannels(image)+i];
4473 }
4474 if ((next_column == MagickFalse) && (t < (ssize_t) scale_image->columns))
4475 for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
4476 scale_scanline[t*(ssize_t) GetPixelChannels(image)+i]=pixel[i];
4477 /*
4478 Transfer scanline to scaled image.
4479 */
4480 for (x=0; x < (ssize_t) scale_image->columns; x++)
4481 {
4482 if (GetPixelWriteMask(scale_image,q) <= (QuantumRange/2))
4483 {
4484 q+=(ptrdiff_t) GetPixelChannels(scale_image);
4485 continue;
4486 }
4487 if (image->alpha_trait != UndefinedPixelTrait)
4488 {
4489 alpha=QuantumScale*scale_scanline[x*(ssize_t)
4490 GetPixelChannels(image)+
4491 GetPixelChannelOffset(image,AlphaPixelChannel)];
4492 alpha=MagickSafeReciprocal(alpha);
4493 }
4494 for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
4495 {
4496 PixelChannel channel = GetPixelChannelChannel(image,i);
4497 PixelTrait traits = GetPixelChannelTraits(image,channel);
4498 scale_traits=GetPixelChannelTraits(scale_image,channel);
4499 if ((traits == UndefinedPixelTrait) ||
4500 (scale_traits == UndefinedPixelTrait))
4501 continue;
4502 if ((traits & BlendPixelTrait) == 0)
4503 {
4504 SetPixelChannel(scale_image,channel,ClampToQuantum(
4505 scale_scanline[x*(ssize_t) GetPixelChannels(image)+i]),q);
4506 continue;
4507 }
4508 SetPixelChannel(scale_image,channel,ClampToQuantum(alpha*
4509 scale_scanline[x*(ssize_t) GetPixelChannels(image)+i]),q);
4510 }
4511 q+=(ptrdiff_t) GetPixelChannels(scale_image);
4512 }
4513 }
4514 if (SyncCacheViewAuthenticPixels(scale_view,exception) == MagickFalse)
4515 {
4516 status=MagickFalse;
4517 break;
4518 }
4519 proceed=SetImageProgress(image,ScaleImageTag,(MagickOffsetType) y,
4520 image->rows);
4521 if (proceed == MagickFalse)
4522 {
4523 status=MagickFalse;
4524 break;
4525 }
4526 }
4527 scale_view=DestroyCacheView(scale_view);
4528 image_view=DestroyCacheView(image_view);
4529 /*
4530 Free allocated memory.
4531 */
4532 y_vector=(double *) RelinquishMagickMemory(y_vector);
4533 scale_scanline=(double *) RelinquishMagickMemory(scale_scanline);
4534 if (scale_image->rows != image->rows)
4535 scanline=(double *) RelinquishMagickMemory(scanline);
4536 x_vector=(double *) RelinquishMagickMemory(x_vector);
4537 scale_image->type=image->type;
4538 if (status == MagickFalse)
4539 scale_image=DestroyImage(scale_image);
4540 return(scale_image);
4541}
4542
4543/*
4544%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
4545% %
4546% %
4547% %
4548% T h u m b n a i l I m a g e %
4549% %
4550% %
4551% %
4552%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
4553%
4554% ThumbnailImage() changes the size of an image to the given dimensions and
4555% removes any associated profiles. The goal is to produce small low cost
4556% thumbnail images suited for display on the Web.
4557%
4558% The format of the ThumbnailImage method is:
4559%
4560% Image *ThumbnailImage(const Image *image,const size_t columns,
4561% const size_t rows,ExceptionInfo *exception)
4562%
4563% A description of each parameter follows:
4564%
4565% o image: the image.
4566%
4567% o columns: the number of columns in the scaled image.
4568%
4569% o rows: the number of rows in the scaled image.
4570%
4571% o exception: return any errors or warnings in this structure.
4572%
4573*/
4574
4575static void url_encode(const char *uri,char *encode_uri)
4576{
4577 char
4578 *p;
4579
4580 const char
4581 *hex = "0123456789ABCDEF";
4582
4583 for (p=encode_uri; *uri != '\0'; uri++)
4584 if ((('a' <= *uri) && (*uri <= 'z')) || (('A' <= *uri) && (*uri <= 'Z')) ||
4585 (('0' <= *uri) && (*uri <= '9')) || (strchr("/-_.~",*uri) != 0))
4586 *p++=(*uri);
4587 else
4588 {
4589 *p++='%';
4590 *p++=hex[(*uri >> 4) & 0xF];
4591 *p++=hex[*uri & 0xF];
4592 }
4593 *p='\0';
4594}
4595
4596MagickExport Image *ThumbnailImage(const Image *image,const size_t columns,
4597 const size_t rows,ExceptionInfo *exception)
4598{
4599#define SampleFactor 5
4600
4601 char
4602 encode_uri[3*MagickPathExtent+1] = "/0";
4603
4604 const char
4605 *name,
4606 *mime_type;
4607
4608 Image
4609 *thumbnail_image;
4610
4611 struct stat
4612 attributes;
4613
4614 assert(image != (Image *) NULL);
4615 assert(image->signature == MagickCoreSignature);
4616 assert(exception != (ExceptionInfo *) NULL);
4617 assert(exception->signature == MagickCoreSignature);
4618 if (IsEventLogging() != MagickFalse)
4619 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
4620 thumbnail_image=CloneImage(image,0,0,MagickTrue,exception);
4621 if (thumbnail_image == (Image *) NULL)
4622 return(thumbnail_image);
4623 if ((columns != image->columns) || (rows != image->rows))
4624 {
4625 Image
4626 *clone_image = thumbnail_image;
4627
4628 ssize_t
4629 x_factor,
4630 y_factor;
4631
4632 x_factor=(ssize_t) (image->columns*MagickSafeReciprocal((double)
4633 columns));
4634 y_factor=(ssize_t) (image->rows*MagickSafeReciprocal((double) rows));
4635 if ((x_factor > 4) && (y_factor > 4))
4636 {
4637 thumbnail_image=SampleImage(clone_image,4*columns,4*rows,exception);
4638 if (thumbnail_image != (Image *) NULL)
4639 {
4640 clone_image=DestroyImage(clone_image);
4641 clone_image=thumbnail_image;
4642 }
4643 }
4644 if ((x_factor > 2) && (y_factor > 2))
4645 {
4646 thumbnail_image=ResizeImage(clone_image,2*columns,2*rows,BoxFilter,
4647 exception);
4648 if (thumbnail_image != (Image *) NULL)
4649 {
4650 clone_image=DestroyImage(clone_image);
4651 clone_image=thumbnail_image;
4652 }
4653 }
4654 thumbnail_image=ResizeImage(clone_image,columns,rows,image->filter ==
4655 UndefinedFilter ? LanczosSharpFilter : image->filter,exception);
4656 clone_image=DestroyImage(clone_image);
4657 if (thumbnail_image == (Image *) NULL)
4658 return(thumbnail_image);
4659 }
4660 (void) ParseAbsoluteGeometry("0x0+0+0",&thumbnail_image->page);
4661 thumbnail_image->depth=8;
4662 thumbnail_image->interlace=NoInterlace;
4663 /*
4664 Strip all profiles except color profiles.
4665 */
4666 ResetImageProfileIterator(thumbnail_image);
4667 for (name=GetNextImageProfile(thumbnail_image); name != (const char *) NULL; )
4668 {
4669 if ((LocaleCompare(name,"icc") != 0) && (LocaleCompare(name,"icm") != 0))
4670 {
4671 (void) DeleteImageProfile(thumbnail_image,name);
4672 ResetImageProfileIterator(thumbnail_image);
4673 }
4674 name=GetNextImageProfile(thumbnail_image);
4675 }
4676 (void) DeleteImageProperty(thumbnail_image,"comment");
4677 url_encode(image->filename,encode_uri);
4678 if (*image->filename != '/')
4679 (void) FormatImageProperty(thumbnail_image,"Thumb::URI","./%s",encode_uri);
4680 else
4681 (void) FormatImageProperty(thumbnail_image,"Thumb::URI","file://%s",
4682 encode_uri);
4683 if (GetPathAttributes(image->filename,&attributes) != MagickFalse )
4684 (void) FormatImageProperty(thumbnail_image,"Thumb::MTime","%.20g",(double)
4685 attributes.st_mtime);
4686 (void) FormatImageProperty(thumbnail_image,"Thumb::Size","%.20g",
4687 (double) GetBlobSize(image));
4688 mime_type=GetImageProperty(image,"mime:type",exception);
4689 if (mime_type != (const char *) NULL)
4690 (void) SetImageProperty(thumbnail_image,"Thumb::Mimetype",mime_type,
4691 exception);
4692 (void) SetImageProperty(thumbnail_image,"software",MagickAuthoritativeURL,
4693 exception);
4694 (void) FormatImageProperty(thumbnail_image,"Thumb::Image::Width","%.20g",
4695 (double) image->magick_columns);
4696 (void) FormatImageProperty(thumbnail_image,"Thumb::Image::Height","%.20g",
4697 (double) image->magick_rows);
4698 (void) FormatImageProperty(thumbnail_image,"Thumb::Document::Pages","%.20g",
4699 (double) GetImageListLength(image));
4700 return(thumbnail_image);
4701}