blob: 51e413d60637ad5236c33a4cd78959945a4dc079 [file] [log] [blame]
cristy3ed852e2009-09-05 21:47:34 +00001/*
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% John Cristy %
17% July 1992 %
18% %
19% %
cristy16af1cb2009-12-11 21:38:29 +000020% Copyright 1999-2010 ImageMagick Studio LLC, a non-profit organization %
cristy3ed852e2009-09-05 21:47:34 +000021% 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% http://www.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/artifact.h"
44#include "magick/blob.h"
45#include "magick/cache.h"
46#include "magick/cache-view.h"
47#include "magick/color.h"
48#include "magick/color-private.h"
49#include "magick/draw.h"
50#include "magick/exception.h"
51#include "magick/exception-private.h"
52#include "magick/gem.h"
53#include "magick/image.h"
54#include "magick/image-private.h"
55#include "magick/list.h"
56#include "magick/memory_.h"
anthony55f12332010-09-10 01:13:02 +000057#include "magick/magick.h"
cristy3ed852e2009-09-05 21:47:34 +000058#include "magick/pixel-private.h"
59#include "magick/property.h"
60#include "magick/monitor.h"
61#include "magick/monitor-private.h"
62#include "magick/pixel.h"
63#include "magick/option.h"
64#include "magick/resample.h"
65#include "magick/resize.h"
66#include "magick/resize-private.h"
67#include "magick/string_.h"
cristyf2f27272009-12-17 14:48:46 +000068#include "magick/string-private.h"
cristy3ed852e2009-09-05 21:47:34 +000069#include "magick/thread-private.h"
70#include "magick/utility.h"
71#include "magick/version.h"
72#if defined(MAGICKCORE_LQR_DELEGATE)
73#include <lqr.h>
74#endif
75
76/*
77 Typedef declarations.
78*/
79struct _ResizeFilter
80{
81 MagickRealType
82 (*filter)(const MagickRealType,const ResizeFilter *),
83 (*window)(const MagickRealType,const ResizeFilter *),
cristy33b1c162010-01-23 22:51:51 +000084 support, /* filter region of support - the filter support limit */
85 window_support, /* window support, usally equal to support (expert only) */
anthony55f12332010-09-10 01:13:02 +000086 scale, /* dimension scaling to fit window support (usally 1.0) */
cristy33b1c162010-01-23 22:51:51 +000087 blur, /* x-scale (blur-sharpen) */
nicolase3b9eca2010-10-24 19:48:22 +000088 coeff[7]; /* cubic coefficents for BC-cubic spline filters */
cristy3ed852e2009-09-05 21:47:34 +000089
cristybb503372010-05-27 20:51:26 +000090 size_t
cristy3ed852e2009-09-05 21:47:34 +000091 signature;
92};
93
94/*
95 Forward declaractions.
96*/
97static MagickRealType
98 I0(MagickRealType x),
anthonyb6d08c52010-09-13 01:17:04 +000099 BesselOrderOne(MagickRealType),
anthony07a3f7f2010-09-16 03:03:11 +0000100 Sinc(const MagickRealType, const ResizeFilter *),
anthonyba5a7c32010-09-15 02:42:25 +0000101 SincFast(const MagickRealType, const ResizeFilter *);
cristy3ed852e2009-09-05 21:47:34 +0000102
103/*
104%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
105% %
106% %
107% %
108+ F i l t e r F u n c t i o n s %
109% %
110% %
111% %
112%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
113%
cristy33b1c162010-01-23 22:51:51 +0000114% These are the various filter and windowing functions that are provided.
cristy3ed852e2009-09-05 21:47:34 +0000115%
cristy33b1c162010-01-23 22:51:51 +0000116% They are internal to this module only. See AcquireResizeFilterInfo() for
117% details of the access to these functions, via the GetResizeFilterSupport()
118% and GetResizeFilterWeight() API interface.
cristy3ed852e2009-09-05 21:47:34 +0000119%
120% The individual filter functions have this format...
121%
122% static MagickRealtype *FilterName(const MagickRealType x,
123% const MagickRealType support)
124%
cristy33b1c162010-01-23 22:51:51 +0000125% A description of each parameter follows:
cristy3ed852e2009-09-05 21:47:34 +0000126%
cristy33b1c162010-01-23 22:51:51 +0000127% o x: the distance from the sampling point generally in the range of 0 to
128% support. The GetResizeFilterWeight() ensures this a positive value.
129%
130% o resize_filter: current filter information. This allows function to
131% access support, and possibly other pre-calculated information defining
132% the functions.
cristy3ed852e2009-09-05 21:47:34 +0000133%
134*/
135
cristyc5c6f662010-09-22 14:23:02 +0000136#define MagickPIL ((MagickRealType) 3.14159265358979323846264338327950288420L)
cristy560d8182010-09-08 22:36:25 +0000137
anthony48f77622010-10-03 14:32:31 +0000138static MagickRealType Jinc(const MagickRealType x,
cristy3ed852e2009-09-05 21:47:34 +0000139 const ResizeFilter *magick_unused(resize_filter))
140{
141 /*
anthony48f77622010-10-03 14:32:31 +0000142 See Pratt "Digital Image Processing" p.97 for Jinc/Bessel functions.
cristy33b1c162010-01-23 22:51:51 +0000143 http://mathworld.wolfram.com/JincFunction.html and page 11 of
anthony48f77622010-10-03 14:32:31 +0000144 http://www.ph.ed.ac.uk/%7ewjh/teaching/mo/slides/lens/lens.pdf
145
nicolase473f722010-10-07 00:05:13 +0000146 The original "zoom" program by Paul Heckbert called this "Bessel".
147 But really it is more accurately named "Jinc".
cristy3ed852e2009-09-05 21:47:34 +0000148 */
149 if (x == 0.0)
nicolas5a36f342010-10-07 00:11:32 +0000150 return(0.5*MagickPIL);
151 return(BesselOrderOne(MagickPIL*x)/x);
cristy3ed852e2009-09-05 21:47:34 +0000152}
153
154static MagickRealType Blackman(const MagickRealType x,
cristyce70c172010-01-07 17:15:30 +0000155 const ResizeFilter *magick_unused(resize_filter))
cristy3ed852e2009-09-05 21:47:34 +0000156{
157 /*
cristy83017922010-09-05 20:45:15 +0000158 Blackman: 2nd order cosine windowing function:
cristy21ce88a2010-09-05 01:37:25 +0000159 0.42 + 0.5 cos(pi x) + 0.08 cos(2pi x)
cristy560d8182010-09-08 22:36:25 +0000160 Refactored by Chantal Racette and Nicolas Robidoux to one trig
161 call and five flops.
cristy3ed852e2009-09-05 21:47:34 +0000162 */
cristyc5c6f662010-09-22 14:23:02 +0000163 const MagickRealType cospix = cos((double) (MagickPIL*x));
cristy560d8182010-09-08 22:36:25 +0000164 return(0.34+cospix*(0.5+cospix*0.16));
cristy3ed852e2009-09-05 21:47:34 +0000165}
166
167static MagickRealType Bohman(const MagickRealType x,
cristyce70c172010-01-07 17:15:30 +0000168 const ResizeFilter *magick_unused(resize_filter))
cristy3ed852e2009-09-05 21:47:34 +0000169{
170 /*
cristy560d8182010-09-08 22:36:25 +0000171 Bohman: 2rd Order cosine windowing function:
172 (1-x) cos(pi x) + sin(pi x) / pi.
nicolasa4f7b4a2010-09-20 20:28:38 +0000173 Refactored by Nicolas Robidoux to one trig call, one sqrt call,
174 and 7 flops, taking advantage of the fact that the support of
175 Bohman is 1 (so that we know that sin(pi x) >= 0).
cristy3ed852e2009-09-05 21:47:34 +0000176 */
cristyc5c6f662010-09-22 14:23:02 +0000177 const double cospix = cos((double) (MagickPIL*x));
nicolasa4f7b4a2010-09-20 20:28:38 +0000178 const double sinpix = sqrt(1.0-cospix*cospix);
nicolas2ffd3b22010-09-24 20:27:31 +0000179 return((1.0-x)*cospix+(1.0/MagickPIL)*sinpix);
cristy3ed852e2009-09-05 21:47:34 +0000180}
181
anthony463be1d2010-09-26 01:07:36 +0000182static MagickRealType Box(const MagickRealType magick_unused(x),
183 const ResizeFilter *magick_unused(resize_filter))
cristy3ed852e2009-09-05 21:47:34 +0000184{
185 /*
nicolas40477452010-09-27 23:42:08 +0000186 A Box filter is a equal weighting function (all weights equal).
anthonyc331dec2010-09-26 01:30:14 +0000187 DO NOT LIMIT results by support or resize point sampling will work
188 as it requests points beyond its normal 0.0 support size.
cristy3ed852e2009-09-05 21:47:34 +0000189 */
anthony463be1d2010-09-26 01:07:36 +0000190 return(1.0);
cristy3ed852e2009-09-05 21:47:34 +0000191}
192
193static MagickRealType CubicBC(const MagickRealType x,
194 const ResizeFilter *resize_filter)
195{
196 /*
197 Cubic Filters using B,C determined values:
nicolase3b9eca2010-10-24 19:48:22 +0000198 Mitchell-Netravali B= 1/3 C= 1/3 "Balanced" cubic spline filter
199 Catmull-Rom B= 0 C= 1/2 Interpolatory and exact on linears
200 Cubic B-Spline B= 1 C= 0 Spline approximation of Gaussian
201 Hermite B= 0 C= 0 Spline with small support (= 1)
cristy3ed852e2009-09-05 21:47:34 +0000202
cristy33b1c162010-01-23 22:51:51 +0000203 See paper by Mitchell and Netravali, Reconstruction Filters in Computer
204 Graphics Computer Graphics, Volume 22, Number 4, August 1988
205 http://www.cs.utexas.edu/users/fussell/courses/cs384g/lectures/mitchell/
cristyf59a8922010-02-28 19:51:23 +0000206 Mitchell.pdf.
cristy3ed852e2009-09-05 21:47:34 +0000207
cristy33b1c162010-01-23 22:51:51 +0000208 Coefficents are determined from B,C values:
anthony06b1edf2010-10-25 01:19:50 +0000209 P0 = ( 6 - 2*B )/6 = coeff[0]
cristy3ed852e2009-09-05 21:47:34 +0000210 P1 = 0
anthony06b1edf2010-10-25 01:19:50 +0000211 P2 = (-18 +12*B + 6*C )/6 = coeff[1]
212 P3 = ( 12 - 9*B - 6*C )/6 = coeff[2]
213 Q0 = ( 8*B +24*C )/6 = coeff[3]
214 Q1 = ( -12*B -48*C )/6 = coeff[4]
215 Q2 = ( 6*B +30*C )/6 = coeff[5]
216 Q3 = ( - 1*B - 6*C )/6 = coeff[6]
cristy3ed852e2009-09-05 21:47:34 +0000217
cristy33b1c162010-01-23 22:51:51 +0000218 which are used to define the filter:
cristyf59a8922010-02-28 19:51:23 +0000219
cristy3ed852e2009-09-05 21:47:34 +0000220 P0 + P1*x + P2*x^2 + P3*x^3 0 <= x < 1
nicolase3b9eca2010-10-24 19:48:22 +0000221 Q0 + Q1*x + Q2*x^2 + Q3*x^3 1 <= x < 2
cristy3ed852e2009-09-05 21:47:34 +0000222
nicolase3b9eca2010-10-24 19:48:22 +0000223 which ensures function is continuous in value and derivative
nicolas89d73f92010-10-24 20:11:54 +0000224 (slope).
cristy3ed852e2009-09-05 21:47:34 +0000225 */
226 if (x < 1.0)
nicolasc6bac3b2010-10-24 18:10:45 +0000227 return(resize_filter->coeff[0]+x*(x*
nicolase3b9eca2010-10-24 19:48:22 +0000228 (resize_filter->coeff[1]+x*resize_filter->coeff[2])));
cristy3ed852e2009-09-05 21:47:34 +0000229 if (x < 2.0)
nicolase3b9eca2010-10-24 19:48:22 +0000230 return(resize_filter->coeff[3]+x*(resize_filter->coeff[4]+x*
231 (resize_filter->coeff[5]+x*resize_filter->coeff[6])));
cristy3ed852e2009-09-05 21:47:34 +0000232 return(0.0);
233}
234
235static MagickRealType Gaussian(const MagickRealType x,
anthonyf5e76ef2010-10-12 01:22:01 +0000236 const ResizeFilter *resize_filter)
cristy3ed852e2009-09-05 21:47:34 +0000237{
cristy560d8182010-09-08 22:36:25 +0000238 /*
anthonyf5e76ef2010-10-12 01:22:01 +0000239 Gaussian with a fixed sigma = 1/2
240
241 Gaussian Formula...
242 exp( -(x^2)/((2.0*sigma^2) ) / sqrt(2*PI*sigma^2)))
243 The constants are pre-calculated...
244 exp( -coeff[0]*(x^2)) ) * coeff[1]
anthony06b1edf2010-10-25 01:19:50 +0000245 However the multiplier coefficent (1) is not needed and not used.
anthonyf5e76ef2010-10-12 01:22:01 +0000246
247 This separates the gaussian 'sigma' value from the 'blur/support' settings
anthony06b1edf2010-10-25 01:19:50 +0000248 allowing for its use in special 'small sigma' gaussians, without the filter
249 'missing' pixels when blurring because the support is too small.
cristy560d8182010-09-08 22:36:25 +0000250 */
anthony06b1edf2010-10-25 01:19:50 +0000251 return(exp((double)(-resize_filter->coeff[0]*x*x)));
252}
cristy3ed852e2009-09-05 21:47:34 +0000253
254static MagickRealType Hanning(const MagickRealType x,
255 const ResizeFilter *magick_unused(resize_filter))
256{
257 /*
nicolas40477452010-09-27 23:42:08 +0000258 Cosine window function:
259 .5+.5cos(pi x).
cristy3ed852e2009-09-05 21:47:34 +0000260 */
cristyc5c6f662010-09-22 14:23:02 +0000261 const MagickRealType cospix = cos((double) (MagickPIL*x));
cristy560d8182010-09-08 22:36:25 +0000262 return(0.5+0.5*cospix);
cristy3ed852e2009-09-05 21:47:34 +0000263}
264
265static MagickRealType Hamming(const MagickRealType x,
266 const ResizeFilter *magick_unused(resize_filter))
267{
268 /*
nicolas40477452010-09-27 23:42:08 +0000269 Offset cosine window function:
270 .54 + .46 cos(pi x).
cristy3ed852e2009-09-05 21:47:34 +0000271 */
cristyc5c6f662010-09-22 14:23:02 +0000272 const MagickRealType cospix = cos((double) (MagickPIL*x));
cristy560d8182010-09-08 22:36:25 +0000273 return(0.54+0.46*cospix);
cristy3ed852e2009-09-05 21:47:34 +0000274}
275
276static MagickRealType Kaiser(const MagickRealType x,
277 const ResizeFilter *magick_unused(resize_filter))
278{
279#define Alpha 6.5
280#define I0A (1.0/I0(Alpha))
281
282 /*
nicolas07bac812010-09-19 18:47:02 +0000283 Kaiser Windowing Function (bessel windowing): Alpha is a free
284 value from 5 to 8 (currently hardcoded to 6.5).
285 Future: make alpha the IOA pre-calculation, an 'expert' setting.
cristy3ed852e2009-09-05 21:47:34 +0000286 */
287 return(I0A*I0(Alpha*sqrt((double) (1.0-x*x))));
288}
289
290static MagickRealType Lagrange(const MagickRealType x,
291 const ResizeFilter *resize_filter)
292{
cristy3ed852e2009-09-05 21:47:34 +0000293 MagickRealType
294 value;
295
cristybb503372010-05-27 20:51:26 +0000296 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +0000297 i;
298
cristy9af9b5d2010-08-15 17:04:28 +0000299 ssize_t
300 n,
301 order;
302
cristy3ed852e2009-09-05 21:47:34 +0000303 /*
nicolas07bac812010-09-19 18:47:02 +0000304 Lagrange piecewise polynomial fit of sinc: N is the 'order' of the
305 lagrange function and depends on the overall support window size
306 of the filter. That is: for a support of 2, it gives a lagrange-4
307 (piecewise cubic function).
cristy3ed852e2009-09-05 21:47:34 +0000308
nicolas07bac812010-09-19 18:47:02 +0000309 "n" identifies the piece of the piecewise polynomial.
cristy3ed852e2009-09-05 21:47:34 +0000310
nicolas07bac812010-09-19 18:47:02 +0000311 See Survey: Interpolation Methods, IEEE Transactions on Medical
312 Imaging, Vol 18, No 11, November 1999, p1049-1075, -- Equation 27
313 on p1064.
cristy3ed852e2009-09-05 21:47:34 +0000314 */
315 if (x > resize_filter->support)
316 return(0.0);
cristybb503372010-05-27 20:51:26 +0000317 order=(ssize_t) (2.0*resize_filter->window_support); /* number of pieces */
anthonyc2d07db2010-09-15 23:47:40 +0000318 /*n=(ssize_t)((1.0*order)/2.0+x); -- which piece does x belong to */
319 n = (ssize_t)(resize_filter->window_support + x);
cristy3ed852e2009-09-05 21:47:34 +0000320 value=1.0f;
321 for (i=0; i < order; i++)
322 if (i != n)
323 value*=(n-i-x)/(n-i);
324 return(value);
325}
326
327static MagickRealType Quadratic(const MagickRealType x,
328 const ResizeFilter *magick_unused(resize_filter))
329{
330 /*
331 2rd order (quadratic) B-Spline approximation of Gaussian.
332 */
333 if (x < 0.5)
334 return(0.75-x*x);
335 if (x < 1.5)
336 return(0.5*(x-1.5)*(x-1.5));
337 return(0.0);
338}
339
anthony07a3f7f2010-09-16 03:03:11 +0000340static MagickRealType Sinc(const MagickRealType x,
cristy3ed852e2009-09-05 21:47:34 +0000341 const ResizeFilter *magick_unused(resize_filter))
342{
anthony720660f2010-09-07 10:05:14 +0000343 /*
nicolas40477452010-09-27 23:42:08 +0000344 Scaled sinc(x) function using a trig call:
nicolas07bac812010-09-19 18:47:02 +0000345 sinc(x) == sin(pi x)/(pi x).
anthony720660f2010-09-07 10:05:14 +0000346 */
anthony2d9b8b52010-09-14 08:31:07 +0000347 if (x != 0.0)
cristy560d8182010-09-08 22:36:25 +0000348 {
cristyc5c6f662010-09-22 14:23:02 +0000349 const MagickRealType pix = (MagickRealType) (MagickPIL*x);
nicolas07bac812010-09-19 18:47:02 +0000350 return(sin((double) pix)/pix);
cristy560d8182010-09-08 22:36:25 +0000351 }
nicolas2ffd3b22010-09-24 20:27:31 +0000352 return((MagickRealType) 1.0);
anthony720660f2010-09-07 10:05:14 +0000353}
354
anthonyba5a7c32010-09-15 02:42:25 +0000355static MagickRealType SincFast(const MagickRealType x,
anthony720660f2010-09-07 10:05:14 +0000356 const ResizeFilter *magick_unused(resize_filter))
357{
cristy560d8182010-09-08 22:36:25 +0000358 /*
359 Approximations of the sinc function sin(pi x)/(pi x) over the
360 interval [-4,4] constructed by Nicolas Robidoux and Chantal
361 Racette with funding from the Natural Sciences and Engineering
362 Research Council of Canada.
nicolas07bac812010-09-19 18:47:02 +0000363
364 Although the approximations are polynomials (for low order of
365 approximation) and quotients of polynomials (for higher order of
366 approximation) and consequently are similar in form to Taylor
367 polynomials/Pade approximants, the approximations are computed
368 with a completely different technique.
369
370 Summary: These approximations are "the best" in terms of bang
371 (accuracy) for the buck (flops). More specifically: Among the
372 polynomial quotients that can be computed using a fixed number of
373 flops (with a given "+ - * / budget"), the chosen polynomial
374 quotient is the one closest to the approximated function with
375 respect to maximum absolute relative error over the given
376 interval.
377
378 The Remez algorithm, as implemented in the boost library's minimax
nicolas3aab40c2010-09-19 21:14:15 +0000379 package, is the key to the construction:
nicolas07bac812010-09-19 18:47:02 +0000380 http://www.boost.org/doc/libs/1_36_0/libs/math/doc/...
381 ...sf_and_dist/html/math_toolkit/backgrounders/remez.html
cristy560d8182010-09-08 22:36:25 +0000382 */
nicolas3aab40c2010-09-19 21:14:15 +0000383 /*
384 If outside of the interval of approximation, use the standard trig
385 formula.
386 */
anthony2d9b8b52010-09-14 08:31:07 +0000387 if (x > 4.0)
cristy03dbbd22010-09-19 23:04:47 +0000388 {
cristyc5c6f662010-09-22 14:23:02 +0000389 const MagickRealType pix = (MagickRealType) (MagickPIL*x);
cristy03dbbd22010-09-19 23:04:47 +0000390 return(sin((double) pix)/pix);
391 }
anthony2d9b8b52010-09-14 08:31:07 +0000392 {
nicolas07bac812010-09-19 18:47:02 +0000393 /*
394 The approximations only depend on x^2 (sinc is an even
395 function).
396 */
397 const MagickRealType xx = x*x;
cristy83017922010-09-05 20:45:15 +0000398#if MAGICKCORE_QUANTUM_DEPTH <= 8
399 /*
anthony2d9b8b52010-09-14 08:31:07 +0000400 Maximum absolute relative error 6.3e-6 < 1/2^17.
cristy738e7562010-09-01 12:48:07 +0000401 */
402 const MagickRealType c0 = 0.173610016489197553621906385078711564924e-2L;
403 const MagickRealType c1 = -0.384186115075660162081071290162149315834e-3L;
404 const MagickRealType c2 = 0.393684603287860108352720146121813443561e-4L;
405 const MagickRealType c3 = -0.248947210682259168029030370205389323899e-5L;
406 const MagickRealType c4 = 0.107791837839662283066379987646635416692e-6L;
407 const MagickRealType c5 = -0.324874073895735800961260474028013982211e-8L;
408 const MagickRealType c6 = 0.628155216606695311524920882748052490116e-10L;
409 const MagickRealType c7 = -0.586110644039348333520104379959307242711e-12L;
nicolas3aab40c2010-09-19 21:14:15 +0000410 const MagickRealType p =
411 c0+xx*(c1+xx*(c2+xx*(c3+xx*(c4+xx*(c5+xx*(c6+xx*c7))))));
nicolas07bac812010-09-19 18:47:02 +0000412 return((xx-1.0)*(xx-4.0)*(xx-9.0)*(xx-16.0)*p);
anthony2d9b8b52010-09-14 08:31:07 +0000413#elif MAGICKCORE_QUANTUM_DEPTH <= 16
cristydbeb3eb2010-09-09 13:41:36 +0000414 /*
anthony2d9b8b52010-09-14 08:31:07 +0000415 Max. abs. rel. error 2.2e-8 < 1/2^25.
cristydbeb3eb2010-09-09 13:41:36 +0000416 */
417 const MagickRealType c0 = 0.173611107357320220183368594093166520811e-2L;
418 const MagickRealType c1 = -0.384240921114946632192116762889211361285e-3L;
nicolas3aab40c2010-09-19 21:14:15 +0000419 const MagickRealType c2 = 0.394201182359318128221229891724947048771e-4L;
420 const MagickRealType c3 = -0.250963301609117217660068889165550534856e-5L;
421 const MagickRealType c4 = 0.111902032818095784414237782071368805120e-6L;
422 const MagickRealType c5 = -0.372895101408779549368465614321137048875e-8L;
423 const MagickRealType c6 = 0.957694196677572570319816780188718518330e-10L;
cristydbeb3eb2010-09-09 13:41:36 +0000424 const MagickRealType c7 = -0.187208577776590710853865174371617338991e-11L;
425 const MagickRealType c8 = 0.253524321426864752676094495396308636823e-13L;
426 const MagickRealType c9 = -0.177084805010701112639035485248501049364e-15L;
anthony853d6972010-10-08 06:01:31 +0000427 const MagickRealType p =
nicolas3aab40c2010-09-19 21:14:15 +0000428 c0+xx*(c1+xx*(c2+xx*(c3+xx*(c4+xx*(c5+xx*(c6+xx*(c7+xx*(c8+xx*c9))))))));
nicolas07bac812010-09-19 18:47:02 +0000429 return((xx-1.0)*(xx-4.0)*(xx-9.0)*(xx-16.0)*p);
nicolasc2d28f02010-09-27 18:56:15 +0000430#else
nicolas3aab40c2010-09-19 21:14:15 +0000431 /*
432 Max. abs. rel. error 1.2e-12 < 1/2^39.
433 */
434 const MagickRealType c0 = 0.173611111110910715186413700076827593074e-2L;
435 const MagickRealType c1 = -0.289105544717893415815859968653611245425e-3L;
436 const MagickRealType c2 = 0.206952161241815727624413291940849294025e-4L;
437 const MagickRealType c3 = -0.834446180169727178193268528095341741698e-6L;
438 const MagickRealType c4 = 0.207010104171026718629622453275917944941e-7L;
439 const MagickRealType c5 = -0.319724784938507108101517564300855542655e-9L;
440 const MagickRealType c6 = 0.288101675249103266147006509214934493930e-11L;
441 const MagickRealType c7 = -0.118218971804934245819960233886876537953e-13L;
442 const MagickRealType p =
443 c0+xx*(c1+xx*(c2+xx*(c3+xx*(c4+xx*(c5+xx*(c6+xx*c7))))));
444 const MagickRealType d0 = 1.0L;
445 const MagickRealType d1 = 0.547981619622284827495856984100563583948e-1L;
446 const MagickRealType d2 = 0.134226268835357312626304688047086921806e-2L;
447 const MagickRealType d3 = 0.178994697503371051002463656833597608689e-4L;
448 const MagickRealType d4 = 0.114633394140438168641246022557689759090e-6L;
449 const MagickRealType q = d0+xx*(d1+xx*(d2+xx*(d3+xx*d4)));
450 return((xx-1.0)*(xx-4.0)*(xx-9.0)*(xx-16.0)/q*p);
cristy657c6352010-08-29 14:05:08 +0000451#endif
cristy83017922010-09-05 20:45:15 +0000452 }
cristy3ed852e2009-09-05 21:47:34 +0000453}
454
455static MagickRealType Triangle(const MagickRealType x,
456 const ResizeFilter *magick_unused(resize_filter))
457{
458 /*
nicolas0edb0862010-09-19 18:56:19 +0000459 1st order (linear) B-Spline, bilinear interpolation, Tent 1D
anthony06b1edf2010-10-25 01:19:50 +0000460 filter, or a Bartlett 2D Cone filter. Also used as a
461 Bartlett Windowing function for Sinc().
cristy3ed852e2009-09-05 21:47:34 +0000462 */
463 if (x < 1.0)
464 return(1.0-x);
465 return(0.0);
466}
467
468static MagickRealType Welsh(const MagickRealType x,
469 const ResizeFilter *magick_unused(resize_filter))
470{
471 /*
472 Welsh parabolic windowing filter.
473 */
cristy560d8182010-09-08 22:36:25 +0000474 if (x < 1.0)
cristy3ed852e2009-09-05 21:47:34 +0000475 return(1.0-x*x);
476 return(0.0);
477}
478
479/*
480%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
481% %
482% %
483% %
484+ A c q u i r e R e s i z e F i l t e r %
485% %
486% %
487% %
488%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
489%
nicolas07bac812010-09-19 18:47:02 +0000490% AcquireResizeFilter() allocates the ResizeFilter structure. Choose
491% from these filters:
cristy3ed852e2009-09-05 21:47:34 +0000492%
493% FIR (Finite impulse Response) Filters
494% Box Triangle Quadratic
495% Cubic Hermite Catrom
496% Mitchell
497%
498% IIR (Infinite impulse Response) Filters
anthony48f77622010-10-03 14:32:31 +0000499% Gaussian Sinc Jinc (Bessel)
cristy3ed852e2009-09-05 21:47:34 +0000500%
anthony48f77622010-10-03 14:32:31 +0000501% Windowed Sinc/Jinc Filters
cristy3ed852e2009-09-05 21:47:34 +0000502% Blackman Hanning Hamming
anthony48f77622010-10-03 14:32:31 +0000503% Kaiser Lanczos
cristy3ed852e2009-09-05 21:47:34 +0000504%
anthony61b5ddd2010-10-05 02:33:31 +0000505% Special purpose Filters
anthony06b1edf2010-10-25 01:19:50 +0000506% SincFast LanczosSharp Lanczos2D Lanczos2DSharp Robidoux
anthony61b5ddd2010-10-05 02:33:31 +0000507%
anthony48f77622010-10-03 14:32:31 +0000508% The users "-filter" selection is used to lookup the default 'expert'
509% settings for that filter from a internal table. However any provided
510% 'expert' settings (see below) may override this selection.
511%
512% FIR filters are used as is, and are limited to that filters support
nicolas07bac812010-09-19 18:47:02 +0000513% window (unless over-ridden). 'Gaussian' while classed as an IIR
anthonyc331dec2010-09-26 01:30:14 +0000514% filter, is also simply clipped by its support size (currently 1.5
anthonyf5e76ef2010-10-12 01:22:01 +0000515% or approximatally 3*sigma as recommended by many references)
cristy3ed852e2009-09-05 21:47:34 +0000516%
anthony48f77622010-10-03 14:32:31 +0000517% The selection is typically either a windowed Sinc, or interpolated
nicolas07bac812010-09-19 18:47:02 +0000518% filter, for use by functions such as ResizeImage(). However if a
anthony48f77622010-10-03 14:32:31 +0000519% 'cylindrical' filter flag is requested, any default Sinc weighting
520% and windowing functions will be promoted to cylindrical Jinc form of
521% function.
cristy3ed852e2009-09-05 21:47:34 +0000522%
anthony48f77622010-10-03 14:32:31 +0000523% Directly requesting 'Sinc' or 'Jinc' will force the use of that
524% filter function without any windowing. This is not recommended,
525% except by image processing experts or in expert options. Selecting a
526% window filtering version of these functions is better.
cristy3ed852e2009-09-05 21:47:34 +0000527%
anthony48f77622010-10-03 14:32:31 +0000528% Lanczos is a special case of a Sinc-windowed Sinc, (or Jinc-Jinc for
529% the cylindrical case) but defaulting to 3-lobe support, rather that
530% the default 4 lobe support of the other windowed sinc/jinc filters.
cristy3ed852e2009-09-05 21:47:34 +0000531%
anthony06b1edf2010-10-25 01:19:50 +0000532% LanczosSharp is a slightly sharpened (blur=0.9830391168464) form of
533% the Lanczos filter. It was designed specifically for cylindrical
534% (radial) EWA (Elliptical Weighted Average) distortion (using a
535% Jinc windowed Jinc), but can also be used as a slightly sharper
536% orthogonal Lanczos (Sinc-Sinc) filter.
537%
anthony48f77622010-10-03 14:32:31 +0000538% Two forms of the 'Sinc' function are available: Sinc and SincFast.
nicolas07bac812010-09-19 18:47:02 +0000539% Sinc is computed using the traditional sin(pi*x)/(pi*x); it is
540% selected if the user specifically specifies the use of a Sinc
541% filter. SincFast uses highly accurate (and fast) polynomial (low Q)
anthony48f77622010-10-03 14:32:31 +0000542% and rational (high Q) approximations, and will be used by default in
543% most cases.
anthonyba5a7c32010-09-15 02:42:25 +0000544%
anthony06b1edf2010-10-25 01:19:50 +0000545% The Lanczos2D and Lanczos2DSharp filters specific for EWA distorts.
546% They are both 2 lobe Lanczos-like filter but always using a
547% Jinc-Jinc filter function regardless of if used as a orthoginal
548% filter or not. The 'sharp' version has been sharpened using a
549% blur=0.958033808.
550%
551% Robidoux is a unique Keys cubic spline, developed from the previous
552% Lanczos2DSharp filter. It is designed to satisfy the following
553% condition:
nicolas0d5e5322010-10-22 15:29:30 +0000554%
555% Robidoux exactly preserves images with only vertical or
556% horizontal features when performing 'no-op" with EWA distortion.
557%
nicolas618a9a72010-10-22 19:23:43 +0000558% That is, Robidoux is the BC-Spline with B=(228 - 108 sqrt(2))/199
559% and C=(108 sqrt(2) - 29)/398. Robidoux turns out to be close to
560% both plain Mitchell and "sharpened" Lanczos2D. For example, it's
561% first crossing is (36 sqrt(2) + 123)/(72 sqrt(2) + 47) which is
562% almost identical to the first crossing of the other two.
anthony61b5ddd2010-10-05 02:33:31 +0000563%
nicolas3061b8a2010-10-22 16:34:52 +0000564% 'EXPERT' OPTIONS:
nicolasce6dc292010-10-22 16:23:07 +0000565%
anthony06b1edf2010-10-25 01:19:50 +0000566% These settings are not recommended for production use without expert
567% knowledge of resampling, filtering, and the effects they have on the
568% resulting resampled (resize ro distorted) image.
nicolas3061b8a2010-10-22 16:34:52 +0000569%
anthony06b1edf2010-10-25 01:19:50 +0000570% You can override any and all filter settings, and it is recommended
571% you make good use of "filter:verbose" to make sure that the overall
572% effect of your selection (before and after) is as expected.
nicolas3061b8a2010-10-22 16:34:52 +0000573%
574% "filter:verbose" Output the exact results of the filter
575% selections made, as well as plotting data for graphing the
576% resulting filter over support range (blur adjusted).
cristy3ed852e2009-09-05 21:47:34 +0000577%
anthony48f77622010-10-03 14:32:31 +0000578% "filter:filter" Select the main function associated with
579% this filter name, as the weighting function of the filter.
580% This can be used to set a windowing function as a weighting
581% function, for special purposes, such as graphing.
cristy3ed852e2009-09-05 21:47:34 +0000582%
nicolas3061b8a2010-10-22 16:34:52 +0000583% If a "filter:window" operation has not been provided, then a
584% 'Box' windowing function will be set to denote that no
585% windowing function is being used.
cristy3ed852e2009-09-05 21:47:34 +0000586%
nicolas3061b8a2010-10-22 16:34:52 +0000587% "filter:window" Select this windowing function for the filter.
588% While any filter could be used as a windowing function, using
589% the 'first lobe' of that filter over the whole support
590% window, using a non-windowing function is not advisible. If
591% no weighting filter function is specifed a 'SincFast' filter
592% will be used.
cristy3ed852e2009-09-05 21:47:34 +0000593%
nicolas3061b8a2010-10-22 16:34:52 +0000594% "filter:lobes" Number of lobes to use for the Sinc/Jinc filter.
595% This a simpler method of setting filter support size that
596% will correctly handle the Sinc/Jinc switch for an operators
597% filtering requirements. Only integers should be given.
cristy3ed852e2009-09-05 21:47:34 +0000598%
nicolas3061b8a2010-10-22 16:34:52 +0000599% "filter:support" Set the support size for filtering to the size
600% given This not recommended for Sinc/Jinc windowed filters
601% (lobes should be used instead). This will override any
602% 'filter:lobes' option.
cristy3ed852e2009-09-05 21:47:34 +0000603%
nicolas3061b8a2010-10-22 16:34:52 +0000604% "filter:win-support" Scale windowing function to this size
605% instead. This causes the windowing (or self-windowing
606% Lagrange filter) to act is if the support window it much much
607% larger than what is actually supplied to the calling
608% operator. The filter however is still clipped to the real
609% support size given, by the support range suppiled to the
610% caller. If unset this will equal the normal filter support
anthonyb6d08c52010-09-13 01:17:04 +0000611% size.
612%
nicolas3061b8a2010-10-22 16:34:52 +0000613% "filter:blur" Scale the filter and support window by this amount.
cristy3ed852e2009-09-05 21:47:34 +0000614% A value >1 will generally result in a more burred image with
615% more ringing effects, while a value <1 will sharpen the
616% resulting image with more aliasing and Morie effects.
617%
nicolas3061b8a2010-10-22 16:34:52 +0000618% "filter:sigma" The sigma value to use for the Gaussian filter
619% only. Defaults to '1/2' for orthogonal and 'sqrt(2)/2' for
620% cylindrical usage. It effectially provides a alturnative to
621% 'blur' for Gaussians without it also effecting the final
622% 'practical support' size.
anthonyf5e76ef2010-10-12 01:22:01 +0000623%
cristy3ed852e2009-09-05 21:47:34 +0000624% "filter:b"
nicolas3061b8a2010-10-22 16:34:52 +0000625% "filter:c" Override the preset B,C values for a Cubic type of
626% filter If only one of these are given it is assumes to be a
627% 'Keys' type of filter such that B+2C=1, where Keys 'alpha'
628% value = C
cristy3ed852e2009-09-05 21:47:34 +0000629%
anthony06b1edf2010-10-25 01:19:50 +0000630% Examples:
cristy3ed852e2009-09-05 21:47:34 +0000631%
nicolas6e1267a2010-10-22 16:35:52 +0000632% Set a true un-windowed Sinc filter with 10 lobes (very slow):
anthony48f77622010-10-03 14:32:31 +0000633% -define filter:filter=Sinc
634% -define filter:lobes=8
cristy3ed852e2009-09-05 21:47:34 +0000635%
nicolas6e1267a2010-10-22 16:35:52 +0000636% Set an 8 lobe Lanczos (Sinc or Jinc) filter:
cristy3ed852e2009-09-05 21:47:34 +0000637% -filter Lanczos
anthony48f77622010-10-03 14:32:31 +0000638% -define filter:lobes=8
anthony7bdc0ed2010-09-15 01:52:32 +0000639%
anthony06b1edf2010-10-25 01:19:50 +0000640%
cristy3ed852e2009-09-05 21:47:34 +0000641% The format of the AcquireResizeFilter method is:
642%
643% ResizeFilter *AcquireResizeFilter(const Image *image,
644% const FilterTypes filter_type, const MagickBooleanType radial,
645% ExceptionInfo *exception)
646%
cristy33b1c162010-01-23 22:51:51 +0000647% A description of each parameter follows:
648%
cristy3ed852e2009-09-05 21:47:34 +0000649% o image: the image.
650%
nicolas07bac812010-09-19 18:47:02 +0000651% o filter: the filter type, defining a preset filter, window and
652% support. The artifact settings listed above will override
653% those selections.
cristy3ed852e2009-09-05 21:47:34 +0000654%
anthony48f77622010-10-03 14:32:31 +0000655% o blur: blur the filter by this amount, use 1.0 if unknown. Image
656% artifact "filter:blur" will override this API call usage, including
657% any internal change (such as for cylindrical usage).
cristy3ed852e2009-09-05 21:47:34 +0000658%
anthony48f77622010-10-03 14:32:31 +0000659% o radial: use a 1D orthogonal filter (Sinc) or 2D cylindrical
660% (radial) filter (Jinc)
cristy3ed852e2009-09-05 21:47:34 +0000661%
662% o exception: return any errors or warnings in this structure.
663%
664*/
665MagickExport ResizeFilter *AcquireResizeFilter(const Image *image,
cristyf59a8922010-02-28 19:51:23 +0000666 const FilterTypes filter,const MagickRealType blur,
cristy3ed852e2009-09-05 21:47:34 +0000667 const MagickBooleanType cylindrical,ExceptionInfo *exception)
668{
669 const char
670 *artifact;
671
672 FilterTypes
673 filter_type,
674 window_type;
675
cristy3ed852e2009-09-05 21:47:34 +0000676 MagickRealType
677 B,
anthonyf5e76ef2010-10-12 01:22:01 +0000678 C,
679 sigma;
cristy3ed852e2009-09-05 21:47:34 +0000680
681 register ResizeFilter
682 *resize_filter;
683
cristy9af9b5d2010-08-15 17:04:28 +0000684 ssize_t
685 option;
686
cristy3ed852e2009-09-05 21:47:34 +0000687 /*
anthony48f77622010-10-03 14:32:31 +0000688 Table Mapping given Filter, into Weighting and Windowing functions.
nicolas07bac812010-09-19 18:47:02 +0000689 A 'Box' windowing function means its a simble non-windowed filter.
anthony48f77622010-10-03 14:32:31 +0000690 An 'SincFast' filter function could be upgraded to a 'Jinc' filter
691 if a "cylindrical", unless a 'Sinc' or 'SincFast' filter was
692 specifically requested.
anthony449887b2010-09-10 02:23:33 +0000693
nicolas07bac812010-09-19 18:47:02 +0000694 WARNING: The order of this tabel must match the order of the
695 FilterTypes enumeration specified in "resample.h", or the filter
696 names will not match the filter being setup.
anthony1e2d5ca2010-09-10 02:24:35 +0000697
698 You can check filter setups with the "filter:verbose" setting.
cristy3ed852e2009-09-05 21:47:34 +0000699 */
700 static struct
701 {
702 FilterTypes
703 filter,
704 window;
705 } const mapping[SentinelFilter] =
706 {
anthony06b1edf2010-10-25 01:19:50 +0000707 { UndefinedFilter, BoxFilter }, /* Undefined (default to Box) */
708 { PointFilter, BoxFilter }, /* SPECIAL: Nearest neighbour */
709 { BoxFilter, BoxFilter }, /* Box averaging filter */
710 { TriangleFilter, BoxFilter }, /* Linear interpolation filter */
711 { HermiteFilter, BoxFilter }, /* Hermite interpolation filter */
712 { SincFastFilter, HanningFilter }, /* Hanning -- cosine-sinc */
713 { SincFastFilter, HammingFilter }, /* Hamming -- '' variation */
714 { SincFastFilter, BlackmanFilter }, /* Blackman -- 2*cosine-sinc */
715 { GaussianFilter, BoxFilter }, /* Gaussian blur filter */
716 { QuadraticFilter, BoxFilter }, /* Quadratic Gaussian approx */
717 { CubicFilter, BoxFilter }, /* Cubic B-Spline */
718 { CatromFilter, BoxFilter }, /* Cubic-Keys interpolator */
719 { MitchellFilter, BoxFilter }, /* 'Ideal' Cubic-Keys filter */
720 { LanczosFilter, SincFastFilter }, /* SPECIAL: 3-lobed sinc-sinc */
721 { JincFilter, BoxFilter }, /* Raw 3-lobed Jinc function */
722 { SincFilter, BoxFilter }, /* Raw 4-lobed Sinc function */
723 { SincFastFilter, KaiserFilter }, /* Kaiser -- square root-sinc */
724 { SincFastFilter, WelshFilter }, /* Welsh -- parabolic-sinc */
725 { SincFastFilter, CubicFilter }, /* Parzen -- cubic-sinc */
726 { LagrangeFilter, BoxFilter }, /* Lagrange self-windowing */
727 { SincFastFilter, BohmanFilter }, /* Bohman -- 2*cosine-sinc */
728 { SincFastFilter, TriangleFilter }, /* Bartlett -- triangle-sinc */
729 { SincFastFilter, BoxFilter }, /* Raw fast sinc ("Pade"-type) */
730 { LanczosSharpFilter, SincFastFilter }, /* SPECIAL: Sharpened Lanczos */
731 { Lanczos2DFilter, JincFilter }, /* SPECIAL: 2-lobed jinc-jinc */
732 { Lanczos2DSharpFilter, JincFilter }, /* SPECIAL: ditto sharpened */
733 { RobidouxFilter, BoxFilter }, /* SPECIAL: Keys tuned for EWA */
cristy3ed852e2009-09-05 21:47:34 +0000734 };
735 /*
nicolas32f44eb2010-09-20 01:23:12 +0000736 Table mapping the filter/window from the above table to an actual
nicolas07bac812010-09-19 18:47:02 +0000737 function. The default support size for that filter as a weighting
738 function, the range to scale with to use that function as a sinc
739 windowing function, (typ 1.0).
anthony933b4bf2010-09-10 02:31:37 +0000740
anthony07a3f7f2010-09-16 03:03:11 +0000741 Note that the filter_type -> function is 1 to 1 except for Sinc(),
nicolas07bac812010-09-19 18:47:02 +0000742 SincFast(), and CubicBC() functions, which may have multiple
743 filter to function associations.
744
745 See "filter:verbose" handling below for the function -> filter
746 mapping.
cristy3ed852e2009-09-05 21:47:34 +0000747 */
748 static struct
749 {
750 MagickRealType
751 (*function)(const MagickRealType, const ResizeFilter*),
nicolas8eccc162010-10-16 19:48:13 +0000752 lobes, /* Default lobes/support size of the weighting filter. */
anthony450db502010-10-19 04:03:03 +0000753 scale, /* Support when function used as a windowing function
754 Typically equal to the location of the first zero crossing. */
755 B,C; /* BC-spline coefficients, ignored if not a CubicBC filter. */
cristy3ed852e2009-09-05 21:47:34 +0000756 } const filters[SentinelFilter] =
757 {
anthony61b5ddd2010-10-05 02:33:31 +0000758 { Box, 0.5, 0.5, 0.0, 0.0 }, /* Undefined (default to Box) */
759 { Box, 0.0, 0.5, 0.0, 0.0 }, /* Point (special handling) */
760 { Box, 0.5, 0.5, 0.0, 0.0 }, /* Box */
761 { Triangle, 1.0, 1.0, 0.0, 0.0 }, /* Triangle */
762 { CubicBC, 1.0, 1.0, 0.0, 0.0 }, /* Hermite (cubic B=C=0) */
763 { Hanning, 1.0, 1.0, 0.0, 0.0 }, /* Hanning, cosine window */
764 { Hamming, 1.0, 1.0, 0.0, 0.0 }, /* Hamming, '' variation */
765 { Blackman, 1.0, 1.0, 0.0, 0.0 }, /* Blackman, 2*cosine window */
anthonyf5e76ef2010-10-12 01:22:01 +0000766 { Gaussian, 2.0, 1.5, 0.0, 0.0 }, /* Gaussian */
anthony61b5ddd2010-10-05 02:33:31 +0000767 { Quadratic, 1.5, 1.5, 0.0, 0.0 }, /* Quadratic gaussian */
768 { CubicBC, 2.0, 2.0, 1.0, 0.0 }, /* Cubic B-Spline (B=1,C=0) */
769 { CubicBC, 2.0, 1.0, 0.0, 0.5 }, /* Catmull-Rom (B=0,C=1/2) */
anthony853d6972010-10-08 06:01:31 +0000770 { CubicBC, 2.0, 1.0, 1./3., 1./3. }, /* Mitchell (B=C=1/3) */
771 { SincFast, 3.0, 1.0, 0.0, 0.0 }, /* Lanczos, 3-lobed Sinc-Sinc */
nicolasf8689f42010-10-18 16:14:08 +0000772 { Jinc, 3.0, 1.2196698912665045, 0.0, 0.0 }, /* Raw 3-lobed Jinc */
anthony61b5ddd2010-10-05 02:33:31 +0000773 { Sinc, 4.0, 1.0, 0.0, 0.0 }, /* Raw 4-lobed Sinc */
anthony06b1edf2010-10-25 01:19:50 +0000774 { SincFast, 4.0, 1.0, 0.0, 0.0 }, /* Raw fast sinc ("Pade"-type) */
anthony61b5ddd2010-10-05 02:33:31 +0000775 { Kaiser, 1.0, 1.0, 0.0, 0.0 }, /* Kaiser (square root window) */
776 { Welsh, 1.0, 1.0, 0.0, 0.0 }, /* Welsh (parabolic window) */
777 { CubicBC, 2.0, 2.0, 1.0, 0.0 }, /* Parzen (B-Spline window) */
778 { Lagrange, 2.0, 1.0, 0.0, 0.0 }, /* Lagrange sinc approximation */
779 { Bohman, 1.0, 1.0, 0.0, 0.0 }, /* Bohman, 2*Cosine window */
780 { Triangle, 1.0, 1.0, 0.0, 0.0 }, /* Bartlett (triangle window) */
anthony06b1edf2010-10-25 01:19:50 +0000781 { SincFast, 3.0, 1.0, 0.0, 0.0 }, /* Sharpened 3-lobe Lanczos */
nicolas8eccc162010-10-16 19:48:13 +0000782 { Jinc, 2.0, 1.2196698912665045, 0.0, 0.0 },
anthony06b1edf2010-10-25 01:19:50 +0000783 /* 2-lobe Cylindrical Lanczos (Jinc-Jinc) */
nicolas8eccc162010-10-16 19:48:13 +0000784 { Jinc, 2.0, 1.1684849904329952, 0.0, 0.0 },
785 /* Lanczos2D sharpened with blur=0.958033808 */
anthony450db502010-10-19 04:03:03 +0000786 { CubicBC, 2.0, 1.1685777620836932,
nicolas0d5e5322010-10-22 15:29:30 +0000787 0.37821575509399867, 0.31089212245300067 }
788 /* Robidoux: Keys cubic close to Lanczos2D sharpened */
cristy3ed852e2009-09-05 21:47:34 +0000789 };
790 /*
anthony9a98fc62010-10-11 02:47:19 +0000791 The known zero crossings of the Jinc() or more accurately the Jinc(x*PI)
anthony450db502010-10-19 04:03:03 +0000792 function being used as a filter. It is used by the "filter:lobes" expert
793 setting and for 'lobes' for Jinc functions in the previous table. This
794 way users do not have to deal with the highly irrational lobe sizes of the
anthony9a98fc62010-10-11 02:47:19 +0000795 Jinc filter.
anthony48f77622010-10-03 14:32:31 +0000796
nicolase473f722010-10-07 00:05:13 +0000797 Values taken from
anthony48f77622010-10-03 14:32:31 +0000798 http://cose.math.bas.bg/webMathematica/webComputing/BesselZeros.jsp
nicolase473f722010-10-07 00:05:13 +0000799 using Jv-function with v=1, then dividing by PI.
cristy3ed852e2009-09-05 21:47:34 +0000800 */
801 static MagickRealType
anthony48f77622010-10-03 14:32:31 +0000802 jinc_zeros[16] =
cristy3ed852e2009-09-05 21:47:34 +0000803 {
nicolas8eccc162010-10-16 19:48:13 +0000804 1.2196698912665045,
805 2.2331305943815286,
806 3.2383154841662362,
807 4.2410628637960699,
808 5.2427643768701817,
809 6.2439216898644877,
810 7.244759868719957,
811 8.2453949139520427,
812 9.2458926849494673,
813 10.246293348754916,
814 11.246622794877883,
815 12.246898461138105,
816 13.247132522181061,
817 14.247333735806849,
anthonyc2d07db2010-09-15 23:47:40 +0000818 15.2475085630373,
nicolas8eccc162010-10-16 19:48:13 +0000819 16.247661874700962
cristy3ed852e2009-09-05 21:47:34 +0000820 };
821
cristy33b1c162010-01-23 22:51:51 +0000822 /*
823 Allocate resize filter.
824 */
cristy3ed852e2009-09-05 21:47:34 +0000825 assert(image != (const Image *) NULL);
826 assert(image->signature == MagickSignature);
827 if (image->debug != MagickFalse)
828 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
829 assert(UndefinedFilter < filter && filter < SentinelFilter);
830 assert(exception != (ExceptionInfo *) NULL);
831 assert(exception->signature == MagickSignature);
cristy73bd4a52010-10-05 11:24:23 +0000832 resize_filter=(ResizeFilter *) AcquireMagickMemory(sizeof(*resize_filter));
cristy3ed852e2009-09-05 21:47:34 +0000833 if (resize_filter == (ResizeFilter *) NULL)
834 ThrowFatalException(ResourceLimitFatalError,"MemoryAllocationFailed");
cristy33b1c162010-01-23 22:51:51 +0000835 /*
836 Defaults for the requested filter.
837 */
838 filter_type=mapping[filter].filter;
839 window_type=mapping[filter].window;
anthonyf5e76ef2010-10-12 01:22:01 +0000840 resize_filter->blur = blur;
841 sigma = 0.5;
anthony48f77622010-10-03 14:32:31 +0000842 /* Cylindrical Filters should use Jinc instead of Sinc */
anthonyb6d08c52010-09-13 01:17:04 +0000843 if (cylindrical != MagickFalse)
cristy33b1c162010-01-23 22:51:51 +0000844 switch (filter_type)
845 {
846 case SincFilter:
anthony48f77622010-10-03 14:32:31 +0000847 /* Promote 1D Sinc Filter to a 2D Jinc filter. */
anthonyb6d08c52010-09-13 01:17:04 +0000848 if ( filter != SincFilter )
anthony48f77622010-10-03 14:32:31 +0000849 filter_type=JincFilter;
cristy33b1c162010-01-23 22:51:51 +0000850 break;
anthonyba5a7c32010-09-15 02:42:25 +0000851 case SincFastFilter:
nicolas07bac812010-09-19 18:47:02 +0000852 /* Ditto for SincFast variant */
anthonyba5a7c32010-09-15 02:42:25 +0000853 if ( filter != SincFastFilter )
anthony48f77622010-10-03 14:32:31 +0000854 filter_type=JincFilter;
anthony7bdc0ed2010-09-15 01:52:32 +0000855 break;
anthony06b1edf2010-10-25 01:19:50 +0000856 case LanczosSharpFilter:
857 /* A slightly sharper Lanczos */
858 resize_filter->blur *= 0.9830391168464;
859 /* fall-thru to promote it to Jinc-Jinc */
cristy33b1c162010-01-23 22:51:51 +0000860 case LanczosFilter:
nicolas45b58a92010-10-07 15:46:39 +0000861 /* Promote Lanczos from a Sinc-Sinc to a Jinc-Jinc. */
anthony48f77622010-10-03 14:32:31 +0000862 filter_type=JincFilter;
863 window_type=JincFilter;
cristy33b1c162010-01-23 22:51:51 +0000864 break;
anthony08958462010-10-12 06:48:35 +0000865 case Lanczos2DSharpFilter:
anthony06b1edf2010-10-25 01:19:50 +0000866 /* Sharpen Lanczos2d so as to optimize for minimal blurring of
867 * orthogonal lines in EWA cylindrical usage. Value determined by
868 * Nicolas Robidoux. This filter is always a Jinc-Jinc.
anthony08958462010-10-12 06:48:35 +0000869 */
870 resize_filter->blur *= 0.958033808;
871 break;
anthonyf5e76ef2010-10-12 01:22:01 +0000872 case GaussianFilter:
cristy68f103e2010-10-14 01:19:07 +0000873 sigma = (MagickRealType) (MagickSQ2/2.0); /* Cylindrical Gaussian sigma is sqrt(2)/2 */
anthonyf5e76ef2010-10-12 01:22:01 +0000874 break;
cristya782ecf2010-01-25 02:59:14 +0000875 default:
876 break;
cristy3ed852e2009-09-05 21:47:34 +0000877 }
anthony61b5ddd2010-10-05 02:33:31 +0000878 else
879 switch (filter_type)
880 {
anthony06b1edf2010-10-25 01:19:50 +0000881 case LanczosSharpFilter:
882 resize_filter->blur *= 0.9830391168464; /* Orthogonal sharpen too */
883 break;
anthonye5f06452010-10-12 05:48:17 +0000884 case Lanczos2DSharpFilter:
anthony06b1edf2010-10-25 01:19:50 +0000885 resize_filter->blur *= 0.958033808; /* Orthogonal sharpen too */
anthony61b5ddd2010-10-05 02:33:31 +0000886 break;
887 default:
888 break;
889 }
890
cristy3ed852e2009-09-05 21:47:34 +0000891 artifact=GetImageArtifact(image,"filter:filter");
cristy33b1c162010-01-23 22:51:51 +0000892 if (artifact != (const char *) NULL)
893 {
cristy9af9b5d2010-08-15 17:04:28 +0000894 option=ParseMagickOption(MagickFilterOptions,MagickFalse,artifact);
cristy33b1c162010-01-23 22:51:51 +0000895 if ((UndefinedFilter < option) && (option < SentinelFilter))
anthony61b5ddd2010-10-05 02:33:31 +0000896 { /* Raw filter request - no window function. */
cristy33b1c162010-01-23 22:51:51 +0000897 filter_type=(FilterTypes) option;
898 window_type=BoxFilter;
899 }
900 if (option == LanczosFilter)
anthony61b5ddd2010-10-05 02:33:31 +0000901 { /* Lanczos is not a real filter but a self windowing Sinc/Jinc. */
anthony853d6972010-10-08 06:01:31 +0000902 filter_type=cylindrical != MagickFalse ? JincFilter : LanczosFilter;
anthony48f77622010-10-03 14:32:31 +0000903 window_type=cylindrical != MagickFalse ? JincFilter : SincFastFilter;
cristy33b1c162010-01-23 22:51:51 +0000904 }
nicolas07bac812010-09-19 18:47:02 +0000905 /* Filter override with a specific window function. */
cristy33b1c162010-01-23 22:51:51 +0000906 artifact=GetImageArtifact(image,"filter:window");
907 if (artifact != (const char *) NULL)
908 {
cristy9af9b5d2010-08-15 17:04:28 +0000909 option=ParseMagickOption(MagickFilterOptions,MagickFalse,artifact);
cristy33b1c162010-01-23 22:51:51 +0000910 if ((UndefinedFilter < option) && (option < SentinelFilter))
911 {
912 if (option != LanczosFilter)
913 window_type=(FilterTypes) option;
cristy9af9b5d2010-08-15 17:04:28 +0000914 else
anthony48f77622010-10-03 14:32:31 +0000915 window_type=cylindrical != MagickFalse ? JincFilter :
cristy03dbbd22010-09-19 23:04:47 +0000916 SincFastFilter;
cristy9af9b5d2010-08-15 17:04:28 +0000917 }
cristy33b1c162010-01-23 22:51:51 +0000918 }
cristy3ed852e2009-09-05 21:47:34 +0000919 }
cristy33b1c162010-01-23 22:51:51 +0000920 else
921 {
anthony48f77622010-10-03 14:32:31 +0000922 /* Window specified, but no filter function? Assume Sinc/Jinc. */
cristy33b1c162010-01-23 22:51:51 +0000923 artifact=GetImageArtifact(image,"filter:window");
924 if (artifact != (const char *) NULL)
925 {
926 option=ParseMagickOption(MagickFilterOptions,MagickFalse,
927 artifact);
928 if ((UndefinedFilter < option) && (option < SentinelFilter))
929 {
anthony61b5ddd2010-10-05 02:33:31 +0000930 filter_type=cylindrical != MagickFalse ?
931 JincFilter : SincFastFilter;
cristy19eb6412010-04-23 14:42:29 +0000932 window_type=(FilterTypes) option;
cristy33b1c162010-01-23 22:51:51 +0000933 }
934 }
935 }
nicolas07bac812010-09-19 18:47:02 +0000936 /* Assign the real functions to use for the filters selected. */
cristy33b1c162010-01-23 22:51:51 +0000937 resize_filter->filter=filters[filter_type].function;
anthony61b5ddd2010-10-05 02:33:31 +0000938 resize_filter->support=filters[filter_type].lobes;
cristy33b1c162010-01-23 22:51:51 +0000939 resize_filter->window=filters[window_type].function;
940 resize_filter->scale=filters[window_type].scale;
cristy3ed852e2009-09-05 21:47:34 +0000941 resize_filter->signature=MagickSignature;
anthony61b5ddd2010-10-05 02:33:31 +0000942
anthonyf5e76ef2010-10-12 01:22:01 +0000943 /* Filter Modifications for orthogonal/cylindrical usage */
anthony10b8bc82010-10-02 12:48:46 +0000944 if (cylindrical != MagickFalse)
945 switch (filter_type)
946 {
947 case PointFilter:
948 case BoxFilter:
949 /* Support for Cylindrical Box should be sqrt(2)/2 */
cristy1c9bb452010-10-03 16:48:33 +0000950 resize_filter->support=(MagickRealType) MagickSQ1_2;
anthony10b8bc82010-10-02 12:48:46 +0000951 break;
anthony81b8bf92010-10-02 13:54:34 +0000952 default:
953 break;
anthony10b8bc82010-10-02 12:48:46 +0000954 }
anthony61b5ddd2010-10-05 02:33:31 +0000955
anthonyf5e76ef2010-10-12 01:22:01 +0000956 /*
anthony06b1edf2010-10-25 01:19:50 +0000957 ** Other Expert Option Modifications
anthonyf5e76ef2010-10-12 01:22:01 +0000958 */
959
960 /* User Sigma Override - no support change */
961 artifact=GetImageArtifact(image,"filter:sigma");
962 if (artifact != (const char *) NULL)
963 sigma=StringToDouble(artifact);
964 /* Define coefficents for Gaussian (assumes no cubic window) */
965 if ( GaussianFilter ) {
966 resize_filter->coeff[0] = 1.0/(2.0*sigma*sigma);
cristy68f103e2010-10-14 01:19:07 +0000967 resize_filter->coeff[1] = (MagickRealType) (1.0/(Magick2PI*sigma*sigma)); /* unused */
anthonyf5e76ef2010-10-12 01:22:01 +0000968 }
969
970 /* Blur Override */
971 artifact=GetImageArtifact(image,"filter:blur");
972 if (artifact != (const char *) NULL)
973 resize_filter->blur=StringToDouble(artifact);
974 if (resize_filter->blur < MagickEpsilon)
975 resize_filter->blur=(MagickRealType) MagickEpsilon;
976
977 /* Support Overrides */
cristy3ed852e2009-09-05 21:47:34 +0000978 artifact=GetImageArtifact(image,"filter:lobes");
cristy33b1c162010-01-23 22:51:51 +0000979 if (artifact != (const char *) NULL)
980 {
cristybb503372010-05-27 20:51:26 +0000981 ssize_t
cristy33b1c162010-01-23 22:51:51 +0000982 lobes;
983
cristy96b16132010-08-29 17:19:52 +0000984 lobes=(ssize_t) StringToLong(artifact);
cristy33b1c162010-01-23 22:51:51 +0000985 if (lobes < 1)
986 lobes=1;
987 resize_filter->support=(MagickRealType) lobes;
cristy3ed852e2009-09-05 21:47:34 +0000988 }
anthony06b1edf2010-10-25 01:19:50 +0000989 /* convert a Jinc function lobes value to a real support value */
anthony61b5ddd2010-10-05 02:33:31 +0000990 if (resize_filter->filter == Jinc)
991 {
992 if (resize_filter->support > 16)
993 resize_filter->support=jinc_zeros[15]; /* largest entry in table */
994 else
995 resize_filter->support = jinc_zeros[((long)resize_filter->support)-1];
996 }
997 /* expert override of the support setting */
cristy3ed852e2009-09-05 21:47:34 +0000998 artifact=GetImageArtifact(image,"filter:support");
999 if (artifact != (const char *) NULL)
cristy33b1c162010-01-23 22:51:51 +00001000 resize_filter->support=fabs(StringToDouble(artifact));
1001 /*
nicolas07bac812010-09-19 18:47:02 +00001002 Scale windowing function separatally to the support 'clipping'
1003 window that calling operator is planning to actually use. (Expert
1004 override)
cristy3ed852e2009-09-05 21:47:34 +00001005 */
anthony55f12332010-09-10 01:13:02 +00001006 resize_filter->window_support=resize_filter->support; /* default */
cristy3ed852e2009-09-05 21:47:34 +00001007 artifact=GetImageArtifact(image,"filter:win-support");
1008 if (artifact != (const char *) NULL)
cristy33b1c162010-01-23 22:51:51 +00001009 resize_filter->window_support=fabs(StringToDouble(artifact));
1010 /*
anthony1f90a6b2010-09-14 08:56:31 +00001011 Adjust window function scaling to the windowing support for
1012 weighting function. This avoids a division on every filter call.
anthony55f12332010-09-10 01:13:02 +00001013 */
1014 resize_filter->scale /= resize_filter->window_support;
anthonyf5e76ef2010-10-12 01:22:01 +00001015
anthony55f12332010-09-10 01:13:02 +00001016 /*
anthonyf5e76ef2010-10-12 01:22:01 +00001017 * Set Cubic Spline B,C values, calculate Cubic coefficients.
cristy33b1c162010-01-23 22:51:51 +00001018 */
cristy3ed852e2009-09-05 21:47:34 +00001019 B=0.0;
1020 C=0.0;
cristy33b1c162010-01-23 22:51:51 +00001021 if ((filters[filter_type].function == CubicBC) ||
1022 (filters[window_type].function == CubicBC))
1023 {
anthony2d9b8b52010-09-14 08:31:07 +00001024 B=filters[filter_type].B;
1025 C=filters[filter_type].C;
1026 if (filters[window_type].function == CubicBC)
cristy33b1c162010-01-23 22:51:51 +00001027 {
anthony2d9b8b52010-09-14 08:31:07 +00001028 B=filters[window_type].B;
1029 C=filters[window_type].C;
cristy33b1c162010-01-23 22:51:51 +00001030 }
cristy33b1c162010-01-23 22:51:51 +00001031 artifact=GetImageArtifact(image,"filter:b");
cristy3ed852e2009-09-05 21:47:34 +00001032 if (artifact != (const char *) NULL)
cristy33b1c162010-01-23 22:51:51 +00001033 {
1034 B=StringToDouble(artifact);
nicolasd15ee9a2010-10-24 18:39:45 +00001035 C=(1.0-B)/2.0; /* Calculate C to get a Keys cubic filter. */
anthonyf5e76ef2010-10-12 01:22:01 +00001036 artifact=GetImageArtifact(image,"filter:c"); /* user C override */
cristy33b1c162010-01-23 22:51:51 +00001037 if (artifact != (const char *) NULL)
1038 C=StringToDouble(artifact);
1039 }
1040 else
1041 {
1042 artifact=GetImageArtifact(image,"filter:c");
1043 if (artifact != (const char *) NULL)
1044 {
1045 C=StringToDouble(artifact);
nicolasd15ee9a2010-10-24 18:39:45 +00001046 B=1.0-2.0*C; /* Calculate B to get a Keys cubic filter. */
cristy33b1c162010-01-23 22:51:51 +00001047 }
1048 }
nicolasc6bac3b2010-10-24 18:10:45 +00001049 /* Convert B,C values into Cubic Coefficents. See CubicBC(). */
nicolasd15ee9a2010-10-24 18:39:45 +00001050 {
anthony06b1edf2010-10-25 01:19:50 +00001051 const double twoB = B+B;
1052 resize_filter->coeff[0]=1.0-(1.0/3.0)*B;
1053 resize_filter->coeff[1]=-3.0+twoB+C;
1054 resize_filter->coeff[2]=2.0-1.5*B-C;
1055 resize_filter->coeff[3]=(4.0/3.0)*B+4.0*C;
1056 resize_filter->coeff[4]=-8.0*C-twoB;
1057 resize_filter->coeff[5]=B+5.0*C;
1058 resize_filter->coeff[6]=(-1.0/6.0)*B-C;
nicolasd15ee9a2010-10-24 18:39:45 +00001059 }
nicolasc6bac3b2010-10-24 18:10:45 +00001060 }
anthonyf5e76ef2010-10-12 01:22:01 +00001061
anthony55f12332010-09-10 01:13:02 +00001062 /*
nicolas07bac812010-09-19 18:47:02 +00001063 Expert Option Request for verbose details of the resulting filter.
anthony55f12332010-09-10 01:13:02 +00001064 */
cristyf5b49372010-10-16 01:06:47 +00001065#if defined(MAGICKCORE_OPENMP_SUPPORT)
1066 #pragma omp master
1067 {
1068#endif
1069 artifact=GetImageArtifact(image,"filter:verbose");
1070 if (artifact != (const char *) NULL)
1071 {
1072 double
anthony06b1edf2010-10-25 01:19:50 +00001073 support,
cristyf5b49372010-10-16 01:06:47 +00001074 x;
cristy3ed852e2009-09-05 21:47:34 +00001075
cristyf5b49372010-10-16 01:06:47 +00001076 /*
1077 Set the weighting function properly when the weighting
1078 function may not exactly match the filter of the same name.
anthony06b1edf2010-10-25 01:19:50 +00001079 EG: a Point filter is really uses a Box weighting function
cristyf5b49372010-10-16 01:06:47 +00001080 with a different support than is typically used.
cristyf5b49372010-10-16 01:06:47 +00001081 */
1082 if (resize_filter->filter == Box) filter_type=BoxFilter;
1083 if (resize_filter->filter == Sinc) filter_type=SincFilter;
1084 if (resize_filter->filter == SincFast) filter_type=SincFastFilter;
1085 if (resize_filter->filter == Jinc) filter_type=JincFilter;
1086 if (resize_filter->filter == CubicBC) filter_type=CubicFilter;
1087 /*
1088 Report Filter Details.
1089 */
1090 support=GetResizeFilterSupport(resize_filter); /* practical_support */
1091 (void) fprintf(stdout,"# Resize Filter (for graphing)\n#\n");
anthony06b1edf2010-10-25 01:19:50 +00001092 (void) fprintf(stdout,"# filter = %s\n",
1093 MagickOptionToMnemonic(MagickFilterOptions,filter_type));
1094 (void) fprintf(stdout,"# window = %s\n",
1095 MagickOptionToMnemonic(MagickFilterOptions, window_type));
1096 (void) fprintf(stdout,"# support = %.*g\n",
1097 GetMagickPrecision(),(double) resize_filter->support);
1098 (void) fprintf(stdout,"# win-support = %.*g\n",
1099 GetMagickPrecision(),(double) resize_filter->window_support);
1100 (void) fprintf(stdout,"# scale_blur = %.*g\n",
1101 GetMagickPrecision(), (double)resize_filter->blur);
cristyf5b49372010-10-16 01:06:47 +00001102 if ( filter_type == GaussianFilter )
anthony06b1edf2010-10-25 01:19:50 +00001103 (void) fprintf(stdout,"# gaussian_sigma = %.*g\n",
1104 GetMagickPrecision(), (double)sigma);
1105 (void) fprintf(stdout,"# practical_support = %.*g\n",
1106 GetMagickPrecision(), (double)support);
cristyf5b49372010-10-16 01:06:47 +00001107 if ( filter_type == CubicFilter || window_type == CubicFilter )
anthony06b1edf2010-10-25 01:19:50 +00001108 (void) fprintf(stdout,"# B,C = %.*g,%.*g\n",
1109 GetMagickPrecision(),(double)B, GetMagickPrecision(),(double)C);
cristyf5b49372010-10-16 01:06:47 +00001110 (void) fprintf(stdout,"\n");
1111 /*
1112 Output values of resulting filter graph -- for graphing
1113 filter result.
1114 */
1115 for (x=0.0; x <= support; x+=0.01f)
1116 (void) fprintf(stdout,"%5.2lf\t%.*g\n",x,GetMagickPrecision(),
1117 (double) GetResizeFilterWeight(resize_filter,x));
1118 /* A final value so gnuplot can graph the 'stop' properly. */
1119 (void) fprintf(stdout,"%5.2lf\t%.*g\n",support,GetMagickPrecision(),
1120 0.0);
1121 }
1122 /* Output the above once only for each image - remove setting */
1123 (void) DeleteImageArtifact((Image *) image,"filter:verbose");
1124#if defined(MAGICKCORE_OPENMP_SUPPORT)
1125 }
1126#endif
cristy3ed852e2009-09-05 21:47:34 +00001127 return(resize_filter);
1128}
1129
1130/*
1131%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1132% %
1133% %
1134% %
1135% A d a p t i v e R e s i z e I m a g e %
1136% %
1137% %
1138% %
1139%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1140%
1141% AdaptiveResizeImage() adaptively resize image with pixel resampling.
1142%
1143% The format of the AdaptiveResizeImage method is:
1144%
cristy9af9b5d2010-08-15 17:04:28 +00001145% Image *AdaptiveResizeImage(const Image *image,const size_t columns,
1146% const size_t rows,ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +00001147%
1148% A description of each parameter follows:
1149%
1150% o image: the image.
1151%
1152% o columns: the number of columns in the resized image.
1153%
1154% o rows: the number of rows in the resized image.
1155%
1156% o exception: return any errors or warnings in this structure.
1157%
1158*/
1159MagickExport Image *AdaptiveResizeImage(const Image *image,
cristybb503372010-05-27 20:51:26 +00001160 const size_t columns,const size_t rows,ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +00001161{
1162#define AdaptiveResizeImageTag "Resize/Image"
1163
cristyc4c8d132010-01-07 01:58:38 +00001164 CacheView
1165 *resize_view;
1166
cristy3ed852e2009-09-05 21:47:34 +00001167 Image
1168 *resize_image;
1169
cristy3ed852e2009-09-05 21:47:34 +00001170 MagickBooleanType
1171 proceed;
1172
1173 MagickPixelPacket
1174 pixel;
1175
1176 PointInfo
1177 offset;
1178
1179 ResampleFilter
1180 *resample_filter;
1181
cristy9af9b5d2010-08-15 17:04:28 +00001182 ssize_t
1183 y;
1184
cristy3ed852e2009-09-05 21:47:34 +00001185 /*
1186 Adaptively resize image.
1187 */
1188 assert(image != (const Image *) NULL);
1189 assert(image->signature == MagickSignature);
1190 if (image->debug != MagickFalse)
1191 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
1192 assert(exception != (ExceptionInfo *) NULL);
1193 assert(exception->signature == MagickSignature);
1194 if ((columns == 0) || (rows == 0))
1195 return((Image *) NULL);
1196 if ((columns == image->columns) && (rows == image->rows))
1197 return(CloneImage(image,0,0,MagickTrue,exception));
1198 resize_image=CloneImage(image,columns,rows,MagickTrue,exception);
1199 if (resize_image == (Image *) NULL)
1200 return((Image *) NULL);
1201 if (SetImageStorageClass(resize_image,DirectClass) == MagickFalse)
1202 {
1203 InheritException(exception,&resize_image->exception);
1204 resize_image=DestroyImage(resize_image);
1205 return((Image *) NULL);
1206 }
1207 GetMagickPixelPacket(image,&pixel);
1208 resample_filter=AcquireResampleFilter(image,exception);
cristy0c7e9eb2010-09-30 14:23:37 +00001209 (void) SetResampleFilter(resample_filter,PointFilter,1.0);
anthonyccf239d2010-09-30 04:23:46 +00001210 (void) SetResampleFilterInterpolateMethod(resample_filter,
cristy0c7e9eb2010-09-30 14:23:37 +00001211 MeshInterpolatePixel);
cristy3ed852e2009-09-05 21:47:34 +00001212 resize_view=AcquireCacheView(resize_image);
cristybb503372010-05-27 20:51:26 +00001213 for (y=0; y < (ssize_t) resize_image->rows; y++)
cristy3ed852e2009-09-05 21:47:34 +00001214 {
1215 register IndexPacket
cristyc47d1f82009-11-26 01:44:43 +00001216 *restrict resize_indexes;
cristy3ed852e2009-09-05 21:47:34 +00001217
cristybb503372010-05-27 20:51:26 +00001218 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +00001219 x;
1220
1221 register PixelPacket
cristyc47d1f82009-11-26 01:44:43 +00001222 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +00001223
1224 q=QueueCacheViewAuthenticPixels(resize_view,0,y,resize_image->columns,1,
1225 exception);
1226 if (q == (PixelPacket *) NULL)
1227 break;
1228 resize_indexes=GetCacheViewAuthenticIndexQueue(resize_view);
1229 offset.y=((MagickRealType) y*image->rows/resize_image->rows);
cristybb503372010-05-27 20:51:26 +00001230 for (x=0; x < (ssize_t) resize_image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +00001231 {
1232 offset.x=((MagickRealType) x*image->columns/resize_image->columns);
1233 (void) ResamplePixelColor(resample_filter,offset.x-0.5,offset.y-0.5,
1234 &pixel);
1235 SetPixelPacket(resize_image,&pixel,q,resize_indexes+x);
1236 q++;
1237 }
1238 if (SyncCacheViewAuthenticPixels(resize_view,exception) == MagickFalse)
1239 break;
cristy96b16132010-08-29 17:19:52 +00001240 proceed=SetImageProgress(image,AdaptiveResizeImageTag,(MagickOffsetType) y,
1241 image->rows);
cristy3ed852e2009-09-05 21:47:34 +00001242 if (proceed == MagickFalse)
1243 break;
1244 }
1245 resample_filter=DestroyResampleFilter(resample_filter);
1246 resize_view=DestroyCacheView(resize_view);
1247 return(resize_image);
1248}
1249
1250/*
1251%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1252% %
1253% %
1254% %
1255+ B e s s e l O r d e r O n e %
1256% %
1257% %
1258% %
1259%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1260%
1261% BesselOrderOne() computes the Bessel function of x of the first kind of
anthony48f77622010-10-03 14:32:31 +00001262% order 0. This is used to create the Jinc() filter function below.
cristy3ed852e2009-09-05 21:47:34 +00001263%
1264% Reduce x to |x| since j1(x)= -j1(-x), and for x in (0,8]
1265%
1266% j1(x) = x*j1(x);
1267%
1268% For x in (8,inf)
1269%
1270% j1(x) = sqrt(2/(pi*x))*(p1(x)*cos(x1)-q1(x)*sin(x1))
1271%
1272% where x1 = x-3*pi/4. Compute sin(x1) and cos(x1) as follow:
1273%
1274% cos(x1) = cos(x)cos(3pi/4)+sin(x)sin(3pi/4)
1275% = 1/sqrt(2) * (sin(x) - cos(x))
1276% sin(x1) = sin(x)cos(3pi/4)-cos(x)sin(3pi/4)
1277% = -1/sqrt(2) * (sin(x) + cos(x))
1278%
1279% The format of the BesselOrderOne method is:
1280%
1281% MagickRealType BesselOrderOne(MagickRealType x)
1282%
1283% A description of each parameter follows:
1284%
1285% o x: MagickRealType value.
1286%
1287*/
1288
1289#undef I0
1290static MagickRealType I0(MagickRealType x)
1291{
1292 MagickRealType
1293 sum,
1294 t,
1295 y;
1296
cristybb503372010-05-27 20:51:26 +00001297 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +00001298 i;
1299
1300 /*
1301 Zeroth order Bessel function of the first kind.
1302 */
1303 sum=1.0;
1304 y=x*x/4.0;
1305 t=y;
1306 for (i=2; t > MagickEpsilon; i++)
1307 {
1308 sum+=t;
1309 t*=y/((MagickRealType) i*i);
1310 }
1311 return(sum);
1312}
1313
1314#undef J1
1315static MagickRealType J1(MagickRealType x)
1316{
1317 MagickRealType
1318 p,
1319 q;
1320
cristybb503372010-05-27 20:51:26 +00001321 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +00001322 i;
1323
1324 static const double
1325 Pone[] =
1326 {
1327 0.581199354001606143928050809e+21,
1328 -0.6672106568924916298020941484e+20,
1329 0.2316433580634002297931815435e+19,
1330 -0.3588817569910106050743641413e+17,
1331 0.2908795263834775409737601689e+15,
1332 -0.1322983480332126453125473247e+13,
1333 0.3413234182301700539091292655e+10,
1334 -0.4695753530642995859767162166e+7,
1335 0.270112271089232341485679099e+4
1336 },
1337 Qone[] =
1338 {
1339 0.11623987080032122878585294e+22,
1340 0.1185770712190320999837113348e+20,
1341 0.6092061398917521746105196863e+17,
1342 0.2081661221307607351240184229e+15,
1343 0.5243710262167649715406728642e+12,
1344 0.1013863514358673989967045588e+10,
1345 0.1501793594998585505921097578e+7,
1346 0.1606931573481487801970916749e+4,
1347 0.1e+1
1348 };
1349
1350 p=Pone[8];
1351 q=Qone[8];
1352 for (i=7; i >= 0; i--)
1353 {
1354 p=p*x*x+Pone[i];
1355 q=q*x*x+Qone[i];
1356 }
1357 return(p/q);
1358}
1359
1360#undef P1
1361static MagickRealType P1(MagickRealType x)
1362{
1363 MagickRealType
1364 p,
1365 q;
1366
cristybb503372010-05-27 20:51:26 +00001367 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +00001368 i;
1369
1370 static const double
1371 Pone[] =
1372 {
1373 0.352246649133679798341724373e+5,
1374 0.62758845247161281269005675e+5,
1375 0.313539631109159574238669888e+5,
1376 0.49854832060594338434500455e+4,
1377 0.2111529182853962382105718e+3,
1378 0.12571716929145341558495e+1
1379 },
1380 Qone[] =
1381 {
1382 0.352246649133679798068390431e+5,
1383 0.626943469593560511888833731e+5,
1384 0.312404063819041039923015703e+5,
1385 0.4930396490181088979386097e+4,
1386 0.2030775189134759322293574e+3,
1387 0.1e+1
1388 };
1389
1390 p=Pone[5];
1391 q=Qone[5];
1392 for (i=4; i >= 0; i--)
1393 {
1394 p=p*(8.0/x)*(8.0/x)+Pone[i];
1395 q=q*(8.0/x)*(8.0/x)+Qone[i];
1396 }
1397 return(p/q);
1398}
1399
1400#undef Q1
1401static MagickRealType Q1(MagickRealType x)
1402{
1403 MagickRealType
1404 p,
1405 q;
1406
cristybb503372010-05-27 20:51:26 +00001407 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +00001408 i;
1409
1410 static const double
1411 Pone[] =
1412 {
1413 0.3511751914303552822533318e+3,
1414 0.7210391804904475039280863e+3,
1415 0.4259873011654442389886993e+3,
1416 0.831898957673850827325226e+2,
1417 0.45681716295512267064405e+1,
1418 0.3532840052740123642735e-1
1419 },
1420 Qone[] =
1421 {
1422 0.74917374171809127714519505e+4,
1423 0.154141773392650970499848051e+5,
1424 0.91522317015169922705904727e+4,
1425 0.18111867005523513506724158e+4,
1426 0.1038187585462133728776636e+3,
1427 0.1e+1
1428 };
1429
1430 p=Pone[5];
1431 q=Qone[5];
1432 for (i=4; i >= 0; i--)
1433 {
1434 p=p*(8.0/x)*(8.0/x)+Pone[i];
1435 q=q*(8.0/x)*(8.0/x)+Qone[i];
1436 }
1437 return(p/q);
1438}
1439
1440static MagickRealType BesselOrderOne(MagickRealType x)
1441{
1442 MagickRealType
1443 p,
1444 q;
1445
1446 if (x == 0.0)
1447 return(0.0);
1448 p=x;
1449 if (x < 0.0)
1450 x=(-x);
1451 if (x < 8.0)
1452 return(p*J1(x));
1453 q=sqrt((double) (2.0/(MagickPI*x)))*(P1(x)*(1.0/sqrt(2.0)*(sin((double) x)-
1454 cos((double) x)))-8.0/x*Q1(x)*(-1.0/sqrt(2.0)*(sin((double) x)+
1455 cos((double) x))));
1456 if (p < 0.0)
1457 q=(-q);
1458 return(q);
1459}
1460
1461/*
1462%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1463% %
1464% %
1465% %
1466+ D e s t r o y R e s i z e F i l t e r %
1467% %
1468% %
1469% %
1470%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1471%
1472% DestroyResizeFilter() destroy the resize filter.
1473%
cristya2ffd7e2010-03-10 20:50:30 +00001474% The format of the DestroyResizeFilter method is:
cristy3ed852e2009-09-05 21:47:34 +00001475%
1476% ResizeFilter *DestroyResizeFilter(ResizeFilter *resize_filter)
1477%
1478% A description of each parameter follows:
1479%
1480% o resize_filter: the resize filter.
1481%
1482*/
1483MagickExport ResizeFilter *DestroyResizeFilter(ResizeFilter *resize_filter)
1484{
1485 assert(resize_filter != (ResizeFilter *) NULL);
1486 assert(resize_filter->signature == MagickSignature);
1487 resize_filter->signature=(~MagickSignature);
1488 resize_filter=(ResizeFilter *) RelinquishMagickMemory(resize_filter);
1489 return(resize_filter);
1490}
1491
1492/*
1493%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1494% %
1495% %
1496% %
1497+ G e t R e s i z e F i l t e r S u p p o r t %
1498% %
1499% %
1500% %
1501%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1502%
1503% GetResizeFilterSupport() return the current support window size for this
1504% filter. Note that this may have been enlarged by filter:blur factor.
1505%
1506% The format of the GetResizeFilterSupport method is:
1507%
1508% MagickRealType GetResizeFilterSupport(const ResizeFilter *resize_filter)
1509%
1510% A description of each parameter follows:
1511%
1512% o filter: Image filter to use.
1513%
1514*/
1515MagickExport MagickRealType GetResizeFilterSupport(
1516 const ResizeFilter *resize_filter)
1517{
1518 assert(resize_filter != (ResizeFilter *) NULL);
1519 assert(resize_filter->signature == MagickSignature);
1520 return(resize_filter->support*resize_filter->blur);
1521}
1522
1523/*
1524%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1525% %
1526% %
1527% %
1528+ G e t R e s i z e F i l t e r W e i g h t %
1529% %
1530% %
1531% %
1532%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1533%
1534% GetResizeFilterWeight evaluates the specified resize filter at the point x
1535% which usally lies between zero and the filters current 'support' and
1536% returns the weight of the filter function at that point.
1537%
1538% The format of the GetResizeFilterWeight method is:
1539%
1540% MagickRealType GetResizeFilterWeight(const ResizeFilter *resize_filter,
1541% const MagickRealType x)
1542%
1543% A description of each parameter follows:
1544%
1545% o filter: the filter type.
1546%
1547% o x: the point.
1548%
1549*/
1550MagickExport MagickRealType GetResizeFilterWeight(
1551 const ResizeFilter *resize_filter,const MagickRealType x)
1552{
1553 MagickRealType
cristyb8385862010-09-26 01:27:51 +00001554 scale,
1555 x_blur;
cristy3ed852e2009-09-05 21:47:34 +00001556
1557 /*
1558 Windowing function - scale the weighting filter by this amount.
1559 */
1560 assert(resize_filter != (ResizeFilter *) NULL);
1561 assert(resize_filter->signature == MagickSignature);
anthony55f12332010-09-10 01:13:02 +00001562 x_blur=fabs((double) x)/resize_filter->blur; /* X offset with blur scaling */
cristy3ed852e2009-09-05 21:47:34 +00001563 if ((resize_filter->window_support < MagickEpsilon) ||
1564 (resize_filter->window == Box))
anthony55f12332010-09-10 01:13:02 +00001565 scale=1.0; /* Point or Box Filter -- avoid division by zero */
cristy3ed852e2009-09-05 21:47:34 +00001566 else
1567 {
anthony55f12332010-09-10 01:13:02 +00001568 scale=resize_filter->scale;
1569 scale=resize_filter->window(x_blur*scale,resize_filter);
cristy3ed852e2009-09-05 21:47:34 +00001570 }
anthony55f12332010-09-10 01:13:02 +00001571 return(scale*resize_filter->filter(x_blur,resize_filter));
cristy3ed852e2009-09-05 21:47:34 +00001572}
1573
1574/*
1575%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1576% %
1577% %
1578% %
1579% M a g n i f y I m a g e %
1580% %
1581% %
1582% %
1583%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1584%
1585% MagnifyImage() is a convenience method that scales an image proportionally
1586% to twice its size.
1587%
1588% The format of the MagnifyImage method is:
1589%
1590% Image *MagnifyImage(const Image *image,ExceptionInfo *exception)
1591%
1592% A description of each parameter follows:
1593%
1594% o image: the image.
1595%
1596% o exception: return any errors or warnings in this structure.
1597%
1598*/
1599MagickExport Image *MagnifyImage(const Image *image,ExceptionInfo *exception)
1600{
1601 Image
1602 *magnify_image;
1603
1604 assert(image != (Image *) NULL);
1605 assert(image->signature == MagickSignature);
1606 if (image->debug != MagickFalse)
1607 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
1608 assert(exception != (ExceptionInfo *) NULL);
1609 assert(exception->signature == MagickSignature);
1610 magnify_image=ResizeImage(image,2*image->columns,2*image->rows,CubicFilter,
1611 1.0,exception);
1612 return(magnify_image);
1613}
1614
1615/*
1616%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1617% %
1618% %
1619% %
1620% M i n i f y I m a g e %
1621% %
1622% %
1623% %
1624%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1625%
1626% MinifyImage() is a convenience method that scales an image proportionally
1627% to half its size.
1628%
1629% The format of the MinifyImage method is:
1630%
1631% Image *MinifyImage(const Image *image,ExceptionInfo *exception)
1632%
1633% A description of each parameter follows:
1634%
1635% o image: the image.
1636%
1637% o exception: return any errors or warnings in this structure.
1638%
1639*/
1640MagickExport Image *MinifyImage(const Image *image,ExceptionInfo *exception)
1641{
1642 Image
1643 *minify_image;
1644
1645 assert(image != (Image *) NULL);
1646 assert(image->signature == MagickSignature);
1647 if (image->debug != MagickFalse)
1648 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
1649 assert(exception != (ExceptionInfo *) NULL);
1650 assert(exception->signature == MagickSignature);
1651 minify_image=ResizeImage(image,image->columns/2,image->rows/2,CubicFilter,
1652 1.0,exception);
1653 return(minify_image);
1654}
1655
1656/*
1657%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1658% %
1659% %
1660% %
1661% R e s a m p l e I m a g e %
1662% %
1663% %
1664% %
1665%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1666%
1667% ResampleImage() resize image in terms of its pixel size, so that when
1668% displayed at the given resolution it will be the same size in terms of
1669% real world units as the original image at the original resolution.
1670%
1671% The format of the ResampleImage method is:
1672%
1673% Image *ResampleImage(Image *image,const double x_resolution,
1674% const double y_resolution,const FilterTypes filter,const double blur,
1675% ExceptionInfo *exception)
1676%
1677% A description of each parameter follows:
1678%
1679% o image: the image to be resized to fit the given resolution.
1680%
1681% o x_resolution: the new image x resolution.
1682%
1683% o y_resolution: the new image y resolution.
1684%
1685% o filter: Image filter to use.
1686%
1687% o blur: the blur factor where > 1 is blurry, < 1 is sharp.
1688%
1689*/
1690MagickExport Image *ResampleImage(const Image *image,const double x_resolution,
1691 const double y_resolution,const FilterTypes filter,const double blur,
1692 ExceptionInfo *exception)
1693{
1694#define ResampleImageTag "Resample/Image"
1695
1696 Image
1697 *resample_image;
1698
cristybb503372010-05-27 20:51:26 +00001699 size_t
cristy3ed852e2009-09-05 21:47:34 +00001700 height,
1701 width;
1702
1703 /*
1704 Initialize sampled image attributes.
1705 */
1706 assert(image != (const Image *) NULL);
1707 assert(image->signature == MagickSignature);
1708 if (image->debug != MagickFalse)
1709 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
1710 assert(exception != (ExceptionInfo *) NULL);
1711 assert(exception->signature == MagickSignature);
cristy9af9b5d2010-08-15 17:04:28 +00001712 width=(size_t) (x_resolution*image->columns/(image->x_resolution == 0.0 ?
1713 72.0 : image->x_resolution)+0.5);
1714 height=(size_t) (y_resolution*image->rows/(image->y_resolution == 0.0 ?
1715 72.0 : image->y_resolution)+0.5);
cristy3ed852e2009-09-05 21:47:34 +00001716 resample_image=ResizeImage(image,width,height,filter,blur,exception);
1717 if (resample_image != (Image *) NULL)
1718 {
1719 resample_image->x_resolution=x_resolution;
1720 resample_image->y_resolution=y_resolution;
1721 }
1722 return(resample_image);
1723}
1724#if defined(MAGICKCORE_LQR_DELEGATE)
1725
1726/*
1727%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1728% %
1729% %
1730% %
1731% L i q u i d R e s c a l e I m a g e %
1732% %
1733% %
1734% %
1735%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1736%
1737% LiquidRescaleImage() rescales image with seam carving.
1738%
1739% The format of the LiquidRescaleImage method is:
1740%
1741% Image *LiquidRescaleImage(const Image *image,
cristybb503372010-05-27 20:51:26 +00001742% const size_t columns,const size_t rows,
cristy3ed852e2009-09-05 21:47:34 +00001743% const double delta_x,const double rigidity,ExceptionInfo *exception)
1744%
1745% A description of each parameter follows:
1746%
1747% o image: the image.
1748%
1749% o columns: the number of columns in the rescaled image.
1750%
1751% o rows: the number of rows in the rescaled image.
1752%
1753% o delta_x: maximum seam transversal step (0 means straight seams).
1754%
1755% o rigidity: introduce a bias for non-straight seams (typically 0).
1756%
1757% o exception: return any errors or warnings in this structure.
1758%
1759*/
cristy9af9b5d2010-08-15 17:04:28 +00001760MagickExport Image *LiquidRescaleImage(const Image *image,const size_t columns,
1761 const size_t rows,const double delta_x,const double rigidity,
1762 ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +00001763{
1764#define LiquidRescaleImageTag "Rescale/Image"
1765
cristyc5c6f662010-09-22 14:23:02 +00001766 CacheView
1767 *rescale_view;
1768
cristy3ed852e2009-09-05 21:47:34 +00001769 const char
1770 *map;
1771
1772 guchar
1773 *packet;
1774
1775 Image
1776 *rescale_image;
1777
1778 int
1779 x,
1780 y;
1781
1782 LqrCarver
1783 *carver;
1784
1785 LqrRetVal
1786 lqr_status;
1787
1788 MagickBooleanType
1789 status;
1790
1791 MagickPixelPacket
1792 pixel;
1793
1794 unsigned char
1795 *pixels;
1796
1797 /*
1798 Liquid rescale image.
1799 */
1800 assert(image != (const Image *) NULL);
1801 assert(image->signature == MagickSignature);
1802 if (image->debug != MagickFalse)
1803 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
1804 assert(exception != (ExceptionInfo *) NULL);
1805 assert(exception->signature == MagickSignature);
1806 if ((columns == 0) || (rows == 0))
1807 return((Image *) NULL);
1808 if ((columns == image->columns) && (rows == image->rows))
1809 return(CloneImage(image,0,0,MagickTrue,exception));
1810 if ((columns <= 2) || (rows <= 2))
cristyd1bb3bc2010-09-07 00:43:58 +00001811 return(ResizeImage(image,columns,rows,image->filter,image->blur,exception));
cristy3ed852e2009-09-05 21:47:34 +00001812 if ((columns >= (2*image->columns)) || (rows >= (2*image->rows)))
1813 {
1814 Image
1815 *resize_image;
1816
cristybb503372010-05-27 20:51:26 +00001817 size_t
cristy3ed852e2009-09-05 21:47:34 +00001818 height,
1819 width;
1820
1821 /*
1822 Honor liquid resize size limitations.
1823 */
1824 for (width=image->columns; columns >= (2*width-1); width*=2);
1825 for (height=image->rows; rows >= (2*height-1); height*=2);
1826 resize_image=ResizeImage(image,width,height,image->filter,image->blur,
1827 exception);
1828 if (resize_image == (Image *) NULL)
1829 return((Image *) NULL);
1830 rescale_image=LiquidRescaleImage(resize_image,columns,rows,delta_x,
1831 rigidity,exception);
1832 resize_image=DestroyImage(resize_image);
1833 return(rescale_image);
1834 }
1835 map="RGB";
1836 if (image->matte == MagickFalse)
1837 map="RGBA";
1838 if (image->colorspace == CMYKColorspace)
1839 {
1840 map="CMYK";
1841 if (image->matte == MagickFalse)
1842 map="CMYKA";
1843 }
1844 pixels=(unsigned char *) AcquireQuantumMemory(image->columns,image->rows*
1845 strlen(map)*sizeof(*pixels));
1846 if (pixels == (unsigned char *) NULL)
1847 return((Image *) NULL);
1848 status=ExportImagePixels(image,0,0,image->columns,image->rows,map,CharPixel,
1849 pixels,exception);
1850 if (status == MagickFalse)
1851 {
1852 pixels=(unsigned char *) RelinquishMagickMemory(pixels);
1853 ThrowImageException(ResourceLimitError,"MemoryAllocationFailed");
1854 }
1855 carver=lqr_carver_new(pixels,image->columns,image->rows,strlen(map));
1856 if (carver == (LqrCarver *) NULL)
1857 {
1858 pixels=(unsigned char *) RelinquishMagickMemory(pixels);
1859 ThrowImageException(ResourceLimitError,"MemoryAllocationFailed");
1860 }
1861 lqr_status=lqr_carver_init(carver,(int) delta_x,rigidity);
1862 lqr_status=lqr_carver_resize(carver,columns,rows);
1863 rescale_image=CloneImage(image,lqr_carver_get_width(carver),
1864 lqr_carver_get_height(carver),MagickTrue,exception);
1865 if (rescale_image == (Image *) NULL)
1866 {
1867 pixels=(unsigned char *) RelinquishMagickMemory(pixels);
1868 return((Image *) NULL);
1869 }
1870 if (SetImageStorageClass(rescale_image,DirectClass) == MagickFalse)
1871 {
1872 InheritException(exception,&rescale_image->exception);
1873 rescale_image=DestroyImage(rescale_image);
1874 return((Image *) NULL);
1875 }
1876 GetMagickPixelPacket(rescale_image,&pixel);
1877 (void) lqr_carver_scan_reset(carver);
cristyc5c6f662010-09-22 14:23:02 +00001878 rescale_view=AcquireCacheView(rescale_image);
cristy3ed852e2009-09-05 21:47:34 +00001879 while (lqr_carver_scan(carver,&x,&y,&packet) != 0)
1880 {
1881 register IndexPacket
cristyc47d1f82009-11-26 01:44:43 +00001882 *restrict rescale_indexes;
cristy3ed852e2009-09-05 21:47:34 +00001883
1884 register PixelPacket
cristyc47d1f82009-11-26 01:44:43 +00001885 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +00001886
anthony22aad252010-09-23 06:59:07 +00001887 q=QueueCacheViewAuthenticPixels(rescale_view,x,y,1,1,exception);
cristy3ed852e2009-09-05 21:47:34 +00001888 if (q == (PixelPacket *) NULL)
1889 break;
cristyc5c6f662010-09-22 14:23:02 +00001890 rescale_indexes=GetCacheViewAuthenticIndexQueue(rescale_view);
cristy3ed852e2009-09-05 21:47:34 +00001891 pixel.red=QuantumRange*(packet[0]/255.0);
1892 pixel.green=QuantumRange*(packet[1]/255.0);
1893 pixel.blue=QuantumRange*(packet[2]/255.0);
1894 if (image->colorspace != CMYKColorspace)
1895 {
1896 if (image->matte == MagickFalse)
1897 pixel.opacity=QuantumRange*(packet[3]/255.0);
1898 }
1899 else
1900 {
1901 pixel.index=QuantumRange*(packet[3]/255.0);
1902 if (image->matte == MagickFalse)
1903 pixel.opacity=QuantumRange*(packet[4]/255.0);
1904 }
1905 SetPixelPacket(rescale_image,&pixel,q,rescale_indexes);
cristyc5c6f662010-09-22 14:23:02 +00001906 if (SyncCacheViewAuthenticPixels(rescale_view,exception) == MagickFalse)
cristy3ed852e2009-09-05 21:47:34 +00001907 break;
1908 }
cristyc5c6f662010-09-22 14:23:02 +00001909 rescale_view=DestroyCacheView(rescale_view);
cristy3ed852e2009-09-05 21:47:34 +00001910 /*
1911 Relinquish resources.
1912 */
1913 lqr_carver_destroy(carver);
1914 return(rescale_image);
1915}
1916#else
1917MagickExport Image *LiquidRescaleImage(const Image *image,
cristy9af9b5d2010-08-15 17:04:28 +00001918 const size_t magick_unused(columns),const size_t magick_unused(rows),
1919 const double magick_unused(delta_x),const double magick_unused(rigidity),
1920 ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +00001921{
1922 assert(image != (const Image *) NULL);
1923 assert(image->signature == MagickSignature);
1924 if (image->debug != MagickFalse)
1925 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
1926 assert(exception != (ExceptionInfo *) NULL);
1927 assert(exception->signature == MagickSignature);
1928 (void) ThrowMagickException(exception,GetMagickModule(),MissingDelegateError,
1929 "DelegateLibrarySupportNotBuiltIn","`%s' (LQR)",image->filename);
1930 return((Image *) NULL);
1931}
1932#endif
1933
1934/*
1935%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1936% %
1937% %
1938% %
1939% R e s i z e I m a g e %
1940% %
1941% %
1942% %
1943%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1944%
1945% ResizeImage() scales an image to the desired dimensions, using the given
cristy5d824382010-09-06 14:00:17 +00001946% filter (see AcquireFilterInfo()).
cristy3ed852e2009-09-05 21:47:34 +00001947%
1948% If an undefined filter is given the filter defaults to Mitchell for a
1949% colormapped image, a image with a matte channel, or if the image is
1950% enlarged. Otherwise the filter defaults to a Lanczos.
1951%
1952% ResizeImage() was inspired by Paul Heckbert's "zoom" program.
1953%
1954% The format of the ResizeImage method is:
1955%
cristybb503372010-05-27 20:51:26 +00001956% Image *ResizeImage(Image *image,const size_t columns,
1957% const size_t rows,const FilterTypes filter,const double blur,
cristy3ed852e2009-09-05 21:47:34 +00001958% ExceptionInfo *exception)
1959%
1960% A description of each parameter follows:
1961%
1962% o image: the image.
1963%
1964% o columns: the number of columns in the scaled image.
1965%
1966% o rows: the number of rows in the scaled image.
1967%
1968% o filter: Image filter to use.
1969%
cristy9af9b5d2010-08-15 17:04:28 +00001970% o blur: the blur factor where > 1 is blurry, < 1 is sharp. Typically set
1971% this to 1.0.
cristy3ed852e2009-09-05 21:47:34 +00001972%
1973% o exception: return any errors or warnings in this structure.
1974%
1975*/
1976
1977typedef struct _ContributionInfo
1978{
1979 MagickRealType
1980 weight;
1981
cristybb503372010-05-27 20:51:26 +00001982 ssize_t
cristy3ed852e2009-09-05 21:47:34 +00001983 pixel;
1984} ContributionInfo;
1985
1986static ContributionInfo **DestroyContributionThreadSet(
1987 ContributionInfo **contribution)
1988{
cristybb503372010-05-27 20:51:26 +00001989 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +00001990 i;
1991
1992 assert(contribution != (ContributionInfo **) NULL);
cristybb503372010-05-27 20:51:26 +00001993 for (i=0; i < (ssize_t) GetOpenMPMaximumThreads(); i++)
cristy3ed852e2009-09-05 21:47:34 +00001994 if (contribution[i] != (ContributionInfo *) NULL)
1995 contribution[i]=(ContributionInfo *) RelinquishMagickMemory(
1996 contribution[i]);
cristyb41ee102010-10-04 16:46:15 +00001997 contribution=(ContributionInfo **) RelinquishMagickMemory(contribution);
cristy3ed852e2009-09-05 21:47:34 +00001998 return(contribution);
1999}
2000
2001static ContributionInfo **AcquireContributionThreadSet(const size_t count)
2002{
cristybb503372010-05-27 20:51:26 +00002003 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +00002004 i;
2005
2006 ContributionInfo
2007 **contribution;
2008
cristybb503372010-05-27 20:51:26 +00002009 size_t
cristy3ed852e2009-09-05 21:47:34 +00002010 number_threads;
2011
2012 number_threads=GetOpenMPMaximumThreads();
cristyb41ee102010-10-04 16:46:15 +00002013 contribution=(ContributionInfo **) AcquireQuantumMemory(number_threads,
cristy3ed852e2009-09-05 21:47:34 +00002014 sizeof(*contribution));
2015 if (contribution == (ContributionInfo **) NULL)
2016 return((ContributionInfo **) NULL);
2017 (void) ResetMagickMemory(contribution,0,number_threads*sizeof(*contribution));
cristybb503372010-05-27 20:51:26 +00002018 for (i=0; i < (ssize_t) number_threads; i++)
cristy3ed852e2009-09-05 21:47:34 +00002019 {
2020 contribution[i]=(ContributionInfo *) AcquireQuantumMemory(count,
2021 sizeof(**contribution));
2022 if (contribution[i] == (ContributionInfo *) NULL)
2023 return(DestroyContributionThreadSet(contribution));
2024 }
2025 return(contribution);
2026}
2027
2028static inline double MagickMax(const double x,const double y)
2029{
2030 if (x > y)
2031 return(x);
2032 return(y);
2033}
2034
2035static inline double MagickMin(const double x,const double y)
2036{
2037 if (x < y)
2038 return(x);
2039 return(y);
2040}
2041
2042static MagickBooleanType HorizontalFilter(const ResizeFilter *resize_filter,
2043 const Image *image,Image *resize_image,const MagickRealType x_factor,
cristy9af9b5d2010-08-15 17:04:28 +00002044 const MagickSizeType span,MagickOffsetType *offset,ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +00002045{
2046#define ResizeImageTag "Resize/Image"
2047
cristyfa112112010-01-04 17:48:07 +00002048 CacheView
2049 *image_view,
2050 *resize_view;
2051
cristy3ed852e2009-09-05 21:47:34 +00002052 ClassType
2053 storage_class;
2054
2055 ContributionInfo
cristyfa112112010-01-04 17:48:07 +00002056 **restrict contributions;
cristy3ed852e2009-09-05 21:47:34 +00002057
cristy3ed852e2009-09-05 21:47:34 +00002058 MagickBooleanType
2059 status;
2060
2061 MagickPixelPacket
2062 zero;
2063
2064 MagickRealType
2065 scale,
2066 support;
2067
cristy9af9b5d2010-08-15 17:04:28 +00002068 ssize_t
2069 x;
2070
cristy3ed852e2009-09-05 21:47:34 +00002071 /*
2072 Apply filter to resize horizontally from image to resize image.
2073 */
cristy5d824382010-09-06 14:00:17 +00002074 scale=MagickMax(1.0/x_factor+MagickEpsilon,1.0);
cristy3ed852e2009-09-05 21:47:34 +00002075 support=scale*GetResizeFilterSupport(resize_filter);
2076 storage_class=support > 0.5 ? DirectClass : image->storage_class;
2077 if (SetImageStorageClass(resize_image,storage_class) == MagickFalse)
2078 {
2079 InheritException(exception,&resize_image->exception);
2080 return(MagickFalse);
2081 }
2082 if (support < 0.5)
2083 {
2084 /*
nicolas07bac812010-09-19 18:47:02 +00002085 Support too small even for nearest neighbour: Reduce to point
2086 sampling.
cristy3ed852e2009-09-05 21:47:34 +00002087 */
2088 support=(MagickRealType) 0.5;
2089 scale=1.0;
2090 }
2091 contributions=AcquireContributionThreadSet((size_t) (2.0*support+3.0));
2092 if (contributions == (ContributionInfo **) NULL)
2093 {
2094 (void) ThrowMagickException(exception,GetMagickModule(),
2095 ResourceLimitError,"MemoryAllocationFailed","`%s'",image->filename);
2096 return(MagickFalse);
2097 }
2098 status=MagickTrue;
2099 scale=1.0/scale;
2100 (void) ResetMagickMemory(&zero,0,sizeof(zero));
2101 image_view=AcquireCacheView(image);
2102 resize_view=AcquireCacheView(resize_image);
cristyb5d5f722009-11-04 03:03:49 +00002103#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristy3ed852e2009-09-05 21:47:34 +00002104 #pragma omp parallel for shared(status)
2105#endif
cristybb503372010-05-27 20:51:26 +00002106 for (x=0; x < (ssize_t) resize_image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +00002107 {
cristy3ed852e2009-09-05 21:47:34 +00002108 MagickRealType
2109 center,
2110 density;
2111
2112 register const IndexPacket
cristyc47d1f82009-11-26 01:44:43 +00002113 *restrict indexes;
cristy3ed852e2009-09-05 21:47:34 +00002114
2115 register const PixelPacket
cristyc47d1f82009-11-26 01:44:43 +00002116 *restrict p;
cristy3ed852e2009-09-05 21:47:34 +00002117
cristy03dbbd22010-09-19 23:04:47 +00002118 register ContributionInfo
2119 *restrict contribution;
2120
cristy3ed852e2009-09-05 21:47:34 +00002121 register IndexPacket
cristyc47d1f82009-11-26 01:44:43 +00002122 *restrict resize_indexes;
cristy3ed852e2009-09-05 21:47:34 +00002123
cristy3ed852e2009-09-05 21:47:34 +00002124 register PixelPacket
cristyc47d1f82009-11-26 01:44:43 +00002125 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +00002126
cristy03dbbd22010-09-19 23:04:47 +00002127 register ssize_t
2128 y;
2129
cristy9af9b5d2010-08-15 17:04:28 +00002130 ssize_t
2131 n,
2132 start,
2133 stop;
2134
cristy3ed852e2009-09-05 21:47:34 +00002135 if (status == MagickFalse)
2136 continue;
2137 center=(MagickRealType) (x+0.5)/x_factor;
cristybb503372010-05-27 20:51:26 +00002138 start=(ssize_t) MagickMax(center-support+0.5,0.0);
2139 stop=(ssize_t) MagickMin(center+support+0.5,(double) image->columns);
cristy3ed852e2009-09-05 21:47:34 +00002140 density=0.0;
2141 contribution=contributions[GetOpenMPThreadId()];
2142 for (n=0; n < (stop-start); n++)
2143 {
2144 contribution[n].pixel=start+n;
2145 contribution[n].weight=GetResizeFilterWeight(resize_filter,scale*
2146 ((MagickRealType) (start+n)-center+0.5));
2147 density+=contribution[n].weight;
2148 }
2149 if ((density != 0.0) && (density != 1.0))
2150 {
cristybb503372010-05-27 20:51:26 +00002151 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +00002152 i;
2153
2154 /*
2155 Normalize.
2156 */
2157 density=1.0/density;
2158 for (i=0; i < n; i++)
2159 contribution[i].weight*=density;
2160 }
cristy9af9b5d2010-08-15 17:04:28 +00002161 p=GetCacheViewVirtualPixels(image_view,contribution[0].pixel,0,(size_t)
2162 (contribution[n-1].pixel-contribution[0].pixel+1),image->rows,exception);
cristy3ed852e2009-09-05 21:47:34 +00002163 q=QueueCacheViewAuthenticPixels(resize_view,x,0,1,resize_image->rows,
2164 exception);
2165 if ((p == (const PixelPacket *) NULL) || (q == (PixelPacket *) NULL))
2166 {
2167 status=MagickFalse;
2168 continue;
2169 }
2170 indexes=GetCacheViewVirtualIndexQueue(image_view);
2171 resize_indexes=GetCacheViewAuthenticIndexQueue(resize_view);
cristybb503372010-05-27 20:51:26 +00002172 for (y=0; y < (ssize_t) resize_image->rows; y++)
cristy3ed852e2009-09-05 21:47:34 +00002173 {
cristy3ed852e2009-09-05 21:47:34 +00002174 MagickPixelPacket
2175 pixel;
2176
2177 MagickRealType
2178 alpha;
2179
cristybb503372010-05-27 20:51:26 +00002180 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +00002181 i;
2182
cristy9af9b5d2010-08-15 17:04:28 +00002183 ssize_t
2184 j;
2185
cristy3ed852e2009-09-05 21:47:34 +00002186 pixel=zero;
2187 if (image->matte == MagickFalse)
2188 {
2189 for (i=0; i < n; i++)
2190 {
2191 j=y*(contribution[n-1].pixel-contribution[0].pixel+1)+
2192 (contribution[i].pixel-contribution[0].pixel);
2193 alpha=contribution[i].weight;
2194 pixel.red+=alpha*(p+j)->red;
2195 pixel.green+=alpha*(p+j)->green;
2196 pixel.blue+=alpha*(p+j)->blue;
2197 pixel.opacity+=alpha*(p+j)->opacity;
2198 }
cristyce70c172010-01-07 17:15:30 +00002199 SetRedPixelComponent(q,ClampRedPixelComponent(&pixel));
2200 SetGreenPixelComponent(q,ClampGreenPixelComponent(&pixel));
2201 SetBluePixelComponent(q,ClampBluePixelComponent(&pixel));
2202 SetOpacityPixelComponent(q,ClampOpacityPixelComponent(&pixel));
cristy3ed852e2009-09-05 21:47:34 +00002203 if ((image->colorspace == CMYKColorspace) &&
2204 (resize_image->colorspace == CMYKColorspace))
2205 {
2206 for (i=0; i < n; i++)
2207 {
2208 j=y*(contribution[n-1].pixel-contribution[0].pixel+1)+
2209 (contribution[i].pixel-contribution[0].pixel);
2210 alpha=contribution[i].weight;
2211 pixel.index+=alpha*indexes[j];
2212 }
cristyce70c172010-01-07 17:15:30 +00002213 resize_indexes[y]=(IndexPacket) ClampToQuantum(pixel.index);
cristy3ed852e2009-09-05 21:47:34 +00002214 }
2215 }
2216 else
2217 {
2218 MagickRealType
2219 gamma;
2220
2221 gamma=0.0;
2222 for (i=0; i < n; i++)
2223 {
2224 j=y*(contribution[n-1].pixel-contribution[0].pixel+1)+
2225 (contribution[i].pixel-contribution[0].pixel);
cristy46f08202010-01-10 04:04:21 +00002226 alpha=contribution[i].weight*QuantumScale*
2227 GetAlphaPixelComponent(p+j);
cristy3ed852e2009-09-05 21:47:34 +00002228 pixel.red+=alpha*(p+j)->red;
2229 pixel.green+=alpha*(p+j)->green;
2230 pixel.blue+=alpha*(p+j)->blue;
2231 pixel.opacity+=contribution[i].weight*(p+j)->opacity;
2232 gamma+=alpha;
2233 }
2234 gamma=1.0/(fabs((double) gamma) <= MagickEpsilon ? 1.0 : gamma);
cristyce70c172010-01-07 17:15:30 +00002235 q->red=ClampToQuantum(gamma*GetRedPixelComponent(&pixel));
2236 q->green=ClampToQuantum(gamma*GetGreenPixelComponent(&pixel));
2237 q->blue=ClampToQuantum(gamma*GetBluePixelComponent(&pixel));
2238 SetOpacityPixelComponent(q,ClampOpacityPixelComponent(&pixel));
cristy3ed852e2009-09-05 21:47:34 +00002239 if ((image->colorspace == CMYKColorspace) &&
2240 (resize_image->colorspace == CMYKColorspace))
2241 {
2242 for (i=0; i < n; i++)
2243 {
2244 j=y*(contribution[n-1].pixel-contribution[0].pixel+1)+
2245 (contribution[i].pixel-contribution[0].pixel);
cristy46f08202010-01-10 04:04:21 +00002246 alpha=contribution[i].weight*QuantumScale*
2247 GetAlphaPixelComponent(p+j);
cristyc197d1c2009-10-13 19:56:53 +00002248 pixel.index+=alpha*indexes[j];
cristy3ed852e2009-09-05 21:47:34 +00002249 }
cristyce70c172010-01-07 17:15:30 +00002250 resize_indexes[y]=(IndexPacket) ClampToQuantum(gamma*
2251 GetIndexPixelComponent(&pixel));
cristy3ed852e2009-09-05 21:47:34 +00002252 }
2253 }
2254 if ((resize_image->storage_class == PseudoClass) &&
2255 (image->storage_class == PseudoClass))
2256 {
cristybb503372010-05-27 20:51:26 +00002257 i=(ssize_t) (MagickMin(MagickMax(center,(double) start),(double) stop-
cristy3ed852e2009-09-05 21:47:34 +00002258 1.0)+0.5);
2259 j=y*(contribution[n-1].pixel-contribution[0].pixel+1)+
2260 (contribution[i-start].pixel-contribution[0].pixel);
2261 resize_indexes[y]=indexes[j];
2262 }
2263 q++;
2264 }
2265 if (SyncCacheViewAuthenticPixels(resize_view,exception) == MagickFalse)
2266 status=MagickFalse;
2267 if (image->progress_monitor != (MagickProgressMonitor) NULL)
2268 {
2269 MagickBooleanType
2270 proceed;
2271
cristyb5d5f722009-11-04 03:03:49 +00002272#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristy3ed852e2009-09-05 21:47:34 +00002273 #pragma omp critical (MagickCore_HorizontalFilter)
2274#endif
cristy9af9b5d2010-08-15 17:04:28 +00002275 proceed=SetImageProgress(image,ResizeImageTag,(*offset)++,span);
cristy3ed852e2009-09-05 21:47:34 +00002276 if (proceed == MagickFalse)
2277 status=MagickFalse;
2278 }
2279 }
2280 resize_view=DestroyCacheView(resize_view);
2281 image_view=DestroyCacheView(image_view);
2282 contributions=DestroyContributionThreadSet(contributions);
2283 return(status);
2284}
2285
2286static MagickBooleanType VerticalFilter(const ResizeFilter *resize_filter,
2287 const Image *image,Image *resize_image,const MagickRealType y_factor,
cristy9af9b5d2010-08-15 17:04:28 +00002288 const MagickSizeType span,MagickOffsetType *offset,ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +00002289{
cristyfa112112010-01-04 17:48:07 +00002290 CacheView
2291 *image_view,
2292 *resize_view;
2293
cristy3ed852e2009-09-05 21:47:34 +00002294 ClassType
2295 storage_class;
2296
2297 ContributionInfo
cristyfa112112010-01-04 17:48:07 +00002298 **restrict contributions;
cristy3ed852e2009-09-05 21:47:34 +00002299
cristy3ed852e2009-09-05 21:47:34 +00002300 MagickBooleanType
2301 status;
2302
2303 MagickPixelPacket
2304 zero;
2305
2306 MagickRealType
2307 scale,
2308 support;
2309
cristy9af9b5d2010-08-15 17:04:28 +00002310 ssize_t
2311 y;
2312
cristy3ed852e2009-09-05 21:47:34 +00002313 /*
cristy9af9b5d2010-08-15 17:04:28 +00002314 Apply filter to resize vertically from image to resize image.
cristy3ed852e2009-09-05 21:47:34 +00002315 */
cristy5d824382010-09-06 14:00:17 +00002316 scale=MagickMax(1.0/y_factor+MagickEpsilon,1.0);
cristy3ed852e2009-09-05 21:47:34 +00002317 support=scale*GetResizeFilterSupport(resize_filter);
2318 storage_class=support > 0.5 ? DirectClass : image->storage_class;
2319 if (SetImageStorageClass(resize_image,storage_class) == MagickFalse)
2320 {
2321 InheritException(exception,&resize_image->exception);
2322 return(MagickFalse);
2323 }
2324 if (support < 0.5)
2325 {
2326 /*
nicolas07bac812010-09-19 18:47:02 +00002327 Support too small even for nearest neighbour: Reduce to point
2328 sampling.
cristy3ed852e2009-09-05 21:47:34 +00002329 */
2330 support=(MagickRealType) 0.5;
2331 scale=1.0;
2332 }
2333 contributions=AcquireContributionThreadSet((size_t) (2.0*support+3.0));
2334 if (contributions == (ContributionInfo **) NULL)
2335 {
2336 (void) ThrowMagickException(exception,GetMagickModule(),
2337 ResourceLimitError,"MemoryAllocationFailed","`%s'",image->filename);
2338 return(MagickFalse);
2339 }
2340 status=MagickTrue;
2341 scale=1.0/scale;
2342 (void) ResetMagickMemory(&zero,0,sizeof(zero));
2343 image_view=AcquireCacheView(image);
2344 resize_view=AcquireCacheView(resize_image);
cristyb5d5f722009-11-04 03:03:49 +00002345#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristy3ed852e2009-09-05 21:47:34 +00002346 #pragma omp parallel for shared(status)
2347#endif
cristybb503372010-05-27 20:51:26 +00002348 for (y=0; y < (ssize_t) resize_image->rows; y++)
cristy3ed852e2009-09-05 21:47:34 +00002349 {
cristy3ed852e2009-09-05 21:47:34 +00002350 MagickRealType
2351 center,
2352 density;
2353
2354 register const IndexPacket
cristyc47d1f82009-11-26 01:44:43 +00002355 *restrict indexes;
cristy3ed852e2009-09-05 21:47:34 +00002356
2357 register const PixelPacket
cristyc47d1f82009-11-26 01:44:43 +00002358 *restrict p;
cristy3ed852e2009-09-05 21:47:34 +00002359
cristy03dbbd22010-09-19 23:04:47 +00002360 register ContributionInfo
2361 *restrict contribution;
2362
cristy3ed852e2009-09-05 21:47:34 +00002363 register IndexPacket
cristyc47d1f82009-11-26 01:44:43 +00002364 *restrict resize_indexes;
cristy3ed852e2009-09-05 21:47:34 +00002365
cristy9af9b5d2010-08-15 17:04:28 +00002366 register PixelPacket
2367 *restrict q;
2368
cristybb503372010-05-27 20:51:26 +00002369 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +00002370 x;
2371
cristy9af9b5d2010-08-15 17:04:28 +00002372 ssize_t
2373 n,
2374 start,
2375 stop;
cristy3ed852e2009-09-05 21:47:34 +00002376
2377 if (status == MagickFalse)
2378 continue;
cristy679e6962010-03-18 00:42:45 +00002379 center=(MagickRealType) (y+0.5)/y_factor;
cristybb503372010-05-27 20:51:26 +00002380 start=(ssize_t) MagickMax(center-support+0.5,0.0);
2381 stop=(ssize_t) MagickMin(center+support+0.5,(double) image->rows);
cristy3ed852e2009-09-05 21:47:34 +00002382 density=0.0;
2383 contribution=contributions[GetOpenMPThreadId()];
2384 for (n=0; n < (stop-start); n++)
2385 {
2386 contribution[n].pixel=start+n;
2387 contribution[n].weight=GetResizeFilterWeight(resize_filter,scale*
2388 ((MagickRealType) (start+n)-center+0.5));
2389 density+=contribution[n].weight;
2390 }
2391 if ((density != 0.0) && (density != 1.0))
2392 {
cristybb503372010-05-27 20:51:26 +00002393 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +00002394 i;
2395
2396 /*
2397 Normalize.
2398 */
2399 density=1.0/density;
2400 for (i=0; i < n; i++)
2401 contribution[i].weight*=density;
2402 }
2403 p=GetCacheViewVirtualPixels(image_view,0,contribution[0].pixel,
cristy9af9b5d2010-08-15 17:04:28 +00002404 image->columns,(size_t) (contribution[n-1].pixel-contribution[0].pixel+1),
2405 exception);
cristy3ed852e2009-09-05 21:47:34 +00002406 q=QueueCacheViewAuthenticPixels(resize_view,0,y,resize_image->columns,1,
2407 exception);
2408 if ((p == (const PixelPacket *) NULL) || (q == (PixelPacket *) NULL))
2409 {
2410 status=MagickFalse;
2411 continue;
2412 }
2413 indexes=GetCacheViewVirtualIndexQueue(image_view);
2414 resize_indexes=GetCacheViewAuthenticIndexQueue(resize_view);
cristybb503372010-05-27 20:51:26 +00002415 for (x=0; x < (ssize_t) resize_image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +00002416 {
cristy3ed852e2009-09-05 21:47:34 +00002417 MagickPixelPacket
2418 pixel;
2419
2420 MagickRealType
2421 alpha;
2422
cristybb503372010-05-27 20:51:26 +00002423 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +00002424 i;
2425
cristy9af9b5d2010-08-15 17:04:28 +00002426 ssize_t
2427 j;
2428
cristy3ed852e2009-09-05 21:47:34 +00002429 pixel=zero;
2430 if (image->matte == MagickFalse)
2431 {
2432 for (i=0; i < n; i++)
2433 {
cristybb503372010-05-27 20:51:26 +00002434 j=(ssize_t) ((contribution[i].pixel-contribution[0].pixel)*
cristy3ed852e2009-09-05 21:47:34 +00002435 image->columns+x);
2436 alpha=contribution[i].weight;
2437 pixel.red+=alpha*(p+j)->red;
2438 pixel.green+=alpha*(p+j)->green;
2439 pixel.blue+=alpha*(p+j)->blue;
2440 pixel.opacity+=alpha*(p+j)->opacity;
2441 }
cristyce70c172010-01-07 17:15:30 +00002442 SetRedPixelComponent(q,ClampRedPixelComponent(&pixel));
2443 SetGreenPixelComponent(q,ClampGreenPixelComponent(&pixel));
2444 SetBluePixelComponent(q,ClampBluePixelComponent(&pixel));
2445 SetOpacityPixelComponent(q,ClampOpacityPixelComponent(&pixel));
cristy3ed852e2009-09-05 21:47:34 +00002446 if ((image->colorspace == CMYKColorspace) &&
2447 (resize_image->colorspace == CMYKColorspace))
2448 {
2449 for (i=0; i < n; i++)
2450 {
cristybb503372010-05-27 20:51:26 +00002451 j=(ssize_t) ((contribution[i].pixel-contribution[0].pixel)*
cristy3ed852e2009-09-05 21:47:34 +00002452 image->columns+x);
2453 alpha=contribution[i].weight;
2454 pixel.index+=alpha*indexes[j];
2455 }
cristyce70c172010-01-07 17:15:30 +00002456 resize_indexes[x]=(IndexPacket) ClampToQuantum(pixel.index);
cristy3ed852e2009-09-05 21:47:34 +00002457 }
2458 }
2459 else
2460 {
2461 MagickRealType
2462 gamma;
2463
2464 gamma=0.0;
2465 for (i=0; i < n; i++)
2466 {
cristybb503372010-05-27 20:51:26 +00002467 j=(ssize_t) ((contribution[i].pixel-contribution[0].pixel)*
cristy3ed852e2009-09-05 21:47:34 +00002468 image->columns+x);
cristy46f08202010-01-10 04:04:21 +00002469 alpha=contribution[i].weight*QuantumScale*
2470 GetAlphaPixelComponent(p+j);
cristy3ed852e2009-09-05 21:47:34 +00002471 pixel.red+=alpha*(p+j)->red;
2472 pixel.green+=alpha*(p+j)->green;
2473 pixel.blue+=alpha*(p+j)->blue;
2474 pixel.opacity+=contribution[i].weight*(p+j)->opacity;
2475 gamma+=alpha;
2476 }
2477 gamma=1.0/(fabs((double) gamma) <= MagickEpsilon ? 1.0 : gamma);
cristyce70c172010-01-07 17:15:30 +00002478 q->red=ClampToQuantum(gamma*GetRedPixelComponent(&pixel));
2479 q->green=ClampToQuantum(gamma*GetGreenPixelComponent(&pixel));
2480 q->blue=ClampToQuantum(gamma*GetBluePixelComponent(&pixel));
2481 SetOpacityPixelComponent(q,ClampOpacityPixelComponent(&pixel));
cristy3ed852e2009-09-05 21:47:34 +00002482 if ((image->colorspace == CMYKColorspace) &&
2483 (resize_image->colorspace == CMYKColorspace))
2484 {
2485 for (i=0; i < n; i++)
2486 {
cristybb503372010-05-27 20:51:26 +00002487 j=(ssize_t) ((contribution[i].pixel-contribution[0].pixel)*
cristy3ed852e2009-09-05 21:47:34 +00002488 image->columns+x);
cristy46f08202010-01-10 04:04:21 +00002489 alpha=contribution[i].weight*QuantumScale*
2490 GetAlphaPixelComponent(p+j);
cristy3ed852e2009-09-05 21:47:34 +00002491 pixel.index+=alpha*indexes[j];
2492 }
cristyce70c172010-01-07 17:15:30 +00002493 resize_indexes[x]=(IndexPacket) ClampToQuantum(gamma*
2494 GetIndexPixelComponent(&pixel));
cristy3ed852e2009-09-05 21:47:34 +00002495 }
2496 }
2497 if ((resize_image->storage_class == PseudoClass) &&
2498 (image->storage_class == PseudoClass))
2499 {
cristybb503372010-05-27 20:51:26 +00002500 i=(ssize_t) (MagickMin(MagickMax(center,(double) start),(double) stop-
cristy3ed852e2009-09-05 21:47:34 +00002501 1.0)+0.5);
cristybb503372010-05-27 20:51:26 +00002502 j=(ssize_t) ((contribution[i-start].pixel-contribution[0].pixel)*
cristy3ed852e2009-09-05 21:47:34 +00002503 image->columns+x);
2504 resize_indexes[x]=indexes[j];
2505 }
2506 q++;
2507 }
2508 if (SyncCacheViewAuthenticPixels(resize_view,exception) == MagickFalse)
2509 status=MagickFalse;
2510 if (image->progress_monitor != (MagickProgressMonitor) NULL)
2511 {
2512 MagickBooleanType
2513 proceed;
2514
cristyb5d5f722009-11-04 03:03:49 +00002515#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristy3ed852e2009-09-05 21:47:34 +00002516 #pragma omp critical (MagickCore_VerticalFilter)
2517#endif
cristy9af9b5d2010-08-15 17:04:28 +00002518 proceed=SetImageProgress(image,ResizeImageTag,(*offset)++,span);
cristy3ed852e2009-09-05 21:47:34 +00002519 if (proceed == MagickFalse)
2520 status=MagickFalse;
2521 }
2522 }
2523 resize_view=DestroyCacheView(resize_view);
2524 image_view=DestroyCacheView(image_view);
2525 contributions=DestroyContributionThreadSet(contributions);
2526 return(status);
2527}
2528
cristybb503372010-05-27 20:51:26 +00002529MagickExport Image *ResizeImage(const Image *image,const size_t columns,
2530 const size_t rows,const FilterTypes filter,const double blur,
cristy3ed852e2009-09-05 21:47:34 +00002531 ExceptionInfo *exception)
2532{
2533#define WorkLoadFactor 0.265
2534
2535 FilterTypes
2536 filter_type;
2537
2538 Image
2539 *filter_image,
2540 *resize_image;
2541
cristy9af9b5d2010-08-15 17:04:28 +00002542 MagickOffsetType
2543 offset;
2544
cristy3ed852e2009-09-05 21:47:34 +00002545 MagickRealType
2546 x_factor,
2547 y_factor;
2548
2549 MagickSizeType
2550 span;
2551
2552 MagickStatusType
2553 status;
2554
2555 ResizeFilter
2556 *resize_filter;
2557
cristy3ed852e2009-09-05 21:47:34 +00002558 /*
2559 Acquire resize image.
2560 */
2561 assert(image != (Image *) NULL);
2562 assert(image->signature == MagickSignature);
2563 if (image->debug != MagickFalse)
2564 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
2565 assert(exception != (ExceptionInfo *) NULL);
2566 assert(exception->signature == MagickSignature);
2567 if ((columns == 0) || (rows == 0))
2568 ThrowImageException(ImageError,"NegativeOrZeroImageSize");
2569 if ((columns == image->columns) && (rows == image->rows) &&
2570 (filter == UndefinedFilter) && (blur == 1.0))
2571 return(CloneImage(image,0,0,MagickTrue,exception));
2572 resize_image=CloneImage(image,columns,rows,MagickTrue,exception);
2573 if (resize_image == (Image *) NULL)
2574 return(resize_image);
2575 /*
2576 Acquire resize filter.
2577 */
2578 x_factor=(MagickRealType) columns/(MagickRealType) image->columns;
2579 y_factor=(MagickRealType) rows/(MagickRealType) image->rows;
2580 if ((x_factor*y_factor) > WorkLoadFactor)
2581 filter_image=CloneImage(image,columns,image->rows,MagickTrue,exception);
2582 else
2583 filter_image=CloneImage(image,image->columns,rows,MagickTrue,exception);
2584 if (filter_image == (Image *) NULL)
2585 return(DestroyImage(resize_image));
2586 filter_type=LanczosFilter;
2587 if (filter != UndefinedFilter)
2588 filter_type=filter;
2589 else
2590 if ((x_factor == 1.0) && (y_factor == 1.0))
2591 filter_type=PointFilter;
2592 else
2593 if ((image->storage_class == PseudoClass) ||
2594 (image->matte != MagickFalse) || ((x_factor*y_factor) > 1.0))
2595 filter_type=MitchellFilter;
2596 resize_filter=AcquireResizeFilter(image,filter_type,blur,MagickFalse,
2597 exception);
2598 /*
2599 Resize image.
2600 */
cristy9af9b5d2010-08-15 17:04:28 +00002601 offset=0;
cristy3ed852e2009-09-05 21:47:34 +00002602 if ((x_factor*y_factor) > WorkLoadFactor)
2603 {
2604 span=(MagickSizeType) (filter_image->columns+rows);
2605 status=HorizontalFilter(resize_filter,image,filter_image,x_factor,span,
cristy9af9b5d2010-08-15 17:04:28 +00002606 &offset,exception);
cristy3ed852e2009-09-05 21:47:34 +00002607 status&=VerticalFilter(resize_filter,filter_image,resize_image,y_factor,
cristy9af9b5d2010-08-15 17:04:28 +00002608 span,&offset,exception);
cristy3ed852e2009-09-05 21:47:34 +00002609 }
2610 else
2611 {
2612 span=(MagickSizeType) (filter_image->rows+columns);
2613 status=VerticalFilter(resize_filter,image,filter_image,y_factor,span,
cristy9af9b5d2010-08-15 17:04:28 +00002614 &offset,exception);
cristy3ed852e2009-09-05 21:47:34 +00002615 status&=HorizontalFilter(resize_filter,filter_image,resize_image,x_factor,
cristy9af9b5d2010-08-15 17:04:28 +00002616 span,&offset,exception);
cristy3ed852e2009-09-05 21:47:34 +00002617 }
2618 /*
2619 Free resources.
2620 */
2621 filter_image=DestroyImage(filter_image);
2622 resize_filter=DestroyResizeFilter(resize_filter);
2623 if ((status == MagickFalse) || (resize_image == (Image *) NULL))
2624 return((Image *) NULL);
2625 resize_image->type=image->type;
2626 return(resize_image);
2627}
2628
2629/*
2630%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2631% %
2632% %
2633% %
2634% S a m p l e I m a g e %
2635% %
2636% %
2637% %
2638%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2639%
2640% SampleImage() scales an image to the desired dimensions with pixel
2641% sampling. Unlike other scaling methods, this method does not introduce
2642% any additional color into the scaled image.
2643%
2644% The format of the SampleImage method is:
2645%
cristybb503372010-05-27 20:51:26 +00002646% Image *SampleImage(const Image *image,const size_t columns,
2647% const size_t rows,ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +00002648%
2649% A description of each parameter follows:
2650%
2651% o image: the image.
2652%
2653% o columns: the number of columns in the sampled image.
2654%
2655% o rows: the number of rows in the sampled image.
2656%
2657% o exception: return any errors or warnings in this structure.
2658%
2659*/
cristybb503372010-05-27 20:51:26 +00002660MagickExport Image *SampleImage(const Image *image,const size_t columns,
2661 const size_t rows,ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +00002662{
2663#define SampleImageTag "Sample/Image"
2664
cristyc4c8d132010-01-07 01:58:38 +00002665 CacheView
2666 *image_view,
2667 *sample_view;
2668
cristy3ed852e2009-09-05 21:47:34 +00002669 Image
2670 *sample_image;
2671
cristy3ed852e2009-09-05 21:47:34 +00002672 MagickBooleanType
2673 status;
2674
cristy5f959472010-05-27 22:19:46 +00002675 MagickOffsetType
2676 progress;
2677
cristybb503372010-05-27 20:51:26 +00002678 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +00002679 x;
2680
cristy5f959472010-05-27 22:19:46 +00002681 ssize_t
2682 *x_offset,
2683 y;
2684
cristy3ed852e2009-09-05 21:47:34 +00002685 /*
2686 Initialize sampled image attributes.
2687 */
2688 assert(image != (const Image *) NULL);
2689 assert(image->signature == MagickSignature);
2690 if (image->debug != MagickFalse)
2691 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
2692 assert(exception != (ExceptionInfo *) NULL);
2693 assert(exception->signature == MagickSignature);
2694 if ((columns == 0) || (rows == 0))
2695 ThrowImageException(ImageError,"NegativeOrZeroImageSize");
2696 if ((columns == image->columns) && (rows == image->rows))
2697 return(CloneImage(image,0,0,MagickTrue,exception));
2698 sample_image=CloneImage(image,columns,rows,MagickTrue,exception);
2699 if (sample_image == (Image *) NULL)
2700 return((Image *) NULL);
2701 /*
2702 Allocate scan line buffer and column offset buffers.
2703 */
cristybb503372010-05-27 20:51:26 +00002704 x_offset=(ssize_t *) AcquireQuantumMemory((size_t) sample_image->columns,
cristy3ed852e2009-09-05 21:47:34 +00002705 sizeof(*x_offset));
cristybb503372010-05-27 20:51:26 +00002706 if (x_offset == (ssize_t *) NULL)
cristy3ed852e2009-09-05 21:47:34 +00002707 {
2708 sample_image=DestroyImage(sample_image);
2709 ThrowImageException(ResourceLimitError,"MemoryAllocationFailed");
2710 }
cristybb503372010-05-27 20:51:26 +00002711 for (x=0; x < (ssize_t) sample_image->columns; x++)
2712 x_offset[x]=(ssize_t) (((MagickRealType) x+0.5)*image->columns/
cristy3ed852e2009-09-05 21:47:34 +00002713 sample_image->columns);
2714 /*
2715 Sample each row.
2716 */
2717 status=MagickTrue;
2718 progress=0;
2719 image_view=AcquireCacheView(image);
2720 sample_view=AcquireCacheView(sample_image);
cristyb5d5f722009-11-04 03:03:49 +00002721#if defined(MAGICKCORE_OPENMP_SUPPORT)
2722 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +00002723#endif
cristybb503372010-05-27 20:51:26 +00002724 for (y=0; y < (ssize_t) sample_image->rows; y++)
cristy3ed852e2009-09-05 21:47:34 +00002725 {
cristy3ed852e2009-09-05 21:47:34 +00002726 register const IndexPacket
cristyc47d1f82009-11-26 01:44:43 +00002727 *restrict indexes;
cristy3ed852e2009-09-05 21:47:34 +00002728
2729 register const PixelPacket
cristyc47d1f82009-11-26 01:44:43 +00002730 *restrict p;
cristy3ed852e2009-09-05 21:47:34 +00002731
2732 register IndexPacket
cristyc47d1f82009-11-26 01:44:43 +00002733 *restrict sample_indexes;
cristy3ed852e2009-09-05 21:47:34 +00002734
cristy3ed852e2009-09-05 21:47:34 +00002735 register PixelPacket
cristyc47d1f82009-11-26 01:44:43 +00002736 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +00002737
cristy03dbbd22010-09-19 23:04:47 +00002738 register ssize_t
2739 x;
2740
cristy9af9b5d2010-08-15 17:04:28 +00002741 ssize_t
2742 y_offset;
2743
cristy3ed852e2009-09-05 21:47:34 +00002744 if (status == MagickFalse)
2745 continue;
cristy9af9b5d2010-08-15 17:04:28 +00002746 y_offset=(ssize_t) (((MagickRealType) y+0.5)*image->rows/
2747 sample_image->rows);
cristy3ed852e2009-09-05 21:47:34 +00002748 p=GetCacheViewVirtualPixels(image_view,0,y_offset,image->columns,1,
2749 exception);
2750 q=QueueCacheViewAuthenticPixels(sample_view,0,y,sample_image->columns,1,
2751 exception);
2752 if ((p == (const PixelPacket *) NULL) || (q == (PixelPacket *) NULL))
2753 {
2754 status=MagickFalse;
2755 continue;
2756 }
2757 indexes=GetCacheViewAuthenticIndexQueue(image_view);
2758 sample_indexes=GetCacheViewAuthenticIndexQueue(sample_view);
2759 /*
2760 Sample each column.
2761 */
cristybb503372010-05-27 20:51:26 +00002762 for (x=0; x < (ssize_t) sample_image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +00002763 *q++=p[x_offset[x]];
2764 if ((image->storage_class == PseudoClass) ||
2765 (image->colorspace == CMYKColorspace))
cristybb503372010-05-27 20:51:26 +00002766 for (x=0; x < (ssize_t) sample_image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +00002767 sample_indexes[x]=indexes[x_offset[x]];
2768 if (SyncCacheViewAuthenticPixels(sample_view,exception) == MagickFalse)
2769 status=MagickFalse;
2770 if (image->progress_monitor != (MagickProgressMonitor) NULL)
2771 {
2772 MagickBooleanType
2773 proceed;
2774
cristyb5d5f722009-11-04 03:03:49 +00002775#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristy3ed852e2009-09-05 21:47:34 +00002776 #pragma omp critical (MagickCore_SampleImage)
2777#endif
2778 proceed=SetImageProgress(image,SampleImageTag,progress++,image->rows);
2779 if (proceed == MagickFalse)
2780 status=MagickFalse;
2781 }
2782 }
2783 image_view=DestroyCacheView(image_view);
2784 sample_view=DestroyCacheView(sample_view);
cristybb503372010-05-27 20:51:26 +00002785 x_offset=(ssize_t *) RelinquishMagickMemory(x_offset);
cristy3ed852e2009-09-05 21:47:34 +00002786 sample_image->type=image->type;
2787 return(sample_image);
2788}
2789
2790/*
2791%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2792% %
2793% %
2794% %
2795% S c a l e I m a g e %
2796% %
2797% %
2798% %
2799%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2800%
2801% ScaleImage() changes the size of an image to the given dimensions.
2802%
2803% The format of the ScaleImage method is:
2804%
cristybb503372010-05-27 20:51:26 +00002805% Image *ScaleImage(const Image *image,const size_t columns,
2806% const size_t rows,ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +00002807%
2808% A description of each parameter follows:
2809%
2810% o image: the image.
2811%
2812% o columns: the number of columns in the scaled image.
2813%
2814% o rows: the number of rows in the scaled image.
2815%
2816% o exception: return any errors or warnings in this structure.
2817%
2818*/
cristybb503372010-05-27 20:51:26 +00002819MagickExport Image *ScaleImage(const Image *image,const size_t columns,
2820 const size_t rows,ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +00002821{
2822#define ScaleImageTag "Scale/Image"
2823
cristyed6cb232010-01-20 03:07:53 +00002824 CacheView
2825 *image_view,
2826 *scale_view;
2827
cristy3ed852e2009-09-05 21:47:34 +00002828 Image
2829 *scale_image;
2830
cristy3ed852e2009-09-05 21:47:34 +00002831 MagickBooleanType
2832 next_column,
2833 next_row,
2834 proceed;
2835
2836 MagickPixelPacket
2837 pixel,
2838 *scale_scanline,
2839 *scanline,
2840 *x_vector,
2841 *y_vector,
2842 zero;
2843
cristy3ed852e2009-09-05 21:47:34 +00002844 PointInfo
2845 scale,
2846 span;
2847
cristybb503372010-05-27 20:51:26 +00002848 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +00002849 i;
2850
cristy9af9b5d2010-08-15 17:04:28 +00002851 ssize_t
2852 number_rows,
2853 y;
2854
cristy3ed852e2009-09-05 21:47:34 +00002855 /*
2856 Initialize scaled image attributes.
2857 */
2858 assert(image != (const Image *) NULL);
2859 assert(image->signature == MagickSignature);
2860 if (image->debug != MagickFalse)
2861 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
2862 assert(exception != (ExceptionInfo *) NULL);
2863 assert(exception->signature == MagickSignature);
2864 if ((columns == 0) || (rows == 0))
2865 return((Image *) NULL);
2866 if ((columns == image->columns) && (rows == image->rows))
2867 return(CloneImage(image,0,0,MagickTrue,exception));
2868 scale_image=CloneImage(image,columns,rows,MagickTrue,exception);
2869 if (scale_image == (Image *) NULL)
2870 return((Image *) NULL);
2871 if (SetImageStorageClass(scale_image,DirectClass) == MagickFalse)
2872 {
2873 InheritException(exception,&scale_image->exception);
2874 scale_image=DestroyImage(scale_image);
2875 return((Image *) NULL);
2876 }
2877 /*
2878 Allocate memory.
2879 */
2880 x_vector=(MagickPixelPacket *) AcquireQuantumMemory((size_t) image->columns,
2881 sizeof(*x_vector));
2882 scanline=x_vector;
2883 if (image->rows != scale_image->rows)
2884 scanline=(MagickPixelPacket *) AcquireQuantumMemory((size_t) image->columns,
2885 sizeof(*scanline));
2886 scale_scanline=(MagickPixelPacket *) AcquireQuantumMemory((size_t)
2887 scale_image->columns,sizeof(*scale_scanline));
2888 y_vector=(MagickPixelPacket *) AcquireQuantumMemory((size_t) image->columns,
2889 sizeof(*y_vector));
2890 if ((scanline == (MagickPixelPacket *) NULL) ||
2891 (scale_scanline == (MagickPixelPacket *) NULL) ||
2892 (x_vector == (MagickPixelPacket *) NULL) ||
2893 (y_vector == (MagickPixelPacket *) NULL))
2894 {
2895 scale_image=DestroyImage(scale_image);
2896 ThrowImageException(ResourceLimitError,"MemoryAllocationFailed");
2897 }
2898 /*
2899 Scale image.
2900 */
2901 number_rows=0;
2902 next_row=MagickTrue;
2903 span.y=1.0;
2904 scale.y=(double) scale_image->rows/(double) image->rows;
2905 (void) ResetMagickMemory(y_vector,0,(size_t) image->columns*
2906 sizeof(*y_vector));
2907 GetMagickPixelPacket(image,&pixel);
2908 (void) ResetMagickMemory(&zero,0,sizeof(zero));
2909 i=0;
cristyed6cb232010-01-20 03:07:53 +00002910 image_view=AcquireCacheView(image);
2911 scale_view=AcquireCacheView(scale_image);
cristybb503372010-05-27 20:51:26 +00002912 for (y=0; y < (ssize_t) scale_image->rows; y++)
cristy3ed852e2009-09-05 21:47:34 +00002913 {
2914 register const IndexPacket
cristyc47d1f82009-11-26 01:44:43 +00002915 *restrict indexes;
cristy3ed852e2009-09-05 21:47:34 +00002916
2917 register const PixelPacket
cristyc47d1f82009-11-26 01:44:43 +00002918 *restrict p;
cristy3ed852e2009-09-05 21:47:34 +00002919
2920 register IndexPacket
cristyc47d1f82009-11-26 01:44:43 +00002921 *restrict scale_indexes;
cristy3ed852e2009-09-05 21:47:34 +00002922
cristy3ed852e2009-09-05 21:47:34 +00002923 register MagickPixelPacket
cristyc47d1f82009-11-26 01:44:43 +00002924 *restrict s,
2925 *restrict t;
cristy3ed852e2009-09-05 21:47:34 +00002926
2927 register PixelPacket
cristyc47d1f82009-11-26 01:44:43 +00002928 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +00002929
cristy9af9b5d2010-08-15 17:04:28 +00002930 register ssize_t
2931 x;
2932
cristyed6cb232010-01-20 03:07:53 +00002933 q=QueueCacheViewAuthenticPixels(scale_view,0,y,scale_image->columns,1,
2934 exception);
cristy3ed852e2009-09-05 21:47:34 +00002935 if (q == (PixelPacket *) NULL)
2936 break;
2937 scale_indexes=GetAuthenticIndexQueue(scale_image);
2938 if (scale_image->rows == image->rows)
2939 {
2940 /*
2941 Read a new scanline.
2942 */
cristyed6cb232010-01-20 03:07:53 +00002943 p=GetCacheViewVirtualPixels(image_view,0,i++,image->columns,1,
2944 exception);
cristy3ed852e2009-09-05 21:47:34 +00002945 if (p == (const PixelPacket *) NULL)
2946 break;
cristyed6cb232010-01-20 03:07:53 +00002947 indexes=GetCacheViewVirtualIndexQueue(image_view);
cristybb503372010-05-27 20:51:26 +00002948 for (x=0; x < (ssize_t) image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +00002949 {
cristyce70c172010-01-07 17:15:30 +00002950 x_vector[x].red=(MagickRealType) GetRedPixelComponent(p);
2951 x_vector[x].green=(MagickRealType) GetGreenPixelComponent(p);
2952 x_vector[x].blue=(MagickRealType) GetBluePixelComponent(p);
cristy3ed852e2009-09-05 21:47:34 +00002953 if (image->matte != MagickFalse)
cristyce70c172010-01-07 17:15:30 +00002954 x_vector[x].opacity=(MagickRealType) GetOpacityPixelComponent(p);
cristy3ed852e2009-09-05 21:47:34 +00002955 if (indexes != (IndexPacket *) NULL)
2956 x_vector[x].index=(MagickRealType) indexes[x];
2957 p++;
2958 }
2959 }
2960 else
2961 {
2962 /*
2963 Scale Y direction.
2964 */
2965 while (scale.y < span.y)
2966 {
cristy9af9b5d2010-08-15 17:04:28 +00002967 if ((next_row != MagickFalse) &&
2968 (number_rows < (ssize_t) image->rows))
cristy3ed852e2009-09-05 21:47:34 +00002969 {
2970 /*
2971 Read a new scanline.
2972 */
cristyed6cb232010-01-20 03:07:53 +00002973 p=GetCacheViewVirtualPixels(image_view,0,i++,image->columns,1,
2974 exception);
cristy3ed852e2009-09-05 21:47:34 +00002975 if (p == (const PixelPacket *) NULL)
2976 break;
cristyed6cb232010-01-20 03:07:53 +00002977 indexes=GetCacheViewVirtualIndexQueue(image_view);
cristybb503372010-05-27 20:51:26 +00002978 for (x=0; x < (ssize_t) image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +00002979 {
cristyce70c172010-01-07 17:15:30 +00002980 x_vector[x].red=(MagickRealType) GetRedPixelComponent(p);
2981 x_vector[x].green=(MagickRealType) GetGreenPixelComponent(p);
2982 x_vector[x].blue=(MagickRealType) GetBluePixelComponent(p);
cristy3ed852e2009-09-05 21:47:34 +00002983 if (image->matte != MagickFalse)
cristyed6cb232010-01-20 03:07:53 +00002984 x_vector[x].opacity=(MagickRealType)
cristy9af9b5d2010-08-15 17:04:28 +00002985 GetOpacityPixelComponent(p);
cristy3ed852e2009-09-05 21:47:34 +00002986 if (indexes != (IndexPacket *) NULL)
2987 x_vector[x].index=(MagickRealType) indexes[x];
2988 p++;
2989 }
2990 number_rows++;
2991 }
cristybb503372010-05-27 20:51:26 +00002992 for (x=0; x < (ssize_t) image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +00002993 {
2994 y_vector[x].red+=scale.y*x_vector[x].red;
2995 y_vector[x].green+=scale.y*x_vector[x].green;
2996 y_vector[x].blue+=scale.y*x_vector[x].blue;
2997 if (scale_image->matte != MagickFalse)
2998 y_vector[x].opacity+=scale.y*x_vector[x].opacity;
2999 if (scale_indexes != (IndexPacket *) NULL)
3000 y_vector[x].index+=scale.y*x_vector[x].index;
3001 }
3002 span.y-=scale.y;
3003 scale.y=(double) scale_image->rows/(double) image->rows;
3004 next_row=MagickTrue;
3005 }
cristybb503372010-05-27 20:51:26 +00003006 if ((next_row != MagickFalse) && (number_rows < (ssize_t) image->rows))
cristy3ed852e2009-09-05 21:47:34 +00003007 {
3008 /*
3009 Read a new scanline.
3010 */
cristyed6cb232010-01-20 03:07:53 +00003011 p=GetCacheViewVirtualPixels(image_view,0,i++,image->columns,1,
3012 exception);
cristy3ed852e2009-09-05 21:47:34 +00003013 if (p == (const PixelPacket *) NULL)
3014 break;
cristyed6cb232010-01-20 03:07:53 +00003015 indexes=GetCacheViewVirtualIndexQueue(image_view);
cristybb503372010-05-27 20:51:26 +00003016 for (x=0; x < (ssize_t) image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +00003017 {
cristyce70c172010-01-07 17:15:30 +00003018 x_vector[x].red=(MagickRealType) GetRedPixelComponent(p);
3019 x_vector[x].green=(MagickRealType) GetGreenPixelComponent(p);
3020 x_vector[x].blue=(MagickRealType) GetBluePixelComponent(p);
cristy3ed852e2009-09-05 21:47:34 +00003021 if (image->matte != MagickFalse)
cristy2b726bd2010-01-11 01:05:39 +00003022 x_vector[x].opacity=(MagickRealType)
3023 GetOpacityPixelComponent(p);
cristy3ed852e2009-09-05 21:47:34 +00003024 if (indexes != (IndexPacket *) NULL)
3025 x_vector[x].index=(MagickRealType) indexes[x];
3026 p++;
3027 }
3028 number_rows++;
3029 next_row=MagickFalse;
3030 }
3031 s=scanline;
cristybb503372010-05-27 20:51:26 +00003032 for (x=0; x < (ssize_t) image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +00003033 {
3034 pixel.red=y_vector[x].red+span.y*x_vector[x].red;
3035 pixel.green=y_vector[x].green+span.y*x_vector[x].green;
3036 pixel.blue=y_vector[x].blue+span.y*x_vector[x].blue;
3037 if (image->matte != MagickFalse)
3038 pixel.opacity=y_vector[x].opacity+span.y*x_vector[x].opacity;
3039 if (scale_indexes != (IndexPacket *) NULL)
3040 pixel.index=y_vector[x].index+span.y*x_vector[x].index;
3041 s->red=pixel.red;
3042 s->green=pixel.green;
3043 s->blue=pixel.blue;
3044 if (scale_image->matte != MagickFalse)
3045 s->opacity=pixel.opacity;
3046 if (scale_indexes != (IndexPacket *) NULL)
3047 s->index=pixel.index;
3048 s++;
3049 y_vector[x]=zero;
3050 }
3051 scale.y-=span.y;
3052 if (scale.y <= 0)
3053 {
3054 scale.y=(double) scale_image->rows/(double) image->rows;
3055 next_row=MagickTrue;
3056 }
3057 span.y=1.0;
3058 }
3059 if (scale_image->columns == image->columns)
3060 {
3061 /*
3062 Transfer scanline to scaled image.
3063 */
3064 s=scanline;
cristybb503372010-05-27 20:51:26 +00003065 for (x=0; x < (ssize_t) scale_image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +00003066 {
cristyce70c172010-01-07 17:15:30 +00003067 q->red=ClampToQuantum(s->red);
3068 q->green=ClampToQuantum(s->green);
3069 q->blue=ClampToQuantum(s->blue);
cristy3ed852e2009-09-05 21:47:34 +00003070 if (scale_image->matte != MagickFalse)
cristyce70c172010-01-07 17:15:30 +00003071 q->opacity=ClampToQuantum(s->opacity);
cristy3ed852e2009-09-05 21:47:34 +00003072 if (scale_indexes != (IndexPacket *) NULL)
cristyce70c172010-01-07 17:15:30 +00003073 scale_indexes[x]=(IndexPacket) ClampToQuantum(s->index);
cristy3ed852e2009-09-05 21:47:34 +00003074 q++;
3075 s++;
3076 }
3077 }
3078 else
3079 {
3080 /*
3081 Scale X direction.
3082 */
3083 pixel=zero;
3084 next_column=MagickFalse;
3085 span.x=1.0;
3086 s=scanline;
3087 t=scale_scanline;
cristybb503372010-05-27 20:51:26 +00003088 for (x=0; x < (ssize_t) image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +00003089 {
3090 scale.x=(double) scale_image->columns/(double) image->columns;
3091 while (scale.x >= span.x)
3092 {
3093 if (next_column != MagickFalse)
3094 {
3095 pixel=zero;
3096 t++;
3097 }
3098 pixel.red+=span.x*s->red;
3099 pixel.green+=span.x*s->green;
3100 pixel.blue+=span.x*s->blue;
3101 if (image->matte != MagickFalse)
3102 pixel.opacity+=span.x*s->opacity;
3103 if (scale_indexes != (IndexPacket *) NULL)
3104 pixel.index+=span.x*s->index;
3105 t->red=pixel.red;
3106 t->green=pixel.green;
3107 t->blue=pixel.blue;
3108 if (scale_image->matte != MagickFalse)
3109 t->opacity=pixel.opacity;
3110 if (scale_indexes != (IndexPacket *) NULL)
3111 t->index=pixel.index;
3112 scale.x-=span.x;
3113 span.x=1.0;
3114 next_column=MagickTrue;
3115 }
3116 if (scale.x > 0)
3117 {
3118 if (next_column != MagickFalse)
3119 {
3120 pixel=zero;
3121 next_column=MagickFalse;
3122 t++;
3123 }
3124 pixel.red+=scale.x*s->red;
3125 pixel.green+=scale.x*s->green;
3126 pixel.blue+=scale.x*s->blue;
3127 if (scale_image->matte != MagickFalse)
3128 pixel.opacity+=scale.x*s->opacity;
3129 if (scale_indexes != (IndexPacket *) NULL)
3130 pixel.index+=scale.x*s->index;
3131 span.x-=scale.x;
3132 }
3133 s++;
3134 }
3135 if (span.x > 0)
3136 {
3137 s--;
3138 pixel.red+=span.x*s->red;
3139 pixel.green+=span.x*s->green;
3140 pixel.blue+=span.x*s->blue;
3141 if (scale_image->matte != MagickFalse)
3142 pixel.opacity+=span.x*s->opacity;
3143 if (scale_indexes != (IndexPacket *) NULL)
3144 pixel.index+=span.x*s->index;
3145 }
3146 if ((next_column == MagickFalse) &&
cristybb503372010-05-27 20:51:26 +00003147 ((ssize_t) (t-scale_scanline) < (ssize_t) scale_image->columns))
cristy3ed852e2009-09-05 21:47:34 +00003148 {
3149 t->red=pixel.red;
3150 t->green=pixel.green;
3151 t->blue=pixel.blue;
3152 if (scale_image->matte != MagickFalse)
3153 t->opacity=pixel.opacity;
3154 if (scale_indexes != (IndexPacket *) NULL)
3155 t->index=pixel.index;
3156 }
3157 /*
3158 Transfer scanline to scaled image.
3159 */
3160 t=scale_scanline;
cristybb503372010-05-27 20:51:26 +00003161 for (x=0; x < (ssize_t) scale_image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +00003162 {
cristyce70c172010-01-07 17:15:30 +00003163 q->red=ClampToQuantum(t->red);
3164 q->green=ClampToQuantum(t->green);
3165 q->blue=ClampToQuantum(t->blue);
cristy3ed852e2009-09-05 21:47:34 +00003166 if (scale_image->matte != MagickFalse)
cristyce70c172010-01-07 17:15:30 +00003167 q->opacity=ClampToQuantum(t->opacity);
cristy3ed852e2009-09-05 21:47:34 +00003168 if (scale_indexes != (IndexPacket *) NULL)
cristyce70c172010-01-07 17:15:30 +00003169 scale_indexes[x]=(IndexPacket) ClampToQuantum(t->index);
cristy3ed852e2009-09-05 21:47:34 +00003170 t++;
3171 q++;
3172 }
3173 }
cristyed6cb232010-01-20 03:07:53 +00003174 if (SyncCacheViewAuthenticPixels(scale_view,exception) == MagickFalse)
cristy3ed852e2009-09-05 21:47:34 +00003175 break;
cristy96b16132010-08-29 17:19:52 +00003176 proceed=SetImageProgress(image,ScaleImageTag,(MagickOffsetType) y,
3177 image->rows);
cristy3ed852e2009-09-05 21:47:34 +00003178 if (proceed == MagickFalse)
3179 break;
3180 }
cristyed6cb232010-01-20 03:07:53 +00003181 scale_view=DestroyCacheView(scale_view);
3182 image_view=DestroyCacheView(image_view);
cristy3ed852e2009-09-05 21:47:34 +00003183 /*
3184 Free allocated memory.
3185 */
3186 y_vector=(MagickPixelPacket *) RelinquishMagickMemory(y_vector);
3187 scale_scanline=(MagickPixelPacket *) RelinquishMagickMemory(scale_scanline);
3188 if (scale_image->rows != image->rows)
3189 scanline=(MagickPixelPacket *) RelinquishMagickMemory(scanline);
3190 x_vector=(MagickPixelPacket *) RelinquishMagickMemory(x_vector);
3191 scale_image->type=image->type;
3192 return(scale_image);
3193}
3194
anthony02b4cb42010-10-10 04:54:35 +00003195#if 0
3196 THIS IS NOT USED -- to be removed
cristy3ed852e2009-09-05 21:47:34 +00003197/*
3198%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3199% %
3200% %
3201% %
3202+ S e t R e s i z e F i l t e r S u p p o r t %
3203% %
3204% %
3205% %
3206%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3207%
3208% SetResizeFilterSupport() specifies which IR filter to use to window
3209%
3210% The format of the SetResizeFilterSupport method is:
3211%
3212% void SetResizeFilterSupport(ResizeFilter *resize_filter,
3213% const MagickRealType support)
3214%
3215% A description of each parameter follows:
3216%
3217% o resize_filter: the resize filter.
3218%
3219% o support: the filter spport radius.
3220%
3221*/
3222MagickExport void SetResizeFilterSupport(ResizeFilter *resize_filter,
3223 const MagickRealType support)
3224{
3225 assert(resize_filter != (ResizeFilter *) NULL);
3226 assert(resize_filter->signature == MagickSignature);
3227 resize_filter->support=support;
3228}
anthony02b4cb42010-10-10 04:54:35 +00003229#endif
cristy3ed852e2009-09-05 21:47:34 +00003230
3231/*
3232%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3233% %
3234% %
3235% %
3236% T h u m b n a i l I m a g e %
3237% %
3238% %
3239% %
3240%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3241%
3242% ThumbnailImage() changes the size of an image to the given dimensions and
3243% removes any associated profiles. The goal is to produce small low cost
3244% thumbnail images suited for display on the Web.
3245%
3246% The format of the ThumbnailImage method is:
3247%
cristybb503372010-05-27 20:51:26 +00003248% Image *ThumbnailImage(const Image *image,const size_t columns,
3249% const size_t rows,ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +00003250%
3251% A description of each parameter follows:
3252%
3253% o image: the image.
3254%
3255% o columns: the number of columns in the scaled image.
3256%
3257% o rows: the number of rows in the scaled image.
3258%
3259% o exception: return any errors or warnings in this structure.
3260%
3261*/
cristy9af9b5d2010-08-15 17:04:28 +00003262MagickExport Image *ThumbnailImage(const Image *image,const size_t columns,
3263 const size_t rows,ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +00003264{
3265#define SampleFactor 5
3266
3267 char
3268 value[MaxTextExtent];
3269
3270 const char
3271 *name;
3272
3273 Image
3274 *thumbnail_image;
3275
3276 MagickRealType
3277 x_factor,
3278 y_factor;
3279
cristybb503372010-05-27 20:51:26 +00003280 size_t
cristy3ed852e2009-09-05 21:47:34 +00003281 version;
3282
cristy9af9b5d2010-08-15 17:04:28 +00003283 struct stat
3284 attributes;
3285
cristy3ed852e2009-09-05 21:47:34 +00003286 assert(image != (Image *) NULL);
3287 assert(image->signature == MagickSignature);
3288 if (image->debug != MagickFalse)
3289 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
3290 assert(exception != (ExceptionInfo *) NULL);
3291 assert(exception->signature == MagickSignature);
3292 x_factor=(MagickRealType) columns/(MagickRealType) image->columns;
3293 y_factor=(MagickRealType) rows/(MagickRealType) image->rows;
3294 if ((x_factor*y_factor) > 0.1)
cristyd1bb3bc2010-09-07 00:43:58 +00003295 thumbnail_image=ResizeImage(image,columns,rows,image->filter,image->blur,
3296 exception);
cristy3ed852e2009-09-05 21:47:34 +00003297 else
3298 if (((SampleFactor*columns) < 128) || ((SampleFactor*rows) < 128))
cristyd1bb3bc2010-09-07 00:43:58 +00003299 thumbnail_image=ResizeImage(image,columns,rows,image->filter,
3300 image->blur,exception);
cristy3ed852e2009-09-05 21:47:34 +00003301 else
3302 {
3303 Image
3304 *sample_image;
3305
3306 sample_image=SampleImage(image,SampleFactor*columns,SampleFactor*rows,
3307 exception);
3308 if (sample_image == (Image *) NULL)
3309 return((Image *) NULL);
cristyd1bb3bc2010-09-07 00:43:58 +00003310 thumbnail_image=ResizeImage(sample_image,columns,rows,image->filter,
3311 image->blur,exception);
cristy3ed852e2009-09-05 21:47:34 +00003312 sample_image=DestroyImage(sample_image);
3313 }
3314 if (thumbnail_image == (Image *) NULL)
3315 return(thumbnail_image);
3316 (void) ParseAbsoluteGeometry("0x0+0+0",&thumbnail_image->page);
3317 if (thumbnail_image->matte == MagickFalse)
3318 (void) SetImageAlphaChannel(thumbnail_image,OpaqueAlphaChannel);
3319 thumbnail_image->depth=8;
3320 thumbnail_image->interlace=NoInterlace;
3321 /*
3322 Strip all profiles except color profiles.
3323 */
3324 ResetImageProfileIterator(thumbnail_image);
3325 for (name=GetNextImageProfile(thumbnail_image); name != (const char *) NULL; )
3326 {
3327 if ((LocaleCompare(name,"icc") != 0) && (LocaleCompare(name,"icm") != 0))
3328 {
cristy2b726bd2010-01-11 01:05:39 +00003329 (void) DeleteImageProfile(thumbnail_image,name);
cristy3ed852e2009-09-05 21:47:34 +00003330 ResetImageProfileIterator(thumbnail_image);
3331 }
3332 name=GetNextImageProfile(thumbnail_image);
3333 }
3334 (void) DeleteImageProperty(thumbnail_image,"comment");
3335 (void) CopyMagickString(value,image->magick_filename,MaxTextExtent);
cristy7b63d662009-11-13 01:58:56 +00003336 if (strstr(image->magick_filename,"//") == (char *) NULL)
3337 (void) FormatMagickString(value,MaxTextExtent,"file://%s",
cristy3ed852e2009-09-05 21:47:34 +00003338 image->magick_filename);
3339 (void) SetImageProperty(thumbnail_image,"Thumb::URI",value);
3340 (void) CopyMagickString(value,image->magick_filename,MaxTextExtent);
3341 if (GetPathAttributes(image->filename,&attributes) != MagickFalse)
3342 {
cristye8c25f92010-06-03 00:53:06 +00003343 (void) FormatMagickString(value,MaxTextExtent,"%.20g",(double)
cristy3ed852e2009-09-05 21:47:34 +00003344 attributes.st_mtime);
3345 (void) SetImageProperty(thumbnail_image,"Thumb::MTime",value);
3346 }
cristye8c25f92010-06-03 00:53:06 +00003347 (void) FormatMagickString(value,MaxTextExtent,"%.20g",(double)
cristy3ed852e2009-09-05 21:47:34 +00003348 attributes.st_mtime);
cristyb9080c92009-12-01 20:13:26 +00003349 (void) FormatMagickSize(GetBlobSize(image),MagickFalse,value);
cristy2ce15c92010-03-12 14:03:41 +00003350 (void) ConcatenateMagickString(value,"B",MaxTextExtent);
cristy3ed852e2009-09-05 21:47:34 +00003351 (void) SetImageProperty(thumbnail_image,"Thumb::Size",value);
3352 (void) FormatMagickString(value,MaxTextExtent,"image/%s",image->magick);
3353 LocaleLower(value);
3354 (void) SetImageProperty(thumbnail_image,"Thumb::Mimetype",value);
3355 (void) SetImageProperty(thumbnail_image,"software",
3356 GetMagickVersion(&version));
cristye8c25f92010-06-03 00:53:06 +00003357 (void) FormatMagickString(value,MaxTextExtent,"%.20g",(double)
3358 image->magick_columns);
cristy3ed852e2009-09-05 21:47:34 +00003359 (void) SetImageProperty(thumbnail_image,"Thumb::Image::Width",value);
cristye8c25f92010-06-03 00:53:06 +00003360 (void) FormatMagickString(value,MaxTextExtent,"%.20g",(double)
cristyf2faecf2010-05-28 19:19:36 +00003361 image->magick_rows);
cristy3ed852e2009-09-05 21:47:34 +00003362 (void) SetImageProperty(thumbnail_image,"Thumb::Image::height",value);
cristye8c25f92010-06-03 00:53:06 +00003363 (void) FormatMagickString(value,MaxTextExtent,"%.20g",(double)
3364 GetImageListLength(image));
cristy3ed852e2009-09-05 21:47:34 +00003365 (void) SetImageProperty(thumbnail_image,"Thumb::Document::Pages",value);
3366 return(thumbnail_image);
3367}