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