blob: 4451caefb8cf3f3e1c9dff94edc1d0f355359c7d [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%
anthony152700d2010-10-28 02:43:18 +0000517% The special a 'cylindrical' filter flag will promote the default
518% 4-lobed Windowed Sinc filter to a 3-lobed Windowed Jinc equivelent,
519% which is better suited to this style of image resampling. This
520% typically happens when using such a filter for images distortions.
cristy3ed852e2009-09-05 21:47:34 +0000521%
anthony152700d2010-10-28 02:43:18 +0000522% Directly requesting 'Sinc', 'Jinc' function as a filter will force
523% the use of function without any windowing, or promotion for
524% cylindrical usage. This is not recommended, except by image
525% processing experts, especially as part of expert option filter
526% function selection.
anthony06b1edf2010-10-25 01:19:50 +0000527%
anthony48f77622010-10-03 14:32:31 +0000528% Two forms of the 'Sinc' function are available: Sinc and SincFast.
nicolas07bac812010-09-19 18:47:02 +0000529% Sinc is computed using the traditional sin(pi*x)/(pi*x); it is
530% selected if the user specifically specifies the use of a Sinc
531% filter. SincFast uses highly accurate (and fast) polynomial (low Q)
anthony48f77622010-10-03 14:32:31 +0000532% and rational (high Q) approximations, and will be used by default in
533% most cases.
anthonyba5a7c32010-09-15 02:42:25 +0000534%
nicolasfc612942010-11-14 17:23:29 +0000535% The Lanczos filter is a special 3-lobed Sinc-windowed Sinc filter
536% (promoted to Jinc-windowed Jinc for cylindrical (Elliptical
537% Weighted Average) use). The Sinc version is the most popular
538% windowed filter.
anthony152700d2010-10-28 02:43:18 +0000539%
nicolasfc612942010-11-14 17:23:29 +0000540% LanczosSharp is a slightly sharpened (blur=0.9812505644269356 < 1)
541% form of the Lanczos filter, specifcally designed for EWA distortion
542% (as a Jinc-Jinc); it can also be used as a slightly sharper
nicolas2d3579a2010-11-13 15:31:28 +0000543% orthogonal Lanczos (Sinc-Sinc) filter. The chosen blur value comes
544% as close as possible to satisfying the following condition without
545% changing the character of the corresponding EWA filter:
anthony152700d2010-10-28 02:43:18 +0000546%
547% 'No-Op' Vertical and Horizontal Line Preservation Condition:
548% Images with only vertical or horizontal features are preserved
549% when performing 'no-op" with EWA distortion.
550%
nicolas2d3579a2010-11-13 15:31:28 +0000551% The Lanczos2 and Lanczos2Sharp filters are 2-lobe versions of the
552% Lanczos filters. The 'sharp' version uses a blur factor of
nicolas56d88732010-10-28 13:20:37 +0000553% 0.958027803631219, again chosen because the resulting EWA filter
nicolasc22cc712010-11-13 16:39:10 +0000554% comes as close as possible to satisfying the above
nicolasfc612942010-11-14 17:23:29 +0000555% condition. (Comment from Nicolas: value may change following the
556% use of a better optimization technique.)
anthony06b1edf2010-10-25 01:19:50 +0000557%
nicolasb7dff642010-10-25 02:04:14 +0000558% Robidoux is another filter tuned for EWA. It is the Keys cubic
anthony152700d2010-10-28 02:43:18 +0000559% filter defined by B=(228 - 108 sqrt(2))/199. Robidoux satisfies the
nicolasb7dff642010-10-25 02:04:14 +0000560% "'No-Op' Vertical and Horizontal Line Preservation Condition"
nicolasc22cc712010-11-13 16:39:10 +0000561% exactly, and it moderately blurs high frequency 'pixel-hash'
562% patterns under no-op. It turns out to be close to both Mitchell
563% and Lanczos2Sharp. For example, its first crossing is at (36
564% sqrt(2) + 123)/(72 sqrt(2) + 47), almost the same as the first
565% crossing of Mitchell and Lanczos2Sharp.
anthony152700d2010-10-28 02:43:18 +0000566%
anthony61b5ddd2010-10-05 02:33:31 +0000567%
nicolas3061b8a2010-10-22 16:34:52 +0000568% 'EXPERT' OPTIONS:
nicolasce6dc292010-10-22 16:23:07 +0000569%
anthony152700d2010-10-28 02:43:18 +0000570% These artifact "defines" are not recommended for production use
571% without expert knowledge of resampling, filtering, and the effects
572% they have on the resulting resampled (resize ro distorted) image.
nicolas3061b8a2010-10-22 16:34:52 +0000573%
anthony152700d2010-10-28 02:43:18 +0000574% They can be used to override any and all filter default, and it is
575% recommended you make good use of "filter:verbose" to make sure that
576% the overall effect of your selection (before and after) is as
577% expected.
nicolas3061b8a2010-10-22 16:34:52 +0000578%
anthony28ad1d72010-10-26 06:30:24 +0000579% "filter:verbose" controls whether to output the exact results of
580% the filter selections made, as well as plotting data for
581% graphing the resulting filter over the filters support range.
cristy3ed852e2009-09-05 21:47:34 +0000582%
anthony48f77622010-10-03 14:32:31 +0000583% "filter:filter" Select the main function associated with
584% this filter name, as the weighting function of the filter.
585% This can be used to set a windowing function as a weighting
586% function, for special purposes, such as graphing.
cristy3ed852e2009-09-05 21:47:34 +0000587%
nicolas3061b8a2010-10-22 16:34:52 +0000588% If a "filter:window" operation has not been provided, then a
589% 'Box' windowing function will be set to denote that no
590% windowing function is being used.
cristy3ed852e2009-09-05 21:47:34 +0000591%
nicolas3061b8a2010-10-22 16:34:52 +0000592% "filter:window" Select this windowing function for the filter.
593% While any filter could be used as a windowing function, using
594% the 'first lobe' of that filter over the whole support
595% window, using a non-windowing function is not advisible. If
596% no weighting filter function is specifed a 'SincFast' filter
597% will be used.
cristy3ed852e2009-09-05 21:47:34 +0000598%
nicolas3061b8a2010-10-22 16:34:52 +0000599% "filter:lobes" Number of lobes to use for the Sinc/Jinc filter.
600% This a simpler method of setting filter support size that
601% will correctly handle the Sinc/Jinc switch for an operators
602% filtering requirements. Only integers should be given.
cristy3ed852e2009-09-05 21:47:34 +0000603%
nicolas3061b8a2010-10-22 16:34:52 +0000604% "filter:support" Set the support size for filtering to the size
605% given This not recommended for Sinc/Jinc windowed filters
606% (lobes should be used instead). This will override any
607% 'filter:lobes' option.
cristy3ed852e2009-09-05 21:47:34 +0000608%
nicolas3061b8a2010-10-22 16:34:52 +0000609% "filter:win-support" Scale windowing function to this size
610% instead. This causes the windowing (or self-windowing
611% Lagrange filter) to act is if the support window it much much
612% larger than what is actually supplied to the calling
613% operator. The filter however is still clipped to the real
614% support size given, by the support range suppiled to the
615% caller. If unset this will equal the normal filter support
anthonyb6d08c52010-09-13 01:17:04 +0000616% size.
617%
nicolas3061b8a2010-10-22 16:34:52 +0000618% "filter:blur" Scale the filter and support window by this amount.
cristy3ed852e2009-09-05 21:47:34 +0000619% A value >1 will generally result in a more burred image with
620% more ringing effects, while a value <1 will sharpen the
anthony152700d2010-10-28 02:43:18 +0000621% resulting image with more aliasing effects.
cristy3ed852e2009-09-05 21:47:34 +0000622%
nicolas3061b8a2010-10-22 16:34:52 +0000623% "filter:sigma" The sigma value to use for the Gaussian filter
624% only. Defaults to '1/2' for orthogonal and 'sqrt(2)/2' for
625% cylindrical usage. It effectially provides a alturnative to
626% 'blur' for Gaussians without it also effecting the final
627% 'practical support' size.
anthonyf5e76ef2010-10-12 01:22:01 +0000628%
cristy3ed852e2009-09-05 21:47:34 +0000629% "filter:b"
nicolas3061b8a2010-10-22 16:34:52 +0000630% "filter:c" Override the preset B,C values for a Cubic type of
631% filter If only one of these are given it is assumes to be a
632% 'Keys' type of filter such that B+2C=1, where Keys 'alpha'
633% value = C
cristy3ed852e2009-09-05 21:47:34 +0000634%
anthony06b1edf2010-10-25 01:19:50 +0000635% Examples:
cristy3ed852e2009-09-05 21:47:34 +0000636%
nicolas6e1267a2010-10-22 16:35:52 +0000637% Set a true un-windowed Sinc filter with 10 lobes (very slow):
anthony48f77622010-10-03 14:32:31 +0000638% -define filter:filter=Sinc
639% -define filter:lobes=8
cristy3ed852e2009-09-05 21:47:34 +0000640%
nicolas6e1267a2010-10-22 16:35:52 +0000641% Set an 8 lobe Lanczos (Sinc or Jinc) filter:
cristy3ed852e2009-09-05 21:47:34 +0000642% -filter Lanczos
anthony48f77622010-10-03 14:32:31 +0000643% -define filter:lobes=8
anthony7bdc0ed2010-09-15 01:52:32 +0000644%
anthony06b1edf2010-10-25 01:19:50 +0000645%
cristy3ed852e2009-09-05 21:47:34 +0000646% The format of the AcquireResizeFilter method is:
647%
648% ResizeFilter *AcquireResizeFilter(const Image *image,
649% const FilterTypes filter_type, const MagickBooleanType radial,
650% ExceptionInfo *exception)
651%
cristy33b1c162010-01-23 22:51:51 +0000652% A description of each parameter follows:
653%
cristy3ed852e2009-09-05 21:47:34 +0000654% o image: the image.
655%
nicolas07bac812010-09-19 18:47:02 +0000656% o filter: the filter type, defining a preset filter, window and
657% support. The artifact settings listed above will override
658% those selections.
cristy3ed852e2009-09-05 21:47:34 +0000659%
anthony48f77622010-10-03 14:32:31 +0000660% o blur: blur the filter by this amount, use 1.0 if unknown. Image
661% artifact "filter:blur" will override this API call usage, including
662% any internal change (such as for cylindrical usage).
cristy3ed852e2009-09-05 21:47:34 +0000663%
anthony48f77622010-10-03 14:32:31 +0000664% o radial: use a 1D orthogonal filter (Sinc) or 2D cylindrical
665% (radial) filter (Jinc)
cristy3ed852e2009-09-05 21:47:34 +0000666%
667% o exception: return any errors or warnings in this structure.
668%
669*/
670MagickExport ResizeFilter *AcquireResizeFilter(const Image *image,
cristyf59a8922010-02-28 19:51:23 +0000671 const FilterTypes filter,const MagickRealType blur,
cristy3ed852e2009-09-05 21:47:34 +0000672 const MagickBooleanType cylindrical,ExceptionInfo *exception)
673{
674 const char
675 *artifact;
676
677 FilterTypes
678 filter_type,
679 window_type;
680
cristy3ed852e2009-09-05 21:47:34 +0000681 MagickRealType
682 B,
anthonyf5e76ef2010-10-12 01:22:01 +0000683 C,
684 sigma;
cristy3ed852e2009-09-05 21:47:34 +0000685
686 register ResizeFilter
687 *resize_filter;
688
cristy9af9b5d2010-08-15 17:04:28 +0000689
cristy3ed852e2009-09-05 21:47:34 +0000690 /*
anthony48f77622010-10-03 14:32:31 +0000691 Table Mapping given Filter, into Weighting and Windowing functions.
nicolas07bac812010-09-19 18:47:02 +0000692 A 'Box' windowing function means its a simble non-windowed filter.
anthony48f77622010-10-03 14:32:31 +0000693 An 'SincFast' filter function could be upgraded to a 'Jinc' filter
694 if a "cylindrical", unless a 'Sinc' or 'SincFast' filter was
695 specifically requested.
anthony449887b2010-09-10 02:23:33 +0000696
nicolas07bac812010-09-19 18:47:02 +0000697 WARNING: The order of this tabel must match the order of the
698 FilterTypes enumeration specified in "resample.h", or the filter
699 names will not match the filter being setup.
anthony1e2d5ca2010-09-10 02:24:35 +0000700
701 You can check filter setups with the "filter:verbose" setting.
cristy3ed852e2009-09-05 21:47:34 +0000702 */
703 static struct
704 {
705 FilterTypes
706 filter,
707 window;
708 } const mapping[SentinelFilter] =
709 {
nicolasb7dff642010-10-25 02:04:14 +0000710 { UndefinedFilter, BoxFilter }, /* Undefined (default to Box) */
711 { PointFilter, BoxFilter }, /* SPECIAL: Nearest neighbour */
712 { BoxFilter, BoxFilter }, /* Box averaging filter */
713 { TriangleFilter, BoxFilter }, /* Linear interpolation filter */
714 { HermiteFilter, BoxFilter }, /* Hermite interpolation filter */
715 { SincFastFilter, HanningFilter }, /* Hanning -- cosine-sinc */
716 { SincFastFilter, HammingFilter }, /* Hamming -- '' variation */
anthony06b1edf2010-10-25 01:19:50 +0000717 { SincFastFilter, BlackmanFilter }, /* Blackman -- 2*cosine-sinc */
nicolasb7dff642010-10-25 02:04:14 +0000718 { GaussianFilter, BoxFilter }, /* Gaussian blur filter */
719 { QuadraticFilter, BoxFilter }, /* Quadratic Gaussian approx */
720 { CubicFilter, BoxFilter }, /* Cubic B-Spline */
721 { CatromFilter, BoxFilter }, /* Cubic-Keys interpolator */
722 { MitchellFilter, BoxFilter }, /* 'Ideal' Cubic-Keys filter */
nicolasb7dff642010-10-25 02:04:14 +0000723 { JincFilter, BoxFilter }, /* Raw 3-lobed Jinc function */
724 { SincFilter, BoxFilter }, /* Raw 4-lobed Sinc function */
anthony152700d2010-10-28 02:43:18 +0000725 { SincFastFilter, BoxFilter }, /* Raw fast sinc ("Pade"-type) */
nicolasb7dff642010-10-25 02:04:14 +0000726 { SincFastFilter, KaiserFilter }, /* Kaiser -- square root-sinc */
727 { SincFastFilter, WelshFilter }, /* Welsh -- parabolic-sinc */
728 { SincFastFilter, CubicFilter }, /* Parzen -- cubic-sinc */
nicolasb7dff642010-10-25 02:04:14 +0000729 { SincFastFilter, BohmanFilter }, /* Bohman -- 2*cosine-sinc */
anthony06b1edf2010-10-25 01:19:50 +0000730 { SincFastFilter, TriangleFilter }, /* Bartlett -- triangle-sinc */
anthony152700d2010-10-28 02:43:18 +0000731 { LagrangeFilter, BoxFilter }, /* Lagrange self-windowing */
732 { LanczosFilter, LanczosFilter }, /* Lanczos Sinc-Sinc filters */
733 { LanczosSharpFilter, LanczosSharpFilter }, /* | these require */
734 { Lanczos2Filter, Lanczos2Filter }, /* | special handling */
735 { Lanczos2SharpFilter,Lanczos2SharpFilter },
736 { RobidouxFilter, BoxFilter }, /* Cubic Keys tuned for EWA */
cristy3ed852e2009-09-05 21:47:34 +0000737 };
738 /*
nicolas32f44eb2010-09-20 01:23:12 +0000739 Table mapping the filter/window from the above table to an actual
nicolas07bac812010-09-19 18:47:02 +0000740 function. The default support size for that filter as a weighting
741 function, the range to scale with to use that function as a sinc
742 windowing function, (typ 1.0).
anthony933b4bf2010-09-10 02:31:37 +0000743
anthony07a3f7f2010-09-16 03:03:11 +0000744 Note that the filter_type -> function is 1 to 1 except for Sinc(),
nicolas07bac812010-09-19 18:47:02 +0000745 SincFast(), and CubicBC() functions, which may have multiple
746 filter to function associations.
747
748 See "filter:verbose" handling below for the function -> filter
749 mapping.
cristy3ed852e2009-09-05 21:47:34 +0000750 */
751 static struct
752 {
753 MagickRealType
754 (*function)(const MagickRealType, const ResizeFilter*),
nicolas8eccc162010-10-16 19:48:13 +0000755 lobes, /* Default lobes/support size of the weighting filter. */
anthony450db502010-10-19 04:03:03 +0000756 scale, /* Support when function used as a windowing function
757 Typically equal to the location of the first zero crossing. */
758 B,C; /* BC-spline coefficients, ignored if not a CubicBC filter. */
cristy3ed852e2009-09-05 21:47:34 +0000759 } const filters[SentinelFilter] =
760 {
anthony61b5ddd2010-10-05 02:33:31 +0000761 { Box, 0.5, 0.5, 0.0, 0.0 }, /* Undefined (default to Box) */
762 { Box, 0.0, 0.5, 0.0, 0.0 }, /* Point (special handling) */
763 { Box, 0.5, 0.5, 0.0, 0.0 }, /* Box */
764 { Triangle, 1.0, 1.0, 0.0, 0.0 }, /* Triangle */
765 { CubicBC, 1.0, 1.0, 0.0, 0.0 }, /* Hermite (cubic B=C=0) */
766 { Hanning, 1.0, 1.0, 0.0, 0.0 }, /* Hanning, cosine window */
767 { Hamming, 1.0, 1.0, 0.0, 0.0 }, /* Hamming, '' variation */
768 { Blackman, 1.0, 1.0, 0.0, 0.0 }, /* Blackman, 2*cosine window */
anthonyf5e76ef2010-10-12 01:22:01 +0000769 { Gaussian, 2.0, 1.5, 0.0, 0.0 }, /* Gaussian */
anthony61b5ddd2010-10-05 02:33:31 +0000770 { Quadratic, 1.5, 1.5, 0.0, 0.0 }, /* Quadratic gaussian */
771 { CubicBC, 2.0, 2.0, 1.0, 0.0 }, /* Cubic B-Spline (B=1,C=0) */
772 { CubicBC, 2.0, 1.0, 0.0, 0.5 }, /* Catmull-Rom (B=0,C=1/2) */
nicolasd349be62010-10-28 18:57:38 +0000773 { CubicBC, 2.0, 8.0/7.0, 1./3., 1./3. }, /* Mitchell (B=C=1/3) */
nicolasf8689f42010-10-18 16:14:08 +0000774 { Jinc, 3.0, 1.2196698912665045, 0.0, 0.0 }, /* Raw 3-lobed Jinc */
anthony61b5ddd2010-10-05 02:33:31 +0000775 { Sinc, 4.0, 1.0, 0.0, 0.0 }, /* Raw 4-lobed Sinc */
anthony06b1edf2010-10-25 01:19:50 +0000776 { SincFast, 4.0, 1.0, 0.0, 0.0 }, /* Raw fast sinc ("Pade"-type) */
anthony61b5ddd2010-10-05 02:33:31 +0000777 { Kaiser, 1.0, 1.0, 0.0, 0.0 }, /* Kaiser (square root window) */
778 { Welsh, 1.0, 1.0, 0.0, 0.0 }, /* Welsh (parabolic window) */
779 { CubicBC, 2.0, 2.0, 1.0, 0.0 }, /* Parzen (B-Spline window) */
anthony61b5ddd2010-10-05 02:33:31 +0000780 { Bohman, 1.0, 1.0, 0.0, 0.0 }, /* Bohman, 2*Cosine window */
781 { Triangle, 1.0, 1.0, 0.0, 0.0 }, /* Bartlett (triangle window) */
anthony152700d2010-10-28 02:43:18 +0000782 { Lagrange, 2.0, 1.0, 0.0, 0.0 }, /* Lagrange sinc approximation */
783 { SincFast, 3.0, 1.0, 0.0, 0.0 }, /* Lanczos, 3-lobed Sinc-Sinc */
784 { SincFast, 3.0, 1.0, 0.0, 0.0 }, /* lanczos, Sharpened */
785 { SincFast, 2.0, 1.0, 0.0, 0.0 }, /* Lanczos, 2-lobed */
786 { SincFast, 2.0, 1.0, 0.0, 0.0 }, /* Lanczos2, sharpened */
anthony450db502010-10-19 04:03:03 +0000787 { CubicBC, 2.0, 1.1685777620836932,
nicolas0d5e5322010-10-22 15:29:30 +0000788 0.37821575509399867, 0.31089212245300067 }
789 /* Robidoux: Keys cubic close to Lanczos2D sharpened */
cristy3ed852e2009-09-05 21:47:34 +0000790 };
791 /*
anthony9a98fc62010-10-11 02:47:19 +0000792 The known zero crossings of the Jinc() or more accurately the Jinc(x*PI)
anthony450db502010-10-19 04:03:03 +0000793 function being used as a filter. It is used by the "filter:lobes" expert
794 setting and for 'lobes' for Jinc functions in the previous table. This
795 way users do not have to deal with the highly irrational lobe sizes of the
anthony9a98fc62010-10-11 02:47:19 +0000796 Jinc filter.
anthony48f77622010-10-03 14:32:31 +0000797
nicolase473f722010-10-07 00:05:13 +0000798 Values taken from
anthony48f77622010-10-03 14:32:31 +0000799 http://cose.math.bas.bg/webMathematica/webComputing/BesselZeros.jsp
nicolase473f722010-10-07 00:05:13 +0000800 using Jv-function with v=1, then dividing by PI.
cristy3ed852e2009-09-05 21:47:34 +0000801 */
802 static MagickRealType
anthony48f77622010-10-03 14:32:31 +0000803 jinc_zeros[16] =
cristy3ed852e2009-09-05 21:47:34 +0000804 {
nicolas8eccc162010-10-16 19:48:13 +0000805 1.2196698912665045,
806 2.2331305943815286,
807 3.2383154841662362,
808 4.2410628637960699,
809 5.2427643768701817,
810 6.2439216898644877,
811 7.244759868719957,
812 8.2453949139520427,
813 9.2458926849494673,
814 10.246293348754916,
815 11.246622794877883,
816 12.246898461138105,
817 13.247132522181061,
818 14.247333735806849,
anthonyc2d07db2010-09-15 23:47:40 +0000819 15.2475085630373,
nicolas8eccc162010-10-16 19:48:13 +0000820 16.247661874700962
cristy3ed852e2009-09-05 21:47:34 +0000821 };
822
cristy33b1c162010-01-23 22:51:51 +0000823 /*
824 Allocate resize filter.
825 */
cristy3ed852e2009-09-05 21:47:34 +0000826 assert(image != (const Image *) NULL);
827 assert(image->signature == MagickSignature);
828 if (image->debug != MagickFalse)
829 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
830 assert(UndefinedFilter < filter && filter < SentinelFilter);
831 assert(exception != (ExceptionInfo *) NULL);
832 assert(exception->signature == MagickSignature);
cristy73bd4a52010-10-05 11:24:23 +0000833 resize_filter=(ResizeFilter *) AcquireMagickMemory(sizeof(*resize_filter));
cristy3ed852e2009-09-05 21:47:34 +0000834 if (resize_filter == (ResizeFilter *) NULL)
835 ThrowFatalException(ResourceLimitFatalError,"MemoryAllocationFailed");
cristy33b1c162010-01-23 22:51:51 +0000836 /*
837 Defaults for the requested filter.
838 */
839 filter_type=mapping[filter].filter;
840 window_type=mapping[filter].window;
anthonyf5e76ef2010-10-12 01:22:01 +0000841 resize_filter->blur = blur;
842 sigma = 0.5;
anthony152700d2010-10-28 02:43:18 +0000843 /* Promote 1D Windowed Sinc Filters to a 2D Windowed Jinc filters */
844 if (cylindrical != MagickFalse && filter_type == SincFastFilter
845 && filter != SincFastFilter )
846 filter_type=JincFilter;
anthony61b5ddd2010-10-05 02:33:31 +0000847
anthony152700d2010-10-28 02:43:18 +0000848 /* Expert filter setting override */
cristy3ed852e2009-09-05 21:47:34 +0000849 artifact=GetImageArtifact(image,"filter:filter");
cristy33b1c162010-01-23 22:51:51 +0000850 if (artifact != (const char *) NULL)
851 {
anthony152700d2010-10-28 02:43:18 +0000852 ssize_t
853 option;
cristy9af9b5d2010-08-15 17:04:28 +0000854 option=ParseMagickOption(MagickFilterOptions,MagickFalse,artifact);
cristy33b1c162010-01-23 22:51:51 +0000855 if ((UndefinedFilter < option) && (option < SentinelFilter))
anthony61b5ddd2010-10-05 02:33:31 +0000856 { /* Raw filter request - no window function. */
cristy33b1c162010-01-23 22:51:51 +0000857 filter_type=(FilterTypes) option;
858 window_type=BoxFilter;
859 }
nicolas07bac812010-09-19 18:47:02 +0000860 /* Filter override with a specific window function. */
cristy33b1c162010-01-23 22:51:51 +0000861 artifact=GetImageArtifact(image,"filter:window");
862 if (artifact != (const char *) NULL)
863 {
cristy9af9b5d2010-08-15 17:04:28 +0000864 option=ParseMagickOption(MagickFilterOptions,MagickFalse,artifact);
cristy33b1c162010-01-23 22:51:51 +0000865 if ((UndefinedFilter < option) && (option < SentinelFilter))
anthony152700d2010-10-28 02:43:18 +0000866 window_type=(FilterTypes) option;
cristy33b1c162010-01-23 22:51:51 +0000867 }
cristy3ed852e2009-09-05 21:47:34 +0000868 }
cristy33b1c162010-01-23 22:51:51 +0000869 else
870 {
anthony48f77622010-10-03 14:32:31 +0000871 /* Window specified, but no filter function? Assume Sinc/Jinc. */
cristy33b1c162010-01-23 22:51:51 +0000872 artifact=GetImageArtifact(image,"filter:window");
873 if (artifact != (const char *) NULL)
874 {
anthony152700d2010-10-28 02:43:18 +0000875 ssize_t
876 option;
cristy33b1c162010-01-23 22:51:51 +0000877 option=ParseMagickOption(MagickFilterOptions,MagickFalse,
878 artifact);
879 if ((UndefinedFilter < option) && (option < SentinelFilter))
880 {
anthony61b5ddd2010-10-05 02:33:31 +0000881 filter_type=cylindrical != MagickFalse ?
882 JincFilter : SincFastFilter;
cristy19eb6412010-04-23 14:42:29 +0000883 window_type=(FilterTypes) option;
cristy33b1c162010-01-23 22:51:51 +0000884 }
885 }
886 }
anthony152700d2010-10-28 02:43:18 +0000887
nicolas07bac812010-09-19 18:47:02 +0000888 /* Assign the real functions to use for the filters selected. */
cristy33b1c162010-01-23 22:51:51 +0000889 resize_filter->filter=filters[filter_type].function;
anthony61b5ddd2010-10-05 02:33:31 +0000890 resize_filter->support=filters[filter_type].lobes;
cristy33b1c162010-01-23 22:51:51 +0000891 resize_filter->window=filters[window_type].function;
892 resize_filter->scale=filters[window_type].scale;
anthony029ba0e2010-10-29 00:54:24 +0000893 resize_filter->signature=MagickSignature;
anthony61b5ddd2010-10-05 02:33:31 +0000894
anthonyf5e76ef2010-10-12 01:22:01 +0000895 /* Filter Modifications for orthogonal/cylindrical usage */
anthony10b8bc82010-10-02 12:48:46 +0000896 if (cylindrical != MagickFalse)
897 switch (filter_type)
898 {
anthony10b8bc82010-10-02 12:48:46 +0000899 case BoxFilter:
900 /* Support for Cylindrical Box should be sqrt(2)/2 */
cristy1c9bb452010-10-03 16:48:33 +0000901 resize_filter->support=(MagickRealType) MagickSQ1_2;
anthony10b8bc82010-10-02 12:48:46 +0000902 break;
anthony152700d2010-10-28 02:43:18 +0000903 case LanczosFilter:
904 case LanczosSharpFilter:
905 case Lanczos2Filter:
906 case Lanczos2SharpFilter:
907 resize_filter->filter=filters[JincFilter].function;
908 resize_filter->window=filters[JincFilter].function;
anthony029ba0e2010-10-29 00:54:24 +0000909 resize_filter->scale=filters[JincFilter].scale;
910 /* number of lobes (support window size) remain unchanged */
anthony152700d2010-10-28 02:43:18 +0000911 break;
912 case GaussianFilter:
913 /* Cylindrical Gaussian sigma is sqrt(2)/2. */
914 sigma = (MagickRealType) (MagickSQ2/2.0);
915 break;
anthony81b8bf92010-10-02 13:54:34 +0000916 default:
917 break;
anthony10b8bc82010-10-02 12:48:46 +0000918 }
anthony152700d2010-10-28 02:43:18 +0000919 /* Global Sharpening (regardless of orthoginal/cylindrical) */
920 switch (filter_type)
921 {
922 case LanczosSharpFilter:
nicolasaf6ee7c2010-11-13 03:34:22 +0000923 resize_filter->blur *= 0.9812505644269356;
anthony152700d2010-10-28 02:43:18 +0000924 break;
925 case Lanczos2SharpFilter:
nicolas56d88732010-10-28 13:20:37 +0000926 resize_filter->blur *= 0.958027803631219;
anthony152700d2010-10-28 02:43:18 +0000927 break;
928 default:
929 break;
930 }
anthony61b5ddd2010-10-05 02:33:31 +0000931
anthonyf5e76ef2010-10-12 01:22:01 +0000932 /*
anthony06b1edf2010-10-25 01:19:50 +0000933 ** Other Expert Option Modifications
anthonyf5e76ef2010-10-12 01:22:01 +0000934 */
935
936 /* User Sigma Override - no support change */
937 artifact=GetImageArtifact(image,"filter:sigma");
938 if (artifact != (const char *) NULL)
939 sigma=StringToDouble(artifact);
940 /* Define coefficents for Gaussian (assumes no cubic window) */
941 if ( GaussianFilter ) {
942 resize_filter->coeff[0] = 1.0/(2.0*sigma*sigma);
cristy68f103e2010-10-14 01:19:07 +0000943 resize_filter->coeff[1] = (MagickRealType) (1.0/(Magick2PI*sigma*sigma)); /* unused */
anthonyf5e76ef2010-10-12 01:22:01 +0000944 }
945
946 /* Blur Override */
947 artifact=GetImageArtifact(image,"filter:blur");
948 if (artifact != (const char *) NULL)
949 resize_filter->blur=StringToDouble(artifact);
950 if (resize_filter->blur < MagickEpsilon)
951 resize_filter->blur=(MagickRealType) MagickEpsilon;
952
953 /* Support Overrides */
cristy3ed852e2009-09-05 21:47:34 +0000954 artifact=GetImageArtifact(image,"filter:lobes");
cristy33b1c162010-01-23 22:51:51 +0000955 if (artifact != (const char *) NULL)
956 {
cristybb503372010-05-27 20:51:26 +0000957 ssize_t
cristy33b1c162010-01-23 22:51:51 +0000958 lobes;
959
cristy96b16132010-08-29 17:19:52 +0000960 lobes=(ssize_t) StringToLong(artifact);
cristy33b1c162010-01-23 22:51:51 +0000961 if (lobes < 1)
962 lobes=1;
963 resize_filter->support=(MagickRealType) lobes;
cristy3ed852e2009-09-05 21:47:34 +0000964 }
anthony152700d2010-10-28 02:43:18 +0000965 /* Convert a Jinc function lobes value to a real support value */
anthony61b5ddd2010-10-05 02:33:31 +0000966 if (resize_filter->filter == Jinc)
967 {
968 if (resize_filter->support > 16)
969 resize_filter->support=jinc_zeros[15]; /* largest entry in table */
970 else
971 resize_filter->support = jinc_zeros[((long)resize_filter->support)-1];
972 }
973 /* expert override of the support setting */
cristy3ed852e2009-09-05 21:47:34 +0000974 artifact=GetImageArtifact(image,"filter:support");
975 if (artifact != (const char *) NULL)
cristy33b1c162010-01-23 22:51:51 +0000976 resize_filter->support=fabs(StringToDouble(artifact));
977 /*
nicolas07bac812010-09-19 18:47:02 +0000978 Scale windowing function separatally to the support 'clipping'
979 window that calling operator is planning to actually use. (Expert
980 override)
cristy3ed852e2009-09-05 21:47:34 +0000981 */
anthony55f12332010-09-10 01:13:02 +0000982 resize_filter->window_support=resize_filter->support; /* default */
cristy3ed852e2009-09-05 21:47:34 +0000983 artifact=GetImageArtifact(image,"filter:win-support");
984 if (artifact != (const char *) NULL)
cristy33b1c162010-01-23 22:51:51 +0000985 resize_filter->window_support=fabs(StringToDouble(artifact));
986 /*
anthony029ba0e2010-10-29 00:54:24 +0000987 Adjust window function scaling to match windowing support for
anthony1f90a6b2010-09-14 08:56:31 +0000988 weighting function. This avoids a division on every filter call.
anthony55f12332010-09-10 01:13:02 +0000989 */
990 resize_filter->scale /= resize_filter->window_support;
anthonyf5e76ef2010-10-12 01:22:01 +0000991
anthony55f12332010-09-10 01:13:02 +0000992 /*
anthonyf5e76ef2010-10-12 01:22:01 +0000993 * Set Cubic Spline B,C values, calculate Cubic coefficients.
cristy33b1c162010-01-23 22:51:51 +0000994 */
cristy3ed852e2009-09-05 21:47:34 +0000995 B=0.0;
996 C=0.0;
cristy33b1c162010-01-23 22:51:51 +0000997 if ((filters[filter_type].function == CubicBC) ||
998 (filters[window_type].function == CubicBC))
999 {
anthony2d9b8b52010-09-14 08:31:07 +00001000 B=filters[filter_type].B;
1001 C=filters[filter_type].C;
1002 if (filters[window_type].function == CubicBC)
cristy33b1c162010-01-23 22:51:51 +00001003 {
anthony2d9b8b52010-09-14 08:31:07 +00001004 B=filters[window_type].B;
1005 C=filters[window_type].C;
cristy33b1c162010-01-23 22:51:51 +00001006 }
cristy33b1c162010-01-23 22:51:51 +00001007 artifact=GetImageArtifact(image,"filter:b");
cristy3ed852e2009-09-05 21:47:34 +00001008 if (artifact != (const char *) NULL)
cristy33b1c162010-01-23 22:51:51 +00001009 {
1010 B=StringToDouble(artifact);
nicolasd15ee9a2010-10-24 18:39:45 +00001011 C=(1.0-B)/2.0; /* Calculate C to get a Keys cubic filter. */
anthonyf5e76ef2010-10-12 01:22:01 +00001012 artifact=GetImageArtifact(image,"filter:c"); /* user C override */
cristy33b1c162010-01-23 22:51:51 +00001013 if (artifact != (const char *) NULL)
1014 C=StringToDouble(artifact);
1015 }
1016 else
1017 {
1018 artifact=GetImageArtifact(image,"filter:c");
1019 if (artifact != (const char *) NULL)
1020 {
1021 C=StringToDouble(artifact);
nicolasb7dff642010-10-25 02:04:14 +00001022 B=1.0-2.0*C; /* Calculate B to get a Keys cubic filter. */
cristy33b1c162010-01-23 22:51:51 +00001023 }
1024 }
nicolasc6bac3b2010-10-24 18:10:45 +00001025 /* Convert B,C values into Cubic Coefficents. See CubicBC(). */
nicolasd15ee9a2010-10-24 18:39:45 +00001026 {
anthony06b1edf2010-10-25 01:19:50 +00001027 const double twoB = B+B;
1028 resize_filter->coeff[0]=1.0-(1.0/3.0)*B;
1029 resize_filter->coeff[1]=-3.0+twoB+C;
1030 resize_filter->coeff[2]=2.0-1.5*B-C;
1031 resize_filter->coeff[3]=(4.0/3.0)*B+4.0*C;
1032 resize_filter->coeff[4]=-8.0*C-twoB;
1033 resize_filter->coeff[5]=B+5.0*C;
1034 resize_filter->coeff[6]=(-1.0/6.0)*B-C;
nicolasd15ee9a2010-10-24 18:39:45 +00001035 }
nicolasc6bac3b2010-10-24 18:10:45 +00001036 }
anthonyf5e76ef2010-10-12 01:22:01 +00001037
anthony55f12332010-09-10 01:13:02 +00001038 /*
nicolas07bac812010-09-19 18:47:02 +00001039 Expert Option Request for verbose details of the resulting filter.
anthony55f12332010-09-10 01:13:02 +00001040 */
cristyf5b49372010-10-16 01:06:47 +00001041#if defined(MAGICKCORE_OPENMP_SUPPORT)
1042 #pragma omp master
1043 {
1044#endif
1045 artifact=GetImageArtifact(image,"filter:verbose");
anthony28ad1d72010-10-26 06:30:24 +00001046 if (IsMagickTrue(artifact))
cristyf5b49372010-10-16 01:06:47 +00001047 {
1048 double
anthony06b1edf2010-10-25 01:19:50 +00001049 support,
cristyf5b49372010-10-16 01:06:47 +00001050 x;
cristy3ed852e2009-09-05 21:47:34 +00001051
cristyf5b49372010-10-16 01:06:47 +00001052 /*
1053 Set the weighting function properly when the weighting
1054 function may not exactly match the filter of the same name.
anthony06b1edf2010-10-25 01:19:50 +00001055 EG: a Point filter is really uses a Box weighting function
cristyf5b49372010-10-16 01:06:47 +00001056 with a different support than is typically used.
cristyf5b49372010-10-16 01:06:47 +00001057 */
1058 if (resize_filter->filter == Box) filter_type=BoxFilter;
1059 if (resize_filter->filter == Sinc) filter_type=SincFilter;
1060 if (resize_filter->filter == SincFast) filter_type=SincFastFilter;
1061 if (resize_filter->filter == Jinc) filter_type=JincFilter;
1062 if (resize_filter->filter == CubicBC) filter_type=CubicFilter;
anthony1ad20052010-10-29 01:53:03 +00001063 if (resize_filter->window == Box) window_type=BoxFilter;
anthony152700d2010-10-28 02:43:18 +00001064 if (resize_filter->window == Sinc) window_type=SincFilter;
1065 if (resize_filter->window == SincFast) window_type=SincFastFilter;
anthony1ad20052010-10-29 01:53:03 +00001066 if (resize_filter->window == Jinc) window_type=JincFilter;
anthony152700d2010-10-28 02:43:18 +00001067 if (resize_filter->window == CubicBC) window_type=CubicFilter;
cristyf5b49372010-10-16 01:06:47 +00001068 /*
1069 Report Filter Details.
1070 */
1071 support=GetResizeFilterSupport(resize_filter); /* practical_support */
1072 (void) fprintf(stdout,"# Resize Filter (for graphing)\n#\n");
anthony06b1edf2010-10-25 01:19:50 +00001073 (void) fprintf(stdout,"# filter = %s\n",
1074 MagickOptionToMnemonic(MagickFilterOptions,filter_type));
1075 (void) fprintf(stdout,"# window = %s\n",
1076 MagickOptionToMnemonic(MagickFilterOptions, window_type));
1077 (void) fprintf(stdout,"# support = %.*g\n",
1078 GetMagickPrecision(),(double) resize_filter->support);
1079 (void) fprintf(stdout,"# win-support = %.*g\n",
1080 GetMagickPrecision(),(double) resize_filter->window_support);
1081 (void) fprintf(stdout,"# scale_blur = %.*g\n",
1082 GetMagickPrecision(), (double)resize_filter->blur);
cristyf5b49372010-10-16 01:06:47 +00001083 if ( filter_type == GaussianFilter )
anthony06b1edf2010-10-25 01:19:50 +00001084 (void) fprintf(stdout,"# gaussian_sigma = %.*g\n",
1085 GetMagickPrecision(), (double)sigma);
1086 (void) fprintf(stdout,"# practical_support = %.*g\n",
1087 GetMagickPrecision(), (double)support);
cristyf5b49372010-10-16 01:06:47 +00001088 if ( filter_type == CubicFilter || window_type == CubicFilter )
anthony06b1edf2010-10-25 01:19:50 +00001089 (void) fprintf(stdout,"# B,C = %.*g,%.*g\n",
1090 GetMagickPrecision(),(double)B, GetMagickPrecision(),(double)C);
cristyf5b49372010-10-16 01:06:47 +00001091 (void) fprintf(stdout,"\n");
1092 /*
1093 Output values of resulting filter graph -- for graphing
1094 filter result.
1095 */
1096 for (x=0.0; x <= support; x+=0.01f)
1097 (void) fprintf(stdout,"%5.2lf\t%.*g\n",x,GetMagickPrecision(),
1098 (double) GetResizeFilterWeight(resize_filter,x));
1099 /* A final value so gnuplot can graph the 'stop' properly. */
1100 (void) fprintf(stdout,"%5.2lf\t%.*g\n",support,GetMagickPrecision(),
1101 0.0);
1102 }
1103 /* Output the above once only for each image - remove setting */
1104 (void) DeleteImageArtifact((Image *) image,"filter:verbose");
1105#if defined(MAGICKCORE_OPENMP_SUPPORT)
1106 }
1107#endif
cristy3ed852e2009-09-05 21:47:34 +00001108 return(resize_filter);
1109}
1110
1111/*
1112%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1113% %
1114% %
1115% %
1116% A d a p t i v e R e s i z e I m a g e %
1117% %
1118% %
1119% %
1120%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1121%
1122% AdaptiveResizeImage() adaptively resize image with pixel resampling.
1123%
1124% The format of the AdaptiveResizeImage method is:
1125%
cristy9af9b5d2010-08-15 17:04:28 +00001126% Image *AdaptiveResizeImage(const Image *image,const size_t columns,
1127% const size_t rows,ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +00001128%
1129% A description of each parameter follows:
1130%
1131% o image: the image.
1132%
1133% o columns: the number of columns in the resized image.
1134%
1135% o rows: the number of rows in the resized image.
1136%
1137% o exception: return any errors or warnings in this structure.
1138%
1139*/
1140MagickExport Image *AdaptiveResizeImage(const Image *image,
cristybb503372010-05-27 20:51:26 +00001141 const size_t columns,const size_t rows,ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +00001142{
1143#define AdaptiveResizeImageTag "Resize/Image"
1144
cristyc4c8d132010-01-07 01:58:38 +00001145 CacheView
1146 *resize_view;
1147
cristy3ed852e2009-09-05 21:47:34 +00001148 Image
1149 *resize_image;
1150
cristy3ed852e2009-09-05 21:47:34 +00001151 MagickBooleanType
1152 proceed;
1153
1154 MagickPixelPacket
1155 pixel;
1156
1157 PointInfo
1158 offset;
1159
1160 ResampleFilter
1161 *resample_filter;
1162
cristy9af9b5d2010-08-15 17:04:28 +00001163 ssize_t
1164 y;
1165
cristy3ed852e2009-09-05 21:47:34 +00001166 /*
1167 Adaptively resize image.
1168 */
1169 assert(image != (const Image *) NULL);
1170 assert(image->signature == MagickSignature);
1171 if (image->debug != MagickFalse)
1172 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
1173 assert(exception != (ExceptionInfo *) NULL);
1174 assert(exception->signature == MagickSignature);
1175 if ((columns == 0) || (rows == 0))
1176 return((Image *) NULL);
1177 if ((columns == image->columns) && (rows == image->rows))
1178 return(CloneImage(image,0,0,MagickTrue,exception));
1179 resize_image=CloneImage(image,columns,rows,MagickTrue,exception);
1180 if (resize_image == (Image *) NULL)
1181 return((Image *) NULL);
1182 if (SetImageStorageClass(resize_image,DirectClass) == MagickFalse)
1183 {
1184 InheritException(exception,&resize_image->exception);
1185 resize_image=DestroyImage(resize_image);
1186 return((Image *) NULL);
1187 }
1188 GetMagickPixelPacket(image,&pixel);
1189 resample_filter=AcquireResampleFilter(image,exception);
cristy0c7e9eb2010-09-30 14:23:37 +00001190 (void) SetResampleFilter(resample_filter,PointFilter,1.0);
anthonyccf239d2010-09-30 04:23:46 +00001191 (void) SetResampleFilterInterpolateMethod(resample_filter,
cristy0c7e9eb2010-09-30 14:23:37 +00001192 MeshInterpolatePixel);
cristy3ed852e2009-09-05 21:47:34 +00001193 resize_view=AcquireCacheView(resize_image);
cristybb503372010-05-27 20:51:26 +00001194 for (y=0; y < (ssize_t) resize_image->rows; y++)
cristy3ed852e2009-09-05 21:47:34 +00001195 {
1196 register IndexPacket
cristyc47d1f82009-11-26 01:44:43 +00001197 *restrict resize_indexes;
cristy3ed852e2009-09-05 21:47:34 +00001198
cristybb503372010-05-27 20:51:26 +00001199 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +00001200 x;
1201
1202 register PixelPacket
cristyc47d1f82009-11-26 01:44:43 +00001203 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +00001204
1205 q=QueueCacheViewAuthenticPixels(resize_view,0,y,resize_image->columns,1,
1206 exception);
1207 if (q == (PixelPacket *) NULL)
1208 break;
1209 resize_indexes=GetCacheViewAuthenticIndexQueue(resize_view);
1210 offset.y=((MagickRealType) y*image->rows/resize_image->rows);
cristybb503372010-05-27 20:51:26 +00001211 for (x=0; x < (ssize_t) resize_image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +00001212 {
1213 offset.x=((MagickRealType) x*image->columns/resize_image->columns);
1214 (void) ResamplePixelColor(resample_filter,offset.x-0.5,offset.y-0.5,
1215 &pixel);
1216 SetPixelPacket(resize_image,&pixel,q,resize_indexes+x);
1217 q++;
1218 }
1219 if (SyncCacheViewAuthenticPixels(resize_view,exception) == MagickFalse)
1220 break;
cristy96b16132010-08-29 17:19:52 +00001221 proceed=SetImageProgress(image,AdaptiveResizeImageTag,(MagickOffsetType) y,
1222 image->rows);
cristy3ed852e2009-09-05 21:47:34 +00001223 if (proceed == MagickFalse)
1224 break;
1225 }
1226 resample_filter=DestroyResampleFilter(resample_filter);
1227 resize_view=DestroyCacheView(resize_view);
1228 return(resize_image);
1229}
1230
1231/*
1232%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1233% %
1234% %
1235% %
1236+ B e s s e l O r d e r O n e %
1237% %
1238% %
1239% %
1240%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1241%
1242% BesselOrderOne() computes the Bessel function of x of the first kind of
anthony48f77622010-10-03 14:32:31 +00001243% order 0. This is used to create the Jinc() filter function below.
cristy3ed852e2009-09-05 21:47:34 +00001244%
1245% Reduce x to |x| since j1(x)= -j1(-x), and for x in (0,8]
1246%
1247% j1(x) = x*j1(x);
1248%
1249% For x in (8,inf)
1250%
1251% j1(x) = sqrt(2/(pi*x))*(p1(x)*cos(x1)-q1(x)*sin(x1))
1252%
1253% where x1 = x-3*pi/4. Compute sin(x1) and cos(x1) as follow:
1254%
1255% cos(x1) = cos(x)cos(3pi/4)+sin(x)sin(3pi/4)
1256% = 1/sqrt(2) * (sin(x) - cos(x))
1257% sin(x1) = sin(x)cos(3pi/4)-cos(x)sin(3pi/4)
1258% = -1/sqrt(2) * (sin(x) + cos(x))
1259%
1260% The format of the BesselOrderOne method is:
1261%
1262% MagickRealType BesselOrderOne(MagickRealType x)
1263%
1264% A description of each parameter follows:
1265%
1266% o x: MagickRealType value.
1267%
1268*/
1269
1270#undef I0
1271static MagickRealType I0(MagickRealType x)
1272{
1273 MagickRealType
1274 sum,
1275 t,
1276 y;
1277
cristybb503372010-05-27 20:51:26 +00001278 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +00001279 i;
1280
1281 /*
1282 Zeroth order Bessel function of the first kind.
1283 */
1284 sum=1.0;
1285 y=x*x/4.0;
1286 t=y;
1287 for (i=2; t > MagickEpsilon; i++)
1288 {
1289 sum+=t;
1290 t*=y/((MagickRealType) i*i);
1291 }
1292 return(sum);
1293}
1294
1295#undef J1
1296static MagickRealType J1(MagickRealType x)
1297{
1298 MagickRealType
1299 p,
1300 q;
1301
cristybb503372010-05-27 20:51:26 +00001302 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +00001303 i;
1304
1305 static const double
1306 Pone[] =
1307 {
1308 0.581199354001606143928050809e+21,
1309 -0.6672106568924916298020941484e+20,
1310 0.2316433580634002297931815435e+19,
1311 -0.3588817569910106050743641413e+17,
1312 0.2908795263834775409737601689e+15,
1313 -0.1322983480332126453125473247e+13,
1314 0.3413234182301700539091292655e+10,
1315 -0.4695753530642995859767162166e+7,
1316 0.270112271089232341485679099e+4
1317 },
1318 Qone[] =
1319 {
1320 0.11623987080032122878585294e+22,
1321 0.1185770712190320999837113348e+20,
1322 0.6092061398917521746105196863e+17,
1323 0.2081661221307607351240184229e+15,
1324 0.5243710262167649715406728642e+12,
1325 0.1013863514358673989967045588e+10,
1326 0.1501793594998585505921097578e+7,
1327 0.1606931573481487801970916749e+4,
1328 0.1e+1
1329 };
1330
1331 p=Pone[8];
1332 q=Qone[8];
1333 for (i=7; i >= 0; i--)
1334 {
1335 p=p*x*x+Pone[i];
1336 q=q*x*x+Qone[i];
1337 }
1338 return(p/q);
1339}
1340
1341#undef P1
1342static MagickRealType P1(MagickRealType x)
1343{
1344 MagickRealType
1345 p,
1346 q;
1347
cristybb503372010-05-27 20:51:26 +00001348 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +00001349 i;
1350
1351 static const double
1352 Pone[] =
1353 {
1354 0.352246649133679798341724373e+5,
1355 0.62758845247161281269005675e+5,
1356 0.313539631109159574238669888e+5,
1357 0.49854832060594338434500455e+4,
1358 0.2111529182853962382105718e+3,
1359 0.12571716929145341558495e+1
1360 },
1361 Qone[] =
1362 {
1363 0.352246649133679798068390431e+5,
1364 0.626943469593560511888833731e+5,
1365 0.312404063819041039923015703e+5,
1366 0.4930396490181088979386097e+4,
1367 0.2030775189134759322293574e+3,
1368 0.1e+1
1369 };
1370
1371 p=Pone[5];
1372 q=Qone[5];
1373 for (i=4; i >= 0; i--)
1374 {
1375 p=p*(8.0/x)*(8.0/x)+Pone[i];
1376 q=q*(8.0/x)*(8.0/x)+Qone[i];
1377 }
1378 return(p/q);
1379}
1380
1381#undef Q1
1382static MagickRealType Q1(MagickRealType x)
1383{
1384 MagickRealType
1385 p,
1386 q;
1387
cristybb503372010-05-27 20:51:26 +00001388 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +00001389 i;
1390
1391 static const double
1392 Pone[] =
1393 {
1394 0.3511751914303552822533318e+3,
1395 0.7210391804904475039280863e+3,
1396 0.4259873011654442389886993e+3,
1397 0.831898957673850827325226e+2,
1398 0.45681716295512267064405e+1,
1399 0.3532840052740123642735e-1
1400 },
1401 Qone[] =
1402 {
1403 0.74917374171809127714519505e+4,
1404 0.154141773392650970499848051e+5,
1405 0.91522317015169922705904727e+4,
1406 0.18111867005523513506724158e+4,
1407 0.1038187585462133728776636e+3,
1408 0.1e+1
1409 };
1410
1411 p=Pone[5];
1412 q=Qone[5];
1413 for (i=4; i >= 0; i--)
1414 {
1415 p=p*(8.0/x)*(8.0/x)+Pone[i];
1416 q=q*(8.0/x)*(8.0/x)+Qone[i];
1417 }
1418 return(p/q);
1419}
1420
1421static MagickRealType BesselOrderOne(MagickRealType x)
1422{
1423 MagickRealType
1424 p,
1425 q;
1426
1427 if (x == 0.0)
1428 return(0.0);
1429 p=x;
1430 if (x < 0.0)
1431 x=(-x);
1432 if (x < 8.0)
1433 return(p*J1(x));
1434 q=sqrt((double) (2.0/(MagickPI*x)))*(P1(x)*(1.0/sqrt(2.0)*(sin((double) x)-
1435 cos((double) x)))-8.0/x*Q1(x)*(-1.0/sqrt(2.0)*(sin((double) x)+
1436 cos((double) x))));
1437 if (p < 0.0)
1438 q=(-q);
1439 return(q);
1440}
1441
1442/*
1443%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1444% %
1445% %
1446% %
1447+ D e s t r o y R e s i z e F i l t e r %
1448% %
1449% %
1450% %
1451%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1452%
1453% DestroyResizeFilter() destroy the resize filter.
1454%
cristya2ffd7e2010-03-10 20:50:30 +00001455% The format of the DestroyResizeFilter method is:
cristy3ed852e2009-09-05 21:47:34 +00001456%
1457% ResizeFilter *DestroyResizeFilter(ResizeFilter *resize_filter)
1458%
1459% A description of each parameter follows:
1460%
1461% o resize_filter: the resize filter.
1462%
1463*/
1464MagickExport ResizeFilter *DestroyResizeFilter(ResizeFilter *resize_filter)
1465{
1466 assert(resize_filter != (ResizeFilter *) NULL);
1467 assert(resize_filter->signature == MagickSignature);
1468 resize_filter->signature=(~MagickSignature);
1469 resize_filter=(ResizeFilter *) RelinquishMagickMemory(resize_filter);
1470 return(resize_filter);
1471}
1472
1473/*
1474%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1475% %
1476% %
1477% %
1478+ G e t R e s i z e F i l t e r S u p p o r t %
1479% %
1480% %
1481% %
1482%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1483%
1484% GetResizeFilterSupport() return the current support window size for this
1485% filter. Note that this may have been enlarged by filter:blur factor.
1486%
1487% The format of the GetResizeFilterSupport method is:
1488%
1489% MagickRealType GetResizeFilterSupport(const ResizeFilter *resize_filter)
1490%
1491% A description of each parameter follows:
1492%
1493% o filter: Image filter to use.
1494%
1495*/
1496MagickExport MagickRealType GetResizeFilterSupport(
1497 const ResizeFilter *resize_filter)
1498{
1499 assert(resize_filter != (ResizeFilter *) NULL);
1500 assert(resize_filter->signature == MagickSignature);
1501 return(resize_filter->support*resize_filter->blur);
1502}
1503
1504/*
1505%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1506% %
1507% %
1508% %
1509+ G e t R e s i z e F i l t e r W e i g h t %
1510% %
1511% %
1512% %
1513%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1514%
1515% GetResizeFilterWeight evaluates the specified resize filter at the point x
1516% which usally lies between zero and the filters current 'support' and
1517% returns the weight of the filter function at that point.
1518%
1519% The format of the GetResizeFilterWeight method is:
1520%
1521% MagickRealType GetResizeFilterWeight(const ResizeFilter *resize_filter,
1522% const MagickRealType x)
1523%
1524% A description of each parameter follows:
1525%
1526% o filter: the filter type.
1527%
1528% o x: the point.
1529%
1530*/
1531MagickExport MagickRealType GetResizeFilterWeight(
1532 const ResizeFilter *resize_filter,const MagickRealType x)
1533{
1534 MagickRealType
cristyb8385862010-09-26 01:27:51 +00001535 scale,
1536 x_blur;
cristy3ed852e2009-09-05 21:47:34 +00001537
1538 /*
1539 Windowing function - scale the weighting filter by this amount.
1540 */
1541 assert(resize_filter != (ResizeFilter *) NULL);
1542 assert(resize_filter->signature == MagickSignature);
anthony55f12332010-09-10 01:13:02 +00001543 x_blur=fabs((double) x)/resize_filter->blur; /* X offset with blur scaling */
cristy3ed852e2009-09-05 21:47:34 +00001544 if ((resize_filter->window_support < MagickEpsilon) ||
1545 (resize_filter->window == Box))
anthony55f12332010-09-10 01:13:02 +00001546 scale=1.0; /* Point or Box Filter -- avoid division by zero */
cristy3ed852e2009-09-05 21:47:34 +00001547 else
1548 {
anthony55f12332010-09-10 01:13:02 +00001549 scale=resize_filter->scale;
1550 scale=resize_filter->window(x_blur*scale,resize_filter);
cristy3ed852e2009-09-05 21:47:34 +00001551 }
anthony55f12332010-09-10 01:13:02 +00001552 return(scale*resize_filter->filter(x_blur,resize_filter));
cristy3ed852e2009-09-05 21:47:34 +00001553}
1554
1555/*
1556%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1557% %
1558% %
1559% %
1560% M a g n i f y I m a g e %
1561% %
1562% %
1563% %
1564%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1565%
1566% MagnifyImage() is a convenience method that scales an image proportionally
1567% to twice its size.
1568%
1569% The format of the MagnifyImage method is:
1570%
1571% Image *MagnifyImage(const Image *image,ExceptionInfo *exception)
1572%
1573% A description of each parameter follows:
1574%
1575% o image: the image.
1576%
1577% o exception: return any errors or warnings in this structure.
1578%
1579*/
1580MagickExport Image *MagnifyImage(const Image *image,ExceptionInfo *exception)
1581{
1582 Image
1583 *magnify_image;
1584
1585 assert(image != (Image *) NULL);
1586 assert(image->signature == MagickSignature);
1587 if (image->debug != MagickFalse)
1588 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
1589 assert(exception != (ExceptionInfo *) NULL);
1590 assert(exception->signature == MagickSignature);
1591 magnify_image=ResizeImage(image,2*image->columns,2*image->rows,CubicFilter,
1592 1.0,exception);
1593 return(magnify_image);
1594}
1595
1596/*
1597%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1598% %
1599% %
1600% %
1601% M i n i f y I m a g e %
1602% %
1603% %
1604% %
1605%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1606%
1607% MinifyImage() is a convenience method that scales an image proportionally
1608% to half its size.
1609%
1610% The format of the MinifyImage method is:
1611%
1612% Image *MinifyImage(const Image *image,ExceptionInfo *exception)
1613%
1614% A description of each parameter follows:
1615%
1616% o image: the image.
1617%
1618% o exception: return any errors or warnings in this structure.
1619%
1620*/
1621MagickExport Image *MinifyImage(const Image *image,ExceptionInfo *exception)
1622{
1623 Image
1624 *minify_image;
1625
1626 assert(image != (Image *) NULL);
1627 assert(image->signature == MagickSignature);
1628 if (image->debug != MagickFalse)
1629 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
1630 assert(exception != (ExceptionInfo *) NULL);
1631 assert(exception->signature == MagickSignature);
1632 minify_image=ResizeImage(image,image->columns/2,image->rows/2,CubicFilter,
1633 1.0,exception);
1634 return(minify_image);
1635}
1636
1637/*
1638%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1639% %
1640% %
1641% %
1642% R e s a m p l e I m a g e %
1643% %
1644% %
1645% %
1646%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1647%
1648% ResampleImage() resize image in terms of its pixel size, so that when
1649% displayed at the given resolution it will be the same size in terms of
1650% real world units as the original image at the original resolution.
1651%
1652% The format of the ResampleImage method is:
1653%
1654% Image *ResampleImage(Image *image,const double x_resolution,
1655% const double y_resolution,const FilterTypes filter,const double blur,
1656% ExceptionInfo *exception)
1657%
1658% A description of each parameter follows:
1659%
1660% o image: the image to be resized to fit the given resolution.
1661%
1662% o x_resolution: the new image x resolution.
1663%
1664% o y_resolution: the new image y resolution.
1665%
1666% o filter: Image filter to use.
1667%
1668% o blur: the blur factor where > 1 is blurry, < 1 is sharp.
1669%
1670*/
1671MagickExport Image *ResampleImage(const Image *image,const double x_resolution,
1672 const double y_resolution,const FilterTypes filter,const double blur,
1673 ExceptionInfo *exception)
1674{
1675#define ResampleImageTag "Resample/Image"
1676
1677 Image
1678 *resample_image;
1679
cristybb503372010-05-27 20:51:26 +00001680 size_t
cristy3ed852e2009-09-05 21:47:34 +00001681 height,
1682 width;
1683
1684 /*
1685 Initialize sampled image attributes.
1686 */
1687 assert(image != (const Image *) NULL);
1688 assert(image->signature == MagickSignature);
1689 if (image->debug != MagickFalse)
1690 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
1691 assert(exception != (ExceptionInfo *) NULL);
1692 assert(exception->signature == MagickSignature);
cristy9af9b5d2010-08-15 17:04:28 +00001693 width=(size_t) (x_resolution*image->columns/(image->x_resolution == 0.0 ?
1694 72.0 : image->x_resolution)+0.5);
1695 height=(size_t) (y_resolution*image->rows/(image->y_resolution == 0.0 ?
1696 72.0 : image->y_resolution)+0.5);
cristy3ed852e2009-09-05 21:47:34 +00001697 resample_image=ResizeImage(image,width,height,filter,blur,exception);
1698 if (resample_image != (Image *) NULL)
1699 {
1700 resample_image->x_resolution=x_resolution;
1701 resample_image->y_resolution=y_resolution;
1702 }
1703 return(resample_image);
1704}
1705#if defined(MAGICKCORE_LQR_DELEGATE)
1706
1707/*
1708%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1709% %
1710% %
1711% %
1712% L i q u i d R e s c a l e I m a g e %
1713% %
1714% %
1715% %
1716%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1717%
1718% LiquidRescaleImage() rescales image with seam carving.
1719%
1720% The format of the LiquidRescaleImage method is:
1721%
1722% Image *LiquidRescaleImage(const Image *image,
cristybb503372010-05-27 20:51:26 +00001723% const size_t columns,const size_t rows,
cristy3ed852e2009-09-05 21:47:34 +00001724% const double delta_x,const double rigidity,ExceptionInfo *exception)
1725%
1726% A description of each parameter follows:
1727%
1728% o image: the image.
1729%
1730% o columns: the number of columns in the rescaled image.
1731%
1732% o rows: the number of rows in the rescaled image.
1733%
1734% o delta_x: maximum seam transversal step (0 means straight seams).
1735%
1736% o rigidity: introduce a bias for non-straight seams (typically 0).
1737%
1738% o exception: return any errors or warnings in this structure.
1739%
1740*/
cristy9af9b5d2010-08-15 17:04:28 +00001741MagickExport Image *LiquidRescaleImage(const Image *image,const size_t columns,
1742 const size_t rows,const double delta_x,const double rigidity,
1743 ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +00001744{
1745#define LiquidRescaleImageTag "Rescale/Image"
1746
cristyc5c6f662010-09-22 14:23:02 +00001747 CacheView
1748 *rescale_view;
1749
cristy3ed852e2009-09-05 21:47:34 +00001750 const char
1751 *map;
1752
1753 guchar
1754 *packet;
1755
1756 Image
1757 *rescale_image;
1758
1759 int
1760 x,
1761 y;
1762
1763 LqrCarver
1764 *carver;
1765
1766 LqrRetVal
1767 lqr_status;
1768
1769 MagickBooleanType
1770 status;
1771
1772 MagickPixelPacket
1773 pixel;
1774
1775 unsigned char
1776 *pixels;
1777
1778 /*
1779 Liquid rescale image.
1780 */
1781 assert(image != (const Image *) NULL);
1782 assert(image->signature == MagickSignature);
1783 if (image->debug != MagickFalse)
1784 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
1785 assert(exception != (ExceptionInfo *) NULL);
1786 assert(exception->signature == MagickSignature);
1787 if ((columns == 0) || (rows == 0))
1788 return((Image *) NULL);
1789 if ((columns == image->columns) && (rows == image->rows))
1790 return(CloneImage(image,0,0,MagickTrue,exception));
1791 if ((columns <= 2) || (rows <= 2))
cristyd1bb3bc2010-09-07 00:43:58 +00001792 return(ResizeImage(image,columns,rows,image->filter,image->blur,exception));
cristy3ed852e2009-09-05 21:47:34 +00001793 if ((columns >= (2*image->columns)) || (rows >= (2*image->rows)))
1794 {
1795 Image
1796 *resize_image;
1797
cristybb503372010-05-27 20:51:26 +00001798 size_t
cristy3ed852e2009-09-05 21:47:34 +00001799 height,
1800 width;
1801
1802 /*
1803 Honor liquid resize size limitations.
1804 */
1805 for (width=image->columns; columns >= (2*width-1); width*=2);
1806 for (height=image->rows; rows >= (2*height-1); height*=2);
1807 resize_image=ResizeImage(image,width,height,image->filter,image->blur,
1808 exception);
1809 if (resize_image == (Image *) NULL)
1810 return((Image *) NULL);
1811 rescale_image=LiquidRescaleImage(resize_image,columns,rows,delta_x,
1812 rigidity,exception);
1813 resize_image=DestroyImage(resize_image);
1814 return(rescale_image);
1815 }
1816 map="RGB";
1817 if (image->matte == MagickFalse)
1818 map="RGBA";
1819 if (image->colorspace == CMYKColorspace)
1820 {
1821 map="CMYK";
1822 if (image->matte == MagickFalse)
1823 map="CMYKA";
1824 }
1825 pixels=(unsigned char *) AcquireQuantumMemory(image->columns,image->rows*
1826 strlen(map)*sizeof(*pixels));
1827 if (pixels == (unsigned char *) NULL)
1828 return((Image *) NULL);
1829 status=ExportImagePixels(image,0,0,image->columns,image->rows,map,CharPixel,
1830 pixels,exception);
1831 if (status == MagickFalse)
1832 {
1833 pixels=(unsigned char *) RelinquishMagickMemory(pixels);
1834 ThrowImageException(ResourceLimitError,"MemoryAllocationFailed");
1835 }
1836 carver=lqr_carver_new(pixels,image->columns,image->rows,strlen(map));
1837 if (carver == (LqrCarver *) NULL)
1838 {
1839 pixels=(unsigned char *) RelinquishMagickMemory(pixels);
1840 ThrowImageException(ResourceLimitError,"MemoryAllocationFailed");
1841 }
1842 lqr_status=lqr_carver_init(carver,(int) delta_x,rigidity);
1843 lqr_status=lqr_carver_resize(carver,columns,rows);
1844 rescale_image=CloneImage(image,lqr_carver_get_width(carver),
1845 lqr_carver_get_height(carver),MagickTrue,exception);
1846 if (rescale_image == (Image *) NULL)
1847 {
1848 pixels=(unsigned char *) RelinquishMagickMemory(pixels);
1849 return((Image *) NULL);
1850 }
1851 if (SetImageStorageClass(rescale_image,DirectClass) == MagickFalse)
1852 {
1853 InheritException(exception,&rescale_image->exception);
1854 rescale_image=DestroyImage(rescale_image);
1855 return((Image *) NULL);
1856 }
1857 GetMagickPixelPacket(rescale_image,&pixel);
1858 (void) lqr_carver_scan_reset(carver);
cristyc5c6f662010-09-22 14:23:02 +00001859 rescale_view=AcquireCacheView(rescale_image);
cristy3ed852e2009-09-05 21:47:34 +00001860 while (lqr_carver_scan(carver,&x,&y,&packet) != 0)
1861 {
1862 register IndexPacket
cristyc47d1f82009-11-26 01:44:43 +00001863 *restrict rescale_indexes;
cristy3ed852e2009-09-05 21:47:34 +00001864
1865 register PixelPacket
cristyc47d1f82009-11-26 01:44:43 +00001866 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +00001867
anthony22aad252010-09-23 06:59:07 +00001868 q=QueueCacheViewAuthenticPixels(rescale_view,x,y,1,1,exception);
cristy3ed852e2009-09-05 21:47:34 +00001869 if (q == (PixelPacket *) NULL)
1870 break;
cristyc5c6f662010-09-22 14:23:02 +00001871 rescale_indexes=GetCacheViewAuthenticIndexQueue(rescale_view);
cristy3ed852e2009-09-05 21:47:34 +00001872 pixel.red=QuantumRange*(packet[0]/255.0);
1873 pixel.green=QuantumRange*(packet[1]/255.0);
1874 pixel.blue=QuantumRange*(packet[2]/255.0);
1875 if (image->colorspace != CMYKColorspace)
1876 {
1877 if (image->matte == MagickFalse)
1878 pixel.opacity=QuantumRange*(packet[3]/255.0);
1879 }
1880 else
1881 {
1882 pixel.index=QuantumRange*(packet[3]/255.0);
1883 if (image->matte == MagickFalse)
1884 pixel.opacity=QuantumRange*(packet[4]/255.0);
1885 }
1886 SetPixelPacket(rescale_image,&pixel,q,rescale_indexes);
cristyc5c6f662010-09-22 14:23:02 +00001887 if (SyncCacheViewAuthenticPixels(rescale_view,exception) == MagickFalse)
cristy3ed852e2009-09-05 21:47:34 +00001888 break;
1889 }
cristyc5c6f662010-09-22 14:23:02 +00001890 rescale_view=DestroyCacheView(rescale_view);
cristy3ed852e2009-09-05 21:47:34 +00001891 /*
1892 Relinquish resources.
1893 */
1894 lqr_carver_destroy(carver);
1895 return(rescale_image);
1896}
1897#else
1898MagickExport Image *LiquidRescaleImage(const Image *image,
cristy9af9b5d2010-08-15 17:04:28 +00001899 const size_t magick_unused(columns),const size_t magick_unused(rows),
1900 const double magick_unused(delta_x),const double magick_unused(rigidity),
1901 ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +00001902{
1903 assert(image != (const Image *) NULL);
1904 assert(image->signature == MagickSignature);
1905 if (image->debug != MagickFalse)
1906 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
1907 assert(exception != (ExceptionInfo *) NULL);
1908 assert(exception->signature == MagickSignature);
1909 (void) ThrowMagickException(exception,GetMagickModule(),MissingDelegateError,
1910 "DelegateLibrarySupportNotBuiltIn","`%s' (LQR)",image->filename);
1911 return((Image *) NULL);
1912}
1913#endif
1914
1915/*
1916%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1917% %
1918% %
1919% %
1920% R e s i z e I m a g e %
1921% %
1922% %
1923% %
1924%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1925%
1926% ResizeImage() scales an image to the desired dimensions, using the given
cristy5d824382010-09-06 14:00:17 +00001927% filter (see AcquireFilterInfo()).
cristy3ed852e2009-09-05 21:47:34 +00001928%
1929% If an undefined filter is given the filter defaults to Mitchell for a
1930% colormapped image, a image with a matte channel, or if the image is
1931% enlarged. Otherwise the filter defaults to a Lanczos.
1932%
1933% ResizeImage() was inspired by Paul Heckbert's "zoom" program.
1934%
1935% The format of the ResizeImage method is:
1936%
cristybb503372010-05-27 20:51:26 +00001937% Image *ResizeImage(Image *image,const size_t columns,
1938% const size_t rows,const FilterTypes filter,const double blur,
cristy3ed852e2009-09-05 21:47:34 +00001939% ExceptionInfo *exception)
1940%
1941% A description of each parameter follows:
1942%
1943% o image: the image.
1944%
1945% o columns: the number of columns in the scaled image.
1946%
1947% o rows: the number of rows in the scaled image.
1948%
1949% o filter: Image filter to use.
1950%
cristy9af9b5d2010-08-15 17:04:28 +00001951% o blur: the blur factor where > 1 is blurry, < 1 is sharp. Typically set
1952% this to 1.0.
cristy3ed852e2009-09-05 21:47:34 +00001953%
1954% o exception: return any errors or warnings in this structure.
1955%
1956*/
1957
1958typedef struct _ContributionInfo
1959{
1960 MagickRealType
1961 weight;
1962
cristybb503372010-05-27 20:51:26 +00001963 ssize_t
cristy3ed852e2009-09-05 21:47:34 +00001964 pixel;
1965} ContributionInfo;
1966
1967static ContributionInfo **DestroyContributionThreadSet(
1968 ContributionInfo **contribution)
1969{
cristybb503372010-05-27 20:51:26 +00001970 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +00001971 i;
1972
1973 assert(contribution != (ContributionInfo **) NULL);
cristybb503372010-05-27 20:51:26 +00001974 for (i=0; i < (ssize_t) GetOpenMPMaximumThreads(); i++)
cristy3ed852e2009-09-05 21:47:34 +00001975 if (contribution[i] != (ContributionInfo *) NULL)
1976 contribution[i]=(ContributionInfo *) RelinquishMagickMemory(
1977 contribution[i]);
cristyb41ee102010-10-04 16:46:15 +00001978 contribution=(ContributionInfo **) RelinquishMagickMemory(contribution);
cristy3ed852e2009-09-05 21:47:34 +00001979 return(contribution);
1980}
1981
1982static ContributionInfo **AcquireContributionThreadSet(const size_t count)
1983{
cristybb503372010-05-27 20:51:26 +00001984 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +00001985 i;
1986
1987 ContributionInfo
1988 **contribution;
1989
cristybb503372010-05-27 20:51:26 +00001990 size_t
cristy3ed852e2009-09-05 21:47:34 +00001991 number_threads;
1992
1993 number_threads=GetOpenMPMaximumThreads();
cristyb41ee102010-10-04 16:46:15 +00001994 contribution=(ContributionInfo **) AcquireQuantumMemory(number_threads,
cristy3ed852e2009-09-05 21:47:34 +00001995 sizeof(*contribution));
1996 if (contribution == (ContributionInfo **) NULL)
1997 return((ContributionInfo **) NULL);
1998 (void) ResetMagickMemory(contribution,0,number_threads*sizeof(*contribution));
cristybb503372010-05-27 20:51:26 +00001999 for (i=0; i < (ssize_t) number_threads; i++)
cristy3ed852e2009-09-05 21:47:34 +00002000 {
2001 contribution[i]=(ContributionInfo *) AcquireQuantumMemory(count,
2002 sizeof(**contribution));
2003 if (contribution[i] == (ContributionInfo *) NULL)
2004 return(DestroyContributionThreadSet(contribution));
2005 }
2006 return(contribution);
2007}
2008
2009static inline double MagickMax(const double x,const double y)
2010{
2011 if (x > y)
2012 return(x);
2013 return(y);
2014}
2015
2016static inline double MagickMin(const double x,const double y)
2017{
2018 if (x < y)
2019 return(x);
2020 return(y);
2021}
2022
2023static MagickBooleanType HorizontalFilter(const ResizeFilter *resize_filter,
2024 const Image *image,Image *resize_image,const MagickRealType x_factor,
cristy9af9b5d2010-08-15 17:04:28 +00002025 const MagickSizeType span,MagickOffsetType *offset,ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +00002026{
2027#define ResizeImageTag "Resize/Image"
2028
cristyfa112112010-01-04 17:48:07 +00002029 CacheView
2030 *image_view,
2031 *resize_view;
2032
cristy3ed852e2009-09-05 21:47:34 +00002033 ClassType
2034 storage_class;
2035
2036 ContributionInfo
cristyfa112112010-01-04 17:48:07 +00002037 **restrict contributions;
cristy3ed852e2009-09-05 21:47:34 +00002038
cristy3ed852e2009-09-05 21:47:34 +00002039 MagickBooleanType
2040 status;
2041
2042 MagickPixelPacket
2043 zero;
2044
2045 MagickRealType
2046 scale,
2047 support;
2048
cristy9af9b5d2010-08-15 17:04:28 +00002049 ssize_t
2050 x;
2051
cristy3ed852e2009-09-05 21:47:34 +00002052 /*
2053 Apply filter to resize horizontally from image to resize image.
2054 */
cristy5d824382010-09-06 14:00:17 +00002055 scale=MagickMax(1.0/x_factor+MagickEpsilon,1.0);
cristy3ed852e2009-09-05 21:47:34 +00002056 support=scale*GetResizeFilterSupport(resize_filter);
2057 storage_class=support > 0.5 ? DirectClass : image->storage_class;
2058 if (SetImageStorageClass(resize_image,storage_class) == MagickFalse)
2059 {
2060 InheritException(exception,&resize_image->exception);
2061 return(MagickFalse);
2062 }
2063 if (support < 0.5)
2064 {
2065 /*
nicolas07bac812010-09-19 18:47:02 +00002066 Support too small even for nearest neighbour: Reduce to point
2067 sampling.
cristy3ed852e2009-09-05 21:47:34 +00002068 */
2069 support=(MagickRealType) 0.5;
2070 scale=1.0;
2071 }
2072 contributions=AcquireContributionThreadSet((size_t) (2.0*support+3.0));
2073 if (contributions == (ContributionInfo **) NULL)
2074 {
2075 (void) ThrowMagickException(exception,GetMagickModule(),
2076 ResourceLimitError,"MemoryAllocationFailed","`%s'",image->filename);
2077 return(MagickFalse);
2078 }
2079 status=MagickTrue;
2080 scale=1.0/scale;
2081 (void) ResetMagickMemory(&zero,0,sizeof(zero));
2082 image_view=AcquireCacheView(image);
2083 resize_view=AcquireCacheView(resize_image);
cristyb5d5f722009-11-04 03:03:49 +00002084#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristy3ed852e2009-09-05 21:47:34 +00002085 #pragma omp parallel for shared(status)
2086#endif
cristybb503372010-05-27 20:51:26 +00002087 for (x=0; x < (ssize_t) resize_image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +00002088 {
cristy3ed852e2009-09-05 21:47:34 +00002089 MagickRealType
2090 center,
2091 density;
2092
2093 register const IndexPacket
cristyc47d1f82009-11-26 01:44:43 +00002094 *restrict indexes;
cristy3ed852e2009-09-05 21:47:34 +00002095
2096 register const PixelPacket
cristyc47d1f82009-11-26 01:44:43 +00002097 *restrict p;
cristy3ed852e2009-09-05 21:47:34 +00002098
cristy03dbbd22010-09-19 23:04:47 +00002099 register ContributionInfo
2100 *restrict contribution;
2101
cristy3ed852e2009-09-05 21:47:34 +00002102 register IndexPacket
cristyc47d1f82009-11-26 01:44:43 +00002103 *restrict resize_indexes;
cristy3ed852e2009-09-05 21:47:34 +00002104
cristy3ed852e2009-09-05 21:47:34 +00002105 register PixelPacket
cristyc47d1f82009-11-26 01:44:43 +00002106 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +00002107
cristy03dbbd22010-09-19 23:04:47 +00002108 register ssize_t
2109 y;
2110
cristy9af9b5d2010-08-15 17:04:28 +00002111 ssize_t
2112 n,
2113 start,
2114 stop;
2115
cristy3ed852e2009-09-05 21:47:34 +00002116 if (status == MagickFalse)
2117 continue;
2118 center=(MagickRealType) (x+0.5)/x_factor;
cristybb503372010-05-27 20:51:26 +00002119 start=(ssize_t) MagickMax(center-support+0.5,0.0);
2120 stop=(ssize_t) MagickMin(center+support+0.5,(double) image->columns);
cristy3ed852e2009-09-05 21:47:34 +00002121 density=0.0;
2122 contribution=contributions[GetOpenMPThreadId()];
2123 for (n=0; n < (stop-start); n++)
2124 {
2125 contribution[n].pixel=start+n;
2126 contribution[n].weight=GetResizeFilterWeight(resize_filter,scale*
2127 ((MagickRealType) (start+n)-center+0.5));
2128 density+=contribution[n].weight;
2129 }
2130 if ((density != 0.0) && (density != 1.0))
2131 {
cristybb503372010-05-27 20:51:26 +00002132 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +00002133 i;
2134
2135 /*
2136 Normalize.
2137 */
2138 density=1.0/density;
2139 for (i=0; i < n; i++)
2140 contribution[i].weight*=density;
2141 }
cristy9af9b5d2010-08-15 17:04:28 +00002142 p=GetCacheViewVirtualPixels(image_view,contribution[0].pixel,0,(size_t)
2143 (contribution[n-1].pixel-contribution[0].pixel+1),image->rows,exception);
cristy3ed852e2009-09-05 21:47:34 +00002144 q=QueueCacheViewAuthenticPixels(resize_view,x,0,1,resize_image->rows,
2145 exception);
2146 if ((p == (const PixelPacket *) NULL) || (q == (PixelPacket *) NULL))
2147 {
2148 status=MagickFalse;
2149 continue;
2150 }
2151 indexes=GetCacheViewVirtualIndexQueue(image_view);
2152 resize_indexes=GetCacheViewAuthenticIndexQueue(resize_view);
cristybb503372010-05-27 20:51:26 +00002153 for (y=0; y < (ssize_t) resize_image->rows; y++)
cristy3ed852e2009-09-05 21:47:34 +00002154 {
cristy3ed852e2009-09-05 21:47:34 +00002155 MagickPixelPacket
2156 pixel;
2157
2158 MagickRealType
2159 alpha;
2160
cristybb503372010-05-27 20:51:26 +00002161 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +00002162 i;
2163
cristy9af9b5d2010-08-15 17:04:28 +00002164 ssize_t
2165 j;
2166
cristy3ed852e2009-09-05 21:47:34 +00002167 pixel=zero;
2168 if (image->matte == MagickFalse)
2169 {
2170 for (i=0; i < n; i++)
2171 {
2172 j=y*(contribution[n-1].pixel-contribution[0].pixel+1)+
2173 (contribution[i].pixel-contribution[0].pixel);
2174 alpha=contribution[i].weight;
2175 pixel.red+=alpha*(p+j)->red;
2176 pixel.green+=alpha*(p+j)->green;
2177 pixel.blue+=alpha*(p+j)->blue;
2178 pixel.opacity+=alpha*(p+j)->opacity;
2179 }
cristyce70c172010-01-07 17:15:30 +00002180 SetRedPixelComponent(q,ClampRedPixelComponent(&pixel));
2181 SetGreenPixelComponent(q,ClampGreenPixelComponent(&pixel));
2182 SetBluePixelComponent(q,ClampBluePixelComponent(&pixel));
2183 SetOpacityPixelComponent(q,ClampOpacityPixelComponent(&pixel));
cristy3ed852e2009-09-05 21:47:34 +00002184 if ((image->colorspace == CMYKColorspace) &&
2185 (resize_image->colorspace == CMYKColorspace))
2186 {
2187 for (i=0; i < n; i++)
2188 {
2189 j=y*(contribution[n-1].pixel-contribution[0].pixel+1)+
2190 (contribution[i].pixel-contribution[0].pixel);
2191 alpha=contribution[i].weight;
2192 pixel.index+=alpha*indexes[j];
2193 }
cristyce70c172010-01-07 17:15:30 +00002194 resize_indexes[y]=(IndexPacket) ClampToQuantum(pixel.index);
cristy3ed852e2009-09-05 21:47:34 +00002195 }
2196 }
2197 else
2198 {
2199 MagickRealType
2200 gamma;
2201
2202 gamma=0.0;
2203 for (i=0; i < n; i++)
2204 {
2205 j=y*(contribution[n-1].pixel-contribution[0].pixel+1)+
2206 (contribution[i].pixel-contribution[0].pixel);
cristy46f08202010-01-10 04:04:21 +00002207 alpha=contribution[i].weight*QuantumScale*
2208 GetAlphaPixelComponent(p+j);
cristy3ed852e2009-09-05 21:47:34 +00002209 pixel.red+=alpha*(p+j)->red;
2210 pixel.green+=alpha*(p+j)->green;
2211 pixel.blue+=alpha*(p+j)->blue;
2212 pixel.opacity+=contribution[i].weight*(p+j)->opacity;
2213 gamma+=alpha;
2214 }
2215 gamma=1.0/(fabs((double) gamma) <= MagickEpsilon ? 1.0 : gamma);
cristyce70c172010-01-07 17:15:30 +00002216 q->red=ClampToQuantum(gamma*GetRedPixelComponent(&pixel));
2217 q->green=ClampToQuantum(gamma*GetGreenPixelComponent(&pixel));
2218 q->blue=ClampToQuantum(gamma*GetBluePixelComponent(&pixel));
2219 SetOpacityPixelComponent(q,ClampOpacityPixelComponent(&pixel));
cristy3ed852e2009-09-05 21:47:34 +00002220 if ((image->colorspace == CMYKColorspace) &&
2221 (resize_image->colorspace == CMYKColorspace))
2222 {
2223 for (i=0; i < n; i++)
2224 {
2225 j=y*(contribution[n-1].pixel-contribution[0].pixel+1)+
2226 (contribution[i].pixel-contribution[0].pixel);
cristy46f08202010-01-10 04:04:21 +00002227 alpha=contribution[i].weight*QuantumScale*
2228 GetAlphaPixelComponent(p+j);
cristyc197d1c2009-10-13 19:56:53 +00002229 pixel.index+=alpha*indexes[j];
cristy3ed852e2009-09-05 21:47:34 +00002230 }
cristyce70c172010-01-07 17:15:30 +00002231 resize_indexes[y]=(IndexPacket) ClampToQuantum(gamma*
2232 GetIndexPixelComponent(&pixel));
cristy3ed852e2009-09-05 21:47:34 +00002233 }
2234 }
2235 if ((resize_image->storage_class == PseudoClass) &&
2236 (image->storage_class == PseudoClass))
2237 {
cristybb503372010-05-27 20:51:26 +00002238 i=(ssize_t) (MagickMin(MagickMax(center,(double) start),(double) stop-
cristy3ed852e2009-09-05 21:47:34 +00002239 1.0)+0.5);
2240 j=y*(contribution[n-1].pixel-contribution[0].pixel+1)+
2241 (contribution[i-start].pixel-contribution[0].pixel);
2242 resize_indexes[y]=indexes[j];
2243 }
2244 q++;
2245 }
2246 if (SyncCacheViewAuthenticPixels(resize_view,exception) == MagickFalse)
2247 status=MagickFalse;
2248 if (image->progress_monitor != (MagickProgressMonitor) NULL)
2249 {
2250 MagickBooleanType
2251 proceed;
2252
cristyb5d5f722009-11-04 03:03:49 +00002253#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristy3ed852e2009-09-05 21:47:34 +00002254 #pragma omp critical (MagickCore_HorizontalFilter)
2255#endif
cristy9af9b5d2010-08-15 17:04:28 +00002256 proceed=SetImageProgress(image,ResizeImageTag,(*offset)++,span);
cristy3ed852e2009-09-05 21:47:34 +00002257 if (proceed == MagickFalse)
2258 status=MagickFalse;
2259 }
2260 }
2261 resize_view=DestroyCacheView(resize_view);
2262 image_view=DestroyCacheView(image_view);
2263 contributions=DestroyContributionThreadSet(contributions);
2264 return(status);
2265}
2266
2267static MagickBooleanType VerticalFilter(const ResizeFilter *resize_filter,
2268 const Image *image,Image *resize_image,const MagickRealType y_factor,
cristy9af9b5d2010-08-15 17:04:28 +00002269 const MagickSizeType span,MagickOffsetType *offset,ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +00002270{
cristyfa112112010-01-04 17:48:07 +00002271 CacheView
2272 *image_view,
2273 *resize_view;
2274
cristy3ed852e2009-09-05 21:47:34 +00002275 ClassType
2276 storage_class;
2277
2278 ContributionInfo
cristyfa112112010-01-04 17:48:07 +00002279 **restrict contributions;
cristy3ed852e2009-09-05 21:47:34 +00002280
cristy3ed852e2009-09-05 21:47:34 +00002281 MagickBooleanType
2282 status;
2283
2284 MagickPixelPacket
2285 zero;
2286
2287 MagickRealType
2288 scale,
2289 support;
2290
cristy9af9b5d2010-08-15 17:04:28 +00002291 ssize_t
2292 y;
2293
cristy3ed852e2009-09-05 21:47:34 +00002294 /*
cristy9af9b5d2010-08-15 17:04:28 +00002295 Apply filter to resize vertically from image to resize image.
cristy3ed852e2009-09-05 21:47:34 +00002296 */
cristy5d824382010-09-06 14:00:17 +00002297 scale=MagickMax(1.0/y_factor+MagickEpsilon,1.0);
cristy3ed852e2009-09-05 21:47:34 +00002298 support=scale*GetResizeFilterSupport(resize_filter);
2299 storage_class=support > 0.5 ? DirectClass : image->storage_class;
2300 if (SetImageStorageClass(resize_image,storage_class) == MagickFalse)
2301 {
2302 InheritException(exception,&resize_image->exception);
2303 return(MagickFalse);
2304 }
2305 if (support < 0.5)
2306 {
2307 /*
nicolas07bac812010-09-19 18:47:02 +00002308 Support too small even for nearest neighbour: Reduce to point
2309 sampling.
cristy3ed852e2009-09-05 21:47:34 +00002310 */
2311 support=(MagickRealType) 0.5;
2312 scale=1.0;
2313 }
2314 contributions=AcquireContributionThreadSet((size_t) (2.0*support+3.0));
2315 if (contributions == (ContributionInfo **) NULL)
2316 {
2317 (void) ThrowMagickException(exception,GetMagickModule(),
2318 ResourceLimitError,"MemoryAllocationFailed","`%s'",image->filename);
2319 return(MagickFalse);
2320 }
2321 status=MagickTrue;
2322 scale=1.0/scale;
2323 (void) ResetMagickMemory(&zero,0,sizeof(zero));
2324 image_view=AcquireCacheView(image);
2325 resize_view=AcquireCacheView(resize_image);
cristyb5d5f722009-11-04 03:03:49 +00002326#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristy3ed852e2009-09-05 21:47:34 +00002327 #pragma omp parallel for shared(status)
2328#endif
cristybb503372010-05-27 20:51:26 +00002329 for (y=0; y < (ssize_t) resize_image->rows; y++)
cristy3ed852e2009-09-05 21:47:34 +00002330 {
cristy3ed852e2009-09-05 21:47:34 +00002331 MagickRealType
2332 center,
2333 density;
2334
2335 register const IndexPacket
cristyc47d1f82009-11-26 01:44:43 +00002336 *restrict indexes;
cristy3ed852e2009-09-05 21:47:34 +00002337
2338 register const PixelPacket
cristyc47d1f82009-11-26 01:44:43 +00002339 *restrict p;
cristy3ed852e2009-09-05 21:47:34 +00002340
cristy03dbbd22010-09-19 23:04:47 +00002341 register ContributionInfo
2342 *restrict contribution;
2343
cristy3ed852e2009-09-05 21:47:34 +00002344 register IndexPacket
cristyc47d1f82009-11-26 01:44:43 +00002345 *restrict resize_indexes;
cristy3ed852e2009-09-05 21:47:34 +00002346
cristy9af9b5d2010-08-15 17:04:28 +00002347 register PixelPacket
2348 *restrict q;
2349
cristybb503372010-05-27 20:51:26 +00002350 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +00002351 x;
2352
cristy9af9b5d2010-08-15 17:04:28 +00002353 ssize_t
2354 n,
2355 start,
2356 stop;
cristy3ed852e2009-09-05 21:47:34 +00002357
2358 if (status == MagickFalse)
2359 continue;
cristy679e6962010-03-18 00:42:45 +00002360 center=(MagickRealType) (y+0.5)/y_factor;
cristybb503372010-05-27 20:51:26 +00002361 start=(ssize_t) MagickMax(center-support+0.5,0.0);
2362 stop=(ssize_t) MagickMin(center+support+0.5,(double) image->rows);
cristy3ed852e2009-09-05 21:47:34 +00002363 density=0.0;
2364 contribution=contributions[GetOpenMPThreadId()];
2365 for (n=0; n < (stop-start); n++)
2366 {
2367 contribution[n].pixel=start+n;
2368 contribution[n].weight=GetResizeFilterWeight(resize_filter,scale*
2369 ((MagickRealType) (start+n)-center+0.5));
2370 density+=contribution[n].weight;
2371 }
2372 if ((density != 0.0) && (density != 1.0))
2373 {
cristybb503372010-05-27 20:51:26 +00002374 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +00002375 i;
2376
2377 /*
2378 Normalize.
2379 */
2380 density=1.0/density;
2381 for (i=0; i < n; i++)
2382 contribution[i].weight*=density;
2383 }
2384 p=GetCacheViewVirtualPixels(image_view,0,contribution[0].pixel,
cristy9af9b5d2010-08-15 17:04:28 +00002385 image->columns,(size_t) (contribution[n-1].pixel-contribution[0].pixel+1),
2386 exception);
cristy3ed852e2009-09-05 21:47:34 +00002387 q=QueueCacheViewAuthenticPixels(resize_view,0,y,resize_image->columns,1,
2388 exception);
2389 if ((p == (const PixelPacket *) NULL) || (q == (PixelPacket *) NULL))
2390 {
2391 status=MagickFalse;
2392 continue;
2393 }
2394 indexes=GetCacheViewVirtualIndexQueue(image_view);
2395 resize_indexes=GetCacheViewAuthenticIndexQueue(resize_view);
cristybb503372010-05-27 20:51:26 +00002396 for (x=0; x < (ssize_t) resize_image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +00002397 {
cristy3ed852e2009-09-05 21:47:34 +00002398 MagickPixelPacket
2399 pixel;
2400
2401 MagickRealType
2402 alpha;
2403
cristybb503372010-05-27 20:51:26 +00002404 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +00002405 i;
2406
cristy9af9b5d2010-08-15 17:04:28 +00002407 ssize_t
2408 j;
2409
cristy3ed852e2009-09-05 21:47:34 +00002410 pixel=zero;
2411 if (image->matte == MagickFalse)
2412 {
2413 for (i=0; i < n; i++)
2414 {
cristybb503372010-05-27 20:51:26 +00002415 j=(ssize_t) ((contribution[i].pixel-contribution[0].pixel)*
cristy3ed852e2009-09-05 21:47:34 +00002416 image->columns+x);
2417 alpha=contribution[i].weight;
2418 pixel.red+=alpha*(p+j)->red;
2419 pixel.green+=alpha*(p+j)->green;
2420 pixel.blue+=alpha*(p+j)->blue;
2421 pixel.opacity+=alpha*(p+j)->opacity;
2422 }
cristyce70c172010-01-07 17:15:30 +00002423 SetRedPixelComponent(q,ClampRedPixelComponent(&pixel));
2424 SetGreenPixelComponent(q,ClampGreenPixelComponent(&pixel));
2425 SetBluePixelComponent(q,ClampBluePixelComponent(&pixel));
2426 SetOpacityPixelComponent(q,ClampOpacityPixelComponent(&pixel));
cristy3ed852e2009-09-05 21:47:34 +00002427 if ((image->colorspace == CMYKColorspace) &&
2428 (resize_image->colorspace == CMYKColorspace))
2429 {
2430 for (i=0; i < n; i++)
2431 {
cristybb503372010-05-27 20:51:26 +00002432 j=(ssize_t) ((contribution[i].pixel-contribution[0].pixel)*
cristy3ed852e2009-09-05 21:47:34 +00002433 image->columns+x);
2434 alpha=contribution[i].weight;
2435 pixel.index+=alpha*indexes[j];
2436 }
cristyce70c172010-01-07 17:15:30 +00002437 resize_indexes[x]=(IndexPacket) ClampToQuantum(pixel.index);
cristy3ed852e2009-09-05 21:47:34 +00002438 }
2439 }
2440 else
2441 {
2442 MagickRealType
2443 gamma;
2444
2445 gamma=0.0;
2446 for (i=0; i < n; i++)
2447 {
cristybb503372010-05-27 20:51:26 +00002448 j=(ssize_t) ((contribution[i].pixel-contribution[0].pixel)*
cristy3ed852e2009-09-05 21:47:34 +00002449 image->columns+x);
cristy46f08202010-01-10 04:04:21 +00002450 alpha=contribution[i].weight*QuantumScale*
2451 GetAlphaPixelComponent(p+j);
cristy3ed852e2009-09-05 21:47:34 +00002452 pixel.red+=alpha*(p+j)->red;
2453 pixel.green+=alpha*(p+j)->green;
2454 pixel.blue+=alpha*(p+j)->blue;
2455 pixel.opacity+=contribution[i].weight*(p+j)->opacity;
2456 gamma+=alpha;
2457 }
2458 gamma=1.0/(fabs((double) gamma) <= MagickEpsilon ? 1.0 : gamma);
cristyce70c172010-01-07 17:15:30 +00002459 q->red=ClampToQuantum(gamma*GetRedPixelComponent(&pixel));
2460 q->green=ClampToQuantum(gamma*GetGreenPixelComponent(&pixel));
2461 q->blue=ClampToQuantum(gamma*GetBluePixelComponent(&pixel));
2462 SetOpacityPixelComponent(q,ClampOpacityPixelComponent(&pixel));
cristy3ed852e2009-09-05 21:47:34 +00002463 if ((image->colorspace == CMYKColorspace) &&
2464 (resize_image->colorspace == CMYKColorspace))
2465 {
2466 for (i=0; i < n; i++)
2467 {
cristybb503372010-05-27 20:51:26 +00002468 j=(ssize_t) ((contribution[i].pixel-contribution[0].pixel)*
cristy3ed852e2009-09-05 21:47:34 +00002469 image->columns+x);
cristy46f08202010-01-10 04:04:21 +00002470 alpha=contribution[i].weight*QuantumScale*
2471 GetAlphaPixelComponent(p+j);
cristy3ed852e2009-09-05 21:47:34 +00002472 pixel.index+=alpha*indexes[j];
2473 }
cristyce70c172010-01-07 17:15:30 +00002474 resize_indexes[x]=(IndexPacket) ClampToQuantum(gamma*
2475 GetIndexPixelComponent(&pixel));
cristy3ed852e2009-09-05 21:47:34 +00002476 }
2477 }
2478 if ((resize_image->storage_class == PseudoClass) &&
2479 (image->storage_class == PseudoClass))
2480 {
cristybb503372010-05-27 20:51:26 +00002481 i=(ssize_t) (MagickMin(MagickMax(center,(double) start),(double) stop-
cristy3ed852e2009-09-05 21:47:34 +00002482 1.0)+0.5);
cristybb503372010-05-27 20:51:26 +00002483 j=(ssize_t) ((contribution[i-start].pixel-contribution[0].pixel)*
cristy3ed852e2009-09-05 21:47:34 +00002484 image->columns+x);
2485 resize_indexes[x]=indexes[j];
2486 }
2487 q++;
2488 }
2489 if (SyncCacheViewAuthenticPixels(resize_view,exception) == MagickFalse)
2490 status=MagickFalse;
2491 if (image->progress_monitor != (MagickProgressMonitor) NULL)
2492 {
2493 MagickBooleanType
2494 proceed;
2495
cristyb5d5f722009-11-04 03:03:49 +00002496#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristy3ed852e2009-09-05 21:47:34 +00002497 #pragma omp critical (MagickCore_VerticalFilter)
2498#endif
cristy9af9b5d2010-08-15 17:04:28 +00002499 proceed=SetImageProgress(image,ResizeImageTag,(*offset)++,span);
cristy3ed852e2009-09-05 21:47:34 +00002500 if (proceed == MagickFalse)
2501 status=MagickFalse;
2502 }
2503 }
2504 resize_view=DestroyCacheView(resize_view);
2505 image_view=DestroyCacheView(image_view);
2506 contributions=DestroyContributionThreadSet(contributions);
2507 return(status);
2508}
2509
cristybb503372010-05-27 20:51:26 +00002510MagickExport Image *ResizeImage(const Image *image,const size_t columns,
2511 const size_t rows,const FilterTypes filter,const double blur,
cristy3ed852e2009-09-05 21:47:34 +00002512 ExceptionInfo *exception)
2513{
2514#define WorkLoadFactor 0.265
2515
2516 FilterTypes
2517 filter_type;
2518
2519 Image
2520 *filter_image,
2521 *resize_image;
2522
cristy9af9b5d2010-08-15 17:04:28 +00002523 MagickOffsetType
2524 offset;
2525
cristy3ed852e2009-09-05 21:47:34 +00002526 MagickRealType
2527 x_factor,
2528 y_factor;
2529
2530 MagickSizeType
2531 span;
2532
2533 MagickStatusType
2534 status;
2535
2536 ResizeFilter
2537 *resize_filter;
2538
cristy3ed852e2009-09-05 21:47:34 +00002539 /*
2540 Acquire resize image.
2541 */
2542 assert(image != (Image *) NULL);
2543 assert(image->signature == MagickSignature);
2544 if (image->debug != MagickFalse)
2545 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
2546 assert(exception != (ExceptionInfo *) NULL);
2547 assert(exception->signature == MagickSignature);
2548 if ((columns == 0) || (rows == 0))
2549 ThrowImageException(ImageError,"NegativeOrZeroImageSize");
2550 if ((columns == image->columns) && (rows == image->rows) &&
2551 (filter == UndefinedFilter) && (blur == 1.0))
2552 return(CloneImage(image,0,0,MagickTrue,exception));
2553 resize_image=CloneImage(image,columns,rows,MagickTrue,exception);
2554 if (resize_image == (Image *) NULL)
2555 return(resize_image);
2556 /*
2557 Acquire resize filter.
2558 */
2559 x_factor=(MagickRealType) columns/(MagickRealType) image->columns;
2560 y_factor=(MagickRealType) rows/(MagickRealType) image->rows;
2561 if ((x_factor*y_factor) > WorkLoadFactor)
2562 filter_image=CloneImage(image,columns,image->rows,MagickTrue,exception);
2563 else
2564 filter_image=CloneImage(image,image->columns,rows,MagickTrue,exception);
2565 if (filter_image == (Image *) NULL)
2566 return(DestroyImage(resize_image));
2567 filter_type=LanczosFilter;
2568 if (filter != UndefinedFilter)
2569 filter_type=filter;
2570 else
2571 if ((x_factor == 1.0) && (y_factor == 1.0))
2572 filter_type=PointFilter;
2573 else
2574 if ((image->storage_class == PseudoClass) ||
2575 (image->matte != MagickFalse) || ((x_factor*y_factor) > 1.0))
2576 filter_type=MitchellFilter;
2577 resize_filter=AcquireResizeFilter(image,filter_type,blur,MagickFalse,
2578 exception);
2579 /*
2580 Resize image.
2581 */
cristy9af9b5d2010-08-15 17:04:28 +00002582 offset=0;
cristy3ed852e2009-09-05 21:47:34 +00002583 if ((x_factor*y_factor) > WorkLoadFactor)
2584 {
2585 span=(MagickSizeType) (filter_image->columns+rows);
2586 status=HorizontalFilter(resize_filter,image,filter_image,x_factor,span,
cristy9af9b5d2010-08-15 17:04:28 +00002587 &offset,exception);
cristy3ed852e2009-09-05 21:47:34 +00002588 status&=VerticalFilter(resize_filter,filter_image,resize_image,y_factor,
cristy9af9b5d2010-08-15 17:04:28 +00002589 span,&offset,exception);
cristy3ed852e2009-09-05 21:47:34 +00002590 }
2591 else
2592 {
2593 span=(MagickSizeType) (filter_image->rows+columns);
2594 status=VerticalFilter(resize_filter,image,filter_image,y_factor,span,
cristy9af9b5d2010-08-15 17:04:28 +00002595 &offset,exception);
cristy3ed852e2009-09-05 21:47:34 +00002596 status&=HorizontalFilter(resize_filter,filter_image,resize_image,x_factor,
cristy9af9b5d2010-08-15 17:04:28 +00002597 span,&offset,exception);
cristy3ed852e2009-09-05 21:47:34 +00002598 }
2599 /*
2600 Free resources.
2601 */
2602 filter_image=DestroyImage(filter_image);
2603 resize_filter=DestroyResizeFilter(resize_filter);
2604 if ((status == MagickFalse) || (resize_image == (Image *) NULL))
2605 return((Image *) NULL);
2606 resize_image->type=image->type;
2607 return(resize_image);
2608}
2609
2610/*
2611%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2612% %
2613% %
2614% %
2615% S a m p l e I m a g e %
2616% %
2617% %
2618% %
2619%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2620%
2621% SampleImage() scales an image to the desired dimensions with pixel
2622% sampling. Unlike other scaling methods, this method does not introduce
2623% any additional color into the scaled image.
2624%
2625% The format of the SampleImage method is:
2626%
cristybb503372010-05-27 20:51:26 +00002627% Image *SampleImage(const Image *image,const size_t columns,
2628% const size_t rows,ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +00002629%
2630% A description of each parameter follows:
2631%
2632% o image: the image.
2633%
2634% o columns: the number of columns in the sampled image.
2635%
2636% o rows: the number of rows in the sampled image.
2637%
2638% o exception: return any errors or warnings in this structure.
2639%
2640*/
cristybb503372010-05-27 20:51:26 +00002641MagickExport Image *SampleImage(const Image *image,const size_t columns,
2642 const size_t rows,ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +00002643{
2644#define SampleImageTag "Sample/Image"
2645
cristyc4c8d132010-01-07 01:58:38 +00002646 CacheView
2647 *image_view,
2648 *sample_view;
2649
cristy3ed852e2009-09-05 21:47:34 +00002650 Image
2651 *sample_image;
2652
cristy3ed852e2009-09-05 21:47:34 +00002653 MagickBooleanType
2654 status;
2655
cristy5f959472010-05-27 22:19:46 +00002656 MagickOffsetType
2657 progress;
2658
cristybb503372010-05-27 20:51:26 +00002659 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +00002660 x;
2661
cristy5f959472010-05-27 22:19:46 +00002662 ssize_t
2663 *x_offset,
2664 y;
2665
cristy3ed852e2009-09-05 21:47:34 +00002666 /*
2667 Initialize sampled image attributes.
2668 */
2669 assert(image != (const Image *) NULL);
2670 assert(image->signature == MagickSignature);
2671 if (image->debug != MagickFalse)
2672 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
2673 assert(exception != (ExceptionInfo *) NULL);
2674 assert(exception->signature == MagickSignature);
2675 if ((columns == 0) || (rows == 0))
2676 ThrowImageException(ImageError,"NegativeOrZeroImageSize");
2677 if ((columns == image->columns) && (rows == image->rows))
2678 return(CloneImage(image,0,0,MagickTrue,exception));
2679 sample_image=CloneImage(image,columns,rows,MagickTrue,exception);
2680 if (sample_image == (Image *) NULL)
2681 return((Image *) NULL);
2682 /*
2683 Allocate scan line buffer and column offset buffers.
2684 */
cristybb503372010-05-27 20:51:26 +00002685 x_offset=(ssize_t *) AcquireQuantumMemory((size_t) sample_image->columns,
cristy3ed852e2009-09-05 21:47:34 +00002686 sizeof(*x_offset));
cristybb503372010-05-27 20:51:26 +00002687 if (x_offset == (ssize_t *) NULL)
cristy3ed852e2009-09-05 21:47:34 +00002688 {
2689 sample_image=DestroyImage(sample_image);
2690 ThrowImageException(ResourceLimitError,"MemoryAllocationFailed");
2691 }
cristybb503372010-05-27 20:51:26 +00002692 for (x=0; x < (ssize_t) sample_image->columns; x++)
2693 x_offset[x]=(ssize_t) (((MagickRealType) x+0.5)*image->columns/
cristy3ed852e2009-09-05 21:47:34 +00002694 sample_image->columns);
2695 /*
2696 Sample each row.
2697 */
2698 status=MagickTrue;
2699 progress=0;
2700 image_view=AcquireCacheView(image);
2701 sample_view=AcquireCacheView(sample_image);
cristyb5d5f722009-11-04 03:03:49 +00002702#if defined(MAGICKCORE_OPENMP_SUPPORT)
2703 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +00002704#endif
cristybb503372010-05-27 20:51:26 +00002705 for (y=0; y < (ssize_t) sample_image->rows; y++)
cristy3ed852e2009-09-05 21:47:34 +00002706 {
cristy3ed852e2009-09-05 21:47:34 +00002707 register const IndexPacket
cristyc47d1f82009-11-26 01:44:43 +00002708 *restrict indexes;
cristy3ed852e2009-09-05 21:47:34 +00002709
2710 register const PixelPacket
cristyc47d1f82009-11-26 01:44:43 +00002711 *restrict p;
cristy3ed852e2009-09-05 21:47:34 +00002712
2713 register IndexPacket
cristyc47d1f82009-11-26 01:44:43 +00002714 *restrict sample_indexes;
cristy3ed852e2009-09-05 21:47:34 +00002715
cristy3ed852e2009-09-05 21:47:34 +00002716 register PixelPacket
cristyc47d1f82009-11-26 01:44:43 +00002717 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +00002718
cristy03dbbd22010-09-19 23:04:47 +00002719 register ssize_t
2720 x;
2721
cristy9af9b5d2010-08-15 17:04:28 +00002722 ssize_t
2723 y_offset;
2724
cristy3ed852e2009-09-05 21:47:34 +00002725 if (status == MagickFalse)
2726 continue;
cristy9af9b5d2010-08-15 17:04:28 +00002727 y_offset=(ssize_t) (((MagickRealType) y+0.5)*image->rows/
2728 sample_image->rows);
cristy3ed852e2009-09-05 21:47:34 +00002729 p=GetCacheViewVirtualPixels(image_view,0,y_offset,image->columns,1,
2730 exception);
2731 q=QueueCacheViewAuthenticPixels(sample_view,0,y,sample_image->columns,1,
2732 exception);
2733 if ((p == (const PixelPacket *) NULL) || (q == (PixelPacket *) NULL))
2734 {
2735 status=MagickFalse;
2736 continue;
2737 }
2738 indexes=GetCacheViewAuthenticIndexQueue(image_view);
2739 sample_indexes=GetCacheViewAuthenticIndexQueue(sample_view);
2740 /*
2741 Sample each column.
2742 */
cristybb503372010-05-27 20:51:26 +00002743 for (x=0; x < (ssize_t) sample_image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +00002744 *q++=p[x_offset[x]];
2745 if ((image->storage_class == PseudoClass) ||
2746 (image->colorspace == CMYKColorspace))
cristybb503372010-05-27 20:51:26 +00002747 for (x=0; x < (ssize_t) sample_image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +00002748 sample_indexes[x]=indexes[x_offset[x]];
2749 if (SyncCacheViewAuthenticPixels(sample_view,exception) == MagickFalse)
2750 status=MagickFalse;
2751 if (image->progress_monitor != (MagickProgressMonitor) NULL)
2752 {
2753 MagickBooleanType
2754 proceed;
2755
cristyb5d5f722009-11-04 03:03:49 +00002756#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristy3ed852e2009-09-05 21:47:34 +00002757 #pragma omp critical (MagickCore_SampleImage)
2758#endif
2759 proceed=SetImageProgress(image,SampleImageTag,progress++,image->rows);
2760 if (proceed == MagickFalse)
2761 status=MagickFalse;
2762 }
2763 }
2764 image_view=DestroyCacheView(image_view);
2765 sample_view=DestroyCacheView(sample_view);
cristybb503372010-05-27 20:51:26 +00002766 x_offset=(ssize_t *) RelinquishMagickMemory(x_offset);
cristy3ed852e2009-09-05 21:47:34 +00002767 sample_image->type=image->type;
2768 return(sample_image);
2769}
2770
2771/*
2772%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2773% %
2774% %
2775% %
2776% S c a l e I m a g e %
2777% %
2778% %
2779% %
2780%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2781%
2782% ScaleImage() changes the size of an image to the given dimensions.
2783%
2784% The format of the ScaleImage method is:
2785%
cristybb503372010-05-27 20:51:26 +00002786% Image *ScaleImage(const Image *image,const size_t columns,
2787% const size_t rows,ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +00002788%
2789% A description of each parameter follows:
2790%
2791% o image: the image.
2792%
2793% o columns: the number of columns in the scaled image.
2794%
2795% o rows: the number of rows in the scaled image.
2796%
2797% o exception: return any errors or warnings in this structure.
2798%
2799*/
cristybb503372010-05-27 20:51:26 +00002800MagickExport Image *ScaleImage(const Image *image,const size_t columns,
2801 const size_t rows,ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +00002802{
2803#define ScaleImageTag "Scale/Image"
2804
cristyed6cb232010-01-20 03:07:53 +00002805 CacheView
2806 *image_view,
2807 *scale_view;
2808
cristy3ed852e2009-09-05 21:47:34 +00002809 Image
2810 *scale_image;
2811
cristy3ed852e2009-09-05 21:47:34 +00002812 MagickBooleanType
2813 next_column,
2814 next_row,
2815 proceed;
2816
2817 MagickPixelPacket
2818 pixel,
2819 *scale_scanline,
2820 *scanline,
2821 *x_vector,
2822 *y_vector,
2823 zero;
2824
cristy3ed852e2009-09-05 21:47:34 +00002825 PointInfo
2826 scale,
2827 span;
2828
cristybb503372010-05-27 20:51:26 +00002829 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +00002830 i;
2831
cristy9af9b5d2010-08-15 17:04:28 +00002832 ssize_t
2833 number_rows,
2834 y;
2835
cristy3ed852e2009-09-05 21:47:34 +00002836 /*
2837 Initialize scaled image attributes.
2838 */
2839 assert(image != (const Image *) NULL);
2840 assert(image->signature == MagickSignature);
2841 if (image->debug != MagickFalse)
2842 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
2843 assert(exception != (ExceptionInfo *) NULL);
2844 assert(exception->signature == MagickSignature);
2845 if ((columns == 0) || (rows == 0))
2846 return((Image *) NULL);
2847 if ((columns == image->columns) && (rows == image->rows))
2848 return(CloneImage(image,0,0,MagickTrue,exception));
2849 scale_image=CloneImage(image,columns,rows,MagickTrue,exception);
2850 if (scale_image == (Image *) NULL)
2851 return((Image *) NULL);
2852 if (SetImageStorageClass(scale_image,DirectClass) == MagickFalse)
2853 {
2854 InheritException(exception,&scale_image->exception);
2855 scale_image=DestroyImage(scale_image);
2856 return((Image *) NULL);
2857 }
2858 /*
2859 Allocate memory.
2860 */
2861 x_vector=(MagickPixelPacket *) AcquireQuantumMemory((size_t) image->columns,
2862 sizeof(*x_vector));
2863 scanline=x_vector;
2864 if (image->rows != scale_image->rows)
2865 scanline=(MagickPixelPacket *) AcquireQuantumMemory((size_t) image->columns,
2866 sizeof(*scanline));
2867 scale_scanline=(MagickPixelPacket *) AcquireQuantumMemory((size_t)
2868 scale_image->columns,sizeof(*scale_scanline));
2869 y_vector=(MagickPixelPacket *) AcquireQuantumMemory((size_t) image->columns,
2870 sizeof(*y_vector));
2871 if ((scanline == (MagickPixelPacket *) NULL) ||
2872 (scale_scanline == (MagickPixelPacket *) NULL) ||
2873 (x_vector == (MagickPixelPacket *) NULL) ||
2874 (y_vector == (MagickPixelPacket *) NULL))
2875 {
2876 scale_image=DestroyImage(scale_image);
2877 ThrowImageException(ResourceLimitError,"MemoryAllocationFailed");
2878 }
2879 /*
2880 Scale image.
2881 */
2882 number_rows=0;
2883 next_row=MagickTrue;
2884 span.y=1.0;
2885 scale.y=(double) scale_image->rows/(double) image->rows;
2886 (void) ResetMagickMemory(y_vector,0,(size_t) image->columns*
2887 sizeof(*y_vector));
2888 GetMagickPixelPacket(image,&pixel);
2889 (void) ResetMagickMemory(&zero,0,sizeof(zero));
2890 i=0;
cristyed6cb232010-01-20 03:07:53 +00002891 image_view=AcquireCacheView(image);
2892 scale_view=AcquireCacheView(scale_image);
cristybb503372010-05-27 20:51:26 +00002893 for (y=0; y < (ssize_t) scale_image->rows; y++)
cristy3ed852e2009-09-05 21:47:34 +00002894 {
2895 register const IndexPacket
cristyc47d1f82009-11-26 01:44:43 +00002896 *restrict indexes;
cristy3ed852e2009-09-05 21:47:34 +00002897
2898 register const PixelPacket
cristyc47d1f82009-11-26 01:44:43 +00002899 *restrict p;
cristy3ed852e2009-09-05 21:47:34 +00002900
2901 register IndexPacket
cristyc47d1f82009-11-26 01:44:43 +00002902 *restrict scale_indexes;
cristy3ed852e2009-09-05 21:47:34 +00002903
cristy3ed852e2009-09-05 21:47:34 +00002904 register MagickPixelPacket
cristyc47d1f82009-11-26 01:44:43 +00002905 *restrict s,
2906 *restrict t;
cristy3ed852e2009-09-05 21:47:34 +00002907
2908 register PixelPacket
cristyc47d1f82009-11-26 01:44:43 +00002909 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +00002910
cristy9af9b5d2010-08-15 17:04:28 +00002911 register ssize_t
2912 x;
2913
cristyed6cb232010-01-20 03:07:53 +00002914 q=QueueCacheViewAuthenticPixels(scale_view,0,y,scale_image->columns,1,
2915 exception);
cristy3ed852e2009-09-05 21:47:34 +00002916 if (q == (PixelPacket *) NULL)
2917 break;
cristyba9a1e22010-11-10 23:14:28 +00002918 scale_indexes=GetCacheViewAuthenticIndexQueue(scale_view);
cristy3ed852e2009-09-05 21:47:34 +00002919 if (scale_image->rows == image->rows)
2920 {
2921 /*
2922 Read a new scanline.
2923 */
cristyed6cb232010-01-20 03:07:53 +00002924 p=GetCacheViewVirtualPixels(image_view,0,i++,image->columns,1,
2925 exception);
cristy3ed852e2009-09-05 21:47:34 +00002926 if (p == (const PixelPacket *) NULL)
2927 break;
cristyed6cb232010-01-20 03:07:53 +00002928 indexes=GetCacheViewVirtualIndexQueue(image_view);
cristybb503372010-05-27 20:51:26 +00002929 for (x=0; x < (ssize_t) image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +00002930 {
cristyce70c172010-01-07 17:15:30 +00002931 x_vector[x].red=(MagickRealType) GetRedPixelComponent(p);
2932 x_vector[x].green=(MagickRealType) GetGreenPixelComponent(p);
2933 x_vector[x].blue=(MagickRealType) GetBluePixelComponent(p);
cristy3ed852e2009-09-05 21:47:34 +00002934 if (image->matte != MagickFalse)
cristyce70c172010-01-07 17:15:30 +00002935 x_vector[x].opacity=(MagickRealType) GetOpacityPixelComponent(p);
cristy3ed852e2009-09-05 21:47:34 +00002936 if (indexes != (IndexPacket *) NULL)
2937 x_vector[x].index=(MagickRealType) indexes[x];
2938 p++;
2939 }
2940 }
2941 else
2942 {
2943 /*
2944 Scale Y direction.
2945 */
2946 while (scale.y < span.y)
2947 {
cristy9af9b5d2010-08-15 17:04:28 +00002948 if ((next_row != MagickFalse) &&
2949 (number_rows < (ssize_t) image->rows))
cristy3ed852e2009-09-05 21:47:34 +00002950 {
2951 /*
2952 Read a new scanline.
2953 */
cristyed6cb232010-01-20 03:07:53 +00002954 p=GetCacheViewVirtualPixels(image_view,0,i++,image->columns,1,
2955 exception);
cristy3ed852e2009-09-05 21:47:34 +00002956 if (p == (const PixelPacket *) NULL)
2957 break;
cristyed6cb232010-01-20 03:07:53 +00002958 indexes=GetCacheViewVirtualIndexQueue(image_view);
cristybb503372010-05-27 20:51:26 +00002959 for (x=0; x < (ssize_t) image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +00002960 {
cristyce70c172010-01-07 17:15:30 +00002961 x_vector[x].red=(MagickRealType) GetRedPixelComponent(p);
2962 x_vector[x].green=(MagickRealType) GetGreenPixelComponent(p);
2963 x_vector[x].blue=(MagickRealType) GetBluePixelComponent(p);
cristy3ed852e2009-09-05 21:47:34 +00002964 if (image->matte != MagickFalse)
cristyed6cb232010-01-20 03:07:53 +00002965 x_vector[x].opacity=(MagickRealType)
cristy9af9b5d2010-08-15 17:04:28 +00002966 GetOpacityPixelComponent(p);
cristy3ed852e2009-09-05 21:47:34 +00002967 if (indexes != (IndexPacket *) NULL)
2968 x_vector[x].index=(MagickRealType) indexes[x];
2969 p++;
2970 }
2971 number_rows++;
2972 }
cristybb503372010-05-27 20:51:26 +00002973 for (x=0; x < (ssize_t) image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +00002974 {
2975 y_vector[x].red+=scale.y*x_vector[x].red;
2976 y_vector[x].green+=scale.y*x_vector[x].green;
2977 y_vector[x].blue+=scale.y*x_vector[x].blue;
2978 if (scale_image->matte != MagickFalse)
2979 y_vector[x].opacity+=scale.y*x_vector[x].opacity;
2980 if (scale_indexes != (IndexPacket *) NULL)
2981 y_vector[x].index+=scale.y*x_vector[x].index;
2982 }
2983 span.y-=scale.y;
2984 scale.y=(double) scale_image->rows/(double) image->rows;
2985 next_row=MagickTrue;
2986 }
cristybb503372010-05-27 20:51:26 +00002987 if ((next_row != MagickFalse) && (number_rows < (ssize_t) image->rows))
cristy3ed852e2009-09-05 21:47:34 +00002988 {
2989 /*
2990 Read a new scanline.
2991 */
cristyed6cb232010-01-20 03:07:53 +00002992 p=GetCacheViewVirtualPixels(image_view,0,i++,image->columns,1,
2993 exception);
cristy3ed852e2009-09-05 21:47:34 +00002994 if (p == (const PixelPacket *) NULL)
2995 break;
cristyed6cb232010-01-20 03:07:53 +00002996 indexes=GetCacheViewVirtualIndexQueue(image_view);
cristybb503372010-05-27 20:51:26 +00002997 for (x=0; x < (ssize_t) image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +00002998 {
cristyce70c172010-01-07 17:15:30 +00002999 x_vector[x].red=(MagickRealType) GetRedPixelComponent(p);
3000 x_vector[x].green=(MagickRealType) GetGreenPixelComponent(p);
3001 x_vector[x].blue=(MagickRealType) GetBluePixelComponent(p);
cristy3ed852e2009-09-05 21:47:34 +00003002 if (image->matte != MagickFalse)
cristy2b726bd2010-01-11 01:05:39 +00003003 x_vector[x].opacity=(MagickRealType)
3004 GetOpacityPixelComponent(p);
cristy3ed852e2009-09-05 21:47:34 +00003005 if (indexes != (IndexPacket *) NULL)
3006 x_vector[x].index=(MagickRealType) indexes[x];
3007 p++;
3008 }
3009 number_rows++;
3010 next_row=MagickFalse;
3011 }
3012 s=scanline;
cristybb503372010-05-27 20:51:26 +00003013 for (x=0; x < (ssize_t) image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +00003014 {
3015 pixel.red=y_vector[x].red+span.y*x_vector[x].red;
3016 pixel.green=y_vector[x].green+span.y*x_vector[x].green;
3017 pixel.blue=y_vector[x].blue+span.y*x_vector[x].blue;
3018 if (image->matte != MagickFalse)
3019 pixel.opacity=y_vector[x].opacity+span.y*x_vector[x].opacity;
3020 if (scale_indexes != (IndexPacket *) NULL)
3021 pixel.index=y_vector[x].index+span.y*x_vector[x].index;
3022 s->red=pixel.red;
3023 s->green=pixel.green;
3024 s->blue=pixel.blue;
3025 if (scale_image->matte != MagickFalse)
3026 s->opacity=pixel.opacity;
3027 if (scale_indexes != (IndexPacket *) NULL)
3028 s->index=pixel.index;
3029 s++;
3030 y_vector[x]=zero;
3031 }
3032 scale.y-=span.y;
3033 if (scale.y <= 0)
3034 {
3035 scale.y=(double) scale_image->rows/(double) image->rows;
3036 next_row=MagickTrue;
3037 }
3038 span.y=1.0;
3039 }
3040 if (scale_image->columns == image->columns)
3041 {
3042 /*
3043 Transfer scanline to scaled image.
3044 */
3045 s=scanline;
cristybb503372010-05-27 20:51:26 +00003046 for (x=0; x < (ssize_t) scale_image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +00003047 {
cristyce70c172010-01-07 17:15:30 +00003048 q->red=ClampToQuantum(s->red);
3049 q->green=ClampToQuantum(s->green);
3050 q->blue=ClampToQuantum(s->blue);
cristy3ed852e2009-09-05 21:47:34 +00003051 if (scale_image->matte != MagickFalse)
cristyce70c172010-01-07 17:15:30 +00003052 q->opacity=ClampToQuantum(s->opacity);
cristy3ed852e2009-09-05 21:47:34 +00003053 if (scale_indexes != (IndexPacket *) NULL)
cristyce70c172010-01-07 17:15:30 +00003054 scale_indexes[x]=(IndexPacket) ClampToQuantum(s->index);
cristy3ed852e2009-09-05 21:47:34 +00003055 q++;
3056 s++;
3057 }
3058 }
3059 else
3060 {
3061 /*
3062 Scale X direction.
3063 */
3064 pixel=zero;
3065 next_column=MagickFalse;
3066 span.x=1.0;
3067 s=scanline;
3068 t=scale_scanline;
cristybb503372010-05-27 20:51:26 +00003069 for (x=0; x < (ssize_t) image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +00003070 {
3071 scale.x=(double) scale_image->columns/(double) image->columns;
3072 while (scale.x >= span.x)
3073 {
3074 if (next_column != MagickFalse)
3075 {
3076 pixel=zero;
3077 t++;
3078 }
3079 pixel.red+=span.x*s->red;
3080 pixel.green+=span.x*s->green;
3081 pixel.blue+=span.x*s->blue;
3082 if (image->matte != MagickFalse)
3083 pixel.opacity+=span.x*s->opacity;
3084 if (scale_indexes != (IndexPacket *) NULL)
3085 pixel.index+=span.x*s->index;
3086 t->red=pixel.red;
3087 t->green=pixel.green;
3088 t->blue=pixel.blue;
3089 if (scale_image->matte != MagickFalse)
3090 t->opacity=pixel.opacity;
3091 if (scale_indexes != (IndexPacket *) NULL)
3092 t->index=pixel.index;
3093 scale.x-=span.x;
3094 span.x=1.0;
3095 next_column=MagickTrue;
3096 }
3097 if (scale.x > 0)
3098 {
3099 if (next_column != MagickFalse)
3100 {
3101 pixel=zero;
3102 next_column=MagickFalse;
3103 t++;
3104 }
3105 pixel.red+=scale.x*s->red;
3106 pixel.green+=scale.x*s->green;
3107 pixel.blue+=scale.x*s->blue;
3108 if (scale_image->matte != MagickFalse)
3109 pixel.opacity+=scale.x*s->opacity;
3110 if (scale_indexes != (IndexPacket *) NULL)
3111 pixel.index+=scale.x*s->index;
3112 span.x-=scale.x;
3113 }
3114 s++;
3115 }
3116 if (span.x > 0)
3117 {
3118 s--;
3119 pixel.red+=span.x*s->red;
3120 pixel.green+=span.x*s->green;
3121 pixel.blue+=span.x*s->blue;
3122 if (scale_image->matte != MagickFalse)
3123 pixel.opacity+=span.x*s->opacity;
3124 if (scale_indexes != (IndexPacket *) NULL)
3125 pixel.index+=span.x*s->index;
3126 }
3127 if ((next_column == MagickFalse) &&
cristybb503372010-05-27 20:51:26 +00003128 ((ssize_t) (t-scale_scanline) < (ssize_t) scale_image->columns))
cristy3ed852e2009-09-05 21:47:34 +00003129 {
3130 t->red=pixel.red;
3131 t->green=pixel.green;
3132 t->blue=pixel.blue;
3133 if (scale_image->matte != MagickFalse)
3134 t->opacity=pixel.opacity;
3135 if (scale_indexes != (IndexPacket *) NULL)
3136 t->index=pixel.index;
3137 }
3138 /*
3139 Transfer scanline to scaled image.
3140 */
3141 t=scale_scanline;
cristybb503372010-05-27 20:51:26 +00003142 for (x=0; x < (ssize_t) scale_image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +00003143 {
cristyce70c172010-01-07 17:15:30 +00003144 q->red=ClampToQuantum(t->red);
3145 q->green=ClampToQuantum(t->green);
3146 q->blue=ClampToQuantum(t->blue);
cristy3ed852e2009-09-05 21:47:34 +00003147 if (scale_image->matte != MagickFalse)
cristyce70c172010-01-07 17:15:30 +00003148 q->opacity=ClampToQuantum(t->opacity);
cristy3ed852e2009-09-05 21:47:34 +00003149 if (scale_indexes != (IndexPacket *) NULL)
cristyce70c172010-01-07 17:15:30 +00003150 scale_indexes[x]=(IndexPacket) ClampToQuantum(t->index);
cristy3ed852e2009-09-05 21:47:34 +00003151 t++;
3152 q++;
3153 }
3154 }
cristyed6cb232010-01-20 03:07:53 +00003155 if (SyncCacheViewAuthenticPixels(scale_view,exception) == MagickFalse)
cristy3ed852e2009-09-05 21:47:34 +00003156 break;
cristy96b16132010-08-29 17:19:52 +00003157 proceed=SetImageProgress(image,ScaleImageTag,(MagickOffsetType) y,
3158 image->rows);
cristy3ed852e2009-09-05 21:47:34 +00003159 if (proceed == MagickFalse)
3160 break;
3161 }
cristyed6cb232010-01-20 03:07:53 +00003162 scale_view=DestroyCacheView(scale_view);
3163 image_view=DestroyCacheView(image_view);
cristy3ed852e2009-09-05 21:47:34 +00003164 /*
3165 Free allocated memory.
3166 */
3167 y_vector=(MagickPixelPacket *) RelinquishMagickMemory(y_vector);
3168 scale_scanline=(MagickPixelPacket *) RelinquishMagickMemory(scale_scanline);
3169 if (scale_image->rows != image->rows)
3170 scanline=(MagickPixelPacket *) RelinquishMagickMemory(scanline);
3171 x_vector=(MagickPixelPacket *) RelinquishMagickMemory(x_vector);
3172 scale_image->type=image->type;
3173 return(scale_image);
3174}
3175
anthony02b4cb42010-10-10 04:54:35 +00003176#if 0
3177 THIS IS NOT USED -- to be removed
cristy3ed852e2009-09-05 21:47:34 +00003178/*
3179%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3180% %
3181% %
3182% %
3183+ S e t R e s i z e F i l t e r S u p p o r t %
3184% %
3185% %
3186% %
3187%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3188%
3189% SetResizeFilterSupport() specifies which IR filter to use to window
3190%
3191% The format of the SetResizeFilterSupport method is:
3192%
3193% void SetResizeFilterSupport(ResizeFilter *resize_filter,
3194% const MagickRealType support)
3195%
3196% A description of each parameter follows:
3197%
3198% o resize_filter: the resize filter.
3199%
3200% o support: the filter spport radius.
3201%
3202*/
3203MagickExport void SetResizeFilterSupport(ResizeFilter *resize_filter,
3204 const MagickRealType support)
3205{
3206 assert(resize_filter != (ResizeFilter *) NULL);
3207 assert(resize_filter->signature == MagickSignature);
3208 resize_filter->support=support;
3209}
anthony02b4cb42010-10-10 04:54:35 +00003210#endif
cristy3ed852e2009-09-05 21:47:34 +00003211
3212/*
3213%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3214% %
3215% %
3216% %
3217% T h u m b n a i l I m a g e %
3218% %
3219% %
3220% %
3221%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3222%
3223% ThumbnailImage() changes the size of an image to the given dimensions and
3224% removes any associated profiles. The goal is to produce small low cost
3225% thumbnail images suited for display on the Web.
3226%
3227% The format of the ThumbnailImage method is:
3228%
cristybb503372010-05-27 20:51:26 +00003229% Image *ThumbnailImage(const Image *image,const size_t columns,
3230% const size_t rows,ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +00003231%
3232% A description of each parameter follows:
3233%
3234% o image: the image.
3235%
3236% o columns: the number of columns in the scaled image.
3237%
3238% o rows: the number of rows in the scaled image.
3239%
3240% o exception: return any errors or warnings in this structure.
3241%
3242*/
cristy9af9b5d2010-08-15 17:04:28 +00003243MagickExport Image *ThumbnailImage(const Image *image,const size_t columns,
3244 const size_t rows,ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +00003245{
3246#define SampleFactor 5
3247
3248 char
3249 value[MaxTextExtent];
3250
3251 const char
3252 *name;
3253
3254 Image
3255 *thumbnail_image;
3256
3257 MagickRealType
3258 x_factor,
3259 y_factor;
3260
cristybb503372010-05-27 20:51:26 +00003261 size_t
cristy3ed852e2009-09-05 21:47:34 +00003262 version;
3263
cristy9af9b5d2010-08-15 17:04:28 +00003264 struct stat
3265 attributes;
3266
cristy3ed852e2009-09-05 21:47:34 +00003267 assert(image != (Image *) NULL);
3268 assert(image->signature == MagickSignature);
3269 if (image->debug != MagickFalse)
3270 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
3271 assert(exception != (ExceptionInfo *) NULL);
3272 assert(exception->signature == MagickSignature);
3273 x_factor=(MagickRealType) columns/(MagickRealType) image->columns;
3274 y_factor=(MagickRealType) rows/(MagickRealType) image->rows;
3275 if ((x_factor*y_factor) > 0.1)
cristyd1bb3bc2010-09-07 00:43:58 +00003276 thumbnail_image=ResizeImage(image,columns,rows,image->filter,image->blur,
3277 exception);
cristy3ed852e2009-09-05 21:47:34 +00003278 else
3279 if (((SampleFactor*columns) < 128) || ((SampleFactor*rows) < 128))
cristyd1bb3bc2010-09-07 00:43:58 +00003280 thumbnail_image=ResizeImage(image,columns,rows,image->filter,
3281 image->blur,exception);
cristy3ed852e2009-09-05 21:47:34 +00003282 else
3283 {
3284 Image
3285 *sample_image;
3286
3287 sample_image=SampleImage(image,SampleFactor*columns,SampleFactor*rows,
3288 exception);
3289 if (sample_image == (Image *) NULL)
3290 return((Image *) NULL);
cristyd1bb3bc2010-09-07 00:43:58 +00003291 thumbnail_image=ResizeImage(sample_image,columns,rows,image->filter,
3292 image->blur,exception);
cristy3ed852e2009-09-05 21:47:34 +00003293 sample_image=DestroyImage(sample_image);
3294 }
3295 if (thumbnail_image == (Image *) NULL)
3296 return(thumbnail_image);
3297 (void) ParseAbsoluteGeometry("0x0+0+0",&thumbnail_image->page);
3298 if (thumbnail_image->matte == MagickFalse)
3299 (void) SetImageAlphaChannel(thumbnail_image,OpaqueAlphaChannel);
3300 thumbnail_image->depth=8;
3301 thumbnail_image->interlace=NoInterlace;
3302 /*
3303 Strip all profiles except color profiles.
3304 */
3305 ResetImageProfileIterator(thumbnail_image);
3306 for (name=GetNextImageProfile(thumbnail_image); name != (const char *) NULL; )
3307 {
3308 if ((LocaleCompare(name,"icc") != 0) && (LocaleCompare(name,"icm") != 0))
3309 {
cristy2b726bd2010-01-11 01:05:39 +00003310 (void) DeleteImageProfile(thumbnail_image,name);
cristy3ed852e2009-09-05 21:47:34 +00003311 ResetImageProfileIterator(thumbnail_image);
3312 }
3313 name=GetNextImageProfile(thumbnail_image);
3314 }
3315 (void) DeleteImageProperty(thumbnail_image,"comment");
3316 (void) CopyMagickString(value,image->magick_filename,MaxTextExtent);
cristy7b63d662009-11-13 01:58:56 +00003317 if (strstr(image->magick_filename,"//") == (char *) NULL)
3318 (void) FormatMagickString(value,MaxTextExtent,"file://%s",
cristy3ed852e2009-09-05 21:47:34 +00003319 image->magick_filename);
3320 (void) SetImageProperty(thumbnail_image,"Thumb::URI",value);
3321 (void) CopyMagickString(value,image->magick_filename,MaxTextExtent);
3322 if (GetPathAttributes(image->filename,&attributes) != MagickFalse)
3323 {
cristye8c25f92010-06-03 00:53:06 +00003324 (void) FormatMagickString(value,MaxTextExtent,"%.20g",(double)
cristy3ed852e2009-09-05 21:47:34 +00003325 attributes.st_mtime);
3326 (void) SetImageProperty(thumbnail_image,"Thumb::MTime",value);
3327 }
cristye8c25f92010-06-03 00:53:06 +00003328 (void) FormatMagickString(value,MaxTextExtent,"%.20g",(double)
cristy3ed852e2009-09-05 21:47:34 +00003329 attributes.st_mtime);
cristyb9080c92009-12-01 20:13:26 +00003330 (void) FormatMagickSize(GetBlobSize(image),MagickFalse,value);
cristy2ce15c92010-03-12 14:03:41 +00003331 (void) ConcatenateMagickString(value,"B",MaxTextExtent);
cristy3ed852e2009-09-05 21:47:34 +00003332 (void) SetImageProperty(thumbnail_image,"Thumb::Size",value);
3333 (void) FormatMagickString(value,MaxTextExtent,"image/%s",image->magick);
3334 LocaleLower(value);
3335 (void) SetImageProperty(thumbnail_image,"Thumb::Mimetype",value);
3336 (void) SetImageProperty(thumbnail_image,"software",
3337 GetMagickVersion(&version));
cristye8c25f92010-06-03 00:53:06 +00003338 (void) FormatMagickString(value,MaxTextExtent,"%.20g",(double)
3339 image->magick_columns);
cristy3ed852e2009-09-05 21:47:34 +00003340 (void) SetImageProperty(thumbnail_image,"Thumb::Image::Width",value);
cristye8c25f92010-06-03 00:53:06 +00003341 (void) FormatMagickString(value,MaxTextExtent,"%.20g",(double)
cristyf2faecf2010-05-28 19:19:36 +00003342 image->magick_rows);
cristy3ed852e2009-09-05 21:47:34 +00003343 (void) SetImageProperty(thumbnail_image,"Thumb::Image::height",value);
cristye8c25f92010-06-03 00:53:06 +00003344 (void) FormatMagickString(value,MaxTextExtent,"%.20g",(double)
3345 GetImageListLength(image));
cristy3ed852e2009-09-05 21:47:34 +00003346 (void) SetImageProperty(thumbnail_image,"Thumb::Document::Pages",value);
3347 return(thumbnail_image);
3348}