blob: 85773955f07bf07ef44ff42ba79d28c9c25bdb3c [file] [log] [blame]
cristy3ed852e2009-09-05 21:47:34 +00001/*
2%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3% %
4% %
5% %
6% RRRR EEEEE SSSSS IIIII ZZZZZ EEEEE %
7% R R E SS I ZZ E %
8% RRRR EEE SSS I ZZZ EEE %
9% R R E SS I ZZ E %
10% R R EEEEE SSSSS IIIII ZZZZZ EEEEE %
11% %
12% %
13% MagickCore Image Resize Methods %
14% %
15% Software Design %
16% John Cristy %
17% July 1992 %
18% %
19% %
cristy16af1cb2009-12-11 21:38:29 +000020% Copyright 1999-2010 ImageMagick Studio LLC, a non-profit organization %
cristy3ed852e2009-09-05 21:47:34 +000021% dedicated to making software imaging solutions freely available. %
22% %
23% You may not use this file except in compliance with the License. You may %
24% obtain a copy of the License at %
25% %
26% http://www.imagemagick.org/script/license.php %
27% %
28% Unless required by applicable law or agreed to in writing, software %
29% distributed under the License is distributed on an "AS IS" BASIS, %
30% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. %
31% See the License for the specific language governing permissions and %
32% limitations under the License. %
33% %
34%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
35%
36%
37*/
38
39/*
40 Include declarations.
41*/
42#include "magick/studio.h"
43#include "magick/artifact.h"
44#include "magick/blob.h"
45#include "magick/cache.h"
46#include "magick/cache-view.h"
47#include "magick/color.h"
48#include "magick/color-private.h"
49#include "magick/draw.h"
50#include "magick/exception.h"
51#include "magick/exception-private.h"
52#include "magick/gem.h"
53#include "magick/image.h"
54#include "magick/image-private.h"
55#include "magick/list.h"
56#include "magick/memory_.h"
anthony55f12332010-09-10 01:13:02 +000057#include "magick/magick.h"
cristy3ed852e2009-09-05 21:47:34 +000058#include "magick/pixel-private.h"
59#include "magick/property.h"
60#include "magick/monitor.h"
61#include "magick/monitor-private.h"
62#include "magick/pixel.h"
63#include "magick/option.h"
64#include "magick/resample.h"
65#include "magick/resize.h"
66#include "magick/resize-private.h"
67#include "magick/string_.h"
cristyf2f27272009-12-17 14:48:46 +000068#include "magick/string-private.h"
cristy3ed852e2009-09-05 21:47:34 +000069#include "magick/thread-private.h"
70#include "magick/utility.h"
71#include "magick/version.h"
72#if defined(MAGICKCORE_LQR_DELEGATE)
73#include <lqr.h>
74#endif
75
76/*
77 Typedef declarations.
78*/
79struct _ResizeFilter
80{
81 MagickRealType
82 (*filter)(const MagickRealType,const ResizeFilter *),
83 (*window)(const MagickRealType,const ResizeFilter *),
cristy33b1c162010-01-23 22:51:51 +000084 support, /* filter region of support - the filter support limit */
85 window_support, /* window support, usally equal to support (expert only) */
anthony55f12332010-09-10 01:13:02 +000086 scale, /* dimension scaling to fit window support (usally 1.0) */
cristy33b1c162010-01-23 22:51:51 +000087 blur, /* x-scale (blur-sharpen) */
88 cubic[8]; /* cubic coefficents for smooth Cubic filters */
cristy3ed852e2009-09-05 21:47:34 +000089
cristybb503372010-05-27 20:51:26 +000090 size_t
cristy3ed852e2009-09-05 21:47:34 +000091 signature;
92};
93
94/*
95 Forward declaractions.
96*/
97static MagickRealType
98 I0(MagickRealType x),
anthonyb6d08c52010-09-13 01:17:04 +000099 BesselOrderOne(MagickRealType),
anthony07a3f7f2010-09-16 03:03:11 +0000100 Sinc(const MagickRealType, const ResizeFilter *),
anthonyba5a7c32010-09-15 02:42:25 +0000101 SincFast(const MagickRealType, const ResizeFilter *);
cristy3ed852e2009-09-05 21:47:34 +0000102
103/*
104%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
105% %
106% %
107% %
108+ F i l t e r F u n c t i o n s %
109% %
110% %
111% %
112%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
113%
cristy33b1c162010-01-23 22:51:51 +0000114% These are the various filter and windowing functions that are provided.
cristy3ed852e2009-09-05 21:47:34 +0000115%
cristy33b1c162010-01-23 22:51:51 +0000116% They are internal to this module only. See AcquireResizeFilterInfo() for
117% details of the access to these functions, via the GetResizeFilterSupport()
118% and GetResizeFilterWeight() API interface.
cristy3ed852e2009-09-05 21:47:34 +0000119%
120% The individual filter functions have this format...
121%
122% static MagickRealtype *FilterName(const MagickRealType x,
123% const MagickRealType support)
124%
cristy33b1c162010-01-23 22:51:51 +0000125% A description of each parameter follows:
cristy3ed852e2009-09-05 21:47:34 +0000126%
cristy33b1c162010-01-23 22:51:51 +0000127% o x: the distance from the sampling point generally in the range of 0 to
128% support. The GetResizeFilterWeight() ensures this a positive value.
129%
130% o resize_filter: current filter information. This allows function to
131% access support, and possibly other pre-calculated information defining
132% the functions.
cristy3ed852e2009-09-05 21:47:34 +0000133%
134*/
135
cristyc5c6f662010-09-22 14:23:02 +0000136#define MagickPIL ((MagickRealType) 3.14159265358979323846264338327950288420L)
cristy560d8182010-09-08 22:36:25 +0000137
anthony48f77622010-10-03 14:32:31 +0000138static MagickRealType Jinc(const MagickRealType x,
cristy3ed852e2009-09-05 21:47:34 +0000139 const ResizeFilter *magick_unused(resize_filter))
140{
141 /*
anthony48f77622010-10-03 14:32:31 +0000142 See Pratt "Digital Image Processing" p.97 for Jinc/Bessel functions.
cristy33b1c162010-01-23 22:51:51 +0000143 http://mathworld.wolfram.com/JincFunction.html and page 11 of
anthony48f77622010-10-03 14:32:31 +0000144 http://www.ph.ed.ac.uk/%7ewjh/teaching/mo/slides/lens/lens.pdf
145
146 The original "zoom" program by Paul Heckbert called this "Bessel"
147 But really its is more accuritally named "Jinc".
cristy3ed852e2009-09-05 21:47:34 +0000148 */
149 if (x == 0.0)
nicolas2ffd3b22010-09-24 20:27:31 +0000150 return(0.25*MagickPIL);
151 return(BesselOrderOne(MagickPIL*x)/(x+x));
cristy3ed852e2009-09-05 21:47:34 +0000152}
153
154static MagickRealType Blackman(const MagickRealType x,
cristyce70c172010-01-07 17:15:30 +0000155 const ResizeFilter *magick_unused(resize_filter))
cristy3ed852e2009-09-05 21:47:34 +0000156{
157 /*
cristy83017922010-09-05 20:45:15 +0000158 Blackman: 2nd order cosine windowing function:
cristy21ce88a2010-09-05 01:37:25 +0000159 0.42 + 0.5 cos(pi x) + 0.08 cos(2pi x)
cristy560d8182010-09-08 22:36:25 +0000160 Refactored by Chantal Racette and Nicolas Robidoux to one trig
161 call and five flops.
cristy3ed852e2009-09-05 21:47:34 +0000162 */
cristyc5c6f662010-09-22 14:23:02 +0000163 const MagickRealType cospix = cos((double) (MagickPIL*x));
cristy560d8182010-09-08 22:36:25 +0000164 return(0.34+cospix*(0.5+cospix*0.16));
cristy3ed852e2009-09-05 21:47:34 +0000165}
166
167static MagickRealType Bohman(const MagickRealType x,
cristyce70c172010-01-07 17:15:30 +0000168 const ResizeFilter *magick_unused(resize_filter))
cristy3ed852e2009-09-05 21:47:34 +0000169{
170 /*
cristy560d8182010-09-08 22:36:25 +0000171 Bohman: 2rd Order cosine windowing function:
172 (1-x) cos(pi x) + sin(pi x) / pi.
nicolasa4f7b4a2010-09-20 20:28:38 +0000173 Refactored by Nicolas Robidoux to one trig call, one sqrt call,
174 and 7 flops, taking advantage of the fact that the support of
175 Bohman is 1 (so that we know that sin(pi x) >= 0).
cristy3ed852e2009-09-05 21:47:34 +0000176 */
cristyc5c6f662010-09-22 14:23:02 +0000177 const double cospix = cos((double) (MagickPIL*x));
nicolasa4f7b4a2010-09-20 20:28:38 +0000178 const double sinpix = sqrt(1.0-cospix*cospix);
nicolas2ffd3b22010-09-24 20:27:31 +0000179 return((1.0-x)*cospix+(1.0/MagickPIL)*sinpix);
cristy3ed852e2009-09-05 21:47:34 +0000180}
181
anthony463be1d2010-09-26 01:07:36 +0000182static MagickRealType Box(const MagickRealType magick_unused(x),
183 const ResizeFilter *magick_unused(resize_filter))
cristy3ed852e2009-09-05 21:47:34 +0000184{
185 /*
nicolas40477452010-09-27 23:42:08 +0000186 A Box filter is a equal weighting function (all weights equal).
anthonyc331dec2010-09-26 01:30:14 +0000187 DO NOT LIMIT results by support or resize point sampling will work
188 as it requests points beyond its normal 0.0 support size.
cristy3ed852e2009-09-05 21:47:34 +0000189 */
anthony463be1d2010-09-26 01:07:36 +0000190 return(1.0);
cristy3ed852e2009-09-05 21:47:34 +0000191}
192
193static MagickRealType CubicBC(const MagickRealType x,
194 const ResizeFilter *resize_filter)
195{
196 /*
197 Cubic Filters using B,C determined values:
cristyf59a8922010-02-28 19:51:23 +0000198 Mitchell-Netravali B=1/3 C=1/3 Qualitively ideal Cubic Filter
199 Catmull-Rom B= 0 C=1/2 Cublic Interpolation Function
200 Cubic B-Spline B= 1 C= 0 Spline Approximation of Gaussian
201 Hermite B= 0 C= 0 Quadratic Spline (support = 1)
cristy3ed852e2009-09-05 21:47:34 +0000202
cristy33b1c162010-01-23 22:51:51 +0000203 See paper by Mitchell and Netravali, Reconstruction Filters in Computer
204 Graphics Computer Graphics, Volume 22, Number 4, August 1988
205 http://www.cs.utexas.edu/users/fussell/courses/cs384g/lectures/mitchell/
cristyf59a8922010-02-28 19:51:23 +0000206 Mitchell.pdf.
cristy3ed852e2009-09-05 21:47:34 +0000207
cristy33b1c162010-01-23 22:51:51 +0000208 Coefficents are determined from B,C values:
cristy3ed852e2009-09-05 21:47:34 +0000209 P0 = ( 6 - 2*B )/6
210 P1 = 0
211 P2 = (-18 +12*B + 6*C )/6
212 P3 = ( 12 - 9*B - 6*C )/6
213 Q0 = ( 8*B +24*C )/6
214 Q1 = ( -12*B -48*C )/6
215 Q2 = ( 6*B +30*C )/6
216 Q3 = ( - 1*B - 6*C )/6
217
cristy33b1c162010-01-23 22:51:51 +0000218 which are used to define the filter:
cristyf59a8922010-02-28 19:51:23 +0000219
cristy3ed852e2009-09-05 21:47:34 +0000220 P0 + P1*x + P2*x^2 + P3*x^3 0 <= x < 1
221 Q0 + Q1*x + Q2*x^2 + Q3*x^3 1 <= x <= 2
222
cristy33b1c162010-01-23 22:51:51 +0000223 which ensures function is continuous in value and derivative (slope).
cristy3ed852e2009-09-05 21:47:34 +0000224 */
225 if (x < 1.0)
226 return(resize_filter->cubic[0]+x*(resize_filter->cubic[1]+x*
227 (resize_filter->cubic[2]+x*resize_filter->cubic[3])));
228 if (x < 2.0)
cristy679e6962010-03-18 00:42:45 +0000229 return(resize_filter->cubic[4]+x*(resize_filter->cubic[5]+x*
cristy03dbbd22010-09-19 23:04:47 +0000230 (resize_filter->cubic[6]+x*resize_filter->cubic[7])));
cristy3ed852e2009-09-05 21:47:34 +0000231 return(0.0);
232}
233
234static MagickRealType Gaussian(const MagickRealType x,
235 const ResizeFilter *magick_unused(resize_filter))
236{
cristy560d8182010-09-08 22:36:25 +0000237 /*
nicolas40477452010-09-27 23:42:08 +0000238 1D Gaussian with sigma=1/2:
anthonyb6d08c52010-09-13 01:17:04 +0000239 exp(-2 x^2)/sqrt(pi/2))
cristy560d8182010-09-08 22:36:25 +0000240 */
anthonyf21ee692010-09-15 12:06:44 +0000241 /*const MagickRealType alpha = (MagickRealType) (2.0/MagickSQ2PI);*/
cristy03dbbd22010-09-19 23:04:47 +0000242 return(exp((double) (-2.0*x*x)));
cristy3ed852e2009-09-05 21:47:34 +0000243}
244
245static MagickRealType Hanning(const MagickRealType x,
246 const ResizeFilter *magick_unused(resize_filter))
247{
248 /*
nicolas40477452010-09-27 23:42:08 +0000249 Cosine window function:
250 .5+.5cos(pi x).
cristy3ed852e2009-09-05 21:47:34 +0000251 */
cristyc5c6f662010-09-22 14:23:02 +0000252 const MagickRealType cospix = cos((double) (MagickPIL*x));
cristy560d8182010-09-08 22:36:25 +0000253 return(0.5+0.5*cospix);
cristy3ed852e2009-09-05 21:47:34 +0000254}
255
256static MagickRealType Hamming(const MagickRealType x,
257 const ResizeFilter *magick_unused(resize_filter))
258{
259 /*
nicolas40477452010-09-27 23:42:08 +0000260 Offset cosine window function:
261 .54 + .46 cos(pi x).
cristy3ed852e2009-09-05 21:47:34 +0000262 */
cristyc5c6f662010-09-22 14:23:02 +0000263 const MagickRealType cospix = cos((double) (MagickPIL*x));
cristy560d8182010-09-08 22:36:25 +0000264 return(0.54+0.46*cospix);
cristy3ed852e2009-09-05 21:47:34 +0000265}
266
267static MagickRealType Kaiser(const MagickRealType x,
268 const ResizeFilter *magick_unused(resize_filter))
269{
270#define Alpha 6.5
271#define I0A (1.0/I0(Alpha))
272
273 /*
nicolas07bac812010-09-19 18:47:02 +0000274 Kaiser Windowing Function (bessel windowing): Alpha is a free
275 value from 5 to 8 (currently hardcoded to 6.5).
276 Future: make alpha the IOA pre-calculation, an 'expert' setting.
cristy3ed852e2009-09-05 21:47:34 +0000277 */
278 return(I0A*I0(Alpha*sqrt((double) (1.0-x*x))));
279}
280
281static MagickRealType Lagrange(const MagickRealType x,
282 const ResizeFilter *resize_filter)
283{
cristy3ed852e2009-09-05 21:47:34 +0000284 MagickRealType
285 value;
286
cristybb503372010-05-27 20:51:26 +0000287 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +0000288 i;
289
cristy9af9b5d2010-08-15 17:04:28 +0000290 ssize_t
291 n,
292 order;
293
cristy3ed852e2009-09-05 21:47:34 +0000294 /*
nicolas07bac812010-09-19 18:47:02 +0000295 Lagrange piecewise polynomial fit of sinc: N is the 'order' of the
296 lagrange function and depends on the overall support window size
297 of the filter. That is: for a support of 2, it gives a lagrange-4
298 (piecewise cubic function).
cristy3ed852e2009-09-05 21:47:34 +0000299
nicolas07bac812010-09-19 18:47:02 +0000300 "n" identifies the piece of the piecewise polynomial.
cristy3ed852e2009-09-05 21:47:34 +0000301
nicolas07bac812010-09-19 18:47:02 +0000302 See Survey: Interpolation Methods, IEEE Transactions on Medical
303 Imaging, Vol 18, No 11, November 1999, p1049-1075, -- Equation 27
304 on p1064.
cristy3ed852e2009-09-05 21:47:34 +0000305 */
306 if (x > resize_filter->support)
307 return(0.0);
cristybb503372010-05-27 20:51:26 +0000308 order=(ssize_t) (2.0*resize_filter->window_support); /* number of pieces */
anthonyc2d07db2010-09-15 23:47:40 +0000309 /*n=(ssize_t)((1.0*order)/2.0+x); -- which piece does x belong to */
310 n = (ssize_t)(resize_filter->window_support + x);
cristy3ed852e2009-09-05 21:47:34 +0000311 value=1.0f;
312 for (i=0; i < order; i++)
313 if (i != n)
314 value*=(n-i-x)/(n-i);
315 return(value);
316}
317
318static MagickRealType Quadratic(const MagickRealType x,
319 const ResizeFilter *magick_unused(resize_filter))
320{
321 /*
322 2rd order (quadratic) B-Spline approximation of Gaussian.
323 */
324 if (x < 0.5)
325 return(0.75-x*x);
326 if (x < 1.5)
327 return(0.5*(x-1.5)*(x-1.5));
328 return(0.0);
329}
330
anthony07a3f7f2010-09-16 03:03:11 +0000331static MagickRealType Sinc(const MagickRealType x,
cristy3ed852e2009-09-05 21:47:34 +0000332 const ResizeFilter *magick_unused(resize_filter))
333{
anthony720660f2010-09-07 10:05:14 +0000334 /*
nicolas40477452010-09-27 23:42:08 +0000335 Scaled sinc(x) function using a trig call:
nicolas07bac812010-09-19 18:47:02 +0000336 sinc(x) == sin(pi x)/(pi x).
anthony720660f2010-09-07 10:05:14 +0000337 */
anthony2d9b8b52010-09-14 08:31:07 +0000338 if (x != 0.0)
cristy560d8182010-09-08 22:36:25 +0000339 {
cristyc5c6f662010-09-22 14:23:02 +0000340 const MagickRealType pix = (MagickRealType) (MagickPIL*x);
nicolas07bac812010-09-19 18:47:02 +0000341 return(sin((double) pix)/pix);
cristy560d8182010-09-08 22:36:25 +0000342 }
nicolas2ffd3b22010-09-24 20:27:31 +0000343 return((MagickRealType) 1.0);
anthony720660f2010-09-07 10:05:14 +0000344}
345
anthonyba5a7c32010-09-15 02:42:25 +0000346static MagickRealType SincFast(const MagickRealType x,
anthony720660f2010-09-07 10:05:14 +0000347 const ResizeFilter *magick_unused(resize_filter))
348{
cristy560d8182010-09-08 22:36:25 +0000349 /*
350 Approximations of the sinc function sin(pi x)/(pi x) over the
351 interval [-4,4] constructed by Nicolas Robidoux and Chantal
352 Racette with funding from the Natural Sciences and Engineering
353 Research Council of Canada.
nicolas07bac812010-09-19 18:47:02 +0000354
355 Although the approximations are polynomials (for low order of
356 approximation) and quotients of polynomials (for higher order of
357 approximation) and consequently are similar in form to Taylor
358 polynomials/Pade approximants, the approximations are computed
359 with a completely different technique.
360
361 Summary: These approximations are "the best" in terms of bang
362 (accuracy) for the buck (flops). More specifically: Among the
363 polynomial quotients that can be computed using a fixed number of
364 flops (with a given "+ - * / budget"), the chosen polynomial
365 quotient is the one closest to the approximated function with
366 respect to maximum absolute relative error over the given
367 interval.
368
369 The Remez algorithm, as implemented in the boost library's minimax
nicolas3aab40c2010-09-19 21:14:15 +0000370 package, is the key to the construction:
nicolas07bac812010-09-19 18:47:02 +0000371 http://www.boost.org/doc/libs/1_36_0/libs/math/doc/...
372 ...sf_and_dist/html/math_toolkit/backgrounders/remez.html
cristy560d8182010-09-08 22:36:25 +0000373 */
nicolas3aab40c2010-09-19 21:14:15 +0000374 /*
375 If outside of the interval of approximation, use the standard trig
376 formula.
377 */
anthony2d9b8b52010-09-14 08:31:07 +0000378 if (x > 4.0)
cristy03dbbd22010-09-19 23:04:47 +0000379 {
cristyc5c6f662010-09-22 14:23:02 +0000380 const MagickRealType pix = (MagickRealType) (MagickPIL*x);
cristy03dbbd22010-09-19 23:04:47 +0000381 return(sin((double) pix)/pix);
382 }
anthony2d9b8b52010-09-14 08:31:07 +0000383 {
nicolas07bac812010-09-19 18:47:02 +0000384 /*
385 The approximations only depend on x^2 (sinc is an even
386 function).
387 */
388 const MagickRealType xx = x*x;
cristy83017922010-09-05 20:45:15 +0000389#if MAGICKCORE_QUANTUM_DEPTH <= 8
390 /*
anthony2d9b8b52010-09-14 08:31:07 +0000391 Maximum absolute relative error 6.3e-6 < 1/2^17.
cristy738e7562010-09-01 12:48:07 +0000392 */
393 const MagickRealType c0 = 0.173610016489197553621906385078711564924e-2L;
394 const MagickRealType c1 = -0.384186115075660162081071290162149315834e-3L;
395 const MagickRealType c2 = 0.393684603287860108352720146121813443561e-4L;
396 const MagickRealType c3 = -0.248947210682259168029030370205389323899e-5L;
397 const MagickRealType c4 = 0.107791837839662283066379987646635416692e-6L;
398 const MagickRealType c5 = -0.324874073895735800961260474028013982211e-8L;
399 const MagickRealType c6 = 0.628155216606695311524920882748052490116e-10L;
400 const MagickRealType c7 = -0.586110644039348333520104379959307242711e-12L;
nicolas3aab40c2010-09-19 21:14:15 +0000401 const MagickRealType p =
402 c0+xx*(c1+xx*(c2+xx*(c3+xx*(c4+xx*(c5+xx*(c6+xx*c7))))));
nicolas07bac812010-09-19 18:47:02 +0000403 return((xx-1.0)*(xx-4.0)*(xx-9.0)*(xx-16.0)*p);
anthony2d9b8b52010-09-14 08:31:07 +0000404#elif MAGICKCORE_QUANTUM_DEPTH <= 16
cristydbeb3eb2010-09-09 13:41:36 +0000405 /*
anthony2d9b8b52010-09-14 08:31:07 +0000406 Max. abs. rel. error 2.2e-8 < 1/2^25.
cristydbeb3eb2010-09-09 13:41:36 +0000407 */
408 const MagickRealType c0 = 0.173611107357320220183368594093166520811e-2L;
409 const MagickRealType c1 = -0.384240921114946632192116762889211361285e-3L;
nicolas3aab40c2010-09-19 21:14:15 +0000410 const MagickRealType c2 = 0.394201182359318128221229891724947048771e-4L;
411 const MagickRealType c3 = -0.250963301609117217660068889165550534856e-5L;
412 const MagickRealType c4 = 0.111902032818095784414237782071368805120e-6L;
413 const MagickRealType c5 = -0.372895101408779549368465614321137048875e-8L;
414 const MagickRealType c6 = 0.957694196677572570319816780188718518330e-10L;
cristydbeb3eb2010-09-09 13:41:36 +0000415 const MagickRealType c7 = -0.187208577776590710853865174371617338991e-11L;
416 const MagickRealType c8 = 0.253524321426864752676094495396308636823e-13L;
417 const MagickRealType c9 = -0.177084805010701112639035485248501049364e-15L;
nicolas3aab40c2010-09-19 21:14:15 +0000418 const MagickRealType p =
419 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 +0000420 return((xx-1.0)*(xx-4.0)*(xx-9.0)*(xx-16.0)*p);
nicolasc2d28f02010-09-27 18:56:15 +0000421#else
nicolas3aab40c2010-09-19 21:14:15 +0000422 /*
423 Max. abs. rel. error 1.2e-12 < 1/2^39.
424 */
425 const MagickRealType c0 = 0.173611111110910715186413700076827593074e-2L;
426 const MagickRealType c1 = -0.289105544717893415815859968653611245425e-3L;
427 const MagickRealType c2 = 0.206952161241815727624413291940849294025e-4L;
428 const MagickRealType c3 = -0.834446180169727178193268528095341741698e-6L;
429 const MagickRealType c4 = 0.207010104171026718629622453275917944941e-7L;
430 const MagickRealType c5 = -0.319724784938507108101517564300855542655e-9L;
431 const MagickRealType c6 = 0.288101675249103266147006509214934493930e-11L;
432 const MagickRealType c7 = -0.118218971804934245819960233886876537953e-13L;
433 const MagickRealType p =
434 c0+xx*(c1+xx*(c2+xx*(c3+xx*(c4+xx*(c5+xx*(c6+xx*c7))))));
435 const MagickRealType d0 = 1.0L;
436 const MagickRealType d1 = 0.547981619622284827495856984100563583948e-1L;
437 const MagickRealType d2 = 0.134226268835357312626304688047086921806e-2L;
438 const MagickRealType d3 = 0.178994697503371051002463656833597608689e-4L;
439 const MagickRealType d4 = 0.114633394140438168641246022557689759090e-6L;
440 const MagickRealType q = d0+xx*(d1+xx*(d2+xx*(d3+xx*d4)));
441 return((xx-1.0)*(xx-4.0)*(xx-9.0)*(xx-16.0)/q*p);
cristy657c6352010-08-29 14:05:08 +0000442#endif
cristy83017922010-09-05 20:45:15 +0000443 }
cristy3ed852e2009-09-05 21:47:34 +0000444}
445
446static MagickRealType Triangle(const MagickRealType x,
447 const ResizeFilter *magick_unused(resize_filter))
448{
449 /*
nicolas0edb0862010-09-19 18:56:19 +0000450 1st order (linear) B-Spline, bilinear interpolation, Tent 1D
451 filter, or a Bartlett 2D Cone filter.
cristy3ed852e2009-09-05 21:47:34 +0000452 */
453 if (x < 1.0)
454 return(1.0-x);
455 return(0.0);
456}
457
458static MagickRealType Welsh(const MagickRealType x,
459 const ResizeFilter *magick_unused(resize_filter))
460{
461 /*
462 Welsh parabolic windowing filter.
463 */
cristy560d8182010-09-08 22:36:25 +0000464 if (x < 1.0)
cristy3ed852e2009-09-05 21:47:34 +0000465 return(1.0-x*x);
466 return(0.0);
467}
468
469/*
470%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
471% %
472% %
473% %
474+ A c q u i r e R e s i z e F i l t e r %
475% %
476% %
477% %
478%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
479%
nicolas07bac812010-09-19 18:47:02 +0000480% AcquireResizeFilter() allocates the ResizeFilter structure. Choose
481% from these filters:
cristy3ed852e2009-09-05 21:47:34 +0000482%
483% FIR (Finite impulse Response) Filters
484% Box Triangle Quadratic
485% Cubic Hermite Catrom
486% Mitchell
487%
488% IIR (Infinite impulse Response) Filters
anthony48f77622010-10-03 14:32:31 +0000489% Gaussian Sinc Jinc (Bessel)
cristy3ed852e2009-09-05 21:47:34 +0000490%
anthony48f77622010-10-03 14:32:31 +0000491% Windowed Sinc/Jinc Filters
cristy3ed852e2009-09-05 21:47:34 +0000492% Blackman Hanning Hamming
anthony48f77622010-10-03 14:32:31 +0000493% Kaiser Lanczos
cristy3ed852e2009-09-05 21:47:34 +0000494%
anthony61b5ddd2010-10-05 02:33:31 +0000495% Special purpose Filters
496% SincFast Lanczos2D
497%
anthony48f77622010-10-03 14:32:31 +0000498% The users "-filter" selection is used to lookup the default 'expert'
499% settings for that filter from a internal table. However any provided
500% 'expert' settings (see below) may override this selection.
501%
502% FIR filters are used as is, and are limited to that filters support
nicolas07bac812010-09-19 18:47:02 +0000503% window (unless over-ridden). 'Gaussian' while classed as an IIR
anthonyc331dec2010-09-26 01:30:14 +0000504% filter, is also simply clipped by its support size (currently 1.5
anthony48f77622010-10-03 14:32:31 +0000505% ro approximatally 3*sigma as recommended by many references)
cristy3ed852e2009-09-05 21:47:34 +0000506%
anthony48f77622010-10-03 14:32:31 +0000507% The selection is typically either a windowed Sinc, or interpolated
nicolas07bac812010-09-19 18:47:02 +0000508% filter, for use by functions such as ResizeImage(). However if a
anthony48f77622010-10-03 14:32:31 +0000509% 'cylindrical' filter flag is requested, any default Sinc weighting
510% and windowing functions will be promoted to cylindrical Jinc form of
511% function.
cristy3ed852e2009-09-05 21:47:34 +0000512%
anthony48f77622010-10-03 14:32:31 +0000513% Directly requesting 'Sinc' or 'Jinc' will force the use of that
514% filter function without any windowing. This is not recommended,
515% except by image processing experts or in expert options. Selecting a
516% window filtering version of these functions is better.
cristy3ed852e2009-09-05 21:47:34 +0000517%
anthony48f77622010-10-03 14:32:31 +0000518% Lanczos is a special case of a Sinc-windowed Sinc, (or Jinc-Jinc for
519% the cylindrical case) but defaulting to 3-lobe support, rather that
520% the default 4 lobe support of the other windowed sinc/jinc filters.
cristy3ed852e2009-09-05 21:47:34 +0000521%
anthony48f77622010-10-03 14:32:31 +0000522% Two forms of the 'Sinc' function are available: Sinc and SincFast.
nicolas07bac812010-09-19 18:47:02 +0000523% Sinc is computed using the traditional sin(pi*x)/(pi*x); it is
524% selected if the user specifically specifies the use of a Sinc
525% filter. SincFast uses highly accurate (and fast) polynomial (low Q)
anthony48f77622010-10-03 14:32:31 +0000526% and rational (high Q) approximations, and will be used by default in
527% most cases.
anthonyba5a7c32010-09-15 02:42:25 +0000528%
anthony61b5ddd2010-10-05 02:33:31 +0000529% The Lanczos2D filter is just a normal 2-lobed Lanczos filter. But if
530% selected as a cylindrical (radial) filter, the 2-lobed Jinc windowed
531% Jinc will be modified by a blur factor to negate the effect of windowing
532% the Jinc function, and greatly reduce resulting blur.
533%
nicolas07bac812010-09-19 18:47:02 +0000534% Special 'expert' options can be used to override any and all filter
535% settings. This is not advised unless you have expert knowledge of
536% the use of resampling filtered techniques. Check on the results of
537% your selections using the "filter:verbose" setting to make sure you
anthony61b5ddd2010-10-05 02:33:31 +0000538% get the exact filter that you are tring to achieve.
cristy3ed852e2009-09-05 21:47:34 +0000539%
anthony48f77622010-10-03 14:32:31 +0000540% "filter:filter" Select the main function associated with
541% this filter name, as the weighting function of the filter.
542% This can be used to set a windowing function as a weighting
543% function, for special purposes, such as graphing.
cristy3ed852e2009-09-05 21:47:34 +0000544%
anthony7bdc0ed2010-09-15 01:52:32 +0000545% If a "filter:window" operation has not been provided, then a 'Box'
546% windowing function will be set to denote that no windowing function
547% is being used.
cristy3ed852e2009-09-05 21:47:34 +0000548%
549% "filter:window" Select this windowing function for the filter.
anthony7bdc0ed2010-09-15 01:52:32 +0000550% While any filter could be used as a windowing function, using the
551% 'first lobe' of that filter over the whole support window, using a
anthony48f77622010-10-03 14:32:31 +0000552% non-windowing function is not advisible. If no weighting filter
553% function is specifed a 'SincFast' filter will be used.
cristy3ed852e2009-09-05 21:47:34 +0000554%
anthony48f77622010-10-03 14:32:31 +0000555% "filter:lobes" Number of lobes to use for the Sinc/Jinc filter.
anthony7bdc0ed2010-09-15 01:52:32 +0000556% This a simpler method of setting filter support size that will
anthony48f77622010-10-03 14:32:31 +0000557% correctly handle the Sinc/Jinc switch for an operators filtering
anthony7bdc0ed2010-09-15 01:52:32 +0000558% requirements. Only integers should be given.
cristy3ed852e2009-09-05 21:47:34 +0000559%
560% "filter:support" Set the support size for filtering to the size given
anthony48f77622010-10-03 14:32:31 +0000561% This not recommended for Sinc/Jinc windowed filters (lobes should
anthony7bdc0ed2010-09-15 01:52:32 +0000562% be used instead). This will override any 'filter:lobes' option.
cristy3ed852e2009-09-05 21:47:34 +0000563%
anthonyb6d08c52010-09-13 01:17:04 +0000564% "filter:win-support" Scale windowing function to this size instead.
565% This causes the windowing (or self-windowing Lagrange filter) to act
566% is if the support window it much much larger than what is actually
567% supplied to the calling operator. The filter however is still
568% clipped to the real support size given, by the support range suppiled
569% to the caller. If unset this will equal the normal filter support
570% size.
571%
cristy3ed852e2009-09-05 21:47:34 +0000572% "filter:blur" Scale the filter and support window by this amount.
573% A value >1 will generally result in a more burred image with
574% more ringing effects, while a value <1 will sharpen the
575% resulting image with more aliasing and Morie effects.
576%
cristy3ed852e2009-09-05 21:47:34 +0000577% "filter:b"
578% "filter:c" Override the preset B,C values for a Cubic type of filter
579% If only one of these are given it is assumes to be a 'Keys'
580% type of filter such that B+2C=1, where Keys 'alpha' value = C
581%
anthonyb6d08c52010-09-13 01:17:04 +0000582% "filter:verbose" Output the exact results of the filter selections
583% made, as well as plotting data for graphing the resulting filter
584% over support range (blur adjusted).
cristy3ed852e2009-09-05 21:47:34 +0000585%
586% Set a true un-windowed Sinc filter with 10 lobes (very slow)
anthony48f77622010-10-03 14:32:31 +0000587% -define filter:filter=Sinc
588% -define filter:lobes=8
cristy3ed852e2009-09-05 21:47:34 +0000589%
anthony48f77622010-10-03 14:32:31 +0000590% For example force an 8 lobe Lanczos (Sinc or Jinc) filter...
cristy3ed852e2009-09-05 21:47:34 +0000591% -filter Lanczos
anthony48f77622010-10-03 14:32:31 +0000592% -define filter:lobes=8
anthony7bdc0ed2010-09-15 01:52:32 +0000593%
cristy3ed852e2009-09-05 21:47:34 +0000594% The format of the AcquireResizeFilter method is:
595%
596% ResizeFilter *AcquireResizeFilter(const Image *image,
597% const FilterTypes filter_type, const MagickBooleanType radial,
598% ExceptionInfo *exception)
599%
cristy33b1c162010-01-23 22:51:51 +0000600% A description of each parameter follows:
601%
cristy3ed852e2009-09-05 21:47:34 +0000602% o image: the image.
603%
nicolas07bac812010-09-19 18:47:02 +0000604% o filter: the filter type, defining a preset filter, window and
605% support. The artifact settings listed above will override
606% those selections.
cristy3ed852e2009-09-05 21:47:34 +0000607%
anthony48f77622010-10-03 14:32:31 +0000608% o blur: blur the filter by this amount, use 1.0 if unknown. Image
609% artifact "filter:blur" will override this API call usage, including
610% any internal change (such as for cylindrical usage).
cristy3ed852e2009-09-05 21:47:34 +0000611%
anthony48f77622010-10-03 14:32:31 +0000612% o radial: use a 1D orthogonal filter (Sinc) or 2D cylindrical
613% (radial) filter (Jinc)
cristy3ed852e2009-09-05 21:47:34 +0000614%
615% o exception: return any errors or warnings in this structure.
616%
617*/
618MagickExport ResizeFilter *AcquireResizeFilter(const Image *image,
cristyf59a8922010-02-28 19:51:23 +0000619 const FilterTypes filter,const MagickRealType blur,
cristy3ed852e2009-09-05 21:47:34 +0000620 const MagickBooleanType cylindrical,ExceptionInfo *exception)
621{
622 const char
623 *artifact;
624
625 FilterTypes
626 filter_type,
627 window_type;
628
cristy3ed852e2009-09-05 21:47:34 +0000629 MagickRealType
630 B,
631 C;
632
633 register ResizeFilter
634 *resize_filter;
635
cristy9af9b5d2010-08-15 17:04:28 +0000636 ssize_t
637 option;
638
cristy3ed852e2009-09-05 21:47:34 +0000639 /*
anthony48f77622010-10-03 14:32:31 +0000640 Table Mapping given Filter, into Weighting and Windowing functions.
nicolas07bac812010-09-19 18:47:02 +0000641 A 'Box' windowing function means its a simble non-windowed filter.
anthony48f77622010-10-03 14:32:31 +0000642 An 'SincFast' filter function could be upgraded to a 'Jinc' filter
643 if a "cylindrical", unless a 'Sinc' or 'SincFast' filter was
644 specifically requested.
anthony449887b2010-09-10 02:23:33 +0000645
nicolas07bac812010-09-19 18:47:02 +0000646 WARNING: The order of this tabel must match the order of the
647 FilterTypes enumeration specified in "resample.h", or the filter
648 names will not match the filter being setup.
anthony1e2d5ca2010-09-10 02:24:35 +0000649
650 You can check filter setups with the "filter:verbose" setting.
cristy3ed852e2009-09-05 21:47:34 +0000651 */
652 static struct
653 {
654 FilterTypes
655 filter,
656 window;
657 } const mapping[SentinelFilter] =
658 {
anthony462ee072010-09-27 12:34:02 +0000659 { UndefinedFilter, BoxFilter }, /* Undefined (default to Box) */
660 { PointFilter, BoxFilter }, /* SPECIAL: Nearest neighbour */
661 { BoxFilter, BoxFilter }, /* Box averaging filter */
662 { TriangleFilter, BoxFilter }, /* Linear interpolation filter */
663 { HermiteFilter, BoxFilter }, /* Hermite interpolation filter */
664 { SincFastFilter, HanningFilter }, /* Hanning -- cosine-sinc */
665 { SincFastFilter, HammingFilter }, /* Hamming -- '' variation */
666 { SincFastFilter, BlackmanFilter }, /* Blackman -- 2*cosine-sinc */
667 { GaussianFilter, BoxFilter }, /* Gaussian blur filter */
668 { QuadraticFilter, BoxFilter }, /* Quadratic Gaussian approximation */
669 { CubicFilter, BoxFilter }, /* Cubic B-Spline */
670 { CatromFilter, BoxFilter }, /* Cubic interpolator */
671 { MitchellFilter, BoxFilter }, /* 'Ideal' cubic filter */
672 { LanczosFilter, SincFastFilter }, /* SPECIAL: 3-lobed sinc-sinc */
anthony48f77622010-10-03 14:32:31 +0000673 { JincFilter, BoxFilter }, /* Raw 3-lobed Jinc function */
674 { SincFilter, BoxFilter }, /* Raw 4-lobed Sinc function */
anthony462ee072010-09-27 12:34:02 +0000675 { SincFastFilter, KaiserFilter }, /* Kaiser -- square root-sinc */
676 { SincFastFilter, WelshFilter }, /* Welsh -- parabolic-sinc */
677 { SincFastFilter, CubicFilter }, /* Parzen -- cubic-sinc */
678 { LagrangeFilter, BoxFilter }, /* Lagrange self-windowing filter */
679 { SincFastFilter, BohmanFilter }, /* Bohman -- 2*cosine-sinc */
680 { SincFastFilter, TriangleFilter }, /* Bartlett -- triangle-sinc */
681 { SincFastFilter, BoxFilter }, /* Raw fast sinc ("Pade"-type) */
anthony61b5ddd2010-10-05 02:33:31 +0000682 { Lanczos2DFilter, JincFilter }, /* SPECIAL: 2-lobed jinc-jinc */
cristy3ed852e2009-09-05 21:47:34 +0000683 };
684 /*
nicolas32f44eb2010-09-20 01:23:12 +0000685 Table mapping the filter/window from the above table to an actual
nicolas07bac812010-09-19 18:47:02 +0000686 function. The default support size for that filter as a weighting
687 function, the range to scale with to use that function as a sinc
688 windowing function, (typ 1.0).
anthony933b4bf2010-09-10 02:31:37 +0000689
anthony07a3f7f2010-09-16 03:03:11 +0000690 Note that the filter_type -> function is 1 to 1 except for Sinc(),
nicolas07bac812010-09-19 18:47:02 +0000691 SincFast(), and CubicBC() functions, which may have multiple
692 filter to function associations.
693
694 See "filter:verbose" handling below for the function -> filter
695 mapping.
cristy3ed852e2009-09-05 21:47:34 +0000696 */
697 static struct
698 {
699 MagickRealType
700 (*function)(const MagickRealType, const ResizeFilter*),
anthony61b5ddd2010-10-05 02:33:31 +0000701 lobes, /* default lobes/support size of the weighting filter */
anthony2d9b8b52010-09-14 08:31:07 +0000702 scale, /* windowing function range, for scaling windowing function */
anthony933b4bf2010-09-10 02:31:37 +0000703 B,C; /* Cubic Filter factors for a CubicBC function, else ignored */
cristy3ed852e2009-09-05 21:47:34 +0000704 } const filters[SentinelFilter] =
705 {
anthony61b5ddd2010-10-05 02:33:31 +0000706 { Box, 0.5, 0.5, 0.0, 0.0 }, /* Undefined (default to Box) */
707 { Box, 0.0, 0.5, 0.0, 0.0 }, /* Point (special handling) */
708 { Box, 0.5, 0.5, 0.0, 0.0 }, /* Box */
709 { Triangle, 1.0, 1.0, 0.0, 0.0 }, /* Triangle */
710 { CubicBC, 1.0, 1.0, 0.0, 0.0 }, /* Hermite (cubic B=C=0) */
711 { Hanning, 1.0, 1.0, 0.0, 0.0 }, /* Hanning, cosine window */
712 { Hamming, 1.0, 1.0, 0.0, 0.0 }, /* Hamming, '' variation */
713 { Blackman, 1.0, 1.0, 0.0, 0.0 }, /* Blackman, 2*cosine window */
714 { Gaussian, 1.5, 1.5, 0.0, 0.0 }, /* Gaussian */
715 { Quadratic, 1.5, 1.5, 0.0, 0.0 }, /* Quadratic gaussian */
716 { CubicBC, 2.0, 2.0, 1.0, 0.0 }, /* Cubic B-Spline (B=1,C=0) */
717 { CubicBC, 2.0, 1.0, 0.0, 0.5 }, /* Catmull-Rom (B=0,C=1/2) */
718 { CubicBC, 2.0, 1.0, 1./3., 1./3. }, /* Mitchell (B=C=1/3) */
719 { SincFast, 3.0, 1.0, 0.0, 0.0 }, /* Lanczos, 3-lobed sinc-sinc */
720 { Jinc, 3.0, 1.21967, 0.0, 0.0 }, /* Raw 3-lobed Jinc */
721 { Sinc, 4.0, 1.0, 0.0, 0.0 }, /* Raw 4-lobed Sinc */
722 { Kaiser, 1.0, 1.0, 0.0, 0.0 }, /* Kaiser (square root window) */
723 { Welsh, 1.0, 1.0, 0.0, 0.0 }, /* Welsh (parabolic window) */
724 { CubicBC, 2.0, 2.0, 1.0, 0.0 }, /* Parzen (B-Spline window) */
725 { Lagrange, 2.0, 1.0, 0.0, 0.0 }, /* Lagrange sinc approximation */
726 { Bohman, 1.0, 1.0, 0.0, 0.0 }, /* Bohman, 2*Cosine window */
727 { Triangle, 1.0, 1.0, 0.0, 0.0 }, /* Bartlett (triangle window) */
728 { SincFast, 4.0, 1.0, 0.0, 0.0 }, /* Raw fast sinc ("Pade"-type) */
729 { Jinc, 2.0, 1.0, 0.0, 0.0 }, /* Lanczos2D, adjusted below */
cristy3ed852e2009-09-05 21:47:34 +0000730 };
731 /*
anthony48f77622010-10-03 14:32:31 +0000732 The known zero crossings of the Jinc() or more accuritally the Jinc(x*PI)
733 function being used as a filter. It is used by the "filter:lobes" for of
734 support selection, so users do not have to deal with the highly irrational
735 sizes of the 'lobes' of the Jinc filter.
736
737 Values were sourced from
738 http://cose.math.bas.bg/webMathematica/webComputing/BesselZeros.jsp
739 Using Jv-function with v=1, then divided by PI.
cristy3ed852e2009-09-05 21:47:34 +0000740 */
741 static MagickRealType
anthony48f77622010-10-03 14:32:31 +0000742 jinc_zeros[16] =
cristy3ed852e2009-09-05 21:47:34 +0000743 {
anthonyc2d07db2010-09-15 23:47:40 +0000744 1.21966989126651,
745 2.23313059438153,
746 3.23831548416624,
747 4.24106286379607,
748 5.24276437687019,
749 6.24392168986449,
750 7.24475986871996,
751 8.24539491395205,
752 9.24589268494948,
753 10.2462933487549,
754 11.2466227948779,
755 12.2468984611381,
756 13.2471325221811,
757 14.2473337358069,
758 15.2475085630373,
759 16.247661874701
cristy3ed852e2009-09-05 21:47:34 +0000760 };
761
cristy33b1c162010-01-23 22:51:51 +0000762 /*
763 Allocate resize filter.
764 */
cristy3ed852e2009-09-05 21:47:34 +0000765 assert(image != (const Image *) NULL);
766 assert(image->signature == MagickSignature);
767 if (image->debug != MagickFalse)
768 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
769 assert(UndefinedFilter < filter && filter < SentinelFilter);
770 assert(exception != (ExceptionInfo *) NULL);
771 assert(exception->signature == MagickSignature);
cristy73bd4a52010-10-05 11:24:23 +0000772 resize_filter=(ResizeFilter *) AcquireMagickMemory(sizeof(*resize_filter));
cristy3ed852e2009-09-05 21:47:34 +0000773 if (resize_filter == (ResizeFilter *) NULL)
774 ThrowFatalException(ResourceLimitFatalError,"MemoryAllocationFailed");
cristy33b1c162010-01-23 22:51:51 +0000775 /*
776 Defaults for the requested filter.
777 */
778 filter_type=mapping[filter].filter;
779 window_type=mapping[filter].window;
anthony48f77622010-10-03 14:32:31 +0000780 /* Cylindrical Filters should use Jinc instead of Sinc */
anthonyb6d08c52010-09-13 01:17:04 +0000781 if (cylindrical != MagickFalse)
cristy33b1c162010-01-23 22:51:51 +0000782 switch (filter_type)
783 {
784 case SincFilter:
anthony48f77622010-10-03 14:32:31 +0000785 /* Promote 1D Sinc Filter to a 2D Jinc filter. */
anthonyb6d08c52010-09-13 01:17:04 +0000786 if ( filter != SincFilter )
anthony48f77622010-10-03 14:32:31 +0000787 filter_type=JincFilter;
cristy33b1c162010-01-23 22:51:51 +0000788 break;
anthonyba5a7c32010-09-15 02:42:25 +0000789 case SincFastFilter:
nicolas07bac812010-09-19 18:47:02 +0000790 /* Ditto for SincFast variant */
anthonyba5a7c32010-09-15 02:42:25 +0000791 if ( filter != SincFastFilter )
anthony48f77622010-10-03 14:32:31 +0000792 filter_type=JincFilter;
anthony7bdc0ed2010-09-15 01:52:32 +0000793 break;
anthony61b5ddd2010-10-05 02:33:31 +0000794
cristy33b1c162010-01-23 22:51:51 +0000795 case LanczosFilter:
anthony48f77622010-10-03 14:32:31 +0000796 /* Promote Lanczos from a Sinc-Sinc to a Jinc-Jinc */
797 filter_type=JincFilter;
798 window_type=JincFilter;
cristy33b1c162010-01-23 22:51:51 +0000799 break;
cristya782ecf2010-01-25 02:59:14 +0000800 default:
801 break;
cristy3ed852e2009-09-05 21:47:34 +0000802 }
anthony61b5ddd2010-10-05 02:33:31 +0000803 else
804 switch (filter_type)
805 {
806 case Lanczos2DFilter:
807 /* depromote to a 2-lobe Sinc-Sinc for orthoginal use */
808 window_type=SincFastFilter;
809 break;
810 default:
811 break;
812 }
813
cristy3ed852e2009-09-05 21:47:34 +0000814 artifact=GetImageArtifact(image,"filter:filter");
cristy33b1c162010-01-23 22:51:51 +0000815 if (artifact != (const char *) NULL)
816 {
cristy9af9b5d2010-08-15 17:04:28 +0000817 option=ParseMagickOption(MagickFilterOptions,MagickFalse,artifact);
cristy33b1c162010-01-23 22:51:51 +0000818 if ((UndefinedFilter < option) && (option < SentinelFilter))
anthony61b5ddd2010-10-05 02:33:31 +0000819 { /* Raw filter request - no window function. */
cristy33b1c162010-01-23 22:51:51 +0000820 filter_type=(FilterTypes) option;
821 window_type=BoxFilter;
822 }
823 if (option == LanczosFilter)
anthony61b5ddd2010-10-05 02:33:31 +0000824 { /* Lanczos is not a real filter but a self windowing Sinc/Jinc. */
825 filter_type=cylindrical != MagickFalse ? JincFilter : Lanczos2DFilter;
anthony48f77622010-10-03 14:32:31 +0000826 window_type=cylindrical != MagickFalse ? JincFilter : SincFastFilter;
cristy33b1c162010-01-23 22:51:51 +0000827 }
nicolas07bac812010-09-19 18:47:02 +0000828 /* Filter override with a specific window function. */
cristy33b1c162010-01-23 22:51:51 +0000829 artifact=GetImageArtifact(image,"filter:window");
830 if (artifact != (const char *) NULL)
831 {
cristy9af9b5d2010-08-15 17:04:28 +0000832 option=ParseMagickOption(MagickFilterOptions,MagickFalse,artifact);
cristy33b1c162010-01-23 22:51:51 +0000833 if ((UndefinedFilter < option) && (option < SentinelFilter))
834 {
835 if (option != LanczosFilter)
836 window_type=(FilterTypes) option;
cristy9af9b5d2010-08-15 17:04:28 +0000837 else
anthony48f77622010-10-03 14:32:31 +0000838 window_type=cylindrical != MagickFalse ? JincFilter :
cristy03dbbd22010-09-19 23:04:47 +0000839 SincFastFilter;
cristy9af9b5d2010-08-15 17:04:28 +0000840 }
cristy33b1c162010-01-23 22:51:51 +0000841 }
cristy3ed852e2009-09-05 21:47:34 +0000842 }
cristy33b1c162010-01-23 22:51:51 +0000843 else
844 {
anthony48f77622010-10-03 14:32:31 +0000845 /* Window specified, but no filter function? Assume Sinc/Jinc. */
cristy33b1c162010-01-23 22:51:51 +0000846 artifact=GetImageArtifact(image,"filter:window");
847 if (artifact != (const char *) NULL)
848 {
849 option=ParseMagickOption(MagickFilterOptions,MagickFalse,
850 artifact);
851 if ((UndefinedFilter < option) && (option < SentinelFilter))
852 {
anthony61b5ddd2010-10-05 02:33:31 +0000853 filter_type=cylindrical != MagickFalse ?
854 JincFilter : SincFastFilter;
cristy19eb6412010-04-23 14:42:29 +0000855 window_type=(FilterTypes) option;
cristy33b1c162010-01-23 22:51:51 +0000856 }
857 }
858 }
nicolas07bac812010-09-19 18:47:02 +0000859 /* Assign the real functions to use for the filters selected. */
cristy33b1c162010-01-23 22:51:51 +0000860 resize_filter->filter=filters[filter_type].function;
anthony61b5ddd2010-10-05 02:33:31 +0000861 resize_filter->support=filters[filter_type].lobes;
cristy33b1c162010-01-23 22:51:51 +0000862 resize_filter->window=filters[window_type].function;
863 resize_filter->scale=filters[window_type].scale;
cristy3ed852e2009-09-05 21:47:34 +0000864 resize_filter->signature=MagickSignature;
anthony61b5ddd2010-10-05 02:33:31 +0000865
anthony10b8bc82010-10-02 12:48:46 +0000866 /* Filter blur -- scaling both filter and support window. */
867 resize_filter->blur=blur;
868 artifact=GetImageArtifact(image,"filter:blur");
869 if (artifact != (const char *) NULL)
870 resize_filter->blur=StringToDouble(artifact);
871 if (resize_filter->blur < MagickEpsilon)
872 resize_filter->blur=(MagickRealType) MagickEpsilon;
873
874 if (cylindrical != MagickFalse)
875 switch (filter_type)
876 {
877 case PointFilter:
878 case BoxFilter:
879 /* Support for Cylindrical Box should be sqrt(2)/2 */
cristy1c9bb452010-10-03 16:48:33 +0000880 resize_filter->support=(MagickRealType) MagickSQ1_2;
anthony10b8bc82010-10-02 12:48:46 +0000881 break;
882 case GaussianFilter:
883 /* Cylindrical Gaussian should have a sigma of sqrt(2)/2
884 * and not the default sigma of 1/2 - so use blur to enlarge
885 * and adjust support so actual practical support = 2.0 by default
886 */
887 resize_filter->blur *= MagickSQ2;
cristy1c9bb452010-10-03 16:48:33 +0000888 resize_filter->support = (MagickRealType) MagickSQ2; /* which times blur => 2.0 */
anthony10b8bc82010-10-02 12:48:46 +0000889 break;
anthony61b5ddd2010-10-05 02:33:31 +0000890 case Lanczos2DFilter:
nicolasddce59c2010-10-06 20:50:33 +0000891 /* Special 2-lobed cylindrical Jinc-Jinc filter with a special
892 * "blur" adjustment (actually, a shortening of the
893 * support). To be used as the default filter for EWA
nicolasa1017812010-10-05 06:41:03 +0000894 * Resampling and Distorts.
nicolas6237c462010-10-05 06:11:49 +0000895 *
896 * Derivation: Set the scaling s=1/blur of the Lanczos2D
897 * filter function so that
nicolasddce59c2010-10-06 20:50:33 +0000898 * Lanczos2D(s)=-2*Lanczos2D(s*sqrt(2))-Lanczos2D(s*2).
nicolas6237c462010-10-05 06:11:49 +0000899 *
nicolasddce59c2010-10-06 20:50:33 +0000900 * This choice ensures that the height of single vertical line
901 * is exactly preserved when "no-op" (the "identity
902 * geometrical transformation) is applied to the image. It
903 * also ensures that, with no-op, the nearest two columns (one
904 * on the left and one on the right) are not changed (they are
905 * kept to zero). The only side-effect of no-op on a single
906 * vertical line is a "ripple" in the two columns located two
907 * pixel away. The size of this ripple, for a unit column
908 * (constant 1), is -5.86142e-4 (essentially non-existant).
909 * In addition, with this choice of "r-scaling," the high
910 * frequency checkerboard mode, as well as the high frequency
911 * vertical and horizontal stripe modes are slightly damped.
nicolas6237c462010-10-05 06:11:49 +0000912 *
nicolasddce59c2010-10-06 20:50:33 +0000913 * Derived by Nicolas Robidoux of Laurentian University.
anthony61b5ddd2010-10-05 02:33:31 +0000914 */
nicolas6237c462010-10-05 06:11:49 +0000915 resize_filter->blur *= (MagickRealType) 0.958033808;
anthony81b8bf92010-10-02 13:54:34 +0000916 default:
917 break;
anthony10b8bc82010-10-02 12:48:46 +0000918 }
anthony61b5ddd2010-10-05 02:33:31 +0000919 else
920 switch (filter_type)
921 {
922 case Lanczos2DFilter:
923 /* depromote to a 2-lobe Sinc-Sinc for orthoginal use */
924 resize_filter->filter=SincFast;
925 break;
926 default:
927 break;
928 }
929
anthony2d9b8b52010-09-14 08:31:07 +0000930 /* Filter support overrides. */
cristy3ed852e2009-09-05 21:47:34 +0000931 artifact=GetImageArtifact(image,"filter:lobes");
cristy33b1c162010-01-23 22:51:51 +0000932 if (artifact != (const char *) NULL)
933 {
cristybb503372010-05-27 20:51:26 +0000934 ssize_t
cristy33b1c162010-01-23 22:51:51 +0000935 lobes;
936
cristy96b16132010-08-29 17:19:52 +0000937 lobes=(ssize_t) StringToLong(artifact);
cristy33b1c162010-01-23 22:51:51 +0000938 if (lobes < 1)
939 lobes=1;
940 resize_filter->support=(MagickRealType) lobes;
cristy3ed852e2009-09-05 21:47:34 +0000941 }
anthony61b5ddd2010-10-05 02:33:31 +0000942 /* convert Jinc lobes to a real support value */
943 if (resize_filter->filter == Jinc)
944 {
945 if (resize_filter->support > 16)
946 resize_filter->support=jinc_zeros[15]; /* largest entry in table */
947 else
948 resize_filter->support = jinc_zeros[((long)resize_filter->support)-1];
949 }
950 /* expert override of the support setting */
cristy3ed852e2009-09-05 21:47:34 +0000951 artifact=GetImageArtifact(image,"filter:support");
952 if (artifact != (const char *) NULL)
cristy33b1c162010-01-23 22:51:51 +0000953 resize_filter->support=fabs(StringToDouble(artifact));
954 /*
nicolas07bac812010-09-19 18:47:02 +0000955 Scale windowing function separatally to the support 'clipping'
956 window that calling operator is planning to actually use. (Expert
957 override)
cristy3ed852e2009-09-05 21:47:34 +0000958 */
anthony55f12332010-09-10 01:13:02 +0000959 resize_filter->window_support=resize_filter->support; /* default */
cristy3ed852e2009-09-05 21:47:34 +0000960 artifact=GetImageArtifact(image,"filter:win-support");
961 if (artifact != (const char *) NULL)
cristy33b1c162010-01-23 22:51:51 +0000962 resize_filter->window_support=fabs(StringToDouble(artifact));
963 /*
anthony1f90a6b2010-09-14 08:56:31 +0000964 Adjust window function scaling to the windowing support for
965 weighting function. This avoids a division on every filter call.
anthony55f12332010-09-10 01:13:02 +0000966 */
967 resize_filter->scale /= resize_filter->window_support;
968 /*
nicolas07bac812010-09-19 18:47:02 +0000969 Set Cubic Spline B,C values, calculate Cubic coefficients.
cristy33b1c162010-01-23 22:51:51 +0000970 */
cristy3ed852e2009-09-05 21:47:34 +0000971 B=0.0;
972 C=0.0;
cristy33b1c162010-01-23 22:51:51 +0000973 if ((filters[filter_type].function == CubicBC) ||
974 (filters[window_type].function == CubicBC))
975 {
anthony2d9b8b52010-09-14 08:31:07 +0000976 B=filters[filter_type].B;
977 C=filters[filter_type].C;
978 if (filters[window_type].function == CubicBC)
cristy33b1c162010-01-23 22:51:51 +0000979 {
anthony2d9b8b52010-09-14 08:31:07 +0000980 B=filters[window_type].B;
981 C=filters[window_type].C;
cristy33b1c162010-01-23 22:51:51 +0000982 }
cristy33b1c162010-01-23 22:51:51 +0000983 artifact=GetImageArtifact(image,"filter:b");
cristy3ed852e2009-09-05 21:47:34 +0000984 if (artifact != (const char *) NULL)
cristy33b1c162010-01-23 22:51:51 +0000985 {
986 B=StringToDouble(artifact);
nicolas07bac812010-09-19 18:47:02 +0000987 C=(1.0-B)/2.0; /* Calculate C as if it is a Keys cubic filter. */
cristy33b1c162010-01-23 22:51:51 +0000988 artifact=GetImageArtifact(image,"filter:c");
989 if (artifact != (const char *) NULL)
990 C=StringToDouble(artifact);
991 }
992 else
993 {
994 artifact=GetImageArtifact(image,"filter:c");
995 if (artifact != (const char *) NULL)
996 {
997 C=StringToDouble(artifact);
nicolas07bac812010-09-19 18:47:02 +0000998 B=1.0-2.0*C; /* Calculate B as if it is a Keys cubic filter. */
cristy33b1c162010-01-23 22:51:51 +0000999 }
1000 }
1001 /*
nicolas07bac812010-09-19 18:47:02 +00001002 Convert B,C values into Cubic Coefficents. See CubicBC().
cristy33b1c162010-01-23 22:51:51 +00001003 */
1004 resize_filter->cubic[0]=(6.0-2.0*B)/6.0;
cristy3ed852e2009-09-05 21:47:34 +00001005 resize_filter->cubic[1]=0.0;
cristy33b1c162010-01-23 22:51:51 +00001006 resize_filter->cubic[2]=(-18.0+12.0*B+6.0*C)/6.0;
1007 resize_filter->cubic[3]=(12.0-9.0*B-6.0*C)/6.0;
1008 resize_filter->cubic[4]=(8.0*B+24.0*C)/6.0;
1009 resize_filter->cubic[5]=(-12.0*B-48.0*C)/6.0;
1010 resize_filter->cubic[6]=(6.0*B+30.0*C)/6.0;
nicolas58c77cd2010-09-20 15:51:39 +00001011 resize_filter->cubic[7]=(-B-6.0*C)/6.0;
cristy3ed852e2009-09-05 21:47:34 +00001012 }
anthony55f12332010-09-10 01:13:02 +00001013 /*
nicolas07bac812010-09-19 18:47:02 +00001014 Expert Option Request for verbose details of the resulting filter.
anthony55f12332010-09-10 01:13:02 +00001015 */
anthonye06e4c12010-09-15 04:03:52 +00001016#if defined(MAGICKCORE_OPENMP_SUPPORT)
anthony61b5ddd2010-10-05 02:33:31 +00001017 if( GetOpenMPThreadId() == 0 ) {
anthonye06e4c12010-09-15 04:03:52 +00001018#endif
1019 artifact=GetImageArtifact(image,"filter:verbose");
1020 if (artifact != (const char *) NULL)
1021 {
1022 double
1023 support,
1024 x;
cristy3ed852e2009-09-05 21:47:34 +00001025
nicolas07bac812010-09-19 18:47:02 +00001026 /*
anthony463be1d2010-09-26 01:07:36 +00001027 Set the weighting function properly when the weighting
1028 function may not exactly match the filter of the same name.
1029 EG: a Point filter really uses a Box weighting function
1030 with a different support than is typically used.
1031
anthonye06e4c12010-09-15 04:03:52 +00001032 */
anthony463be1d2010-09-26 01:07:36 +00001033 if (resize_filter->filter == Box) filter_type=BoxFilter;
1034 if (resize_filter->filter == Sinc) filter_type=SincFilter;
1035 if (resize_filter->filter == SincFast) filter_type=SincFastFilter;
anthony61b5ddd2010-10-05 02:33:31 +00001036 if (resize_filter->filter == Jinc) filter_type=JincFilter;
anthony463be1d2010-09-26 01:07:36 +00001037 if (resize_filter->filter == CubicBC) filter_type=CubicFilter;
anthonye06e4c12010-09-15 04:03:52 +00001038 /*
nicolas07bac812010-09-19 18:47:02 +00001039 Report Filter Details.
anthonye06e4c12010-09-15 04:03:52 +00001040 */
cristy03dbbd22010-09-19 23:04:47 +00001041 support=GetResizeFilterSupport(resize_filter); /* support range */
anthony61b5ddd2010-10-05 02:33:31 +00001042 (void) fprintf(stdout,"# Resize Filter (for graphing)\n#\n");
cristy03dbbd22010-09-19 23:04:47 +00001043 (void) fprintf(stdout,"# filter = %s\n",MagickOptionToMnemonic(
1044 MagickFilterOptions,filter_type));
1045 (void) fprintf(stdout,"# window = %s\n",MagickOptionToMnemonic(
anthony463be1d2010-09-26 01:07:36 +00001046 MagickFilterOptions, window_type));
cristy03dbbd22010-09-19 23:04:47 +00001047 (void) fprintf(stdout,"# support = %.*g\n",GetMagickPrecision(),
anthony463be1d2010-09-26 01:07:36 +00001048 (double) resize_filter->support);
cristy03dbbd22010-09-19 23:04:47 +00001049 (void) fprintf(stdout,"# win-support = %.*g\n",GetMagickPrecision(),
anthony463be1d2010-09-26 01:07:36 +00001050 (double) resize_filter->window_support);
cristy03dbbd22010-09-19 23:04:47 +00001051 (void) fprintf(stdout,"# blur = %.*g\n",GetMagickPrecision(),
1052 (double) resize_filter->blur);
anthony463be1d2010-09-26 01:07:36 +00001053 (void) fprintf(stdout,"# blurred_support = %.*g\n",GetMagickPrecision(),
cristy03dbbd22010-09-19 23:04:47 +00001054 (double) support);
1055 (void) fprintf(stdout,"# B,C = %.*g,%.*g\n",GetMagickPrecision(),
1056 (double) B,GetMagickPrecision(),(double) C);
anthony61b5ddd2010-10-05 02:33:31 +00001057 (void) fprintf(stdout,"\n");
anthonye06e4c12010-09-15 04:03:52 +00001058 /*
nicolas07bac812010-09-19 18:47:02 +00001059 Output values of resulting filter graph -- for graphing
1060 filter result.
anthonye06e4c12010-09-15 04:03:52 +00001061 */
1062 for (x=0.0; x <= support; x+=0.01f)
cristy03dbbd22010-09-19 23:04:47 +00001063 (void) fprintf(stdout,"%5.2lf\t%.*g\n",x,GetMagickPrecision(),
1064 (double) GetResizeFilterWeight(resize_filter,x));
nicolas07bac812010-09-19 18:47:02 +00001065 /* A final value so gnuplot can graph the 'stop' properly. */
cristy03dbbd22010-09-19 23:04:47 +00001066 (void) fprintf(stdout,"%5.2lf\t%.*g\n",support,GetMagickPrecision(),
1067 0.0);
anthonye06e4c12010-09-15 04:03:52 +00001068 }
1069#if defined(MAGICKCORE_OPENMP_SUPPORT)
anthony61b5ddd2010-10-05 02:33:31 +00001070 }
anthonye06e4c12010-09-15 04:03:52 +00001071#endif
cristy3ed852e2009-09-05 21:47:34 +00001072 return(resize_filter);
1073}
1074
1075/*
1076%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1077% %
1078% %
1079% %
1080% A d a p t i v e R e s i z e I m a g e %
1081% %
1082% %
1083% %
1084%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1085%
1086% AdaptiveResizeImage() adaptively resize image with pixel resampling.
1087%
1088% The format of the AdaptiveResizeImage method is:
1089%
cristy9af9b5d2010-08-15 17:04:28 +00001090% Image *AdaptiveResizeImage(const Image *image,const size_t columns,
1091% const size_t rows,ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +00001092%
1093% A description of each parameter follows:
1094%
1095% o image: the image.
1096%
1097% o columns: the number of columns in the resized image.
1098%
1099% o rows: the number of rows in the resized image.
1100%
1101% o exception: return any errors or warnings in this structure.
1102%
1103*/
1104MagickExport Image *AdaptiveResizeImage(const Image *image,
cristybb503372010-05-27 20:51:26 +00001105 const size_t columns,const size_t rows,ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +00001106{
1107#define AdaptiveResizeImageTag "Resize/Image"
1108
cristyc4c8d132010-01-07 01:58:38 +00001109 CacheView
1110 *resize_view;
1111
cristy3ed852e2009-09-05 21:47:34 +00001112 Image
1113 *resize_image;
1114
cristy3ed852e2009-09-05 21:47:34 +00001115 MagickBooleanType
1116 proceed;
1117
1118 MagickPixelPacket
1119 pixel;
1120
1121 PointInfo
1122 offset;
1123
1124 ResampleFilter
1125 *resample_filter;
1126
cristy9af9b5d2010-08-15 17:04:28 +00001127 ssize_t
1128 y;
1129
cristy3ed852e2009-09-05 21:47:34 +00001130 /*
1131 Adaptively resize image.
1132 */
1133 assert(image != (const Image *) NULL);
1134 assert(image->signature == MagickSignature);
1135 if (image->debug != MagickFalse)
1136 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
1137 assert(exception != (ExceptionInfo *) NULL);
1138 assert(exception->signature == MagickSignature);
1139 if ((columns == 0) || (rows == 0))
1140 return((Image *) NULL);
1141 if ((columns == image->columns) && (rows == image->rows))
1142 return(CloneImage(image,0,0,MagickTrue,exception));
1143 resize_image=CloneImage(image,columns,rows,MagickTrue,exception);
1144 if (resize_image == (Image *) NULL)
1145 return((Image *) NULL);
1146 if (SetImageStorageClass(resize_image,DirectClass) == MagickFalse)
1147 {
1148 InheritException(exception,&resize_image->exception);
1149 resize_image=DestroyImage(resize_image);
1150 return((Image *) NULL);
1151 }
1152 GetMagickPixelPacket(image,&pixel);
1153 resample_filter=AcquireResampleFilter(image,exception);
cristy0c7e9eb2010-09-30 14:23:37 +00001154 (void) SetResampleFilter(resample_filter,PointFilter,1.0);
anthonyccf239d2010-09-30 04:23:46 +00001155 (void) SetResampleFilterInterpolateMethod(resample_filter,
cristy0c7e9eb2010-09-30 14:23:37 +00001156 MeshInterpolatePixel);
cristy3ed852e2009-09-05 21:47:34 +00001157 resize_view=AcquireCacheView(resize_image);
cristybb503372010-05-27 20:51:26 +00001158 for (y=0; y < (ssize_t) resize_image->rows; y++)
cristy3ed852e2009-09-05 21:47:34 +00001159 {
1160 register IndexPacket
cristyc47d1f82009-11-26 01:44:43 +00001161 *restrict resize_indexes;
cristy3ed852e2009-09-05 21:47:34 +00001162
cristybb503372010-05-27 20:51:26 +00001163 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +00001164 x;
1165
1166 register PixelPacket
cristyc47d1f82009-11-26 01:44:43 +00001167 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +00001168
1169 q=QueueCacheViewAuthenticPixels(resize_view,0,y,resize_image->columns,1,
1170 exception);
1171 if (q == (PixelPacket *) NULL)
1172 break;
1173 resize_indexes=GetCacheViewAuthenticIndexQueue(resize_view);
1174 offset.y=((MagickRealType) y*image->rows/resize_image->rows);
cristybb503372010-05-27 20:51:26 +00001175 for (x=0; x < (ssize_t) resize_image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +00001176 {
1177 offset.x=((MagickRealType) x*image->columns/resize_image->columns);
1178 (void) ResamplePixelColor(resample_filter,offset.x-0.5,offset.y-0.5,
1179 &pixel);
1180 SetPixelPacket(resize_image,&pixel,q,resize_indexes+x);
1181 q++;
1182 }
1183 if (SyncCacheViewAuthenticPixels(resize_view,exception) == MagickFalse)
1184 break;
cristy96b16132010-08-29 17:19:52 +00001185 proceed=SetImageProgress(image,AdaptiveResizeImageTag,(MagickOffsetType) y,
1186 image->rows);
cristy3ed852e2009-09-05 21:47:34 +00001187 if (proceed == MagickFalse)
1188 break;
1189 }
1190 resample_filter=DestroyResampleFilter(resample_filter);
1191 resize_view=DestroyCacheView(resize_view);
1192 return(resize_image);
1193}
1194
1195/*
1196%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1197% %
1198% %
1199% %
1200+ B e s s e l O r d e r O n e %
1201% %
1202% %
1203% %
1204%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1205%
1206% BesselOrderOne() computes the Bessel function of x of the first kind of
anthony48f77622010-10-03 14:32:31 +00001207% order 0. This is used to create the Jinc() filter function below.
cristy3ed852e2009-09-05 21:47:34 +00001208%
1209% Reduce x to |x| since j1(x)= -j1(-x), and for x in (0,8]
1210%
1211% j1(x) = x*j1(x);
1212%
1213% For x in (8,inf)
1214%
1215% j1(x) = sqrt(2/(pi*x))*(p1(x)*cos(x1)-q1(x)*sin(x1))
1216%
1217% where x1 = x-3*pi/4. Compute sin(x1) and cos(x1) as follow:
1218%
1219% cos(x1) = cos(x)cos(3pi/4)+sin(x)sin(3pi/4)
1220% = 1/sqrt(2) * (sin(x) - cos(x))
1221% sin(x1) = sin(x)cos(3pi/4)-cos(x)sin(3pi/4)
1222% = -1/sqrt(2) * (sin(x) + cos(x))
1223%
1224% The format of the BesselOrderOne method is:
1225%
1226% MagickRealType BesselOrderOne(MagickRealType x)
1227%
1228% A description of each parameter follows:
1229%
1230% o x: MagickRealType value.
1231%
1232*/
1233
1234#undef I0
1235static MagickRealType I0(MagickRealType x)
1236{
1237 MagickRealType
1238 sum,
1239 t,
1240 y;
1241
cristybb503372010-05-27 20:51:26 +00001242 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +00001243 i;
1244
1245 /*
1246 Zeroth order Bessel function of the first kind.
1247 */
1248 sum=1.0;
1249 y=x*x/4.0;
1250 t=y;
1251 for (i=2; t > MagickEpsilon; i++)
1252 {
1253 sum+=t;
1254 t*=y/((MagickRealType) i*i);
1255 }
1256 return(sum);
1257}
1258
1259#undef J1
1260static MagickRealType J1(MagickRealType x)
1261{
1262 MagickRealType
1263 p,
1264 q;
1265
cristybb503372010-05-27 20:51:26 +00001266 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +00001267 i;
1268
1269 static const double
1270 Pone[] =
1271 {
1272 0.581199354001606143928050809e+21,
1273 -0.6672106568924916298020941484e+20,
1274 0.2316433580634002297931815435e+19,
1275 -0.3588817569910106050743641413e+17,
1276 0.2908795263834775409737601689e+15,
1277 -0.1322983480332126453125473247e+13,
1278 0.3413234182301700539091292655e+10,
1279 -0.4695753530642995859767162166e+7,
1280 0.270112271089232341485679099e+4
1281 },
1282 Qone[] =
1283 {
1284 0.11623987080032122878585294e+22,
1285 0.1185770712190320999837113348e+20,
1286 0.6092061398917521746105196863e+17,
1287 0.2081661221307607351240184229e+15,
1288 0.5243710262167649715406728642e+12,
1289 0.1013863514358673989967045588e+10,
1290 0.1501793594998585505921097578e+7,
1291 0.1606931573481487801970916749e+4,
1292 0.1e+1
1293 };
1294
1295 p=Pone[8];
1296 q=Qone[8];
1297 for (i=7; i >= 0; i--)
1298 {
1299 p=p*x*x+Pone[i];
1300 q=q*x*x+Qone[i];
1301 }
1302 return(p/q);
1303}
1304
1305#undef P1
1306static MagickRealType P1(MagickRealType x)
1307{
1308 MagickRealType
1309 p,
1310 q;
1311
cristybb503372010-05-27 20:51:26 +00001312 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +00001313 i;
1314
1315 static const double
1316 Pone[] =
1317 {
1318 0.352246649133679798341724373e+5,
1319 0.62758845247161281269005675e+5,
1320 0.313539631109159574238669888e+5,
1321 0.49854832060594338434500455e+4,
1322 0.2111529182853962382105718e+3,
1323 0.12571716929145341558495e+1
1324 },
1325 Qone[] =
1326 {
1327 0.352246649133679798068390431e+5,
1328 0.626943469593560511888833731e+5,
1329 0.312404063819041039923015703e+5,
1330 0.4930396490181088979386097e+4,
1331 0.2030775189134759322293574e+3,
1332 0.1e+1
1333 };
1334
1335 p=Pone[5];
1336 q=Qone[5];
1337 for (i=4; i >= 0; i--)
1338 {
1339 p=p*(8.0/x)*(8.0/x)+Pone[i];
1340 q=q*(8.0/x)*(8.0/x)+Qone[i];
1341 }
1342 return(p/q);
1343}
1344
1345#undef Q1
1346static MagickRealType Q1(MagickRealType x)
1347{
1348 MagickRealType
1349 p,
1350 q;
1351
cristybb503372010-05-27 20:51:26 +00001352 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +00001353 i;
1354
1355 static const double
1356 Pone[] =
1357 {
1358 0.3511751914303552822533318e+3,
1359 0.7210391804904475039280863e+3,
1360 0.4259873011654442389886993e+3,
1361 0.831898957673850827325226e+2,
1362 0.45681716295512267064405e+1,
1363 0.3532840052740123642735e-1
1364 },
1365 Qone[] =
1366 {
1367 0.74917374171809127714519505e+4,
1368 0.154141773392650970499848051e+5,
1369 0.91522317015169922705904727e+4,
1370 0.18111867005523513506724158e+4,
1371 0.1038187585462133728776636e+3,
1372 0.1e+1
1373 };
1374
1375 p=Pone[5];
1376 q=Qone[5];
1377 for (i=4; i >= 0; i--)
1378 {
1379 p=p*(8.0/x)*(8.0/x)+Pone[i];
1380 q=q*(8.0/x)*(8.0/x)+Qone[i];
1381 }
1382 return(p/q);
1383}
1384
1385static MagickRealType BesselOrderOne(MagickRealType x)
1386{
1387 MagickRealType
1388 p,
1389 q;
1390
1391 if (x == 0.0)
1392 return(0.0);
1393 p=x;
1394 if (x < 0.0)
1395 x=(-x);
1396 if (x < 8.0)
1397 return(p*J1(x));
1398 q=sqrt((double) (2.0/(MagickPI*x)))*(P1(x)*(1.0/sqrt(2.0)*(sin((double) x)-
1399 cos((double) x)))-8.0/x*Q1(x)*(-1.0/sqrt(2.0)*(sin((double) x)+
1400 cos((double) x))));
1401 if (p < 0.0)
1402 q=(-q);
1403 return(q);
1404}
1405
1406/*
1407%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1408% %
1409% %
1410% %
1411+ D e s t r o y R e s i z e F i l t e r %
1412% %
1413% %
1414% %
1415%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1416%
1417% DestroyResizeFilter() destroy the resize filter.
1418%
cristya2ffd7e2010-03-10 20:50:30 +00001419% The format of the DestroyResizeFilter method is:
cristy3ed852e2009-09-05 21:47:34 +00001420%
1421% ResizeFilter *DestroyResizeFilter(ResizeFilter *resize_filter)
1422%
1423% A description of each parameter follows:
1424%
1425% o resize_filter: the resize filter.
1426%
1427*/
1428MagickExport ResizeFilter *DestroyResizeFilter(ResizeFilter *resize_filter)
1429{
1430 assert(resize_filter != (ResizeFilter *) NULL);
1431 assert(resize_filter->signature == MagickSignature);
1432 resize_filter->signature=(~MagickSignature);
1433 resize_filter=(ResizeFilter *) RelinquishMagickMemory(resize_filter);
1434 return(resize_filter);
1435}
1436
1437/*
1438%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1439% %
1440% %
1441% %
1442+ G e t R e s i z e F i l t e r S u p p o r t %
1443% %
1444% %
1445% %
1446%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1447%
1448% GetResizeFilterSupport() return the current support window size for this
1449% filter. Note that this may have been enlarged by filter:blur factor.
1450%
1451% The format of the GetResizeFilterSupport method is:
1452%
1453% MagickRealType GetResizeFilterSupport(const ResizeFilter *resize_filter)
1454%
1455% A description of each parameter follows:
1456%
1457% o filter: Image filter to use.
1458%
1459*/
1460MagickExport MagickRealType GetResizeFilterSupport(
1461 const ResizeFilter *resize_filter)
1462{
1463 assert(resize_filter != (ResizeFilter *) NULL);
1464 assert(resize_filter->signature == MagickSignature);
1465 return(resize_filter->support*resize_filter->blur);
1466}
1467
1468/*
1469%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1470% %
1471% %
1472% %
1473+ G e t R e s i z e F i l t e r W e i g h t %
1474% %
1475% %
1476% %
1477%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1478%
1479% GetResizeFilterWeight evaluates the specified resize filter at the point x
1480% which usally lies between zero and the filters current 'support' and
1481% returns the weight of the filter function at that point.
1482%
1483% The format of the GetResizeFilterWeight method is:
1484%
1485% MagickRealType GetResizeFilterWeight(const ResizeFilter *resize_filter,
1486% const MagickRealType x)
1487%
1488% A description of each parameter follows:
1489%
1490% o filter: the filter type.
1491%
1492% o x: the point.
1493%
1494*/
1495MagickExport MagickRealType GetResizeFilterWeight(
1496 const ResizeFilter *resize_filter,const MagickRealType x)
1497{
1498 MagickRealType
cristyb8385862010-09-26 01:27:51 +00001499 scale,
1500 x_blur;
cristy3ed852e2009-09-05 21:47:34 +00001501
1502 /*
1503 Windowing function - scale the weighting filter by this amount.
1504 */
1505 assert(resize_filter != (ResizeFilter *) NULL);
1506 assert(resize_filter->signature == MagickSignature);
anthony55f12332010-09-10 01:13:02 +00001507 x_blur=fabs((double) x)/resize_filter->blur; /* X offset with blur scaling */
cristy3ed852e2009-09-05 21:47:34 +00001508 if ((resize_filter->window_support < MagickEpsilon) ||
1509 (resize_filter->window == Box))
anthony55f12332010-09-10 01:13:02 +00001510 scale=1.0; /* Point or Box Filter -- avoid division by zero */
cristy3ed852e2009-09-05 21:47:34 +00001511 else
1512 {
anthony55f12332010-09-10 01:13:02 +00001513 scale=resize_filter->scale;
1514 scale=resize_filter->window(x_blur*scale,resize_filter);
cristy3ed852e2009-09-05 21:47:34 +00001515 }
anthony55f12332010-09-10 01:13:02 +00001516 return(scale*resize_filter->filter(x_blur,resize_filter));
cristy3ed852e2009-09-05 21:47:34 +00001517}
1518
1519/*
1520%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1521% %
1522% %
1523% %
1524% M a g n i f y I m a g e %
1525% %
1526% %
1527% %
1528%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1529%
1530% MagnifyImage() is a convenience method that scales an image proportionally
1531% to twice its size.
1532%
1533% The format of the MagnifyImage method is:
1534%
1535% Image *MagnifyImage(const Image *image,ExceptionInfo *exception)
1536%
1537% A description of each parameter follows:
1538%
1539% o image: the image.
1540%
1541% o exception: return any errors or warnings in this structure.
1542%
1543*/
1544MagickExport Image *MagnifyImage(const Image *image,ExceptionInfo *exception)
1545{
1546 Image
1547 *magnify_image;
1548
1549 assert(image != (Image *) NULL);
1550 assert(image->signature == MagickSignature);
1551 if (image->debug != MagickFalse)
1552 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
1553 assert(exception != (ExceptionInfo *) NULL);
1554 assert(exception->signature == MagickSignature);
1555 magnify_image=ResizeImage(image,2*image->columns,2*image->rows,CubicFilter,
1556 1.0,exception);
1557 return(magnify_image);
1558}
1559
1560/*
1561%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1562% %
1563% %
1564% %
1565% M i n i f y I m a g e %
1566% %
1567% %
1568% %
1569%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1570%
1571% MinifyImage() is a convenience method that scales an image proportionally
1572% to half its size.
1573%
1574% The format of the MinifyImage method is:
1575%
1576% Image *MinifyImage(const Image *image,ExceptionInfo *exception)
1577%
1578% A description of each parameter follows:
1579%
1580% o image: the image.
1581%
1582% o exception: return any errors or warnings in this structure.
1583%
1584*/
1585MagickExport Image *MinifyImage(const Image *image,ExceptionInfo *exception)
1586{
1587 Image
1588 *minify_image;
1589
1590 assert(image != (Image *) NULL);
1591 assert(image->signature == MagickSignature);
1592 if (image->debug != MagickFalse)
1593 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
1594 assert(exception != (ExceptionInfo *) NULL);
1595 assert(exception->signature == MagickSignature);
1596 minify_image=ResizeImage(image,image->columns/2,image->rows/2,CubicFilter,
1597 1.0,exception);
1598 return(minify_image);
1599}
1600
1601/*
1602%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1603% %
1604% %
1605% %
1606% R e s a m p l e I m a g e %
1607% %
1608% %
1609% %
1610%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1611%
1612% ResampleImage() resize image in terms of its pixel size, so that when
1613% displayed at the given resolution it will be the same size in terms of
1614% real world units as the original image at the original resolution.
1615%
1616% The format of the ResampleImage method is:
1617%
1618% Image *ResampleImage(Image *image,const double x_resolution,
1619% const double y_resolution,const FilterTypes filter,const double blur,
1620% ExceptionInfo *exception)
1621%
1622% A description of each parameter follows:
1623%
1624% o image: the image to be resized to fit the given resolution.
1625%
1626% o x_resolution: the new image x resolution.
1627%
1628% o y_resolution: the new image y resolution.
1629%
1630% o filter: Image filter to use.
1631%
1632% o blur: the blur factor where > 1 is blurry, < 1 is sharp.
1633%
1634*/
1635MagickExport Image *ResampleImage(const Image *image,const double x_resolution,
1636 const double y_resolution,const FilterTypes filter,const double blur,
1637 ExceptionInfo *exception)
1638{
1639#define ResampleImageTag "Resample/Image"
1640
1641 Image
1642 *resample_image;
1643
cristybb503372010-05-27 20:51:26 +00001644 size_t
cristy3ed852e2009-09-05 21:47:34 +00001645 height,
1646 width;
1647
1648 /*
1649 Initialize sampled image attributes.
1650 */
1651 assert(image != (const Image *) NULL);
1652 assert(image->signature == MagickSignature);
1653 if (image->debug != MagickFalse)
1654 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
1655 assert(exception != (ExceptionInfo *) NULL);
1656 assert(exception->signature == MagickSignature);
cristy9af9b5d2010-08-15 17:04:28 +00001657 width=(size_t) (x_resolution*image->columns/(image->x_resolution == 0.0 ?
1658 72.0 : image->x_resolution)+0.5);
1659 height=(size_t) (y_resolution*image->rows/(image->y_resolution == 0.0 ?
1660 72.0 : image->y_resolution)+0.5);
cristy3ed852e2009-09-05 21:47:34 +00001661 resample_image=ResizeImage(image,width,height,filter,blur,exception);
1662 if (resample_image != (Image *) NULL)
1663 {
1664 resample_image->x_resolution=x_resolution;
1665 resample_image->y_resolution=y_resolution;
1666 }
1667 return(resample_image);
1668}
1669#if defined(MAGICKCORE_LQR_DELEGATE)
1670
1671/*
1672%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1673% %
1674% %
1675% %
1676% L i q u i d R e s c a l e I m a g e %
1677% %
1678% %
1679% %
1680%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1681%
1682% LiquidRescaleImage() rescales image with seam carving.
1683%
1684% The format of the LiquidRescaleImage method is:
1685%
1686% Image *LiquidRescaleImage(const Image *image,
cristybb503372010-05-27 20:51:26 +00001687% const size_t columns,const size_t rows,
cristy3ed852e2009-09-05 21:47:34 +00001688% const double delta_x,const double rigidity,ExceptionInfo *exception)
1689%
1690% A description of each parameter follows:
1691%
1692% o image: the image.
1693%
1694% o columns: the number of columns in the rescaled image.
1695%
1696% o rows: the number of rows in the rescaled image.
1697%
1698% o delta_x: maximum seam transversal step (0 means straight seams).
1699%
1700% o rigidity: introduce a bias for non-straight seams (typically 0).
1701%
1702% o exception: return any errors or warnings in this structure.
1703%
1704*/
cristy9af9b5d2010-08-15 17:04:28 +00001705MagickExport Image *LiquidRescaleImage(const Image *image,const size_t columns,
1706 const size_t rows,const double delta_x,const double rigidity,
1707 ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +00001708{
1709#define LiquidRescaleImageTag "Rescale/Image"
1710
cristyc5c6f662010-09-22 14:23:02 +00001711 CacheView
1712 *rescale_view;
1713
cristy3ed852e2009-09-05 21:47:34 +00001714 const char
1715 *map;
1716
1717 guchar
1718 *packet;
1719
1720 Image
1721 *rescale_image;
1722
1723 int
1724 x,
1725 y;
1726
1727 LqrCarver
1728 *carver;
1729
1730 LqrRetVal
1731 lqr_status;
1732
1733 MagickBooleanType
1734 status;
1735
1736 MagickPixelPacket
1737 pixel;
1738
1739 unsigned char
1740 *pixels;
1741
1742 /*
1743 Liquid rescale image.
1744 */
1745 assert(image != (const Image *) NULL);
1746 assert(image->signature == MagickSignature);
1747 if (image->debug != MagickFalse)
1748 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
1749 assert(exception != (ExceptionInfo *) NULL);
1750 assert(exception->signature == MagickSignature);
1751 if ((columns == 0) || (rows == 0))
1752 return((Image *) NULL);
1753 if ((columns == image->columns) && (rows == image->rows))
1754 return(CloneImage(image,0,0,MagickTrue,exception));
1755 if ((columns <= 2) || (rows <= 2))
cristyd1bb3bc2010-09-07 00:43:58 +00001756 return(ResizeImage(image,columns,rows,image->filter,image->blur,exception));
cristy3ed852e2009-09-05 21:47:34 +00001757 if ((columns >= (2*image->columns)) || (rows >= (2*image->rows)))
1758 {
1759 Image
1760 *resize_image;
1761
cristybb503372010-05-27 20:51:26 +00001762 size_t
cristy3ed852e2009-09-05 21:47:34 +00001763 height,
1764 width;
1765
1766 /*
1767 Honor liquid resize size limitations.
1768 */
1769 for (width=image->columns; columns >= (2*width-1); width*=2);
1770 for (height=image->rows; rows >= (2*height-1); height*=2);
1771 resize_image=ResizeImage(image,width,height,image->filter,image->blur,
1772 exception);
1773 if (resize_image == (Image *) NULL)
1774 return((Image *) NULL);
1775 rescale_image=LiquidRescaleImage(resize_image,columns,rows,delta_x,
1776 rigidity,exception);
1777 resize_image=DestroyImage(resize_image);
1778 return(rescale_image);
1779 }
1780 map="RGB";
1781 if (image->matte == MagickFalse)
1782 map="RGBA";
1783 if (image->colorspace == CMYKColorspace)
1784 {
1785 map="CMYK";
1786 if (image->matte == MagickFalse)
1787 map="CMYKA";
1788 }
1789 pixels=(unsigned char *) AcquireQuantumMemory(image->columns,image->rows*
1790 strlen(map)*sizeof(*pixels));
1791 if (pixels == (unsigned char *) NULL)
1792 return((Image *) NULL);
1793 status=ExportImagePixels(image,0,0,image->columns,image->rows,map,CharPixel,
1794 pixels,exception);
1795 if (status == MagickFalse)
1796 {
1797 pixels=(unsigned char *) RelinquishMagickMemory(pixels);
1798 ThrowImageException(ResourceLimitError,"MemoryAllocationFailed");
1799 }
1800 carver=lqr_carver_new(pixels,image->columns,image->rows,strlen(map));
1801 if (carver == (LqrCarver *) NULL)
1802 {
1803 pixels=(unsigned char *) RelinquishMagickMemory(pixels);
1804 ThrowImageException(ResourceLimitError,"MemoryAllocationFailed");
1805 }
1806 lqr_status=lqr_carver_init(carver,(int) delta_x,rigidity);
1807 lqr_status=lqr_carver_resize(carver,columns,rows);
1808 rescale_image=CloneImage(image,lqr_carver_get_width(carver),
1809 lqr_carver_get_height(carver),MagickTrue,exception);
1810 if (rescale_image == (Image *) NULL)
1811 {
1812 pixels=(unsigned char *) RelinquishMagickMemory(pixels);
1813 return((Image *) NULL);
1814 }
1815 if (SetImageStorageClass(rescale_image,DirectClass) == MagickFalse)
1816 {
1817 InheritException(exception,&rescale_image->exception);
1818 rescale_image=DestroyImage(rescale_image);
1819 return((Image *) NULL);
1820 }
1821 GetMagickPixelPacket(rescale_image,&pixel);
1822 (void) lqr_carver_scan_reset(carver);
cristyc5c6f662010-09-22 14:23:02 +00001823 rescale_view=AcquireCacheView(rescale_image);
cristy3ed852e2009-09-05 21:47:34 +00001824 while (lqr_carver_scan(carver,&x,&y,&packet) != 0)
1825 {
1826 register IndexPacket
cristyc47d1f82009-11-26 01:44:43 +00001827 *restrict rescale_indexes;
cristy3ed852e2009-09-05 21:47:34 +00001828
1829 register PixelPacket
cristyc47d1f82009-11-26 01:44:43 +00001830 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +00001831
anthony22aad252010-09-23 06:59:07 +00001832 q=QueueCacheViewAuthenticPixels(rescale_view,x,y,1,1,exception);
cristy3ed852e2009-09-05 21:47:34 +00001833 if (q == (PixelPacket *) NULL)
1834 break;
cristyc5c6f662010-09-22 14:23:02 +00001835 rescale_indexes=GetCacheViewAuthenticIndexQueue(rescale_view);
cristy3ed852e2009-09-05 21:47:34 +00001836 pixel.red=QuantumRange*(packet[0]/255.0);
1837 pixel.green=QuantumRange*(packet[1]/255.0);
1838 pixel.blue=QuantumRange*(packet[2]/255.0);
1839 if (image->colorspace != CMYKColorspace)
1840 {
1841 if (image->matte == MagickFalse)
1842 pixel.opacity=QuantumRange*(packet[3]/255.0);
1843 }
1844 else
1845 {
1846 pixel.index=QuantumRange*(packet[3]/255.0);
1847 if (image->matte == MagickFalse)
1848 pixel.opacity=QuantumRange*(packet[4]/255.0);
1849 }
1850 SetPixelPacket(rescale_image,&pixel,q,rescale_indexes);
cristyc5c6f662010-09-22 14:23:02 +00001851 if (SyncCacheViewAuthenticPixels(rescale_view,exception) == MagickFalse)
cristy3ed852e2009-09-05 21:47:34 +00001852 break;
1853 }
cristyc5c6f662010-09-22 14:23:02 +00001854 rescale_view=DestroyCacheView(rescale_view);
cristy3ed852e2009-09-05 21:47:34 +00001855 /*
1856 Relinquish resources.
1857 */
1858 lqr_carver_destroy(carver);
1859 return(rescale_image);
1860}
1861#else
1862MagickExport Image *LiquidRescaleImage(const Image *image,
cristy9af9b5d2010-08-15 17:04:28 +00001863 const size_t magick_unused(columns),const size_t magick_unused(rows),
1864 const double magick_unused(delta_x),const double magick_unused(rigidity),
1865 ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +00001866{
1867 assert(image != (const Image *) NULL);
1868 assert(image->signature == MagickSignature);
1869 if (image->debug != MagickFalse)
1870 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
1871 assert(exception != (ExceptionInfo *) NULL);
1872 assert(exception->signature == MagickSignature);
1873 (void) ThrowMagickException(exception,GetMagickModule(),MissingDelegateError,
1874 "DelegateLibrarySupportNotBuiltIn","`%s' (LQR)",image->filename);
1875 return((Image *) NULL);
1876}
1877#endif
1878
1879/*
1880%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1881% %
1882% %
1883% %
1884% R e s i z e I m a g e %
1885% %
1886% %
1887% %
1888%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1889%
1890% ResizeImage() scales an image to the desired dimensions, using the given
cristy5d824382010-09-06 14:00:17 +00001891% filter (see AcquireFilterInfo()).
cristy3ed852e2009-09-05 21:47:34 +00001892%
1893% If an undefined filter is given the filter defaults to Mitchell for a
1894% colormapped image, a image with a matte channel, or if the image is
1895% enlarged. Otherwise the filter defaults to a Lanczos.
1896%
1897% ResizeImage() was inspired by Paul Heckbert's "zoom" program.
1898%
1899% The format of the ResizeImage method is:
1900%
cristybb503372010-05-27 20:51:26 +00001901% Image *ResizeImage(Image *image,const size_t columns,
1902% const size_t rows,const FilterTypes filter,const double blur,
cristy3ed852e2009-09-05 21:47:34 +00001903% ExceptionInfo *exception)
1904%
1905% A description of each parameter follows:
1906%
1907% o image: the image.
1908%
1909% o columns: the number of columns in the scaled image.
1910%
1911% o rows: the number of rows in the scaled image.
1912%
1913% o filter: Image filter to use.
1914%
cristy9af9b5d2010-08-15 17:04:28 +00001915% o blur: the blur factor where > 1 is blurry, < 1 is sharp. Typically set
1916% this to 1.0.
cristy3ed852e2009-09-05 21:47:34 +00001917%
1918% o exception: return any errors or warnings in this structure.
1919%
1920*/
1921
1922typedef struct _ContributionInfo
1923{
1924 MagickRealType
1925 weight;
1926
cristybb503372010-05-27 20:51:26 +00001927 ssize_t
cristy3ed852e2009-09-05 21:47:34 +00001928 pixel;
1929} ContributionInfo;
1930
1931static ContributionInfo **DestroyContributionThreadSet(
1932 ContributionInfo **contribution)
1933{
cristybb503372010-05-27 20:51:26 +00001934 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +00001935 i;
1936
1937 assert(contribution != (ContributionInfo **) NULL);
cristybb503372010-05-27 20:51:26 +00001938 for (i=0; i < (ssize_t) GetOpenMPMaximumThreads(); i++)
cristy3ed852e2009-09-05 21:47:34 +00001939 if (contribution[i] != (ContributionInfo *) NULL)
1940 contribution[i]=(ContributionInfo *) RelinquishMagickMemory(
1941 contribution[i]);
cristyb41ee102010-10-04 16:46:15 +00001942 contribution=(ContributionInfo **) RelinquishMagickMemory(contribution);
cristy3ed852e2009-09-05 21:47:34 +00001943 return(contribution);
1944}
1945
1946static ContributionInfo **AcquireContributionThreadSet(const size_t count)
1947{
cristybb503372010-05-27 20:51:26 +00001948 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +00001949 i;
1950
1951 ContributionInfo
1952 **contribution;
1953
cristybb503372010-05-27 20:51:26 +00001954 size_t
cristy3ed852e2009-09-05 21:47:34 +00001955 number_threads;
1956
1957 number_threads=GetOpenMPMaximumThreads();
cristyb41ee102010-10-04 16:46:15 +00001958 contribution=(ContributionInfo **) AcquireQuantumMemory(number_threads,
cristy3ed852e2009-09-05 21:47:34 +00001959 sizeof(*contribution));
1960 if (contribution == (ContributionInfo **) NULL)
1961 return((ContributionInfo **) NULL);
1962 (void) ResetMagickMemory(contribution,0,number_threads*sizeof(*contribution));
cristybb503372010-05-27 20:51:26 +00001963 for (i=0; i < (ssize_t) number_threads; i++)
cristy3ed852e2009-09-05 21:47:34 +00001964 {
1965 contribution[i]=(ContributionInfo *) AcquireQuantumMemory(count,
1966 sizeof(**contribution));
1967 if (contribution[i] == (ContributionInfo *) NULL)
1968 return(DestroyContributionThreadSet(contribution));
1969 }
1970 return(contribution);
1971}
1972
1973static inline double MagickMax(const double x,const double y)
1974{
1975 if (x > y)
1976 return(x);
1977 return(y);
1978}
1979
1980static inline double MagickMin(const double x,const double y)
1981{
1982 if (x < y)
1983 return(x);
1984 return(y);
1985}
1986
1987static MagickBooleanType HorizontalFilter(const ResizeFilter *resize_filter,
1988 const Image *image,Image *resize_image,const MagickRealType x_factor,
cristy9af9b5d2010-08-15 17:04:28 +00001989 const MagickSizeType span,MagickOffsetType *offset,ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +00001990{
1991#define ResizeImageTag "Resize/Image"
1992
cristyfa112112010-01-04 17:48:07 +00001993 CacheView
1994 *image_view,
1995 *resize_view;
1996
cristy3ed852e2009-09-05 21:47:34 +00001997 ClassType
1998 storage_class;
1999
2000 ContributionInfo
cristyfa112112010-01-04 17:48:07 +00002001 **restrict contributions;
cristy3ed852e2009-09-05 21:47:34 +00002002
cristy3ed852e2009-09-05 21:47:34 +00002003 MagickBooleanType
2004 status;
2005
2006 MagickPixelPacket
2007 zero;
2008
2009 MagickRealType
2010 scale,
2011 support;
2012
cristy9af9b5d2010-08-15 17:04:28 +00002013 ssize_t
2014 x;
2015
cristy3ed852e2009-09-05 21:47:34 +00002016 /*
2017 Apply filter to resize horizontally from image to resize image.
2018 */
cristy5d824382010-09-06 14:00:17 +00002019 scale=MagickMax(1.0/x_factor+MagickEpsilon,1.0);
cristy3ed852e2009-09-05 21:47:34 +00002020 support=scale*GetResizeFilterSupport(resize_filter);
2021 storage_class=support > 0.5 ? DirectClass : image->storage_class;
2022 if (SetImageStorageClass(resize_image,storage_class) == MagickFalse)
2023 {
2024 InheritException(exception,&resize_image->exception);
2025 return(MagickFalse);
2026 }
2027 if (support < 0.5)
2028 {
2029 /*
nicolas07bac812010-09-19 18:47:02 +00002030 Support too small even for nearest neighbour: Reduce to point
2031 sampling.
cristy3ed852e2009-09-05 21:47:34 +00002032 */
2033 support=(MagickRealType) 0.5;
2034 scale=1.0;
2035 }
2036 contributions=AcquireContributionThreadSet((size_t) (2.0*support+3.0));
2037 if (contributions == (ContributionInfo **) NULL)
2038 {
2039 (void) ThrowMagickException(exception,GetMagickModule(),
2040 ResourceLimitError,"MemoryAllocationFailed","`%s'",image->filename);
2041 return(MagickFalse);
2042 }
2043 status=MagickTrue;
2044 scale=1.0/scale;
2045 (void) ResetMagickMemory(&zero,0,sizeof(zero));
2046 image_view=AcquireCacheView(image);
2047 resize_view=AcquireCacheView(resize_image);
cristyb5d5f722009-11-04 03:03:49 +00002048#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristy3ed852e2009-09-05 21:47:34 +00002049 #pragma omp parallel for shared(status)
2050#endif
cristybb503372010-05-27 20:51:26 +00002051 for (x=0; x < (ssize_t) resize_image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +00002052 {
cristy3ed852e2009-09-05 21:47:34 +00002053 MagickRealType
2054 center,
2055 density;
2056
2057 register const IndexPacket
cristyc47d1f82009-11-26 01:44:43 +00002058 *restrict indexes;
cristy3ed852e2009-09-05 21:47:34 +00002059
2060 register const PixelPacket
cristyc47d1f82009-11-26 01:44:43 +00002061 *restrict p;
cristy3ed852e2009-09-05 21:47:34 +00002062
cristy03dbbd22010-09-19 23:04:47 +00002063 register ContributionInfo
2064 *restrict contribution;
2065
cristy3ed852e2009-09-05 21:47:34 +00002066 register IndexPacket
cristyc47d1f82009-11-26 01:44:43 +00002067 *restrict resize_indexes;
cristy3ed852e2009-09-05 21:47:34 +00002068
cristy3ed852e2009-09-05 21:47:34 +00002069 register PixelPacket
cristyc47d1f82009-11-26 01:44:43 +00002070 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +00002071
cristy03dbbd22010-09-19 23:04:47 +00002072 register ssize_t
2073 y;
2074
cristy9af9b5d2010-08-15 17:04:28 +00002075 ssize_t
2076 n,
2077 start,
2078 stop;
2079
cristy3ed852e2009-09-05 21:47:34 +00002080 if (status == MagickFalse)
2081 continue;
2082 center=(MagickRealType) (x+0.5)/x_factor;
cristybb503372010-05-27 20:51:26 +00002083 start=(ssize_t) MagickMax(center-support+0.5,0.0);
2084 stop=(ssize_t) MagickMin(center+support+0.5,(double) image->columns);
cristy3ed852e2009-09-05 21:47:34 +00002085 density=0.0;
2086 contribution=contributions[GetOpenMPThreadId()];
2087 for (n=0; n < (stop-start); n++)
2088 {
2089 contribution[n].pixel=start+n;
2090 contribution[n].weight=GetResizeFilterWeight(resize_filter,scale*
2091 ((MagickRealType) (start+n)-center+0.5));
2092 density+=contribution[n].weight;
2093 }
2094 if ((density != 0.0) && (density != 1.0))
2095 {
cristybb503372010-05-27 20:51:26 +00002096 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +00002097 i;
2098
2099 /*
2100 Normalize.
2101 */
2102 density=1.0/density;
2103 for (i=0; i < n; i++)
2104 contribution[i].weight*=density;
2105 }
cristy9af9b5d2010-08-15 17:04:28 +00002106 p=GetCacheViewVirtualPixels(image_view,contribution[0].pixel,0,(size_t)
2107 (contribution[n-1].pixel-contribution[0].pixel+1),image->rows,exception);
cristy3ed852e2009-09-05 21:47:34 +00002108 q=QueueCacheViewAuthenticPixels(resize_view,x,0,1,resize_image->rows,
2109 exception);
2110 if ((p == (const PixelPacket *) NULL) || (q == (PixelPacket *) NULL))
2111 {
2112 status=MagickFalse;
2113 continue;
2114 }
2115 indexes=GetCacheViewVirtualIndexQueue(image_view);
2116 resize_indexes=GetCacheViewAuthenticIndexQueue(resize_view);
cristybb503372010-05-27 20:51:26 +00002117 for (y=0; y < (ssize_t) resize_image->rows; y++)
cristy3ed852e2009-09-05 21:47:34 +00002118 {
cristy3ed852e2009-09-05 21:47:34 +00002119 MagickPixelPacket
2120 pixel;
2121
2122 MagickRealType
2123 alpha;
2124
cristybb503372010-05-27 20:51:26 +00002125 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +00002126 i;
2127
cristy9af9b5d2010-08-15 17:04:28 +00002128 ssize_t
2129 j;
2130
cristy3ed852e2009-09-05 21:47:34 +00002131 pixel=zero;
2132 if (image->matte == MagickFalse)
2133 {
2134 for (i=0; i < n; i++)
2135 {
2136 j=y*(contribution[n-1].pixel-contribution[0].pixel+1)+
2137 (contribution[i].pixel-contribution[0].pixel);
2138 alpha=contribution[i].weight;
2139 pixel.red+=alpha*(p+j)->red;
2140 pixel.green+=alpha*(p+j)->green;
2141 pixel.blue+=alpha*(p+j)->blue;
2142 pixel.opacity+=alpha*(p+j)->opacity;
2143 }
cristyce70c172010-01-07 17:15:30 +00002144 SetRedPixelComponent(q,ClampRedPixelComponent(&pixel));
2145 SetGreenPixelComponent(q,ClampGreenPixelComponent(&pixel));
2146 SetBluePixelComponent(q,ClampBluePixelComponent(&pixel));
2147 SetOpacityPixelComponent(q,ClampOpacityPixelComponent(&pixel));
cristy3ed852e2009-09-05 21:47:34 +00002148 if ((image->colorspace == CMYKColorspace) &&
2149 (resize_image->colorspace == CMYKColorspace))
2150 {
2151 for (i=0; i < n; i++)
2152 {
2153 j=y*(contribution[n-1].pixel-contribution[0].pixel+1)+
2154 (contribution[i].pixel-contribution[0].pixel);
2155 alpha=contribution[i].weight;
2156 pixel.index+=alpha*indexes[j];
2157 }
cristyce70c172010-01-07 17:15:30 +00002158 resize_indexes[y]=(IndexPacket) ClampToQuantum(pixel.index);
cristy3ed852e2009-09-05 21:47:34 +00002159 }
2160 }
2161 else
2162 {
2163 MagickRealType
2164 gamma;
2165
2166 gamma=0.0;
2167 for (i=0; i < n; i++)
2168 {
2169 j=y*(contribution[n-1].pixel-contribution[0].pixel+1)+
2170 (contribution[i].pixel-contribution[0].pixel);
cristy46f08202010-01-10 04:04:21 +00002171 alpha=contribution[i].weight*QuantumScale*
2172 GetAlphaPixelComponent(p+j);
cristy3ed852e2009-09-05 21:47:34 +00002173 pixel.red+=alpha*(p+j)->red;
2174 pixel.green+=alpha*(p+j)->green;
2175 pixel.blue+=alpha*(p+j)->blue;
2176 pixel.opacity+=contribution[i].weight*(p+j)->opacity;
2177 gamma+=alpha;
2178 }
2179 gamma=1.0/(fabs((double) gamma) <= MagickEpsilon ? 1.0 : gamma);
cristyce70c172010-01-07 17:15:30 +00002180 q->red=ClampToQuantum(gamma*GetRedPixelComponent(&pixel));
2181 q->green=ClampToQuantum(gamma*GetGreenPixelComponent(&pixel));
2182 q->blue=ClampToQuantum(gamma*GetBluePixelComponent(&pixel));
2183 SetOpacityPixelComponent(q,ClampOpacityPixelComponent(&pixel));
cristy3ed852e2009-09-05 21:47:34 +00002184 if ((image->colorspace == CMYKColorspace) &&
2185 (resize_image->colorspace == CMYKColorspace))
2186 {
2187 for (i=0; i < n; i++)
2188 {
2189 j=y*(contribution[n-1].pixel-contribution[0].pixel+1)+
2190 (contribution[i].pixel-contribution[0].pixel);
cristy46f08202010-01-10 04:04:21 +00002191 alpha=contribution[i].weight*QuantumScale*
2192 GetAlphaPixelComponent(p+j);
cristyc197d1c2009-10-13 19:56:53 +00002193 pixel.index+=alpha*indexes[j];
cristy3ed852e2009-09-05 21:47:34 +00002194 }
cristyce70c172010-01-07 17:15:30 +00002195 resize_indexes[y]=(IndexPacket) ClampToQuantum(gamma*
2196 GetIndexPixelComponent(&pixel));
cristy3ed852e2009-09-05 21:47:34 +00002197 }
2198 }
2199 if ((resize_image->storage_class == PseudoClass) &&
2200 (image->storage_class == PseudoClass))
2201 {
cristybb503372010-05-27 20:51:26 +00002202 i=(ssize_t) (MagickMin(MagickMax(center,(double) start),(double) stop-
cristy3ed852e2009-09-05 21:47:34 +00002203 1.0)+0.5);
2204 j=y*(contribution[n-1].pixel-contribution[0].pixel+1)+
2205 (contribution[i-start].pixel-contribution[0].pixel);
2206 resize_indexes[y]=indexes[j];
2207 }
2208 q++;
2209 }
2210 if (SyncCacheViewAuthenticPixels(resize_view,exception) == MagickFalse)
2211 status=MagickFalse;
2212 if (image->progress_monitor != (MagickProgressMonitor) NULL)
2213 {
2214 MagickBooleanType
2215 proceed;
2216
cristyb5d5f722009-11-04 03:03:49 +00002217#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristy3ed852e2009-09-05 21:47:34 +00002218 #pragma omp critical (MagickCore_HorizontalFilter)
2219#endif
cristy9af9b5d2010-08-15 17:04:28 +00002220 proceed=SetImageProgress(image,ResizeImageTag,(*offset)++,span);
cristy3ed852e2009-09-05 21:47:34 +00002221 if (proceed == MagickFalse)
2222 status=MagickFalse;
2223 }
2224 }
2225 resize_view=DestroyCacheView(resize_view);
2226 image_view=DestroyCacheView(image_view);
2227 contributions=DestroyContributionThreadSet(contributions);
2228 return(status);
2229}
2230
2231static MagickBooleanType VerticalFilter(const ResizeFilter *resize_filter,
2232 const Image *image,Image *resize_image,const MagickRealType y_factor,
cristy9af9b5d2010-08-15 17:04:28 +00002233 const MagickSizeType span,MagickOffsetType *offset,ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +00002234{
cristyfa112112010-01-04 17:48:07 +00002235 CacheView
2236 *image_view,
2237 *resize_view;
2238
cristy3ed852e2009-09-05 21:47:34 +00002239 ClassType
2240 storage_class;
2241
2242 ContributionInfo
cristyfa112112010-01-04 17:48:07 +00002243 **restrict contributions;
cristy3ed852e2009-09-05 21:47:34 +00002244
cristy3ed852e2009-09-05 21:47:34 +00002245 MagickBooleanType
2246 status;
2247
2248 MagickPixelPacket
2249 zero;
2250
2251 MagickRealType
2252 scale,
2253 support;
2254
cristy9af9b5d2010-08-15 17:04:28 +00002255 ssize_t
2256 y;
2257
cristy3ed852e2009-09-05 21:47:34 +00002258 /*
cristy9af9b5d2010-08-15 17:04:28 +00002259 Apply filter to resize vertically from image to resize image.
cristy3ed852e2009-09-05 21:47:34 +00002260 */
cristy5d824382010-09-06 14:00:17 +00002261 scale=MagickMax(1.0/y_factor+MagickEpsilon,1.0);
cristy3ed852e2009-09-05 21:47:34 +00002262 support=scale*GetResizeFilterSupport(resize_filter);
2263 storage_class=support > 0.5 ? DirectClass : image->storage_class;
2264 if (SetImageStorageClass(resize_image,storage_class) == MagickFalse)
2265 {
2266 InheritException(exception,&resize_image->exception);
2267 return(MagickFalse);
2268 }
2269 if (support < 0.5)
2270 {
2271 /*
nicolas07bac812010-09-19 18:47:02 +00002272 Support too small even for nearest neighbour: Reduce to point
2273 sampling.
cristy3ed852e2009-09-05 21:47:34 +00002274 */
2275 support=(MagickRealType) 0.5;
2276 scale=1.0;
2277 }
2278 contributions=AcquireContributionThreadSet((size_t) (2.0*support+3.0));
2279 if (contributions == (ContributionInfo **) NULL)
2280 {
2281 (void) ThrowMagickException(exception,GetMagickModule(),
2282 ResourceLimitError,"MemoryAllocationFailed","`%s'",image->filename);
2283 return(MagickFalse);
2284 }
2285 status=MagickTrue;
2286 scale=1.0/scale;
2287 (void) ResetMagickMemory(&zero,0,sizeof(zero));
2288 image_view=AcquireCacheView(image);
2289 resize_view=AcquireCacheView(resize_image);
cristyb5d5f722009-11-04 03:03:49 +00002290#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristy3ed852e2009-09-05 21:47:34 +00002291 #pragma omp parallel for shared(status)
2292#endif
cristybb503372010-05-27 20:51:26 +00002293 for (y=0; y < (ssize_t) resize_image->rows; y++)
cristy3ed852e2009-09-05 21:47:34 +00002294 {
cristy3ed852e2009-09-05 21:47:34 +00002295 MagickRealType
2296 center,
2297 density;
2298
2299 register const IndexPacket
cristyc47d1f82009-11-26 01:44:43 +00002300 *restrict indexes;
cristy3ed852e2009-09-05 21:47:34 +00002301
2302 register const PixelPacket
cristyc47d1f82009-11-26 01:44:43 +00002303 *restrict p;
cristy3ed852e2009-09-05 21:47:34 +00002304
cristy03dbbd22010-09-19 23:04:47 +00002305 register ContributionInfo
2306 *restrict contribution;
2307
cristy3ed852e2009-09-05 21:47:34 +00002308 register IndexPacket
cristyc47d1f82009-11-26 01:44:43 +00002309 *restrict resize_indexes;
cristy3ed852e2009-09-05 21:47:34 +00002310
cristy9af9b5d2010-08-15 17:04:28 +00002311 register PixelPacket
2312 *restrict q;
2313
cristybb503372010-05-27 20:51:26 +00002314 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +00002315 x;
2316
cristy9af9b5d2010-08-15 17:04:28 +00002317 ssize_t
2318 n,
2319 start,
2320 stop;
cristy3ed852e2009-09-05 21:47:34 +00002321
2322 if (status == MagickFalse)
2323 continue;
cristy679e6962010-03-18 00:42:45 +00002324 center=(MagickRealType) (y+0.5)/y_factor;
cristybb503372010-05-27 20:51:26 +00002325 start=(ssize_t) MagickMax(center-support+0.5,0.0);
2326 stop=(ssize_t) MagickMin(center+support+0.5,(double) image->rows);
cristy3ed852e2009-09-05 21:47:34 +00002327 density=0.0;
2328 contribution=contributions[GetOpenMPThreadId()];
2329 for (n=0; n < (stop-start); n++)
2330 {
2331 contribution[n].pixel=start+n;
2332 contribution[n].weight=GetResizeFilterWeight(resize_filter,scale*
2333 ((MagickRealType) (start+n)-center+0.5));
2334 density+=contribution[n].weight;
2335 }
2336 if ((density != 0.0) && (density != 1.0))
2337 {
cristybb503372010-05-27 20:51:26 +00002338 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +00002339 i;
2340
2341 /*
2342 Normalize.
2343 */
2344 density=1.0/density;
2345 for (i=0; i < n; i++)
2346 contribution[i].weight*=density;
2347 }
2348 p=GetCacheViewVirtualPixels(image_view,0,contribution[0].pixel,
cristy9af9b5d2010-08-15 17:04:28 +00002349 image->columns,(size_t) (contribution[n-1].pixel-contribution[0].pixel+1),
2350 exception);
cristy3ed852e2009-09-05 21:47:34 +00002351 q=QueueCacheViewAuthenticPixels(resize_view,0,y,resize_image->columns,1,
2352 exception);
2353 if ((p == (const PixelPacket *) NULL) || (q == (PixelPacket *) NULL))
2354 {
2355 status=MagickFalse;
2356 continue;
2357 }
2358 indexes=GetCacheViewVirtualIndexQueue(image_view);
2359 resize_indexes=GetCacheViewAuthenticIndexQueue(resize_view);
cristybb503372010-05-27 20:51:26 +00002360 for (x=0; x < (ssize_t) resize_image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +00002361 {
cristy3ed852e2009-09-05 21:47:34 +00002362 MagickPixelPacket
2363 pixel;
2364
2365 MagickRealType
2366 alpha;
2367
cristybb503372010-05-27 20:51:26 +00002368 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +00002369 i;
2370
cristy9af9b5d2010-08-15 17:04:28 +00002371 ssize_t
2372 j;
2373
cristy3ed852e2009-09-05 21:47:34 +00002374 pixel=zero;
2375 if (image->matte == MagickFalse)
2376 {
2377 for (i=0; i < n; i++)
2378 {
cristybb503372010-05-27 20:51:26 +00002379 j=(ssize_t) ((contribution[i].pixel-contribution[0].pixel)*
cristy3ed852e2009-09-05 21:47:34 +00002380 image->columns+x);
2381 alpha=contribution[i].weight;
2382 pixel.red+=alpha*(p+j)->red;
2383 pixel.green+=alpha*(p+j)->green;
2384 pixel.blue+=alpha*(p+j)->blue;
2385 pixel.opacity+=alpha*(p+j)->opacity;
2386 }
cristyce70c172010-01-07 17:15:30 +00002387 SetRedPixelComponent(q,ClampRedPixelComponent(&pixel));
2388 SetGreenPixelComponent(q,ClampGreenPixelComponent(&pixel));
2389 SetBluePixelComponent(q,ClampBluePixelComponent(&pixel));
2390 SetOpacityPixelComponent(q,ClampOpacityPixelComponent(&pixel));
cristy3ed852e2009-09-05 21:47:34 +00002391 if ((image->colorspace == CMYKColorspace) &&
2392 (resize_image->colorspace == CMYKColorspace))
2393 {
2394 for (i=0; i < n; i++)
2395 {
cristybb503372010-05-27 20:51:26 +00002396 j=(ssize_t) ((contribution[i].pixel-contribution[0].pixel)*
cristy3ed852e2009-09-05 21:47:34 +00002397 image->columns+x);
2398 alpha=contribution[i].weight;
2399 pixel.index+=alpha*indexes[j];
2400 }
cristyce70c172010-01-07 17:15:30 +00002401 resize_indexes[x]=(IndexPacket) ClampToQuantum(pixel.index);
cristy3ed852e2009-09-05 21:47:34 +00002402 }
2403 }
2404 else
2405 {
2406 MagickRealType
2407 gamma;
2408
2409 gamma=0.0;
2410 for (i=0; i < n; i++)
2411 {
cristybb503372010-05-27 20:51:26 +00002412 j=(ssize_t) ((contribution[i].pixel-contribution[0].pixel)*
cristy3ed852e2009-09-05 21:47:34 +00002413 image->columns+x);
cristy46f08202010-01-10 04:04:21 +00002414 alpha=contribution[i].weight*QuantumScale*
2415 GetAlphaPixelComponent(p+j);
cristy3ed852e2009-09-05 21:47:34 +00002416 pixel.red+=alpha*(p+j)->red;
2417 pixel.green+=alpha*(p+j)->green;
2418 pixel.blue+=alpha*(p+j)->blue;
2419 pixel.opacity+=contribution[i].weight*(p+j)->opacity;
2420 gamma+=alpha;
2421 }
2422 gamma=1.0/(fabs((double) gamma) <= MagickEpsilon ? 1.0 : gamma);
cristyce70c172010-01-07 17:15:30 +00002423 q->red=ClampToQuantum(gamma*GetRedPixelComponent(&pixel));
2424 q->green=ClampToQuantum(gamma*GetGreenPixelComponent(&pixel));
2425 q->blue=ClampToQuantum(gamma*GetBluePixelComponent(&pixel));
2426 SetOpacityPixelComponent(q,ClampOpacityPixelComponent(&pixel));
cristy3ed852e2009-09-05 21:47:34 +00002427 if ((image->colorspace == CMYKColorspace) &&
2428 (resize_image->colorspace == CMYKColorspace))
2429 {
2430 for (i=0; i < n; i++)
2431 {
cristybb503372010-05-27 20:51:26 +00002432 j=(ssize_t) ((contribution[i].pixel-contribution[0].pixel)*
cristy3ed852e2009-09-05 21:47:34 +00002433 image->columns+x);
cristy46f08202010-01-10 04:04:21 +00002434 alpha=contribution[i].weight*QuantumScale*
2435 GetAlphaPixelComponent(p+j);
cristy3ed852e2009-09-05 21:47:34 +00002436 pixel.index+=alpha*indexes[j];
2437 }
cristyce70c172010-01-07 17:15:30 +00002438 resize_indexes[x]=(IndexPacket) ClampToQuantum(gamma*
2439 GetIndexPixelComponent(&pixel));
cristy3ed852e2009-09-05 21:47:34 +00002440 }
2441 }
2442 if ((resize_image->storage_class == PseudoClass) &&
2443 (image->storage_class == PseudoClass))
2444 {
cristybb503372010-05-27 20:51:26 +00002445 i=(ssize_t) (MagickMin(MagickMax(center,(double) start),(double) stop-
cristy3ed852e2009-09-05 21:47:34 +00002446 1.0)+0.5);
cristybb503372010-05-27 20:51:26 +00002447 j=(ssize_t) ((contribution[i-start].pixel-contribution[0].pixel)*
cristy3ed852e2009-09-05 21:47:34 +00002448 image->columns+x);
2449 resize_indexes[x]=indexes[j];
2450 }
2451 q++;
2452 }
2453 if (SyncCacheViewAuthenticPixels(resize_view,exception) == MagickFalse)
2454 status=MagickFalse;
2455 if (image->progress_monitor != (MagickProgressMonitor) NULL)
2456 {
2457 MagickBooleanType
2458 proceed;
2459
cristyb5d5f722009-11-04 03:03:49 +00002460#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristy3ed852e2009-09-05 21:47:34 +00002461 #pragma omp critical (MagickCore_VerticalFilter)
2462#endif
cristy9af9b5d2010-08-15 17:04:28 +00002463 proceed=SetImageProgress(image,ResizeImageTag,(*offset)++,span);
cristy3ed852e2009-09-05 21:47:34 +00002464 if (proceed == MagickFalse)
2465 status=MagickFalse;
2466 }
2467 }
2468 resize_view=DestroyCacheView(resize_view);
2469 image_view=DestroyCacheView(image_view);
2470 contributions=DestroyContributionThreadSet(contributions);
2471 return(status);
2472}
2473
cristybb503372010-05-27 20:51:26 +00002474MagickExport Image *ResizeImage(const Image *image,const size_t columns,
2475 const size_t rows,const FilterTypes filter,const double blur,
cristy3ed852e2009-09-05 21:47:34 +00002476 ExceptionInfo *exception)
2477{
2478#define WorkLoadFactor 0.265
2479
2480 FilterTypes
2481 filter_type;
2482
2483 Image
2484 *filter_image,
2485 *resize_image;
2486
cristy9af9b5d2010-08-15 17:04:28 +00002487 MagickOffsetType
2488 offset;
2489
cristy3ed852e2009-09-05 21:47:34 +00002490 MagickRealType
2491 x_factor,
2492 y_factor;
2493
2494 MagickSizeType
2495 span;
2496
2497 MagickStatusType
2498 status;
2499
2500 ResizeFilter
2501 *resize_filter;
2502
cristy3ed852e2009-09-05 21:47:34 +00002503 /*
2504 Acquire resize image.
2505 */
2506 assert(image != (Image *) NULL);
2507 assert(image->signature == MagickSignature);
2508 if (image->debug != MagickFalse)
2509 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
2510 assert(exception != (ExceptionInfo *) NULL);
2511 assert(exception->signature == MagickSignature);
2512 if ((columns == 0) || (rows == 0))
2513 ThrowImageException(ImageError,"NegativeOrZeroImageSize");
2514 if ((columns == image->columns) && (rows == image->rows) &&
2515 (filter == UndefinedFilter) && (blur == 1.0))
2516 return(CloneImage(image,0,0,MagickTrue,exception));
2517 resize_image=CloneImage(image,columns,rows,MagickTrue,exception);
2518 if (resize_image == (Image *) NULL)
2519 return(resize_image);
2520 /*
2521 Acquire resize filter.
2522 */
2523 x_factor=(MagickRealType) columns/(MagickRealType) image->columns;
2524 y_factor=(MagickRealType) rows/(MagickRealType) image->rows;
2525 if ((x_factor*y_factor) > WorkLoadFactor)
2526 filter_image=CloneImage(image,columns,image->rows,MagickTrue,exception);
2527 else
2528 filter_image=CloneImage(image,image->columns,rows,MagickTrue,exception);
2529 if (filter_image == (Image *) NULL)
2530 return(DestroyImage(resize_image));
2531 filter_type=LanczosFilter;
2532 if (filter != UndefinedFilter)
2533 filter_type=filter;
2534 else
2535 if ((x_factor == 1.0) && (y_factor == 1.0))
2536 filter_type=PointFilter;
2537 else
2538 if ((image->storage_class == PseudoClass) ||
2539 (image->matte != MagickFalse) || ((x_factor*y_factor) > 1.0))
2540 filter_type=MitchellFilter;
2541 resize_filter=AcquireResizeFilter(image,filter_type,blur,MagickFalse,
2542 exception);
2543 /*
2544 Resize image.
2545 */
cristy9af9b5d2010-08-15 17:04:28 +00002546 offset=0;
cristy3ed852e2009-09-05 21:47:34 +00002547 if ((x_factor*y_factor) > WorkLoadFactor)
2548 {
2549 span=(MagickSizeType) (filter_image->columns+rows);
2550 status=HorizontalFilter(resize_filter,image,filter_image,x_factor,span,
cristy9af9b5d2010-08-15 17:04:28 +00002551 &offset,exception);
cristy3ed852e2009-09-05 21:47:34 +00002552 status&=VerticalFilter(resize_filter,filter_image,resize_image,y_factor,
cristy9af9b5d2010-08-15 17:04:28 +00002553 span,&offset,exception);
cristy3ed852e2009-09-05 21:47:34 +00002554 }
2555 else
2556 {
2557 span=(MagickSizeType) (filter_image->rows+columns);
2558 status=VerticalFilter(resize_filter,image,filter_image,y_factor,span,
cristy9af9b5d2010-08-15 17:04:28 +00002559 &offset,exception);
cristy3ed852e2009-09-05 21:47:34 +00002560 status&=HorizontalFilter(resize_filter,filter_image,resize_image,x_factor,
cristy9af9b5d2010-08-15 17:04:28 +00002561 span,&offset,exception);
cristy3ed852e2009-09-05 21:47:34 +00002562 }
2563 /*
2564 Free resources.
2565 */
2566 filter_image=DestroyImage(filter_image);
2567 resize_filter=DestroyResizeFilter(resize_filter);
2568 if ((status == MagickFalse) || (resize_image == (Image *) NULL))
2569 return((Image *) NULL);
2570 resize_image->type=image->type;
2571 return(resize_image);
2572}
2573
2574/*
2575%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2576% %
2577% %
2578% %
2579% S a m p l e I m a g e %
2580% %
2581% %
2582% %
2583%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2584%
2585% SampleImage() scales an image to the desired dimensions with pixel
2586% sampling. Unlike other scaling methods, this method does not introduce
2587% any additional color into the scaled image.
2588%
2589% The format of the SampleImage method is:
2590%
cristybb503372010-05-27 20:51:26 +00002591% Image *SampleImage(const Image *image,const size_t columns,
2592% const size_t rows,ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +00002593%
2594% A description of each parameter follows:
2595%
2596% o image: the image.
2597%
2598% o columns: the number of columns in the sampled image.
2599%
2600% o rows: the number of rows in the sampled image.
2601%
2602% o exception: return any errors or warnings in this structure.
2603%
2604*/
cristybb503372010-05-27 20:51:26 +00002605MagickExport Image *SampleImage(const Image *image,const size_t columns,
2606 const size_t rows,ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +00002607{
2608#define SampleImageTag "Sample/Image"
2609
cristyc4c8d132010-01-07 01:58:38 +00002610 CacheView
2611 *image_view,
2612 *sample_view;
2613
cristy3ed852e2009-09-05 21:47:34 +00002614 Image
2615 *sample_image;
2616
cristy3ed852e2009-09-05 21:47:34 +00002617 MagickBooleanType
2618 status;
2619
cristy5f959472010-05-27 22:19:46 +00002620 MagickOffsetType
2621 progress;
2622
cristybb503372010-05-27 20:51:26 +00002623 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +00002624 x;
2625
cristy5f959472010-05-27 22:19:46 +00002626 ssize_t
2627 *x_offset,
2628 y;
2629
cristy3ed852e2009-09-05 21:47:34 +00002630 /*
2631 Initialize sampled image attributes.
2632 */
2633 assert(image != (const Image *) NULL);
2634 assert(image->signature == MagickSignature);
2635 if (image->debug != MagickFalse)
2636 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
2637 assert(exception != (ExceptionInfo *) NULL);
2638 assert(exception->signature == MagickSignature);
2639 if ((columns == 0) || (rows == 0))
2640 ThrowImageException(ImageError,"NegativeOrZeroImageSize");
2641 if ((columns == image->columns) && (rows == image->rows))
2642 return(CloneImage(image,0,0,MagickTrue,exception));
2643 sample_image=CloneImage(image,columns,rows,MagickTrue,exception);
2644 if (sample_image == (Image *) NULL)
2645 return((Image *) NULL);
2646 /*
2647 Allocate scan line buffer and column offset buffers.
2648 */
cristybb503372010-05-27 20:51:26 +00002649 x_offset=(ssize_t *) AcquireQuantumMemory((size_t) sample_image->columns,
cristy3ed852e2009-09-05 21:47:34 +00002650 sizeof(*x_offset));
cristybb503372010-05-27 20:51:26 +00002651 if (x_offset == (ssize_t *) NULL)
cristy3ed852e2009-09-05 21:47:34 +00002652 {
2653 sample_image=DestroyImage(sample_image);
2654 ThrowImageException(ResourceLimitError,"MemoryAllocationFailed");
2655 }
cristybb503372010-05-27 20:51:26 +00002656 for (x=0; x < (ssize_t) sample_image->columns; x++)
2657 x_offset[x]=(ssize_t) (((MagickRealType) x+0.5)*image->columns/
cristy3ed852e2009-09-05 21:47:34 +00002658 sample_image->columns);
2659 /*
2660 Sample each row.
2661 */
2662 status=MagickTrue;
2663 progress=0;
2664 image_view=AcquireCacheView(image);
2665 sample_view=AcquireCacheView(sample_image);
cristyb5d5f722009-11-04 03:03:49 +00002666#if defined(MAGICKCORE_OPENMP_SUPPORT)
2667 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +00002668#endif
cristybb503372010-05-27 20:51:26 +00002669 for (y=0; y < (ssize_t) sample_image->rows; y++)
cristy3ed852e2009-09-05 21:47:34 +00002670 {
cristy3ed852e2009-09-05 21:47:34 +00002671 register const IndexPacket
cristyc47d1f82009-11-26 01:44:43 +00002672 *restrict indexes;
cristy3ed852e2009-09-05 21:47:34 +00002673
2674 register const PixelPacket
cristyc47d1f82009-11-26 01:44:43 +00002675 *restrict p;
cristy3ed852e2009-09-05 21:47:34 +00002676
2677 register IndexPacket
cristyc47d1f82009-11-26 01:44:43 +00002678 *restrict sample_indexes;
cristy3ed852e2009-09-05 21:47:34 +00002679
cristy3ed852e2009-09-05 21:47:34 +00002680 register PixelPacket
cristyc47d1f82009-11-26 01:44:43 +00002681 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +00002682
cristy03dbbd22010-09-19 23:04:47 +00002683 register ssize_t
2684 x;
2685
cristy9af9b5d2010-08-15 17:04:28 +00002686 ssize_t
2687 y_offset;
2688
cristy3ed852e2009-09-05 21:47:34 +00002689 if (status == MagickFalse)
2690 continue;
cristy9af9b5d2010-08-15 17:04:28 +00002691 y_offset=(ssize_t) (((MagickRealType) y+0.5)*image->rows/
2692 sample_image->rows);
cristy3ed852e2009-09-05 21:47:34 +00002693 p=GetCacheViewVirtualPixels(image_view,0,y_offset,image->columns,1,
2694 exception);
2695 q=QueueCacheViewAuthenticPixels(sample_view,0,y,sample_image->columns,1,
2696 exception);
2697 if ((p == (const PixelPacket *) NULL) || (q == (PixelPacket *) NULL))
2698 {
2699 status=MagickFalse;
2700 continue;
2701 }
2702 indexes=GetCacheViewAuthenticIndexQueue(image_view);
2703 sample_indexes=GetCacheViewAuthenticIndexQueue(sample_view);
2704 /*
2705 Sample each column.
2706 */
cristybb503372010-05-27 20:51:26 +00002707 for (x=0; x < (ssize_t) sample_image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +00002708 *q++=p[x_offset[x]];
2709 if ((image->storage_class == PseudoClass) ||
2710 (image->colorspace == CMYKColorspace))
cristybb503372010-05-27 20:51:26 +00002711 for (x=0; x < (ssize_t) sample_image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +00002712 sample_indexes[x]=indexes[x_offset[x]];
2713 if (SyncCacheViewAuthenticPixels(sample_view,exception) == MagickFalse)
2714 status=MagickFalse;
2715 if (image->progress_monitor != (MagickProgressMonitor) NULL)
2716 {
2717 MagickBooleanType
2718 proceed;
2719
cristyb5d5f722009-11-04 03:03:49 +00002720#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristy3ed852e2009-09-05 21:47:34 +00002721 #pragma omp critical (MagickCore_SampleImage)
2722#endif
2723 proceed=SetImageProgress(image,SampleImageTag,progress++,image->rows);
2724 if (proceed == MagickFalse)
2725 status=MagickFalse;
2726 }
2727 }
2728 image_view=DestroyCacheView(image_view);
2729 sample_view=DestroyCacheView(sample_view);
cristybb503372010-05-27 20:51:26 +00002730 x_offset=(ssize_t *) RelinquishMagickMemory(x_offset);
cristy3ed852e2009-09-05 21:47:34 +00002731 sample_image->type=image->type;
2732 return(sample_image);
2733}
2734
2735/*
2736%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2737% %
2738% %
2739% %
2740% S c a l e I m a g e %
2741% %
2742% %
2743% %
2744%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2745%
2746% ScaleImage() changes the size of an image to the given dimensions.
2747%
2748% The format of the ScaleImage method is:
2749%
cristybb503372010-05-27 20:51:26 +00002750% Image *ScaleImage(const Image *image,const size_t columns,
2751% const size_t rows,ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +00002752%
2753% A description of each parameter follows:
2754%
2755% o image: the image.
2756%
2757% o columns: the number of columns in the scaled image.
2758%
2759% o rows: the number of rows in the scaled image.
2760%
2761% o exception: return any errors or warnings in this structure.
2762%
2763*/
cristybb503372010-05-27 20:51:26 +00002764MagickExport Image *ScaleImage(const Image *image,const size_t columns,
2765 const size_t rows,ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +00002766{
2767#define ScaleImageTag "Scale/Image"
2768
cristyed6cb232010-01-20 03:07:53 +00002769 CacheView
2770 *image_view,
2771 *scale_view;
2772
cristy3ed852e2009-09-05 21:47:34 +00002773 Image
2774 *scale_image;
2775
cristy3ed852e2009-09-05 21:47:34 +00002776 MagickBooleanType
2777 next_column,
2778 next_row,
2779 proceed;
2780
2781 MagickPixelPacket
2782 pixel,
2783 *scale_scanline,
2784 *scanline,
2785 *x_vector,
2786 *y_vector,
2787 zero;
2788
cristy3ed852e2009-09-05 21:47:34 +00002789 PointInfo
2790 scale,
2791 span;
2792
cristybb503372010-05-27 20:51:26 +00002793 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +00002794 i;
2795
cristy9af9b5d2010-08-15 17:04:28 +00002796 ssize_t
2797 number_rows,
2798 y;
2799
cristy3ed852e2009-09-05 21:47:34 +00002800 /*
2801 Initialize scaled image attributes.
2802 */
2803 assert(image != (const Image *) NULL);
2804 assert(image->signature == MagickSignature);
2805 if (image->debug != MagickFalse)
2806 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
2807 assert(exception != (ExceptionInfo *) NULL);
2808 assert(exception->signature == MagickSignature);
2809 if ((columns == 0) || (rows == 0))
2810 return((Image *) NULL);
2811 if ((columns == image->columns) && (rows == image->rows))
2812 return(CloneImage(image,0,0,MagickTrue,exception));
2813 scale_image=CloneImage(image,columns,rows,MagickTrue,exception);
2814 if (scale_image == (Image *) NULL)
2815 return((Image *) NULL);
2816 if (SetImageStorageClass(scale_image,DirectClass) == MagickFalse)
2817 {
2818 InheritException(exception,&scale_image->exception);
2819 scale_image=DestroyImage(scale_image);
2820 return((Image *) NULL);
2821 }
2822 /*
2823 Allocate memory.
2824 */
2825 x_vector=(MagickPixelPacket *) AcquireQuantumMemory((size_t) image->columns,
2826 sizeof(*x_vector));
2827 scanline=x_vector;
2828 if (image->rows != scale_image->rows)
2829 scanline=(MagickPixelPacket *) AcquireQuantumMemory((size_t) image->columns,
2830 sizeof(*scanline));
2831 scale_scanline=(MagickPixelPacket *) AcquireQuantumMemory((size_t)
2832 scale_image->columns,sizeof(*scale_scanline));
2833 y_vector=(MagickPixelPacket *) AcquireQuantumMemory((size_t) image->columns,
2834 sizeof(*y_vector));
2835 if ((scanline == (MagickPixelPacket *) NULL) ||
2836 (scale_scanline == (MagickPixelPacket *) NULL) ||
2837 (x_vector == (MagickPixelPacket *) NULL) ||
2838 (y_vector == (MagickPixelPacket *) NULL))
2839 {
2840 scale_image=DestroyImage(scale_image);
2841 ThrowImageException(ResourceLimitError,"MemoryAllocationFailed");
2842 }
2843 /*
2844 Scale image.
2845 */
2846 number_rows=0;
2847 next_row=MagickTrue;
2848 span.y=1.0;
2849 scale.y=(double) scale_image->rows/(double) image->rows;
2850 (void) ResetMagickMemory(y_vector,0,(size_t) image->columns*
2851 sizeof(*y_vector));
2852 GetMagickPixelPacket(image,&pixel);
2853 (void) ResetMagickMemory(&zero,0,sizeof(zero));
2854 i=0;
cristyed6cb232010-01-20 03:07:53 +00002855 image_view=AcquireCacheView(image);
2856 scale_view=AcquireCacheView(scale_image);
cristybb503372010-05-27 20:51:26 +00002857 for (y=0; y < (ssize_t) scale_image->rows; y++)
cristy3ed852e2009-09-05 21:47:34 +00002858 {
2859 register const IndexPacket
cristyc47d1f82009-11-26 01:44:43 +00002860 *restrict indexes;
cristy3ed852e2009-09-05 21:47:34 +00002861
2862 register const PixelPacket
cristyc47d1f82009-11-26 01:44:43 +00002863 *restrict p;
cristy3ed852e2009-09-05 21:47:34 +00002864
2865 register IndexPacket
cristyc47d1f82009-11-26 01:44:43 +00002866 *restrict scale_indexes;
cristy3ed852e2009-09-05 21:47:34 +00002867
cristy3ed852e2009-09-05 21:47:34 +00002868 register MagickPixelPacket
cristyc47d1f82009-11-26 01:44:43 +00002869 *restrict s,
2870 *restrict t;
cristy3ed852e2009-09-05 21:47:34 +00002871
2872 register PixelPacket
cristyc47d1f82009-11-26 01:44:43 +00002873 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +00002874
cristy9af9b5d2010-08-15 17:04:28 +00002875 register ssize_t
2876 x;
2877
cristyed6cb232010-01-20 03:07:53 +00002878 q=QueueCacheViewAuthenticPixels(scale_view,0,y,scale_image->columns,1,
2879 exception);
cristy3ed852e2009-09-05 21:47:34 +00002880 if (q == (PixelPacket *) NULL)
2881 break;
2882 scale_indexes=GetAuthenticIndexQueue(scale_image);
2883 if (scale_image->rows == image->rows)
2884 {
2885 /*
2886 Read a new scanline.
2887 */
cristyed6cb232010-01-20 03:07:53 +00002888 p=GetCacheViewVirtualPixels(image_view,0,i++,image->columns,1,
2889 exception);
cristy3ed852e2009-09-05 21:47:34 +00002890 if (p == (const PixelPacket *) NULL)
2891 break;
cristyed6cb232010-01-20 03:07:53 +00002892 indexes=GetCacheViewVirtualIndexQueue(image_view);
cristybb503372010-05-27 20:51:26 +00002893 for (x=0; x < (ssize_t) image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +00002894 {
cristyce70c172010-01-07 17:15:30 +00002895 x_vector[x].red=(MagickRealType) GetRedPixelComponent(p);
2896 x_vector[x].green=(MagickRealType) GetGreenPixelComponent(p);
2897 x_vector[x].blue=(MagickRealType) GetBluePixelComponent(p);
cristy3ed852e2009-09-05 21:47:34 +00002898 if (image->matte != MagickFalse)
cristyce70c172010-01-07 17:15:30 +00002899 x_vector[x].opacity=(MagickRealType) GetOpacityPixelComponent(p);
cristy3ed852e2009-09-05 21:47:34 +00002900 if (indexes != (IndexPacket *) NULL)
2901 x_vector[x].index=(MagickRealType) indexes[x];
2902 p++;
2903 }
2904 }
2905 else
2906 {
2907 /*
2908 Scale Y direction.
2909 */
2910 while (scale.y < span.y)
2911 {
cristy9af9b5d2010-08-15 17:04:28 +00002912 if ((next_row != MagickFalse) &&
2913 (number_rows < (ssize_t) image->rows))
cristy3ed852e2009-09-05 21:47:34 +00002914 {
2915 /*
2916 Read a new scanline.
2917 */
cristyed6cb232010-01-20 03:07:53 +00002918 p=GetCacheViewVirtualPixels(image_view,0,i++,image->columns,1,
2919 exception);
cristy3ed852e2009-09-05 21:47:34 +00002920 if (p == (const PixelPacket *) NULL)
2921 break;
cristyed6cb232010-01-20 03:07:53 +00002922 indexes=GetCacheViewVirtualIndexQueue(image_view);
cristybb503372010-05-27 20:51:26 +00002923 for (x=0; x < (ssize_t) image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +00002924 {
cristyce70c172010-01-07 17:15:30 +00002925 x_vector[x].red=(MagickRealType) GetRedPixelComponent(p);
2926 x_vector[x].green=(MagickRealType) GetGreenPixelComponent(p);
2927 x_vector[x].blue=(MagickRealType) GetBluePixelComponent(p);
cristy3ed852e2009-09-05 21:47:34 +00002928 if (image->matte != MagickFalse)
cristyed6cb232010-01-20 03:07:53 +00002929 x_vector[x].opacity=(MagickRealType)
cristy9af9b5d2010-08-15 17:04:28 +00002930 GetOpacityPixelComponent(p);
cristy3ed852e2009-09-05 21:47:34 +00002931 if (indexes != (IndexPacket *) NULL)
2932 x_vector[x].index=(MagickRealType) indexes[x];
2933 p++;
2934 }
2935 number_rows++;
2936 }
cristybb503372010-05-27 20:51:26 +00002937 for (x=0; x < (ssize_t) image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +00002938 {
2939 y_vector[x].red+=scale.y*x_vector[x].red;
2940 y_vector[x].green+=scale.y*x_vector[x].green;
2941 y_vector[x].blue+=scale.y*x_vector[x].blue;
2942 if (scale_image->matte != MagickFalse)
2943 y_vector[x].opacity+=scale.y*x_vector[x].opacity;
2944 if (scale_indexes != (IndexPacket *) NULL)
2945 y_vector[x].index+=scale.y*x_vector[x].index;
2946 }
2947 span.y-=scale.y;
2948 scale.y=(double) scale_image->rows/(double) image->rows;
2949 next_row=MagickTrue;
2950 }
cristybb503372010-05-27 20:51:26 +00002951 if ((next_row != MagickFalse) && (number_rows < (ssize_t) image->rows))
cristy3ed852e2009-09-05 21:47:34 +00002952 {
2953 /*
2954 Read a new scanline.
2955 */
cristyed6cb232010-01-20 03:07:53 +00002956 p=GetCacheViewVirtualPixels(image_view,0,i++,image->columns,1,
2957 exception);
cristy3ed852e2009-09-05 21:47:34 +00002958 if (p == (const PixelPacket *) NULL)
2959 break;
cristyed6cb232010-01-20 03:07:53 +00002960 indexes=GetCacheViewVirtualIndexQueue(image_view);
cristybb503372010-05-27 20:51:26 +00002961 for (x=0; x < (ssize_t) image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +00002962 {
cristyce70c172010-01-07 17:15:30 +00002963 x_vector[x].red=(MagickRealType) GetRedPixelComponent(p);
2964 x_vector[x].green=(MagickRealType) GetGreenPixelComponent(p);
2965 x_vector[x].blue=(MagickRealType) GetBluePixelComponent(p);
cristy3ed852e2009-09-05 21:47:34 +00002966 if (image->matte != MagickFalse)
cristy2b726bd2010-01-11 01:05:39 +00002967 x_vector[x].opacity=(MagickRealType)
2968 GetOpacityPixelComponent(p);
cristy3ed852e2009-09-05 21:47:34 +00002969 if (indexes != (IndexPacket *) NULL)
2970 x_vector[x].index=(MagickRealType) indexes[x];
2971 p++;
2972 }
2973 number_rows++;
2974 next_row=MagickFalse;
2975 }
2976 s=scanline;
cristybb503372010-05-27 20:51:26 +00002977 for (x=0; x < (ssize_t) image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +00002978 {
2979 pixel.red=y_vector[x].red+span.y*x_vector[x].red;
2980 pixel.green=y_vector[x].green+span.y*x_vector[x].green;
2981 pixel.blue=y_vector[x].blue+span.y*x_vector[x].blue;
2982 if (image->matte != MagickFalse)
2983 pixel.opacity=y_vector[x].opacity+span.y*x_vector[x].opacity;
2984 if (scale_indexes != (IndexPacket *) NULL)
2985 pixel.index=y_vector[x].index+span.y*x_vector[x].index;
2986 s->red=pixel.red;
2987 s->green=pixel.green;
2988 s->blue=pixel.blue;
2989 if (scale_image->matte != MagickFalse)
2990 s->opacity=pixel.opacity;
2991 if (scale_indexes != (IndexPacket *) NULL)
2992 s->index=pixel.index;
2993 s++;
2994 y_vector[x]=zero;
2995 }
2996 scale.y-=span.y;
2997 if (scale.y <= 0)
2998 {
2999 scale.y=(double) scale_image->rows/(double) image->rows;
3000 next_row=MagickTrue;
3001 }
3002 span.y=1.0;
3003 }
3004 if (scale_image->columns == image->columns)
3005 {
3006 /*
3007 Transfer scanline to scaled image.
3008 */
3009 s=scanline;
cristybb503372010-05-27 20:51:26 +00003010 for (x=0; x < (ssize_t) scale_image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +00003011 {
cristyce70c172010-01-07 17:15:30 +00003012 q->red=ClampToQuantum(s->red);
3013 q->green=ClampToQuantum(s->green);
3014 q->blue=ClampToQuantum(s->blue);
cristy3ed852e2009-09-05 21:47:34 +00003015 if (scale_image->matte != MagickFalse)
cristyce70c172010-01-07 17:15:30 +00003016 q->opacity=ClampToQuantum(s->opacity);
cristy3ed852e2009-09-05 21:47:34 +00003017 if (scale_indexes != (IndexPacket *) NULL)
cristyce70c172010-01-07 17:15:30 +00003018 scale_indexes[x]=(IndexPacket) ClampToQuantum(s->index);
cristy3ed852e2009-09-05 21:47:34 +00003019 q++;
3020 s++;
3021 }
3022 }
3023 else
3024 {
3025 /*
3026 Scale X direction.
3027 */
3028 pixel=zero;
3029 next_column=MagickFalse;
3030 span.x=1.0;
3031 s=scanline;
3032 t=scale_scanline;
cristybb503372010-05-27 20:51:26 +00003033 for (x=0; x < (ssize_t) image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +00003034 {
3035 scale.x=(double) scale_image->columns/(double) image->columns;
3036 while (scale.x >= span.x)
3037 {
3038 if (next_column != MagickFalse)
3039 {
3040 pixel=zero;
3041 t++;
3042 }
3043 pixel.red+=span.x*s->red;
3044 pixel.green+=span.x*s->green;
3045 pixel.blue+=span.x*s->blue;
3046 if (image->matte != MagickFalse)
3047 pixel.opacity+=span.x*s->opacity;
3048 if (scale_indexes != (IndexPacket *) NULL)
3049 pixel.index+=span.x*s->index;
3050 t->red=pixel.red;
3051 t->green=pixel.green;
3052 t->blue=pixel.blue;
3053 if (scale_image->matte != MagickFalse)
3054 t->opacity=pixel.opacity;
3055 if (scale_indexes != (IndexPacket *) NULL)
3056 t->index=pixel.index;
3057 scale.x-=span.x;
3058 span.x=1.0;
3059 next_column=MagickTrue;
3060 }
3061 if (scale.x > 0)
3062 {
3063 if (next_column != MagickFalse)
3064 {
3065 pixel=zero;
3066 next_column=MagickFalse;
3067 t++;
3068 }
3069 pixel.red+=scale.x*s->red;
3070 pixel.green+=scale.x*s->green;
3071 pixel.blue+=scale.x*s->blue;
3072 if (scale_image->matte != MagickFalse)
3073 pixel.opacity+=scale.x*s->opacity;
3074 if (scale_indexes != (IndexPacket *) NULL)
3075 pixel.index+=scale.x*s->index;
3076 span.x-=scale.x;
3077 }
3078 s++;
3079 }
3080 if (span.x > 0)
3081 {
3082 s--;
3083 pixel.red+=span.x*s->red;
3084 pixel.green+=span.x*s->green;
3085 pixel.blue+=span.x*s->blue;
3086 if (scale_image->matte != MagickFalse)
3087 pixel.opacity+=span.x*s->opacity;
3088 if (scale_indexes != (IndexPacket *) NULL)
3089 pixel.index+=span.x*s->index;
3090 }
3091 if ((next_column == MagickFalse) &&
cristybb503372010-05-27 20:51:26 +00003092 ((ssize_t) (t-scale_scanline) < (ssize_t) scale_image->columns))
cristy3ed852e2009-09-05 21:47:34 +00003093 {
3094 t->red=pixel.red;
3095 t->green=pixel.green;
3096 t->blue=pixel.blue;
3097 if (scale_image->matte != MagickFalse)
3098 t->opacity=pixel.opacity;
3099 if (scale_indexes != (IndexPacket *) NULL)
3100 t->index=pixel.index;
3101 }
3102 /*
3103 Transfer scanline to scaled image.
3104 */
3105 t=scale_scanline;
cristybb503372010-05-27 20:51:26 +00003106 for (x=0; x < (ssize_t) scale_image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +00003107 {
cristyce70c172010-01-07 17:15:30 +00003108 q->red=ClampToQuantum(t->red);
3109 q->green=ClampToQuantum(t->green);
3110 q->blue=ClampToQuantum(t->blue);
cristy3ed852e2009-09-05 21:47:34 +00003111 if (scale_image->matte != MagickFalse)
cristyce70c172010-01-07 17:15:30 +00003112 q->opacity=ClampToQuantum(t->opacity);
cristy3ed852e2009-09-05 21:47:34 +00003113 if (scale_indexes != (IndexPacket *) NULL)
cristyce70c172010-01-07 17:15:30 +00003114 scale_indexes[x]=(IndexPacket) ClampToQuantum(t->index);
cristy3ed852e2009-09-05 21:47:34 +00003115 t++;
3116 q++;
3117 }
3118 }
cristyed6cb232010-01-20 03:07:53 +00003119 if (SyncCacheViewAuthenticPixels(scale_view,exception) == MagickFalse)
cristy3ed852e2009-09-05 21:47:34 +00003120 break;
cristy96b16132010-08-29 17:19:52 +00003121 proceed=SetImageProgress(image,ScaleImageTag,(MagickOffsetType) y,
3122 image->rows);
cristy3ed852e2009-09-05 21:47:34 +00003123 if (proceed == MagickFalse)
3124 break;
3125 }
cristyed6cb232010-01-20 03:07:53 +00003126 scale_view=DestroyCacheView(scale_view);
3127 image_view=DestroyCacheView(image_view);
cristy3ed852e2009-09-05 21:47:34 +00003128 /*
3129 Free allocated memory.
3130 */
3131 y_vector=(MagickPixelPacket *) RelinquishMagickMemory(y_vector);
3132 scale_scanline=(MagickPixelPacket *) RelinquishMagickMemory(scale_scanline);
3133 if (scale_image->rows != image->rows)
3134 scanline=(MagickPixelPacket *) RelinquishMagickMemory(scanline);
3135 x_vector=(MagickPixelPacket *) RelinquishMagickMemory(x_vector);
3136 scale_image->type=image->type;
3137 return(scale_image);
3138}
3139
3140/*
3141%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3142% %
3143% %
3144% %
3145+ S e t R e s i z e F i l t e r S u p p o r t %
3146% %
3147% %
3148% %
3149%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3150%
3151% SetResizeFilterSupport() specifies which IR filter to use to window
3152%
3153% The format of the SetResizeFilterSupport method is:
3154%
3155% void SetResizeFilterSupport(ResizeFilter *resize_filter,
3156% const MagickRealType support)
3157%
3158% A description of each parameter follows:
3159%
3160% o resize_filter: the resize filter.
3161%
3162% o support: the filter spport radius.
3163%
3164*/
3165MagickExport void SetResizeFilterSupport(ResizeFilter *resize_filter,
3166 const MagickRealType support)
3167{
3168 assert(resize_filter != (ResizeFilter *) NULL);
3169 assert(resize_filter->signature == MagickSignature);
3170 resize_filter->support=support;
3171}
3172
3173/*
3174%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3175% %
3176% %
3177% %
3178% T h u m b n a i l I m a g e %
3179% %
3180% %
3181% %
3182%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3183%
3184% ThumbnailImage() changes the size of an image to the given dimensions and
3185% removes any associated profiles. The goal is to produce small low cost
3186% thumbnail images suited for display on the Web.
3187%
3188% The format of the ThumbnailImage method is:
3189%
cristybb503372010-05-27 20:51:26 +00003190% Image *ThumbnailImage(const Image *image,const size_t columns,
3191% const size_t rows,ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +00003192%
3193% A description of each parameter follows:
3194%
3195% o image: the image.
3196%
3197% o columns: the number of columns in the scaled image.
3198%
3199% o rows: the number of rows in the scaled image.
3200%
3201% o exception: return any errors or warnings in this structure.
3202%
3203*/
cristy9af9b5d2010-08-15 17:04:28 +00003204MagickExport Image *ThumbnailImage(const Image *image,const size_t columns,
3205 const size_t rows,ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +00003206{
3207#define SampleFactor 5
3208
3209 char
3210 value[MaxTextExtent];
3211
3212 const char
3213 *name;
3214
3215 Image
3216 *thumbnail_image;
3217
3218 MagickRealType
3219 x_factor,
3220 y_factor;
3221
cristybb503372010-05-27 20:51:26 +00003222 size_t
cristy3ed852e2009-09-05 21:47:34 +00003223 version;
3224
cristy9af9b5d2010-08-15 17:04:28 +00003225 struct stat
3226 attributes;
3227
cristy3ed852e2009-09-05 21:47:34 +00003228 assert(image != (Image *) NULL);
3229 assert(image->signature == MagickSignature);
3230 if (image->debug != MagickFalse)
3231 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
3232 assert(exception != (ExceptionInfo *) NULL);
3233 assert(exception->signature == MagickSignature);
3234 x_factor=(MagickRealType) columns/(MagickRealType) image->columns;
3235 y_factor=(MagickRealType) rows/(MagickRealType) image->rows;
3236 if ((x_factor*y_factor) > 0.1)
cristyd1bb3bc2010-09-07 00:43:58 +00003237 thumbnail_image=ResizeImage(image,columns,rows,image->filter,image->blur,
3238 exception);
cristy3ed852e2009-09-05 21:47:34 +00003239 else
3240 if (((SampleFactor*columns) < 128) || ((SampleFactor*rows) < 128))
cristyd1bb3bc2010-09-07 00:43:58 +00003241 thumbnail_image=ResizeImage(image,columns,rows,image->filter,
3242 image->blur,exception);
cristy3ed852e2009-09-05 21:47:34 +00003243 else
3244 {
3245 Image
3246 *sample_image;
3247
3248 sample_image=SampleImage(image,SampleFactor*columns,SampleFactor*rows,
3249 exception);
3250 if (sample_image == (Image *) NULL)
3251 return((Image *) NULL);
cristyd1bb3bc2010-09-07 00:43:58 +00003252 thumbnail_image=ResizeImage(sample_image,columns,rows,image->filter,
3253 image->blur,exception);
cristy3ed852e2009-09-05 21:47:34 +00003254 sample_image=DestroyImage(sample_image);
3255 }
3256 if (thumbnail_image == (Image *) NULL)
3257 return(thumbnail_image);
3258 (void) ParseAbsoluteGeometry("0x0+0+0",&thumbnail_image->page);
3259 if (thumbnail_image->matte == MagickFalse)
3260 (void) SetImageAlphaChannel(thumbnail_image,OpaqueAlphaChannel);
3261 thumbnail_image->depth=8;
3262 thumbnail_image->interlace=NoInterlace;
3263 /*
3264 Strip all profiles except color profiles.
3265 */
3266 ResetImageProfileIterator(thumbnail_image);
3267 for (name=GetNextImageProfile(thumbnail_image); name != (const char *) NULL; )
3268 {
3269 if ((LocaleCompare(name,"icc") != 0) && (LocaleCompare(name,"icm") != 0))
3270 {
cristy2b726bd2010-01-11 01:05:39 +00003271 (void) DeleteImageProfile(thumbnail_image,name);
cristy3ed852e2009-09-05 21:47:34 +00003272 ResetImageProfileIterator(thumbnail_image);
3273 }
3274 name=GetNextImageProfile(thumbnail_image);
3275 }
3276 (void) DeleteImageProperty(thumbnail_image,"comment");
3277 (void) CopyMagickString(value,image->magick_filename,MaxTextExtent);
cristy7b63d662009-11-13 01:58:56 +00003278 if (strstr(image->magick_filename,"//") == (char *) NULL)
3279 (void) FormatMagickString(value,MaxTextExtent,"file://%s",
cristy3ed852e2009-09-05 21:47:34 +00003280 image->magick_filename);
3281 (void) SetImageProperty(thumbnail_image,"Thumb::URI",value);
3282 (void) CopyMagickString(value,image->magick_filename,MaxTextExtent);
3283 if (GetPathAttributes(image->filename,&attributes) != MagickFalse)
3284 {
cristye8c25f92010-06-03 00:53:06 +00003285 (void) FormatMagickString(value,MaxTextExtent,"%.20g",(double)
cristy3ed852e2009-09-05 21:47:34 +00003286 attributes.st_mtime);
3287 (void) SetImageProperty(thumbnail_image,"Thumb::MTime",value);
3288 }
cristye8c25f92010-06-03 00:53:06 +00003289 (void) FormatMagickString(value,MaxTextExtent,"%.20g",(double)
cristy3ed852e2009-09-05 21:47:34 +00003290 attributes.st_mtime);
cristyb9080c92009-12-01 20:13:26 +00003291 (void) FormatMagickSize(GetBlobSize(image),MagickFalse,value);
cristy2ce15c92010-03-12 14:03:41 +00003292 (void) ConcatenateMagickString(value,"B",MaxTextExtent);
cristy3ed852e2009-09-05 21:47:34 +00003293 (void) SetImageProperty(thumbnail_image,"Thumb::Size",value);
3294 (void) FormatMagickString(value,MaxTextExtent,"image/%s",image->magick);
3295 LocaleLower(value);
3296 (void) SetImageProperty(thumbnail_image,"Thumb::Mimetype",value);
3297 (void) SetImageProperty(thumbnail_image,"software",
3298 GetMagickVersion(&version));
cristye8c25f92010-06-03 00:53:06 +00003299 (void) FormatMagickString(value,MaxTextExtent,"%.20g",(double)
3300 image->magick_columns);
cristy3ed852e2009-09-05 21:47:34 +00003301 (void) SetImageProperty(thumbnail_image,"Thumb::Image::Width",value);
cristye8c25f92010-06-03 00:53:06 +00003302 (void) FormatMagickString(value,MaxTextExtent,"%.20g",(double)
cristyf2faecf2010-05-28 19:19:36 +00003303 image->magick_rows);
cristy3ed852e2009-09-05 21:47:34 +00003304 (void) SetImageProperty(thumbnail_image,"Thumb::Image::height",value);
cristye8c25f92010-06-03 00:53:06 +00003305 (void) FormatMagickString(value,MaxTextExtent,"%.20g",(double)
3306 GetImageListLength(image));
cristy3ed852e2009-09-05 21:47:34 +00003307 (void) SetImageProperty(thumbnail_image,"Thumb::Document::Pages",value);
3308 return(thumbnail_image);
3309}