blob: ede77d9b73276df08a374a22b1b931a46ce19ba8 [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) */
anthonyf5e76ef2010-10-12 01:22:01 +000088 coeff[8]; /* cubic coefficents for smooth Cubic 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:
cristyf59a8922010-02-28 19:51:23 +0000198 Mitchell-Netravali B=1/3 C=1/3 Qualitively ideal Cubic Filter
199 Catmull-Rom B= 0 C=1/2 Cublic Interpolation Function
200 Cubic B-Spline B= 1 C= 0 Spline Approximation of Gaussian
201 Hermite B= 0 C= 0 Quadratic Spline (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:
cristy3ed852e2009-09-05 21:47:34 +0000209 P0 = ( 6 - 2*B )/6
210 P1 = 0
211 P2 = (-18 +12*B + 6*C )/6
212 P3 = ( 12 - 9*B - 6*C )/6
213 Q0 = ( 8*B +24*C )/6
214 Q1 = ( -12*B -48*C )/6
215 Q2 = ( 6*B +30*C )/6
216 Q3 = ( - 1*B - 6*C )/6
217
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
221 Q0 + Q1*x + Q2*x^2 + Q3*x^3 1 <= x <= 2
222
cristy33b1c162010-01-23 22:51:51 +0000223 which ensures function is continuous in value and derivative (slope).
nicolasc6bac3b2010-10-24 18:10:45 +0000224
225 (This implies that coeff[1] is always zero. It is omitted in the
226 computation below.)
cristy3ed852e2009-09-05 21:47:34 +0000227 */
228 if (x < 1.0)
nicolasc6bac3b2010-10-24 18:10:45 +0000229 return(resize_filter->coeff[0]+x*(x*
anthonyf5e76ef2010-10-12 01:22:01 +0000230 (resize_filter->coeff[2]+x*resize_filter->coeff[3])));
cristy3ed852e2009-09-05 21:47:34 +0000231 if (x < 2.0)
anthonyf5e76ef2010-10-12 01:22:01 +0000232 return(resize_filter->coeff[4]+x*(resize_filter->coeff[5]+x*
233 (resize_filter->coeff[6]+x*resize_filter->coeff[7])));
cristy3ed852e2009-09-05 21:47:34 +0000234 return(0.0);
235}
236
237static MagickRealType Gaussian(const MagickRealType x,
anthonyf5e76ef2010-10-12 01:22:01 +0000238 const ResizeFilter *resize_filter)
cristy3ed852e2009-09-05 21:47:34 +0000239{
cristy560d8182010-09-08 22:36:25 +0000240 /*
anthonyf5e76ef2010-10-12 01:22:01 +0000241 Gaussian with a fixed sigma = 1/2
242
243 Gaussian Formula...
244 exp( -(x^2)/((2.0*sigma^2) ) / sqrt(2*PI*sigma^2)))
245 The constants are pre-calculated...
246 exp( -coeff[0]*(x^2)) ) * coeff[1]
247 However the multiplier coefficent is not needed and not used.
248
249 This separates the gaussian 'sigma' value from the 'blur/support' settings
250 allows for its use in special 'small sigma' gaussians, without the filter
251 'missing' pixels when blur and thus support becomes too small.
cristy560d8182010-09-08 22:36:25 +0000252 */
anthonyf5e76ef2010-10-12 01:22:01 +0000253 return(exp((double)(-resize_filter->coeff[0]*x*x))); }
cristy3ed852e2009-09-05 21:47:34 +0000254
255static MagickRealType Hanning(const MagickRealType x,
256 const ResizeFilter *magick_unused(resize_filter))
257{
258 /*
nicolas40477452010-09-27 23:42:08 +0000259 Cosine window function:
260 .5+.5cos(pi x).
cristy3ed852e2009-09-05 21:47:34 +0000261 */
cristyc5c6f662010-09-22 14:23:02 +0000262 const MagickRealType cospix = cos((double) (MagickPIL*x));
cristy560d8182010-09-08 22:36:25 +0000263 return(0.5+0.5*cospix);
cristy3ed852e2009-09-05 21:47:34 +0000264}
265
266static MagickRealType Hamming(const MagickRealType x,
267 const ResizeFilter *magick_unused(resize_filter))
268{
269 /*
nicolas40477452010-09-27 23:42:08 +0000270 Offset cosine window function:
271 .54 + .46 cos(pi x).
cristy3ed852e2009-09-05 21:47:34 +0000272 */
cristyc5c6f662010-09-22 14:23:02 +0000273 const MagickRealType cospix = cos((double) (MagickPIL*x));
cristy560d8182010-09-08 22:36:25 +0000274 return(0.54+0.46*cospix);
cristy3ed852e2009-09-05 21:47:34 +0000275}
276
277static MagickRealType Kaiser(const MagickRealType x,
278 const ResizeFilter *magick_unused(resize_filter))
279{
280#define Alpha 6.5
281#define I0A (1.0/I0(Alpha))
282
283 /*
nicolas07bac812010-09-19 18:47:02 +0000284 Kaiser Windowing Function (bessel windowing): Alpha is a free
285 value from 5 to 8 (currently hardcoded to 6.5).
286 Future: make alpha the IOA pre-calculation, an 'expert' setting.
cristy3ed852e2009-09-05 21:47:34 +0000287 */
288 return(I0A*I0(Alpha*sqrt((double) (1.0-x*x))));
289}
290
291static MagickRealType Lagrange(const MagickRealType x,
292 const ResizeFilter *resize_filter)
293{
cristy3ed852e2009-09-05 21:47:34 +0000294 MagickRealType
295 value;
296
cristybb503372010-05-27 20:51:26 +0000297 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +0000298 i;
299
cristy9af9b5d2010-08-15 17:04:28 +0000300 ssize_t
301 n,
302 order;
303
cristy3ed852e2009-09-05 21:47:34 +0000304 /*
nicolas07bac812010-09-19 18:47:02 +0000305 Lagrange piecewise polynomial fit of sinc: N is the 'order' of the
306 lagrange function and depends on the overall support window size
307 of the filter. That is: for a support of 2, it gives a lagrange-4
308 (piecewise cubic function).
cristy3ed852e2009-09-05 21:47:34 +0000309
nicolas07bac812010-09-19 18:47:02 +0000310 "n" identifies the piece of the piecewise polynomial.
cristy3ed852e2009-09-05 21:47:34 +0000311
nicolas07bac812010-09-19 18:47:02 +0000312 See Survey: Interpolation Methods, IEEE Transactions on Medical
313 Imaging, Vol 18, No 11, November 1999, p1049-1075, -- Equation 27
314 on p1064.
cristy3ed852e2009-09-05 21:47:34 +0000315 */
316 if (x > resize_filter->support)
317 return(0.0);
cristybb503372010-05-27 20:51:26 +0000318 order=(ssize_t) (2.0*resize_filter->window_support); /* number of pieces */
anthonyc2d07db2010-09-15 23:47:40 +0000319 /*n=(ssize_t)((1.0*order)/2.0+x); -- which piece does x belong to */
320 n = (ssize_t)(resize_filter->window_support + x);
cristy3ed852e2009-09-05 21:47:34 +0000321 value=1.0f;
322 for (i=0; i < order; i++)
323 if (i != n)
324 value*=(n-i-x)/(n-i);
325 return(value);
326}
327
328static MagickRealType Quadratic(const MagickRealType x,
329 const ResizeFilter *magick_unused(resize_filter))
330{
331 /*
332 2rd order (quadratic) B-Spline approximation of Gaussian.
333 */
334 if (x < 0.5)
335 return(0.75-x*x);
336 if (x < 1.5)
337 return(0.5*(x-1.5)*(x-1.5));
338 return(0.0);
339}
340
anthony07a3f7f2010-09-16 03:03:11 +0000341static MagickRealType Sinc(const MagickRealType x,
cristy3ed852e2009-09-05 21:47:34 +0000342 const ResizeFilter *magick_unused(resize_filter))
343{
anthony720660f2010-09-07 10:05:14 +0000344 /*
nicolas40477452010-09-27 23:42:08 +0000345 Scaled sinc(x) function using a trig call:
nicolas07bac812010-09-19 18:47:02 +0000346 sinc(x) == sin(pi x)/(pi x).
anthony720660f2010-09-07 10:05:14 +0000347 */
anthony2d9b8b52010-09-14 08:31:07 +0000348 if (x != 0.0)
cristy560d8182010-09-08 22:36:25 +0000349 {
cristyc5c6f662010-09-22 14:23:02 +0000350 const MagickRealType pix = (MagickRealType) (MagickPIL*x);
nicolas07bac812010-09-19 18:47:02 +0000351 return(sin((double) pix)/pix);
cristy560d8182010-09-08 22:36:25 +0000352 }
nicolas2ffd3b22010-09-24 20:27:31 +0000353 return((MagickRealType) 1.0);
anthony720660f2010-09-07 10:05:14 +0000354}
355
anthonyba5a7c32010-09-15 02:42:25 +0000356static MagickRealType SincFast(const MagickRealType x,
anthony720660f2010-09-07 10:05:14 +0000357 const ResizeFilter *magick_unused(resize_filter))
358{
cristy560d8182010-09-08 22:36:25 +0000359 /*
360 Approximations of the sinc function sin(pi x)/(pi x) over the
361 interval [-4,4] constructed by Nicolas Robidoux and Chantal
362 Racette with funding from the Natural Sciences and Engineering
363 Research Council of Canada.
nicolas07bac812010-09-19 18:47:02 +0000364
365 Although the approximations are polynomials (for low order of
366 approximation) and quotients of polynomials (for higher order of
367 approximation) and consequently are similar in form to Taylor
368 polynomials/Pade approximants, the approximations are computed
369 with a completely different technique.
370
371 Summary: These approximations are "the best" in terms of bang
372 (accuracy) for the buck (flops). More specifically: Among the
373 polynomial quotients that can be computed using a fixed number of
374 flops (with a given "+ - * / budget"), the chosen polynomial
375 quotient is the one closest to the approximated function with
376 respect to maximum absolute relative error over the given
377 interval.
378
379 The Remez algorithm, as implemented in the boost library's minimax
nicolas3aab40c2010-09-19 21:14:15 +0000380 package, is the key to the construction:
nicolas07bac812010-09-19 18:47:02 +0000381 http://www.boost.org/doc/libs/1_36_0/libs/math/doc/...
382 ...sf_and_dist/html/math_toolkit/backgrounders/remez.html
cristy560d8182010-09-08 22:36:25 +0000383 */
nicolas3aab40c2010-09-19 21:14:15 +0000384 /*
385 If outside of the interval of approximation, use the standard trig
386 formula.
387 */
anthony2d9b8b52010-09-14 08:31:07 +0000388 if (x > 4.0)
cristy03dbbd22010-09-19 23:04:47 +0000389 {
cristyc5c6f662010-09-22 14:23:02 +0000390 const MagickRealType pix = (MagickRealType) (MagickPIL*x);
cristy03dbbd22010-09-19 23:04:47 +0000391 return(sin((double) pix)/pix);
392 }
anthony2d9b8b52010-09-14 08:31:07 +0000393 {
nicolas07bac812010-09-19 18:47:02 +0000394 /*
395 The approximations only depend on x^2 (sinc is an even
396 function).
397 */
398 const MagickRealType xx = x*x;
cristy83017922010-09-05 20:45:15 +0000399#if MAGICKCORE_QUANTUM_DEPTH <= 8
400 /*
anthony2d9b8b52010-09-14 08:31:07 +0000401 Maximum absolute relative error 6.3e-6 < 1/2^17.
cristy738e7562010-09-01 12:48:07 +0000402 */
403 const MagickRealType c0 = 0.173610016489197553621906385078711564924e-2L;
404 const MagickRealType c1 = -0.384186115075660162081071290162149315834e-3L;
405 const MagickRealType c2 = 0.393684603287860108352720146121813443561e-4L;
406 const MagickRealType c3 = -0.248947210682259168029030370205389323899e-5L;
407 const MagickRealType c4 = 0.107791837839662283066379987646635416692e-6L;
408 const MagickRealType c5 = -0.324874073895735800961260474028013982211e-8L;
409 const MagickRealType c6 = 0.628155216606695311524920882748052490116e-10L;
410 const MagickRealType c7 = -0.586110644039348333520104379959307242711e-12L;
nicolas3aab40c2010-09-19 21:14:15 +0000411 const MagickRealType p =
412 c0+xx*(c1+xx*(c2+xx*(c3+xx*(c4+xx*(c5+xx*(c6+xx*c7))))));
nicolas07bac812010-09-19 18:47:02 +0000413 return((xx-1.0)*(xx-4.0)*(xx-9.0)*(xx-16.0)*p);
anthony2d9b8b52010-09-14 08:31:07 +0000414#elif MAGICKCORE_QUANTUM_DEPTH <= 16
cristydbeb3eb2010-09-09 13:41:36 +0000415 /*
anthony2d9b8b52010-09-14 08:31:07 +0000416 Max. abs. rel. error 2.2e-8 < 1/2^25.
cristydbeb3eb2010-09-09 13:41:36 +0000417 */
418 const MagickRealType c0 = 0.173611107357320220183368594093166520811e-2L;
419 const MagickRealType c1 = -0.384240921114946632192116762889211361285e-3L;
nicolas3aab40c2010-09-19 21:14:15 +0000420 const MagickRealType c2 = 0.394201182359318128221229891724947048771e-4L;
421 const MagickRealType c3 = -0.250963301609117217660068889165550534856e-5L;
422 const MagickRealType c4 = 0.111902032818095784414237782071368805120e-6L;
423 const MagickRealType c5 = -0.372895101408779549368465614321137048875e-8L;
424 const MagickRealType c6 = 0.957694196677572570319816780188718518330e-10L;
cristydbeb3eb2010-09-09 13:41:36 +0000425 const MagickRealType c7 = -0.187208577776590710853865174371617338991e-11L;
426 const MagickRealType c8 = 0.253524321426864752676094495396308636823e-13L;
427 const MagickRealType c9 = -0.177084805010701112639035485248501049364e-15L;
anthony853d6972010-10-08 06:01:31 +0000428 const MagickRealType p =
nicolas3aab40c2010-09-19 21:14:15 +0000429 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 +0000430 return((xx-1.0)*(xx-4.0)*(xx-9.0)*(xx-16.0)*p);
nicolasc2d28f02010-09-27 18:56:15 +0000431#else
nicolas3aab40c2010-09-19 21:14:15 +0000432 /*
433 Max. abs. rel. error 1.2e-12 < 1/2^39.
434 */
435 const MagickRealType c0 = 0.173611111110910715186413700076827593074e-2L;
436 const MagickRealType c1 = -0.289105544717893415815859968653611245425e-3L;
437 const MagickRealType c2 = 0.206952161241815727624413291940849294025e-4L;
438 const MagickRealType c3 = -0.834446180169727178193268528095341741698e-6L;
439 const MagickRealType c4 = 0.207010104171026718629622453275917944941e-7L;
440 const MagickRealType c5 = -0.319724784938507108101517564300855542655e-9L;
441 const MagickRealType c6 = 0.288101675249103266147006509214934493930e-11L;
442 const MagickRealType c7 = -0.118218971804934245819960233886876537953e-13L;
443 const MagickRealType p =
444 c0+xx*(c1+xx*(c2+xx*(c3+xx*(c4+xx*(c5+xx*(c6+xx*c7))))));
445 const MagickRealType d0 = 1.0L;
446 const MagickRealType d1 = 0.547981619622284827495856984100563583948e-1L;
447 const MagickRealType d2 = 0.134226268835357312626304688047086921806e-2L;
448 const MagickRealType d3 = 0.178994697503371051002463656833597608689e-4L;
449 const MagickRealType d4 = 0.114633394140438168641246022557689759090e-6L;
450 const MagickRealType q = d0+xx*(d1+xx*(d2+xx*(d3+xx*d4)));
451 return((xx-1.0)*(xx-4.0)*(xx-9.0)*(xx-16.0)/q*p);
cristy657c6352010-08-29 14:05:08 +0000452#endif
cristy83017922010-09-05 20:45:15 +0000453 }
cristy3ed852e2009-09-05 21:47:34 +0000454}
455
456static MagickRealType Triangle(const MagickRealType x,
457 const ResizeFilter *magick_unused(resize_filter))
458{
459 /*
nicolas0edb0862010-09-19 18:56:19 +0000460 1st order (linear) B-Spline, bilinear interpolation, Tent 1D
461 filter, or a Bartlett 2D Cone filter.
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
anthony853d6972010-10-08 06:01:31 +0000506% SincFast Lanczos2D Robidoux
anthony61b5ddd2010-10-05 02:33:31 +0000507%
anthony48f77622010-10-03 14:32:31 +0000508% The users "-filter" selection is used to lookup the default 'expert'
509% settings for that filter from a internal table. However any provided
510% 'expert' settings (see below) may override this selection.
511%
512% FIR filters are used as is, and are limited to that filters support
nicolas07bac812010-09-19 18:47:02 +0000513% window (unless over-ridden). 'Gaussian' while classed as an IIR
anthonyc331dec2010-09-26 01:30:14 +0000514% filter, is also simply clipped by its support size (currently 1.5
anthonyf5e76ef2010-10-12 01:22:01 +0000515% or approximatally 3*sigma as recommended by many references)
cristy3ed852e2009-09-05 21:47:34 +0000516%
anthony48f77622010-10-03 14:32:31 +0000517% The selection is typically either a windowed Sinc, or interpolated
nicolas07bac812010-09-19 18:47:02 +0000518% filter, for use by functions such as ResizeImage(). However if a
anthony48f77622010-10-03 14:32:31 +0000519% 'cylindrical' filter flag is requested, any default Sinc weighting
520% and windowing functions will be promoted to cylindrical Jinc form of
521% function.
cristy3ed852e2009-09-05 21:47:34 +0000522%
anthony48f77622010-10-03 14:32:31 +0000523% Directly requesting 'Sinc' or 'Jinc' will force the use of that
524% filter function without any windowing. This is not recommended,
525% except by image processing experts or in expert options. Selecting a
526% window filtering version of these functions is better.
cristy3ed852e2009-09-05 21:47:34 +0000527%
anthony48f77622010-10-03 14:32:31 +0000528% Lanczos is a special case of a Sinc-windowed Sinc, (or Jinc-Jinc for
529% the cylindrical case) but defaulting to 3-lobe support, rather that
530% the default 4 lobe support of the other windowed sinc/jinc filters.
cristy3ed852e2009-09-05 21:47:34 +0000531%
anthony48f77622010-10-03 14:32:31 +0000532% Two forms of the 'Sinc' function are available: Sinc and SincFast.
nicolas07bac812010-09-19 18:47:02 +0000533% Sinc is computed using the traditional sin(pi*x)/(pi*x); it is
534% selected if the user specifically specifies the use of a Sinc
535% filter. SincFast uses highly accurate (and fast) polynomial (low Q)
anthony48f77622010-10-03 14:32:31 +0000536% and rational (high Q) approximations, and will be used by default in
537% most cases.
anthonyba5a7c32010-09-15 02:42:25 +0000538%
nicolas1eb2fcf2010-10-10 20:45:17 +0000539% The Lanczos2D and Robidoux filters are tuned for cylindrical
540% (radial) EWA (Elliptical Weighted Average) distortion. Lanczos2D
nicolasdff19b42010-10-10 20:53:13 +0000541% is a 2 lobe Lanczos-like filter using Jinc (for EWA) or Sinc.
nicolas6ca6e962010-10-10 20:49:01 +0000542% Robidoux used to be a sharpened version of Lanczos2D (with
nicolas0d5e5322010-10-22 15:29:30 +0000543% blur=0.958033808). Now, Robidoux is the unique Keys cubic spline
544% filter satisfying the following condition:
545%
546% Robidoux exactly preserves images with only vertical or
547% horizontal features when performing 'no-op" with EWA distortion.
548%
nicolas618a9a72010-10-22 19:23:43 +0000549% That is, Robidoux is the BC-Spline with B=(228 - 108 sqrt(2))/199
550% and C=(108 sqrt(2) - 29)/398. Robidoux turns out to be close to
551% both plain Mitchell and "sharpened" Lanczos2D. For example, it's
552% first crossing is (36 sqrt(2) + 123)/(72 sqrt(2) + 47) which is
553% almost identical to the first crossing of the other two.
anthony61b5ddd2010-10-05 02:33:31 +0000554%
nicolas3061b8a2010-10-22 16:34:52 +0000555% 'EXPERT' OPTIONS:
nicolasce6dc292010-10-22 16:23:07 +0000556%
nicolas3061b8a2010-10-22 16:34:52 +0000557% (Not recommended without expert knowledge of resampling and
558% filtering.)
559%
560% You can override any and all filter settings. Use "filter:verbose"
561% to make sure that the overall effect of your selections is as
562% expected.
563%
564% "filter:verbose" Output the exact results of the filter
565% selections made, as well as plotting data for graphing the
566% resulting filter over support range (blur adjusted).
cristy3ed852e2009-09-05 21:47:34 +0000567%
anthony48f77622010-10-03 14:32:31 +0000568% "filter:filter" Select the main function associated with
569% this filter name, as the weighting function of the filter.
570% This can be used to set a windowing function as a weighting
571% function, for special purposes, such as graphing.
cristy3ed852e2009-09-05 21:47:34 +0000572%
nicolas3061b8a2010-10-22 16:34:52 +0000573% If a "filter:window" operation has not been provided, then a
574% 'Box' windowing function will be set to denote that no
575% windowing function is being used.
cristy3ed852e2009-09-05 21:47:34 +0000576%
nicolas3061b8a2010-10-22 16:34:52 +0000577% "filter:window" Select this windowing function for the filter.
578% While any filter could be used as a windowing function, using
579% the 'first lobe' of that filter over the whole support
580% window, using a non-windowing function is not advisible. If
581% no weighting filter function is specifed a 'SincFast' filter
582% will be used.
cristy3ed852e2009-09-05 21:47:34 +0000583%
nicolas3061b8a2010-10-22 16:34:52 +0000584% "filter:lobes" Number of lobes to use for the Sinc/Jinc filter.
585% This a simpler method of setting filter support size that
586% will correctly handle the Sinc/Jinc switch for an operators
587% filtering requirements. Only integers should be given.
cristy3ed852e2009-09-05 21:47:34 +0000588%
nicolas3061b8a2010-10-22 16:34:52 +0000589% "filter:support" Set the support size for filtering to the size
590% given This not recommended for Sinc/Jinc windowed filters
591% (lobes should be used instead). This will override any
592% 'filter:lobes' option.
cristy3ed852e2009-09-05 21:47:34 +0000593%
nicolas3061b8a2010-10-22 16:34:52 +0000594% "filter:win-support" Scale windowing function to this size
595% instead. This causes the windowing (or self-windowing
596% Lagrange filter) to act is if the support window it much much
597% larger than what is actually supplied to the calling
598% operator. The filter however is still clipped to the real
599% support size given, by the support range suppiled to the
600% caller. If unset this will equal the normal filter support
anthonyb6d08c52010-09-13 01:17:04 +0000601% size.
602%
nicolas3061b8a2010-10-22 16:34:52 +0000603% "filter:blur" Scale the filter and support window by this amount.
cristy3ed852e2009-09-05 21:47:34 +0000604% A value >1 will generally result in a more burred image with
605% more ringing effects, while a value <1 will sharpen the
606% resulting image with more aliasing and Morie effects.
607%
nicolas3061b8a2010-10-22 16:34:52 +0000608% "filter:sigma" The sigma value to use for the Gaussian filter
609% only. Defaults to '1/2' for orthogonal and 'sqrt(2)/2' for
610% cylindrical usage. It effectially provides a alturnative to
611% 'blur' for Gaussians without it also effecting the final
612% 'practical support' size.
anthonyf5e76ef2010-10-12 01:22:01 +0000613%
cristy3ed852e2009-09-05 21:47:34 +0000614% "filter:b"
nicolas3061b8a2010-10-22 16:34:52 +0000615% "filter:c" Override the preset B,C values for a Cubic type of
616% filter If only one of these are given it is assumes to be a
617% 'Keys' type of filter such that B+2C=1, where Keys 'alpha'
618% value = C
cristy3ed852e2009-09-05 21:47:34 +0000619%
nicolas3061b8a2010-10-22 16:34:52 +0000620% Examples:
cristy3ed852e2009-09-05 21:47:34 +0000621%
nicolas6e1267a2010-10-22 16:35:52 +0000622% Set a true un-windowed Sinc filter with 10 lobes (very slow):
anthony48f77622010-10-03 14:32:31 +0000623% -define filter:filter=Sinc
624% -define filter:lobes=8
cristy3ed852e2009-09-05 21:47:34 +0000625%
nicolas6e1267a2010-10-22 16:35:52 +0000626% Set an 8 lobe Lanczos (Sinc or Jinc) filter:
cristy3ed852e2009-09-05 21:47:34 +0000627% -filter Lanczos
anthony48f77622010-10-03 14:32:31 +0000628% -define filter:lobes=8
anthony7bdc0ed2010-09-15 01:52:32 +0000629%
cristy3ed852e2009-09-05 21:47:34 +0000630% The format of the AcquireResizeFilter method is:
631%
632% ResizeFilter *AcquireResizeFilter(const Image *image,
633% const FilterTypes filter_type, const MagickBooleanType radial,
634% ExceptionInfo *exception)
635%
cristy33b1c162010-01-23 22:51:51 +0000636% A description of each parameter follows:
637%
cristy3ed852e2009-09-05 21:47:34 +0000638% o image: the image.
639%
nicolas07bac812010-09-19 18:47:02 +0000640% o filter: the filter type, defining a preset filter, window and
641% support. The artifact settings listed above will override
642% those selections.
cristy3ed852e2009-09-05 21:47:34 +0000643%
anthony48f77622010-10-03 14:32:31 +0000644% o blur: blur the filter by this amount, use 1.0 if unknown. Image
645% artifact "filter:blur" will override this API call usage, including
646% any internal change (such as for cylindrical usage).
cristy3ed852e2009-09-05 21:47:34 +0000647%
anthony48f77622010-10-03 14:32:31 +0000648% o radial: use a 1D orthogonal filter (Sinc) or 2D cylindrical
649% (radial) filter (Jinc)
cristy3ed852e2009-09-05 21:47:34 +0000650%
651% o exception: return any errors or warnings in this structure.
652%
653*/
654MagickExport ResizeFilter *AcquireResizeFilter(const Image *image,
cristyf59a8922010-02-28 19:51:23 +0000655 const FilterTypes filter,const MagickRealType blur,
cristy3ed852e2009-09-05 21:47:34 +0000656 const MagickBooleanType cylindrical,ExceptionInfo *exception)
657{
658 const char
659 *artifact;
660
661 FilterTypes
662 filter_type,
663 window_type;
664
cristy3ed852e2009-09-05 21:47:34 +0000665 MagickRealType
666 B,
anthonyf5e76ef2010-10-12 01:22:01 +0000667 C,
668 sigma;
cristy3ed852e2009-09-05 21:47:34 +0000669
670 register ResizeFilter
671 *resize_filter;
672
cristy9af9b5d2010-08-15 17:04:28 +0000673 ssize_t
674 option;
675
cristy3ed852e2009-09-05 21:47:34 +0000676 /*
anthony48f77622010-10-03 14:32:31 +0000677 Table Mapping given Filter, into Weighting and Windowing functions.
nicolas07bac812010-09-19 18:47:02 +0000678 A 'Box' windowing function means its a simble non-windowed filter.
anthony48f77622010-10-03 14:32:31 +0000679 An 'SincFast' filter function could be upgraded to a 'Jinc' filter
680 if a "cylindrical", unless a 'Sinc' or 'SincFast' filter was
681 specifically requested.
anthony449887b2010-09-10 02:23:33 +0000682
nicolas07bac812010-09-19 18:47:02 +0000683 WARNING: The order of this tabel must match the order of the
684 FilterTypes enumeration specified in "resample.h", or the filter
685 names will not match the filter being setup.
anthony1e2d5ca2010-09-10 02:24:35 +0000686
687 You can check filter setups with the "filter:verbose" setting.
cristy3ed852e2009-09-05 21:47:34 +0000688 */
689 static struct
690 {
691 FilterTypes
692 filter,
693 window;
694 } const mapping[SentinelFilter] =
695 {
anthony462ee072010-09-27 12:34:02 +0000696 { UndefinedFilter, BoxFilter }, /* Undefined (default to Box) */
697 { PointFilter, BoxFilter }, /* SPECIAL: Nearest neighbour */
698 { BoxFilter, BoxFilter }, /* Box averaging filter */
699 { TriangleFilter, BoxFilter }, /* Linear interpolation filter */
700 { HermiteFilter, BoxFilter }, /* Hermite interpolation filter */
701 { SincFastFilter, HanningFilter }, /* Hanning -- cosine-sinc */
702 { SincFastFilter, HammingFilter }, /* Hamming -- '' variation */
703 { SincFastFilter, BlackmanFilter }, /* Blackman -- 2*cosine-sinc */
704 { GaussianFilter, BoxFilter }, /* Gaussian blur filter */
705 { QuadraticFilter, BoxFilter }, /* Quadratic Gaussian approximation */
706 { CubicFilter, BoxFilter }, /* Cubic B-Spline */
707 { CatromFilter, BoxFilter }, /* Cubic interpolator */
708 { MitchellFilter, BoxFilter }, /* 'Ideal' cubic filter */
709 { LanczosFilter, SincFastFilter }, /* SPECIAL: 3-lobed sinc-sinc */
anthony48f77622010-10-03 14:32:31 +0000710 { JincFilter, BoxFilter }, /* Raw 3-lobed Jinc function */
711 { SincFilter, BoxFilter }, /* Raw 4-lobed Sinc function */
anthony462ee072010-09-27 12:34:02 +0000712 { SincFastFilter, KaiserFilter }, /* Kaiser -- square root-sinc */
713 { SincFastFilter, WelshFilter }, /* Welsh -- parabolic-sinc */
714 { SincFastFilter, CubicFilter }, /* Parzen -- cubic-sinc */
715 { LagrangeFilter, BoxFilter }, /* Lagrange self-windowing filter */
716 { SincFastFilter, BohmanFilter }, /* Bohman -- 2*cosine-sinc */
717 { SincFastFilter, TriangleFilter }, /* Bartlett -- triangle-sinc */
718 { SincFastFilter, BoxFilter }, /* Raw fast sinc ("Pade"-type) */
nicolas5835ee02010-10-10 20:40:15 +0000719 { Lanczos2DFilter, JincFilter }, /* SPECIAL: 2-lobed jinc-jinc */
anthonye5f06452010-10-12 05:48:17 +0000720 { Lanczos2DSharpFilter, JincFilter },/* SPECIAL: ditto sharpened */
721 { RobidouxFilter, BoxFilter }, /* SPECIAL: Keys cubic tuned for EWA */
cristy3ed852e2009-09-05 21:47:34 +0000722 };
723 /*
nicolas32f44eb2010-09-20 01:23:12 +0000724 Table mapping the filter/window from the above table to an actual
nicolas07bac812010-09-19 18:47:02 +0000725 function. The default support size for that filter as a weighting
726 function, the range to scale with to use that function as a sinc
727 windowing function, (typ 1.0).
anthony933b4bf2010-09-10 02:31:37 +0000728
anthony07a3f7f2010-09-16 03:03:11 +0000729 Note that the filter_type -> function is 1 to 1 except for Sinc(),
nicolas07bac812010-09-19 18:47:02 +0000730 SincFast(), and CubicBC() functions, which may have multiple
731 filter to function associations.
732
733 See "filter:verbose" handling below for the function -> filter
734 mapping.
cristy3ed852e2009-09-05 21:47:34 +0000735 */
736 static struct
737 {
738 MagickRealType
739 (*function)(const MagickRealType, const ResizeFilter*),
nicolas8eccc162010-10-16 19:48:13 +0000740 lobes, /* Default lobes/support size of the weighting filter. */
anthony450db502010-10-19 04:03:03 +0000741 scale, /* Support when function used as a windowing function
742 Typically equal to the location of the first zero crossing. */
743 B,C; /* BC-spline coefficients, ignored if not a CubicBC filter. */
cristy3ed852e2009-09-05 21:47:34 +0000744 } const filters[SentinelFilter] =
745 {
anthony61b5ddd2010-10-05 02:33:31 +0000746 { Box, 0.5, 0.5, 0.0, 0.0 }, /* Undefined (default to Box) */
747 { Box, 0.0, 0.5, 0.0, 0.0 }, /* Point (special handling) */
748 { Box, 0.5, 0.5, 0.0, 0.0 }, /* Box */
749 { Triangle, 1.0, 1.0, 0.0, 0.0 }, /* Triangle */
750 { CubicBC, 1.0, 1.0, 0.0, 0.0 }, /* Hermite (cubic B=C=0) */
751 { Hanning, 1.0, 1.0, 0.0, 0.0 }, /* Hanning, cosine window */
752 { Hamming, 1.0, 1.0, 0.0, 0.0 }, /* Hamming, '' variation */
753 { Blackman, 1.0, 1.0, 0.0, 0.0 }, /* Blackman, 2*cosine window */
anthonyf5e76ef2010-10-12 01:22:01 +0000754 { Gaussian, 2.0, 1.5, 0.0, 0.0 }, /* Gaussian */
anthony61b5ddd2010-10-05 02:33:31 +0000755 { Quadratic, 1.5, 1.5, 0.0, 0.0 }, /* Quadratic gaussian */
756 { CubicBC, 2.0, 2.0, 1.0, 0.0 }, /* Cubic B-Spline (B=1,C=0) */
757 { CubicBC, 2.0, 1.0, 0.0, 0.5 }, /* Catmull-Rom (B=0,C=1/2) */
anthony853d6972010-10-08 06:01:31 +0000758 { CubicBC, 2.0, 1.0, 1./3., 1./3. }, /* Mitchell (B=C=1/3) */
759 { SincFast, 3.0, 1.0, 0.0, 0.0 }, /* Lanczos, 3-lobed Sinc-Sinc */
nicolasf8689f42010-10-18 16:14:08 +0000760 { Jinc, 3.0, 1.2196698912665045, 0.0, 0.0 }, /* Raw 3-lobed Jinc */
anthony61b5ddd2010-10-05 02:33:31 +0000761 { Sinc, 4.0, 1.0, 0.0, 0.0 }, /* Raw 4-lobed Sinc */
762 { Kaiser, 1.0, 1.0, 0.0, 0.0 }, /* Kaiser (square root window) */
763 { Welsh, 1.0, 1.0, 0.0, 0.0 }, /* Welsh (parabolic window) */
764 { CubicBC, 2.0, 2.0, 1.0, 0.0 }, /* Parzen (B-Spline window) */
765 { Lagrange, 2.0, 1.0, 0.0, 0.0 }, /* Lagrange sinc approximation */
766 { Bohman, 1.0, 1.0, 0.0, 0.0 }, /* Bohman, 2*Cosine window */
767 { Triangle, 1.0, 1.0, 0.0, 0.0 }, /* Bartlett (triangle window) */
768 { SincFast, 4.0, 1.0, 0.0, 0.0 }, /* Raw fast sinc ("Pade"-type) */
nicolas8eccc162010-10-16 19:48:13 +0000769 { Jinc, 2.0, 1.2196698912665045, 0.0, 0.0 },
770 /* Lanczos2D (Jinc-Jinc) */
771 { Jinc, 2.0, 1.1684849904329952, 0.0, 0.0 },
772 /* Lanczos2D sharpened with blur=0.958033808 */
anthony450db502010-10-19 04:03:03 +0000773 { CubicBC, 2.0, 1.1685777620836932,
nicolas0d5e5322010-10-22 15:29:30 +0000774 0.37821575509399867, 0.31089212245300067 }
775 /* Robidoux: Keys cubic close to Lanczos2D sharpened */
cristy3ed852e2009-09-05 21:47:34 +0000776 };
777 /*
anthony9a98fc62010-10-11 02:47:19 +0000778 The known zero crossings of the Jinc() or more accurately the Jinc(x*PI)
anthony450db502010-10-19 04:03:03 +0000779 function being used as a filter. It is used by the "filter:lobes" expert
780 setting and for 'lobes' for Jinc functions in the previous table. This
781 way users do not have to deal with the highly irrational lobe sizes of the
anthony9a98fc62010-10-11 02:47:19 +0000782 Jinc filter.
anthony48f77622010-10-03 14:32:31 +0000783
nicolase473f722010-10-07 00:05:13 +0000784 Values taken from
anthony48f77622010-10-03 14:32:31 +0000785 http://cose.math.bas.bg/webMathematica/webComputing/BesselZeros.jsp
nicolase473f722010-10-07 00:05:13 +0000786 using Jv-function with v=1, then dividing by PI.
cristy3ed852e2009-09-05 21:47:34 +0000787 */
788 static MagickRealType
anthony48f77622010-10-03 14:32:31 +0000789 jinc_zeros[16] =
cristy3ed852e2009-09-05 21:47:34 +0000790 {
nicolas8eccc162010-10-16 19:48:13 +0000791 1.2196698912665045,
792 2.2331305943815286,
793 3.2383154841662362,
794 4.2410628637960699,
795 5.2427643768701817,
796 6.2439216898644877,
797 7.244759868719957,
798 8.2453949139520427,
799 9.2458926849494673,
800 10.246293348754916,
801 11.246622794877883,
802 12.246898461138105,
803 13.247132522181061,
804 14.247333735806849,
anthonyc2d07db2010-09-15 23:47:40 +0000805 15.2475085630373,
nicolas8eccc162010-10-16 19:48:13 +0000806 16.247661874700962
cristy3ed852e2009-09-05 21:47:34 +0000807 };
808
cristy33b1c162010-01-23 22:51:51 +0000809 /*
810 Allocate resize filter.
811 */
cristy3ed852e2009-09-05 21:47:34 +0000812 assert(image != (const Image *) NULL);
813 assert(image->signature == MagickSignature);
814 if (image->debug != MagickFalse)
815 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
816 assert(UndefinedFilter < filter && filter < SentinelFilter);
817 assert(exception != (ExceptionInfo *) NULL);
818 assert(exception->signature == MagickSignature);
cristy73bd4a52010-10-05 11:24:23 +0000819 resize_filter=(ResizeFilter *) AcquireMagickMemory(sizeof(*resize_filter));
cristy3ed852e2009-09-05 21:47:34 +0000820 if (resize_filter == (ResizeFilter *) NULL)
821 ThrowFatalException(ResourceLimitFatalError,"MemoryAllocationFailed");
cristy33b1c162010-01-23 22:51:51 +0000822 /*
823 Defaults for the requested filter.
824 */
825 filter_type=mapping[filter].filter;
826 window_type=mapping[filter].window;
anthonyf5e76ef2010-10-12 01:22:01 +0000827 resize_filter->blur = blur;
828 sigma = 0.5;
anthony48f77622010-10-03 14:32:31 +0000829 /* Cylindrical Filters should use Jinc instead of Sinc */
anthonyb6d08c52010-09-13 01:17:04 +0000830 if (cylindrical != MagickFalse)
cristy33b1c162010-01-23 22:51:51 +0000831 switch (filter_type)
832 {
833 case SincFilter:
anthony48f77622010-10-03 14:32:31 +0000834 /* Promote 1D Sinc Filter to a 2D Jinc filter. */
anthonyb6d08c52010-09-13 01:17:04 +0000835 if ( filter != SincFilter )
anthony48f77622010-10-03 14:32:31 +0000836 filter_type=JincFilter;
cristy33b1c162010-01-23 22:51:51 +0000837 break;
anthonyba5a7c32010-09-15 02:42:25 +0000838 case SincFastFilter:
nicolas07bac812010-09-19 18:47:02 +0000839 /* Ditto for SincFast variant */
anthonyba5a7c32010-09-15 02:42:25 +0000840 if ( filter != SincFastFilter )
anthony48f77622010-10-03 14:32:31 +0000841 filter_type=JincFilter;
anthony7bdc0ed2010-09-15 01:52:32 +0000842 break;
cristy33b1c162010-01-23 22:51:51 +0000843 case LanczosFilter:
nicolas45b58a92010-10-07 15:46:39 +0000844 /* Promote Lanczos from a Sinc-Sinc to a Jinc-Jinc. */
anthony48f77622010-10-03 14:32:31 +0000845 filter_type=JincFilter;
846 window_type=JincFilter;
cristy33b1c162010-01-23 22:51:51 +0000847 break;
anthony08958462010-10-12 06:48:35 +0000848 case Lanczos2DSharpFilter:
nicolas1f4c0512010-10-20 20:19:30 +0000849 /* Sharpened by Nicolas Robidoux so as to optimize for minimal
850 * blurring of orthogonal lines
anthony08958462010-10-12 06:48:35 +0000851 */
852 resize_filter->blur *= 0.958033808;
853 break;
anthonyf5e76ef2010-10-12 01:22:01 +0000854 case GaussianFilter:
cristy68f103e2010-10-14 01:19:07 +0000855 sigma = (MagickRealType) (MagickSQ2/2.0); /* Cylindrical Gaussian sigma is sqrt(2)/2 */
anthonyf5e76ef2010-10-12 01:22:01 +0000856 break;
cristya782ecf2010-01-25 02:59:14 +0000857 default:
858 break;
cristy3ed852e2009-09-05 21:47:34 +0000859 }
anthony61b5ddd2010-10-05 02:33:31 +0000860 else
861 switch (filter_type)
862 {
863 case Lanczos2DFilter:
anthonye5f06452010-10-12 05:48:17 +0000864 case Lanczos2DSharpFilter:
nicolas45b58a92010-10-07 15:46:39 +0000865 /* Demote to a 2-lobe Sinc-Sinc for orthogonal use. */
anthony61b5ddd2010-10-05 02:33:31 +0000866 window_type=SincFastFilter;
867 break;
868 default:
869 break;
870 }
871
cristy3ed852e2009-09-05 21:47:34 +0000872 artifact=GetImageArtifact(image,"filter:filter");
cristy33b1c162010-01-23 22:51:51 +0000873 if (artifact != (const char *) NULL)
874 {
cristy9af9b5d2010-08-15 17:04:28 +0000875 option=ParseMagickOption(MagickFilterOptions,MagickFalse,artifact);
cristy33b1c162010-01-23 22:51:51 +0000876 if ((UndefinedFilter < option) && (option < SentinelFilter))
anthony61b5ddd2010-10-05 02:33:31 +0000877 { /* Raw filter request - no window function. */
cristy33b1c162010-01-23 22:51:51 +0000878 filter_type=(FilterTypes) option;
879 window_type=BoxFilter;
880 }
881 if (option == LanczosFilter)
anthony61b5ddd2010-10-05 02:33:31 +0000882 { /* Lanczos is not a real filter but a self windowing Sinc/Jinc. */
anthony853d6972010-10-08 06:01:31 +0000883 filter_type=cylindrical != MagickFalse ? JincFilter : LanczosFilter;
anthony48f77622010-10-03 14:32:31 +0000884 window_type=cylindrical != MagickFalse ? JincFilter : SincFastFilter;
cristy33b1c162010-01-23 22:51:51 +0000885 }
nicolas07bac812010-09-19 18:47:02 +0000886 /* Filter override with a specific window function. */
cristy33b1c162010-01-23 22:51:51 +0000887 artifact=GetImageArtifact(image,"filter:window");
888 if (artifact != (const char *) NULL)
889 {
cristy9af9b5d2010-08-15 17:04:28 +0000890 option=ParseMagickOption(MagickFilterOptions,MagickFalse,artifact);
cristy33b1c162010-01-23 22:51:51 +0000891 if ((UndefinedFilter < option) && (option < SentinelFilter))
892 {
893 if (option != LanczosFilter)
894 window_type=(FilterTypes) option;
cristy9af9b5d2010-08-15 17:04:28 +0000895 else
anthony48f77622010-10-03 14:32:31 +0000896 window_type=cylindrical != MagickFalse ? JincFilter :
cristy03dbbd22010-09-19 23:04:47 +0000897 SincFastFilter;
cristy9af9b5d2010-08-15 17:04:28 +0000898 }
cristy33b1c162010-01-23 22:51:51 +0000899 }
cristy3ed852e2009-09-05 21:47:34 +0000900 }
cristy33b1c162010-01-23 22:51:51 +0000901 else
902 {
anthony48f77622010-10-03 14:32:31 +0000903 /* Window specified, but no filter function? Assume Sinc/Jinc. */
cristy33b1c162010-01-23 22:51:51 +0000904 artifact=GetImageArtifact(image,"filter:window");
905 if (artifact != (const char *) NULL)
906 {
907 option=ParseMagickOption(MagickFilterOptions,MagickFalse,
908 artifact);
909 if ((UndefinedFilter < option) && (option < SentinelFilter))
910 {
anthony61b5ddd2010-10-05 02:33:31 +0000911 filter_type=cylindrical != MagickFalse ?
912 JincFilter : SincFastFilter;
cristy19eb6412010-04-23 14:42:29 +0000913 window_type=(FilterTypes) option;
cristy33b1c162010-01-23 22:51:51 +0000914 }
915 }
916 }
nicolas07bac812010-09-19 18:47:02 +0000917 /* Assign the real functions to use for the filters selected. */
cristy33b1c162010-01-23 22:51:51 +0000918 resize_filter->filter=filters[filter_type].function;
anthony61b5ddd2010-10-05 02:33:31 +0000919 resize_filter->support=filters[filter_type].lobes;
cristy33b1c162010-01-23 22:51:51 +0000920 resize_filter->window=filters[window_type].function;
921 resize_filter->scale=filters[window_type].scale;
cristy3ed852e2009-09-05 21:47:34 +0000922 resize_filter->signature=MagickSignature;
anthony61b5ddd2010-10-05 02:33:31 +0000923
anthonyf5e76ef2010-10-12 01:22:01 +0000924 /* Filter Modifications for orthogonal/cylindrical usage */
anthony10b8bc82010-10-02 12:48:46 +0000925 if (cylindrical != MagickFalse)
926 switch (filter_type)
927 {
928 case PointFilter:
929 case BoxFilter:
930 /* Support for Cylindrical Box should be sqrt(2)/2 */
cristy1c9bb452010-10-03 16:48:33 +0000931 resize_filter->support=(MagickRealType) MagickSQ1_2;
anthony10b8bc82010-10-02 12:48:46 +0000932 break;
anthony81b8bf92010-10-02 13:54:34 +0000933 default:
934 break;
anthony10b8bc82010-10-02 12:48:46 +0000935 }
anthony61b5ddd2010-10-05 02:33:31 +0000936 else
937 switch (filter_type)
938 {
939 case Lanczos2DFilter:
anthonye5f06452010-10-12 05:48:17 +0000940 case Lanczos2DSharpFilter:
anthony853d6972010-10-08 06:01:31 +0000941 /* Demote to a 2-lobe Lanczos (Sinc-Sinc) for orthogonal use. */
anthony61b5ddd2010-10-05 02:33:31 +0000942 resize_filter->filter=SincFast;
943 break;
944 default:
945 break;
946 }
947
anthonyf5e76ef2010-10-12 01:22:01 +0000948 /*
949 ** More Expert Option Modifications
950 */
951
952 /* User Sigma Override - no support change */
953 artifact=GetImageArtifact(image,"filter:sigma");
954 if (artifact != (const char *) NULL)
955 sigma=StringToDouble(artifact);
956 /* Define coefficents for Gaussian (assumes no cubic window) */
957 if ( GaussianFilter ) {
958 resize_filter->coeff[0] = 1.0/(2.0*sigma*sigma);
cristy68f103e2010-10-14 01:19:07 +0000959 resize_filter->coeff[1] = (MagickRealType) (1.0/(Magick2PI*sigma*sigma)); /* unused */
anthonyf5e76ef2010-10-12 01:22:01 +0000960 }
961
962 /* Blur Override */
963 artifact=GetImageArtifact(image,"filter:blur");
964 if (artifact != (const char *) NULL)
965 resize_filter->blur=StringToDouble(artifact);
966 if (resize_filter->blur < MagickEpsilon)
967 resize_filter->blur=(MagickRealType) MagickEpsilon;
968
969 /* Support Overrides */
cristy3ed852e2009-09-05 21:47:34 +0000970 artifact=GetImageArtifact(image,"filter:lobes");
cristy33b1c162010-01-23 22:51:51 +0000971 if (artifact != (const char *) NULL)
972 {
cristybb503372010-05-27 20:51:26 +0000973 ssize_t
cristy33b1c162010-01-23 22:51:51 +0000974 lobes;
975
cristy96b16132010-08-29 17:19:52 +0000976 lobes=(ssize_t) StringToLong(artifact);
cristy33b1c162010-01-23 22:51:51 +0000977 if (lobes < 1)
978 lobes=1;
979 resize_filter->support=(MagickRealType) lobes;
cristy3ed852e2009-09-05 21:47:34 +0000980 }
anthony61b5ddd2010-10-05 02:33:31 +0000981 /* convert Jinc lobes to a real support value */
982 if (resize_filter->filter == Jinc)
983 {
984 if (resize_filter->support > 16)
985 resize_filter->support=jinc_zeros[15]; /* largest entry in table */
986 else
987 resize_filter->support = jinc_zeros[((long)resize_filter->support)-1];
988 }
989 /* expert override of the support setting */
cristy3ed852e2009-09-05 21:47:34 +0000990 artifact=GetImageArtifact(image,"filter:support");
991 if (artifact != (const char *) NULL)
cristy33b1c162010-01-23 22:51:51 +0000992 resize_filter->support=fabs(StringToDouble(artifact));
993 /*
nicolas07bac812010-09-19 18:47:02 +0000994 Scale windowing function separatally to the support 'clipping'
995 window that calling operator is planning to actually use. (Expert
996 override)
cristy3ed852e2009-09-05 21:47:34 +0000997 */
anthony55f12332010-09-10 01:13:02 +0000998 resize_filter->window_support=resize_filter->support; /* default */
cristy3ed852e2009-09-05 21:47:34 +0000999 artifact=GetImageArtifact(image,"filter:win-support");
1000 if (artifact != (const char *) NULL)
cristy33b1c162010-01-23 22:51:51 +00001001 resize_filter->window_support=fabs(StringToDouble(artifact));
1002 /*
anthony1f90a6b2010-09-14 08:56:31 +00001003 Adjust window function scaling to the windowing support for
1004 weighting function. This avoids a division on every filter call.
anthony55f12332010-09-10 01:13:02 +00001005 */
1006 resize_filter->scale /= resize_filter->window_support;
anthonyf5e76ef2010-10-12 01:22:01 +00001007
anthony55f12332010-09-10 01:13:02 +00001008 /*
anthonyf5e76ef2010-10-12 01:22:01 +00001009 * Set Cubic Spline B,C values, calculate Cubic coefficients.
cristy33b1c162010-01-23 22:51:51 +00001010 */
cristy3ed852e2009-09-05 21:47:34 +00001011 B=0.0;
1012 C=0.0;
cristy33b1c162010-01-23 22:51:51 +00001013 if ((filters[filter_type].function == CubicBC) ||
1014 (filters[window_type].function == CubicBC))
1015 {
anthony2d9b8b52010-09-14 08:31:07 +00001016 B=filters[filter_type].B;
1017 C=filters[filter_type].C;
1018 if (filters[window_type].function == CubicBC)
cristy33b1c162010-01-23 22:51:51 +00001019 {
anthony2d9b8b52010-09-14 08:31:07 +00001020 B=filters[window_type].B;
1021 C=filters[window_type].C;
cristy33b1c162010-01-23 22:51:51 +00001022 }
cristy33b1c162010-01-23 22:51:51 +00001023 artifact=GetImageArtifact(image,"filter:b");
cristy3ed852e2009-09-05 21:47:34 +00001024 if (artifact != (const char *) NULL)
cristy33b1c162010-01-23 22:51:51 +00001025 {
1026 B=StringToDouble(artifact);
nicolas07bac812010-09-19 18:47:02 +00001027 C=(1.0-B)/2.0; /* Calculate C as if it is a Keys cubic filter. */
anthonyf5e76ef2010-10-12 01:22:01 +00001028 artifact=GetImageArtifact(image,"filter:c"); /* user C override */
cristy33b1c162010-01-23 22:51:51 +00001029 if (artifact != (const char *) NULL)
1030 C=StringToDouble(artifact);
1031 }
1032 else
1033 {
1034 artifact=GetImageArtifact(image,"filter:c");
1035 if (artifact != (const char *) NULL)
1036 {
1037 C=StringToDouble(artifact);
nicolas07bac812010-09-19 18:47:02 +00001038 B=1.0-2.0*C; /* Calculate B as if it is a Keys cubic filter. */
cristy33b1c162010-01-23 22:51:51 +00001039 }
1040 }
nicolasc6bac3b2010-10-24 18:10:45 +00001041 /* Convert B,C values into Cubic Coefficents. See CubicBC(). */
1042 resize_filter->coeff[0]=1.0-(1.0/3.0)*B;
1043 resize_filter->coeff[1]=0.0;
1044 resize_filter->coeff[2]=-3.0+2.0*B+C;
1045 resize_filter->coeff[3]=2.0-1.5*B-C;
1046 resize_filter->coeff[4]=(4.0/3.0)*B+4.0*C;
1047 resize_filter->coeff[5]=-2.0*B-8.0*C;
1048 resize_filter->coeff[6]=B+5.0*C;
1049 resize_filter->coeff[7]=(-1.0/6.0)*B-C;
1050 }
anthonyf5e76ef2010-10-12 01:22:01 +00001051
anthony55f12332010-09-10 01:13:02 +00001052 /*
nicolas07bac812010-09-19 18:47:02 +00001053 Expert Option Request for verbose details of the resulting filter.
anthony55f12332010-09-10 01:13:02 +00001054 */
cristyf5b49372010-10-16 01:06:47 +00001055#if defined(MAGICKCORE_OPENMP_SUPPORT)
1056 #pragma omp master
1057 {
1058#endif
1059 artifact=GetImageArtifact(image,"filter:verbose");
1060 if (artifact != (const char *) NULL)
1061 {
1062 double
anthony450db502010-10-19 04:03:03 +00001063 support,
cristyf5b49372010-10-16 01:06:47 +00001064 x;
cristy3ed852e2009-09-05 21:47:34 +00001065
cristyf5b49372010-10-16 01:06:47 +00001066 /*
1067 Set the weighting function properly when the weighting
1068 function may not exactly match the filter of the same name.
1069 EG: a Point filter really uses a Box weighting function
1070 with a different support than is typically used.
anthony463be1d2010-09-26 01:07:36 +00001071
cristyf5b49372010-10-16 01:06:47 +00001072 */
1073 if (resize_filter->filter == Box) filter_type=BoxFilter;
1074 if (resize_filter->filter == Sinc) filter_type=SincFilter;
1075 if (resize_filter->filter == SincFast) filter_type=SincFastFilter;
1076 if (resize_filter->filter == Jinc) filter_type=JincFilter;
1077 if (resize_filter->filter == CubicBC) filter_type=CubicFilter;
1078 /*
1079 Report Filter Details.
1080 */
1081 support=GetResizeFilterSupport(resize_filter); /* practical_support */
1082 (void) fprintf(stdout,"# Resize Filter (for graphing)\n#\n");
1083 (void) fprintf(stdout,"# filter = %s\n",MagickOptionToMnemonic(
1084 MagickFilterOptions,filter_type));
1085 (void) fprintf(stdout,"# window = %s\n",MagickOptionToMnemonic(
1086 MagickFilterOptions, window_type));
1087 (void) fprintf(stdout,"# support = %.*g\n",GetMagickPrecision(),
1088 (double) resize_filter->support);
1089 (void) fprintf(stdout,"# win-support = %.*g\n",GetMagickPrecision(),
1090 (double) resize_filter->window_support);
1091 (void) fprintf(stdout,"# scale_blur = %.*g\n",GetMagickPrecision(),
1092 (double) resize_filter->blur);
1093 if ( filter_type == GaussianFilter )
1094 (void) fprintf(stdout,"# gaussian_sigma = %.*g\n",GetMagickPrecision(),
1095 (double) sigma);
1096 (void) fprintf(stdout,"# practical_support = %.*g\n",GetMagickPrecision(),
1097 (double) support);
1098 if ( filter_type == CubicFilter || window_type == CubicFilter )
1099 (void) fprintf(stdout,"# B,C = %.*g,%.*g\n",GetMagickPrecision(),
1100 (double) B,GetMagickPrecision(),(double) C);
1101 (void) fprintf(stdout,"\n");
1102 /*
1103 Output values of resulting filter graph -- for graphing
1104 filter result.
1105 */
1106 for (x=0.0; x <= support; x+=0.01f)
1107 (void) fprintf(stdout,"%5.2lf\t%.*g\n",x,GetMagickPrecision(),
1108 (double) GetResizeFilterWeight(resize_filter,x));
1109 /* A final value so gnuplot can graph the 'stop' properly. */
1110 (void) fprintf(stdout,"%5.2lf\t%.*g\n",support,GetMagickPrecision(),
1111 0.0);
1112 }
1113 /* Output the above once only for each image - remove setting */
1114 (void) DeleteImageArtifact((Image *) image,"filter:verbose");
1115#if defined(MAGICKCORE_OPENMP_SUPPORT)
1116 }
1117#endif
cristy3ed852e2009-09-05 21:47:34 +00001118 return(resize_filter);
1119}
1120
1121/*
1122%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1123% %
1124% %
1125% %
1126% A d a p t i v e R e s i z e I m a g e %
1127% %
1128% %
1129% %
1130%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1131%
1132% AdaptiveResizeImage() adaptively resize image with pixel resampling.
1133%
1134% The format of the AdaptiveResizeImage method is:
1135%
cristy9af9b5d2010-08-15 17:04:28 +00001136% Image *AdaptiveResizeImage(const Image *image,const size_t columns,
1137% const size_t rows,ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +00001138%
1139% A description of each parameter follows:
1140%
1141% o image: the image.
1142%
1143% o columns: the number of columns in the resized image.
1144%
1145% o rows: the number of rows in the resized image.
1146%
1147% o exception: return any errors or warnings in this structure.
1148%
1149*/
1150MagickExport Image *AdaptiveResizeImage(const Image *image,
cristybb503372010-05-27 20:51:26 +00001151 const size_t columns,const size_t rows,ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +00001152{
1153#define AdaptiveResizeImageTag "Resize/Image"
1154
cristyc4c8d132010-01-07 01:58:38 +00001155 CacheView
1156 *resize_view;
1157
cristy3ed852e2009-09-05 21:47:34 +00001158 Image
1159 *resize_image;
1160
cristy3ed852e2009-09-05 21:47:34 +00001161 MagickBooleanType
1162 proceed;
1163
1164 MagickPixelPacket
1165 pixel;
1166
1167 PointInfo
1168 offset;
1169
1170 ResampleFilter
1171 *resample_filter;
1172
cristy9af9b5d2010-08-15 17:04:28 +00001173 ssize_t
1174 y;
1175
cristy3ed852e2009-09-05 21:47:34 +00001176 /*
1177 Adaptively resize image.
1178 */
1179 assert(image != (const Image *) NULL);
1180 assert(image->signature == MagickSignature);
1181 if (image->debug != MagickFalse)
1182 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
1183 assert(exception != (ExceptionInfo *) NULL);
1184 assert(exception->signature == MagickSignature);
1185 if ((columns == 0) || (rows == 0))
1186 return((Image *) NULL);
1187 if ((columns == image->columns) && (rows == image->rows))
1188 return(CloneImage(image,0,0,MagickTrue,exception));
1189 resize_image=CloneImage(image,columns,rows,MagickTrue,exception);
1190 if (resize_image == (Image *) NULL)
1191 return((Image *) NULL);
1192 if (SetImageStorageClass(resize_image,DirectClass) == MagickFalse)
1193 {
1194 InheritException(exception,&resize_image->exception);
1195 resize_image=DestroyImage(resize_image);
1196 return((Image *) NULL);
1197 }
1198 GetMagickPixelPacket(image,&pixel);
1199 resample_filter=AcquireResampleFilter(image,exception);
cristy0c7e9eb2010-09-30 14:23:37 +00001200 (void) SetResampleFilter(resample_filter,PointFilter,1.0);
anthonyccf239d2010-09-30 04:23:46 +00001201 (void) SetResampleFilterInterpolateMethod(resample_filter,
cristy0c7e9eb2010-09-30 14:23:37 +00001202 MeshInterpolatePixel);
cristy3ed852e2009-09-05 21:47:34 +00001203 resize_view=AcquireCacheView(resize_image);
cristybb503372010-05-27 20:51:26 +00001204 for (y=0; y < (ssize_t) resize_image->rows; y++)
cristy3ed852e2009-09-05 21:47:34 +00001205 {
1206 register IndexPacket
cristyc47d1f82009-11-26 01:44:43 +00001207 *restrict resize_indexes;
cristy3ed852e2009-09-05 21:47:34 +00001208
cristybb503372010-05-27 20:51:26 +00001209 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +00001210 x;
1211
1212 register PixelPacket
cristyc47d1f82009-11-26 01:44:43 +00001213 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +00001214
1215 q=QueueCacheViewAuthenticPixels(resize_view,0,y,resize_image->columns,1,
1216 exception);
1217 if (q == (PixelPacket *) NULL)
1218 break;
1219 resize_indexes=GetCacheViewAuthenticIndexQueue(resize_view);
1220 offset.y=((MagickRealType) y*image->rows/resize_image->rows);
cristybb503372010-05-27 20:51:26 +00001221 for (x=0; x < (ssize_t) resize_image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +00001222 {
1223 offset.x=((MagickRealType) x*image->columns/resize_image->columns);
1224 (void) ResamplePixelColor(resample_filter,offset.x-0.5,offset.y-0.5,
1225 &pixel);
1226 SetPixelPacket(resize_image,&pixel,q,resize_indexes+x);
1227 q++;
1228 }
1229 if (SyncCacheViewAuthenticPixels(resize_view,exception) == MagickFalse)
1230 break;
cristy96b16132010-08-29 17:19:52 +00001231 proceed=SetImageProgress(image,AdaptiveResizeImageTag,(MagickOffsetType) y,
1232 image->rows);
cristy3ed852e2009-09-05 21:47:34 +00001233 if (proceed == MagickFalse)
1234 break;
1235 }
1236 resample_filter=DestroyResampleFilter(resample_filter);
1237 resize_view=DestroyCacheView(resize_view);
1238 return(resize_image);
1239}
1240
1241/*
1242%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1243% %
1244% %
1245% %
1246+ B e s s e l O r d e r O n e %
1247% %
1248% %
1249% %
1250%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1251%
1252% BesselOrderOne() computes the Bessel function of x of the first kind of
anthony48f77622010-10-03 14:32:31 +00001253% order 0. This is used to create the Jinc() filter function below.
cristy3ed852e2009-09-05 21:47:34 +00001254%
1255% Reduce x to |x| since j1(x)= -j1(-x), and for x in (0,8]
1256%
1257% j1(x) = x*j1(x);
1258%
1259% For x in (8,inf)
1260%
1261% j1(x) = sqrt(2/(pi*x))*(p1(x)*cos(x1)-q1(x)*sin(x1))
1262%
1263% where x1 = x-3*pi/4. Compute sin(x1) and cos(x1) as follow:
1264%
1265% cos(x1) = cos(x)cos(3pi/4)+sin(x)sin(3pi/4)
1266% = 1/sqrt(2) * (sin(x) - cos(x))
1267% sin(x1) = sin(x)cos(3pi/4)-cos(x)sin(3pi/4)
1268% = -1/sqrt(2) * (sin(x) + cos(x))
1269%
1270% The format of the BesselOrderOne method is:
1271%
1272% MagickRealType BesselOrderOne(MagickRealType x)
1273%
1274% A description of each parameter follows:
1275%
1276% o x: MagickRealType value.
1277%
1278*/
1279
1280#undef I0
1281static MagickRealType I0(MagickRealType x)
1282{
1283 MagickRealType
1284 sum,
1285 t,
1286 y;
1287
cristybb503372010-05-27 20:51:26 +00001288 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +00001289 i;
1290
1291 /*
1292 Zeroth order Bessel function of the first kind.
1293 */
1294 sum=1.0;
1295 y=x*x/4.0;
1296 t=y;
1297 for (i=2; t > MagickEpsilon; i++)
1298 {
1299 sum+=t;
1300 t*=y/((MagickRealType) i*i);
1301 }
1302 return(sum);
1303}
1304
1305#undef J1
1306static MagickRealType J1(MagickRealType x)
1307{
1308 MagickRealType
1309 p,
1310 q;
1311
cristybb503372010-05-27 20:51:26 +00001312 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +00001313 i;
1314
1315 static const double
1316 Pone[] =
1317 {
1318 0.581199354001606143928050809e+21,
1319 -0.6672106568924916298020941484e+20,
1320 0.2316433580634002297931815435e+19,
1321 -0.3588817569910106050743641413e+17,
1322 0.2908795263834775409737601689e+15,
1323 -0.1322983480332126453125473247e+13,
1324 0.3413234182301700539091292655e+10,
1325 -0.4695753530642995859767162166e+7,
1326 0.270112271089232341485679099e+4
1327 },
1328 Qone[] =
1329 {
1330 0.11623987080032122878585294e+22,
1331 0.1185770712190320999837113348e+20,
1332 0.6092061398917521746105196863e+17,
1333 0.2081661221307607351240184229e+15,
1334 0.5243710262167649715406728642e+12,
1335 0.1013863514358673989967045588e+10,
1336 0.1501793594998585505921097578e+7,
1337 0.1606931573481487801970916749e+4,
1338 0.1e+1
1339 };
1340
1341 p=Pone[8];
1342 q=Qone[8];
1343 for (i=7; i >= 0; i--)
1344 {
1345 p=p*x*x+Pone[i];
1346 q=q*x*x+Qone[i];
1347 }
1348 return(p/q);
1349}
1350
1351#undef P1
1352static MagickRealType P1(MagickRealType x)
1353{
1354 MagickRealType
1355 p,
1356 q;
1357
cristybb503372010-05-27 20:51:26 +00001358 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +00001359 i;
1360
1361 static const double
1362 Pone[] =
1363 {
1364 0.352246649133679798341724373e+5,
1365 0.62758845247161281269005675e+5,
1366 0.313539631109159574238669888e+5,
1367 0.49854832060594338434500455e+4,
1368 0.2111529182853962382105718e+3,
1369 0.12571716929145341558495e+1
1370 },
1371 Qone[] =
1372 {
1373 0.352246649133679798068390431e+5,
1374 0.626943469593560511888833731e+5,
1375 0.312404063819041039923015703e+5,
1376 0.4930396490181088979386097e+4,
1377 0.2030775189134759322293574e+3,
1378 0.1e+1
1379 };
1380
1381 p=Pone[5];
1382 q=Qone[5];
1383 for (i=4; i >= 0; i--)
1384 {
1385 p=p*(8.0/x)*(8.0/x)+Pone[i];
1386 q=q*(8.0/x)*(8.0/x)+Qone[i];
1387 }
1388 return(p/q);
1389}
1390
1391#undef Q1
1392static MagickRealType Q1(MagickRealType x)
1393{
1394 MagickRealType
1395 p,
1396 q;
1397
cristybb503372010-05-27 20:51:26 +00001398 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +00001399 i;
1400
1401 static const double
1402 Pone[] =
1403 {
1404 0.3511751914303552822533318e+3,
1405 0.7210391804904475039280863e+3,
1406 0.4259873011654442389886993e+3,
1407 0.831898957673850827325226e+2,
1408 0.45681716295512267064405e+1,
1409 0.3532840052740123642735e-1
1410 },
1411 Qone[] =
1412 {
1413 0.74917374171809127714519505e+4,
1414 0.154141773392650970499848051e+5,
1415 0.91522317015169922705904727e+4,
1416 0.18111867005523513506724158e+4,
1417 0.1038187585462133728776636e+3,
1418 0.1e+1
1419 };
1420
1421 p=Pone[5];
1422 q=Qone[5];
1423 for (i=4; i >= 0; i--)
1424 {
1425 p=p*(8.0/x)*(8.0/x)+Pone[i];
1426 q=q*(8.0/x)*(8.0/x)+Qone[i];
1427 }
1428 return(p/q);
1429}
1430
1431static MagickRealType BesselOrderOne(MagickRealType x)
1432{
1433 MagickRealType
1434 p,
1435 q;
1436
1437 if (x == 0.0)
1438 return(0.0);
1439 p=x;
1440 if (x < 0.0)
1441 x=(-x);
1442 if (x < 8.0)
1443 return(p*J1(x));
1444 q=sqrt((double) (2.0/(MagickPI*x)))*(P1(x)*(1.0/sqrt(2.0)*(sin((double) x)-
1445 cos((double) x)))-8.0/x*Q1(x)*(-1.0/sqrt(2.0)*(sin((double) x)+
1446 cos((double) x))));
1447 if (p < 0.0)
1448 q=(-q);
1449 return(q);
1450}
1451
1452/*
1453%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1454% %
1455% %
1456% %
1457+ D e s t r o y R e s i z e F i l t e r %
1458% %
1459% %
1460% %
1461%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1462%
1463% DestroyResizeFilter() destroy the resize filter.
1464%
cristya2ffd7e2010-03-10 20:50:30 +00001465% The format of the DestroyResizeFilter method is:
cristy3ed852e2009-09-05 21:47:34 +00001466%
1467% ResizeFilter *DestroyResizeFilter(ResizeFilter *resize_filter)
1468%
1469% A description of each parameter follows:
1470%
1471% o resize_filter: the resize filter.
1472%
1473*/
1474MagickExport ResizeFilter *DestroyResizeFilter(ResizeFilter *resize_filter)
1475{
1476 assert(resize_filter != (ResizeFilter *) NULL);
1477 assert(resize_filter->signature == MagickSignature);
1478 resize_filter->signature=(~MagickSignature);
1479 resize_filter=(ResizeFilter *) RelinquishMagickMemory(resize_filter);
1480 return(resize_filter);
1481}
1482
1483/*
1484%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1485% %
1486% %
1487% %
1488+ G e t R e s i z e F i l t e r S u p p o r t %
1489% %
1490% %
1491% %
1492%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1493%
1494% GetResizeFilterSupport() return the current support window size for this
1495% filter. Note that this may have been enlarged by filter:blur factor.
1496%
1497% The format of the GetResizeFilterSupport method is:
1498%
1499% MagickRealType GetResizeFilterSupport(const ResizeFilter *resize_filter)
1500%
1501% A description of each parameter follows:
1502%
1503% o filter: Image filter to use.
1504%
1505*/
1506MagickExport MagickRealType GetResizeFilterSupport(
1507 const ResizeFilter *resize_filter)
1508{
1509 assert(resize_filter != (ResizeFilter *) NULL);
1510 assert(resize_filter->signature == MagickSignature);
1511 return(resize_filter->support*resize_filter->blur);
1512}
1513
1514/*
1515%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1516% %
1517% %
1518% %
1519+ G e t R e s i z e F i l t e r W e i g h t %
1520% %
1521% %
1522% %
1523%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1524%
1525% GetResizeFilterWeight evaluates the specified resize filter at the point x
1526% which usally lies between zero and the filters current 'support' and
1527% returns the weight of the filter function at that point.
1528%
1529% The format of the GetResizeFilterWeight method is:
1530%
1531% MagickRealType GetResizeFilterWeight(const ResizeFilter *resize_filter,
1532% const MagickRealType x)
1533%
1534% A description of each parameter follows:
1535%
1536% o filter: the filter type.
1537%
1538% o x: the point.
1539%
1540*/
1541MagickExport MagickRealType GetResizeFilterWeight(
1542 const ResizeFilter *resize_filter,const MagickRealType x)
1543{
1544 MagickRealType
cristyb8385862010-09-26 01:27:51 +00001545 scale,
1546 x_blur;
cristy3ed852e2009-09-05 21:47:34 +00001547
1548 /*
1549 Windowing function - scale the weighting filter by this amount.
1550 */
1551 assert(resize_filter != (ResizeFilter *) NULL);
1552 assert(resize_filter->signature == MagickSignature);
anthony55f12332010-09-10 01:13:02 +00001553 x_blur=fabs((double) x)/resize_filter->blur; /* X offset with blur scaling */
cristy3ed852e2009-09-05 21:47:34 +00001554 if ((resize_filter->window_support < MagickEpsilon) ||
1555 (resize_filter->window == Box))
anthony55f12332010-09-10 01:13:02 +00001556 scale=1.0; /* Point or Box Filter -- avoid division by zero */
cristy3ed852e2009-09-05 21:47:34 +00001557 else
1558 {
anthony55f12332010-09-10 01:13:02 +00001559 scale=resize_filter->scale;
1560 scale=resize_filter->window(x_blur*scale,resize_filter);
cristy3ed852e2009-09-05 21:47:34 +00001561 }
anthony55f12332010-09-10 01:13:02 +00001562 return(scale*resize_filter->filter(x_blur,resize_filter));
cristy3ed852e2009-09-05 21:47:34 +00001563}
1564
1565/*
1566%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1567% %
1568% %
1569% %
1570% M a g n i f y I m a g e %
1571% %
1572% %
1573% %
1574%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1575%
1576% MagnifyImage() is a convenience method that scales an image proportionally
1577% to twice its size.
1578%
1579% The format of the MagnifyImage method is:
1580%
1581% Image *MagnifyImage(const Image *image,ExceptionInfo *exception)
1582%
1583% A description of each parameter follows:
1584%
1585% o image: the image.
1586%
1587% o exception: return any errors or warnings in this structure.
1588%
1589*/
1590MagickExport Image *MagnifyImage(const Image *image,ExceptionInfo *exception)
1591{
1592 Image
1593 *magnify_image;
1594
1595 assert(image != (Image *) NULL);
1596 assert(image->signature == MagickSignature);
1597 if (image->debug != MagickFalse)
1598 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
1599 assert(exception != (ExceptionInfo *) NULL);
1600 assert(exception->signature == MagickSignature);
1601 magnify_image=ResizeImage(image,2*image->columns,2*image->rows,CubicFilter,
1602 1.0,exception);
1603 return(magnify_image);
1604}
1605
1606/*
1607%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1608% %
1609% %
1610% %
1611% M i n i f y I m a g e %
1612% %
1613% %
1614% %
1615%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1616%
1617% MinifyImage() is a convenience method that scales an image proportionally
1618% to half its size.
1619%
1620% The format of the MinifyImage method is:
1621%
1622% Image *MinifyImage(const Image *image,ExceptionInfo *exception)
1623%
1624% A description of each parameter follows:
1625%
1626% o image: the image.
1627%
1628% o exception: return any errors or warnings in this structure.
1629%
1630*/
1631MagickExport Image *MinifyImage(const Image *image,ExceptionInfo *exception)
1632{
1633 Image
1634 *minify_image;
1635
1636 assert(image != (Image *) NULL);
1637 assert(image->signature == MagickSignature);
1638 if (image->debug != MagickFalse)
1639 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
1640 assert(exception != (ExceptionInfo *) NULL);
1641 assert(exception->signature == MagickSignature);
1642 minify_image=ResizeImage(image,image->columns/2,image->rows/2,CubicFilter,
1643 1.0,exception);
1644 return(minify_image);
1645}
1646
1647/*
1648%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1649% %
1650% %
1651% %
1652% R e s a m p l e I m a g e %
1653% %
1654% %
1655% %
1656%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1657%
1658% ResampleImage() resize image in terms of its pixel size, so that when
1659% displayed at the given resolution it will be the same size in terms of
1660% real world units as the original image at the original resolution.
1661%
1662% The format of the ResampleImage method is:
1663%
1664% Image *ResampleImage(Image *image,const double x_resolution,
1665% const double y_resolution,const FilterTypes filter,const double blur,
1666% ExceptionInfo *exception)
1667%
1668% A description of each parameter follows:
1669%
1670% o image: the image to be resized to fit the given resolution.
1671%
1672% o x_resolution: the new image x resolution.
1673%
1674% o y_resolution: the new image y resolution.
1675%
1676% o filter: Image filter to use.
1677%
1678% o blur: the blur factor where > 1 is blurry, < 1 is sharp.
1679%
1680*/
1681MagickExport Image *ResampleImage(const Image *image,const double x_resolution,
1682 const double y_resolution,const FilterTypes filter,const double blur,
1683 ExceptionInfo *exception)
1684{
1685#define ResampleImageTag "Resample/Image"
1686
1687 Image
1688 *resample_image;
1689
cristybb503372010-05-27 20:51:26 +00001690 size_t
cristy3ed852e2009-09-05 21:47:34 +00001691 height,
1692 width;
1693
1694 /*
1695 Initialize sampled image attributes.
1696 */
1697 assert(image != (const Image *) NULL);
1698 assert(image->signature == MagickSignature);
1699 if (image->debug != MagickFalse)
1700 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
1701 assert(exception != (ExceptionInfo *) NULL);
1702 assert(exception->signature == MagickSignature);
cristy9af9b5d2010-08-15 17:04:28 +00001703 width=(size_t) (x_resolution*image->columns/(image->x_resolution == 0.0 ?
1704 72.0 : image->x_resolution)+0.5);
1705 height=(size_t) (y_resolution*image->rows/(image->y_resolution == 0.0 ?
1706 72.0 : image->y_resolution)+0.5);
cristy3ed852e2009-09-05 21:47:34 +00001707 resample_image=ResizeImage(image,width,height,filter,blur,exception);
1708 if (resample_image != (Image *) NULL)
1709 {
1710 resample_image->x_resolution=x_resolution;
1711 resample_image->y_resolution=y_resolution;
1712 }
1713 return(resample_image);
1714}
1715#if defined(MAGICKCORE_LQR_DELEGATE)
1716
1717/*
1718%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1719% %
1720% %
1721% %
1722% L i q u i d R e s c a l e I m a g e %
1723% %
1724% %
1725% %
1726%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1727%
1728% LiquidRescaleImage() rescales image with seam carving.
1729%
1730% The format of the LiquidRescaleImage method is:
1731%
1732% Image *LiquidRescaleImage(const Image *image,
cristybb503372010-05-27 20:51:26 +00001733% const size_t columns,const size_t rows,
cristy3ed852e2009-09-05 21:47:34 +00001734% const double delta_x,const double rigidity,ExceptionInfo *exception)
1735%
1736% A description of each parameter follows:
1737%
1738% o image: the image.
1739%
1740% o columns: the number of columns in the rescaled image.
1741%
1742% o rows: the number of rows in the rescaled image.
1743%
1744% o delta_x: maximum seam transversal step (0 means straight seams).
1745%
1746% o rigidity: introduce a bias for non-straight seams (typically 0).
1747%
1748% o exception: return any errors or warnings in this structure.
1749%
1750*/
cristy9af9b5d2010-08-15 17:04:28 +00001751MagickExport Image *LiquidRescaleImage(const Image *image,const size_t columns,
1752 const size_t rows,const double delta_x,const double rigidity,
1753 ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +00001754{
1755#define LiquidRescaleImageTag "Rescale/Image"
1756
cristyc5c6f662010-09-22 14:23:02 +00001757 CacheView
1758 *rescale_view;
1759
cristy3ed852e2009-09-05 21:47:34 +00001760 const char
1761 *map;
1762
1763 guchar
1764 *packet;
1765
1766 Image
1767 *rescale_image;
1768
1769 int
1770 x,
1771 y;
1772
1773 LqrCarver
1774 *carver;
1775
1776 LqrRetVal
1777 lqr_status;
1778
1779 MagickBooleanType
1780 status;
1781
1782 MagickPixelPacket
1783 pixel;
1784
1785 unsigned char
1786 *pixels;
1787
1788 /*
1789 Liquid rescale image.
1790 */
1791 assert(image != (const Image *) NULL);
1792 assert(image->signature == MagickSignature);
1793 if (image->debug != MagickFalse)
1794 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
1795 assert(exception != (ExceptionInfo *) NULL);
1796 assert(exception->signature == MagickSignature);
1797 if ((columns == 0) || (rows == 0))
1798 return((Image *) NULL);
1799 if ((columns == image->columns) && (rows == image->rows))
1800 return(CloneImage(image,0,0,MagickTrue,exception));
1801 if ((columns <= 2) || (rows <= 2))
cristyd1bb3bc2010-09-07 00:43:58 +00001802 return(ResizeImage(image,columns,rows,image->filter,image->blur,exception));
cristy3ed852e2009-09-05 21:47:34 +00001803 if ((columns >= (2*image->columns)) || (rows >= (2*image->rows)))
1804 {
1805 Image
1806 *resize_image;
1807
cristybb503372010-05-27 20:51:26 +00001808 size_t
cristy3ed852e2009-09-05 21:47:34 +00001809 height,
1810 width;
1811
1812 /*
1813 Honor liquid resize size limitations.
1814 */
1815 for (width=image->columns; columns >= (2*width-1); width*=2);
1816 for (height=image->rows; rows >= (2*height-1); height*=2);
1817 resize_image=ResizeImage(image,width,height,image->filter,image->blur,
1818 exception);
1819 if (resize_image == (Image *) NULL)
1820 return((Image *) NULL);
1821 rescale_image=LiquidRescaleImage(resize_image,columns,rows,delta_x,
1822 rigidity,exception);
1823 resize_image=DestroyImage(resize_image);
1824 return(rescale_image);
1825 }
1826 map="RGB";
1827 if (image->matte == MagickFalse)
1828 map="RGBA";
1829 if (image->colorspace == CMYKColorspace)
1830 {
1831 map="CMYK";
1832 if (image->matte == MagickFalse)
1833 map="CMYKA";
1834 }
1835 pixels=(unsigned char *) AcquireQuantumMemory(image->columns,image->rows*
1836 strlen(map)*sizeof(*pixels));
1837 if (pixels == (unsigned char *) NULL)
1838 return((Image *) NULL);
1839 status=ExportImagePixels(image,0,0,image->columns,image->rows,map,CharPixel,
1840 pixels,exception);
1841 if (status == MagickFalse)
1842 {
1843 pixels=(unsigned char *) RelinquishMagickMemory(pixels);
1844 ThrowImageException(ResourceLimitError,"MemoryAllocationFailed");
1845 }
1846 carver=lqr_carver_new(pixels,image->columns,image->rows,strlen(map));
1847 if (carver == (LqrCarver *) NULL)
1848 {
1849 pixels=(unsigned char *) RelinquishMagickMemory(pixels);
1850 ThrowImageException(ResourceLimitError,"MemoryAllocationFailed");
1851 }
1852 lqr_status=lqr_carver_init(carver,(int) delta_x,rigidity);
1853 lqr_status=lqr_carver_resize(carver,columns,rows);
1854 rescale_image=CloneImage(image,lqr_carver_get_width(carver),
1855 lqr_carver_get_height(carver),MagickTrue,exception);
1856 if (rescale_image == (Image *) NULL)
1857 {
1858 pixels=(unsigned char *) RelinquishMagickMemory(pixels);
1859 return((Image *) NULL);
1860 }
1861 if (SetImageStorageClass(rescale_image,DirectClass) == MagickFalse)
1862 {
1863 InheritException(exception,&rescale_image->exception);
1864 rescale_image=DestroyImage(rescale_image);
1865 return((Image *) NULL);
1866 }
1867 GetMagickPixelPacket(rescale_image,&pixel);
1868 (void) lqr_carver_scan_reset(carver);
cristyc5c6f662010-09-22 14:23:02 +00001869 rescale_view=AcquireCacheView(rescale_image);
cristy3ed852e2009-09-05 21:47:34 +00001870 while (lqr_carver_scan(carver,&x,&y,&packet) != 0)
1871 {
1872 register IndexPacket
cristyc47d1f82009-11-26 01:44:43 +00001873 *restrict rescale_indexes;
cristy3ed852e2009-09-05 21:47:34 +00001874
1875 register PixelPacket
cristyc47d1f82009-11-26 01:44:43 +00001876 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +00001877
anthony22aad252010-09-23 06:59:07 +00001878 q=QueueCacheViewAuthenticPixels(rescale_view,x,y,1,1,exception);
cristy3ed852e2009-09-05 21:47:34 +00001879 if (q == (PixelPacket *) NULL)
1880 break;
cristyc5c6f662010-09-22 14:23:02 +00001881 rescale_indexes=GetCacheViewAuthenticIndexQueue(rescale_view);
cristy3ed852e2009-09-05 21:47:34 +00001882 pixel.red=QuantumRange*(packet[0]/255.0);
1883 pixel.green=QuantumRange*(packet[1]/255.0);
1884 pixel.blue=QuantumRange*(packet[2]/255.0);
1885 if (image->colorspace != CMYKColorspace)
1886 {
1887 if (image->matte == MagickFalse)
1888 pixel.opacity=QuantumRange*(packet[3]/255.0);
1889 }
1890 else
1891 {
1892 pixel.index=QuantumRange*(packet[3]/255.0);
1893 if (image->matte == MagickFalse)
1894 pixel.opacity=QuantumRange*(packet[4]/255.0);
1895 }
1896 SetPixelPacket(rescale_image,&pixel,q,rescale_indexes);
cristyc5c6f662010-09-22 14:23:02 +00001897 if (SyncCacheViewAuthenticPixels(rescale_view,exception) == MagickFalse)
cristy3ed852e2009-09-05 21:47:34 +00001898 break;
1899 }
cristyc5c6f662010-09-22 14:23:02 +00001900 rescale_view=DestroyCacheView(rescale_view);
cristy3ed852e2009-09-05 21:47:34 +00001901 /*
1902 Relinquish resources.
1903 */
1904 lqr_carver_destroy(carver);
1905 return(rescale_image);
1906}
1907#else
1908MagickExport Image *LiquidRescaleImage(const Image *image,
cristy9af9b5d2010-08-15 17:04:28 +00001909 const size_t magick_unused(columns),const size_t magick_unused(rows),
1910 const double magick_unused(delta_x),const double magick_unused(rigidity),
1911 ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +00001912{
1913 assert(image != (const Image *) NULL);
1914 assert(image->signature == MagickSignature);
1915 if (image->debug != MagickFalse)
1916 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
1917 assert(exception != (ExceptionInfo *) NULL);
1918 assert(exception->signature == MagickSignature);
1919 (void) ThrowMagickException(exception,GetMagickModule(),MissingDelegateError,
1920 "DelegateLibrarySupportNotBuiltIn","`%s' (LQR)",image->filename);
1921 return((Image *) NULL);
1922}
1923#endif
1924
1925/*
1926%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1927% %
1928% %
1929% %
1930% R e s i z e I m a g e %
1931% %
1932% %
1933% %
1934%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1935%
1936% ResizeImage() scales an image to the desired dimensions, using the given
cristy5d824382010-09-06 14:00:17 +00001937% filter (see AcquireFilterInfo()).
cristy3ed852e2009-09-05 21:47:34 +00001938%
1939% If an undefined filter is given the filter defaults to Mitchell for a
1940% colormapped image, a image with a matte channel, or if the image is
1941% enlarged. Otherwise the filter defaults to a Lanczos.
1942%
1943% ResizeImage() was inspired by Paul Heckbert's "zoom" program.
1944%
1945% The format of the ResizeImage method is:
1946%
cristybb503372010-05-27 20:51:26 +00001947% Image *ResizeImage(Image *image,const size_t columns,
1948% const size_t rows,const FilterTypes filter,const double blur,
cristy3ed852e2009-09-05 21:47:34 +00001949% ExceptionInfo *exception)
1950%
1951% A description of each parameter follows:
1952%
1953% o image: the image.
1954%
1955% o columns: the number of columns in the scaled image.
1956%
1957% o rows: the number of rows in the scaled image.
1958%
1959% o filter: Image filter to use.
1960%
cristy9af9b5d2010-08-15 17:04:28 +00001961% o blur: the blur factor where > 1 is blurry, < 1 is sharp. Typically set
1962% this to 1.0.
cristy3ed852e2009-09-05 21:47:34 +00001963%
1964% o exception: return any errors or warnings in this structure.
1965%
1966*/
1967
1968typedef struct _ContributionInfo
1969{
1970 MagickRealType
1971 weight;
1972
cristybb503372010-05-27 20:51:26 +00001973 ssize_t
cristy3ed852e2009-09-05 21:47:34 +00001974 pixel;
1975} ContributionInfo;
1976
1977static ContributionInfo **DestroyContributionThreadSet(
1978 ContributionInfo **contribution)
1979{
cristybb503372010-05-27 20:51:26 +00001980 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +00001981 i;
1982
1983 assert(contribution != (ContributionInfo **) NULL);
cristybb503372010-05-27 20:51:26 +00001984 for (i=0; i < (ssize_t) GetOpenMPMaximumThreads(); i++)
cristy3ed852e2009-09-05 21:47:34 +00001985 if (contribution[i] != (ContributionInfo *) NULL)
1986 contribution[i]=(ContributionInfo *) RelinquishMagickMemory(
1987 contribution[i]);
cristyb41ee102010-10-04 16:46:15 +00001988 contribution=(ContributionInfo **) RelinquishMagickMemory(contribution);
cristy3ed852e2009-09-05 21:47:34 +00001989 return(contribution);
1990}
1991
1992static ContributionInfo **AcquireContributionThreadSet(const size_t count)
1993{
cristybb503372010-05-27 20:51:26 +00001994 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +00001995 i;
1996
1997 ContributionInfo
1998 **contribution;
1999
cristybb503372010-05-27 20:51:26 +00002000 size_t
cristy3ed852e2009-09-05 21:47:34 +00002001 number_threads;
2002
2003 number_threads=GetOpenMPMaximumThreads();
cristyb41ee102010-10-04 16:46:15 +00002004 contribution=(ContributionInfo **) AcquireQuantumMemory(number_threads,
cristy3ed852e2009-09-05 21:47:34 +00002005 sizeof(*contribution));
2006 if (contribution == (ContributionInfo **) NULL)
2007 return((ContributionInfo **) NULL);
2008 (void) ResetMagickMemory(contribution,0,number_threads*sizeof(*contribution));
cristybb503372010-05-27 20:51:26 +00002009 for (i=0; i < (ssize_t) number_threads; i++)
cristy3ed852e2009-09-05 21:47:34 +00002010 {
2011 contribution[i]=(ContributionInfo *) AcquireQuantumMemory(count,
2012 sizeof(**contribution));
2013 if (contribution[i] == (ContributionInfo *) NULL)
2014 return(DestroyContributionThreadSet(contribution));
2015 }
2016 return(contribution);
2017}
2018
2019static inline double MagickMax(const double x,const double y)
2020{
2021 if (x > y)
2022 return(x);
2023 return(y);
2024}
2025
2026static inline double MagickMin(const double x,const double y)
2027{
2028 if (x < y)
2029 return(x);
2030 return(y);
2031}
2032
2033static MagickBooleanType HorizontalFilter(const ResizeFilter *resize_filter,
2034 const Image *image,Image *resize_image,const MagickRealType x_factor,
cristy9af9b5d2010-08-15 17:04:28 +00002035 const MagickSizeType span,MagickOffsetType *offset,ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +00002036{
2037#define ResizeImageTag "Resize/Image"
2038
cristyfa112112010-01-04 17:48:07 +00002039 CacheView
2040 *image_view,
2041 *resize_view;
2042
cristy3ed852e2009-09-05 21:47:34 +00002043 ClassType
2044 storage_class;
2045
2046 ContributionInfo
cristyfa112112010-01-04 17:48:07 +00002047 **restrict contributions;
cristy3ed852e2009-09-05 21:47:34 +00002048
cristy3ed852e2009-09-05 21:47:34 +00002049 MagickBooleanType
2050 status;
2051
2052 MagickPixelPacket
2053 zero;
2054
2055 MagickRealType
2056 scale,
2057 support;
2058
cristy9af9b5d2010-08-15 17:04:28 +00002059 ssize_t
2060 x;
2061
cristy3ed852e2009-09-05 21:47:34 +00002062 /*
2063 Apply filter to resize horizontally from image to resize image.
2064 */
cristy5d824382010-09-06 14:00:17 +00002065 scale=MagickMax(1.0/x_factor+MagickEpsilon,1.0);
cristy3ed852e2009-09-05 21:47:34 +00002066 support=scale*GetResizeFilterSupport(resize_filter);
2067 storage_class=support > 0.5 ? DirectClass : image->storage_class;
2068 if (SetImageStorageClass(resize_image,storage_class) == MagickFalse)
2069 {
2070 InheritException(exception,&resize_image->exception);
2071 return(MagickFalse);
2072 }
2073 if (support < 0.5)
2074 {
2075 /*
nicolas07bac812010-09-19 18:47:02 +00002076 Support too small even for nearest neighbour: Reduce to point
2077 sampling.
cristy3ed852e2009-09-05 21:47:34 +00002078 */
2079 support=(MagickRealType) 0.5;
2080 scale=1.0;
2081 }
2082 contributions=AcquireContributionThreadSet((size_t) (2.0*support+3.0));
2083 if (contributions == (ContributionInfo **) NULL)
2084 {
2085 (void) ThrowMagickException(exception,GetMagickModule(),
2086 ResourceLimitError,"MemoryAllocationFailed","`%s'",image->filename);
2087 return(MagickFalse);
2088 }
2089 status=MagickTrue;
2090 scale=1.0/scale;
2091 (void) ResetMagickMemory(&zero,0,sizeof(zero));
2092 image_view=AcquireCacheView(image);
2093 resize_view=AcquireCacheView(resize_image);
cristyb5d5f722009-11-04 03:03:49 +00002094#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristy3ed852e2009-09-05 21:47:34 +00002095 #pragma omp parallel for shared(status)
2096#endif
cristybb503372010-05-27 20:51:26 +00002097 for (x=0; x < (ssize_t) resize_image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +00002098 {
cristy3ed852e2009-09-05 21:47:34 +00002099 MagickRealType
2100 center,
2101 density;
2102
2103 register const IndexPacket
cristyc47d1f82009-11-26 01:44:43 +00002104 *restrict indexes;
cristy3ed852e2009-09-05 21:47:34 +00002105
2106 register const PixelPacket
cristyc47d1f82009-11-26 01:44:43 +00002107 *restrict p;
cristy3ed852e2009-09-05 21:47:34 +00002108
cristy03dbbd22010-09-19 23:04:47 +00002109 register ContributionInfo
2110 *restrict contribution;
2111
cristy3ed852e2009-09-05 21:47:34 +00002112 register IndexPacket
cristyc47d1f82009-11-26 01:44:43 +00002113 *restrict resize_indexes;
cristy3ed852e2009-09-05 21:47:34 +00002114
cristy3ed852e2009-09-05 21:47:34 +00002115 register PixelPacket
cristyc47d1f82009-11-26 01:44:43 +00002116 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +00002117
cristy03dbbd22010-09-19 23:04:47 +00002118 register ssize_t
2119 y;
2120
cristy9af9b5d2010-08-15 17:04:28 +00002121 ssize_t
2122 n,
2123 start,
2124 stop;
2125
cristy3ed852e2009-09-05 21:47:34 +00002126 if (status == MagickFalse)
2127 continue;
2128 center=(MagickRealType) (x+0.5)/x_factor;
cristybb503372010-05-27 20:51:26 +00002129 start=(ssize_t) MagickMax(center-support+0.5,0.0);
2130 stop=(ssize_t) MagickMin(center+support+0.5,(double) image->columns);
cristy3ed852e2009-09-05 21:47:34 +00002131 density=0.0;
2132 contribution=contributions[GetOpenMPThreadId()];
2133 for (n=0; n < (stop-start); n++)
2134 {
2135 contribution[n].pixel=start+n;
2136 contribution[n].weight=GetResizeFilterWeight(resize_filter,scale*
2137 ((MagickRealType) (start+n)-center+0.5));
2138 density+=contribution[n].weight;
2139 }
2140 if ((density != 0.0) && (density != 1.0))
2141 {
cristybb503372010-05-27 20:51:26 +00002142 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +00002143 i;
2144
2145 /*
2146 Normalize.
2147 */
2148 density=1.0/density;
2149 for (i=0; i < n; i++)
2150 contribution[i].weight*=density;
2151 }
cristy9af9b5d2010-08-15 17:04:28 +00002152 p=GetCacheViewVirtualPixels(image_view,contribution[0].pixel,0,(size_t)
2153 (contribution[n-1].pixel-contribution[0].pixel+1),image->rows,exception);
cristy3ed852e2009-09-05 21:47:34 +00002154 q=QueueCacheViewAuthenticPixels(resize_view,x,0,1,resize_image->rows,
2155 exception);
2156 if ((p == (const PixelPacket *) NULL) || (q == (PixelPacket *) NULL))
2157 {
2158 status=MagickFalse;
2159 continue;
2160 }
2161 indexes=GetCacheViewVirtualIndexQueue(image_view);
2162 resize_indexes=GetCacheViewAuthenticIndexQueue(resize_view);
cristybb503372010-05-27 20:51:26 +00002163 for (y=0; y < (ssize_t) resize_image->rows; y++)
cristy3ed852e2009-09-05 21:47:34 +00002164 {
cristy3ed852e2009-09-05 21:47:34 +00002165 MagickPixelPacket
2166 pixel;
2167
2168 MagickRealType
2169 alpha;
2170
cristybb503372010-05-27 20:51:26 +00002171 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +00002172 i;
2173
cristy9af9b5d2010-08-15 17:04:28 +00002174 ssize_t
2175 j;
2176
cristy3ed852e2009-09-05 21:47:34 +00002177 pixel=zero;
2178 if (image->matte == MagickFalse)
2179 {
2180 for (i=0; i < n; i++)
2181 {
2182 j=y*(contribution[n-1].pixel-contribution[0].pixel+1)+
2183 (contribution[i].pixel-contribution[0].pixel);
2184 alpha=contribution[i].weight;
2185 pixel.red+=alpha*(p+j)->red;
2186 pixel.green+=alpha*(p+j)->green;
2187 pixel.blue+=alpha*(p+j)->blue;
2188 pixel.opacity+=alpha*(p+j)->opacity;
2189 }
cristyce70c172010-01-07 17:15:30 +00002190 SetRedPixelComponent(q,ClampRedPixelComponent(&pixel));
2191 SetGreenPixelComponent(q,ClampGreenPixelComponent(&pixel));
2192 SetBluePixelComponent(q,ClampBluePixelComponent(&pixel));
2193 SetOpacityPixelComponent(q,ClampOpacityPixelComponent(&pixel));
cristy3ed852e2009-09-05 21:47:34 +00002194 if ((image->colorspace == CMYKColorspace) &&
2195 (resize_image->colorspace == CMYKColorspace))
2196 {
2197 for (i=0; i < n; i++)
2198 {
2199 j=y*(contribution[n-1].pixel-contribution[0].pixel+1)+
2200 (contribution[i].pixel-contribution[0].pixel);
2201 alpha=contribution[i].weight;
2202 pixel.index+=alpha*indexes[j];
2203 }
cristyce70c172010-01-07 17:15:30 +00002204 resize_indexes[y]=(IndexPacket) ClampToQuantum(pixel.index);
cristy3ed852e2009-09-05 21:47:34 +00002205 }
2206 }
2207 else
2208 {
2209 MagickRealType
2210 gamma;
2211
2212 gamma=0.0;
2213 for (i=0; i < n; i++)
2214 {
2215 j=y*(contribution[n-1].pixel-contribution[0].pixel+1)+
2216 (contribution[i].pixel-contribution[0].pixel);
cristy46f08202010-01-10 04:04:21 +00002217 alpha=contribution[i].weight*QuantumScale*
2218 GetAlphaPixelComponent(p+j);
cristy3ed852e2009-09-05 21:47:34 +00002219 pixel.red+=alpha*(p+j)->red;
2220 pixel.green+=alpha*(p+j)->green;
2221 pixel.blue+=alpha*(p+j)->blue;
2222 pixel.opacity+=contribution[i].weight*(p+j)->opacity;
2223 gamma+=alpha;
2224 }
2225 gamma=1.0/(fabs((double) gamma) <= MagickEpsilon ? 1.0 : gamma);
cristyce70c172010-01-07 17:15:30 +00002226 q->red=ClampToQuantum(gamma*GetRedPixelComponent(&pixel));
2227 q->green=ClampToQuantum(gamma*GetGreenPixelComponent(&pixel));
2228 q->blue=ClampToQuantum(gamma*GetBluePixelComponent(&pixel));
2229 SetOpacityPixelComponent(q,ClampOpacityPixelComponent(&pixel));
cristy3ed852e2009-09-05 21:47:34 +00002230 if ((image->colorspace == CMYKColorspace) &&
2231 (resize_image->colorspace == CMYKColorspace))
2232 {
2233 for (i=0; i < n; i++)
2234 {
2235 j=y*(contribution[n-1].pixel-contribution[0].pixel+1)+
2236 (contribution[i].pixel-contribution[0].pixel);
cristy46f08202010-01-10 04:04:21 +00002237 alpha=contribution[i].weight*QuantumScale*
2238 GetAlphaPixelComponent(p+j);
cristyc197d1c2009-10-13 19:56:53 +00002239 pixel.index+=alpha*indexes[j];
cristy3ed852e2009-09-05 21:47:34 +00002240 }
cristyce70c172010-01-07 17:15:30 +00002241 resize_indexes[y]=(IndexPacket) ClampToQuantum(gamma*
2242 GetIndexPixelComponent(&pixel));
cristy3ed852e2009-09-05 21:47:34 +00002243 }
2244 }
2245 if ((resize_image->storage_class == PseudoClass) &&
2246 (image->storage_class == PseudoClass))
2247 {
cristybb503372010-05-27 20:51:26 +00002248 i=(ssize_t) (MagickMin(MagickMax(center,(double) start),(double) stop-
cristy3ed852e2009-09-05 21:47:34 +00002249 1.0)+0.5);
2250 j=y*(contribution[n-1].pixel-contribution[0].pixel+1)+
2251 (contribution[i-start].pixel-contribution[0].pixel);
2252 resize_indexes[y]=indexes[j];
2253 }
2254 q++;
2255 }
2256 if (SyncCacheViewAuthenticPixels(resize_view,exception) == MagickFalse)
2257 status=MagickFalse;
2258 if (image->progress_monitor != (MagickProgressMonitor) NULL)
2259 {
2260 MagickBooleanType
2261 proceed;
2262
cristyb5d5f722009-11-04 03:03:49 +00002263#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristy3ed852e2009-09-05 21:47:34 +00002264 #pragma omp critical (MagickCore_HorizontalFilter)
2265#endif
cristy9af9b5d2010-08-15 17:04:28 +00002266 proceed=SetImageProgress(image,ResizeImageTag,(*offset)++,span);
cristy3ed852e2009-09-05 21:47:34 +00002267 if (proceed == MagickFalse)
2268 status=MagickFalse;
2269 }
2270 }
2271 resize_view=DestroyCacheView(resize_view);
2272 image_view=DestroyCacheView(image_view);
2273 contributions=DestroyContributionThreadSet(contributions);
2274 return(status);
2275}
2276
2277static MagickBooleanType VerticalFilter(const ResizeFilter *resize_filter,
2278 const Image *image,Image *resize_image,const MagickRealType y_factor,
cristy9af9b5d2010-08-15 17:04:28 +00002279 const MagickSizeType span,MagickOffsetType *offset,ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +00002280{
cristyfa112112010-01-04 17:48:07 +00002281 CacheView
2282 *image_view,
2283 *resize_view;
2284
cristy3ed852e2009-09-05 21:47:34 +00002285 ClassType
2286 storage_class;
2287
2288 ContributionInfo
cristyfa112112010-01-04 17:48:07 +00002289 **restrict contributions;
cristy3ed852e2009-09-05 21:47:34 +00002290
cristy3ed852e2009-09-05 21:47:34 +00002291 MagickBooleanType
2292 status;
2293
2294 MagickPixelPacket
2295 zero;
2296
2297 MagickRealType
2298 scale,
2299 support;
2300
cristy9af9b5d2010-08-15 17:04:28 +00002301 ssize_t
2302 y;
2303
cristy3ed852e2009-09-05 21:47:34 +00002304 /*
cristy9af9b5d2010-08-15 17:04:28 +00002305 Apply filter to resize vertically from image to resize image.
cristy3ed852e2009-09-05 21:47:34 +00002306 */
cristy5d824382010-09-06 14:00:17 +00002307 scale=MagickMax(1.0/y_factor+MagickEpsilon,1.0);
cristy3ed852e2009-09-05 21:47:34 +00002308 support=scale*GetResizeFilterSupport(resize_filter);
2309 storage_class=support > 0.5 ? DirectClass : image->storage_class;
2310 if (SetImageStorageClass(resize_image,storage_class) == MagickFalse)
2311 {
2312 InheritException(exception,&resize_image->exception);
2313 return(MagickFalse);
2314 }
2315 if (support < 0.5)
2316 {
2317 /*
nicolas07bac812010-09-19 18:47:02 +00002318 Support too small even for nearest neighbour: Reduce to point
2319 sampling.
cristy3ed852e2009-09-05 21:47:34 +00002320 */
2321 support=(MagickRealType) 0.5;
2322 scale=1.0;
2323 }
2324 contributions=AcquireContributionThreadSet((size_t) (2.0*support+3.0));
2325 if (contributions == (ContributionInfo **) NULL)
2326 {
2327 (void) ThrowMagickException(exception,GetMagickModule(),
2328 ResourceLimitError,"MemoryAllocationFailed","`%s'",image->filename);
2329 return(MagickFalse);
2330 }
2331 status=MagickTrue;
2332 scale=1.0/scale;
2333 (void) ResetMagickMemory(&zero,0,sizeof(zero));
2334 image_view=AcquireCacheView(image);
2335 resize_view=AcquireCacheView(resize_image);
cristyb5d5f722009-11-04 03:03:49 +00002336#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristy3ed852e2009-09-05 21:47:34 +00002337 #pragma omp parallel for shared(status)
2338#endif
cristybb503372010-05-27 20:51:26 +00002339 for (y=0; y < (ssize_t) resize_image->rows; y++)
cristy3ed852e2009-09-05 21:47:34 +00002340 {
cristy3ed852e2009-09-05 21:47:34 +00002341 MagickRealType
2342 center,
2343 density;
2344
2345 register const IndexPacket
cristyc47d1f82009-11-26 01:44:43 +00002346 *restrict indexes;
cristy3ed852e2009-09-05 21:47:34 +00002347
2348 register const PixelPacket
cristyc47d1f82009-11-26 01:44:43 +00002349 *restrict p;
cristy3ed852e2009-09-05 21:47:34 +00002350
cristy03dbbd22010-09-19 23:04:47 +00002351 register ContributionInfo
2352 *restrict contribution;
2353
cristy3ed852e2009-09-05 21:47:34 +00002354 register IndexPacket
cristyc47d1f82009-11-26 01:44:43 +00002355 *restrict resize_indexes;
cristy3ed852e2009-09-05 21:47:34 +00002356
cristy9af9b5d2010-08-15 17:04:28 +00002357 register PixelPacket
2358 *restrict q;
2359
cristybb503372010-05-27 20:51:26 +00002360 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +00002361 x;
2362
cristy9af9b5d2010-08-15 17:04:28 +00002363 ssize_t
2364 n,
2365 start,
2366 stop;
cristy3ed852e2009-09-05 21:47:34 +00002367
2368 if (status == MagickFalse)
2369 continue;
cristy679e6962010-03-18 00:42:45 +00002370 center=(MagickRealType) (y+0.5)/y_factor;
cristybb503372010-05-27 20:51:26 +00002371 start=(ssize_t) MagickMax(center-support+0.5,0.0);
2372 stop=(ssize_t) MagickMin(center+support+0.5,(double) image->rows);
cristy3ed852e2009-09-05 21:47:34 +00002373 density=0.0;
2374 contribution=contributions[GetOpenMPThreadId()];
2375 for (n=0; n < (stop-start); n++)
2376 {
2377 contribution[n].pixel=start+n;
2378 contribution[n].weight=GetResizeFilterWeight(resize_filter,scale*
2379 ((MagickRealType) (start+n)-center+0.5));
2380 density+=contribution[n].weight;
2381 }
2382 if ((density != 0.0) && (density != 1.0))
2383 {
cristybb503372010-05-27 20:51:26 +00002384 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +00002385 i;
2386
2387 /*
2388 Normalize.
2389 */
2390 density=1.0/density;
2391 for (i=0; i < n; i++)
2392 contribution[i].weight*=density;
2393 }
2394 p=GetCacheViewVirtualPixels(image_view,0,contribution[0].pixel,
cristy9af9b5d2010-08-15 17:04:28 +00002395 image->columns,(size_t) (contribution[n-1].pixel-contribution[0].pixel+1),
2396 exception);
cristy3ed852e2009-09-05 21:47:34 +00002397 q=QueueCacheViewAuthenticPixels(resize_view,0,y,resize_image->columns,1,
2398 exception);
2399 if ((p == (const PixelPacket *) NULL) || (q == (PixelPacket *) NULL))
2400 {
2401 status=MagickFalse;
2402 continue;
2403 }
2404 indexes=GetCacheViewVirtualIndexQueue(image_view);
2405 resize_indexes=GetCacheViewAuthenticIndexQueue(resize_view);
cristybb503372010-05-27 20:51:26 +00002406 for (x=0; x < (ssize_t) resize_image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +00002407 {
cristy3ed852e2009-09-05 21:47:34 +00002408 MagickPixelPacket
2409 pixel;
2410
2411 MagickRealType
2412 alpha;
2413
cristybb503372010-05-27 20:51:26 +00002414 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +00002415 i;
2416
cristy9af9b5d2010-08-15 17:04:28 +00002417 ssize_t
2418 j;
2419
cristy3ed852e2009-09-05 21:47:34 +00002420 pixel=zero;
2421 if (image->matte == MagickFalse)
2422 {
2423 for (i=0; i < n; i++)
2424 {
cristybb503372010-05-27 20:51:26 +00002425 j=(ssize_t) ((contribution[i].pixel-contribution[0].pixel)*
cristy3ed852e2009-09-05 21:47:34 +00002426 image->columns+x);
2427 alpha=contribution[i].weight;
2428 pixel.red+=alpha*(p+j)->red;
2429 pixel.green+=alpha*(p+j)->green;
2430 pixel.blue+=alpha*(p+j)->blue;
2431 pixel.opacity+=alpha*(p+j)->opacity;
2432 }
cristyce70c172010-01-07 17:15:30 +00002433 SetRedPixelComponent(q,ClampRedPixelComponent(&pixel));
2434 SetGreenPixelComponent(q,ClampGreenPixelComponent(&pixel));
2435 SetBluePixelComponent(q,ClampBluePixelComponent(&pixel));
2436 SetOpacityPixelComponent(q,ClampOpacityPixelComponent(&pixel));
cristy3ed852e2009-09-05 21:47:34 +00002437 if ((image->colorspace == CMYKColorspace) &&
2438 (resize_image->colorspace == CMYKColorspace))
2439 {
2440 for (i=0; i < n; i++)
2441 {
cristybb503372010-05-27 20:51:26 +00002442 j=(ssize_t) ((contribution[i].pixel-contribution[0].pixel)*
cristy3ed852e2009-09-05 21:47:34 +00002443 image->columns+x);
2444 alpha=contribution[i].weight;
2445 pixel.index+=alpha*indexes[j];
2446 }
cristyce70c172010-01-07 17:15:30 +00002447 resize_indexes[x]=(IndexPacket) ClampToQuantum(pixel.index);
cristy3ed852e2009-09-05 21:47:34 +00002448 }
2449 }
2450 else
2451 {
2452 MagickRealType
2453 gamma;
2454
2455 gamma=0.0;
2456 for (i=0; i < n; i++)
2457 {
cristybb503372010-05-27 20:51:26 +00002458 j=(ssize_t) ((contribution[i].pixel-contribution[0].pixel)*
cristy3ed852e2009-09-05 21:47:34 +00002459 image->columns+x);
cristy46f08202010-01-10 04:04:21 +00002460 alpha=contribution[i].weight*QuantumScale*
2461 GetAlphaPixelComponent(p+j);
cristy3ed852e2009-09-05 21:47:34 +00002462 pixel.red+=alpha*(p+j)->red;
2463 pixel.green+=alpha*(p+j)->green;
2464 pixel.blue+=alpha*(p+j)->blue;
2465 pixel.opacity+=contribution[i].weight*(p+j)->opacity;
2466 gamma+=alpha;
2467 }
2468 gamma=1.0/(fabs((double) gamma) <= MagickEpsilon ? 1.0 : gamma);
cristyce70c172010-01-07 17:15:30 +00002469 q->red=ClampToQuantum(gamma*GetRedPixelComponent(&pixel));
2470 q->green=ClampToQuantum(gamma*GetGreenPixelComponent(&pixel));
2471 q->blue=ClampToQuantum(gamma*GetBluePixelComponent(&pixel));
2472 SetOpacityPixelComponent(q,ClampOpacityPixelComponent(&pixel));
cristy3ed852e2009-09-05 21:47:34 +00002473 if ((image->colorspace == CMYKColorspace) &&
2474 (resize_image->colorspace == CMYKColorspace))
2475 {
2476 for (i=0; i < n; i++)
2477 {
cristybb503372010-05-27 20:51:26 +00002478 j=(ssize_t) ((contribution[i].pixel-contribution[0].pixel)*
cristy3ed852e2009-09-05 21:47:34 +00002479 image->columns+x);
cristy46f08202010-01-10 04:04:21 +00002480 alpha=contribution[i].weight*QuantumScale*
2481 GetAlphaPixelComponent(p+j);
cristy3ed852e2009-09-05 21:47:34 +00002482 pixel.index+=alpha*indexes[j];
2483 }
cristyce70c172010-01-07 17:15:30 +00002484 resize_indexes[x]=(IndexPacket) ClampToQuantum(gamma*
2485 GetIndexPixelComponent(&pixel));
cristy3ed852e2009-09-05 21:47:34 +00002486 }
2487 }
2488 if ((resize_image->storage_class == PseudoClass) &&
2489 (image->storage_class == PseudoClass))
2490 {
cristybb503372010-05-27 20:51:26 +00002491 i=(ssize_t) (MagickMin(MagickMax(center,(double) start),(double) stop-
cristy3ed852e2009-09-05 21:47:34 +00002492 1.0)+0.5);
cristybb503372010-05-27 20:51:26 +00002493 j=(ssize_t) ((contribution[i-start].pixel-contribution[0].pixel)*
cristy3ed852e2009-09-05 21:47:34 +00002494 image->columns+x);
2495 resize_indexes[x]=indexes[j];
2496 }
2497 q++;
2498 }
2499 if (SyncCacheViewAuthenticPixels(resize_view,exception) == MagickFalse)
2500 status=MagickFalse;
2501 if (image->progress_monitor != (MagickProgressMonitor) NULL)
2502 {
2503 MagickBooleanType
2504 proceed;
2505
cristyb5d5f722009-11-04 03:03:49 +00002506#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristy3ed852e2009-09-05 21:47:34 +00002507 #pragma omp critical (MagickCore_VerticalFilter)
2508#endif
cristy9af9b5d2010-08-15 17:04:28 +00002509 proceed=SetImageProgress(image,ResizeImageTag,(*offset)++,span);
cristy3ed852e2009-09-05 21:47:34 +00002510 if (proceed == MagickFalse)
2511 status=MagickFalse;
2512 }
2513 }
2514 resize_view=DestroyCacheView(resize_view);
2515 image_view=DestroyCacheView(image_view);
2516 contributions=DestroyContributionThreadSet(contributions);
2517 return(status);
2518}
2519
cristybb503372010-05-27 20:51:26 +00002520MagickExport Image *ResizeImage(const Image *image,const size_t columns,
2521 const size_t rows,const FilterTypes filter,const double blur,
cristy3ed852e2009-09-05 21:47:34 +00002522 ExceptionInfo *exception)
2523{
2524#define WorkLoadFactor 0.265
2525
2526 FilterTypes
2527 filter_type;
2528
2529 Image
2530 *filter_image,
2531 *resize_image;
2532
cristy9af9b5d2010-08-15 17:04:28 +00002533 MagickOffsetType
2534 offset;
2535
cristy3ed852e2009-09-05 21:47:34 +00002536 MagickRealType
2537 x_factor,
2538 y_factor;
2539
2540 MagickSizeType
2541 span;
2542
2543 MagickStatusType
2544 status;
2545
2546 ResizeFilter
2547 *resize_filter;
2548
cristy3ed852e2009-09-05 21:47:34 +00002549 /*
2550 Acquire resize image.
2551 */
2552 assert(image != (Image *) NULL);
2553 assert(image->signature == MagickSignature);
2554 if (image->debug != MagickFalse)
2555 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
2556 assert(exception != (ExceptionInfo *) NULL);
2557 assert(exception->signature == MagickSignature);
2558 if ((columns == 0) || (rows == 0))
2559 ThrowImageException(ImageError,"NegativeOrZeroImageSize");
2560 if ((columns == image->columns) && (rows == image->rows) &&
2561 (filter == UndefinedFilter) && (blur == 1.0))
2562 return(CloneImage(image,0,0,MagickTrue,exception));
2563 resize_image=CloneImage(image,columns,rows,MagickTrue,exception);
2564 if (resize_image == (Image *) NULL)
2565 return(resize_image);
2566 /*
2567 Acquire resize filter.
2568 */
2569 x_factor=(MagickRealType) columns/(MagickRealType) image->columns;
2570 y_factor=(MagickRealType) rows/(MagickRealType) image->rows;
2571 if ((x_factor*y_factor) > WorkLoadFactor)
2572 filter_image=CloneImage(image,columns,image->rows,MagickTrue,exception);
2573 else
2574 filter_image=CloneImage(image,image->columns,rows,MagickTrue,exception);
2575 if (filter_image == (Image *) NULL)
2576 return(DestroyImage(resize_image));
2577 filter_type=LanczosFilter;
2578 if (filter != UndefinedFilter)
2579 filter_type=filter;
2580 else
2581 if ((x_factor == 1.0) && (y_factor == 1.0))
2582 filter_type=PointFilter;
2583 else
2584 if ((image->storage_class == PseudoClass) ||
2585 (image->matte != MagickFalse) || ((x_factor*y_factor) > 1.0))
2586 filter_type=MitchellFilter;
2587 resize_filter=AcquireResizeFilter(image,filter_type,blur,MagickFalse,
2588 exception);
2589 /*
2590 Resize image.
2591 */
cristy9af9b5d2010-08-15 17:04:28 +00002592 offset=0;
cristy3ed852e2009-09-05 21:47:34 +00002593 if ((x_factor*y_factor) > WorkLoadFactor)
2594 {
2595 span=(MagickSizeType) (filter_image->columns+rows);
2596 status=HorizontalFilter(resize_filter,image,filter_image,x_factor,span,
cristy9af9b5d2010-08-15 17:04:28 +00002597 &offset,exception);
cristy3ed852e2009-09-05 21:47:34 +00002598 status&=VerticalFilter(resize_filter,filter_image,resize_image,y_factor,
cristy9af9b5d2010-08-15 17:04:28 +00002599 span,&offset,exception);
cristy3ed852e2009-09-05 21:47:34 +00002600 }
2601 else
2602 {
2603 span=(MagickSizeType) (filter_image->rows+columns);
2604 status=VerticalFilter(resize_filter,image,filter_image,y_factor,span,
cristy9af9b5d2010-08-15 17:04:28 +00002605 &offset,exception);
cristy3ed852e2009-09-05 21:47:34 +00002606 status&=HorizontalFilter(resize_filter,filter_image,resize_image,x_factor,
cristy9af9b5d2010-08-15 17:04:28 +00002607 span,&offset,exception);
cristy3ed852e2009-09-05 21:47:34 +00002608 }
2609 /*
2610 Free resources.
2611 */
2612 filter_image=DestroyImage(filter_image);
2613 resize_filter=DestroyResizeFilter(resize_filter);
2614 if ((status == MagickFalse) || (resize_image == (Image *) NULL))
2615 return((Image *) NULL);
2616 resize_image->type=image->type;
2617 return(resize_image);
2618}
2619
2620/*
2621%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2622% %
2623% %
2624% %
2625% S a m p l e I m a g e %
2626% %
2627% %
2628% %
2629%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2630%
2631% SampleImage() scales an image to the desired dimensions with pixel
2632% sampling. Unlike other scaling methods, this method does not introduce
2633% any additional color into the scaled image.
2634%
2635% The format of the SampleImage method is:
2636%
cristybb503372010-05-27 20:51:26 +00002637% Image *SampleImage(const Image *image,const size_t columns,
2638% const size_t rows,ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +00002639%
2640% A description of each parameter follows:
2641%
2642% o image: the image.
2643%
2644% o columns: the number of columns in the sampled image.
2645%
2646% o rows: the number of rows in the sampled image.
2647%
2648% o exception: return any errors or warnings in this structure.
2649%
2650*/
cristybb503372010-05-27 20:51:26 +00002651MagickExport Image *SampleImage(const Image *image,const size_t columns,
2652 const size_t rows,ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +00002653{
2654#define SampleImageTag "Sample/Image"
2655
cristyc4c8d132010-01-07 01:58:38 +00002656 CacheView
2657 *image_view,
2658 *sample_view;
2659
cristy3ed852e2009-09-05 21:47:34 +00002660 Image
2661 *sample_image;
2662
cristy3ed852e2009-09-05 21:47:34 +00002663 MagickBooleanType
2664 status;
2665
cristy5f959472010-05-27 22:19:46 +00002666 MagickOffsetType
2667 progress;
2668
cristybb503372010-05-27 20:51:26 +00002669 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +00002670 x;
2671
cristy5f959472010-05-27 22:19:46 +00002672 ssize_t
2673 *x_offset,
2674 y;
2675
cristy3ed852e2009-09-05 21:47:34 +00002676 /*
2677 Initialize sampled image attributes.
2678 */
2679 assert(image != (const Image *) NULL);
2680 assert(image->signature == MagickSignature);
2681 if (image->debug != MagickFalse)
2682 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
2683 assert(exception != (ExceptionInfo *) NULL);
2684 assert(exception->signature == MagickSignature);
2685 if ((columns == 0) || (rows == 0))
2686 ThrowImageException(ImageError,"NegativeOrZeroImageSize");
2687 if ((columns == image->columns) && (rows == image->rows))
2688 return(CloneImage(image,0,0,MagickTrue,exception));
2689 sample_image=CloneImage(image,columns,rows,MagickTrue,exception);
2690 if (sample_image == (Image *) NULL)
2691 return((Image *) NULL);
2692 /*
2693 Allocate scan line buffer and column offset buffers.
2694 */
cristybb503372010-05-27 20:51:26 +00002695 x_offset=(ssize_t *) AcquireQuantumMemory((size_t) sample_image->columns,
cristy3ed852e2009-09-05 21:47:34 +00002696 sizeof(*x_offset));
cristybb503372010-05-27 20:51:26 +00002697 if (x_offset == (ssize_t *) NULL)
cristy3ed852e2009-09-05 21:47:34 +00002698 {
2699 sample_image=DestroyImage(sample_image);
2700 ThrowImageException(ResourceLimitError,"MemoryAllocationFailed");
2701 }
cristybb503372010-05-27 20:51:26 +00002702 for (x=0; x < (ssize_t) sample_image->columns; x++)
2703 x_offset[x]=(ssize_t) (((MagickRealType) x+0.5)*image->columns/
cristy3ed852e2009-09-05 21:47:34 +00002704 sample_image->columns);
2705 /*
2706 Sample each row.
2707 */
2708 status=MagickTrue;
2709 progress=0;
2710 image_view=AcquireCacheView(image);
2711 sample_view=AcquireCacheView(sample_image);
cristyb5d5f722009-11-04 03:03:49 +00002712#if defined(MAGICKCORE_OPENMP_SUPPORT)
2713 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +00002714#endif
cristybb503372010-05-27 20:51:26 +00002715 for (y=0; y < (ssize_t) sample_image->rows; y++)
cristy3ed852e2009-09-05 21:47:34 +00002716 {
cristy3ed852e2009-09-05 21:47:34 +00002717 register const IndexPacket
cristyc47d1f82009-11-26 01:44:43 +00002718 *restrict indexes;
cristy3ed852e2009-09-05 21:47:34 +00002719
2720 register const PixelPacket
cristyc47d1f82009-11-26 01:44:43 +00002721 *restrict p;
cristy3ed852e2009-09-05 21:47:34 +00002722
2723 register IndexPacket
cristyc47d1f82009-11-26 01:44:43 +00002724 *restrict sample_indexes;
cristy3ed852e2009-09-05 21:47:34 +00002725
cristy3ed852e2009-09-05 21:47:34 +00002726 register PixelPacket
cristyc47d1f82009-11-26 01:44:43 +00002727 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +00002728
cristy03dbbd22010-09-19 23:04:47 +00002729 register ssize_t
2730 x;
2731
cristy9af9b5d2010-08-15 17:04:28 +00002732 ssize_t
2733 y_offset;
2734
cristy3ed852e2009-09-05 21:47:34 +00002735 if (status == MagickFalse)
2736 continue;
cristy9af9b5d2010-08-15 17:04:28 +00002737 y_offset=(ssize_t) (((MagickRealType) y+0.5)*image->rows/
2738 sample_image->rows);
cristy3ed852e2009-09-05 21:47:34 +00002739 p=GetCacheViewVirtualPixels(image_view,0,y_offset,image->columns,1,
2740 exception);
2741 q=QueueCacheViewAuthenticPixels(sample_view,0,y,sample_image->columns,1,
2742 exception);
2743 if ((p == (const PixelPacket *) NULL) || (q == (PixelPacket *) NULL))
2744 {
2745 status=MagickFalse;
2746 continue;
2747 }
2748 indexes=GetCacheViewAuthenticIndexQueue(image_view);
2749 sample_indexes=GetCacheViewAuthenticIndexQueue(sample_view);
2750 /*
2751 Sample each column.
2752 */
cristybb503372010-05-27 20:51:26 +00002753 for (x=0; x < (ssize_t) sample_image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +00002754 *q++=p[x_offset[x]];
2755 if ((image->storage_class == PseudoClass) ||
2756 (image->colorspace == CMYKColorspace))
cristybb503372010-05-27 20:51:26 +00002757 for (x=0; x < (ssize_t) sample_image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +00002758 sample_indexes[x]=indexes[x_offset[x]];
2759 if (SyncCacheViewAuthenticPixels(sample_view,exception) == MagickFalse)
2760 status=MagickFalse;
2761 if (image->progress_monitor != (MagickProgressMonitor) NULL)
2762 {
2763 MagickBooleanType
2764 proceed;
2765
cristyb5d5f722009-11-04 03:03:49 +00002766#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristy3ed852e2009-09-05 21:47:34 +00002767 #pragma omp critical (MagickCore_SampleImage)
2768#endif
2769 proceed=SetImageProgress(image,SampleImageTag,progress++,image->rows);
2770 if (proceed == MagickFalse)
2771 status=MagickFalse;
2772 }
2773 }
2774 image_view=DestroyCacheView(image_view);
2775 sample_view=DestroyCacheView(sample_view);
cristybb503372010-05-27 20:51:26 +00002776 x_offset=(ssize_t *) RelinquishMagickMemory(x_offset);
cristy3ed852e2009-09-05 21:47:34 +00002777 sample_image->type=image->type;
2778 return(sample_image);
2779}
2780
2781/*
2782%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2783% %
2784% %
2785% %
2786% S c a l e I m a g e %
2787% %
2788% %
2789% %
2790%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2791%
2792% ScaleImage() changes the size of an image to the given dimensions.
2793%
2794% The format of the ScaleImage method is:
2795%
cristybb503372010-05-27 20:51:26 +00002796% Image *ScaleImage(const Image *image,const size_t columns,
2797% const size_t rows,ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +00002798%
2799% A description of each parameter follows:
2800%
2801% o image: the image.
2802%
2803% o columns: the number of columns in the scaled image.
2804%
2805% o rows: the number of rows in the scaled image.
2806%
2807% o exception: return any errors or warnings in this structure.
2808%
2809*/
cristybb503372010-05-27 20:51:26 +00002810MagickExport Image *ScaleImage(const Image *image,const size_t columns,
2811 const size_t rows,ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +00002812{
2813#define ScaleImageTag "Scale/Image"
2814
cristyed6cb232010-01-20 03:07:53 +00002815 CacheView
2816 *image_view,
2817 *scale_view;
2818
cristy3ed852e2009-09-05 21:47:34 +00002819 Image
2820 *scale_image;
2821
cristy3ed852e2009-09-05 21:47:34 +00002822 MagickBooleanType
2823 next_column,
2824 next_row,
2825 proceed;
2826
2827 MagickPixelPacket
2828 pixel,
2829 *scale_scanline,
2830 *scanline,
2831 *x_vector,
2832 *y_vector,
2833 zero;
2834
cristy3ed852e2009-09-05 21:47:34 +00002835 PointInfo
2836 scale,
2837 span;
2838
cristybb503372010-05-27 20:51:26 +00002839 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +00002840 i;
2841
cristy9af9b5d2010-08-15 17:04:28 +00002842 ssize_t
2843 number_rows,
2844 y;
2845
cristy3ed852e2009-09-05 21:47:34 +00002846 /*
2847 Initialize scaled image attributes.
2848 */
2849 assert(image != (const Image *) NULL);
2850 assert(image->signature == MagickSignature);
2851 if (image->debug != MagickFalse)
2852 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
2853 assert(exception != (ExceptionInfo *) NULL);
2854 assert(exception->signature == MagickSignature);
2855 if ((columns == 0) || (rows == 0))
2856 return((Image *) NULL);
2857 if ((columns == image->columns) && (rows == image->rows))
2858 return(CloneImage(image,0,0,MagickTrue,exception));
2859 scale_image=CloneImage(image,columns,rows,MagickTrue,exception);
2860 if (scale_image == (Image *) NULL)
2861 return((Image *) NULL);
2862 if (SetImageStorageClass(scale_image,DirectClass) == MagickFalse)
2863 {
2864 InheritException(exception,&scale_image->exception);
2865 scale_image=DestroyImage(scale_image);
2866 return((Image *) NULL);
2867 }
2868 /*
2869 Allocate memory.
2870 */
2871 x_vector=(MagickPixelPacket *) AcquireQuantumMemory((size_t) image->columns,
2872 sizeof(*x_vector));
2873 scanline=x_vector;
2874 if (image->rows != scale_image->rows)
2875 scanline=(MagickPixelPacket *) AcquireQuantumMemory((size_t) image->columns,
2876 sizeof(*scanline));
2877 scale_scanline=(MagickPixelPacket *) AcquireQuantumMemory((size_t)
2878 scale_image->columns,sizeof(*scale_scanline));
2879 y_vector=(MagickPixelPacket *) AcquireQuantumMemory((size_t) image->columns,
2880 sizeof(*y_vector));
2881 if ((scanline == (MagickPixelPacket *) NULL) ||
2882 (scale_scanline == (MagickPixelPacket *) NULL) ||
2883 (x_vector == (MagickPixelPacket *) NULL) ||
2884 (y_vector == (MagickPixelPacket *) NULL))
2885 {
2886 scale_image=DestroyImage(scale_image);
2887 ThrowImageException(ResourceLimitError,"MemoryAllocationFailed");
2888 }
2889 /*
2890 Scale image.
2891 */
2892 number_rows=0;
2893 next_row=MagickTrue;
2894 span.y=1.0;
2895 scale.y=(double) scale_image->rows/(double) image->rows;
2896 (void) ResetMagickMemory(y_vector,0,(size_t) image->columns*
2897 sizeof(*y_vector));
2898 GetMagickPixelPacket(image,&pixel);
2899 (void) ResetMagickMemory(&zero,0,sizeof(zero));
2900 i=0;
cristyed6cb232010-01-20 03:07:53 +00002901 image_view=AcquireCacheView(image);
2902 scale_view=AcquireCacheView(scale_image);
cristybb503372010-05-27 20:51:26 +00002903 for (y=0; y < (ssize_t) scale_image->rows; y++)
cristy3ed852e2009-09-05 21:47:34 +00002904 {
2905 register const IndexPacket
cristyc47d1f82009-11-26 01:44:43 +00002906 *restrict indexes;
cristy3ed852e2009-09-05 21:47:34 +00002907
2908 register const PixelPacket
cristyc47d1f82009-11-26 01:44:43 +00002909 *restrict p;
cristy3ed852e2009-09-05 21:47:34 +00002910
2911 register IndexPacket
cristyc47d1f82009-11-26 01:44:43 +00002912 *restrict scale_indexes;
cristy3ed852e2009-09-05 21:47:34 +00002913
cristy3ed852e2009-09-05 21:47:34 +00002914 register MagickPixelPacket
cristyc47d1f82009-11-26 01:44:43 +00002915 *restrict s,
2916 *restrict t;
cristy3ed852e2009-09-05 21:47:34 +00002917
2918 register PixelPacket
cristyc47d1f82009-11-26 01:44:43 +00002919 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +00002920
cristy9af9b5d2010-08-15 17:04:28 +00002921 register ssize_t
2922 x;
2923
cristyed6cb232010-01-20 03:07:53 +00002924 q=QueueCacheViewAuthenticPixels(scale_view,0,y,scale_image->columns,1,
2925 exception);
cristy3ed852e2009-09-05 21:47:34 +00002926 if (q == (PixelPacket *) NULL)
2927 break;
2928 scale_indexes=GetAuthenticIndexQueue(scale_image);
2929 if (scale_image->rows == image->rows)
2930 {
2931 /*
2932 Read a new scanline.
2933 */
cristyed6cb232010-01-20 03:07:53 +00002934 p=GetCacheViewVirtualPixels(image_view,0,i++,image->columns,1,
2935 exception);
cristy3ed852e2009-09-05 21:47:34 +00002936 if (p == (const PixelPacket *) NULL)
2937 break;
cristyed6cb232010-01-20 03:07:53 +00002938 indexes=GetCacheViewVirtualIndexQueue(image_view);
cristybb503372010-05-27 20:51:26 +00002939 for (x=0; x < (ssize_t) image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +00002940 {
cristyce70c172010-01-07 17:15:30 +00002941 x_vector[x].red=(MagickRealType) GetRedPixelComponent(p);
2942 x_vector[x].green=(MagickRealType) GetGreenPixelComponent(p);
2943 x_vector[x].blue=(MagickRealType) GetBluePixelComponent(p);
cristy3ed852e2009-09-05 21:47:34 +00002944 if (image->matte != MagickFalse)
cristyce70c172010-01-07 17:15:30 +00002945 x_vector[x].opacity=(MagickRealType) GetOpacityPixelComponent(p);
cristy3ed852e2009-09-05 21:47:34 +00002946 if (indexes != (IndexPacket *) NULL)
2947 x_vector[x].index=(MagickRealType) indexes[x];
2948 p++;
2949 }
2950 }
2951 else
2952 {
2953 /*
2954 Scale Y direction.
2955 */
2956 while (scale.y < span.y)
2957 {
cristy9af9b5d2010-08-15 17:04:28 +00002958 if ((next_row != MagickFalse) &&
2959 (number_rows < (ssize_t) image->rows))
cristy3ed852e2009-09-05 21:47:34 +00002960 {
2961 /*
2962 Read a new scanline.
2963 */
cristyed6cb232010-01-20 03:07:53 +00002964 p=GetCacheViewVirtualPixels(image_view,0,i++,image->columns,1,
2965 exception);
cristy3ed852e2009-09-05 21:47:34 +00002966 if (p == (const PixelPacket *) NULL)
2967 break;
cristyed6cb232010-01-20 03:07:53 +00002968 indexes=GetCacheViewVirtualIndexQueue(image_view);
cristybb503372010-05-27 20:51:26 +00002969 for (x=0; x < (ssize_t) image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +00002970 {
cristyce70c172010-01-07 17:15:30 +00002971 x_vector[x].red=(MagickRealType) GetRedPixelComponent(p);
2972 x_vector[x].green=(MagickRealType) GetGreenPixelComponent(p);
2973 x_vector[x].blue=(MagickRealType) GetBluePixelComponent(p);
cristy3ed852e2009-09-05 21:47:34 +00002974 if (image->matte != MagickFalse)
cristyed6cb232010-01-20 03:07:53 +00002975 x_vector[x].opacity=(MagickRealType)
cristy9af9b5d2010-08-15 17:04:28 +00002976 GetOpacityPixelComponent(p);
cristy3ed852e2009-09-05 21:47:34 +00002977 if (indexes != (IndexPacket *) NULL)
2978 x_vector[x].index=(MagickRealType) indexes[x];
2979 p++;
2980 }
2981 number_rows++;
2982 }
cristybb503372010-05-27 20:51:26 +00002983 for (x=0; x < (ssize_t) image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +00002984 {
2985 y_vector[x].red+=scale.y*x_vector[x].red;
2986 y_vector[x].green+=scale.y*x_vector[x].green;
2987 y_vector[x].blue+=scale.y*x_vector[x].blue;
2988 if (scale_image->matte != MagickFalse)
2989 y_vector[x].opacity+=scale.y*x_vector[x].opacity;
2990 if (scale_indexes != (IndexPacket *) NULL)
2991 y_vector[x].index+=scale.y*x_vector[x].index;
2992 }
2993 span.y-=scale.y;
2994 scale.y=(double) scale_image->rows/(double) image->rows;
2995 next_row=MagickTrue;
2996 }
cristybb503372010-05-27 20:51:26 +00002997 if ((next_row != MagickFalse) && (number_rows < (ssize_t) image->rows))
cristy3ed852e2009-09-05 21:47:34 +00002998 {
2999 /*
3000 Read a new scanline.
3001 */
cristyed6cb232010-01-20 03:07:53 +00003002 p=GetCacheViewVirtualPixels(image_view,0,i++,image->columns,1,
3003 exception);
cristy3ed852e2009-09-05 21:47:34 +00003004 if (p == (const PixelPacket *) NULL)
3005 break;
cristyed6cb232010-01-20 03:07:53 +00003006 indexes=GetCacheViewVirtualIndexQueue(image_view);
cristybb503372010-05-27 20:51:26 +00003007 for (x=0; x < (ssize_t) image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +00003008 {
cristyce70c172010-01-07 17:15:30 +00003009 x_vector[x].red=(MagickRealType) GetRedPixelComponent(p);
3010 x_vector[x].green=(MagickRealType) GetGreenPixelComponent(p);
3011 x_vector[x].blue=(MagickRealType) GetBluePixelComponent(p);
cristy3ed852e2009-09-05 21:47:34 +00003012 if (image->matte != MagickFalse)
cristy2b726bd2010-01-11 01:05:39 +00003013 x_vector[x].opacity=(MagickRealType)
3014 GetOpacityPixelComponent(p);
cristy3ed852e2009-09-05 21:47:34 +00003015 if (indexes != (IndexPacket *) NULL)
3016 x_vector[x].index=(MagickRealType) indexes[x];
3017 p++;
3018 }
3019 number_rows++;
3020 next_row=MagickFalse;
3021 }
3022 s=scanline;
cristybb503372010-05-27 20:51:26 +00003023 for (x=0; x < (ssize_t) image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +00003024 {
3025 pixel.red=y_vector[x].red+span.y*x_vector[x].red;
3026 pixel.green=y_vector[x].green+span.y*x_vector[x].green;
3027 pixel.blue=y_vector[x].blue+span.y*x_vector[x].blue;
3028 if (image->matte != MagickFalse)
3029 pixel.opacity=y_vector[x].opacity+span.y*x_vector[x].opacity;
3030 if (scale_indexes != (IndexPacket *) NULL)
3031 pixel.index=y_vector[x].index+span.y*x_vector[x].index;
3032 s->red=pixel.red;
3033 s->green=pixel.green;
3034 s->blue=pixel.blue;
3035 if (scale_image->matte != MagickFalse)
3036 s->opacity=pixel.opacity;
3037 if (scale_indexes != (IndexPacket *) NULL)
3038 s->index=pixel.index;
3039 s++;
3040 y_vector[x]=zero;
3041 }
3042 scale.y-=span.y;
3043 if (scale.y <= 0)
3044 {
3045 scale.y=(double) scale_image->rows/(double) image->rows;
3046 next_row=MagickTrue;
3047 }
3048 span.y=1.0;
3049 }
3050 if (scale_image->columns == image->columns)
3051 {
3052 /*
3053 Transfer scanline to scaled image.
3054 */
3055 s=scanline;
cristybb503372010-05-27 20:51:26 +00003056 for (x=0; x < (ssize_t) scale_image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +00003057 {
cristyce70c172010-01-07 17:15:30 +00003058 q->red=ClampToQuantum(s->red);
3059 q->green=ClampToQuantum(s->green);
3060 q->blue=ClampToQuantum(s->blue);
cristy3ed852e2009-09-05 21:47:34 +00003061 if (scale_image->matte != MagickFalse)
cristyce70c172010-01-07 17:15:30 +00003062 q->opacity=ClampToQuantum(s->opacity);
cristy3ed852e2009-09-05 21:47:34 +00003063 if (scale_indexes != (IndexPacket *) NULL)
cristyce70c172010-01-07 17:15:30 +00003064 scale_indexes[x]=(IndexPacket) ClampToQuantum(s->index);
cristy3ed852e2009-09-05 21:47:34 +00003065 q++;
3066 s++;
3067 }
3068 }
3069 else
3070 {
3071 /*
3072 Scale X direction.
3073 */
3074 pixel=zero;
3075 next_column=MagickFalse;
3076 span.x=1.0;
3077 s=scanline;
3078 t=scale_scanline;
cristybb503372010-05-27 20:51:26 +00003079 for (x=0; x < (ssize_t) image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +00003080 {
3081 scale.x=(double) scale_image->columns/(double) image->columns;
3082 while (scale.x >= span.x)
3083 {
3084 if (next_column != MagickFalse)
3085 {
3086 pixel=zero;
3087 t++;
3088 }
3089 pixel.red+=span.x*s->red;
3090 pixel.green+=span.x*s->green;
3091 pixel.blue+=span.x*s->blue;
3092 if (image->matte != MagickFalse)
3093 pixel.opacity+=span.x*s->opacity;
3094 if (scale_indexes != (IndexPacket *) NULL)
3095 pixel.index+=span.x*s->index;
3096 t->red=pixel.red;
3097 t->green=pixel.green;
3098 t->blue=pixel.blue;
3099 if (scale_image->matte != MagickFalse)
3100 t->opacity=pixel.opacity;
3101 if (scale_indexes != (IndexPacket *) NULL)
3102 t->index=pixel.index;
3103 scale.x-=span.x;
3104 span.x=1.0;
3105 next_column=MagickTrue;
3106 }
3107 if (scale.x > 0)
3108 {
3109 if (next_column != MagickFalse)
3110 {
3111 pixel=zero;
3112 next_column=MagickFalse;
3113 t++;
3114 }
3115 pixel.red+=scale.x*s->red;
3116 pixel.green+=scale.x*s->green;
3117 pixel.blue+=scale.x*s->blue;
3118 if (scale_image->matte != MagickFalse)
3119 pixel.opacity+=scale.x*s->opacity;
3120 if (scale_indexes != (IndexPacket *) NULL)
3121 pixel.index+=scale.x*s->index;
3122 span.x-=scale.x;
3123 }
3124 s++;
3125 }
3126 if (span.x > 0)
3127 {
3128 s--;
3129 pixel.red+=span.x*s->red;
3130 pixel.green+=span.x*s->green;
3131 pixel.blue+=span.x*s->blue;
3132 if (scale_image->matte != MagickFalse)
3133 pixel.opacity+=span.x*s->opacity;
3134 if (scale_indexes != (IndexPacket *) NULL)
3135 pixel.index+=span.x*s->index;
3136 }
3137 if ((next_column == MagickFalse) &&
cristybb503372010-05-27 20:51:26 +00003138 ((ssize_t) (t-scale_scanline) < (ssize_t) scale_image->columns))
cristy3ed852e2009-09-05 21:47:34 +00003139 {
3140 t->red=pixel.red;
3141 t->green=pixel.green;
3142 t->blue=pixel.blue;
3143 if (scale_image->matte != MagickFalse)
3144 t->opacity=pixel.opacity;
3145 if (scale_indexes != (IndexPacket *) NULL)
3146 t->index=pixel.index;
3147 }
3148 /*
3149 Transfer scanline to scaled image.
3150 */
3151 t=scale_scanline;
cristybb503372010-05-27 20:51:26 +00003152 for (x=0; x < (ssize_t) scale_image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +00003153 {
cristyce70c172010-01-07 17:15:30 +00003154 q->red=ClampToQuantum(t->red);
3155 q->green=ClampToQuantum(t->green);
3156 q->blue=ClampToQuantum(t->blue);
cristy3ed852e2009-09-05 21:47:34 +00003157 if (scale_image->matte != MagickFalse)
cristyce70c172010-01-07 17:15:30 +00003158 q->opacity=ClampToQuantum(t->opacity);
cristy3ed852e2009-09-05 21:47:34 +00003159 if (scale_indexes != (IndexPacket *) NULL)
cristyce70c172010-01-07 17:15:30 +00003160 scale_indexes[x]=(IndexPacket) ClampToQuantum(t->index);
cristy3ed852e2009-09-05 21:47:34 +00003161 t++;
3162 q++;
3163 }
3164 }
cristyed6cb232010-01-20 03:07:53 +00003165 if (SyncCacheViewAuthenticPixels(scale_view,exception) == MagickFalse)
cristy3ed852e2009-09-05 21:47:34 +00003166 break;
cristy96b16132010-08-29 17:19:52 +00003167 proceed=SetImageProgress(image,ScaleImageTag,(MagickOffsetType) y,
3168 image->rows);
cristy3ed852e2009-09-05 21:47:34 +00003169 if (proceed == MagickFalse)
3170 break;
3171 }
cristyed6cb232010-01-20 03:07:53 +00003172 scale_view=DestroyCacheView(scale_view);
3173 image_view=DestroyCacheView(image_view);
cristy3ed852e2009-09-05 21:47:34 +00003174 /*
3175 Free allocated memory.
3176 */
3177 y_vector=(MagickPixelPacket *) RelinquishMagickMemory(y_vector);
3178 scale_scanline=(MagickPixelPacket *) RelinquishMagickMemory(scale_scanline);
3179 if (scale_image->rows != image->rows)
3180 scanline=(MagickPixelPacket *) RelinquishMagickMemory(scanline);
3181 x_vector=(MagickPixelPacket *) RelinquishMagickMemory(x_vector);
3182 scale_image->type=image->type;
3183 return(scale_image);
3184}
3185
anthony02b4cb42010-10-10 04:54:35 +00003186#if 0
3187 THIS IS NOT USED -- to be removed
cristy3ed852e2009-09-05 21:47:34 +00003188/*
3189%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3190% %
3191% %
3192% %
3193+ S e t R e s i z e F i l t e r S u p p o r t %
3194% %
3195% %
3196% %
3197%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3198%
3199% SetResizeFilterSupport() specifies which IR filter to use to window
3200%
3201% The format of the SetResizeFilterSupport method is:
3202%
3203% void SetResizeFilterSupport(ResizeFilter *resize_filter,
3204% const MagickRealType support)
3205%
3206% A description of each parameter follows:
3207%
3208% o resize_filter: the resize filter.
3209%
3210% o support: the filter spport radius.
3211%
3212*/
3213MagickExport void SetResizeFilterSupport(ResizeFilter *resize_filter,
3214 const MagickRealType support)
3215{
3216 assert(resize_filter != (ResizeFilter *) NULL);
3217 assert(resize_filter->signature == MagickSignature);
3218 resize_filter->support=support;
3219}
anthony02b4cb42010-10-10 04:54:35 +00003220#endif
cristy3ed852e2009-09-05 21:47:34 +00003221
3222/*
3223%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3224% %
3225% %
3226% %
3227% T h u m b n a i l I m a g e %
3228% %
3229% %
3230% %
3231%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3232%
3233% ThumbnailImage() changes the size of an image to the given dimensions and
3234% removes any associated profiles. The goal is to produce small low cost
3235% thumbnail images suited for display on the Web.
3236%
3237% The format of the ThumbnailImage method is:
3238%
cristybb503372010-05-27 20:51:26 +00003239% Image *ThumbnailImage(const Image *image,const size_t columns,
3240% const size_t rows,ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +00003241%
3242% A description of each parameter follows:
3243%
3244% o image: the image.
3245%
3246% o columns: the number of columns in the scaled image.
3247%
3248% o rows: the number of rows in the scaled image.
3249%
3250% o exception: return any errors or warnings in this structure.
3251%
3252*/
cristy9af9b5d2010-08-15 17:04:28 +00003253MagickExport Image *ThumbnailImage(const Image *image,const size_t columns,
3254 const size_t rows,ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +00003255{
3256#define SampleFactor 5
3257
3258 char
3259 value[MaxTextExtent];
3260
3261 const char
3262 *name;
3263
3264 Image
3265 *thumbnail_image;
3266
3267 MagickRealType
3268 x_factor,
3269 y_factor;
3270
cristybb503372010-05-27 20:51:26 +00003271 size_t
cristy3ed852e2009-09-05 21:47:34 +00003272 version;
3273
cristy9af9b5d2010-08-15 17:04:28 +00003274 struct stat
3275 attributes;
3276
cristy3ed852e2009-09-05 21:47:34 +00003277 assert(image != (Image *) NULL);
3278 assert(image->signature == MagickSignature);
3279 if (image->debug != MagickFalse)
3280 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
3281 assert(exception != (ExceptionInfo *) NULL);
3282 assert(exception->signature == MagickSignature);
3283 x_factor=(MagickRealType) columns/(MagickRealType) image->columns;
3284 y_factor=(MagickRealType) rows/(MagickRealType) image->rows;
3285 if ((x_factor*y_factor) > 0.1)
cristyd1bb3bc2010-09-07 00:43:58 +00003286 thumbnail_image=ResizeImage(image,columns,rows,image->filter,image->blur,
3287 exception);
cristy3ed852e2009-09-05 21:47:34 +00003288 else
3289 if (((SampleFactor*columns) < 128) || ((SampleFactor*rows) < 128))
cristyd1bb3bc2010-09-07 00:43:58 +00003290 thumbnail_image=ResizeImage(image,columns,rows,image->filter,
3291 image->blur,exception);
cristy3ed852e2009-09-05 21:47:34 +00003292 else
3293 {
3294 Image
3295 *sample_image;
3296
3297 sample_image=SampleImage(image,SampleFactor*columns,SampleFactor*rows,
3298 exception);
3299 if (sample_image == (Image *) NULL)
3300 return((Image *) NULL);
cristyd1bb3bc2010-09-07 00:43:58 +00003301 thumbnail_image=ResizeImage(sample_image,columns,rows,image->filter,
3302 image->blur,exception);
cristy3ed852e2009-09-05 21:47:34 +00003303 sample_image=DestroyImage(sample_image);
3304 }
3305 if (thumbnail_image == (Image *) NULL)
3306 return(thumbnail_image);
3307 (void) ParseAbsoluteGeometry("0x0+0+0",&thumbnail_image->page);
3308 if (thumbnail_image->matte == MagickFalse)
3309 (void) SetImageAlphaChannel(thumbnail_image,OpaqueAlphaChannel);
3310 thumbnail_image->depth=8;
3311 thumbnail_image->interlace=NoInterlace;
3312 /*
3313 Strip all profiles except color profiles.
3314 */
3315 ResetImageProfileIterator(thumbnail_image);
3316 for (name=GetNextImageProfile(thumbnail_image); name != (const char *) NULL; )
3317 {
3318 if ((LocaleCompare(name,"icc") != 0) && (LocaleCompare(name,"icm") != 0))
3319 {
cristy2b726bd2010-01-11 01:05:39 +00003320 (void) DeleteImageProfile(thumbnail_image,name);
cristy3ed852e2009-09-05 21:47:34 +00003321 ResetImageProfileIterator(thumbnail_image);
3322 }
3323 name=GetNextImageProfile(thumbnail_image);
3324 }
3325 (void) DeleteImageProperty(thumbnail_image,"comment");
3326 (void) CopyMagickString(value,image->magick_filename,MaxTextExtent);
cristy7b63d662009-11-13 01:58:56 +00003327 if (strstr(image->magick_filename,"//") == (char *) NULL)
3328 (void) FormatMagickString(value,MaxTextExtent,"file://%s",
cristy3ed852e2009-09-05 21:47:34 +00003329 image->magick_filename);
3330 (void) SetImageProperty(thumbnail_image,"Thumb::URI",value);
3331 (void) CopyMagickString(value,image->magick_filename,MaxTextExtent);
3332 if (GetPathAttributes(image->filename,&attributes) != MagickFalse)
3333 {
cristye8c25f92010-06-03 00:53:06 +00003334 (void) FormatMagickString(value,MaxTextExtent,"%.20g",(double)
cristy3ed852e2009-09-05 21:47:34 +00003335 attributes.st_mtime);
3336 (void) SetImageProperty(thumbnail_image,"Thumb::MTime",value);
3337 }
cristye8c25f92010-06-03 00:53:06 +00003338 (void) FormatMagickString(value,MaxTextExtent,"%.20g",(double)
cristy3ed852e2009-09-05 21:47:34 +00003339 attributes.st_mtime);
cristyb9080c92009-12-01 20:13:26 +00003340 (void) FormatMagickSize(GetBlobSize(image),MagickFalse,value);
cristy2ce15c92010-03-12 14:03:41 +00003341 (void) ConcatenateMagickString(value,"B",MaxTextExtent);
cristy3ed852e2009-09-05 21:47:34 +00003342 (void) SetImageProperty(thumbnail_image,"Thumb::Size",value);
3343 (void) FormatMagickString(value,MaxTextExtent,"image/%s",image->magick);
3344 LocaleLower(value);
3345 (void) SetImageProperty(thumbnail_image,"Thumb::Mimetype",value);
3346 (void) SetImageProperty(thumbnail_image,"software",
3347 GetMagickVersion(&version));
cristye8c25f92010-06-03 00:53:06 +00003348 (void) FormatMagickString(value,MaxTextExtent,"%.20g",(double)
3349 image->magick_columns);
cristy3ed852e2009-09-05 21:47:34 +00003350 (void) SetImageProperty(thumbnail_image,"Thumb::Image::Width",value);
cristye8c25f92010-06-03 00:53:06 +00003351 (void) FormatMagickString(value,MaxTextExtent,"%.20g",(double)
cristyf2faecf2010-05-28 19:19:36 +00003352 image->magick_rows);
cristy3ed852e2009-09-05 21:47:34 +00003353 (void) SetImageProperty(thumbnail_image,"Thumb::Image::height",value);
cristye8c25f92010-06-03 00:53:06 +00003354 (void) FormatMagickString(value,MaxTextExtent,"%.20g",(double)
3355 GetImageListLength(image));
cristy3ed852e2009-09-05 21:47:34 +00003356 (void) SetImageProperty(thumbnail_image,"Thumb::Document::Pages",value);
3357 return(thumbnail_image);
3358}