blob: 74380ff1a422acadfbfc2fdfcd212d25b3eb465f [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% %
cristy7e41fe82010-12-04 23:12:08 +000020% Copyright 1999-2011 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"
cristya6a18782010-11-15 01:56:25 +000065#include "magick/resample-private.h"
cristy3ed852e2009-09-05 21:47:34 +000066#include "magick/resize.h"
67#include "magick/resize-private.h"
68#include "magick/string_.h"
cristyf2f27272009-12-17 14:48:46 +000069#include "magick/string-private.h"
cristy3ed852e2009-09-05 21:47:34 +000070#include "magick/thread-private.h"
71#include "magick/utility.h"
72#include "magick/version.h"
73#if defined(MAGICKCORE_LQR_DELEGATE)
74#include <lqr.h>
75#endif
76
77/*
78 Typedef declarations.
79*/
80struct _ResizeFilter
81{
82 MagickRealType
83 (*filter)(const MagickRealType,const ResizeFilter *),
84 (*window)(const MagickRealType,const ResizeFilter *),
cristy33b1c162010-01-23 22:51:51 +000085 support, /* filter region of support - the filter support limit */
86 window_support, /* window support, usally equal to support (expert only) */
anthony55f12332010-09-10 01:13:02 +000087 scale, /* dimension scaling to fit window support (usally 1.0) */
cristy33b1c162010-01-23 22:51:51 +000088 blur, /* x-scale (blur-sharpen) */
cristy5cce74b2010-11-15 03:24:28 +000089 coefficient[7]; /* cubic coefficents for BC-cubic spline filters */
cristy3ed852e2009-09-05 21:47:34 +000090
cristybb503372010-05-27 20:51:26 +000091 size_t
cristy3ed852e2009-09-05 21:47:34 +000092 signature;
93};
94
95/*
96 Forward declaractions.
97*/
98static MagickRealType
99 I0(MagickRealType x),
anthonyb6d08c52010-09-13 01:17:04 +0000100 BesselOrderOne(MagickRealType),
anthony07a3f7f2010-09-16 03:03:11 +0000101 Sinc(const MagickRealType, const ResizeFilter *),
anthonyba5a7c32010-09-15 02:42:25 +0000102 SincFast(const MagickRealType, const ResizeFilter *);
cristy3ed852e2009-09-05 21:47:34 +0000103
104/*
105%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
106% %
107% %
108% %
109+ F i l t e r F u n c t i o n s %
110% %
111% %
112% %
113%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
114%
cristy33b1c162010-01-23 22:51:51 +0000115% These are the various filter and windowing functions that are provided.
cristy3ed852e2009-09-05 21:47:34 +0000116%
cristy33b1c162010-01-23 22:51:51 +0000117% They are internal to this module only. See AcquireResizeFilterInfo() for
118% details of the access to these functions, via the GetResizeFilterSupport()
119% and GetResizeFilterWeight() API interface.
cristy3ed852e2009-09-05 21:47:34 +0000120%
121% The individual filter functions have this format...
122%
123% static MagickRealtype *FilterName(const MagickRealType x,
124% const MagickRealType support)
125%
cristy33b1c162010-01-23 22:51:51 +0000126% A description of each parameter follows:
cristy3ed852e2009-09-05 21:47:34 +0000127%
cristy33b1c162010-01-23 22:51:51 +0000128% o x: the distance from the sampling point generally in the range of 0 to
129% support. The GetResizeFilterWeight() ensures this a positive value.
130%
131% o resize_filter: current filter information. This allows function to
132% access support, and possibly other pre-calculated information defining
133% the functions.
cristy3ed852e2009-09-05 21:47:34 +0000134%
135*/
136
cristyc5c6f662010-09-22 14:23:02 +0000137#define MagickPIL ((MagickRealType) 3.14159265358979323846264338327950288420L)
cristy560d8182010-09-08 22:36:25 +0000138
anthony48f77622010-10-03 14:32:31 +0000139static MagickRealType Jinc(const MagickRealType x,
cristy3ed852e2009-09-05 21:47:34 +0000140 const ResizeFilter *magick_unused(resize_filter))
141{
142 /*
anthony48f77622010-10-03 14:32:31 +0000143 See Pratt "Digital Image Processing" p.97 for Jinc/Bessel functions.
cristy33b1c162010-01-23 22:51:51 +0000144 http://mathworld.wolfram.com/JincFunction.html and page 11 of
anthony48f77622010-10-03 14:32:31 +0000145 http://www.ph.ed.ac.uk/%7ewjh/teaching/mo/slides/lens/lens.pdf
146
nicolase473f722010-10-07 00:05:13 +0000147 The original "zoom" program by Paul Heckbert called this "Bessel".
148 But really it is more accurately named "Jinc".
cristy3ed852e2009-09-05 21:47:34 +0000149 */
150 if (x == 0.0)
nicolas5a36f342010-10-07 00:11:32 +0000151 return(0.5*MagickPIL);
152 return(BesselOrderOne(MagickPIL*x)/x);
cristy3ed852e2009-09-05 21:47:34 +0000153}
154
155static MagickRealType Blackman(const MagickRealType x,
cristyce70c172010-01-07 17:15:30 +0000156 const ResizeFilter *magick_unused(resize_filter))
cristy3ed852e2009-09-05 21:47:34 +0000157{
158 /*
cristy83017922010-09-05 20:45:15 +0000159 Blackman: 2nd order cosine windowing function:
cristy21ce88a2010-09-05 01:37:25 +0000160 0.42 + 0.5 cos(pi x) + 0.08 cos(2pi x)
cristy560d8182010-09-08 22:36:25 +0000161 Refactored by Chantal Racette and Nicolas Robidoux to one trig
162 call and five flops.
cristy3ed852e2009-09-05 21:47:34 +0000163 */
cristyc5c6f662010-09-22 14:23:02 +0000164 const MagickRealType cospix = cos((double) (MagickPIL*x));
cristy560d8182010-09-08 22:36:25 +0000165 return(0.34+cospix*(0.5+cospix*0.16));
cristy3ed852e2009-09-05 21:47:34 +0000166}
167
168static MagickRealType Bohman(const MagickRealType x,
cristyce70c172010-01-07 17:15:30 +0000169 const ResizeFilter *magick_unused(resize_filter))
cristy3ed852e2009-09-05 21:47:34 +0000170{
171 /*
cristy560d8182010-09-08 22:36:25 +0000172 Bohman: 2rd Order cosine windowing function:
173 (1-x) cos(pi x) + sin(pi x) / pi.
nicolasa4f7b4a2010-09-20 20:28:38 +0000174 Refactored by Nicolas Robidoux to one trig call, one sqrt call,
175 and 7 flops, taking advantage of the fact that the support of
176 Bohman is 1 (so that we know that sin(pi x) >= 0).
cristy3ed852e2009-09-05 21:47:34 +0000177 */
cristyc5c6f662010-09-22 14:23:02 +0000178 const double cospix = cos((double) (MagickPIL*x));
nicolasa4f7b4a2010-09-20 20:28:38 +0000179 const double sinpix = sqrt(1.0-cospix*cospix);
nicolas2ffd3b22010-09-24 20:27:31 +0000180 return((1.0-x)*cospix+(1.0/MagickPIL)*sinpix);
cristy3ed852e2009-09-05 21:47:34 +0000181}
182
anthony463be1d2010-09-26 01:07:36 +0000183static MagickRealType Box(const MagickRealType magick_unused(x),
184 const ResizeFilter *magick_unused(resize_filter))
cristy3ed852e2009-09-05 21:47:34 +0000185{
186 /*
nicolas40477452010-09-27 23:42:08 +0000187 A Box filter is a equal weighting function (all weights equal).
anthonyc331dec2010-09-26 01:30:14 +0000188 DO NOT LIMIT results by support or resize point sampling will work
189 as it requests points beyond its normal 0.0 support size.
cristy3ed852e2009-09-05 21:47:34 +0000190 */
anthony463be1d2010-09-26 01:07:36 +0000191 return(1.0);
cristy3ed852e2009-09-05 21:47:34 +0000192}
193
194static MagickRealType CubicBC(const MagickRealType x,
195 const ResizeFilter *resize_filter)
196{
197 /*
198 Cubic Filters using B,C determined values:
nicolase3b9eca2010-10-24 19:48:22 +0000199 Mitchell-Netravali B= 1/3 C= 1/3 "Balanced" cubic spline filter
200 Catmull-Rom B= 0 C= 1/2 Interpolatory and exact on linears
201 Cubic B-Spline B= 1 C= 0 Spline approximation of Gaussian
202 Hermite B= 0 C= 0 Spline with small support (= 1)
cristy3ed852e2009-09-05 21:47:34 +0000203
cristy33b1c162010-01-23 22:51:51 +0000204 See paper by Mitchell and Netravali, Reconstruction Filters in Computer
205 Graphics Computer Graphics, Volume 22, Number 4, August 1988
206 http://www.cs.utexas.edu/users/fussell/courses/cs384g/lectures/mitchell/
cristyf59a8922010-02-28 19:51:23 +0000207 Mitchell.pdf.
cristy3ed852e2009-09-05 21:47:34 +0000208
cristy33b1c162010-01-23 22:51:51 +0000209 Coefficents are determined from B,C values:
anthony06b1edf2010-10-25 01:19:50 +0000210 P0 = ( 6 - 2*B )/6 = coeff[0]
cristy3ed852e2009-09-05 21:47:34 +0000211 P1 = 0
anthony06b1edf2010-10-25 01:19:50 +0000212 P2 = (-18 +12*B + 6*C )/6 = coeff[1]
213 P3 = ( 12 - 9*B - 6*C )/6 = coeff[2]
214 Q0 = ( 8*B +24*C )/6 = coeff[3]
215 Q1 = ( -12*B -48*C )/6 = coeff[4]
216 Q2 = ( 6*B +30*C )/6 = coeff[5]
217 Q3 = ( - 1*B - 6*C )/6 = coeff[6]
cristy3ed852e2009-09-05 21:47:34 +0000218
cristy33b1c162010-01-23 22:51:51 +0000219 which are used to define the filter:
cristyf59a8922010-02-28 19:51:23 +0000220
cristy3ed852e2009-09-05 21:47:34 +0000221 P0 + P1*x + P2*x^2 + P3*x^3 0 <= x < 1
nicolase3b9eca2010-10-24 19:48:22 +0000222 Q0 + Q1*x + Q2*x^2 + Q3*x^3 1 <= x < 2
cristy3ed852e2009-09-05 21:47:34 +0000223
nicolase3b9eca2010-10-24 19:48:22 +0000224 which ensures function is continuous in value and derivative
nicolas89d73f92010-10-24 20:11:54 +0000225 (slope).
cristy3ed852e2009-09-05 21:47:34 +0000226 */
227 if (x < 1.0)
cristy5cce74b2010-11-15 03:24:28 +0000228 return(resize_filter->coefficient[0]+x*(x*
229 (resize_filter->coefficient[1]+x*resize_filter->coefficient[2])));
cristy3ed852e2009-09-05 21:47:34 +0000230 if (x < 2.0)
cristy5cce74b2010-11-15 03:24:28 +0000231 return(resize_filter->coefficient[3]+x*(resize_filter->coefficient[4]+x*
232 (resize_filter->coefficient[5]+x*resize_filter->coefficient[6])));
cristy3ed852e2009-09-05 21:47:34 +0000233 return(0.0);
234}
235
236static MagickRealType Gaussian(const MagickRealType x,
anthonyf5e76ef2010-10-12 01:22:01 +0000237 const ResizeFilter *resize_filter)
cristy3ed852e2009-09-05 21:47:34 +0000238{
cristy560d8182010-09-08 22:36:25 +0000239 /*
anthonyf5e76ef2010-10-12 01:22:01 +0000240 Gaussian with a fixed sigma = 1/2
241
anthony993d8382011-02-06 12:35:36 +0000242 Gaussian Formula (1D) ...
243 exp( -(x^2)/((2.0*sigma^2) ) / sqrt(2*PI)sigma^2))
anthonyf5e76ef2010-10-12 01:22:01 +0000244 The constants are pre-calculated...
245 exp( -coeff[0]*(x^2)) ) * coeff[1]
anthony06b1edf2010-10-25 01:19:50 +0000246 However the multiplier coefficent (1) is not needed and not used.
anthonyf5e76ef2010-10-12 01:22:01 +0000247
anthony993d8382011-02-06 12:35:36 +0000248 Gaussian Formula (2D) ...
249 exp( -(x^2)/((2.0*sigma^2) ) / (PI*sigma^2) )
250 Note that it is only a change in the normalization multiplier
251 which is not needed or used when gausian is used as a filter.
252
253 This separates the gaussian 'sigma' value from the 'blur/support'
254 settings allowing for its use in special 'small sigma' gaussians,
255 without the filter 'missing' pixels because the support becomes too
256 small.
cristy560d8182010-09-08 22:36:25 +0000257 */
cristy5cce74b2010-11-15 03:24:28 +0000258 return(exp((double)(-resize_filter->coefficient[0]*x*x)));
anthony06b1edf2010-10-25 01:19:50 +0000259}
cristy3ed852e2009-09-05 21:47:34 +0000260
261static MagickRealType Hanning(const MagickRealType x,
262 const ResizeFilter *magick_unused(resize_filter))
263{
264 /*
nicolas40477452010-09-27 23:42:08 +0000265 Cosine window function:
266 .5+.5cos(pi x).
cristy3ed852e2009-09-05 21:47:34 +0000267 */
cristyc5c6f662010-09-22 14:23:02 +0000268 const MagickRealType cospix = cos((double) (MagickPIL*x));
cristy560d8182010-09-08 22:36:25 +0000269 return(0.5+0.5*cospix);
cristy3ed852e2009-09-05 21:47:34 +0000270}
271
272static MagickRealType Hamming(const MagickRealType x,
273 const ResizeFilter *magick_unused(resize_filter))
274{
275 /*
nicolas40477452010-09-27 23:42:08 +0000276 Offset cosine window function:
277 .54 + .46 cos(pi x).
cristy3ed852e2009-09-05 21:47:34 +0000278 */
cristyc5c6f662010-09-22 14:23:02 +0000279 const MagickRealType cospix = cos((double) (MagickPIL*x));
cristy560d8182010-09-08 22:36:25 +0000280 return(0.54+0.46*cospix);
cristy3ed852e2009-09-05 21:47:34 +0000281}
282
283static MagickRealType Kaiser(const MagickRealType x,
284 const ResizeFilter *magick_unused(resize_filter))
285{
286#define Alpha 6.5
287#define I0A (1.0/I0(Alpha))
288
289 /*
nicolas07bac812010-09-19 18:47:02 +0000290 Kaiser Windowing Function (bessel windowing): Alpha is a free
291 value from 5 to 8 (currently hardcoded to 6.5).
292 Future: make alpha the IOA pre-calculation, an 'expert' setting.
cristy3ed852e2009-09-05 21:47:34 +0000293 */
294 return(I0A*I0(Alpha*sqrt((double) (1.0-x*x))));
295}
296
297static MagickRealType Lagrange(const MagickRealType x,
298 const ResizeFilter *resize_filter)
299{
cristy3ed852e2009-09-05 21:47:34 +0000300 MagickRealType
301 value;
302
cristybb503372010-05-27 20:51:26 +0000303 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +0000304 i;
305
cristy9af9b5d2010-08-15 17:04:28 +0000306 ssize_t
307 n,
308 order;
309
cristy3ed852e2009-09-05 21:47:34 +0000310 /*
nicolas07bac812010-09-19 18:47:02 +0000311 Lagrange piecewise polynomial fit of sinc: N is the 'order' of the
312 lagrange function and depends on the overall support window size
313 of the filter. That is: for a support of 2, it gives a lagrange-4
314 (piecewise cubic function).
cristy3ed852e2009-09-05 21:47:34 +0000315
nicolas07bac812010-09-19 18:47:02 +0000316 "n" identifies the piece of the piecewise polynomial.
cristy3ed852e2009-09-05 21:47:34 +0000317
nicolas07bac812010-09-19 18:47:02 +0000318 See Survey: Interpolation Methods, IEEE Transactions on Medical
319 Imaging, Vol 18, No 11, November 1999, p1049-1075, -- Equation 27
320 on p1064.
cristy3ed852e2009-09-05 21:47:34 +0000321 */
322 if (x > resize_filter->support)
323 return(0.0);
cristybb503372010-05-27 20:51:26 +0000324 order=(ssize_t) (2.0*resize_filter->window_support); /* number of pieces */
anthonyc2d07db2010-09-15 23:47:40 +0000325 /*n=(ssize_t)((1.0*order)/2.0+x); -- which piece does x belong to */
326 n = (ssize_t)(resize_filter->window_support + x);
cristy3ed852e2009-09-05 21:47:34 +0000327 value=1.0f;
328 for (i=0; i < order; i++)
329 if (i != n)
330 value*=(n-i-x)/(n-i);
331 return(value);
332}
333
334static MagickRealType Quadratic(const MagickRealType x,
335 const ResizeFilter *magick_unused(resize_filter))
336{
337 /*
338 2rd order (quadratic) B-Spline approximation of Gaussian.
339 */
340 if (x < 0.5)
341 return(0.75-x*x);
342 if (x < 1.5)
343 return(0.5*(x-1.5)*(x-1.5));
344 return(0.0);
345}
346
anthony07a3f7f2010-09-16 03:03:11 +0000347static MagickRealType Sinc(const MagickRealType x,
cristy3ed852e2009-09-05 21:47:34 +0000348 const ResizeFilter *magick_unused(resize_filter))
349{
anthony720660f2010-09-07 10:05:14 +0000350 /*
nicolas40477452010-09-27 23:42:08 +0000351 Scaled sinc(x) function using a trig call:
nicolas07bac812010-09-19 18:47:02 +0000352 sinc(x) == sin(pi x)/(pi x).
anthony720660f2010-09-07 10:05:14 +0000353 */
anthony2d9b8b52010-09-14 08:31:07 +0000354 if (x != 0.0)
cristy560d8182010-09-08 22:36:25 +0000355 {
cristyc5c6f662010-09-22 14:23:02 +0000356 const MagickRealType pix = (MagickRealType) (MagickPIL*x);
nicolas07bac812010-09-19 18:47:02 +0000357 return(sin((double) pix)/pix);
cristy560d8182010-09-08 22:36:25 +0000358 }
nicolas2ffd3b22010-09-24 20:27:31 +0000359 return((MagickRealType) 1.0);
anthony720660f2010-09-07 10:05:14 +0000360}
361
anthonyba5a7c32010-09-15 02:42:25 +0000362static MagickRealType SincFast(const MagickRealType x,
anthony720660f2010-09-07 10:05:14 +0000363 const ResizeFilter *magick_unused(resize_filter))
364{
cristy560d8182010-09-08 22:36:25 +0000365 /*
366 Approximations of the sinc function sin(pi x)/(pi x) over the
367 interval [-4,4] constructed by Nicolas Robidoux and Chantal
368 Racette with funding from the Natural Sciences and Engineering
369 Research Council of Canada.
nicolas07bac812010-09-19 18:47:02 +0000370
371 Although the approximations are polynomials (for low order of
372 approximation) and quotients of polynomials (for higher order of
373 approximation) and consequently are similar in form to Taylor
374 polynomials/Pade approximants, the approximations are computed
375 with a completely different technique.
376
377 Summary: These approximations are "the best" in terms of bang
378 (accuracy) for the buck (flops). More specifically: Among the
379 polynomial quotients that can be computed using a fixed number of
380 flops (with a given "+ - * / budget"), the chosen polynomial
381 quotient is the one closest to the approximated function with
382 respect to maximum absolute relative error over the given
383 interval.
384
385 The Remez algorithm, as implemented in the boost library's minimax
nicolas3aab40c2010-09-19 21:14:15 +0000386 package, is the key to the construction:
nicolas07bac812010-09-19 18:47:02 +0000387 http://www.boost.org/doc/libs/1_36_0/libs/math/doc/...
388 ...sf_and_dist/html/math_toolkit/backgrounders/remez.html
cristy560d8182010-09-08 22:36:25 +0000389 */
nicolas3aab40c2010-09-19 21:14:15 +0000390 /*
391 If outside of the interval of approximation, use the standard trig
392 formula.
393 */
anthony2d9b8b52010-09-14 08:31:07 +0000394 if (x > 4.0)
cristy03dbbd22010-09-19 23:04:47 +0000395 {
cristyc5c6f662010-09-22 14:23:02 +0000396 const MagickRealType pix = (MagickRealType) (MagickPIL*x);
cristy03dbbd22010-09-19 23:04:47 +0000397 return(sin((double) pix)/pix);
398 }
anthony2d9b8b52010-09-14 08:31:07 +0000399 {
nicolas07bac812010-09-19 18:47:02 +0000400 /*
401 The approximations only depend on x^2 (sinc is an even
402 function).
403 */
404 const MagickRealType xx = x*x;
cristy83017922010-09-05 20:45:15 +0000405#if MAGICKCORE_QUANTUM_DEPTH <= 8
406 /*
anthony2d9b8b52010-09-14 08:31:07 +0000407 Maximum absolute relative error 6.3e-6 < 1/2^17.
cristy738e7562010-09-01 12:48:07 +0000408 */
409 const MagickRealType c0 = 0.173610016489197553621906385078711564924e-2L;
410 const MagickRealType c1 = -0.384186115075660162081071290162149315834e-3L;
411 const MagickRealType c2 = 0.393684603287860108352720146121813443561e-4L;
412 const MagickRealType c3 = -0.248947210682259168029030370205389323899e-5L;
413 const MagickRealType c4 = 0.107791837839662283066379987646635416692e-6L;
414 const MagickRealType c5 = -0.324874073895735800961260474028013982211e-8L;
415 const MagickRealType c6 = 0.628155216606695311524920882748052490116e-10L;
416 const MagickRealType c7 = -0.586110644039348333520104379959307242711e-12L;
nicolas3aab40c2010-09-19 21:14:15 +0000417 const MagickRealType p =
418 c0+xx*(c1+xx*(c2+xx*(c3+xx*(c4+xx*(c5+xx*(c6+xx*c7))))));
nicolas07bac812010-09-19 18:47:02 +0000419 return((xx-1.0)*(xx-4.0)*(xx-9.0)*(xx-16.0)*p);
anthony2d9b8b52010-09-14 08:31:07 +0000420#elif MAGICKCORE_QUANTUM_DEPTH <= 16
cristydbeb3eb2010-09-09 13:41:36 +0000421 /*
anthony2d9b8b52010-09-14 08:31:07 +0000422 Max. abs. rel. error 2.2e-8 < 1/2^25.
cristydbeb3eb2010-09-09 13:41:36 +0000423 */
424 const MagickRealType c0 = 0.173611107357320220183368594093166520811e-2L;
425 const MagickRealType c1 = -0.384240921114946632192116762889211361285e-3L;
nicolas3aab40c2010-09-19 21:14:15 +0000426 const MagickRealType c2 = 0.394201182359318128221229891724947048771e-4L;
427 const MagickRealType c3 = -0.250963301609117217660068889165550534856e-5L;
428 const MagickRealType c4 = 0.111902032818095784414237782071368805120e-6L;
429 const MagickRealType c5 = -0.372895101408779549368465614321137048875e-8L;
430 const MagickRealType c6 = 0.957694196677572570319816780188718518330e-10L;
cristydbeb3eb2010-09-09 13:41:36 +0000431 const MagickRealType c7 = -0.187208577776590710853865174371617338991e-11L;
432 const MagickRealType c8 = 0.253524321426864752676094495396308636823e-13L;
433 const MagickRealType c9 = -0.177084805010701112639035485248501049364e-15L;
anthony853d6972010-10-08 06:01:31 +0000434 const MagickRealType p =
nicolas3aab40c2010-09-19 21:14:15 +0000435 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 +0000436 return((xx-1.0)*(xx-4.0)*(xx-9.0)*(xx-16.0)*p);
nicolasc2d28f02010-09-27 18:56:15 +0000437#else
nicolas3aab40c2010-09-19 21:14:15 +0000438 /*
439 Max. abs. rel. error 1.2e-12 < 1/2^39.
440 */
441 const MagickRealType c0 = 0.173611111110910715186413700076827593074e-2L;
442 const MagickRealType c1 = -0.289105544717893415815859968653611245425e-3L;
443 const MagickRealType c2 = 0.206952161241815727624413291940849294025e-4L;
444 const MagickRealType c3 = -0.834446180169727178193268528095341741698e-6L;
445 const MagickRealType c4 = 0.207010104171026718629622453275917944941e-7L;
446 const MagickRealType c5 = -0.319724784938507108101517564300855542655e-9L;
447 const MagickRealType c6 = 0.288101675249103266147006509214934493930e-11L;
448 const MagickRealType c7 = -0.118218971804934245819960233886876537953e-13L;
449 const MagickRealType p =
450 c0+xx*(c1+xx*(c2+xx*(c3+xx*(c4+xx*(c5+xx*(c6+xx*c7))))));
451 const MagickRealType d0 = 1.0L;
452 const MagickRealType d1 = 0.547981619622284827495856984100563583948e-1L;
453 const MagickRealType d2 = 0.134226268835357312626304688047086921806e-2L;
454 const MagickRealType d3 = 0.178994697503371051002463656833597608689e-4L;
455 const MagickRealType d4 = 0.114633394140438168641246022557689759090e-6L;
456 const MagickRealType q = d0+xx*(d1+xx*(d2+xx*(d3+xx*d4)));
457 return((xx-1.0)*(xx-4.0)*(xx-9.0)*(xx-16.0)/q*p);
cristy657c6352010-08-29 14:05:08 +0000458#endif
cristy83017922010-09-05 20:45:15 +0000459 }
cristy3ed852e2009-09-05 21:47:34 +0000460}
461
462static MagickRealType Triangle(const MagickRealType x,
463 const ResizeFilter *magick_unused(resize_filter))
464{
465 /*
nicolas0edb0862010-09-19 18:56:19 +0000466 1st order (linear) B-Spline, bilinear interpolation, Tent 1D
anthony06b1edf2010-10-25 01:19:50 +0000467 filter, or a Bartlett 2D Cone filter. Also used as a
468 Bartlett Windowing function for Sinc().
cristy3ed852e2009-09-05 21:47:34 +0000469 */
470 if (x < 1.0)
471 return(1.0-x);
472 return(0.0);
473}
474
475static MagickRealType Welsh(const MagickRealType x,
476 const ResizeFilter *magick_unused(resize_filter))
477{
478 /*
479 Welsh parabolic windowing filter.
480 */
cristy560d8182010-09-08 22:36:25 +0000481 if (x < 1.0)
cristy3ed852e2009-09-05 21:47:34 +0000482 return(1.0-x*x);
483 return(0.0);
484}
485
486/*
487%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
488% %
489% %
490% %
491+ A c q u i r e R e s i z e F i l t e r %
492% %
493% %
494% %
495%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
496%
cristy31456282011-02-06 21:07:04 +0000497% AcquireResizeFilter() allocates the ResizeFilter structure. Choose from
498% these filters:
cristy3ed852e2009-09-05 21:47:34 +0000499%
500% FIR (Finite impulse Response) Filters
501% Box Triangle Quadratic
502% Cubic Hermite Catrom
503% Mitchell
504%
505% IIR (Infinite impulse Response) Filters
anthony48f77622010-10-03 14:32:31 +0000506% Gaussian Sinc Jinc (Bessel)
cristy3ed852e2009-09-05 21:47:34 +0000507%
anthony48f77622010-10-03 14:32:31 +0000508% Windowed Sinc/Jinc Filters
cristy3ed852e2009-09-05 21:47:34 +0000509% Blackman Hanning Hamming
anthony48f77622010-10-03 14:32:31 +0000510% Kaiser Lanczos
cristy3ed852e2009-09-05 21:47:34 +0000511%
anthony61b5ddd2010-10-05 02:33:31 +0000512% Special purpose Filters
anthony06b1edf2010-10-25 01:19:50 +0000513% SincFast LanczosSharp Lanczos2D Lanczos2DSharp Robidoux
anthony61b5ddd2010-10-05 02:33:31 +0000514%
anthony48f77622010-10-03 14:32:31 +0000515% The users "-filter" selection is used to lookup the default 'expert'
516% settings for that filter from a internal table. However any provided
517% 'expert' settings (see below) may override this selection.
518%
cristy31456282011-02-06 21:07:04 +0000519% FIR filters are used as is, and are limited to that filters support window
520% (unless over-ridden). 'Gaussian' while classed as an IIR filter, is also
521% simply clipped by its support size (currently 1.5 or approximatally 3*sigma
522% as recommended by many references)
cristy3ed852e2009-09-05 21:47:34 +0000523%
cristy31456282011-02-06 21:07:04 +0000524% The special a 'cylindrical' filter flag will promote the default 4-lobed
525% Windowed Sinc filter to a 3-lobed Windowed Jinc equivelent, which is better
526% suited to this style of image resampling. This typically happens when using
527% such a filter for images distortions.
cristy3ed852e2009-09-05 21:47:34 +0000528%
cristy31456282011-02-06 21:07:04 +0000529% Directly requesting 'Sinc', 'Jinc' function as a filter will force the use
530% of function without any windowing, or promotion for cylindrical usage. This
531% is not recommended, except by image processing experts, especially as part
532% of expert option filter function selection.
anthony06b1edf2010-10-25 01:19:50 +0000533%
cristy31456282011-02-06 21:07:04 +0000534% Two forms of the 'Sinc' function are available: Sinc and SincFast. Sinc is
535% computed using the traditional sin(pi*x)/(pi*x); it is selected if the user
536% specifically specifies the use of a Sinc filter. SincFast uses highly
537% accurate (and fast) polynomial (low Q) and rational (high Q) approximations,
538% and will be used by default in most cases.
anthonyba5a7c32010-09-15 02:42:25 +0000539%
cristy31456282011-02-06 21:07:04 +0000540% The Lanczos filter is a special 3-lobed Sinc-windowed Sinc filter (promoted
541% to Jinc-windowed Jinc for cylindrical (Elliptical Weighted Average) use).
542% The Sinc version is the most popular windowed filter.
anthony152700d2010-10-28 02:43:18 +0000543%
cristy31456282011-02-06 21:07:04 +0000544% LanczosSharp is a slightly sharpened (blur=0.9812505644269356 < 1) form of
545% the Lanczos filter, specifically designed for EWA distortion (as a
546% Jinc-Jinc); it can also be used as a slightly sharper orthogonal Lanczos
547% (Sinc-Sinc) filter. The chosen blur value comes as close as possible to
548% satisfying the following condition without changing the character of the
549% corresponding EWA filter:
anthony152700d2010-10-28 02:43:18 +0000550%
cristy31456282011-02-06 21:07:04 +0000551% 'No-Op' Vertical and Horizontal Line Preservation Condition: Images with
552% only vertical or horizontal features are preserved when performing 'no-op"
553% with EWA distortion.
anthony152700d2010-10-28 02:43:18 +0000554%
cristy31456282011-02-06 21:07:04 +0000555% The Lanczos2 and Lanczos2Sharp filters are 2-lobe versions of the Lanczos
556% filters. The 'sharp' version uses a blur factor of 0.9549963639785485,
557% again chosen because the resulting EWA filter comes as close as possible to
558% satisfying the above condition.
anthony06b1edf2010-10-25 01:19:50 +0000559%
cristy31456282011-02-06 21:07:04 +0000560% Robidoux is another filter tuned for EWA. It is the Keys cubic filter
561% defined by B=(228 - 108 sqrt(2))/199. Robidoux satisfies the "'No-Op'
562% Vertical and Horizontal Line Preservation Condition" exactly, and it
563% moderately blurs high frequency 'pixel-hash' patterns under no-op. It turns
564% out to be close to both Mitchell and Lanczos2Sharp. For example, its first
565% crossing is at (36 sqrt(2) + 123)/(72 sqrt(2) + 47), almost the same as the
566% first crossing of Mitchell and Lanczos2Sharp.
anthony61b5ddd2010-10-05 02:33:31 +0000567%
nicolas3061b8a2010-10-22 16:34:52 +0000568% 'EXPERT' OPTIONS:
nicolasce6dc292010-10-22 16:23:07 +0000569%
cristy31456282011-02-06 21:07:04 +0000570% These artifact "defines" are not recommended for production use without
571% expert knowledge of resampling, filtering, and the effects they have on the
572% resulting resampled (resize ro distorted) image.
nicolas3061b8a2010-10-22 16:34:52 +0000573%
anthony152700d2010-10-28 02:43:18 +0000574% They can be used to override any and all filter default, and it is
cristy31456282011-02-06 21:07:04 +0000575% recommended you make good use of "filter:verbose" to make sure that the
576% overall effect of your selection (before and after) is as expected.
nicolas3061b8a2010-10-22 16:34:52 +0000577%
cristy31456282011-02-06 21:07:04 +0000578% "filter:verbose" controls whether to output the exact results of the
579% filter selections made, as well as plotting data for graphing the
580% resulting filter over the filters support range.
cristy3ed852e2009-09-05 21:47:34 +0000581%
cristy31456282011-02-06 21:07:04 +0000582% "filter:filter" select the main function associated with this filter
583% name, as the weighting function of the filter. This can be used to
584% set a windowing function as a weighting function, for special
585% purposes, such as graphing.
cristy3ed852e2009-09-05 21:47:34 +0000586%
cristy31456282011-02-06 21:07:04 +0000587% If a "filter:window" operation has not been provided, a 'Box'
588% windowing function will be set to denote that no windowing function is
589% being used.
cristy3ed852e2009-09-05 21:47:34 +0000590%
cristy31456282011-02-06 21:07:04 +0000591% "filter:window" Select this windowing function for the filter. While any
592% filter could be used as a windowing function, using the 'first lobe' of
593% that filter over the whole support window, using a non-windowing
594% function is not advisible. If no weighting filter function is specifed
595% a 'SincFast' filter is used.
cristy3ed852e2009-09-05 21:47:34 +0000596%
cristy31456282011-02-06 21:07:04 +0000597% "filter:lobes" Number of lobes to use for the Sinc/Jinc filter. This a
598% simpler method of setting filter support size that will correctly
599% handle the Sinc/Jinc switch for an operators filtering requirements.
600% Only integers should be given.
cristy3ed852e2009-09-05 21:47:34 +0000601%
cristy31456282011-02-06 21:07:04 +0000602% "filter:support" Set the support size for filtering to the size given.
603% This not recommended for Sinc/Jinc windowed filters (lobes should be
604% used instead). This will override any 'filter:lobes' option.
cristy3ed852e2009-09-05 21:47:34 +0000605%
cristy31456282011-02-06 21:07:04 +0000606% "filter:win-support" Scale windowing function to this size instead. This
607% causes the windowing (or self-windowing Lagrange filter) to act is if
608% the support window it much much larger than what is actually supplied
609% to the calling operator. The filter however is still clipped to the
610% real support size given, by the support range suppiled to the caller.
611% If unset this will equal the normal filter support size.
anthonyb6d08c52010-09-13 01:17:04 +0000612%
cristy31456282011-02-06 21:07:04 +0000613% "filter:blur" Scale the filter and support window by this amount. A value
614% > 1 will generally result in a more burred image with more ringing
615% effects, while a value <1 will sharpen the resulting image with more
616% aliasing effects.
cristy3ed852e2009-09-05 21:47:34 +0000617%
cristy31456282011-02-06 21:07:04 +0000618% "filter:sigma" The sigma value to use for the Gaussian filter only.
619% Defaults to '1/2'. Using a different sigma effectively provides a
620% method of using the filter as a 'blur' convolution. Particularly when
621% using it for Distort.
anthonyf5e76ef2010-10-12 01:22:01 +0000622%
cristy3ed852e2009-09-05 21:47:34 +0000623% "filter:b"
cristy31456282011-02-06 21:07:04 +0000624% "filter:c" Override the preset B,C values for a Cubic type of filter.
625% If only one of these are given it is assumes to be a 'Keys' type of
626% filter such that B+2C=1, where Keys 'alpha' value = C.
cristy3ed852e2009-09-05 21:47:34 +0000627%
anthony06b1edf2010-10-25 01:19:50 +0000628% Examples:
cristy3ed852e2009-09-05 21:47:34 +0000629%
nicolas6e1267a2010-10-22 16:35:52 +0000630% Set a true un-windowed Sinc filter with 10 lobes (very slow):
anthony48f77622010-10-03 14:32:31 +0000631% -define filter:filter=Sinc
632% -define filter:lobes=8
cristy3ed852e2009-09-05 21:47:34 +0000633%
nicolas6e1267a2010-10-22 16:35:52 +0000634% Set an 8 lobe Lanczos (Sinc or Jinc) filter:
cristy3ed852e2009-09-05 21:47:34 +0000635% -filter Lanczos
anthony48f77622010-10-03 14:32:31 +0000636% -define filter:lobes=8
anthony7bdc0ed2010-09-15 01:52:32 +0000637%
cristy3ed852e2009-09-05 21:47:34 +0000638% The format of the AcquireResizeFilter method is:
639%
640% ResizeFilter *AcquireResizeFilter(const Image *image,
641% const FilterTypes filter_type, const MagickBooleanType radial,
642% ExceptionInfo *exception)
643%
cristy33b1c162010-01-23 22:51:51 +0000644% A description of each parameter follows:
645%
cristy3ed852e2009-09-05 21:47:34 +0000646% o image: the image.
647%
cristy31456282011-02-06 21:07:04 +0000648% o filter: the filter type, defining a preset filter, window and support.
649% The artifact settings listed above will override those selections.
cristy3ed852e2009-09-05 21:47:34 +0000650%
anthony48f77622010-10-03 14:32:31 +0000651% o blur: blur the filter by this amount, use 1.0 if unknown. Image
cristy31456282011-02-06 21:07:04 +0000652% artifact "filter:blur" will override this API call usage, including any
653% internal change (such as for cylindrical usage).
cristy3ed852e2009-09-05 21:47:34 +0000654%
cristy31456282011-02-06 21:07:04 +0000655% o radial: use a 1D orthogonal filter (Sinc) or 2D cylindrical (radial)
656% filter (Jinc).
cristy3ed852e2009-09-05 21:47:34 +0000657%
658% o exception: return any errors or warnings in this structure.
659%
660*/
661MagickExport ResizeFilter *AcquireResizeFilter(const Image *image,
cristyf59a8922010-02-28 19:51:23 +0000662 const FilterTypes filter,const MagickRealType blur,
cristy3ed852e2009-09-05 21:47:34 +0000663 const MagickBooleanType cylindrical,ExceptionInfo *exception)
664{
665 const char
666 *artifact;
667
668 FilterTypes
669 filter_type,
670 window_type;
671
cristy3ed852e2009-09-05 21:47:34 +0000672 MagickRealType
673 B,
anthonyf5e76ef2010-10-12 01:22:01 +0000674 C,
675 sigma;
cristy3ed852e2009-09-05 21:47:34 +0000676
677 register ResizeFilter
678 *resize_filter;
679
680 /*
cristy31456282011-02-06 21:07:04 +0000681 Table Mapping given Filter, into Weighting and Windowing functions. A
682 'Box' windowing function means its a simble non-windowed filter. An
683 'SincFast' filter function could be upgraded to a 'Jinc' filter if a
684 "cylindrical", unless a 'Sinc' or 'SincFast' filter was specifically
685 requested.
anthony449887b2010-09-10 02:23:33 +0000686
cristy31456282011-02-06 21:07:04 +0000687 WARNING: The order of this tabel must match the order of the FilterTypes
688 enumeration specified in "resample.h", or the filter names will not match
689 the filter being setup.
anthony1e2d5ca2010-09-10 02:24:35 +0000690
691 You can check filter setups with the "filter:verbose" setting.
cristy3ed852e2009-09-05 21:47:34 +0000692 */
693 static struct
694 {
695 FilterTypes
696 filter,
697 window;
698 } const mapping[SentinelFilter] =
699 {
nicolasb7dff642010-10-25 02:04:14 +0000700 { UndefinedFilter, BoxFilter }, /* Undefined (default to Box) */
701 { PointFilter, BoxFilter }, /* SPECIAL: Nearest neighbour */
702 { BoxFilter, BoxFilter }, /* Box averaging filter */
703 { TriangleFilter, BoxFilter }, /* Linear interpolation filter */
704 { HermiteFilter, BoxFilter }, /* Hermite interpolation filter */
705 { SincFastFilter, HanningFilter }, /* Hanning -- cosine-sinc */
706 { SincFastFilter, HammingFilter }, /* Hamming -- '' variation */
anthony06b1edf2010-10-25 01:19:50 +0000707 { SincFastFilter, BlackmanFilter }, /* Blackman -- 2*cosine-sinc */
nicolasb7dff642010-10-25 02:04:14 +0000708 { GaussianFilter, BoxFilter }, /* Gaussian blur filter */
709 { QuadraticFilter, BoxFilter }, /* Quadratic Gaussian approx */
710 { CubicFilter, BoxFilter }, /* Cubic B-Spline */
711 { CatromFilter, BoxFilter }, /* Cubic-Keys interpolator */
712 { MitchellFilter, BoxFilter }, /* 'Ideal' Cubic-Keys filter */
nicolasb7dff642010-10-25 02:04:14 +0000713 { JincFilter, BoxFilter }, /* Raw 3-lobed Jinc function */
714 { SincFilter, BoxFilter }, /* Raw 4-lobed Sinc function */
anthony152700d2010-10-28 02:43:18 +0000715 { SincFastFilter, BoxFilter }, /* Raw fast sinc ("Pade"-type) */
nicolasb7dff642010-10-25 02:04:14 +0000716 { SincFastFilter, KaiserFilter }, /* Kaiser -- square root-sinc */
717 { SincFastFilter, WelshFilter }, /* Welsh -- parabolic-sinc */
718 { SincFastFilter, CubicFilter }, /* Parzen -- cubic-sinc */
nicolasb7dff642010-10-25 02:04:14 +0000719 { SincFastFilter, BohmanFilter }, /* Bohman -- 2*cosine-sinc */
anthony06b1edf2010-10-25 01:19:50 +0000720 { SincFastFilter, TriangleFilter }, /* Bartlett -- triangle-sinc */
anthony152700d2010-10-28 02:43:18 +0000721 { LagrangeFilter, BoxFilter }, /* Lagrange self-windowing */
722 { LanczosFilter, LanczosFilter }, /* Lanczos Sinc-Sinc filters */
723 { LanczosSharpFilter, LanczosSharpFilter }, /* | these require */
724 { Lanczos2Filter, Lanczos2Filter }, /* | special handling */
725 { Lanczos2SharpFilter,Lanczos2SharpFilter },
726 { RobidouxFilter, BoxFilter }, /* Cubic Keys tuned for EWA */
cristy3ed852e2009-09-05 21:47:34 +0000727 };
728 /*
cristy31456282011-02-06 21:07:04 +0000729 Table mapping the filter/window from the above table to an actual function.
730 The default support size for that filter as a weighting function, the range
731 to scale with to use that function as a sinc windowing function, (typ 1.0).
anthony933b4bf2010-09-10 02:31:37 +0000732
anthony07a3f7f2010-09-16 03:03:11 +0000733 Note that the filter_type -> function is 1 to 1 except for Sinc(),
cristy31456282011-02-06 21:07:04 +0000734 SincFast(), and CubicBC() functions, which may have multiple filter to
735 function associations.
nicolas07bac812010-09-19 18:47:02 +0000736
cristy31456282011-02-06 21:07:04 +0000737 See "filter:verbose" handling below for the function -> filter mapping.
cristy3ed852e2009-09-05 21:47:34 +0000738 */
739 static struct
740 {
741 MagickRealType
742 (*function)(const MagickRealType, const ResizeFilter*),
nicolas8eccc162010-10-16 19:48:13 +0000743 lobes, /* Default lobes/support size of the weighting filter. */
anthony450db502010-10-19 04:03:03 +0000744 scale, /* Support when function used as a windowing function
745 Typically equal to the location of the first zero crossing. */
746 B,C; /* BC-spline coefficients, ignored if not a CubicBC filter. */
cristy3ed852e2009-09-05 21:47:34 +0000747 } const filters[SentinelFilter] =
748 {
anthony61b5ddd2010-10-05 02:33:31 +0000749 { Box, 0.5, 0.5, 0.0, 0.0 }, /* Undefined (default to Box) */
750 { Box, 0.0, 0.5, 0.0, 0.0 }, /* Point (special handling) */
751 { Box, 0.5, 0.5, 0.0, 0.0 }, /* Box */
752 { Triangle, 1.0, 1.0, 0.0, 0.0 }, /* Triangle */
753 { CubicBC, 1.0, 1.0, 0.0, 0.0 }, /* Hermite (cubic B=C=0) */
754 { Hanning, 1.0, 1.0, 0.0, 0.0 }, /* Hanning, cosine window */
755 { Hamming, 1.0, 1.0, 0.0, 0.0 }, /* Hamming, '' variation */
756 { Blackman, 1.0, 1.0, 0.0, 0.0 }, /* Blackman, 2*cosine window */
anthonyf5e76ef2010-10-12 01:22:01 +0000757 { Gaussian, 2.0, 1.5, 0.0, 0.0 }, /* Gaussian */
anthony61b5ddd2010-10-05 02:33:31 +0000758 { Quadratic, 1.5, 1.5, 0.0, 0.0 }, /* Quadratic gaussian */
759 { CubicBC, 2.0, 2.0, 1.0, 0.0 }, /* Cubic B-Spline (B=1,C=0) */
760 { CubicBC, 2.0, 1.0, 0.0, 0.5 }, /* Catmull-Rom (B=0,C=1/2) */
nicolasd349be62010-10-28 18:57:38 +0000761 { CubicBC, 2.0, 8.0/7.0, 1./3., 1./3. }, /* Mitchell (B=C=1/3) */
nicolasf8689f42010-10-18 16:14:08 +0000762 { Jinc, 3.0, 1.2196698912665045, 0.0, 0.0 }, /* Raw 3-lobed Jinc */
anthony61b5ddd2010-10-05 02:33:31 +0000763 { Sinc, 4.0, 1.0, 0.0, 0.0 }, /* Raw 4-lobed Sinc */
anthony06b1edf2010-10-25 01:19:50 +0000764 { SincFast, 4.0, 1.0, 0.0, 0.0 }, /* Raw fast sinc ("Pade"-type) */
anthony61b5ddd2010-10-05 02:33:31 +0000765 { Kaiser, 1.0, 1.0, 0.0, 0.0 }, /* Kaiser (square root window) */
766 { Welsh, 1.0, 1.0, 0.0, 0.0 }, /* Welsh (parabolic window) */
767 { CubicBC, 2.0, 2.0, 1.0, 0.0 }, /* Parzen (B-Spline window) */
anthony61b5ddd2010-10-05 02:33:31 +0000768 { Bohman, 1.0, 1.0, 0.0, 0.0 }, /* Bohman, 2*Cosine window */
769 { Triangle, 1.0, 1.0, 0.0, 0.0 }, /* Bartlett (triangle window) */
anthony152700d2010-10-28 02:43:18 +0000770 { Lagrange, 2.0, 1.0, 0.0, 0.0 }, /* Lagrange sinc approximation */
771 { SincFast, 3.0, 1.0, 0.0, 0.0 }, /* Lanczos, 3-lobed Sinc-Sinc */
772 { SincFast, 3.0, 1.0, 0.0, 0.0 }, /* lanczos, Sharpened */
773 { SincFast, 2.0, 1.0, 0.0, 0.0 }, /* Lanczos, 2-lobed */
774 { SincFast, 2.0, 1.0, 0.0, 0.0 }, /* Lanczos2, sharpened */
anthony450db502010-10-19 04:03:03 +0000775 { CubicBC, 2.0, 1.1685777620836932,
nicolas0d5e5322010-10-22 15:29:30 +0000776 0.37821575509399867, 0.31089212245300067 }
777 /* Robidoux: Keys cubic close to Lanczos2D sharpened */
cristy3ed852e2009-09-05 21:47:34 +0000778 };
779 /*
anthony9a98fc62010-10-11 02:47:19 +0000780 The known zero crossings of the Jinc() or more accurately the Jinc(x*PI)
anthony450db502010-10-19 04:03:03 +0000781 function being used as a filter. It is used by the "filter:lobes" expert
cristy31456282011-02-06 21:07:04 +0000782 setting and for 'lobes' for Jinc functions in the previous table. This way
783 users do not have to deal with the highly irrational lobe sizes of the Jinc
784 filter.
anthony48f77622010-10-03 14:32:31 +0000785
nicolase473f722010-10-07 00:05:13 +0000786 Values taken from
cristy31456282011-02-06 21:07:04 +0000787 http://cose.math.bas.bg/webMathematica/webComputing/BesselZeros.jsp using
788 Jv-function with v=1, then dividing by PI.
cristy3ed852e2009-09-05 21:47:34 +0000789 */
790 static MagickRealType
anthony48f77622010-10-03 14:32:31 +0000791 jinc_zeros[16] =
cristy3ed852e2009-09-05 21:47:34 +0000792 {
nicolas8eccc162010-10-16 19:48:13 +0000793 1.2196698912665045,
794 2.2331305943815286,
795 3.2383154841662362,
796 4.2410628637960699,
797 5.2427643768701817,
798 6.2439216898644877,
799 7.244759868719957,
800 8.2453949139520427,
801 9.2458926849494673,
802 10.246293348754916,
803 11.246622794877883,
804 12.246898461138105,
805 13.247132522181061,
806 14.247333735806849,
anthonyc2d07db2010-09-15 23:47:40 +0000807 15.2475085630373,
nicolas8eccc162010-10-16 19:48:13 +0000808 16.247661874700962
cristy3ed852e2009-09-05 21:47:34 +0000809 };
810
cristy33b1c162010-01-23 22:51:51 +0000811 /*
812 Allocate resize filter.
813 */
cristy3ed852e2009-09-05 21:47:34 +0000814 assert(image != (const Image *) NULL);
815 assert(image->signature == MagickSignature);
816 if (image->debug != MagickFalse)
817 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
818 assert(UndefinedFilter < filter && filter < SentinelFilter);
819 assert(exception != (ExceptionInfo *) NULL);
820 assert(exception->signature == MagickSignature);
cristy73bd4a52010-10-05 11:24:23 +0000821 resize_filter=(ResizeFilter *) AcquireMagickMemory(sizeof(*resize_filter));
cristy3ed852e2009-09-05 21:47:34 +0000822 if (resize_filter == (ResizeFilter *) NULL)
823 ThrowFatalException(ResourceLimitFatalError,"MemoryAllocationFailed");
cristy33b1c162010-01-23 22:51:51 +0000824 /*
825 Defaults for the requested filter.
826 */
827 filter_type=mapping[filter].filter;
828 window_type=mapping[filter].window;
anthony993d8382011-02-06 12:35:36 +0000829 resize_filter->blur = blur; /* function argument blur factor */
830 sigma = 0.5; /* guassian sigma of half a pixel by default */
anthony152700d2010-10-28 02:43:18 +0000831 /* Promote 1D Windowed Sinc Filters to a 2D Windowed Jinc filters */
832 if (cylindrical != MagickFalse && filter_type == SincFastFilter
833 && filter != SincFastFilter )
834 filter_type=JincFilter;
anthony61b5ddd2010-10-05 02:33:31 +0000835
anthony152700d2010-10-28 02:43:18 +0000836 /* Expert filter setting override */
cristy3ed852e2009-09-05 21:47:34 +0000837 artifact=GetImageArtifact(image,"filter:filter");
cristy33b1c162010-01-23 22:51:51 +0000838 if (artifact != (const char *) NULL)
839 {
anthony152700d2010-10-28 02:43:18 +0000840 ssize_t
841 option;
cristy9af9b5d2010-08-15 17:04:28 +0000842 option=ParseMagickOption(MagickFilterOptions,MagickFalse,artifact);
cristy33b1c162010-01-23 22:51:51 +0000843 if ((UndefinedFilter < option) && (option < SentinelFilter))
anthony61b5ddd2010-10-05 02:33:31 +0000844 { /* Raw filter request - no window function. */
cristy33b1c162010-01-23 22:51:51 +0000845 filter_type=(FilterTypes) option;
846 window_type=BoxFilter;
847 }
nicolas07bac812010-09-19 18:47:02 +0000848 /* Filter override with a specific window function. */
cristy33b1c162010-01-23 22:51:51 +0000849 artifact=GetImageArtifact(image,"filter:window");
850 if (artifact != (const char *) NULL)
851 {
cristy9af9b5d2010-08-15 17:04:28 +0000852 option=ParseMagickOption(MagickFilterOptions,MagickFalse,artifact);
cristy33b1c162010-01-23 22:51:51 +0000853 if ((UndefinedFilter < option) && (option < SentinelFilter))
anthony152700d2010-10-28 02:43:18 +0000854 window_type=(FilterTypes) option;
cristy33b1c162010-01-23 22:51:51 +0000855 }
cristy3ed852e2009-09-05 21:47:34 +0000856 }
cristy33b1c162010-01-23 22:51:51 +0000857 else
858 {
anthony48f77622010-10-03 14:32:31 +0000859 /* Window specified, but no filter function? Assume Sinc/Jinc. */
cristy33b1c162010-01-23 22:51:51 +0000860 artifact=GetImageArtifact(image,"filter:window");
861 if (artifact != (const char *) NULL)
862 {
anthony152700d2010-10-28 02:43:18 +0000863 ssize_t
864 option;
cristy33b1c162010-01-23 22:51:51 +0000865 option=ParseMagickOption(MagickFilterOptions,MagickFalse,
866 artifact);
867 if ((UndefinedFilter < option) && (option < SentinelFilter))
868 {
anthony61b5ddd2010-10-05 02:33:31 +0000869 filter_type=cylindrical != MagickFalse ?
870 JincFilter : SincFastFilter;
cristy19eb6412010-04-23 14:42:29 +0000871 window_type=(FilterTypes) option;
cristy33b1c162010-01-23 22:51:51 +0000872 }
873 }
874 }
anthony152700d2010-10-28 02:43:18 +0000875
nicolas07bac812010-09-19 18:47:02 +0000876 /* Assign the real functions to use for the filters selected. */
cristy33b1c162010-01-23 22:51:51 +0000877 resize_filter->filter=filters[filter_type].function;
anthony61b5ddd2010-10-05 02:33:31 +0000878 resize_filter->support=filters[filter_type].lobes;
cristy33b1c162010-01-23 22:51:51 +0000879 resize_filter->window=filters[window_type].function;
880 resize_filter->scale=filters[window_type].scale;
anthony029ba0e2010-10-29 00:54:24 +0000881 resize_filter->signature=MagickSignature;
anthony61b5ddd2010-10-05 02:33:31 +0000882
anthonyf5e76ef2010-10-12 01:22:01 +0000883 /* Filter Modifications for orthogonal/cylindrical usage */
anthony10b8bc82010-10-02 12:48:46 +0000884 if (cylindrical != MagickFalse)
885 switch (filter_type)
886 {
anthony10b8bc82010-10-02 12:48:46 +0000887 case BoxFilter:
888 /* Support for Cylindrical Box should be sqrt(2)/2 */
cristy1c9bb452010-10-03 16:48:33 +0000889 resize_filter->support=(MagickRealType) MagickSQ1_2;
anthony10b8bc82010-10-02 12:48:46 +0000890 break;
anthony152700d2010-10-28 02:43:18 +0000891 case LanczosFilter:
892 case LanczosSharpFilter:
893 case Lanczos2Filter:
894 case Lanczos2SharpFilter:
895 resize_filter->filter=filters[JincFilter].function;
896 resize_filter->window=filters[JincFilter].function;
anthony029ba0e2010-10-29 00:54:24 +0000897 resize_filter->scale=filters[JincFilter].scale;
898 /* number of lobes (support window size) remain unchanged */
anthony152700d2010-10-28 02:43:18 +0000899 break;
anthony81b8bf92010-10-02 13:54:34 +0000900 default:
901 break;
anthony10b8bc82010-10-02 12:48:46 +0000902 }
anthony152700d2010-10-28 02:43:18 +0000903 /* Global Sharpening (regardless of orthoginal/cylindrical) */
904 switch (filter_type)
905 {
906 case LanczosSharpFilter:
nicolasaf6ee7c2010-11-13 03:34:22 +0000907 resize_filter->blur *= 0.9812505644269356;
anthony152700d2010-10-28 02:43:18 +0000908 break;
909 case Lanczos2SharpFilter:
nicolasca48bce2010-11-14 17:54:53 +0000910 resize_filter->blur *= 0.9549963639785485;
anthony152700d2010-10-28 02:43:18 +0000911 break;
912 default:
913 break;
914 }
anthony61b5ddd2010-10-05 02:33:31 +0000915
anthonyf5e76ef2010-10-12 01:22:01 +0000916 /*
anthony06b1edf2010-10-25 01:19:50 +0000917 ** Other Expert Option Modifications
anthonyf5e76ef2010-10-12 01:22:01 +0000918 */
919
920 /* User Sigma Override - no support change */
921 artifact=GetImageArtifact(image,"filter:sigma");
922 if (artifact != (const char *) NULL)
923 sigma=StringToDouble(artifact);
anthony993d8382011-02-06 12:35:36 +0000924 /* Define coefficents for Gaussian */
anthonyf5e76ef2010-10-12 01:22:01 +0000925 if ( GaussianFilter ) {
cristy5cce74b2010-11-15 03:24:28 +0000926 resize_filter->coefficient[0]=1.0/(2.0*sigma*sigma);
927 resize_filter->coefficient[1]=(MagickRealType) (1.0/(Magick2PI*sigma*
anthony993d8382011-02-06 12:35:36 +0000928 sigma)); /* Normalization Multiplier - unneeded for filters */
anthonyf5e76ef2010-10-12 01:22:01 +0000929 }
930
931 /* Blur Override */
932 artifact=GetImageArtifact(image,"filter:blur");
933 if (artifact != (const char *) NULL)
934 resize_filter->blur=StringToDouble(artifact);
935 if (resize_filter->blur < MagickEpsilon)
936 resize_filter->blur=(MagickRealType) MagickEpsilon;
937
938 /* Support Overrides */
cristy3ed852e2009-09-05 21:47:34 +0000939 artifact=GetImageArtifact(image,"filter:lobes");
cristy33b1c162010-01-23 22:51:51 +0000940 if (artifact != (const char *) NULL)
941 {
cristybb503372010-05-27 20:51:26 +0000942 ssize_t
cristy33b1c162010-01-23 22:51:51 +0000943 lobes;
944
cristy96b16132010-08-29 17:19:52 +0000945 lobes=(ssize_t) StringToLong(artifact);
cristy33b1c162010-01-23 22:51:51 +0000946 if (lobes < 1)
947 lobes=1;
948 resize_filter->support=(MagickRealType) lobes;
cristy3ed852e2009-09-05 21:47:34 +0000949 }
anthony152700d2010-10-28 02:43:18 +0000950 /* Convert a Jinc function lobes value to a real support value */
anthony61b5ddd2010-10-05 02:33:31 +0000951 if (resize_filter->filter == Jinc)
952 {
953 if (resize_filter->support > 16)
954 resize_filter->support=jinc_zeros[15]; /* largest entry in table */
955 else
956 resize_filter->support = jinc_zeros[((long)resize_filter->support)-1];
957 }
958 /* expert override of the support setting */
cristy3ed852e2009-09-05 21:47:34 +0000959 artifact=GetImageArtifact(image,"filter:support");
960 if (artifact != (const char *) NULL)
cristy33b1c162010-01-23 22:51:51 +0000961 resize_filter->support=fabs(StringToDouble(artifact));
962 /*
nicolas07bac812010-09-19 18:47:02 +0000963 Scale windowing function separatally to the support 'clipping'
964 window that calling operator is planning to actually use. (Expert
965 override)
cristy3ed852e2009-09-05 21:47:34 +0000966 */
anthony55f12332010-09-10 01:13:02 +0000967 resize_filter->window_support=resize_filter->support; /* default */
cristy3ed852e2009-09-05 21:47:34 +0000968 artifact=GetImageArtifact(image,"filter:win-support");
969 if (artifact != (const char *) NULL)
cristy33b1c162010-01-23 22:51:51 +0000970 resize_filter->window_support=fabs(StringToDouble(artifact));
971 /*
anthony029ba0e2010-10-29 00:54:24 +0000972 Adjust window function scaling to match windowing support for
anthony1f90a6b2010-09-14 08:56:31 +0000973 weighting function. This avoids a division on every filter call.
anthony55f12332010-09-10 01:13:02 +0000974 */
975 resize_filter->scale /= resize_filter->window_support;
anthonyf5e76ef2010-10-12 01:22:01 +0000976
anthony55f12332010-09-10 01:13:02 +0000977 /*
anthonyf5e76ef2010-10-12 01:22:01 +0000978 * Set Cubic Spline B,C values, calculate Cubic coefficients.
cristy33b1c162010-01-23 22:51:51 +0000979 */
cristy3ed852e2009-09-05 21:47:34 +0000980 B=0.0;
981 C=0.0;
cristy33b1c162010-01-23 22:51:51 +0000982 if ((filters[filter_type].function == CubicBC) ||
983 (filters[window_type].function == CubicBC))
984 {
anthony2d9b8b52010-09-14 08:31:07 +0000985 B=filters[filter_type].B;
986 C=filters[filter_type].C;
987 if (filters[window_type].function == CubicBC)
cristy33b1c162010-01-23 22:51:51 +0000988 {
anthony2d9b8b52010-09-14 08:31:07 +0000989 B=filters[window_type].B;
990 C=filters[window_type].C;
cristy33b1c162010-01-23 22:51:51 +0000991 }
cristy33b1c162010-01-23 22:51:51 +0000992 artifact=GetImageArtifact(image,"filter:b");
cristy3ed852e2009-09-05 21:47:34 +0000993 if (artifact != (const char *) NULL)
cristy33b1c162010-01-23 22:51:51 +0000994 {
995 B=StringToDouble(artifact);
nicolasd15ee9a2010-10-24 18:39:45 +0000996 C=(1.0-B)/2.0; /* Calculate C to get a Keys cubic filter. */
anthonyf5e76ef2010-10-12 01:22:01 +0000997 artifact=GetImageArtifact(image,"filter:c"); /* user C override */
cristy33b1c162010-01-23 22:51:51 +0000998 if (artifact != (const char *) NULL)
999 C=StringToDouble(artifact);
1000 }
1001 else
1002 {
1003 artifact=GetImageArtifact(image,"filter:c");
1004 if (artifact != (const char *) NULL)
1005 {
1006 C=StringToDouble(artifact);
nicolasb7dff642010-10-25 02:04:14 +00001007 B=1.0-2.0*C; /* Calculate B to get a Keys cubic filter. */
cristy33b1c162010-01-23 22:51:51 +00001008 }
1009 }
nicolasc6bac3b2010-10-24 18:10:45 +00001010 /* Convert B,C values into Cubic Coefficents. See CubicBC(). */
nicolasd15ee9a2010-10-24 18:39:45 +00001011 {
anthony06b1edf2010-10-25 01:19:50 +00001012 const double twoB = B+B;
cristy5cce74b2010-11-15 03:24:28 +00001013 resize_filter->coefficient[0]=1.0-(1.0/3.0)*B;
1014 resize_filter->coefficient[1]=-3.0+twoB+C;
1015 resize_filter->coefficient[2]=2.0-1.5*B-C;
1016 resize_filter->coefficient[3]=(4.0/3.0)*B+4.0*C;
1017 resize_filter->coefficient[4]=-8.0*C-twoB;
1018 resize_filter->coefficient[5]=B+5.0*C;
1019 resize_filter->coefficient[6]=(-1.0/6.0)*B-C;
nicolasd15ee9a2010-10-24 18:39:45 +00001020 }
nicolasc6bac3b2010-10-24 18:10:45 +00001021 }
anthonyf5e76ef2010-10-12 01:22:01 +00001022
anthony55f12332010-09-10 01:13:02 +00001023 /*
nicolas07bac812010-09-19 18:47:02 +00001024 Expert Option Request for verbose details of the resulting filter.
anthony55f12332010-09-10 01:13:02 +00001025 */
cristyf5b49372010-10-16 01:06:47 +00001026#if defined(MAGICKCORE_OPENMP_SUPPORT)
1027 #pragma omp master
1028 {
1029#endif
1030 artifact=GetImageArtifact(image,"filter:verbose");
anthony28ad1d72010-10-26 06:30:24 +00001031 if (IsMagickTrue(artifact))
cristyf5b49372010-10-16 01:06:47 +00001032 {
1033 double
anthony06b1edf2010-10-25 01:19:50 +00001034 support,
cristyf5b49372010-10-16 01:06:47 +00001035 x;
cristy3ed852e2009-09-05 21:47:34 +00001036
cristyf5b49372010-10-16 01:06:47 +00001037 /*
1038 Set the weighting function properly when the weighting
1039 function may not exactly match the filter of the same name.
anthony06b1edf2010-10-25 01:19:50 +00001040 EG: a Point filter is really uses a Box weighting function
cristyf5b49372010-10-16 01:06:47 +00001041 with a different support than is typically used.
cristyf5b49372010-10-16 01:06:47 +00001042 */
1043 if (resize_filter->filter == Box) filter_type=BoxFilter;
1044 if (resize_filter->filter == Sinc) filter_type=SincFilter;
1045 if (resize_filter->filter == SincFast) filter_type=SincFastFilter;
1046 if (resize_filter->filter == Jinc) filter_type=JincFilter;
1047 if (resize_filter->filter == CubicBC) filter_type=CubicFilter;
anthony1ad20052010-10-29 01:53:03 +00001048 if (resize_filter->window == Box) window_type=BoxFilter;
anthony152700d2010-10-28 02:43:18 +00001049 if (resize_filter->window == Sinc) window_type=SincFilter;
1050 if (resize_filter->window == SincFast) window_type=SincFastFilter;
anthony1ad20052010-10-29 01:53:03 +00001051 if (resize_filter->window == Jinc) window_type=JincFilter;
anthony152700d2010-10-28 02:43:18 +00001052 if (resize_filter->window == CubicBC) window_type=CubicFilter;
cristyf5b49372010-10-16 01:06:47 +00001053 /*
1054 Report Filter Details.
1055 */
1056 support=GetResizeFilterSupport(resize_filter); /* practical_support */
1057 (void) fprintf(stdout,"# Resize Filter (for graphing)\n#\n");
anthony06b1edf2010-10-25 01:19:50 +00001058 (void) fprintf(stdout,"# filter = %s\n",
1059 MagickOptionToMnemonic(MagickFilterOptions,filter_type));
1060 (void) fprintf(stdout,"# window = %s\n",
1061 MagickOptionToMnemonic(MagickFilterOptions, window_type));
1062 (void) fprintf(stdout,"# support = %.*g\n",
1063 GetMagickPrecision(),(double) resize_filter->support);
1064 (void) fprintf(stdout,"# win-support = %.*g\n",
1065 GetMagickPrecision(),(double) resize_filter->window_support);
1066 (void) fprintf(stdout,"# scale_blur = %.*g\n",
1067 GetMagickPrecision(), (double)resize_filter->blur);
cristyf5b49372010-10-16 01:06:47 +00001068 if ( filter_type == GaussianFilter )
anthony06b1edf2010-10-25 01:19:50 +00001069 (void) fprintf(stdout,"# gaussian_sigma = %.*g\n",
1070 GetMagickPrecision(), (double)sigma);
1071 (void) fprintf(stdout,"# practical_support = %.*g\n",
1072 GetMagickPrecision(), (double)support);
cristyf5b49372010-10-16 01:06:47 +00001073 if ( filter_type == CubicFilter || window_type == CubicFilter )
anthony06b1edf2010-10-25 01:19:50 +00001074 (void) fprintf(stdout,"# B,C = %.*g,%.*g\n",
1075 GetMagickPrecision(),(double)B, GetMagickPrecision(),(double)C);
cristyf5b49372010-10-16 01:06:47 +00001076 (void) fprintf(stdout,"\n");
1077 /*
1078 Output values of resulting filter graph -- for graphing
1079 filter result.
1080 */
1081 for (x=0.0; x <= support; x+=0.01f)
1082 (void) fprintf(stdout,"%5.2lf\t%.*g\n",x,GetMagickPrecision(),
1083 (double) GetResizeFilterWeight(resize_filter,x));
1084 /* A final value so gnuplot can graph the 'stop' properly. */
1085 (void) fprintf(stdout,"%5.2lf\t%.*g\n",support,GetMagickPrecision(),
1086 0.0);
1087 }
1088 /* Output the above once only for each image - remove setting */
1089 (void) DeleteImageArtifact((Image *) image,"filter:verbose");
1090#if defined(MAGICKCORE_OPENMP_SUPPORT)
1091 }
1092#endif
cristy3ed852e2009-09-05 21:47:34 +00001093 return(resize_filter);
1094}
1095
1096/*
1097%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1098% %
1099% %
1100% %
1101% A d a p t i v e R e s i z e I m a g e %
1102% %
1103% %
1104% %
1105%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1106%
1107% AdaptiveResizeImage() adaptively resize image with pixel resampling.
1108%
1109% The format of the AdaptiveResizeImage method is:
1110%
cristy9af9b5d2010-08-15 17:04:28 +00001111% Image *AdaptiveResizeImage(const Image *image,const size_t columns,
1112% const size_t rows,ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +00001113%
1114% A description of each parameter follows:
1115%
1116% o image: the image.
1117%
1118% o columns: the number of columns in the resized image.
1119%
1120% o rows: the number of rows in the resized image.
1121%
1122% o exception: return any errors or warnings in this structure.
1123%
1124*/
1125MagickExport Image *AdaptiveResizeImage(const Image *image,
cristybb503372010-05-27 20:51:26 +00001126 const size_t columns,const size_t rows,ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +00001127{
1128#define AdaptiveResizeImageTag "Resize/Image"
1129
cristyc4c8d132010-01-07 01:58:38 +00001130 CacheView
1131 *resize_view;
1132
cristy3ed852e2009-09-05 21:47:34 +00001133 Image
1134 *resize_image;
1135
cristy3ed852e2009-09-05 21:47:34 +00001136 MagickBooleanType
cristy5cce74b2010-11-15 03:24:28 +00001137 status;
cristy3ed852e2009-09-05 21:47:34 +00001138
cristy5cce74b2010-11-15 03:24:28 +00001139 MagickOffsetType
1140 progress;
cristy3ed852e2009-09-05 21:47:34 +00001141
1142 ResampleFilter
cristya6a18782010-11-15 01:56:25 +00001143 **resample_filter;
cristy3ed852e2009-09-05 21:47:34 +00001144
cristy9af9b5d2010-08-15 17:04:28 +00001145 ssize_t
1146 y;
1147
cristy3ed852e2009-09-05 21:47:34 +00001148 /*
1149 Adaptively resize image.
1150 */
1151 assert(image != (const Image *) NULL);
1152 assert(image->signature == MagickSignature);
1153 if (image->debug != MagickFalse)
1154 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
1155 assert(exception != (ExceptionInfo *) NULL);
1156 assert(exception->signature == MagickSignature);
1157 if ((columns == 0) || (rows == 0))
1158 return((Image *) NULL);
1159 if ((columns == image->columns) && (rows == image->rows))
1160 return(CloneImage(image,0,0,MagickTrue,exception));
1161 resize_image=CloneImage(image,columns,rows,MagickTrue,exception);
1162 if (resize_image == (Image *) NULL)
1163 return((Image *) NULL);
1164 if (SetImageStorageClass(resize_image,DirectClass) == MagickFalse)
1165 {
1166 InheritException(exception,&resize_image->exception);
1167 resize_image=DestroyImage(resize_image);
1168 return((Image *) NULL);
1169 }
cristy5cce74b2010-11-15 03:24:28 +00001170 status=MagickTrue;
1171 progress=0;
cristya6a18782010-11-15 01:56:25 +00001172 resample_filter=AcquireResampleFilterThreadSet(image,
1173 UndefinedVirtualPixelMethod,MagickTrue,exception);
cristy3ed852e2009-09-05 21:47:34 +00001174 resize_view=AcquireCacheView(resize_image);
cristy5cce74b2010-11-15 03:24:28 +00001175#if defined(MAGICKCORE_OPENMP_SUPPORT)
1176 #pragma omp parallel for schedule(dynamic,4) shared(progress,status) omp_throttle(1)
1177#endif
cristybb503372010-05-27 20:51:26 +00001178 for (y=0; y < (ssize_t) resize_image->rows; y++)
cristy3ed852e2009-09-05 21:47:34 +00001179 {
cristya6a18782010-11-15 01:56:25 +00001180 const int
1181 id = GetOpenMPThreadId();
1182
cristy5cce74b2010-11-15 03:24:28 +00001183 MagickPixelPacket
1184 pixel;
1185
1186 PointInfo
1187 offset;
1188
cristy3ed852e2009-09-05 21:47:34 +00001189 register IndexPacket
cristyc47d1f82009-11-26 01:44:43 +00001190 *restrict resize_indexes;
cristy3ed852e2009-09-05 21:47:34 +00001191
cristy3ed852e2009-09-05 21:47:34 +00001192 register PixelPacket
cristyc47d1f82009-11-26 01:44:43 +00001193 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +00001194
cristyb2a9be02010-11-15 15:00:14 +00001195 register ssize_t
1196 x;
1197
cristy5cce74b2010-11-15 03:24:28 +00001198 if (status == MagickFalse)
1199 continue;
cristy3ed852e2009-09-05 21:47:34 +00001200 q=QueueCacheViewAuthenticPixels(resize_view,0,y,resize_image->columns,1,
1201 exception);
1202 if (q == (PixelPacket *) NULL)
cristy5cce74b2010-11-15 03:24:28 +00001203 continue;
cristy3ed852e2009-09-05 21:47:34 +00001204 resize_indexes=GetCacheViewAuthenticIndexQueue(resize_view);
1205 offset.y=((MagickRealType) y*image->rows/resize_image->rows);
cristy5cce74b2010-11-15 03:24:28 +00001206 GetMagickPixelPacket(image,&pixel);
cristybb503372010-05-27 20:51:26 +00001207 for (x=0; x < (ssize_t) resize_image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +00001208 {
1209 offset.x=((MagickRealType) x*image->columns/resize_image->columns);
cristya6a18782010-11-15 01:56:25 +00001210 (void) ResamplePixelColor(resample_filter[id],offset.x-0.5,offset.y-0.5,
cristy3ed852e2009-09-05 21:47:34 +00001211 &pixel);
1212 SetPixelPacket(resize_image,&pixel,q,resize_indexes+x);
1213 q++;
1214 }
1215 if (SyncCacheViewAuthenticPixels(resize_view,exception) == MagickFalse)
cristy5cce74b2010-11-15 03:24:28 +00001216 continue;
1217 if (image->progress_monitor != (MagickProgressMonitor) NULL)
1218 {
1219 MagickBooleanType
1220 proceed;
1221
1222#if defined(MAGICKCORE_OPENMP_SUPPORT)
1223 #pragma omp critical (MagickCore_AdaptiveResizeImage)
1224#endif
1225 proceed=SetImageProgress(image,AdaptiveResizeImageTag,progress++,
1226 image->rows);
1227 if (proceed == MagickFalse)
1228 status=MagickFalse;
1229 }
cristy3ed852e2009-09-05 21:47:34 +00001230 }
cristya6a18782010-11-15 01:56:25 +00001231 resample_filter=DestroyResampleFilterThreadSet(resample_filter);
cristy3ed852e2009-09-05 21:47:34 +00001232 resize_view=DestroyCacheView(resize_view);
cristy5cce74b2010-11-15 03:24:28 +00001233 if (status == MagickFalse)
1234 resize_image=DestroyImage(resize_image);
cristy3ed852e2009-09-05 21:47:34 +00001235 return(resize_image);
1236}
1237
1238/*
1239%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1240% %
1241% %
1242% %
1243+ B e s s e l O r d e r O n e %
1244% %
1245% %
1246% %
1247%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1248%
1249% BesselOrderOne() computes the Bessel function of x of the first kind of
anthony48f77622010-10-03 14:32:31 +00001250% order 0. This is used to create the Jinc() filter function below.
cristy3ed852e2009-09-05 21:47:34 +00001251%
1252% Reduce x to |x| since j1(x)= -j1(-x), and for x in (0,8]
1253%
1254% j1(x) = x*j1(x);
1255%
1256% For x in (8,inf)
1257%
1258% j1(x) = sqrt(2/(pi*x))*(p1(x)*cos(x1)-q1(x)*sin(x1))
1259%
1260% where x1 = x-3*pi/4. Compute sin(x1) and cos(x1) as follow:
1261%
1262% cos(x1) = cos(x)cos(3pi/4)+sin(x)sin(3pi/4)
1263% = 1/sqrt(2) * (sin(x) - cos(x))
1264% sin(x1) = sin(x)cos(3pi/4)-cos(x)sin(3pi/4)
1265% = -1/sqrt(2) * (sin(x) + cos(x))
1266%
1267% The format of the BesselOrderOne method is:
1268%
1269% MagickRealType BesselOrderOne(MagickRealType x)
1270%
1271% A description of each parameter follows:
1272%
1273% o x: MagickRealType value.
1274%
1275*/
1276
1277#undef I0
1278static MagickRealType I0(MagickRealType x)
1279{
1280 MagickRealType
1281 sum,
1282 t,
1283 y;
1284
cristybb503372010-05-27 20:51:26 +00001285 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +00001286 i;
1287
1288 /*
1289 Zeroth order Bessel function of the first kind.
1290 */
1291 sum=1.0;
1292 y=x*x/4.0;
1293 t=y;
1294 for (i=2; t > MagickEpsilon; i++)
1295 {
1296 sum+=t;
1297 t*=y/((MagickRealType) i*i);
1298 }
1299 return(sum);
1300}
1301
1302#undef J1
1303static MagickRealType J1(MagickRealType x)
1304{
1305 MagickRealType
1306 p,
1307 q;
1308
cristybb503372010-05-27 20:51:26 +00001309 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +00001310 i;
1311
1312 static const double
1313 Pone[] =
1314 {
1315 0.581199354001606143928050809e+21,
1316 -0.6672106568924916298020941484e+20,
1317 0.2316433580634002297931815435e+19,
1318 -0.3588817569910106050743641413e+17,
1319 0.2908795263834775409737601689e+15,
1320 -0.1322983480332126453125473247e+13,
1321 0.3413234182301700539091292655e+10,
1322 -0.4695753530642995859767162166e+7,
1323 0.270112271089232341485679099e+4
1324 },
1325 Qone[] =
1326 {
1327 0.11623987080032122878585294e+22,
1328 0.1185770712190320999837113348e+20,
1329 0.6092061398917521746105196863e+17,
1330 0.2081661221307607351240184229e+15,
1331 0.5243710262167649715406728642e+12,
1332 0.1013863514358673989967045588e+10,
1333 0.1501793594998585505921097578e+7,
1334 0.1606931573481487801970916749e+4,
1335 0.1e+1
1336 };
1337
1338 p=Pone[8];
1339 q=Qone[8];
1340 for (i=7; i >= 0; i--)
1341 {
1342 p=p*x*x+Pone[i];
1343 q=q*x*x+Qone[i];
1344 }
1345 return(p/q);
1346}
1347
1348#undef P1
1349static MagickRealType P1(MagickRealType x)
1350{
1351 MagickRealType
1352 p,
1353 q;
1354
cristybb503372010-05-27 20:51:26 +00001355 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +00001356 i;
1357
1358 static const double
1359 Pone[] =
1360 {
1361 0.352246649133679798341724373e+5,
1362 0.62758845247161281269005675e+5,
1363 0.313539631109159574238669888e+5,
1364 0.49854832060594338434500455e+4,
1365 0.2111529182853962382105718e+3,
1366 0.12571716929145341558495e+1
1367 },
1368 Qone[] =
1369 {
1370 0.352246649133679798068390431e+5,
1371 0.626943469593560511888833731e+5,
1372 0.312404063819041039923015703e+5,
1373 0.4930396490181088979386097e+4,
1374 0.2030775189134759322293574e+3,
1375 0.1e+1
1376 };
1377
1378 p=Pone[5];
1379 q=Qone[5];
1380 for (i=4; i >= 0; i--)
1381 {
1382 p=p*(8.0/x)*(8.0/x)+Pone[i];
1383 q=q*(8.0/x)*(8.0/x)+Qone[i];
1384 }
1385 return(p/q);
1386}
1387
1388#undef Q1
1389static MagickRealType Q1(MagickRealType x)
1390{
1391 MagickRealType
1392 p,
1393 q;
1394
cristybb503372010-05-27 20:51:26 +00001395 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +00001396 i;
1397
1398 static const double
1399 Pone[] =
1400 {
1401 0.3511751914303552822533318e+3,
1402 0.7210391804904475039280863e+3,
1403 0.4259873011654442389886993e+3,
1404 0.831898957673850827325226e+2,
1405 0.45681716295512267064405e+1,
1406 0.3532840052740123642735e-1
1407 },
1408 Qone[] =
1409 {
1410 0.74917374171809127714519505e+4,
1411 0.154141773392650970499848051e+5,
1412 0.91522317015169922705904727e+4,
1413 0.18111867005523513506724158e+4,
1414 0.1038187585462133728776636e+3,
1415 0.1e+1
1416 };
1417
1418 p=Pone[5];
1419 q=Qone[5];
1420 for (i=4; i >= 0; i--)
1421 {
1422 p=p*(8.0/x)*(8.0/x)+Pone[i];
1423 q=q*(8.0/x)*(8.0/x)+Qone[i];
1424 }
1425 return(p/q);
1426}
1427
1428static MagickRealType BesselOrderOne(MagickRealType x)
1429{
1430 MagickRealType
1431 p,
1432 q;
1433
1434 if (x == 0.0)
1435 return(0.0);
1436 p=x;
1437 if (x < 0.0)
1438 x=(-x);
1439 if (x < 8.0)
1440 return(p*J1(x));
1441 q=sqrt((double) (2.0/(MagickPI*x)))*(P1(x)*(1.0/sqrt(2.0)*(sin((double) x)-
1442 cos((double) x)))-8.0/x*Q1(x)*(-1.0/sqrt(2.0)*(sin((double) x)+
1443 cos((double) x))));
1444 if (p < 0.0)
1445 q=(-q);
1446 return(q);
1447}
1448
1449/*
1450%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1451% %
1452% %
1453% %
1454+ D e s t r o y R e s i z e F i l t e r %
1455% %
1456% %
1457% %
1458%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1459%
1460% DestroyResizeFilter() destroy the resize filter.
1461%
cristya2ffd7e2010-03-10 20:50:30 +00001462% The format of the DestroyResizeFilter method is:
cristy3ed852e2009-09-05 21:47:34 +00001463%
1464% ResizeFilter *DestroyResizeFilter(ResizeFilter *resize_filter)
1465%
1466% A description of each parameter follows:
1467%
1468% o resize_filter: the resize filter.
1469%
1470*/
1471MagickExport ResizeFilter *DestroyResizeFilter(ResizeFilter *resize_filter)
1472{
1473 assert(resize_filter != (ResizeFilter *) NULL);
1474 assert(resize_filter->signature == MagickSignature);
1475 resize_filter->signature=(~MagickSignature);
1476 resize_filter=(ResizeFilter *) RelinquishMagickMemory(resize_filter);
1477 return(resize_filter);
1478}
1479
1480/*
1481%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1482% %
1483% %
1484% %
1485+ G e t R e s i z e F i l t e r S u p p o r t %
1486% %
1487% %
1488% %
1489%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1490%
1491% GetResizeFilterSupport() return the current support window size for this
1492% filter. Note that this may have been enlarged by filter:blur factor.
1493%
1494% The format of the GetResizeFilterSupport method is:
1495%
1496% MagickRealType GetResizeFilterSupport(const ResizeFilter *resize_filter)
1497%
1498% A description of each parameter follows:
1499%
1500% o filter: Image filter to use.
1501%
1502*/
1503MagickExport MagickRealType GetResizeFilterSupport(
1504 const ResizeFilter *resize_filter)
1505{
1506 assert(resize_filter != (ResizeFilter *) NULL);
1507 assert(resize_filter->signature == MagickSignature);
1508 return(resize_filter->support*resize_filter->blur);
1509}
1510
1511/*
1512%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1513% %
1514% %
1515% %
1516+ G e t R e s i z e F i l t e r W e i g h t %
1517% %
1518% %
1519% %
1520%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1521%
1522% GetResizeFilterWeight evaluates the specified resize filter at the point x
1523% which usally lies between zero and the filters current 'support' and
1524% returns the weight of the filter function at that point.
1525%
1526% The format of the GetResizeFilterWeight method is:
1527%
1528% MagickRealType GetResizeFilterWeight(const ResizeFilter *resize_filter,
1529% const MagickRealType x)
1530%
1531% A description of each parameter follows:
1532%
1533% o filter: the filter type.
1534%
1535% o x: the point.
1536%
1537*/
1538MagickExport MagickRealType GetResizeFilterWeight(
1539 const ResizeFilter *resize_filter,const MagickRealType x)
1540{
1541 MagickRealType
cristyb8385862010-09-26 01:27:51 +00001542 scale,
1543 x_blur;
cristy3ed852e2009-09-05 21:47:34 +00001544
1545 /*
1546 Windowing function - scale the weighting filter by this amount.
1547 */
1548 assert(resize_filter != (ResizeFilter *) NULL);
1549 assert(resize_filter->signature == MagickSignature);
anthony55f12332010-09-10 01:13:02 +00001550 x_blur=fabs((double) x)/resize_filter->blur; /* X offset with blur scaling */
cristy3ed852e2009-09-05 21:47:34 +00001551 if ((resize_filter->window_support < MagickEpsilon) ||
1552 (resize_filter->window == Box))
anthony55f12332010-09-10 01:13:02 +00001553 scale=1.0; /* Point or Box Filter -- avoid division by zero */
cristy3ed852e2009-09-05 21:47:34 +00001554 else
1555 {
anthony55f12332010-09-10 01:13:02 +00001556 scale=resize_filter->scale;
1557 scale=resize_filter->window(x_blur*scale,resize_filter);
cristy3ed852e2009-09-05 21:47:34 +00001558 }
anthony55f12332010-09-10 01:13:02 +00001559 return(scale*resize_filter->filter(x_blur,resize_filter));
cristy3ed852e2009-09-05 21:47:34 +00001560}
1561
1562/*
1563%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1564% %
1565% %
1566% %
1567% M a g n i f y I m a g e %
1568% %
1569% %
1570% %
1571%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1572%
1573% MagnifyImage() is a convenience method that scales an image proportionally
1574% to twice its size.
1575%
1576% The format of the MagnifyImage method is:
1577%
1578% Image *MagnifyImage(const Image *image,ExceptionInfo *exception)
1579%
1580% A description of each parameter follows:
1581%
1582% o image: the image.
1583%
1584% o exception: return any errors or warnings in this structure.
1585%
1586*/
1587MagickExport Image *MagnifyImage(const Image *image,ExceptionInfo *exception)
1588{
1589 Image
1590 *magnify_image;
1591
1592 assert(image != (Image *) NULL);
1593 assert(image->signature == MagickSignature);
1594 if (image->debug != MagickFalse)
1595 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
1596 assert(exception != (ExceptionInfo *) NULL);
1597 assert(exception->signature == MagickSignature);
1598 magnify_image=ResizeImage(image,2*image->columns,2*image->rows,CubicFilter,
1599 1.0,exception);
1600 return(magnify_image);
1601}
1602
1603/*
1604%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1605% %
1606% %
1607% %
1608% M i n i f y I m a g e %
1609% %
1610% %
1611% %
1612%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1613%
1614% MinifyImage() is a convenience method that scales an image proportionally
1615% to half its size.
1616%
1617% The format of the MinifyImage method is:
1618%
1619% Image *MinifyImage(const Image *image,ExceptionInfo *exception)
1620%
1621% A description of each parameter follows:
1622%
1623% o image: the image.
1624%
1625% o exception: return any errors or warnings in this structure.
1626%
1627*/
1628MagickExport Image *MinifyImage(const Image *image,ExceptionInfo *exception)
1629{
1630 Image
1631 *minify_image;
1632
1633 assert(image != (Image *) NULL);
1634 assert(image->signature == MagickSignature);
1635 if (image->debug != MagickFalse)
1636 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
1637 assert(exception != (ExceptionInfo *) NULL);
1638 assert(exception->signature == MagickSignature);
cristy31456282011-02-06 21:07:04 +00001639 minify_image=ResizeImage(image,image->columns/2,image->rows/2,CubicFilter,1.0,
1640 exception);
cristy3ed852e2009-09-05 21:47:34 +00001641 return(minify_image);
1642}
1643
1644/*
1645%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1646% %
1647% %
1648% %
1649% R e s a m p l e I m a g e %
1650% %
1651% %
1652% %
1653%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1654%
1655% ResampleImage() resize image in terms of its pixel size, so that when
1656% displayed at the given resolution it will be the same size in terms of
1657% real world units as the original image at the original resolution.
1658%
1659% The format of the ResampleImage method is:
1660%
1661% Image *ResampleImage(Image *image,const double x_resolution,
1662% const double y_resolution,const FilterTypes filter,const double blur,
1663% ExceptionInfo *exception)
1664%
1665% A description of each parameter follows:
1666%
1667% o image: the image to be resized to fit the given resolution.
1668%
1669% o x_resolution: the new image x resolution.
1670%
1671% o y_resolution: the new image y resolution.
1672%
1673% o filter: Image filter to use.
1674%
1675% o blur: the blur factor where > 1 is blurry, < 1 is sharp.
1676%
1677*/
1678MagickExport Image *ResampleImage(const Image *image,const double x_resolution,
1679 const double y_resolution,const FilterTypes filter,const double blur,
1680 ExceptionInfo *exception)
1681{
1682#define ResampleImageTag "Resample/Image"
1683
1684 Image
1685 *resample_image;
1686
cristybb503372010-05-27 20:51:26 +00001687 size_t
cristy3ed852e2009-09-05 21:47:34 +00001688 height,
1689 width;
1690
1691 /*
1692 Initialize sampled image attributes.
1693 */
1694 assert(image != (const Image *) NULL);
1695 assert(image->signature == MagickSignature);
1696 if (image->debug != MagickFalse)
1697 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
1698 assert(exception != (ExceptionInfo *) NULL);
1699 assert(exception->signature == MagickSignature);
cristy9af9b5d2010-08-15 17:04:28 +00001700 width=(size_t) (x_resolution*image->columns/(image->x_resolution == 0.0 ?
1701 72.0 : image->x_resolution)+0.5);
1702 height=(size_t) (y_resolution*image->rows/(image->y_resolution == 0.0 ?
1703 72.0 : image->y_resolution)+0.5);
cristy3ed852e2009-09-05 21:47:34 +00001704 resample_image=ResizeImage(image,width,height,filter,blur,exception);
1705 if (resample_image != (Image *) NULL)
1706 {
1707 resample_image->x_resolution=x_resolution;
1708 resample_image->y_resolution=y_resolution;
1709 }
1710 return(resample_image);
1711}
1712#if defined(MAGICKCORE_LQR_DELEGATE)
1713
1714/*
1715%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1716% %
1717% %
1718% %
1719% L i q u i d R e s c a l e I m a g e %
1720% %
1721% %
1722% %
1723%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1724%
1725% LiquidRescaleImage() rescales image with seam carving.
1726%
1727% The format of the LiquidRescaleImage method is:
1728%
1729% Image *LiquidRescaleImage(const Image *image,
cristybb503372010-05-27 20:51:26 +00001730% const size_t columns,const size_t rows,
cristy3ed852e2009-09-05 21:47:34 +00001731% const double delta_x,const double rigidity,ExceptionInfo *exception)
1732%
1733% A description of each parameter follows:
1734%
1735% o image: the image.
1736%
1737% o columns: the number of columns in the rescaled image.
1738%
1739% o rows: the number of rows in the rescaled image.
1740%
1741% o delta_x: maximum seam transversal step (0 means straight seams).
1742%
1743% o rigidity: introduce a bias for non-straight seams (typically 0).
1744%
1745% o exception: return any errors or warnings in this structure.
1746%
1747*/
cristy9af9b5d2010-08-15 17:04:28 +00001748MagickExport Image *LiquidRescaleImage(const Image *image,const size_t columns,
1749 const size_t rows,const double delta_x,const double rigidity,
1750 ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +00001751{
1752#define LiquidRescaleImageTag "Rescale/Image"
1753
cristyc5c6f662010-09-22 14:23:02 +00001754 CacheView
1755 *rescale_view;
1756
cristy3ed852e2009-09-05 21:47:34 +00001757 const char
1758 *map;
1759
1760 guchar
1761 *packet;
1762
1763 Image
1764 *rescale_image;
1765
1766 int
1767 x,
1768 y;
1769
1770 LqrCarver
1771 *carver;
1772
1773 LqrRetVal
1774 lqr_status;
1775
1776 MagickBooleanType
1777 status;
1778
1779 MagickPixelPacket
1780 pixel;
1781
1782 unsigned char
1783 *pixels;
1784
1785 /*
1786 Liquid rescale image.
1787 */
1788 assert(image != (const Image *) NULL);
1789 assert(image->signature == MagickSignature);
1790 if (image->debug != MagickFalse)
1791 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
1792 assert(exception != (ExceptionInfo *) NULL);
1793 assert(exception->signature == MagickSignature);
1794 if ((columns == 0) || (rows == 0))
1795 return((Image *) NULL);
1796 if ((columns == image->columns) && (rows == image->rows))
1797 return(CloneImage(image,0,0,MagickTrue,exception));
1798 if ((columns <= 2) || (rows <= 2))
cristyd1bb3bc2010-09-07 00:43:58 +00001799 return(ResizeImage(image,columns,rows,image->filter,image->blur,exception));
cristy3ed852e2009-09-05 21:47:34 +00001800 if ((columns >= (2*image->columns)) || (rows >= (2*image->rows)))
1801 {
1802 Image
1803 *resize_image;
1804
cristybb503372010-05-27 20:51:26 +00001805 size_t
cristy3ed852e2009-09-05 21:47:34 +00001806 height,
1807 width;
1808
1809 /*
1810 Honor liquid resize size limitations.
1811 */
1812 for (width=image->columns; columns >= (2*width-1); width*=2);
1813 for (height=image->rows; rows >= (2*height-1); height*=2);
1814 resize_image=ResizeImage(image,width,height,image->filter,image->blur,
1815 exception);
1816 if (resize_image == (Image *) NULL)
1817 return((Image *) NULL);
1818 rescale_image=LiquidRescaleImage(resize_image,columns,rows,delta_x,
1819 rigidity,exception);
1820 resize_image=DestroyImage(resize_image);
1821 return(rescale_image);
1822 }
1823 map="RGB";
1824 if (image->matte == MagickFalse)
1825 map="RGBA";
1826 if (image->colorspace == CMYKColorspace)
1827 {
1828 map="CMYK";
1829 if (image->matte == MagickFalse)
1830 map="CMYKA";
1831 }
1832 pixels=(unsigned char *) AcquireQuantumMemory(image->columns,image->rows*
1833 strlen(map)*sizeof(*pixels));
1834 if (pixels == (unsigned char *) NULL)
1835 return((Image *) NULL);
1836 status=ExportImagePixels(image,0,0,image->columns,image->rows,map,CharPixel,
1837 pixels,exception);
1838 if (status == MagickFalse)
1839 {
1840 pixels=(unsigned char *) RelinquishMagickMemory(pixels);
1841 ThrowImageException(ResourceLimitError,"MemoryAllocationFailed");
1842 }
1843 carver=lqr_carver_new(pixels,image->columns,image->rows,strlen(map));
1844 if (carver == (LqrCarver *) NULL)
1845 {
1846 pixels=(unsigned char *) RelinquishMagickMemory(pixels);
1847 ThrowImageException(ResourceLimitError,"MemoryAllocationFailed");
1848 }
1849 lqr_status=lqr_carver_init(carver,(int) delta_x,rigidity);
1850 lqr_status=lqr_carver_resize(carver,columns,rows);
1851 rescale_image=CloneImage(image,lqr_carver_get_width(carver),
1852 lqr_carver_get_height(carver),MagickTrue,exception);
1853 if (rescale_image == (Image *) NULL)
1854 {
1855 pixels=(unsigned char *) RelinquishMagickMemory(pixels);
1856 return((Image *) NULL);
1857 }
1858 if (SetImageStorageClass(rescale_image,DirectClass) == MagickFalse)
1859 {
1860 InheritException(exception,&rescale_image->exception);
1861 rescale_image=DestroyImage(rescale_image);
1862 return((Image *) NULL);
1863 }
1864 GetMagickPixelPacket(rescale_image,&pixel);
1865 (void) lqr_carver_scan_reset(carver);
cristyc5c6f662010-09-22 14:23:02 +00001866 rescale_view=AcquireCacheView(rescale_image);
cristy3ed852e2009-09-05 21:47:34 +00001867 while (lqr_carver_scan(carver,&x,&y,&packet) != 0)
1868 {
1869 register IndexPacket
cristyc47d1f82009-11-26 01:44:43 +00001870 *restrict rescale_indexes;
cristy3ed852e2009-09-05 21:47:34 +00001871
1872 register PixelPacket
cristyc47d1f82009-11-26 01:44:43 +00001873 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +00001874
anthony22aad252010-09-23 06:59:07 +00001875 q=QueueCacheViewAuthenticPixels(rescale_view,x,y,1,1,exception);
cristy3ed852e2009-09-05 21:47:34 +00001876 if (q == (PixelPacket *) NULL)
1877 break;
cristyc5c6f662010-09-22 14:23:02 +00001878 rescale_indexes=GetCacheViewAuthenticIndexQueue(rescale_view);
cristy3ed852e2009-09-05 21:47:34 +00001879 pixel.red=QuantumRange*(packet[0]/255.0);
1880 pixel.green=QuantumRange*(packet[1]/255.0);
1881 pixel.blue=QuantumRange*(packet[2]/255.0);
1882 if (image->colorspace != CMYKColorspace)
1883 {
1884 if (image->matte == MagickFalse)
1885 pixel.opacity=QuantumRange*(packet[3]/255.0);
1886 }
1887 else
1888 {
1889 pixel.index=QuantumRange*(packet[3]/255.0);
1890 if (image->matte == MagickFalse)
1891 pixel.opacity=QuantumRange*(packet[4]/255.0);
1892 }
1893 SetPixelPacket(rescale_image,&pixel,q,rescale_indexes);
cristyc5c6f662010-09-22 14:23:02 +00001894 if (SyncCacheViewAuthenticPixels(rescale_view,exception) == MagickFalse)
cristy3ed852e2009-09-05 21:47:34 +00001895 break;
1896 }
cristyc5c6f662010-09-22 14:23:02 +00001897 rescale_view=DestroyCacheView(rescale_view);
cristy3ed852e2009-09-05 21:47:34 +00001898 /*
1899 Relinquish resources.
1900 */
1901 lqr_carver_destroy(carver);
1902 return(rescale_image);
1903}
1904#else
1905MagickExport Image *LiquidRescaleImage(const Image *image,
cristy9af9b5d2010-08-15 17:04:28 +00001906 const size_t magick_unused(columns),const size_t magick_unused(rows),
1907 const double magick_unused(delta_x),const double magick_unused(rigidity),
1908 ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +00001909{
1910 assert(image != (const Image *) NULL);
1911 assert(image->signature == MagickSignature);
1912 if (image->debug != MagickFalse)
1913 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
1914 assert(exception != (ExceptionInfo *) NULL);
1915 assert(exception->signature == MagickSignature);
1916 (void) ThrowMagickException(exception,GetMagickModule(),MissingDelegateError,
1917 "DelegateLibrarySupportNotBuiltIn","`%s' (LQR)",image->filename);
1918 return((Image *) NULL);
1919}
1920#endif
1921
1922/*
1923%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1924% %
1925% %
1926% %
1927% R e s i z e I m a g e %
1928% %
1929% %
1930% %
1931%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1932%
1933% ResizeImage() scales an image to the desired dimensions, using the given
cristy5d824382010-09-06 14:00:17 +00001934% filter (see AcquireFilterInfo()).
cristy3ed852e2009-09-05 21:47:34 +00001935%
1936% If an undefined filter is given the filter defaults to Mitchell for a
1937% colormapped image, a image with a matte channel, or if the image is
1938% enlarged. Otherwise the filter defaults to a Lanczos.
1939%
1940% ResizeImage() was inspired by Paul Heckbert's "zoom" program.
1941%
1942% The format of the ResizeImage method is:
1943%
cristybb503372010-05-27 20:51:26 +00001944% Image *ResizeImage(Image *image,const size_t columns,
1945% const size_t rows,const FilterTypes filter,const double blur,
cristy3ed852e2009-09-05 21:47:34 +00001946% ExceptionInfo *exception)
1947%
1948% A description of each parameter follows:
1949%
1950% o image: the image.
1951%
1952% o columns: the number of columns in the scaled image.
1953%
1954% o rows: the number of rows in the scaled image.
1955%
1956% o filter: Image filter to use.
1957%
cristy9af9b5d2010-08-15 17:04:28 +00001958% o blur: the blur factor where > 1 is blurry, < 1 is sharp. Typically set
1959% this to 1.0.
cristy3ed852e2009-09-05 21:47:34 +00001960%
1961% o exception: return any errors or warnings in this structure.
1962%
1963*/
1964
1965typedef struct _ContributionInfo
1966{
1967 MagickRealType
1968 weight;
1969
cristybb503372010-05-27 20:51:26 +00001970 ssize_t
cristy3ed852e2009-09-05 21:47:34 +00001971 pixel;
1972} ContributionInfo;
1973
1974static ContributionInfo **DestroyContributionThreadSet(
1975 ContributionInfo **contribution)
1976{
cristybb503372010-05-27 20:51:26 +00001977 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +00001978 i;
1979
1980 assert(contribution != (ContributionInfo **) NULL);
cristybb503372010-05-27 20:51:26 +00001981 for (i=0; i < (ssize_t) GetOpenMPMaximumThreads(); i++)
cristy3ed852e2009-09-05 21:47:34 +00001982 if (contribution[i] != (ContributionInfo *) NULL)
1983 contribution[i]=(ContributionInfo *) RelinquishMagickMemory(
1984 contribution[i]);
cristyb41ee102010-10-04 16:46:15 +00001985 contribution=(ContributionInfo **) RelinquishMagickMemory(contribution);
cristy3ed852e2009-09-05 21:47:34 +00001986 return(contribution);
1987}
1988
1989static ContributionInfo **AcquireContributionThreadSet(const size_t count)
1990{
cristybb503372010-05-27 20:51:26 +00001991 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +00001992 i;
1993
1994 ContributionInfo
1995 **contribution;
1996
cristybb503372010-05-27 20:51:26 +00001997 size_t
cristy3ed852e2009-09-05 21:47:34 +00001998 number_threads;
1999
2000 number_threads=GetOpenMPMaximumThreads();
cristyb41ee102010-10-04 16:46:15 +00002001 contribution=(ContributionInfo **) AcquireQuantumMemory(number_threads,
cristy3ed852e2009-09-05 21:47:34 +00002002 sizeof(*contribution));
2003 if (contribution == (ContributionInfo **) NULL)
2004 return((ContributionInfo **) NULL);
2005 (void) ResetMagickMemory(contribution,0,number_threads*sizeof(*contribution));
cristybb503372010-05-27 20:51:26 +00002006 for (i=0; i < (ssize_t) number_threads; i++)
cristy3ed852e2009-09-05 21:47:34 +00002007 {
2008 contribution[i]=(ContributionInfo *) AcquireQuantumMemory(count,
2009 sizeof(**contribution));
2010 if (contribution[i] == (ContributionInfo *) NULL)
2011 return(DestroyContributionThreadSet(contribution));
2012 }
2013 return(contribution);
2014}
2015
2016static inline double MagickMax(const double x,const double y)
2017{
2018 if (x > y)
2019 return(x);
2020 return(y);
2021}
2022
2023static inline double MagickMin(const double x,const double y)
2024{
2025 if (x < y)
2026 return(x);
2027 return(y);
2028}
2029
2030static MagickBooleanType HorizontalFilter(const ResizeFilter *resize_filter,
2031 const Image *image,Image *resize_image,const MagickRealType x_factor,
cristy9af9b5d2010-08-15 17:04:28 +00002032 const MagickSizeType span,MagickOffsetType *offset,ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +00002033{
2034#define ResizeImageTag "Resize/Image"
2035
cristyfa112112010-01-04 17:48:07 +00002036 CacheView
2037 *image_view,
2038 *resize_view;
2039
cristy3ed852e2009-09-05 21:47:34 +00002040 ClassType
2041 storage_class;
2042
2043 ContributionInfo
cristyfa112112010-01-04 17:48:07 +00002044 **restrict contributions;
cristy3ed852e2009-09-05 21:47:34 +00002045
cristy3ed852e2009-09-05 21:47:34 +00002046 MagickBooleanType
2047 status;
2048
2049 MagickPixelPacket
2050 zero;
2051
2052 MagickRealType
2053 scale,
2054 support;
2055
cristy9af9b5d2010-08-15 17:04:28 +00002056 ssize_t
2057 x;
2058
cristy3ed852e2009-09-05 21:47:34 +00002059 /*
2060 Apply filter to resize horizontally from image to resize image.
2061 */
cristy5d824382010-09-06 14:00:17 +00002062 scale=MagickMax(1.0/x_factor+MagickEpsilon,1.0);
cristy3ed852e2009-09-05 21:47:34 +00002063 support=scale*GetResizeFilterSupport(resize_filter);
2064 storage_class=support > 0.5 ? DirectClass : image->storage_class;
2065 if (SetImageStorageClass(resize_image,storage_class) == MagickFalse)
2066 {
2067 InheritException(exception,&resize_image->exception);
2068 return(MagickFalse);
2069 }
2070 if (support < 0.5)
2071 {
2072 /*
nicolas07bac812010-09-19 18:47:02 +00002073 Support too small even for nearest neighbour: Reduce to point
2074 sampling.
cristy3ed852e2009-09-05 21:47:34 +00002075 */
2076 support=(MagickRealType) 0.5;
2077 scale=1.0;
2078 }
2079 contributions=AcquireContributionThreadSet((size_t) (2.0*support+3.0));
2080 if (contributions == (ContributionInfo **) NULL)
2081 {
2082 (void) ThrowMagickException(exception,GetMagickModule(),
2083 ResourceLimitError,"MemoryAllocationFailed","`%s'",image->filename);
2084 return(MagickFalse);
2085 }
2086 status=MagickTrue;
2087 scale=1.0/scale;
2088 (void) ResetMagickMemory(&zero,0,sizeof(zero));
2089 image_view=AcquireCacheView(image);
2090 resize_view=AcquireCacheView(resize_image);
cristyb5d5f722009-11-04 03:03:49 +00002091#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristy3ed852e2009-09-05 21:47:34 +00002092 #pragma omp parallel for shared(status)
2093#endif
cristybb503372010-05-27 20:51:26 +00002094 for (x=0; x < (ssize_t) resize_image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +00002095 {
cristy3ed852e2009-09-05 21:47:34 +00002096 MagickRealType
2097 center,
2098 density;
2099
2100 register const IndexPacket
cristyc47d1f82009-11-26 01:44:43 +00002101 *restrict indexes;
cristy3ed852e2009-09-05 21:47:34 +00002102
2103 register const PixelPacket
cristyc47d1f82009-11-26 01:44:43 +00002104 *restrict p;
cristy3ed852e2009-09-05 21:47:34 +00002105
cristy03dbbd22010-09-19 23:04:47 +00002106 register ContributionInfo
2107 *restrict contribution;
2108
cristy3ed852e2009-09-05 21:47:34 +00002109 register IndexPacket
cristyc47d1f82009-11-26 01:44:43 +00002110 *restrict resize_indexes;
cristy3ed852e2009-09-05 21:47:34 +00002111
cristy3ed852e2009-09-05 21:47:34 +00002112 register PixelPacket
cristyc47d1f82009-11-26 01:44:43 +00002113 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +00002114
cristy03dbbd22010-09-19 23:04:47 +00002115 register ssize_t
2116 y;
2117
cristy9af9b5d2010-08-15 17:04:28 +00002118 ssize_t
2119 n,
2120 start,
2121 stop;
2122
cristy3ed852e2009-09-05 21:47:34 +00002123 if (status == MagickFalse)
2124 continue;
2125 center=(MagickRealType) (x+0.5)/x_factor;
cristybb503372010-05-27 20:51:26 +00002126 start=(ssize_t) MagickMax(center-support+0.5,0.0);
2127 stop=(ssize_t) MagickMin(center+support+0.5,(double) image->columns);
cristy3ed852e2009-09-05 21:47:34 +00002128 density=0.0;
2129 contribution=contributions[GetOpenMPThreadId()];
2130 for (n=0; n < (stop-start); n++)
2131 {
2132 contribution[n].pixel=start+n;
2133 contribution[n].weight=GetResizeFilterWeight(resize_filter,scale*
2134 ((MagickRealType) (start+n)-center+0.5));
2135 density+=contribution[n].weight;
2136 }
2137 if ((density != 0.0) && (density != 1.0))
2138 {
cristybb503372010-05-27 20:51:26 +00002139 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +00002140 i;
2141
2142 /*
2143 Normalize.
2144 */
2145 density=1.0/density;
2146 for (i=0; i < n; i++)
2147 contribution[i].weight*=density;
2148 }
cristy9af9b5d2010-08-15 17:04:28 +00002149 p=GetCacheViewVirtualPixels(image_view,contribution[0].pixel,0,(size_t)
2150 (contribution[n-1].pixel-contribution[0].pixel+1),image->rows,exception);
cristy3ed852e2009-09-05 21:47:34 +00002151 q=QueueCacheViewAuthenticPixels(resize_view,x,0,1,resize_image->rows,
2152 exception);
2153 if ((p == (const PixelPacket *) NULL) || (q == (PixelPacket *) NULL))
2154 {
2155 status=MagickFalse;
2156 continue;
2157 }
2158 indexes=GetCacheViewVirtualIndexQueue(image_view);
2159 resize_indexes=GetCacheViewAuthenticIndexQueue(resize_view);
cristybb503372010-05-27 20:51:26 +00002160 for (y=0; y < (ssize_t) resize_image->rows; y++)
cristy3ed852e2009-09-05 21:47:34 +00002161 {
cristy3ed852e2009-09-05 21:47:34 +00002162 MagickPixelPacket
2163 pixel;
2164
2165 MagickRealType
2166 alpha;
2167
cristybb503372010-05-27 20:51:26 +00002168 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +00002169 i;
2170
cristy9af9b5d2010-08-15 17:04:28 +00002171 ssize_t
2172 j;
2173
cristy3ed852e2009-09-05 21:47:34 +00002174 pixel=zero;
2175 if (image->matte == MagickFalse)
2176 {
2177 for (i=0; i < n; i++)
2178 {
2179 j=y*(contribution[n-1].pixel-contribution[0].pixel+1)+
2180 (contribution[i].pixel-contribution[0].pixel);
2181 alpha=contribution[i].weight;
2182 pixel.red+=alpha*(p+j)->red;
2183 pixel.green+=alpha*(p+j)->green;
2184 pixel.blue+=alpha*(p+j)->blue;
2185 pixel.opacity+=alpha*(p+j)->opacity;
2186 }
cristyce70c172010-01-07 17:15:30 +00002187 SetRedPixelComponent(q,ClampRedPixelComponent(&pixel));
2188 SetGreenPixelComponent(q,ClampGreenPixelComponent(&pixel));
2189 SetBluePixelComponent(q,ClampBluePixelComponent(&pixel));
2190 SetOpacityPixelComponent(q,ClampOpacityPixelComponent(&pixel));
cristy3ed852e2009-09-05 21:47:34 +00002191 if ((image->colorspace == CMYKColorspace) &&
2192 (resize_image->colorspace == CMYKColorspace))
2193 {
2194 for (i=0; i < n; i++)
2195 {
2196 j=y*(contribution[n-1].pixel-contribution[0].pixel+1)+
2197 (contribution[i].pixel-contribution[0].pixel);
2198 alpha=contribution[i].weight;
2199 pixel.index+=alpha*indexes[j];
2200 }
cristyce70c172010-01-07 17:15:30 +00002201 resize_indexes[y]=(IndexPacket) ClampToQuantum(pixel.index);
cristy3ed852e2009-09-05 21:47:34 +00002202 }
2203 }
2204 else
2205 {
2206 MagickRealType
2207 gamma;
2208
2209 gamma=0.0;
2210 for (i=0; i < n; i++)
2211 {
2212 j=y*(contribution[n-1].pixel-contribution[0].pixel+1)+
2213 (contribution[i].pixel-contribution[0].pixel);
cristy46f08202010-01-10 04:04:21 +00002214 alpha=contribution[i].weight*QuantumScale*
2215 GetAlphaPixelComponent(p+j);
cristy3ed852e2009-09-05 21:47:34 +00002216 pixel.red+=alpha*(p+j)->red;
2217 pixel.green+=alpha*(p+j)->green;
2218 pixel.blue+=alpha*(p+j)->blue;
2219 pixel.opacity+=contribution[i].weight*(p+j)->opacity;
2220 gamma+=alpha;
2221 }
2222 gamma=1.0/(fabs((double) gamma) <= MagickEpsilon ? 1.0 : gamma);
cristyce70c172010-01-07 17:15:30 +00002223 q->red=ClampToQuantum(gamma*GetRedPixelComponent(&pixel));
2224 q->green=ClampToQuantum(gamma*GetGreenPixelComponent(&pixel));
2225 q->blue=ClampToQuantum(gamma*GetBluePixelComponent(&pixel));
2226 SetOpacityPixelComponent(q,ClampOpacityPixelComponent(&pixel));
cristy3ed852e2009-09-05 21:47:34 +00002227 if ((image->colorspace == CMYKColorspace) &&
2228 (resize_image->colorspace == CMYKColorspace))
2229 {
2230 for (i=0; i < n; i++)
2231 {
2232 j=y*(contribution[n-1].pixel-contribution[0].pixel+1)+
2233 (contribution[i].pixel-contribution[0].pixel);
cristy46f08202010-01-10 04:04:21 +00002234 alpha=contribution[i].weight*QuantumScale*
2235 GetAlphaPixelComponent(p+j);
cristyc197d1c2009-10-13 19:56:53 +00002236 pixel.index+=alpha*indexes[j];
cristy3ed852e2009-09-05 21:47:34 +00002237 }
cristyce70c172010-01-07 17:15:30 +00002238 resize_indexes[y]=(IndexPacket) ClampToQuantum(gamma*
2239 GetIndexPixelComponent(&pixel));
cristy3ed852e2009-09-05 21:47:34 +00002240 }
2241 }
2242 if ((resize_image->storage_class == PseudoClass) &&
2243 (image->storage_class == PseudoClass))
2244 {
cristybb503372010-05-27 20:51:26 +00002245 i=(ssize_t) (MagickMin(MagickMax(center,(double) start),(double) stop-
cristy3ed852e2009-09-05 21:47:34 +00002246 1.0)+0.5);
2247 j=y*(contribution[n-1].pixel-contribution[0].pixel+1)+
2248 (contribution[i-start].pixel-contribution[0].pixel);
2249 resize_indexes[y]=indexes[j];
2250 }
2251 q++;
2252 }
2253 if (SyncCacheViewAuthenticPixels(resize_view,exception) == MagickFalse)
2254 status=MagickFalse;
2255 if (image->progress_monitor != (MagickProgressMonitor) NULL)
2256 {
2257 MagickBooleanType
2258 proceed;
2259
cristyb5d5f722009-11-04 03:03:49 +00002260#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristy3ed852e2009-09-05 21:47:34 +00002261 #pragma omp critical (MagickCore_HorizontalFilter)
2262#endif
cristy9af9b5d2010-08-15 17:04:28 +00002263 proceed=SetImageProgress(image,ResizeImageTag,(*offset)++,span);
cristy3ed852e2009-09-05 21:47:34 +00002264 if (proceed == MagickFalse)
2265 status=MagickFalse;
2266 }
2267 }
2268 resize_view=DestroyCacheView(resize_view);
2269 image_view=DestroyCacheView(image_view);
2270 contributions=DestroyContributionThreadSet(contributions);
2271 return(status);
2272}
2273
2274static MagickBooleanType VerticalFilter(const ResizeFilter *resize_filter,
2275 const Image *image,Image *resize_image,const MagickRealType y_factor,
cristy9af9b5d2010-08-15 17:04:28 +00002276 const MagickSizeType span,MagickOffsetType *offset,ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +00002277{
cristyfa112112010-01-04 17:48:07 +00002278 CacheView
2279 *image_view,
2280 *resize_view;
2281
cristy3ed852e2009-09-05 21:47:34 +00002282 ClassType
2283 storage_class;
2284
2285 ContributionInfo
cristyfa112112010-01-04 17:48:07 +00002286 **restrict contributions;
cristy3ed852e2009-09-05 21:47:34 +00002287
cristy3ed852e2009-09-05 21:47:34 +00002288 MagickBooleanType
2289 status;
2290
2291 MagickPixelPacket
2292 zero;
2293
2294 MagickRealType
2295 scale,
2296 support;
2297
cristy9af9b5d2010-08-15 17:04:28 +00002298 ssize_t
2299 y;
2300
cristy3ed852e2009-09-05 21:47:34 +00002301 /*
cristy9af9b5d2010-08-15 17:04:28 +00002302 Apply filter to resize vertically from image to resize image.
cristy3ed852e2009-09-05 21:47:34 +00002303 */
cristy5d824382010-09-06 14:00:17 +00002304 scale=MagickMax(1.0/y_factor+MagickEpsilon,1.0);
cristy3ed852e2009-09-05 21:47:34 +00002305 support=scale*GetResizeFilterSupport(resize_filter);
2306 storage_class=support > 0.5 ? DirectClass : image->storage_class;
2307 if (SetImageStorageClass(resize_image,storage_class) == MagickFalse)
2308 {
2309 InheritException(exception,&resize_image->exception);
2310 return(MagickFalse);
2311 }
2312 if (support < 0.5)
2313 {
2314 /*
nicolas07bac812010-09-19 18:47:02 +00002315 Support too small even for nearest neighbour: Reduce to point
2316 sampling.
cristy3ed852e2009-09-05 21:47:34 +00002317 */
2318 support=(MagickRealType) 0.5;
2319 scale=1.0;
2320 }
2321 contributions=AcquireContributionThreadSet((size_t) (2.0*support+3.0));
2322 if (contributions == (ContributionInfo **) NULL)
2323 {
2324 (void) ThrowMagickException(exception,GetMagickModule(),
2325 ResourceLimitError,"MemoryAllocationFailed","`%s'",image->filename);
2326 return(MagickFalse);
2327 }
2328 status=MagickTrue;
2329 scale=1.0/scale;
2330 (void) ResetMagickMemory(&zero,0,sizeof(zero));
2331 image_view=AcquireCacheView(image);
2332 resize_view=AcquireCacheView(resize_image);
cristyb5d5f722009-11-04 03:03:49 +00002333#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristy3ed852e2009-09-05 21:47:34 +00002334 #pragma omp parallel for shared(status)
2335#endif
cristybb503372010-05-27 20:51:26 +00002336 for (y=0; y < (ssize_t) resize_image->rows; y++)
cristy3ed852e2009-09-05 21:47:34 +00002337 {
cristy3ed852e2009-09-05 21:47:34 +00002338 MagickRealType
2339 center,
2340 density;
2341
2342 register const IndexPacket
cristyc47d1f82009-11-26 01:44:43 +00002343 *restrict indexes;
cristy3ed852e2009-09-05 21:47:34 +00002344
2345 register const PixelPacket
cristyc47d1f82009-11-26 01:44:43 +00002346 *restrict p;
cristy3ed852e2009-09-05 21:47:34 +00002347
cristy03dbbd22010-09-19 23:04:47 +00002348 register ContributionInfo
2349 *restrict contribution;
2350
cristy3ed852e2009-09-05 21:47:34 +00002351 register IndexPacket
cristyc47d1f82009-11-26 01:44:43 +00002352 *restrict resize_indexes;
cristy3ed852e2009-09-05 21:47:34 +00002353
cristy9af9b5d2010-08-15 17:04:28 +00002354 register PixelPacket
2355 *restrict q;
2356
cristybb503372010-05-27 20:51:26 +00002357 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +00002358 x;
2359
cristy9af9b5d2010-08-15 17:04:28 +00002360 ssize_t
2361 n,
2362 start,
2363 stop;
cristy3ed852e2009-09-05 21:47:34 +00002364
2365 if (status == MagickFalse)
2366 continue;
cristy679e6962010-03-18 00:42:45 +00002367 center=(MagickRealType) (y+0.5)/y_factor;
cristybb503372010-05-27 20:51:26 +00002368 start=(ssize_t) MagickMax(center-support+0.5,0.0);
2369 stop=(ssize_t) MagickMin(center+support+0.5,(double) image->rows);
cristy3ed852e2009-09-05 21:47:34 +00002370 density=0.0;
2371 contribution=contributions[GetOpenMPThreadId()];
2372 for (n=0; n < (stop-start); n++)
2373 {
2374 contribution[n].pixel=start+n;
2375 contribution[n].weight=GetResizeFilterWeight(resize_filter,scale*
2376 ((MagickRealType) (start+n)-center+0.5));
2377 density+=contribution[n].weight;
2378 }
2379 if ((density != 0.0) && (density != 1.0))
2380 {
cristybb503372010-05-27 20:51:26 +00002381 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +00002382 i;
2383
2384 /*
2385 Normalize.
2386 */
2387 density=1.0/density;
2388 for (i=0; i < n; i++)
2389 contribution[i].weight*=density;
2390 }
2391 p=GetCacheViewVirtualPixels(image_view,0,contribution[0].pixel,
cristy9af9b5d2010-08-15 17:04:28 +00002392 image->columns,(size_t) (contribution[n-1].pixel-contribution[0].pixel+1),
2393 exception);
cristy3ed852e2009-09-05 21:47:34 +00002394 q=QueueCacheViewAuthenticPixels(resize_view,0,y,resize_image->columns,1,
2395 exception);
2396 if ((p == (const PixelPacket *) NULL) || (q == (PixelPacket *) NULL))
2397 {
2398 status=MagickFalse;
2399 continue;
2400 }
2401 indexes=GetCacheViewVirtualIndexQueue(image_view);
2402 resize_indexes=GetCacheViewAuthenticIndexQueue(resize_view);
cristybb503372010-05-27 20:51:26 +00002403 for (x=0; x < (ssize_t) resize_image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +00002404 {
cristy3ed852e2009-09-05 21:47:34 +00002405 MagickPixelPacket
2406 pixel;
2407
2408 MagickRealType
2409 alpha;
2410
cristybb503372010-05-27 20:51:26 +00002411 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +00002412 i;
2413
cristy9af9b5d2010-08-15 17:04:28 +00002414 ssize_t
2415 j;
2416
cristy3ed852e2009-09-05 21:47:34 +00002417 pixel=zero;
2418 if (image->matte == MagickFalse)
2419 {
2420 for (i=0; i < n; i++)
2421 {
cristybb503372010-05-27 20:51:26 +00002422 j=(ssize_t) ((contribution[i].pixel-contribution[0].pixel)*
cristy3ed852e2009-09-05 21:47:34 +00002423 image->columns+x);
2424 alpha=contribution[i].weight;
2425 pixel.red+=alpha*(p+j)->red;
2426 pixel.green+=alpha*(p+j)->green;
2427 pixel.blue+=alpha*(p+j)->blue;
2428 pixel.opacity+=alpha*(p+j)->opacity;
2429 }
cristyce70c172010-01-07 17:15:30 +00002430 SetRedPixelComponent(q,ClampRedPixelComponent(&pixel));
2431 SetGreenPixelComponent(q,ClampGreenPixelComponent(&pixel));
2432 SetBluePixelComponent(q,ClampBluePixelComponent(&pixel));
2433 SetOpacityPixelComponent(q,ClampOpacityPixelComponent(&pixel));
cristy3ed852e2009-09-05 21:47:34 +00002434 if ((image->colorspace == CMYKColorspace) &&
2435 (resize_image->colorspace == CMYKColorspace))
2436 {
2437 for (i=0; i < n; i++)
2438 {
cristybb503372010-05-27 20:51:26 +00002439 j=(ssize_t) ((contribution[i].pixel-contribution[0].pixel)*
cristy3ed852e2009-09-05 21:47:34 +00002440 image->columns+x);
2441 alpha=contribution[i].weight;
2442 pixel.index+=alpha*indexes[j];
2443 }
cristyce70c172010-01-07 17:15:30 +00002444 resize_indexes[x]=(IndexPacket) ClampToQuantum(pixel.index);
cristy3ed852e2009-09-05 21:47:34 +00002445 }
2446 }
2447 else
2448 {
2449 MagickRealType
2450 gamma;
2451
2452 gamma=0.0;
2453 for (i=0; i < n; i++)
2454 {
cristybb503372010-05-27 20:51:26 +00002455 j=(ssize_t) ((contribution[i].pixel-contribution[0].pixel)*
cristy3ed852e2009-09-05 21:47:34 +00002456 image->columns+x);
cristy46f08202010-01-10 04:04:21 +00002457 alpha=contribution[i].weight*QuantumScale*
2458 GetAlphaPixelComponent(p+j);
cristy3ed852e2009-09-05 21:47:34 +00002459 pixel.red+=alpha*(p+j)->red;
2460 pixel.green+=alpha*(p+j)->green;
2461 pixel.blue+=alpha*(p+j)->blue;
2462 pixel.opacity+=contribution[i].weight*(p+j)->opacity;
2463 gamma+=alpha;
2464 }
2465 gamma=1.0/(fabs((double) gamma) <= MagickEpsilon ? 1.0 : gamma);
cristyce70c172010-01-07 17:15:30 +00002466 q->red=ClampToQuantum(gamma*GetRedPixelComponent(&pixel));
2467 q->green=ClampToQuantum(gamma*GetGreenPixelComponent(&pixel));
2468 q->blue=ClampToQuantum(gamma*GetBluePixelComponent(&pixel));
2469 SetOpacityPixelComponent(q,ClampOpacityPixelComponent(&pixel));
cristy3ed852e2009-09-05 21:47:34 +00002470 if ((image->colorspace == CMYKColorspace) &&
2471 (resize_image->colorspace == CMYKColorspace))
2472 {
2473 for (i=0; i < n; i++)
2474 {
cristybb503372010-05-27 20:51:26 +00002475 j=(ssize_t) ((contribution[i].pixel-contribution[0].pixel)*
cristy3ed852e2009-09-05 21:47:34 +00002476 image->columns+x);
cristy46f08202010-01-10 04:04:21 +00002477 alpha=contribution[i].weight*QuantumScale*
2478 GetAlphaPixelComponent(p+j);
cristy3ed852e2009-09-05 21:47:34 +00002479 pixel.index+=alpha*indexes[j];
2480 }
cristyce70c172010-01-07 17:15:30 +00002481 resize_indexes[x]=(IndexPacket) ClampToQuantum(gamma*
2482 GetIndexPixelComponent(&pixel));
cristy3ed852e2009-09-05 21:47:34 +00002483 }
2484 }
2485 if ((resize_image->storage_class == PseudoClass) &&
2486 (image->storage_class == PseudoClass))
2487 {
cristybb503372010-05-27 20:51:26 +00002488 i=(ssize_t) (MagickMin(MagickMax(center,(double) start),(double) stop-
cristy3ed852e2009-09-05 21:47:34 +00002489 1.0)+0.5);
cristybb503372010-05-27 20:51:26 +00002490 j=(ssize_t) ((contribution[i-start].pixel-contribution[0].pixel)*
cristy3ed852e2009-09-05 21:47:34 +00002491 image->columns+x);
2492 resize_indexes[x]=indexes[j];
2493 }
2494 q++;
2495 }
2496 if (SyncCacheViewAuthenticPixels(resize_view,exception) == MagickFalse)
2497 status=MagickFalse;
2498 if (image->progress_monitor != (MagickProgressMonitor) NULL)
2499 {
2500 MagickBooleanType
2501 proceed;
2502
cristyb5d5f722009-11-04 03:03:49 +00002503#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristy3ed852e2009-09-05 21:47:34 +00002504 #pragma omp critical (MagickCore_VerticalFilter)
2505#endif
cristy9af9b5d2010-08-15 17:04:28 +00002506 proceed=SetImageProgress(image,ResizeImageTag,(*offset)++,span);
cristy3ed852e2009-09-05 21:47:34 +00002507 if (proceed == MagickFalse)
2508 status=MagickFalse;
2509 }
2510 }
2511 resize_view=DestroyCacheView(resize_view);
2512 image_view=DestroyCacheView(image_view);
2513 contributions=DestroyContributionThreadSet(contributions);
2514 return(status);
2515}
2516
cristybb503372010-05-27 20:51:26 +00002517MagickExport Image *ResizeImage(const Image *image,const size_t columns,
2518 const size_t rows,const FilterTypes filter,const double blur,
cristy3ed852e2009-09-05 21:47:34 +00002519 ExceptionInfo *exception)
2520{
2521#define WorkLoadFactor 0.265
2522
2523 FilterTypes
2524 filter_type;
2525
2526 Image
2527 *filter_image,
2528 *resize_image;
2529
cristy9af9b5d2010-08-15 17:04:28 +00002530 MagickOffsetType
2531 offset;
2532
cristy3ed852e2009-09-05 21:47:34 +00002533 MagickRealType
2534 x_factor,
2535 y_factor;
2536
2537 MagickSizeType
2538 span;
2539
2540 MagickStatusType
2541 status;
2542
2543 ResizeFilter
2544 *resize_filter;
2545
cristy3ed852e2009-09-05 21:47:34 +00002546 /*
2547 Acquire resize image.
2548 */
2549 assert(image != (Image *) NULL);
2550 assert(image->signature == MagickSignature);
2551 if (image->debug != MagickFalse)
2552 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
2553 assert(exception != (ExceptionInfo *) NULL);
2554 assert(exception->signature == MagickSignature);
2555 if ((columns == 0) || (rows == 0))
2556 ThrowImageException(ImageError,"NegativeOrZeroImageSize");
2557 if ((columns == image->columns) && (rows == image->rows) &&
2558 (filter == UndefinedFilter) && (blur == 1.0))
2559 return(CloneImage(image,0,0,MagickTrue,exception));
2560 resize_image=CloneImage(image,columns,rows,MagickTrue,exception);
2561 if (resize_image == (Image *) NULL)
2562 return(resize_image);
2563 /*
2564 Acquire resize filter.
2565 */
2566 x_factor=(MagickRealType) columns/(MagickRealType) image->columns;
2567 y_factor=(MagickRealType) rows/(MagickRealType) image->rows;
2568 if ((x_factor*y_factor) > WorkLoadFactor)
2569 filter_image=CloneImage(image,columns,image->rows,MagickTrue,exception);
2570 else
2571 filter_image=CloneImage(image,image->columns,rows,MagickTrue,exception);
2572 if (filter_image == (Image *) NULL)
2573 return(DestroyImage(resize_image));
2574 filter_type=LanczosFilter;
2575 if (filter != UndefinedFilter)
2576 filter_type=filter;
2577 else
2578 if ((x_factor == 1.0) && (y_factor == 1.0))
2579 filter_type=PointFilter;
2580 else
2581 if ((image->storage_class == PseudoClass) ||
2582 (image->matte != MagickFalse) || ((x_factor*y_factor) > 1.0))
2583 filter_type=MitchellFilter;
2584 resize_filter=AcquireResizeFilter(image,filter_type,blur,MagickFalse,
2585 exception);
2586 /*
2587 Resize image.
2588 */
cristy9af9b5d2010-08-15 17:04:28 +00002589 offset=0;
cristy3ed852e2009-09-05 21:47:34 +00002590 if ((x_factor*y_factor) > WorkLoadFactor)
2591 {
2592 span=(MagickSizeType) (filter_image->columns+rows);
2593 status=HorizontalFilter(resize_filter,image,filter_image,x_factor,span,
cristy9af9b5d2010-08-15 17:04:28 +00002594 &offset,exception);
cristy3ed852e2009-09-05 21:47:34 +00002595 status&=VerticalFilter(resize_filter,filter_image,resize_image,y_factor,
cristy9af9b5d2010-08-15 17:04:28 +00002596 span,&offset,exception);
cristy3ed852e2009-09-05 21:47:34 +00002597 }
2598 else
2599 {
2600 span=(MagickSizeType) (filter_image->rows+columns);
2601 status=VerticalFilter(resize_filter,image,filter_image,y_factor,span,
cristy9af9b5d2010-08-15 17:04:28 +00002602 &offset,exception);
cristy3ed852e2009-09-05 21:47:34 +00002603 status&=HorizontalFilter(resize_filter,filter_image,resize_image,x_factor,
cristy9af9b5d2010-08-15 17:04:28 +00002604 span,&offset,exception);
cristy3ed852e2009-09-05 21:47:34 +00002605 }
2606 /*
2607 Free resources.
2608 */
2609 filter_image=DestroyImage(filter_image);
2610 resize_filter=DestroyResizeFilter(resize_filter);
2611 if ((status == MagickFalse) || (resize_image == (Image *) NULL))
2612 return((Image *) NULL);
2613 resize_image->type=image->type;
2614 return(resize_image);
2615}
2616
2617/*
2618%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2619% %
2620% %
2621% %
2622% S a m p l e I m a g e %
2623% %
2624% %
2625% %
2626%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2627%
2628% SampleImage() scales an image to the desired dimensions with pixel
2629% sampling. Unlike other scaling methods, this method does not introduce
2630% any additional color into the scaled image.
2631%
2632% The format of the SampleImage method is:
2633%
cristybb503372010-05-27 20:51:26 +00002634% Image *SampleImage(const Image *image,const size_t columns,
2635% const size_t rows,ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +00002636%
2637% A description of each parameter follows:
2638%
2639% o image: the image.
2640%
2641% o columns: the number of columns in the sampled image.
2642%
2643% o rows: the number of rows in the sampled image.
2644%
2645% o exception: return any errors or warnings in this structure.
2646%
2647*/
cristybb503372010-05-27 20:51:26 +00002648MagickExport Image *SampleImage(const Image *image,const size_t columns,
2649 const size_t rows,ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +00002650{
2651#define SampleImageTag "Sample/Image"
2652
cristyc4c8d132010-01-07 01:58:38 +00002653 CacheView
2654 *image_view,
2655 *sample_view;
2656
cristy3ed852e2009-09-05 21:47:34 +00002657 Image
2658 *sample_image;
2659
cristy3ed852e2009-09-05 21:47:34 +00002660 MagickBooleanType
2661 status;
2662
cristy5f959472010-05-27 22:19:46 +00002663 MagickOffsetType
2664 progress;
2665
cristybb503372010-05-27 20:51:26 +00002666 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +00002667 x;
2668
cristy5f959472010-05-27 22:19:46 +00002669 ssize_t
2670 *x_offset,
2671 y;
2672
cristy3ed852e2009-09-05 21:47:34 +00002673 /*
2674 Initialize sampled image attributes.
2675 */
2676 assert(image != (const Image *) NULL);
2677 assert(image->signature == MagickSignature);
2678 if (image->debug != MagickFalse)
2679 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
2680 assert(exception != (ExceptionInfo *) NULL);
2681 assert(exception->signature == MagickSignature);
2682 if ((columns == 0) || (rows == 0))
2683 ThrowImageException(ImageError,"NegativeOrZeroImageSize");
2684 if ((columns == image->columns) && (rows == image->rows))
2685 return(CloneImage(image,0,0,MagickTrue,exception));
2686 sample_image=CloneImage(image,columns,rows,MagickTrue,exception);
2687 if (sample_image == (Image *) NULL)
2688 return((Image *) NULL);
2689 /*
2690 Allocate scan line buffer and column offset buffers.
2691 */
cristybb503372010-05-27 20:51:26 +00002692 x_offset=(ssize_t *) AcquireQuantumMemory((size_t) sample_image->columns,
cristy3ed852e2009-09-05 21:47:34 +00002693 sizeof(*x_offset));
cristybb503372010-05-27 20:51:26 +00002694 if (x_offset == (ssize_t *) NULL)
cristy3ed852e2009-09-05 21:47:34 +00002695 {
2696 sample_image=DestroyImage(sample_image);
2697 ThrowImageException(ResourceLimitError,"MemoryAllocationFailed");
2698 }
cristybb503372010-05-27 20:51:26 +00002699 for (x=0; x < (ssize_t) sample_image->columns; x++)
2700 x_offset[x]=(ssize_t) (((MagickRealType) x+0.5)*image->columns/
cristy3ed852e2009-09-05 21:47:34 +00002701 sample_image->columns);
2702 /*
2703 Sample each row.
2704 */
2705 status=MagickTrue;
2706 progress=0;
2707 image_view=AcquireCacheView(image);
2708 sample_view=AcquireCacheView(sample_image);
cristyb5d5f722009-11-04 03:03:49 +00002709#if defined(MAGICKCORE_OPENMP_SUPPORT)
2710 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +00002711#endif
cristybb503372010-05-27 20:51:26 +00002712 for (y=0; y < (ssize_t) sample_image->rows; y++)
cristy3ed852e2009-09-05 21:47:34 +00002713 {
cristy3ed852e2009-09-05 21:47:34 +00002714 register const IndexPacket
cristyc47d1f82009-11-26 01:44:43 +00002715 *restrict indexes;
cristy3ed852e2009-09-05 21:47:34 +00002716
2717 register const PixelPacket
cristyc47d1f82009-11-26 01:44:43 +00002718 *restrict p;
cristy3ed852e2009-09-05 21:47:34 +00002719
2720 register IndexPacket
cristyc47d1f82009-11-26 01:44:43 +00002721 *restrict sample_indexes;
cristy3ed852e2009-09-05 21:47:34 +00002722
cristy3ed852e2009-09-05 21:47:34 +00002723 register PixelPacket
cristyc47d1f82009-11-26 01:44:43 +00002724 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +00002725
cristy03dbbd22010-09-19 23:04:47 +00002726 register ssize_t
2727 x;
2728
cristy9af9b5d2010-08-15 17:04:28 +00002729 ssize_t
2730 y_offset;
2731
cristy3ed852e2009-09-05 21:47:34 +00002732 if (status == MagickFalse)
2733 continue;
cristy9af9b5d2010-08-15 17:04:28 +00002734 y_offset=(ssize_t) (((MagickRealType) y+0.5)*image->rows/
2735 sample_image->rows);
cristy3ed852e2009-09-05 21:47:34 +00002736 p=GetCacheViewVirtualPixels(image_view,0,y_offset,image->columns,1,
2737 exception);
2738 q=QueueCacheViewAuthenticPixels(sample_view,0,y,sample_image->columns,1,
2739 exception);
2740 if ((p == (const PixelPacket *) NULL) || (q == (PixelPacket *) NULL))
2741 {
2742 status=MagickFalse;
2743 continue;
2744 }
2745 indexes=GetCacheViewAuthenticIndexQueue(image_view);
2746 sample_indexes=GetCacheViewAuthenticIndexQueue(sample_view);
2747 /*
2748 Sample each column.
2749 */
cristybb503372010-05-27 20:51:26 +00002750 for (x=0; x < (ssize_t) sample_image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +00002751 *q++=p[x_offset[x]];
2752 if ((image->storage_class == PseudoClass) ||
2753 (image->colorspace == CMYKColorspace))
cristybb503372010-05-27 20:51:26 +00002754 for (x=0; x < (ssize_t) sample_image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +00002755 sample_indexes[x]=indexes[x_offset[x]];
2756 if (SyncCacheViewAuthenticPixels(sample_view,exception) == MagickFalse)
2757 status=MagickFalse;
2758 if (image->progress_monitor != (MagickProgressMonitor) NULL)
2759 {
2760 MagickBooleanType
2761 proceed;
2762
cristyb5d5f722009-11-04 03:03:49 +00002763#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristy3ed852e2009-09-05 21:47:34 +00002764 #pragma omp critical (MagickCore_SampleImage)
2765#endif
2766 proceed=SetImageProgress(image,SampleImageTag,progress++,image->rows);
2767 if (proceed == MagickFalse)
2768 status=MagickFalse;
2769 }
2770 }
2771 image_view=DestroyCacheView(image_view);
2772 sample_view=DestroyCacheView(sample_view);
cristybb503372010-05-27 20:51:26 +00002773 x_offset=(ssize_t *) RelinquishMagickMemory(x_offset);
cristy3ed852e2009-09-05 21:47:34 +00002774 sample_image->type=image->type;
2775 return(sample_image);
2776}
2777
2778/*
2779%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2780% %
2781% %
2782% %
2783% S c a l e I m a g e %
2784% %
2785% %
2786% %
2787%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2788%
2789% ScaleImage() changes the size of an image to the given dimensions.
2790%
2791% The format of the ScaleImage method is:
2792%
cristybb503372010-05-27 20:51:26 +00002793% Image *ScaleImage(const Image *image,const size_t columns,
2794% const size_t rows,ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +00002795%
2796% A description of each parameter follows:
2797%
2798% o image: the image.
2799%
2800% o columns: the number of columns in the scaled image.
2801%
2802% o rows: the number of rows in the scaled image.
2803%
2804% o exception: return any errors or warnings in this structure.
2805%
2806*/
cristybb503372010-05-27 20:51:26 +00002807MagickExport Image *ScaleImage(const Image *image,const size_t columns,
2808 const size_t rows,ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +00002809{
2810#define ScaleImageTag "Scale/Image"
2811
cristyed6cb232010-01-20 03:07:53 +00002812 CacheView
2813 *image_view,
2814 *scale_view;
2815
cristy3ed852e2009-09-05 21:47:34 +00002816 Image
2817 *scale_image;
2818
cristy3ed852e2009-09-05 21:47:34 +00002819 MagickBooleanType
2820 next_column,
2821 next_row,
2822 proceed;
2823
2824 MagickPixelPacket
2825 pixel,
2826 *scale_scanline,
2827 *scanline,
2828 *x_vector,
2829 *y_vector,
2830 zero;
2831
cristy3ed852e2009-09-05 21:47:34 +00002832 PointInfo
2833 scale,
2834 span;
2835
cristybb503372010-05-27 20:51:26 +00002836 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +00002837 i;
2838
cristy9af9b5d2010-08-15 17:04:28 +00002839 ssize_t
2840 number_rows,
2841 y;
2842
cristy3ed852e2009-09-05 21:47:34 +00002843 /*
2844 Initialize scaled image attributes.
2845 */
2846 assert(image != (const Image *) NULL);
2847 assert(image->signature == MagickSignature);
2848 if (image->debug != MagickFalse)
2849 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
2850 assert(exception != (ExceptionInfo *) NULL);
2851 assert(exception->signature == MagickSignature);
2852 if ((columns == 0) || (rows == 0))
2853 return((Image *) NULL);
2854 if ((columns == image->columns) && (rows == image->rows))
2855 return(CloneImage(image,0,0,MagickTrue,exception));
2856 scale_image=CloneImage(image,columns,rows,MagickTrue,exception);
2857 if (scale_image == (Image *) NULL)
2858 return((Image *) NULL);
2859 if (SetImageStorageClass(scale_image,DirectClass) == MagickFalse)
2860 {
2861 InheritException(exception,&scale_image->exception);
2862 scale_image=DestroyImage(scale_image);
2863 return((Image *) NULL);
2864 }
2865 /*
2866 Allocate memory.
2867 */
2868 x_vector=(MagickPixelPacket *) AcquireQuantumMemory((size_t) image->columns,
2869 sizeof(*x_vector));
2870 scanline=x_vector;
2871 if (image->rows != scale_image->rows)
2872 scanline=(MagickPixelPacket *) AcquireQuantumMemory((size_t) image->columns,
2873 sizeof(*scanline));
2874 scale_scanline=(MagickPixelPacket *) AcquireQuantumMemory((size_t)
2875 scale_image->columns,sizeof(*scale_scanline));
2876 y_vector=(MagickPixelPacket *) AcquireQuantumMemory((size_t) image->columns,
2877 sizeof(*y_vector));
2878 if ((scanline == (MagickPixelPacket *) NULL) ||
2879 (scale_scanline == (MagickPixelPacket *) NULL) ||
2880 (x_vector == (MagickPixelPacket *) NULL) ||
2881 (y_vector == (MagickPixelPacket *) NULL))
2882 {
2883 scale_image=DestroyImage(scale_image);
2884 ThrowImageException(ResourceLimitError,"MemoryAllocationFailed");
2885 }
2886 /*
2887 Scale image.
2888 */
2889 number_rows=0;
2890 next_row=MagickTrue;
2891 span.y=1.0;
2892 scale.y=(double) scale_image->rows/(double) image->rows;
2893 (void) ResetMagickMemory(y_vector,0,(size_t) image->columns*
2894 sizeof(*y_vector));
2895 GetMagickPixelPacket(image,&pixel);
2896 (void) ResetMagickMemory(&zero,0,sizeof(zero));
2897 i=0;
cristyed6cb232010-01-20 03:07:53 +00002898 image_view=AcquireCacheView(image);
2899 scale_view=AcquireCacheView(scale_image);
cristybb503372010-05-27 20:51:26 +00002900 for (y=0; y < (ssize_t) scale_image->rows; y++)
cristy3ed852e2009-09-05 21:47:34 +00002901 {
2902 register const IndexPacket
cristyc47d1f82009-11-26 01:44:43 +00002903 *restrict indexes;
cristy3ed852e2009-09-05 21:47:34 +00002904
2905 register const PixelPacket
cristyc47d1f82009-11-26 01:44:43 +00002906 *restrict p;
cristy3ed852e2009-09-05 21:47:34 +00002907
2908 register IndexPacket
cristyc47d1f82009-11-26 01:44:43 +00002909 *restrict scale_indexes;
cristy3ed852e2009-09-05 21:47:34 +00002910
cristy3ed852e2009-09-05 21:47:34 +00002911 register MagickPixelPacket
cristyc47d1f82009-11-26 01:44:43 +00002912 *restrict s,
2913 *restrict t;
cristy3ed852e2009-09-05 21:47:34 +00002914
2915 register PixelPacket
cristyc47d1f82009-11-26 01:44:43 +00002916 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +00002917
cristy9af9b5d2010-08-15 17:04:28 +00002918 register ssize_t
2919 x;
2920
cristyed6cb232010-01-20 03:07:53 +00002921 q=QueueCacheViewAuthenticPixels(scale_view,0,y,scale_image->columns,1,
2922 exception);
cristy3ed852e2009-09-05 21:47:34 +00002923 if (q == (PixelPacket *) NULL)
2924 break;
cristyba9a1e22010-11-10 23:14:28 +00002925 scale_indexes=GetCacheViewAuthenticIndexQueue(scale_view);
cristy3ed852e2009-09-05 21:47:34 +00002926 if (scale_image->rows == image->rows)
2927 {
2928 /*
2929 Read a new scanline.
2930 */
cristyed6cb232010-01-20 03:07:53 +00002931 p=GetCacheViewVirtualPixels(image_view,0,i++,image->columns,1,
2932 exception);
cristy3ed852e2009-09-05 21:47:34 +00002933 if (p == (const PixelPacket *) NULL)
2934 break;
cristyed6cb232010-01-20 03:07:53 +00002935 indexes=GetCacheViewVirtualIndexQueue(image_view);
cristybb503372010-05-27 20:51:26 +00002936 for (x=0; x < (ssize_t) image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +00002937 {
cristyce70c172010-01-07 17:15:30 +00002938 x_vector[x].red=(MagickRealType) GetRedPixelComponent(p);
2939 x_vector[x].green=(MagickRealType) GetGreenPixelComponent(p);
2940 x_vector[x].blue=(MagickRealType) GetBluePixelComponent(p);
cristy3ed852e2009-09-05 21:47:34 +00002941 if (image->matte != MagickFalse)
cristyce70c172010-01-07 17:15:30 +00002942 x_vector[x].opacity=(MagickRealType) GetOpacityPixelComponent(p);
cristy3ed852e2009-09-05 21:47:34 +00002943 if (indexes != (IndexPacket *) NULL)
2944 x_vector[x].index=(MagickRealType) indexes[x];
2945 p++;
2946 }
2947 }
2948 else
2949 {
2950 /*
2951 Scale Y direction.
2952 */
2953 while (scale.y < span.y)
2954 {
cristy9af9b5d2010-08-15 17:04:28 +00002955 if ((next_row != MagickFalse) &&
2956 (number_rows < (ssize_t) image->rows))
cristy3ed852e2009-09-05 21:47:34 +00002957 {
2958 /*
2959 Read a new scanline.
2960 */
cristyed6cb232010-01-20 03:07:53 +00002961 p=GetCacheViewVirtualPixels(image_view,0,i++,image->columns,1,
2962 exception);
cristy3ed852e2009-09-05 21:47:34 +00002963 if (p == (const PixelPacket *) NULL)
2964 break;
cristyed6cb232010-01-20 03:07:53 +00002965 indexes=GetCacheViewVirtualIndexQueue(image_view);
cristybb503372010-05-27 20:51:26 +00002966 for (x=0; x < (ssize_t) image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +00002967 {
cristyce70c172010-01-07 17:15:30 +00002968 x_vector[x].red=(MagickRealType) GetRedPixelComponent(p);
2969 x_vector[x].green=(MagickRealType) GetGreenPixelComponent(p);
2970 x_vector[x].blue=(MagickRealType) GetBluePixelComponent(p);
cristy3ed852e2009-09-05 21:47:34 +00002971 if (image->matte != MagickFalse)
cristyed6cb232010-01-20 03:07:53 +00002972 x_vector[x].opacity=(MagickRealType)
cristy9af9b5d2010-08-15 17:04:28 +00002973 GetOpacityPixelComponent(p);
cristy3ed852e2009-09-05 21:47:34 +00002974 if (indexes != (IndexPacket *) NULL)
2975 x_vector[x].index=(MagickRealType) indexes[x];
2976 p++;
2977 }
2978 number_rows++;
2979 }
cristybb503372010-05-27 20:51:26 +00002980 for (x=0; x < (ssize_t) image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +00002981 {
2982 y_vector[x].red+=scale.y*x_vector[x].red;
2983 y_vector[x].green+=scale.y*x_vector[x].green;
2984 y_vector[x].blue+=scale.y*x_vector[x].blue;
2985 if (scale_image->matte != MagickFalse)
2986 y_vector[x].opacity+=scale.y*x_vector[x].opacity;
2987 if (scale_indexes != (IndexPacket *) NULL)
2988 y_vector[x].index+=scale.y*x_vector[x].index;
2989 }
2990 span.y-=scale.y;
2991 scale.y=(double) scale_image->rows/(double) image->rows;
2992 next_row=MagickTrue;
2993 }
cristybb503372010-05-27 20:51:26 +00002994 if ((next_row != MagickFalse) && (number_rows < (ssize_t) image->rows))
cristy3ed852e2009-09-05 21:47:34 +00002995 {
2996 /*
2997 Read a new scanline.
2998 */
cristyed6cb232010-01-20 03:07:53 +00002999 p=GetCacheViewVirtualPixels(image_view,0,i++,image->columns,1,
3000 exception);
cristy3ed852e2009-09-05 21:47:34 +00003001 if (p == (const PixelPacket *) NULL)
3002 break;
cristyed6cb232010-01-20 03:07:53 +00003003 indexes=GetCacheViewVirtualIndexQueue(image_view);
cristybb503372010-05-27 20:51:26 +00003004 for (x=0; x < (ssize_t) image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +00003005 {
cristyce70c172010-01-07 17:15:30 +00003006 x_vector[x].red=(MagickRealType) GetRedPixelComponent(p);
3007 x_vector[x].green=(MagickRealType) GetGreenPixelComponent(p);
3008 x_vector[x].blue=(MagickRealType) GetBluePixelComponent(p);
cristy3ed852e2009-09-05 21:47:34 +00003009 if (image->matte != MagickFalse)
cristy2b726bd2010-01-11 01:05:39 +00003010 x_vector[x].opacity=(MagickRealType)
3011 GetOpacityPixelComponent(p);
cristy3ed852e2009-09-05 21:47:34 +00003012 if (indexes != (IndexPacket *) NULL)
3013 x_vector[x].index=(MagickRealType) indexes[x];
3014 p++;
3015 }
3016 number_rows++;
3017 next_row=MagickFalse;
3018 }
3019 s=scanline;
cristybb503372010-05-27 20:51:26 +00003020 for (x=0; x < (ssize_t) image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +00003021 {
3022 pixel.red=y_vector[x].red+span.y*x_vector[x].red;
3023 pixel.green=y_vector[x].green+span.y*x_vector[x].green;
3024 pixel.blue=y_vector[x].blue+span.y*x_vector[x].blue;
3025 if (image->matte != MagickFalse)
3026 pixel.opacity=y_vector[x].opacity+span.y*x_vector[x].opacity;
3027 if (scale_indexes != (IndexPacket *) NULL)
3028 pixel.index=y_vector[x].index+span.y*x_vector[x].index;
3029 s->red=pixel.red;
3030 s->green=pixel.green;
3031 s->blue=pixel.blue;
3032 if (scale_image->matte != MagickFalse)
3033 s->opacity=pixel.opacity;
3034 if (scale_indexes != (IndexPacket *) NULL)
3035 s->index=pixel.index;
3036 s++;
3037 y_vector[x]=zero;
3038 }
3039 scale.y-=span.y;
3040 if (scale.y <= 0)
3041 {
3042 scale.y=(double) scale_image->rows/(double) image->rows;
3043 next_row=MagickTrue;
3044 }
3045 span.y=1.0;
3046 }
3047 if (scale_image->columns == image->columns)
3048 {
3049 /*
3050 Transfer scanline to scaled image.
3051 */
3052 s=scanline;
cristybb503372010-05-27 20:51:26 +00003053 for (x=0; x < (ssize_t) scale_image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +00003054 {
cristyce70c172010-01-07 17:15:30 +00003055 q->red=ClampToQuantum(s->red);
3056 q->green=ClampToQuantum(s->green);
3057 q->blue=ClampToQuantum(s->blue);
cristy3ed852e2009-09-05 21:47:34 +00003058 if (scale_image->matte != MagickFalse)
cristyce70c172010-01-07 17:15:30 +00003059 q->opacity=ClampToQuantum(s->opacity);
cristy3ed852e2009-09-05 21:47:34 +00003060 if (scale_indexes != (IndexPacket *) NULL)
cristyce70c172010-01-07 17:15:30 +00003061 scale_indexes[x]=(IndexPacket) ClampToQuantum(s->index);
cristy3ed852e2009-09-05 21:47:34 +00003062 q++;
3063 s++;
3064 }
3065 }
3066 else
3067 {
3068 /*
3069 Scale X direction.
3070 */
3071 pixel=zero;
3072 next_column=MagickFalse;
3073 span.x=1.0;
3074 s=scanline;
3075 t=scale_scanline;
cristybb503372010-05-27 20:51:26 +00003076 for (x=0; x < (ssize_t) image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +00003077 {
3078 scale.x=(double) scale_image->columns/(double) image->columns;
3079 while (scale.x >= span.x)
3080 {
3081 if (next_column != MagickFalse)
3082 {
3083 pixel=zero;
3084 t++;
3085 }
3086 pixel.red+=span.x*s->red;
3087 pixel.green+=span.x*s->green;
3088 pixel.blue+=span.x*s->blue;
3089 if (image->matte != MagickFalse)
3090 pixel.opacity+=span.x*s->opacity;
3091 if (scale_indexes != (IndexPacket *) NULL)
3092 pixel.index+=span.x*s->index;
3093 t->red=pixel.red;
3094 t->green=pixel.green;
3095 t->blue=pixel.blue;
3096 if (scale_image->matte != MagickFalse)
3097 t->opacity=pixel.opacity;
3098 if (scale_indexes != (IndexPacket *) NULL)
3099 t->index=pixel.index;
3100 scale.x-=span.x;
3101 span.x=1.0;
3102 next_column=MagickTrue;
3103 }
3104 if (scale.x > 0)
3105 {
3106 if (next_column != MagickFalse)
3107 {
3108 pixel=zero;
3109 next_column=MagickFalse;
3110 t++;
3111 }
3112 pixel.red+=scale.x*s->red;
3113 pixel.green+=scale.x*s->green;
3114 pixel.blue+=scale.x*s->blue;
3115 if (scale_image->matte != MagickFalse)
3116 pixel.opacity+=scale.x*s->opacity;
3117 if (scale_indexes != (IndexPacket *) NULL)
3118 pixel.index+=scale.x*s->index;
3119 span.x-=scale.x;
3120 }
3121 s++;
3122 }
3123 if (span.x > 0)
3124 {
3125 s--;
3126 pixel.red+=span.x*s->red;
3127 pixel.green+=span.x*s->green;
3128 pixel.blue+=span.x*s->blue;
3129 if (scale_image->matte != MagickFalse)
3130 pixel.opacity+=span.x*s->opacity;
3131 if (scale_indexes != (IndexPacket *) NULL)
3132 pixel.index+=span.x*s->index;
3133 }
3134 if ((next_column == MagickFalse) &&
cristybb503372010-05-27 20:51:26 +00003135 ((ssize_t) (t-scale_scanline) < (ssize_t) scale_image->columns))
cristy3ed852e2009-09-05 21:47:34 +00003136 {
3137 t->red=pixel.red;
3138 t->green=pixel.green;
3139 t->blue=pixel.blue;
3140 if (scale_image->matte != MagickFalse)
3141 t->opacity=pixel.opacity;
3142 if (scale_indexes != (IndexPacket *) NULL)
3143 t->index=pixel.index;
3144 }
3145 /*
3146 Transfer scanline to scaled image.
3147 */
3148 t=scale_scanline;
cristybb503372010-05-27 20:51:26 +00003149 for (x=0; x < (ssize_t) scale_image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +00003150 {
cristyce70c172010-01-07 17:15:30 +00003151 q->red=ClampToQuantum(t->red);
3152 q->green=ClampToQuantum(t->green);
3153 q->blue=ClampToQuantum(t->blue);
cristy3ed852e2009-09-05 21:47:34 +00003154 if (scale_image->matte != MagickFalse)
cristyce70c172010-01-07 17:15:30 +00003155 q->opacity=ClampToQuantum(t->opacity);
cristy3ed852e2009-09-05 21:47:34 +00003156 if (scale_indexes != (IndexPacket *) NULL)
cristyce70c172010-01-07 17:15:30 +00003157 scale_indexes[x]=(IndexPacket) ClampToQuantum(t->index);
cristy3ed852e2009-09-05 21:47:34 +00003158 t++;
3159 q++;
3160 }
3161 }
cristyed6cb232010-01-20 03:07:53 +00003162 if (SyncCacheViewAuthenticPixels(scale_view,exception) == MagickFalse)
cristy3ed852e2009-09-05 21:47:34 +00003163 break;
cristy96b16132010-08-29 17:19:52 +00003164 proceed=SetImageProgress(image,ScaleImageTag,(MagickOffsetType) y,
3165 image->rows);
cristy3ed852e2009-09-05 21:47:34 +00003166 if (proceed == MagickFalse)
3167 break;
3168 }
cristyed6cb232010-01-20 03:07:53 +00003169 scale_view=DestroyCacheView(scale_view);
3170 image_view=DestroyCacheView(image_view);
cristy3ed852e2009-09-05 21:47:34 +00003171 /*
3172 Free allocated memory.
3173 */
3174 y_vector=(MagickPixelPacket *) RelinquishMagickMemory(y_vector);
3175 scale_scanline=(MagickPixelPacket *) RelinquishMagickMemory(scale_scanline);
3176 if (scale_image->rows != image->rows)
3177 scanline=(MagickPixelPacket *) RelinquishMagickMemory(scanline);
3178 x_vector=(MagickPixelPacket *) RelinquishMagickMemory(x_vector);
3179 scale_image->type=image->type;
3180 return(scale_image);
3181}
3182
anthony02b4cb42010-10-10 04:54:35 +00003183#if 0
3184 THIS IS NOT USED -- to be removed
cristy3ed852e2009-09-05 21:47:34 +00003185/*
3186%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3187% %
3188% %
3189% %
3190+ S e t R e s i z e F i l t e r S u p p o r t %
3191% %
3192% %
3193% %
3194%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3195%
3196% SetResizeFilterSupport() specifies which IR filter to use to window
3197%
3198% The format of the SetResizeFilterSupport method is:
3199%
3200% void SetResizeFilterSupport(ResizeFilter *resize_filter,
3201% const MagickRealType support)
3202%
3203% A description of each parameter follows:
3204%
3205% o resize_filter: the resize filter.
3206%
3207% o support: the filter spport radius.
3208%
3209*/
3210MagickExport void SetResizeFilterSupport(ResizeFilter *resize_filter,
3211 const MagickRealType support)
3212{
3213 assert(resize_filter != (ResizeFilter *) NULL);
3214 assert(resize_filter->signature == MagickSignature);
3215 resize_filter->support=support;
3216}
anthony02b4cb42010-10-10 04:54:35 +00003217#endif
cristy3ed852e2009-09-05 21:47:34 +00003218
3219/*
3220%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3221% %
3222% %
3223% %
3224% T h u m b n a i l I m a g e %
3225% %
3226% %
3227% %
3228%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3229%
3230% ThumbnailImage() changes the size of an image to the given dimensions and
3231% removes any associated profiles. The goal is to produce small low cost
3232% thumbnail images suited for display on the Web.
3233%
3234% The format of the ThumbnailImage method is:
3235%
cristybb503372010-05-27 20:51:26 +00003236% Image *ThumbnailImage(const Image *image,const size_t columns,
3237% const size_t rows,ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +00003238%
3239% A description of each parameter follows:
3240%
3241% o image: the image.
3242%
3243% o columns: the number of columns in the scaled image.
3244%
3245% o rows: the number of rows in the scaled image.
3246%
3247% o exception: return any errors or warnings in this structure.
3248%
3249*/
cristy9af9b5d2010-08-15 17:04:28 +00003250MagickExport Image *ThumbnailImage(const Image *image,const size_t columns,
3251 const size_t rows,ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +00003252{
3253#define SampleFactor 5
3254
3255 char
3256 value[MaxTextExtent];
3257
3258 const char
3259 *name;
3260
3261 Image
3262 *thumbnail_image;
3263
3264 MagickRealType
3265 x_factor,
3266 y_factor;
3267
cristybb503372010-05-27 20:51:26 +00003268 size_t
cristy3ed852e2009-09-05 21:47:34 +00003269 version;
3270
cristy9af9b5d2010-08-15 17:04:28 +00003271 struct stat
3272 attributes;
3273
cristy3ed852e2009-09-05 21:47:34 +00003274 assert(image != (Image *) NULL);
3275 assert(image->signature == MagickSignature);
3276 if (image->debug != MagickFalse)
3277 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
3278 assert(exception != (ExceptionInfo *) NULL);
3279 assert(exception->signature == MagickSignature);
3280 x_factor=(MagickRealType) columns/(MagickRealType) image->columns;
3281 y_factor=(MagickRealType) rows/(MagickRealType) image->rows;
3282 if ((x_factor*y_factor) > 0.1)
cristyd1bb3bc2010-09-07 00:43:58 +00003283 thumbnail_image=ResizeImage(image,columns,rows,image->filter,image->blur,
3284 exception);
cristy3ed852e2009-09-05 21:47:34 +00003285 else
3286 if (((SampleFactor*columns) < 128) || ((SampleFactor*rows) < 128))
cristyd1bb3bc2010-09-07 00:43:58 +00003287 thumbnail_image=ResizeImage(image,columns,rows,image->filter,
3288 image->blur,exception);
cristy3ed852e2009-09-05 21:47:34 +00003289 else
3290 {
3291 Image
3292 *sample_image;
3293
3294 sample_image=SampleImage(image,SampleFactor*columns,SampleFactor*rows,
3295 exception);
3296 if (sample_image == (Image *) NULL)
3297 return((Image *) NULL);
cristyd1bb3bc2010-09-07 00:43:58 +00003298 thumbnail_image=ResizeImage(sample_image,columns,rows,image->filter,
3299 image->blur,exception);
cristy3ed852e2009-09-05 21:47:34 +00003300 sample_image=DestroyImage(sample_image);
3301 }
3302 if (thumbnail_image == (Image *) NULL)
3303 return(thumbnail_image);
3304 (void) ParseAbsoluteGeometry("0x0+0+0",&thumbnail_image->page);
3305 if (thumbnail_image->matte == MagickFalse)
3306 (void) SetImageAlphaChannel(thumbnail_image,OpaqueAlphaChannel);
3307 thumbnail_image->depth=8;
3308 thumbnail_image->interlace=NoInterlace;
3309 /*
3310 Strip all profiles except color profiles.
3311 */
3312 ResetImageProfileIterator(thumbnail_image);
3313 for (name=GetNextImageProfile(thumbnail_image); name != (const char *) NULL; )
3314 {
3315 if ((LocaleCompare(name,"icc") != 0) && (LocaleCompare(name,"icm") != 0))
3316 {
cristy2b726bd2010-01-11 01:05:39 +00003317 (void) DeleteImageProfile(thumbnail_image,name);
cristy3ed852e2009-09-05 21:47:34 +00003318 ResetImageProfileIterator(thumbnail_image);
3319 }
3320 name=GetNextImageProfile(thumbnail_image);
3321 }
3322 (void) DeleteImageProperty(thumbnail_image,"comment");
3323 (void) CopyMagickString(value,image->magick_filename,MaxTextExtent);
cristy7b63d662009-11-13 01:58:56 +00003324 if (strstr(image->magick_filename,"//") == (char *) NULL)
3325 (void) FormatMagickString(value,MaxTextExtent,"file://%s",
cristy3ed852e2009-09-05 21:47:34 +00003326 image->magick_filename);
3327 (void) SetImageProperty(thumbnail_image,"Thumb::URI",value);
3328 (void) CopyMagickString(value,image->magick_filename,MaxTextExtent);
3329 if (GetPathAttributes(image->filename,&attributes) != MagickFalse)
3330 {
cristye8c25f92010-06-03 00:53:06 +00003331 (void) FormatMagickString(value,MaxTextExtent,"%.20g",(double)
cristy3ed852e2009-09-05 21:47:34 +00003332 attributes.st_mtime);
3333 (void) SetImageProperty(thumbnail_image,"Thumb::MTime",value);
3334 }
cristye8c25f92010-06-03 00:53:06 +00003335 (void) FormatMagickString(value,MaxTextExtent,"%.20g",(double)
cristy3ed852e2009-09-05 21:47:34 +00003336 attributes.st_mtime);
cristyb9080c92009-12-01 20:13:26 +00003337 (void) FormatMagickSize(GetBlobSize(image),MagickFalse,value);
cristy2ce15c92010-03-12 14:03:41 +00003338 (void) ConcatenateMagickString(value,"B",MaxTextExtent);
cristy3ed852e2009-09-05 21:47:34 +00003339 (void) SetImageProperty(thumbnail_image,"Thumb::Size",value);
3340 (void) FormatMagickString(value,MaxTextExtent,"image/%s",image->magick);
3341 LocaleLower(value);
3342 (void) SetImageProperty(thumbnail_image,"Thumb::Mimetype",value);
3343 (void) SetImageProperty(thumbnail_image,"software",
3344 GetMagickVersion(&version));
cristye8c25f92010-06-03 00:53:06 +00003345 (void) FormatMagickString(value,MaxTextExtent,"%.20g",(double)
3346 image->magick_columns);
cristy3ed852e2009-09-05 21:47:34 +00003347 (void) SetImageProperty(thumbnail_image,"Thumb::Image::Width",value);
cristye8c25f92010-06-03 00:53:06 +00003348 (void) FormatMagickString(value,MaxTextExtent,"%.20g",(double)
cristyf2faecf2010-05-28 19:19:36 +00003349 image->magick_rows);
cristy3ed852e2009-09-05 21:47:34 +00003350 (void) SetImageProperty(thumbnail_image,"Thumb::Image::height",value);
cristye8c25f92010-06-03 00:53:06 +00003351 (void) FormatMagickString(value,MaxTextExtent,"%.20g",(double)
3352 GetImageListLength(image));
cristy3ed852e2009-09-05 21:47:34 +00003353 (void) SetImageProperty(thumbnail_image,"Thumb::Document::Pages",value);
3354 return(thumbnail_image);
3355}