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