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