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