blob: 2783354aa49151d6443fcedfca909739181433e4 [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
nicolase473f722010-10-07 00:05:13 +0000146 The original "zoom" program by Paul Heckbert called this "Bessel".
147 But really it is more accurately named "Jinc".
cristy3ed852e2009-09-05 21:47:34 +0000148 */
149 if (x == 0.0)
nicolas5a36f342010-10-07 00:11:32 +0000150 return(0.5*MagickPIL);
151 return(BesselOrderOne(MagickPIL*x)/x);
cristy3ed852e2009-09-05 21:47:34 +0000152}
153
154static MagickRealType Blackman(const MagickRealType x,
cristyce70c172010-01-07 17:15:30 +0000155 const ResizeFilter *magick_unused(resize_filter))
cristy3ed852e2009-09-05 21:47:34 +0000156{
157 /*
cristy83017922010-09-05 20:45:15 +0000158 Blackman: 2nd order cosine windowing function:
cristy21ce88a2010-09-05 01:37:25 +0000159 0.42 + 0.5 cos(pi x) + 0.08 cos(2pi x)
cristy560d8182010-09-08 22:36:25 +0000160 Refactored by Chantal Racette and Nicolas Robidoux to one trig
161 call and five flops.
cristy3ed852e2009-09-05 21:47:34 +0000162 */
cristyc5c6f662010-09-22 14:23:02 +0000163 const MagickRealType cospix = cos((double) (MagickPIL*x));
cristy560d8182010-09-08 22:36:25 +0000164 return(0.34+cospix*(0.5+cospix*0.16));
cristy3ed852e2009-09-05 21:47:34 +0000165}
166
167static MagickRealType Bohman(const MagickRealType x,
cristyce70c172010-01-07 17:15:30 +0000168 const ResizeFilter *magick_unused(resize_filter))
cristy3ed852e2009-09-05 21:47:34 +0000169{
170 /*
cristy560d8182010-09-08 22:36:25 +0000171 Bohman: 2rd Order cosine windowing function:
172 (1-x) cos(pi x) + sin(pi x) / pi.
nicolasa4f7b4a2010-09-20 20:28:38 +0000173 Refactored by Nicolas Robidoux to one trig call, one sqrt call,
174 and 7 flops, taking advantage of the fact that the support of
175 Bohman is 1 (so that we know that sin(pi x) >= 0).
cristy3ed852e2009-09-05 21:47:34 +0000176 */
cristyc5c6f662010-09-22 14:23:02 +0000177 const double cospix = cos((double) (MagickPIL*x));
nicolasa4f7b4a2010-09-20 20:28:38 +0000178 const double sinpix = sqrt(1.0-cospix*cospix);
nicolas2ffd3b22010-09-24 20:27:31 +0000179 return((1.0-x)*cospix+(1.0/MagickPIL)*sinpix);
cristy3ed852e2009-09-05 21:47:34 +0000180}
181
anthony463be1d2010-09-26 01:07:36 +0000182static MagickRealType Box(const MagickRealType magick_unused(x),
183 const ResizeFilter *magick_unused(resize_filter))
cristy3ed852e2009-09-05 21:47:34 +0000184{
185 /*
nicolas40477452010-09-27 23:42:08 +0000186 A Box filter is a equal weighting function (all weights equal).
anthonyc331dec2010-09-26 01:30:14 +0000187 DO NOT LIMIT results by support or resize point sampling will work
188 as it requests points beyond its normal 0.0 support size.
cristy3ed852e2009-09-05 21:47:34 +0000189 */
anthony463be1d2010-09-26 01:07:36 +0000190 return(1.0);
cristy3ed852e2009-09-05 21:47:34 +0000191}
192
193static MagickRealType CubicBC(const MagickRealType x,
194 const ResizeFilter *resize_filter)
195{
196 /*
197 Cubic Filters using B,C determined values:
cristyf59a8922010-02-28 19:51:23 +0000198 Mitchell-Netravali B=1/3 C=1/3 Qualitively ideal Cubic Filter
199 Catmull-Rom B= 0 C=1/2 Cublic Interpolation Function
200 Cubic B-Spline B= 1 C= 0 Spline Approximation of Gaussian
201 Hermite B= 0 C= 0 Quadratic Spline (support = 1)
cristy3ed852e2009-09-05 21:47:34 +0000202
cristy33b1c162010-01-23 22:51:51 +0000203 See paper by Mitchell and Netravali, Reconstruction Filters in Computer
204 Graphics Computer Graphics, Volume 22, Number 4, August 1988
205 http://www.cs.utexas.edu/users/fussell/courses/cs384g/lectures/mitchell/
cristyf59a8922010-02-28 19:51:23 +0000206 Mitchell.pdf.
cristy3ed852e2009-09-05 21:47:34 +0000207
cristy33b1c162010-01-23 22:51:51 +0000208 Coefficents are determined from B,C values:
cristy3ed852e2009-09-05 21:47:34 +0000209 P0 = ( 6 - 2*B )/6
210 P1 = 0
211 P2 = (-18 +12*B + 6*C )/6
212 P3 = ( 12 - 9*B - 6*C )/6
213 Q0 = ( 8*B +24*C )/6
214 Q1 = ( -12*B -48*C )/6
215 Q2 = ( 6*B +30*C )/6
216 Q3 = ( - 1*B - 6*C )/6
217
cristy33b1c162010-01-23 22:51:51 +0000218 which are used to define the filter:
cristyf59a8922010-02-28 19:51:23 +0000219
cristy3ed852e2009-09-05 21:47:34 +0000220 P0 + P1*x + P2*x^2 + P3*x^3 0 <= x < 1
221 Q0 + Q1*x + Q2*x^2 + Q3*x^3 1 <= x <= 2
222
cristy33b1c162010-01-23 22:51:51 +0000223 which ensures function is continuous in value and derivative (slope).
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;
anthony853d6972010-10-08 06:01:31 +0000418 const MagickRealType p =
nicolas3aab40c2010-09-19 21:14:15 +0000419 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
anthony853d6972010-10-08 06:01:31 +0000496% SincFast Lanczos2D Robidoux
anthony61b5ddd2010-10-05 02:33:31 +0000497%
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%
nicolas1eb2fcf2010-10-10 20:45:17 +0000529% The Lanczos2D and Robidoux filters are tuned for cylindrical
530% (radial) EWA (Elliptical Weighted Average) distortion. Lanczos2D
nicolasdff19b42010-10-10 20:53:13 +0000531% is a 2 lobe Lanczos-like filter using Jinc (for EWA) or Sinc.
nicolas6ca6e962010-10-10 20:49:01 +0000532% Robidoux used to be a sharpened version of Lanczos2D (with
nicolasdff19b42010-10-10 20:53:13 +0000533% blur=0.958033808). Now, it is the unique Cubic 'Keys' filter that
534% exactly preserves images with only vertical or horizontal features
535% when performing 'no-op" with EWA distortion. It turns out to be
536% close to both plain Mitchell (slightly more blurry) and "sharpened"
nicolas6ca6e962010-10-10 20:49:01 +0000537% Lanczos2D.
anthony61b5ddd2010-10-05 02:33:31 +0000538%
nicolas07bac812010-09-19 18:47:02 +0000539% Special 'expert' options can be used to override any and all filter
540% settings. This is not advised unless you have expert knowledge of
541% the use of resampling filtered techniques. Check on the results of
542% your selections using the "filter:verbose" setting to make sure you
anthony61b5ddd2010-10-05 02:33:31 +0000543% get the exact filter that you are tring to achieve.
cristy3ed852e2009-09-05 21:47:34 +0000544%
anthony48f77622010-10-03 14:32:31 +0000545% "filter:filter" Select the main function associated with
546% this filter name, as the weighting function of the filter.
547% This can be used to set a windowing function as a weighting
548% function, for special purposes, such as graphing.
cristy3ed852e2009-09-05 21:47:34 +0000549%
anthony7bdc0ed2010-09-15 01:52:32 +0000550% If a "filter:window" operation has not been provided, then a 'Box'
551% windowing function will be set to denote that no windowing function
552% is being used.
cristy3ed852e2009-09-05 21:47:34 +0000553%
554% "filter:window" Select this windowing function for the filter.
anthony7bdc0ed2010-09-15 01:52:32 +0000555% While any filter could be used as a windowing function, using the
556% 'first lobe' of that filter over the whole support window, using a
anthony48f77622010-10-03 14:32:31 +0000557% non-windowing function is not advisible. If no weighting filter
558% function is specifed a 'SincFast' filter will be used.
cristy3ed852e2009-09-05 21:47:34 +0000559%
anthony48f77622010-10-03 14:32:31 +0000560% "filter:lobes" Number of lobes to use for the Sinc/Jinc filter.
anthony7bdc0ed2010-09-15 01:52:32 +0000561% This a simpler method of setting filter support size that will
anthony48f77622010-10-03 14:32:31 +0000562% correctly handle the Sinc/Jinc switch for an operators filtering
anthony7bdc0ed2010-09-15 01:52:32 +0000563% requirements. Only integers should be given.
cristy3ed852e2009-09-05 21:47:34 +0000564%
565% "filter:support" Set the support size for filtering to the size given
anthony48f77622010-10-03 14:32:31 +0000566% This not recommended for Sinc/Jinc windowed filters (lobes should
anthony7bdc0ed2010-09-15 01:52:32 +0000567% be used instead). This will override any 'filter:lobes' option.
cristy3ed852e2009-09-05 21:47:34 +0000568%
anthonyb6d08c52010-09-13 01:17:04 +0000569% "filter:win-support" Scale windowing function to this size instead.
570% This causes the windowing (or self-windowing Lagrange filter) to act
571% is if the support window it much much larger than what is actually
572% supplied to the calling operator. The filter however is still
573% clipped to the real support size given, by the support range suppiled
574% to the caller. If unset this will equal the normal filter support
575% size.
576%
cristy3ed852e2009-09-05 21:47:34 +0000577% "filter:blur" Scale the filter and support window by this amount.
578% A value >1 will generally result in a more burred image with
579% more ringing effects, while a value <1 will sharpen the
580% resulting image with more aliasing and Morie effects.
581%
cristy3ed852e2009-09-05 21:47:34 +0000582% "filter:b"
583% "filter:c" Override the preset B,C values for a Cubic type of filter
584% If only one of these are given it is assumes to be a 'Keys'
585% type of filter such that B+2C=1, where Keys 'alpha' value = C
586%
anthonyb6d08c52010-09-13 01:17:04 +0000587% "filter:verbose" Output the exact results of the filter selections
588% made, as well as plotting data for graphing the resulting filter
589% over support range (blur adjusted).
cristy3ed852e2009-09-05 21:47:34 +0000590%
591% Set a true un-windowed Sinc filter with 10 lobes (very slow)
anthony48f77622010-10-03 14:32:31 +0000592% -define filter:filter=Sinc
593% -define filter:lobes=8
cristy3ed852e2009-09-05 21:47:34 +0000594%
anthony48f77622010-10-03 14:32:31 +0000595% For example force an 8 lobe Lanczos (Sinc or Jinc) filter...
cristy3ed852e2009-09-05 21:47:34 +0000596% -filter Lanczos
anthony48f77622010-10-03 14:32:31 +0000597% -define filter:lobes=8
anthony7bdc0ed2010-09-15 01:52:32 +0000598%
cristy3ed852e2009-09-05 21:47:34 +0000599% The format of the AcquireResizeFilter method is:
600%
601% ResizeFilter *AcquireResizeFilter(const Image *image,
602% const FilterTypes filter_type, const MagickBooleanType radial,
603% ExceptionInfo *exception)
604%
cristy33b1c162010-01-23 22:51:51 +0000605% A description of each parameter follows:
606%
cristy3ed852e2009-09-05 21:47:34 +0000607% o image: the image.
608%
nicolas07bac812010-09-19 18:47:02 +0000609% o filter: the filter type, defining a preset filter, window and
610% support. The artifact settings listed above will override
611% those selections.
cristy3ed852e2009-09-05 21:47:34 +0000612%
anthony48f77622010-10-03 14:32:31 +0000613% o blur: blur the filter by this amount, use 1.0 if unknown. Image
614% artifact "filter:blur" will override this API call usage, including
615% any internal change (such as for cylindrical usage).
cristy3ed852e2009-09-05 21:47:34 +0000616%
anthony48f77622010-10-03 14:32:31 +0000617% o radial: use a 1D orthogonal filter (Sinc) or 2D cylindrical
618% (radial) filter (Jinc)
cristy3ed852e2009-09-05 21:47:34 +0000619%
620% o exception: return any errors or warnings in this structure.
621%
622*/
623MagickExport ResizeFilter *AcquireResizeFilter(const Image *image,
cristyf59a8922010-02-28 19:51:23 +0000624 const FilterTypes filter,const MagickRealType blur,
cristy3ed852e2009-09-05 21:47:34 +0000625 const MagickBooleanType cylindrical,ExceptionInfo *exception)
626{
627 const char
628 *artifact;
629
630 FilterTypes
631 filter_type,
632 window_type;
633
cristy3ed852e2009-09-05 21:47:34 +0000634 MagickRealType
635 B,
636 C;
637
638 register ResizeFilter
639 *resize_filter;
640
cristy9af9b5d2010-08-15 17:04:28 +0000641 ssize_t
642 option;
643
cristy3ed852e2009-09-05 21:47:34 +0000644 /*
anthony48f77622010-10-03 14:32:31 +0000645 Table Mapping given Filter, into Weighting and Windowing functions.
nicolas07bac812010-09-19 18:47:02 +0000646 A 'Box' windowing function means its a simble non-windowed filter.
anthony48f77622010-10-03 14:32:31 +0000647 An 'SincFast' filter function could be upgraded to a 'Jinc' filter
648 if a "cylindrical", unless a 'Sinc' or 'SincFast' filter was
649 specifically requested.
anthony449887b2010-09-10 02:23:33 +0000650
nicolas07bac812010-09-19 18:47:02 +0000651 WARNING: The order of this tabel must match the order of the
652 FilterTypes enumeration specified in "resample.h", or the filter
653 names will not match the filter being setup.
anthony1e2d5ca2010-09-10 02:24:35 +0000654
655 You can check filter setups with the "filter:verbose" setting.
cristy3ed852e2009-09-05 21:47:34 +0000656 */
657 static struct
658 {
659 FilterTypes
660 filter,
661 window;
662 } const mapping[SentinelFilter] =
663 {
anthony462ee072010-09-27 12:34:02 +0000664 { UndefinedFilter, BoxFilter }, /* Undefined (default to Box) */
665 { PointFilter, BoxFilter }, /* SPECIAL: Nearest neighbour */
666 { BoxFilter, BoxFilter }, /* Box averaging filter */
667 { TriangleFilter, BoxFilter }, /* Linear interpolation filter */
668 { HermiteFilter, BoxFilter }, /* Hermite interpolation filter */
669 { SincFastFilter, HanningFilter }, /* Hanning -- cosine-sinc */
670 { SincFastFilter, HammingFilter }, /* Hamming -- '' variation */
671 { SincFastFilter, BlackmanFilter }, /* Blackman -- 2*cosine-sinc */
672 { GaussianFilter, BoxFilter }, /* Gaussian blur filter */
673 { QuadraticFilter, BoxFilter }, /* Quadratic Gaussian approximation */
674 { CubicFilter, BoxFilter }, /* Cubic B-Spline */
675 { CatromFilter, BoxFilter }, /* Cubic interpolator */
676 { MitchellFilter, BoxFilter }, /* 'Ideal' cubic filter */
677 { LanczosFilter, SincFastFilter }, /* SPECIAL: 3-lobed sinc-sinc */
anthony48f77622010-10-03 14:32:31 +0000678 { JincFilter, BoxFilter }, /* Raw 3-lobed Jinc function */
679 { SincFilter, BoxFilter }, /* Raw 4-lobed Sinc function */
anthony462ee072010-09-27 12:34:02 +0000680 { SincFastFilter, KaiserFilter }, /* Kaiser -- square root-sinc */
681 { SincFastFilter, WelshFilter }, /* Welsh -- parabolic-sinc */
682 { SincFastFilter, CubicFilter }, /* Parzen -- cubic-sinc */
683 { LagrangeFilter, BoxFilter }, /* Lagrange self-windowing filter */
684 { SincFastFilter, BohmanFilter }, /* Bohman -- 2*cosine-sinc */
685 { SincFastFilter, TriangleFilter }, /* Bartlett -- triangle-sinc */
686 { SincFastFilter, BoxFilter }, /* Raw fast sinc ("Pade"-type) */
nicolas5835ee02010-10-10 20:40:15 +0000687 { Lanczos2DFilter, JincFilter }, /* SPECIAL: 2-lobed jinc-jinc */
nicolas1eb2fcf2010-10-10 20:45:17 +0000688 { RobidouxFilter, BoxFilter }, /* SPECIAL: Keys cubic tuned for EWA */
cristy3ed852e2009-09-05 21:47:34 +0000689 };
690 /*
nicolas32f44eb2010-09-20 01:23:12 +0000691 Table mapping the filter/window from the above table to an actual
nicolas07bac812010-09-19 18:47:02 +0000692 function. The default support size for that filter as a weighting
693 function, the range to scale with to use that function as a sinc
694 windowing function, (typ 1.0).
anthony933b4bf2010-09-10 02:31:37 +0000695
anthony07a3f7f2010-09-16 03:03:11 +0000696 Note that the filter_type -> function is 1 to 1 except for Sinc(),
nicolas07bac812010-09-19 18:47:02 +0000697 SincFast(), and CubicBC() functions, which may have multiple
698 filter to function associations.
699
700 See "filter:verbose" handling below for the function -> filter
701 mapping.
cristy3ed852e2009-09-05 21:47:34 +0000702 */
703 static struct
704 {
705 MagickRealType
706 (*function)(const MagickRealType, const ResizeFilter*),
anthony61b5ddd2010-10-05 02:33:31 +0000707 lobes, /* default lobes/support size of the weighting filter */
anthony2d9b8b52010-09-14 08:31:07 +0000708 scale, /* windowing function range, for scaling windowing function */
anthony933b4bf2010-09-10 02:31:37 +0000709 B,C; /* Cubic Filter factors for a CubicBC function, else ignored */
cristy3ed852e2009-09-05 21:47:34 +0000710 } const filters[SentinelFilter] =
711 {
anthony61b5ddd2010-10-05 02:33:31 +0000712 { Box, 0.5, 0.5, 0.0, 0.0 }, /* Undefined (default to Box) */
713 { Box, 0.0, 0.5, 0.0, 0.0 }, /* Point (special handling) */
714 { Box, 0.5, 0.5, 0.0, 0.0 }, /* Box */
715 { Triangle, 1.0, 1.0, 0.0, 0.0 }, /* Triangle */
716 { CubicBC, 1.0, 1.0, 0.0, 0.0 }, /* Hermite (cubic B=C=0) */
717 { Hanning, 1.0, 1.0, 0.0, 0.0 }, /* Hanning, cosine window */
718 { Hamming, 1.0, 1.0, 0.0, 0.0 }, /* Hamming, '' variation */
719 { Blackman, 1.0, 1.0, 0.0, 0.0 }, /* Blackman, 2*cosine window */
720 { Gaussian, 1.5, 1.5, 0.0, 0.0 }, /* Gaussian */
721 { Quadratic, 1.5, 1.5, 0.0, 0.0 }, /* Quadratic gaussian */
722 { CubicBC, 2.0, 2.0, 1.0, 0.0 }, /* Cubic B-Spline (B=1,C=0) */
723 { CubicBC, 2.0, 1.0, 0.0, 0.5 }, /* Catmull-Rom (B=0,C=1/2) */
anthony853d6972010-10-08 06:01:31 +0000724 { CubicBC, 2.0, 1.0, 1./3., 1./3. }, /* Mitchell (B=C=1/3) */
725 { SincFast, 3.0, 1.0, 0.0, 0.0 }, /* Lanczos, 3-lobed Sinc-Sinc */
726 { Jinc, 3.0, 1.21967, 0.0, 0.0 }, /* Raw 3-lobed Jinc */
anthony61b5ddd2010-10-05 02:33:31 +0000727 { Sinc, 4.0, 1.0, 0.0, 0.0 }, /* Raw 4-lobed Sinc */
728 { Kaiser, 1.0, 1.0, 0.0, 0.0 }, /* Kaiser (square root window) */
729 { Welsh, 1.0, 1.0, 0.0, 0.0 }, /* Welsh (parabolic window) */
730 { CubicBC, 2.0, 2.0, 1.0, 0.0 }, /* Parzen (B-Spline window) */
731 { Lagrange, 2.0, 1.0, 0.0, 0.0 }, /* Lagrange sinc approximation */
732 { Bohman, 1.0, 1.0, 0.0, 0.0 }, /* Bohman, 2*Cosine window */
733 { Triangle, 1.0, 1.0, 0.0, 0.0 }, /* Bartlett (triangle window) */
734 { SincFast, 4.0, 1.0, 0.0, 0.0 }, /* Raw fast sinc ("Pade"-type) */
nicolas0134bbe2010-10-10 15:55:42 +0000735 { Jinc, 2.0, 1.0, 0.0, 0.0 }, /* Lanczos2D (Jinc-Jinc) */
anthony02b4cb42010-10-10 04:54:35 +0000736 { CubicBC, 2.0, 1.0, 0.37821575509399862, 0.31089212245300069 }
nicolas0134bbe2010-10-10 15:55:42 +0000737 /* Robidoux: Keys cubic close to Lanczos2D with blur=0.958033808 */
cristy3ed852e2009-09-05 21:47:34 +0000738 };
739 /*
nicolase473f722010-10-07 00:05:13 +0000740 The known zero crossings of the Jinc() or more accurately the
741 Jinc(x*PI) function being used as a filter. It is used by the
742 "filter:lobes" for support selection, so users do not have to deal
743 with the highly irrational sizes of the 'lobes' of the Jinc
744 filter.
anthony48f77622010-10-03 14:32:31 +0000745
nicolase473f722010-10-07 00:05:13 +0000746 Values taken from
anthony48f77622010-10-03 14:32:31 +0000747 http://cose.math.bas.bg/webMathematica/webComputing/BesselZeros.jsp
nicolase473f722010-10-07 00:05:13 +0000748 using Jv-function with v=1, then dividing by PI.
cristy3ed852e2009-09-05 21:47:34 +0000749 */
750 static MagickRealType
anthony48f77622010-10-03 14:32:31 +0000751 jinc_zeros[16] =
cristy3ed852e2009-09-05 21:47:34 +0000752 {
anthonyc2d07db2010-09-15 23:47:40 +0000753 1.21966989126651,
754 2.23313059438153,
755 3.23831548416624,
756 4.24106286379607,
757 5.24276437687019,
758 6.24392168986449,
759 7.24475986871996,
760 8.24539491395205,
761 9.24589268494948,
762 10.2462933487549,
763 11.2466227948779,
764 12.2468984611381,
765 13.2471325221811,
766 14.2473337358069,
767 15.2475085630373,
768 16.247661874701
cristy3ed852e2009-09-05 21:47:34 +0000769 };
770
cristy33b1c162010-01-23 22:51:51 +0000771 /*
772 Allocate resize filter.
773 */
cristy3ed852e2009-09-05 21:47:34 +0000774 assert(image != (const Image *) NULL);
775 assert(image->signature == MagickSignature);
776 if (image->debug != MagickFalse)
777 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
778 assert(UndefinedFilter < filter && filter < SentinelFilter);
779 assert(exception != (ExceptionInfo *) NULL);
780 assert(exception->signature == MagickSignature);
cristy73bd4a52010-10-05 11:24:23 +0000781 resize_filter=(ResizeFilter *) AcquireMagickMemory(sizeof(*resize_filter));
cristy3ed852e2009-09-05 21:47:34 +0000782 if (resize_filter == (ResizeFilter *) NULL)
783 ThrowFatalException(ResourceLimitFatalError,"MemoryAllocationFailed");
cristy33b1c162010-01-23 22:51:51 +0000784 /*
785 Defaults for the requested filter.
786 */
787 filter_type=mapping[filter].filter;
788 window_type=mapping[filter].window;
anthony48f77622010-10-03 14:32:31 +0000789 /* Cylindrical Filters should use Jinc instead of Sinc */
anthonyb6d08c52010-09-13 01:17:04 +0000790 if (cylindrical != MagickFalse)
cristy33b1c162010-01-23 22:51:51 +0000791 switch (filter_type)
792 {
793 case SincFilter:
anthony48f77622010-10-03 14:32:31 +0000794 /* Promote 1D Sinc Filter to a 2D Jinc filter. */
anthonyb6d08c52010-09-13 01:17:04 +0000795 if ( filter != SincFilter )
anthony48f77622010-10-03 14:32:31 +0000796 filter_type=JincFilter;
cristy33b1c162010-01-23 22:51:51 +0000797 break;
anthonyba5a7c32010-09-15 02:42:25 +0000798 case SincFastFilter:
nicolas07bac812010-09-19 18:47:02 +0000799 /* Ditto for SincFast variant */
anthonyba5a7c32010-09-15 02:42:25 +0000800 if ( filter != SincFastFilter )
anthony48f77622010-10-03 14:32:31 +0000801 filter_type=JincFilter;
anthony7bdc0ed2010-09-15 01:52:32 +0000802 break;
anthony61b5ddd2010-10-05 02:33:31 +0000803
cristy33b1c162010-01-23 22:51:51 +0000804 case LanczosFilter:
nicolas45b58a92010-10-07 15:46:39 +0000805 /* Promote Lanczos from a Sinc-Sinc to a Jinc-Jinc. */
anthony48f77622010-10-03 14:32:31 +0000806 filter_type=JincFilter;
807 window_type=JincFilter;
cristy33b1c162010-01-23 22:51:51 +0000808 break;
cristya782ecf2010-01-25 02:59:14 +0000809 default:
810 break;
cristy3ed852e2009-09-05 21:47:34 +0000811 }
anthony61b5ddd2010-10-05 02:33:31 +0000812 else
813 switch (filter_type)
814 {
815 case Lanczos2DFilter:
nicolas45b58a92010-10-07 15:46:39 +0000816 /* Demote to a 2-lobe Sinc-Sinc for orthogonal use. */
anthony61b5ddd2010-10-05 02:33:31 +0000817 window_type=SincFastFilter;
818 break;
819 default:
820 break;
821 }
822
cristy3ed852e2009-09-05 21:47:34 +0000823 artifact=GetImageArtifact(image,"filter:filter");
cristy33b1c162010-01-23 22:51:51 +0000824 if (artifact != (const char *) NULL)
825 {
cristy9af9b5d2010-08-15 17:04:28 +0000826 option=ParseMagickOption(MagickFilterOptions,MagickFalse,artifact);
cristy33b1c162010-01-23 22:51:51 +0000827 if ((UndefinedFilter < option) && (option < SentinelFilter))
anthony61b5ddd2010-10-05 02:33:31 +0000828 { /* Raw filter request - no window function. */
cristy33b1c162010-01-23 22:51:51 +0000829 filter_type=(FilterTypes) option;
830 window_type=BoxFilter;
831 }
832 if (option == LanczosFilter)
anthony61b5ddd2010-10-05 02:33:31 +0000833 { /* Lanczos is not a real filter but a self windowing Sinc/Jinc. */
anthony853d6972010-10-08 06:01:31 +0000834 filter_type=cylindrical != MagickFalse ? JincFilter : LanczosFilter;
anthony48f77622010-10-03 14:32:31 +0000835 window_type=cylindrical != MagickFalse ? JincFilter : SincFastFilter;
cristy33b1c162010-01-23 22:51:51 +0000836 }
nicolas07bac812010-09-19 18:47:02 +0000837 /* Filter override with a specific window function. */
cristy33b1c162010-01-23 22:51:51 +0000838 artifact=GetImageArtifact(image,"filter:window");
839 if (artifact != (const char *) NULL)
840 {
cristy9af9b5d2010-08-15 17:04:28 +0000841 option=ParseMagickOption(MagickFilterOptions,MagickFalse,artifact);
cristy33b1c162010-01-23 22:51:51 +0000842 if ((UndefinedFilter < option) && (option < SentinelFilter))
843 {
844 if (option != LanczosFilter)
845 window_type=(FilterTypes) option;
cristy9af9b5d2010-08-15 17:04:28 +0000846 else
anthony48f77622010-10-03 14:32:31 +0000847 window_type=cylindrical != MagickFalse ? JincFilter :
cristy03dbbd22010-09-19 23:04:47 +0000848 SincFastFilter;
cristy9af9b5d2010-08-15 17:04:28 +0000849 }
cristy33b1c162010-01-23 22:51:51 +0000850 }
cristy3ed852e2009-09-05 21:47:34 +0000851 }
cristy33b1c162010-01-23 22:51:51 +0000852 else
853 {
anthony48f77622010-10-03 14:32:31 +0000854 /* Window specified, but no filter function? Assume Sinc/Jinc. */
cristy33b1c162010-01-23 22:51:51 +0000855 artifact=GetImageArtifact(image,"filter:window");
856 if (artifact != (const char *) NULL)
857 {
858 option=ParseMagickOption(MagickFilterOptions,MagickFalse,
859 artifact);
860 if ((UndefinedFilter < option) && (option < SentinelFilter))
861 {
anthony61b5ddd2010-10-05 02:33:31 +0000862 filter_type=cylindrical != MagickFalse ?
863 JincFilter : SincFastFilter;
cristy19eb6412010-04-23 14:42:29 +0000864 window_type=(FilterTypes) option;
cristy33b1c162010-01-23 22:51:51 +0000865 }
866 }
867 }
nicolas07bac812010-09-19 18:47:02 +0000868 /* Assign the real functions to use for the filters selected. */
cristy33b1c162010-01-23 22:51:51 +0000869 resize_filter->filter=filters[filter_type].function;
anthony61b5ddd2010-10-05 02:33:31 +0000870 resize_filter->support=filters[filter_type].lobes;
cristy33b1c162010-01-23 22:51:51 +0000871 resize_filter->window=filters[window_type].function;
872 resize_filter->scale=filters[window_type].scale;
cristy3ed852e2009-09-05 21:47:34 +0000873 resize_filter->signature=MagickSignature;
anthony61b5ddd2010-10-05 02:33:31 +0000874
anthony10b8bc82010-10-02 12:48:46 +0000875 /* Filter blur -- scaling both filter and support window. */
876 resize_filter->blur=blur;
877 artifact=GetImageArtifact(image,"filter:blur");
878 if (artifact != (const char *) NULL)
879 resize_filter->blur=StringToDouble(artifact);
880 if (resize_filter->blur < MagickEpsilon)
881 resize_filter->blur=(MagickRealType) MagickEpsilon;
882
883 if (cylindrical != MagickFalse)
884 switch (filter_type)
885 {
886 case PointFilter:
887 case BoxFilter:
888 /* Support for Cylindrical Box should be sqrt(2)/2 */
cristy1c9bb452010-10-03 16:48:33 +0000889 resize_filter->support=(MagickRealType) MagickSQ1_2;
anthony10b8bc82010-10-02 12:48:46 +0000890 break;
891 case GaussianFilter:
892 /* Cylindrical Gaussian should have a sigma of sqrt(2)/2
893 * and not the default sigma of 1/2 - so use blur to enlarge
894 * and adjust support so actual practical support = 2.0 by default
895 */
896 resize_filter->blur *= MagickSQ2;
cristy1c9bb452010-10-03 16:48:33 +0000897 resize_filter->support = (MagickRealType) MagickSQ2; /* which times blur => 2.0 */
anthony10b8bc82010-10-02 12:48:46 +0000898 break;
anthony81b8bf92010-10-02 13:54:34 +0000899 default:
900 break;
anthony10b8bc82010-10-02 12:48:46 +0000901 }
anthony61b5ddd2010-10-05 02:33:31 +0000902 else
903 switch (filter_type)
904 {
905 case Lanczos2DFilter:
anthony853d6972010-10-08 06:01:31 +0000906 /* Demote to a 2-lobe Lanczos (Sinc-Sinc) for orthogonal use. */
anthony61b5ddd2010-10-05 02:33:31 +0000907 resize_filter->filter=SincFast;
908 break;
909 default:
910 break;
911 }
912
anthony2d9b8b52010-09-14 08:31:07 +0000913 /* Filter support overrides. */
cristy3ed852e2009-09-05 21:47:34 +0000914 artifact=GetImageArtifact(image,"filter:lobes");
cristy33b1c162010-01-23 22:51:51 +0000915 if (artifact != (const char *) NULL)
916 {
cristybb503372010-05-27 20:51:26 +0000917 ssize_t
cristy33b1c162010-01-23 22:51:51 +0000918 lobes;
919
cristy96b16132010-08-29 17:19:52 +0000920 lobes=(ssize_t) StringToLong(artifact);
cristy33b1c162010-01-23 22:51:51 +0000921 if (lobes < 1)
922 lobes=1;
923 resize_filter->support=(MagickRealType) lobes;
cristy3ed852e2009-09-05 21:47:34 +0000924 }
anthony61b5ddd2010-10-05 02:33:31 +0000925 /* convert Jinc lobes to a real support value */
926 if (resize_filter->filter == Jinc)
927 {
928 if (resize_filter->support > 16)
929 resize_filter->support=jinc_zeros[15]; /* largest entry in table */
930 else
931 resize_filter->support = jinc_zeros[((long)resize_filter->support)-1];
932 }
933 /* expert override of the support setting */
cristy3ed852e2009-09-05 21:47:34 +0000934 artifact=GetImageArtifact(image,"filter:support");
935 if (artifact != (const char *) NULL)
cristy33b1c162010-01-23 22:51:51 +0000936 resize_filter->support=fabs(StringToDouble(artifact));
937 /*
nicolas07bac812010-09-19 18:47:02 +0000938 Scale windowing function separatally to the support 'clipping'
939 window that calling operator is planning to actually use. (Expert
940 override)
cristy3ed852e2009-09-05 21:47:34 +0000941 */
anthony55f12332010-09-10 01:13:02 +0000942 resize_filter->window_support=resize_filter->support; /* default */
cristy3ed852e2009-09-05 21:47:34 +0000943 artifact=GetImageArtifact(image,"filter:win-support");
944 if (artifact != (const char *) NULL)
cristy33b1c162010-01-23 22:51:51 +0000945 resize_filter->window_support=fabs(StringToDouble(artifact));
946 /*
anthony1f90a6b2010-09-14 08:56:31 +0000947 Adjust window function scaling to the windowing support for
948 weighting function. This avoids a division on every filter call.
anthony55f12332010-09-10 01:13:02 +0000949 */
950 resize_filter->scale /= resize_filter->window_support;
951 /*
nicolas07bac812010-09-19 18:47:02 +0000952 Set Cubic Spline B,C values, calculate Cubic coefficients.
cristy33b1c162010-01-23 22:51:51 +0000953 */
cristy3ed852e2009-09-05 21:47:34 +0000954 B=0.0;
955 C=0.0;
cristy33b1c162010-01-23 22:51:51 +0000956 if ((filters[filter_type].function == CubicBC) ||
957 (filters[window_type].function == CubicBC))
958 {
anthony2d9b8b52010-09-14 08:31:07 +0000959 B=filters[filter_type].B;
960 C=filters[filter_type].C;
961 if (filters[window_type].function == CubicBC)
cristy33b1c162010-01-23 22:51:51 +0000962 {
anthony2d9b8b52010-09-14 08:31:07 +0000963 B=filters[window_type].B;
964 C=filters[window_type].C;
cristy33b1c162010-01-23 22:51:51 +0000965 }
cristy33b1c162010-01-23 22:51:51 +0000966 artifact=GetImageArtifact(image,"filter:b");
cristy3ed852e2009-09-05 21:47:34 +0000967 if (artifact != (const char *) NULL)
cristy33b1c162010-01-23 22:51:51 +0000968 {
969 B=StringToDouble(artifact);
nicolas07bac812010-09-19 18:47:02 +0000970 C=(1.0-B)/2.0; /* Calculate C as if it is a Keys cubic filter. */
cristy33b1c162010-01-23 22:51:51 +0000971 artifact=GetImageArtifact(image,"filter:c");
972 if (artifact != (const char *) NULL)
973 C=StringToDouble(artifact);
974 }
975 else
976 {
977 artifact=GetImageArtifact(image,"filter:c");
978 if (artifact != (const char *) NULL)
979 {
980 C=StringToDouble(artifact);
nicolas07bac812010-09-19 18:47:02 +0000981 B=1.0-2.0*C; /* Calculate B as if it is a Keys cubic filter. */
cristy33b1c162010-01-23 22:51:51 +0000982 }
983 }
984 /*
nicolas07bac812010-09-19 18:47:02 +0000985 Convert B,C values into Cubic Coefficents. See CubicBC().
cristy33b1c162010-01-23 22:51:51 +0000986 */
987 resize_filter->cubic[0]=(6.0-2.0*B)/6.0;
cristy3ed852e2009-09-05 21:47:34 +0000988 resize_filter->cubic[1]=0.0;
cristy33b1c162010-01-23 22:51:51 +0000989 resize_filter->cubic[2]=(-18.0+12.0*B+6.0*C)/6.0;
990 resize_filter->cubic[3]=(12.0-9.0*B-6.0*C)/6.0;
991 resize_filter->cubic[4]=(8.0*B+24.0*C)/6.0;
992 resize_filter->cubic[5]=(-12.0*B-48.0*C)/6.0;
993 resize_filter->cubic[6]=(6.0*B+30.0*C)/6.0;
nicolas58c77cd2010-09-20 15:51:39 +0000994 resize_filter->cubic[7]=(-B-6.0*C)/6.0;
cristy3ed852e2009-09-05 21:47:34 +0000995 }
anthony55f12332010-09-10 01:13:02 +0000996 /*
nicolas07bac812010-09-19 18:47:02 +0000997 Expert Option Request for verbose details of the resulting filter.
anthony55f12332010-09-10 01:13:02 +0000998 */
anthonye06e4c12010-09-15 04:03:52 +0000999#if defined(MAGICKCORE_OPENMP_SUPPORT)
anthony72949792010-10-08 04:44:56 +00001000 #pragma omp single
1001 {
anthonye06e4c12010-09-15 04:03:52 +00001002#endif
1003 artifact=GetImageArtifact(image,"filter:verbose");
1004 if (artifact != (const char *) NULL)
1005 {
1006 double
1007 support,
1008 x;
cristy3ed852e2009-09-05 21:47:34 +00001009
nicolas07bac812010-09-19 18:47:02 +00001010 /*
anthony463be1d2010-09-26 01:07:36 +00001011 Set the weighting function properly when the weighting
1012 function may not exactly match the filter of the same name.
1013 EG: a Point filter really uses a Box weighting function
1014 with a different support than is typically used.
1015
anthonye06e4c12010-09-15 04:03:52 +00001016 */
anthony463be1d2010-09-26 01:07:36 +00001017 if (resize_filter->filter == Box) filter_type=BoxFilter;
1018 if (resize_filter->filter == Sinc) filter_type=SincFilter;
1019 if (resize_filter->filter == SincFast) filter_type=SincFastFilter;
anthony61b5ddd2010-10-05 02:33:31 +00001020 if (resize_filter->filter == Jinc) filter_type=JincFilter;
anthony463be1d2010-09-26 01:07:36 +00001021 if (resize_filter->filter == CubicBC) filter_type=CubicFilter;
anthonye06e4c12010-09-15 04:03:52 +00001022 /*
nicolas07bac812010-09-19 18:47:02 +00001023 Report Filter Details.
anthonye06e4c12010-09-15 04:03:52 +00001024 */
cristy03dbbd22010-09-19 23:04:47 +00001025 support=GetResizeFilterSupport(resize_filter); /* support range */
anthony61b5ddd2010-10-05 02:33:31 +00001026 (void) fprintf(stdout,"# Resize Filter (for graphing)\n#\n");
cristy03dbbd22010-09-19 23:04:47 +00001027 (void) fprintf(stdout,"# filter = %s\n",MagickOptionToMnemonic(
1028 MagickFilterOptions,filter_type));
1029 (void) fprintf(stdout,"# window = %s\n",MagickOptionToMnemonic(
anthony463be1d2010-09-26 01:07:36 +00001030 MagickFilterOptions, window_type));
cristy03dbbd22010-09-19 23:04:47 +00001031 (void) fprintf(stdout,"# support = %.*g\n",GetMagickPrecision(),
anthony463be1d2010-09-26 01:07:36 +00001032 (double) resize_filter->support);
cristy03dbbd22010-09-19 23:04:47 +00001033 (void) fprintf(stdout,"# win-support = %.*g\n",GetMagickPrecision(),
anthony463be1d2010-09-26 01:07:36 +00001034 (double) resize_filter->window_support);
cristy03dbbd22010-09-19 23:04:47 +00001035 (void) fprintf(stdout,"# blur = %.*g\n",GetMagickPrecision(),
1036 (double) resize_filter->blur);
anthony463be1d2010-09-26 01:07:36 +00001037 (void) fprintf(stdout,"# blurred_support = %.*g\n",GetMagickPrecision(),
cristy03dbbd22010-09-19 23:04:47 +00001038 (double) support);
1039 (void) fprintf(stdout,"# B,C = %.*g,%.*g\n",GetMagickPrecision(),
1040 (double) B,GetMagickPrecision(),(double) C);
anthony61b5ddd2010-10-05 02:33:31 +00001041 (void) fprintf(stdout,"\n");
anthonye06e4c12010-09-15 04:03:52 +00001042 /*
nicolas07bac812010-09-19 18:47:02 +00001043 Output values of resulting filter graph -- for graphing
1044 filter result.
anthonye06e4c12010-09-15 04:03:52 +00001045 */
1046 for (x=0.0; x <= support; x+=0.01f)
cristy03dbbd22010-09-19 23:04:47 +00001047 (void) fprintf(stdout,"%5.2lf\t%.*g\n",x,GetMagickPrecision(),
1048 (double) GetResizeFilterWeight(resize_filter,x));
nicolas07bac812010-09-19 18:47:02 +00001049 /* A final value so gnuplot can graph the 'stop' properly. */
cristy03dbbd22010-09-19 23:04:47 +00001050 (void) fprintf(stdout,"%5.2lf\t%.*g\n",support,GetMagickPrecision(),
1051 0.0);
anthonye06e4c12010-09-15 04:03:52 +00001052 }
anthony72949792010-10-08 04:44:56 +00001053 /* output the above once only for each image, and each setting */
cristybb66d9c2010-10-09 01:40:31 +00001054 (void) DeleteImageArtifact((Image *) image,"filter:verbose");
anthonye06e4c12010-09-15 04:03:52 +00001055#if defined(MAGICKCORE_OPENMP_SUPPORT)
anthony61b5ddd2010-10-05 02:33:31 +00001056 }
anthonye06e4c12010-09-15 04:03:52 +00001057#endif
cristy3ed852e2009-09-05 21:47:34 +00001058 return(resize_filter);
1059}
1060
1061/*
1062%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1063% %
1064% %
1065% %
1066% A d a p t i v e R e s i z e I m a g e %
1067% %
1068% %
1069% %
1070%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1071%
1072% AdaptiveResizeImage() adaptively resize image with pixel resampling.
1073%
1074% The format of the AdaptiveResizeImage method is:
1075%
cristy9af9b5d2010-08-15 17:04:28 +00001076% Image *AdaptiveResizeImage(const Image *image,const size_t columns,
1077% const size_t rows,ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +00001078%
1079% A description of each parameter follows:
1080%
1081% o image: the image.
1082%
1083% o columns: the number of columns in the resized image.
1084%
1085% o rows: the number of rows in the resized image.
1086%
1087% o exception: return any errors or warnings in this structure.
1088%
1089*/
1090MagickExport Image *AdaptiveResizeImage(const Image *image,
cristybb503372010-05-27 20:51:26 +00001091 const size_t columns,const size_t rows,ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +00001092{
1093#define AdaptiveResizeImageTag "Resize/Image"
1094
cristyc4c8d132010-01-07 01:58:38 +00001095 CacheView
1096 *resize_view;
1097
cristy3ed852e2009-09-05 21:47:34 +00001098 Image
1099 *resize_image;
1100
cristy3ed852e2009-09-05 21:47:34 +00001101 MagickBooleanType
1102 proceed;
1103
1104 MagickPixelPacket
1105 pixel;
1106
1107 PointInfo
1108 offset;
1109
1110 ResampleFilter
1111 *resample_filter;
1112
cristy9af9b5d2010-08-15 17:04:28 +00001113 ssize_t
1114 y;
1115
cristy3ed852e2009-09-05 21:47:34 +00001116 /*
1117 Adaptively resize image.
1118 */
1119 assert(image != (const Image *) NULL);
1120 assert(image->signature == MagickSignature);
1121 if (image->debug != MagickFalse)
1122 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
1123 assert(exception != (ExceptionInfo *) NULL);
1124 assert(exception->signature == MagickSignature);
1125 if ((columns == 0) || (rows == 0))
1126 return((Image *) NULL);
1127 if ((columns == image->columns) && (rows == image->rows))
1128 return(CloneImage(image,0,0,MagickTrue,exception));
1129 resize_image=CloneImage(image,columns,rows,MagickTrue,exception);
1130 if (resize_image == (Image *) NULL)
1131 return((Image *) NULL);
1132 if (SetImageStorageClass(resize_image,DirectClass) == MagickFalse)
1133 {
1134 InheritException(exception,&resize_image->exception);
1135 resize_image=DestroyImage(resize_image);
1136 return((Image *) NULL);
1137 }
1138 GetMagickPixelPacket(image,&pixel);
1139 resample_filter=AcquireResampleFilter(image,exception);
cristy0c7e9eb2010-09-30 14:23:37 +00001140 (void) SetResampleFilter(resample_filter,PointFilter,1.0);
anthonyccf239d2010-09-30 04:23:46 +00001141 (void) SetResampleFilterInterpolateMethod(resample_filter,
cristy0c7e9eb2010-09-30 14:23:37 +00001142 MeshInterpolatePixel);
cristy3ed852e2009-09-05 21:47:34 +00001143 resize_view=AcquireCacheView(resize_image);
cristybb503372010-05-27 20:51:26 +00001144 for (y=0; y < (ssize_t) resize_image->rows; y++)
cristy3ed852e2009-09-05 21:47:34 +00001145 {
1146 register IndexPacket
cristyc47d1f82009-11-26 01:44:43 +00001147 *restrict resize_indexes;
cristy3ed852e2009-09-05 21:47:34 +00001148
cristybb503372010-05-27 20:51:26 +00001149 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +00001150 x;
1151
1152 register PixelPacket
cristyc47d1f82009-11-26 01:44:43 +00001153 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +00001154
1155 q=QueueCacheViewAuthenticPixels(resize_view,0,y,resize_image->columns,1,
1156 exception);
1157 if (q == (PixelPacket *) NULL)
1158 break;
1159 resize_indexes=GetCacheViewAuthenticIndexQueue(resize_view);
1160 offset.y=((MagickRealType) y*image->rows/resize_image->rows);
cristybb503372010-05-27 20:51:26 +00001161 for (x=0; x < (ssize_t) resize_image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +00001162 {
1163 offset.x=((MagickRealType) x*image->columns/resize_image->columns);
1164 (void) ResamplePixelColor(resample_filter,offset.x-0.5,offset.y-0.5,
1165 &pixel);
1166 SetPixelPacket(resize_image,&pixel,q,resize_indexes+x);
1167 q++;
1168 }
1169 if (SyncCacheViewAuthenticPixels(resize_view,exception) == MagickFalse)
1170 break;
cristy96b16132010-08-29 17:19:52 +00001171 proceed=SetImageProgress(image,AdaptiveResizeImageTag,(MagickOffsetType) y,
1172 image->rows);
cristy3ed852e2009-09-05 21:47:34 +00001173 if (proceed == MagickFalse)
1174 break;
1175 }
1176 resample_filter=DestroyResampleFilter(resample_filter);
1177 resize_view=DestroyCacheView(resize_view);
1178 return(resize_image);
1179}
1180
1181/*
1182%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1183% %
1184% %
1185% %
1186+ B e s s e l O r d e r O n e %
1187% %
1188% %
1189% %
1190%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1191%
1192% BesselOrderOne() computes the Bessel function of x of the first kind of
anthony48f77622010-10-03 14:32:31 +00001193% order 0. This is used to create the Jinc() filter function below.
cristy3ed852e2009-09-05 21:47:34 +00001194%
1195% Reduce x to |x| since j1(x)= -j1(-x), and for x in (0,8]
1196%
1197% j1(x) = x*j1(x);
1198%
1199% For x in (8,inf)
1200%
1201% j1(x) = sqrt(2/(pi*x))*(p1(x)*cos(x1)-q1(x)*sin(x1))
1202%
1203% where x1 = x-3*pi/4. Compute sin(x1) and cos(x1) as follow:
1204%
1205% cos(x1) = cos(x)cos(3pi/4)+sin(x)sin(3pi/4)
1206% = 1/sqrt(2) * (sin(x) - cos(x))
1207% sin(x1) = sin(x)cos(3pi/4)-cos(x)sin(3pi/4)
1208% = -1/sqrt(2) * (sin(x) + cos(x))
1209%
1210% The format of the BesselOrderOne method is:
1211%
1212% MagickRealType BesselOrderOne(MagickRealType x)
1213%
1214% A description of each parameter follows:
1215%
1216% o x: MagickRealType value.
1217%
1218*/
1219
1220#undef I0
1221static MagickRealType I0(MagickRealType x)
1222{
1223 MagickRealType
1224 sum,
1225 t,
1226 y;
1227
cristybb503372010-05-27 20:51:26 +00001228 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +00001229 i;
1230
1231 /*
1232 Zeroth order Bessel function of the first kind.
1233 */
1234 sum=1.0;
1235 y=x*x/4.0;
1236 t=y;
1237 for (i=2; t > MagickEpsilon; i++)
1238 {
1239 sum+=t;
1240 t*=y/((MagickRealType) i*i);
1241 }
1242 return(sum);
1243}
1244
1245#undef J1
1246static MagickRealType J1(MagickRealType x)
1247{
1248 MagickRealType
1249 p,
1250 q;
1251
cristybb503372010-05-27 20:51:26 +00001252 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +00001253 i;
1254
1255 static const double
1256 Pone[] =
1257 {
1258 0.581199354001606143928050809e+21,
1259 -0.6672106568924916298020941484e+20,
1260 0.2316433580634002297931815435e+19,
1261 -0.3588817569910106050743641413e+17,
1262 0.2908795263834775409737601689e+15,
1263 -0.1322983480332126453125473247e+13,
1264 0.3413234182301700539091292655e+10,
1265 -0.4695753530642995859767162166e+7,
1266 0.270112271089232341485679099e+4
1267 },
1268 Qone[] =
1269 {
1270 0.11623987080032122878585294e+22,
1271 0.1185770712190320999837113348e+20,
1272 0.6092061398917521746105196863e+17,
1273 0.2081661221307607351240184229e+15,
1274 0.5243710262167649715406728642e+12,
1275 0.1013863514358673989967045588e+10,
1276 0.1501793594998585505921097578e+7,
1277 0.1606931573481487801970916749e+4,
1278 0.1e+1
1279 };
1280
1281 p=Pone[8];
1282 q=Qone[8];
1283 for (i=7; i >= 0; i--)
1284 {
1285 p=p*x*x+Pone[i];
1286 q=q*x*x+Qone[i];
1287 }
1288 return(p/q);
1289}
1290
1291#undef P1
1292static MagickRealType P1(MagickRealType x)
1293{
1294 MagickRealType
1295 p,
1296 q;
1297
cristybb503372010-05-27 20:51:26 +00001298 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +00001299 i;
1300
1301 static const double
1302 Pone[] =
1303 {
1304 0.352246649133679798341724373e+5,
1305 0.62758845247161281269005675e+5,
1306 0.313539631109159574238669888e+5,
1307 0.49854832060594338434500455e+4,
1308 0.2111529182853962382105718e+3,
1309 0.12571716929145341558495e+1
1310 },
1311 Qone[] =
1312 {
1313 0.352246649133679798068390431e+5,
1314 0.626943469593560511888833731e+5,
1315 0.312404063819041039923015703e+5,
1316 0.4930396490181088979386097e+4,
1317 0.2030775189134759322293574e+3,
1318 0.1e+1
1319 };
1320
1321 p=Pone[5];
1322 q=Qone[5];
1323 for (i=4; i >= 0; i--)
1324 {
1325 p=p*(8.0/x)*(8.0/x)+Pone[i];
1326 q=q*(8.0/x)*(8.0/x)+Qone[i];
1327 }
1328 return(p/q);
1329}
1330
1331#undef Q1
1332static MagickRealType Q1(MagickRealType x)
1333{
1334 MagickRealType
1335 p,
1336 q;
1337
cristybb503372010-05-27 20:51:26 +00001338 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +00001339 i;
1340
1341 static const double
1342 Pone[] =
1343 {
1344 0.3511751914303552822533318e+3,
1345 0.7210391804904475039280863e+3,
1346 0.4259873011654442389886993e+3,
1347 0.831898957673850827325226e+2,
1348 0.45681716295512267064405e+1,
1349 0.3532840052740123642735e-1
1350 },
1351 Qone[] =
1352 {
1353 0.74917374171809127714519505e+4,
1354 0.154141773392650970499848051e+5,
1355 0.91522317015169922705904727e+4,
1356 0.18111867005523513506724158e+4,
1357 0.1038187585462133728776636e+3,
1358 0.1e+1
1359 };
1360
1361 p=Pone[5];
1362 q=Qone[5];
1363 for (i=4; i >= 0; i--)
1364 {
1365 p=p*(8.0/x)*(8.0/x)+Pone[i];
1366 q=q*(8.0/x)*(8.0/x)+Qone[i];
1367 }
1368 return(p/q);
1369}
1370
1371static MagickRealType BesselOrderOne(MagickRealType x)
1372{
1373 MagickRealType
1374 p,
1375 q;
1376
1377 if (x == 0.0)
1378 return(0.0);
1379 p=x;
1380 if (x < 0.0)
1381 x=(-x);
1382 if (x < 8.0)
1383 return(p*J1(x));
1384 q=sqrt((double) (2.0/(MagickPI*x)))*(P1(x)*(1.0/sqrt(2.0)*(sin((double) x)-
1385 cos((double) x)))-8.0/x*Q1(x)*(-1.0/sqrt(2.0)*(sin((double) x)+
1386 cos((double) x))));
1387 if (p < 0.0)
1388 q=(-q);
1389 return(q);
1390}
1391
1392/*
1393%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1394% %
1395% %
1396% %
1397+ D e s t r o y R e s i z e F i l t e r %
1398% %
1399% %
1400% %
1401%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1402%
1403% DestroyResizeFilter() destroy the resize filter.
1404%
cristya2ffd7e2010-03-10 20:50:30 +00001405% The format of the DestroyResizeFilter method is:
cristy3ed852e2009-09-05 21:47:34 +00001406%
1407% ResizeFilter *DestroyResizeFilter(ResizeFilter *resize_filter)
1408%
1409% A description of each parameter follows:
1410%
1411% o resize_filter: the resize filter.
1412%
1413*/
1414MagickExport ResizeFilter *DestroyResizeFilter(ResizeFilter *resize_filter)
1415{
1416 assert(resize_filter != (ResizeFilter *) NULL);
1417 assert(resize_filter->signature == MagickSignature);
1418 resize_filter->signature=(~MagickSignature);
1419 resize_filter=(ResizeFilter *) RelinquishMagickMemory(resize_filter);
1420 return(resize_filter);
1421}
1422
1423/*
1424%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1425% %
1426% %
1427% %
1428+ G e t R e s i z e F i l t e r S u p p o r t %
1429% %
1430% %
1431% %
1432%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1433%
1434% GetResizeFilterSupport() return the current support window size for this
1435% filter. Note that this may have been enlarged by filter:blur factor.
1436%
1437% The format of the GetResizeFilterSupport method is:
1438%
1439% MagickRealType GetResizeFilterSupport(const ResizeFilter *resize_filter)
1440%
1441% A description of each parameter follows:
1442%
1443% o filter: Image filter to use.
1444%
1445*/
1446MagickExport MagickRealType GetResizeFilterSupport(
1447 const ResizeFilter *resize_filter)
1448{
1449 assert(resize_filter != (ResizeFilter *) NULL);
1450 assert(resize_filter->signature == MagickSignature);
1451 return(resize_filter->support*resize_filter->blur);
1452}
1453
1454/*
1455%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1456% %
1457% %
1458% %
1459+ G e t R e s i z e F i l t e r W e i g h t %
1460% %
1461% %
1462% %
1463%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1464%
1465% GetResizeFilterWeight evaluates the specified resize filter at the point x
1466% which usally lies between zero and the filters current 'support' and
1467% returns the weight of the filter function at that point.
1468%
1469% The format of the GetResizeFilterWeight method is:
1470%
1471% MagickRealType GetResizeFilterWeight(const ResizeFilter *resize_filter,
1472% const MagickRealType x)
1473%
1474% A description of each parameter follows:
1475%
1476% o filter: the filter type.
1477%
1478% o x: the point.
1479%
1480*/
1481MagickExport MagickRealType GetResizeFilterWeight(
1482 const ResizeFilter *resize_filter,const MagickRealType x)
1483{
1484 MagickRealType
cristyb8385862010-09-26 01:27:51 +00001485 scale,
1486 x_blur;
cristy3ed852e2009-09-05 21:47:34 +00001487
1488 /*
1489 Windowing function - scale the weighting filter by this amount.
1490 */
1491 assert(resize_filter != (ResizeFilter *) NULL);
1492 assert(resize_filter->signature == MagickSignature);
anthony55f12332010-09-10 01:13:02 +00001493 x_blur=fabs((double) x)/resize_filter->blur; /* X offset with blur scaling */
cristy3ed852e2009-09-05 21:47:34 +00001494 if ((resize_filter->window_support < MagickEpsilon) ||
1495 (resize_filter->window == Box))
anthony55f12332010-09-10 01:13:02 +00001496 scale=1.0; /* Point or Box Filter -- avoid division by zero */
cristy3ed852e2009-09-05 21:47:34 +00001497 else
1498 {
anthony55f12332010-09-10 01:13:02 +00001499 scale=resize_filter->scale;
1500 scale=resize_filter->window(x_blur*scale,resize_filter);
cristy3ed852e2009-09-05 21:47:34 +00001501 }
anthony55f12332010-09-10 01:13:02 +00001502 return(scale*resize_filter->filter(x_blur,resize_filter));
cristy3ed852e2009-09-05 21:47:34 +00001503}
1504
1505/*
1506%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1507% %
1508% %
1509% %
1510% M a g n i f y I m a g e %
1511% %
1512% %
1513% %
1514%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1515%
1516% MagnifyImage() is a convenience method that scales an image proportionally
1517% to twice its size.
1518%
1519% The format of the MagnifyImage method is:
1520%
1521% Image *MagnifyImage(const Image *image,ExceptionInfo *exception)
1522%
1523% A description of each parameter follows:
1524%
1525% o image: the image.
1526%
1527% o exception: return any errors or warnings in this structure.
1528%
1529*/
1530MagickExport Image *MagnifyImage(const Image *image,ExceptionInfo *exception)
1531{
1532 Image
1533 *magnify_image;
1534
1535 assert(image != (Image *) NULL);
1536 assert(image->signature == MagickSignature);
1537 if (image->debug != MagickFalse)
1538 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
1539 assert(exception != (ExceptionInfo *) NULL);
1540 assert(exception->signature == MagickSignature);
1541 magnify_image=ResizeImage(image,2*image->columns,2*image->rows,CubicFilter,
1542 1.0,exception);
1543 return(magnify_image);
1544}
1545
1546/*
1547%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1548% %
1549% %
1550% %
1551% M i n i f y I m a g e %
1552% %
1553% %
1554% %
1555%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1556%
1557% MinifyImage() is a convenience method that scales an image proportionally
1558% to half its size.
1559%
1560% The format of the MinifyImage method is:
1561%
1562% Image *MinifyImage(const Image *image,ExceptionInfo *exception)
1563%
1564% A description of each parameter follows:
1565%
1566% o image: the image.
1567%
1568% o exception: return any errors or warnings in this structure.
1569%
1570*/
1571MagickExport Image *MinifyImage(const Image *image,ExceptionInfo *exception)
1572{
1573 Image
1574 *minify_image;
1575
1576 assert(image != (Image *) NULL);
1577 assert(image->signature == MagickSignature);
1578 if (image->debug != MagickFalse)
1579 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
1580 assert(exception != (ExceptionInfo *) NULL);
1581 assert(exception->signature == MagickSignature);
1582 minify_image=ResizeImage(image,image->columns/2,image->rows/2,CubicFilter,
1583 1.0,exception);
1584 return(minify_image);
1585}
1586
1587/*
1588%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1589% %
1590% %
1591% %
1592% R e s a m p l e I m a g e %
1593% %
1594% %
1595% %
1596%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1597%
1598% ResampleImage() resize image in terms of its pixel size, so that when
1599% displayed at the given resolution it will be the same size in terms of
1600% real world units as the original image at the original resolution.
1601%
1602% The format of the ResampleImage method is:
1603%
1604% Image *ResampleImage(Image *image,const double x_resolution,
1605% const double y_resolution,const FilterTypes filter,const double blur,
1606% ExceptionInfo *exception)
1607%
1608% A description of each parameter follows:
1609%
1610% o image: the image to be resized to fit the given resolution.
1611%
1612% o x_resolution: the new image x resolution.
1613%
1614% o y_resolution: the new image y resolution.
1615%
1616% o filter: Image filter to use.
1617%
1618% o blur: the blur factor where > 1 is blurry, < 1 is sharp.
1619%
1620*/
1621MagickExport Image *ResampleImage(const Image *image,const double x_resolution,
1622 const double y_resolution,const FilterTypes filter,const double blur,
1623 ExceptionInfo *exception)
1624{
1625#define ResampleImageTag "Resample/Image"
1626
1627 Image
1628 *resample_image;
1629
cristybb503372010-05-27 20:51:26 +00001630 size_t
cristy3ed852e2009-09-05 21:47:34 +00001631 height,
1632 width;
1633
1634 /*
1635 Initialize sampled image attributes.
1636 */
1637 assert(image != (const Image *) NULL);
1638 assert(image->signature == MagickSignature);
1639 if (image->debug != MagickFalse)
1640 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
1641 assert(exception != (ExceptionInfo *) NULL);
1642 assert(exception->signature == MagickSignature);
cristy9af9b5d2010-08-15 17:04:28 +00001643 width=(size_t) (x_resolution*image->columns/(image->x_resolution == 0.0 ?
1644 72.0 : image->x_resolution)+0.5);
1645 height=(size_t) (y_resolution*image->rows/(image->y_resolution == 0.0 ?
1646 72.0 : image->y_resolution)+0.5);
cristy3ed852e2009-09-05 21:47:34 +00001647 resample_image=ResizeImage(image,width,height,filter,blur,exception);
1648 if (resample_image != (Image *) NULL)
1649 {
1650 resample_image->x_resolution=x_resolution;
1651 resample_image->y_resolution=y_resolution;
1652 }
1653 return(resample_image);
1654}
1655#if defined(MAGICKCORE_LQR_DELEGATE)
1656
1657/*
1658%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1659% %
1660% %
1661% %
1662% L i q u i d R e s c a l e I m a g e %
1663% %
1664% %
1665% %
1666%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1667%
1668% LiquidRescaleImage() rescales image with seam carving.
1669%
1670% The format of the LiquidRescaleImage method is:
1671%
1672% Image *LiquidRescaleImage(const Image *image,
cristybb503372010-05-27 20:51:26 +00001673% const size_t columns,const size_t rows,
cristy3ed852e2009-09-05 21:47:34 +00001674% const double delta_x,const double rigidity,ExceptionInfo *exception)
1675%
1676% A description of each parameter follows:
1677%
1678% o image: the image.
1679%
1680% o columns: the number of columns in the rescaled image.
1681%
1682% o rows: the number of rows in the rescaled image.
1683%
1684% o delta_x: maximum seam transversal step (0 means straight seams).
1685%
1686% o rigidity: introduce a bias for non-straight seams (typically 0).
1687%
1688% o exception: return any errors or warnings in this structure.
1689%
1690*/
cristy9af9b5d2010-08-15 17:04:28 +00001691MagickExport Image *LiquidRescaleImage(const Image *image,const size_t columns,
1692 const size_t rows,const double delta_x,const double rigidity,
1693 ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +00001694{
1695#define LiquidRescaleImageTag "Rescale/Image"
1696
cristyc5c6f662010-09-22 14:23:02 +00001697 CacheView
1698 *rescale_view;
1699
cristy3ed852e2009-09-05 21:47:34 +00001700 const char
1701 *map;
1702
1703 guchar
1704 *packet;
1705
1706 Image
1707 *rescale_image;
1708
1709 int
1710 x,
1711 y;
1712
1713 LqrCarver
1714 *carver;
1715
1716 LqrRetVal
1717 lqr_status;
1718
1719 MagickBooleanType
1720 status;
1721
1722 MagickPixelPacket
1723 pixel;
1724
1725 unsigned char
1726 *pixels;
1727
1728 /*
1729 Liquid rescale image.
1730 */
1731 assert(image != (const Image *) NULL);
1732 assert(image->signature == MagickSignature);
1733 if (image->debug != MagickFalse)
1734 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
1735 assert(exception != (ExceptionInfo *) NULL);
1736 assert(exception->signature == MagickSignature);
1737 if ((columns == 0) || (rows == 0))
1738 return((Image *) NULL);
1739 if ((columns == image->columns) && (rows == image->rows))
1740 return(CloneImage(image,0,0,MagickTrue,exception));
1741 if ((columns <= 2) || (rows <= 2))
cristyd1bb3bc2010-09-07 00:43:58 +00001742 return(ResizeImage(image,columns,rows,image->filter,image->blur,exception));
cristy3ed852e2009-09-05 21:47:34 +00001743 if ((columns >= (2*image->columns)) || (rows >= (2*image->rows)))
1744 {
1745 Image
1746 *resize_image;
1747
cristybb503372010-05-27 20:51:26 +00001748 size_t
cristy3ed852e2009-09-05 21:47:34 +00001749 height,
1750 width;
1751
1752 /*
1753 Honor liquid resize size limitations.
1754 */
1755 for (width=image->columns; columns >= (2*width-1); width*=2);
1756 for (height=image->rows; rows >= (2*height-1); height*=2);
1757 resize_image=ResizeImage(image,width,height,image->filter,image->blur,
1758 exception);
1759 if (resize_image == (Image *) NULL)
1760 return((Image *) NULL);
1761 rescale_image=LiquidRescaleImage(resize_image,columns,rows,delta_x,
1762 rigidity,exception);
1763 resize_image=DestroyImage(resize_image);
1764 return(rescale_image);
1765 }
1766 map="RGB";
1767 if (image->matte == MagickFalse)
1768 map="RGBA";
1769 if (image->colorspace == CMYKColorspace)
1770 {
1771 map="CMYK";
1772 if (image->matte == MagickFalse)
1773 map="CMYKA";
1774 }
1775 pixels=(unsigned char *) AcquireQuantumMemory(image->columns,image->rows*
1776 strlen(map)*sizeof(*pixels));
1777 if (pixels == (unsigned char *) NULL)
1778 return((Image *) NULL);
1779 status=ExportImagePixels(image,0,0,image->columns,image->rows,map,CharPixel,
1780 pixels,exception);
1781 if (status == MagickFalse)
1782 {
1783 pixels=(unsigned char *) RelinquishMagickMemory(pixels);
1784 ThrowImageException(ResourceLimitError,"MemoryAllocationFailed");
1785 }
1786 carver=lqr_carver_new(pixels,image->columns,image->rows,strlen(map));
1787 if (carver == (LqrCarver *) NULL)
1788 {
1789 pixels=(unsigned char *) RelinquishMagickMemory(pixels);
1790 ThrowImageException(ResourceLimitError,"MemoryAllocationFailed");
1791 }
1792 lqr_status=lqr_carver_init(carver,(int) delta_x,rigidity);
1793 lqr_status=lqr_carver_resize(carver,columns,rows);
1794 rescale_image=CloneImage(image,lqr_carver_get_width(carver),
1795 lqr_carver_get_height(carver),MagickTrue,exception);
1796 if (rescale_image == (Image *) NULL)
1797 {
1798 pixels=(unsigned char *) RelinquishMagickMemory(pixels);
1799 return((Image *) NULL);
1800 }
1801 if (SetImageStorageClass(rescale_image,DirectClass) == MagickFalse)
1802 {
1803 InheritException(exception,&rescale_image->exception);
1804 rescale_image=DestroyImage(rescale_image);
1805 return((Image *) NULL);
1806 }
1807 GetMagickPixelPacket(rescale_image,&pixel);
1808 (void) lqr_carver_scan_reset(carver);
cristyc5c6f662010-09-22 14:23:02 +00001809 rescale_view=AcquireCacheView(rescale_image);
cristy3ed852e2009-09-05 21:47:34 +00001810 while (lqr_carver_scan(carver,&x,&y,&packet) != 0)
1811 {
1812 register IndexPacket
cristyc47d1f82009-11-26 01:44:43 +00001813 *restrict rescale_indexes;
cristy3ed852e2009-09-05 21:47:34 +00001814
1815 register PixelPacket
cristyc47d1f82009-11-26 01:44:43 +00001816 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +00001817
anthony22aad252010-09-23 06:59:07 +00001818 q=QueueCacheViewAuthenticPixels(rescale_view,x,y,1,1,exception);
cristy3ed852e2009-09-05 21:47:34 +00001819 if (q == (PixelPacket *) NULL)
1820 break;
cristyc5c6f662010-09-22 14:23:02 +00001821 rescale_indexes=GetCacheViewAuthenticIndexQueue(rescale_view);
cristy3ed852e2009-09-05 21:47:34 +00001822 pixel.red=QuantumRange*(packet[0]/255.0);
1823 pixel.green=QuantumRange*(packet[1]/255.0);
1824 pixel.blue=QuantumRange*(packet[2]/255.0);
1825 if (image->colorspace != CMYKColorspace)
1826 {
1827 if (image->matte == MagickFalse)
1828 pixel.opacity=QuantumRange*(packet[3]/255.0);
1829 }
1830 else
1831 {
1832 pixel.index=QuantumRange*(packet[3]/255.0);
1833 if (image->matte == MagickFalse)
1834 pixel.opacity=QuantumRange*(packet[4]/255.0);
1835 }
1836 SetPixelPacket(rescale_image,&pixel,q,rescale_indexes);
cristyc5c6f662010-09-22 14:23:02 +00001837 if (SyncCacheViewAuthenticPixels(rescale_view,exception) == MagickFalse)
cristy3ed852e2009-09-05 21:47:34 +00001838 break;
1839 }
cristyc5c6f662010-09-22 14:23:02 +00001840 rescale_view=DestroyCacheView(rescale_view);
cristy3ed852e2009-09-05 21:47:34 +00001841 /*
1842 Relinquish resources.
1843 */
1844 lqr_carver_destroy(carver);
1845 return(rescale_image);
1846}
1847#else
1848MagickExport Image *LiquidRescaleImage(const Image *image,
cristy9af9b5d2010-08-15 17:04:28 +00001849 const size_t magick_unused(columns),const size_t magick_unused(rows),
1850 const double magick_unused(delta_x),const double magick_unused(rigidity),
1851 ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +00001852{
1853 assert(image != (const Image *) NULL);
1854 assert(image->signature == MagickSignature);
1855 if (image->debug != MagickFalse)
1856 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
1857 assert(exception != (ExceptionInfo *) NULL);
1858 assert(exception->signature == MagickSignature);
1859 (void) ThrowMagickException(exception,GetMagickModule(),MissingDelegateError,
1860 "DelegateLibrarySupportNotBuiltIn","`%s' (LQR)",image->filename);
1861 return((Image *) NULL);
1862}
1863#endif
1864
1865/*
1866%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1867% %
1868% %
1869% %
1870% R e s i z e I m a g e %
1871% %
1872% %
1873% %
1874%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1875%
1876% ResizeImage() scales an image to the desired dimensions, using the given
cristy5d824382010-09-06 14:00:17 +00001877% filter (see AcquireFilterInfo()).
cristy3ed852e2009-09-05 21:47:34 +00001878%
1879% If an undefined filter is given the filter defaults to Mitchell for a
1880% colormapped image, a image with a matte channel, or if the image is
1881% enlarged. Otherwise the filter defaults to a Lanczos.
1882%
1883% ResizeImage() was inspired by Paul Heckbert's "zoom" program.
1884%
1885% The format of the ResizeImage method is:
1886%
cristybb503372010-05-27 20:51:26 +00001887% Image *ResizeImage(Image *image,const size_t columns,
1888% const size_t rows,const FilterTypes filter,const double blur,
cristy3ed852e2009-09-05 21:47:34 +00001889% ExceptionInfo *exception)
1890%
1891% A description of each parameter follows:
1892%
1893% o image: the image.
1894%
1895% o columns: the number of columns in the scaled image.
1896%
1897% o rows: the number of rows in the scaled image.
1898%
1899% o filter: Image filter to use.
1900%
cristy9af9b5d2010-08-15 17:04:28 +00001901% o blur: the blur factor where > 1 is blurry, < 1 is sharp. Typically set
1902% this to 1.0.
cristy3ed852e2009-09-05 21:47:34 +00001903%
1904% o exception: return any errors or warnings in this structure.
1905%
1906*/
1907
1908typedef struct _ContributionInfo
1909{
1910 MagickRealType
1911 weight;
1912
cristybb503372010-05-27 20:51:26 +00001913 ssize_t
cristy3ed852e2009-09-05 21:47:34 +00001914 pixel;
1915} ContributionInfo;
1916
1917static ContributionInfo **DestroyContributionThreadSet(
1918 ContributionInfo **contribution)
1919{
cristybb503372010-05-27 20:51:26 +00001920 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +00001921 i;
1922
1923 assert(contribution != (ContributionInfo **) NULL);
cristybb503372010-05-27 20:51:26 +00001924 for (i=0; i < (ssize_t) GetOpenMPMaximumThreads(); i++)
cristy3ed852e2009-09-05 21:47:34 +00001925 if (contribution[i] != (ContributionInfo *) NULL)
1926 contribution[i]=(ContributionInfo *) RelinquishMagickMemory(
1927 contribution[i]);
cristyb41ee102010-10-04 16:46:15 +00001928 contribution=(ContributionInfo **) RelinquishMagickMemory(contribution);
cristy3ed852e2009-09-05 21:47:34 +00001929 return(contribution);
1930}
1931
1932static ContributionInfo **AcquireContributionThreadSet(const size_t count)
1933{
cristybb503372010-05-27 20:51:26 +00001934 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +00001935 i;
1936
1937 ContributionInfo
1938 **contribution;
1939
cristybb503372010-05-27 20:51:26 +00001940 size_t
cristy3ed852e2009-09-05 21:47:34 +00001941 number_threads;
1942
1943 number_threads=GetOpenMPMaximumThreads();
cristyb41ee102010-10-04 16:46:15 +00001944 contribution=(ContributionInfo **) AcquireQuantumMemory(number_threads,
cristy3ed852e2009-09-05 21:47:34 +00001945 sizeof(*contribution));
1946 if (contribution == (ContributionInfo **) NULL)
1947 return((ContributionInfo **) NULL);
1948 (void) ResetMagickMemory(contribution,0,number_threads*sizeof(*contribution));
cristybb503372010-05-27 20:51:26 +00001949 for (i=0; i < (ssize_t) number_threads; i++)
cristy3ed852e2009-09-05 21:47:34 +00001950 {
1951 contribution[i]=(ContributionInfo *) AcquireQuantumMemory(count,
1952 sizeof(**contribution));
1953 if (contribution[i] == (ContributionInfo *) NULL)
1954 return(DestroyContributionThreadSet(contribution));
1955 }
1956 return(contribution);
1957}
1958
1959static inline double MagickMax(const double x,const double y)
1960{
1961 if (x > y)
1962 return(x);
1963 return(y);
1964}
1965
1966static inline double MagickMin(const double x,const double y)
1967{
1968 if (x < y)
1969 return(x);
1970 return(y);
1971}
1972
1973static MagickBooleanType HorizontalFilter(const ResizeFilter *resize_filter,
1974 const Image *image,Image *resize_image,const MagickRealType x_factor,
cristy9af9b5d2010-08-15 17:04:28 +00001975 const MagickSizeType span,MagickOffsetType *offset,ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +00001976{
1977#define ResizeImageTag "Resize/Image"
1978
cristyfa112112010-01-04 17:48:07 +00001979 CacheView
1980 *image_view,
1981 *resize_view;
1982
cristy3ed852e2009-09-05 21:47:34 +00001983 ClassType
1984 storage_class;
1985
1986 ContributionInfo
cristyfa112112010-01-04 17:48:07 +00001987 **restrict contributions;
cristy3ed852e2009-09-05 21:47:34 +00001988
cristy3ed852e2009-09-05 21:47:34 +00001989 MagickBooleanType
1990 status;
1991
1992 MagickPixelPacket
1993 zero;
1994
1995 MagickRealType
1996 scale,
1997 support;
1998
cristy9af9b5d2010-08-15 17:04:28 +00001999 ssize_t
2000 x;
2001
cristy3ed852e2009-09-05 21:47:34 +00002002 /*
2003 Apply filter to resize horizontally from image to resize image.
2004 */
cristy5d824382010-09-06 14:00:17 +00002005 scale=MagickMax(1.0/x_factor+MagickEpsilon,1.0);
cristy3ed852e2009-09-05 21:47:34 +00002006 support=scale*GetResizeFilterSupport(resize_filter);
2007 storage_class=support > 0.5 ? DirectClass : image->storage_class;
2008 if (SetImageStorageClass(resize_image,storage_class) == MagickFalse)
2009 {
2010 InheritException(exception,&resize_image->exception);
2011 return(MagickFalse);
2012 }
2013 if (support < 0.5)
2014 {
2015 /*
nicolas07bac812010-09-19 18:47:02 +00002016 Support too small even for nearest neighbour: Reduce to point
2017 sampling.
cristy3ed852e2009-09-05 21:47:34 +00002018 */
2019 support=(MagickRealType) 0.5;
2020 scale=1.0;
2021 }
2022 contributions=AcquireContributionThreadSet((size_t) (2.0*support+3.0));
2023 if (contributions == (ContributionInfo **) NULL)
2024 {
2025 (void) ThrowMagickException(exception,GetMagickModule(),
2026 ResourceLimitError,"MemoryAllocationFailed","`%s'",image->filename);
2027 return(MagickFalse);
2028 }
2029 status=MagickTrue;
2030 scale=1.0/scale;
2031 (void) ResetMagickMemory(&zero,0,sizeof(zero));
2032 image_view=AcquireCacheView(image);
2033 resize_view=AcquireCacheView(resize_image);
cristyb5d5f722009-11-04 03:03:49 +00002034#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristy3ed852e2009-09-05 21:47:34 +00002035 #pragma omp parallel for shared(status)
2036#endif
cristybb503372010-05-27 20:51:26 +00002037 for (x=0; x < (ssize_t) resize_image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +00002038 {
cristy3ed852e2009-09-05 21:47:34 +00002039 MagickRealType
2040 center,
2041 density;
2042
2043 register const IndexPacket
cristyc47d1f82009-11-26 01:44:43 +00002044 *restrict indexes;
cristy3ed852e2009-09-05 21:47:34 +00002045
2046 register const PixelPacket
cristyc47d1f82009-11-26 01:44:43 +00002047 *restrict p;
cristy3ed852e2009-09-05 21:47:34 +00002048
cristy03dbbd22010-09-19 23:04:47 +00002049 register ContributionInfo
2050 *restrict contribution;
2051
cristy3ed852e2009-09-05 21:47:34 +00002052 register IndexPacket
cristyc47d1f82009-11-26 01:44:43 +00002053 *restrict resize_indexes;
cristy3ed852e2009-09-05 21:47:34 +00002054
cristy3ed852e2009-09-05 21:47:34 +00002055 register PixelPacket
cristyc47d1f82009-11-26 01:44:43 +00002056 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +00002057
cristy03dbbd22010-09-19 23:04:47 +00002058 register ssize_t
2059 y;
2060
cristy9af9b5d2010-08-15 17:04:28 +00002061 ssize_t
2062 n,
2063 start,
2064 stop;
2065
cristy3ed852e2009-09-05 21:47:34 +00002066 if (status == MagickFalse)
2067 continue;
2068 center=(MagickRealType) (x+0.5)/x_factor;
cristybb503372010-05-27 20:51:26 +00002069 start=(ssize_t) MagickMax(center-support+0.5,0.0);
2070 stop=(ssize_t) MagickMin(center+support+0.5,(double) image->columns);
cristy3ed852e2009-09-05 21:47:34 +00002071 density=0.0;
2072 contribution=contributions[GetOpenMPThreadId()];
2073 for (n=0; n < (stop-start); n++)
2074 {
2075 contribution[n].pixel=start+n;
2076 contribution[n].weight=GetResizeFilterWeight(resize_filter,scale*
2077 ((MagickRealType) (start+n)-center+0.5));
2078 density+=contribution[n].weight;
2079 }
2080 if ((density != 0.0) && (density != 1.0))
2081 {
cristybb503372010-05-27 20:51:26 +00002082 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +00002083 i;
2084
2085 /*
2086 Normalize.
2087 */
2088 density=1.0/density;
2089 for (i=0; i < n; i++)
2090 contribution[i].weight*=density;
2091 }
cristy9af9b5d2010-08-15 17:04:28 +00002092 p=GetCacheViewVirtualPixels(image_view,contribution[0].pixel,0,(size_t)
2093 (contribution[n-1].pixel-contribution[0].pixel+1),image->rows,exception);
cristy3ed852e2009-09-05 21:47:34 +00002094 q=QueueCacheViewAuthenticPixels(resize_view,x,0,1,resize_image->rows,
2095 exception);
2096 if ((p == (const PixelPacket *) NULL) || (q == (PixelPacket *) NULL))
2097 {
2098 status=MagickFalse;
2099 continue;
2100 }
2101 indexes=GetCacheViewVirtualIndexQueue(image_view);
2102 resize_indexes=GetCacheViewAuthenticIndexQueue(resize_view);
cristybb503372010-05-27 20:51:26 +00002103 for (y=0; y < (ssize_t) resize_image->rows; y++)
cristy3ed852e2009-09-05 21:47:34 +00002104 {
cristy3ed852e2009-09-05 21:47:34 +00002105 MagickPixelPacket
2106 pixel;
2107
2108 MagickRealType
2109 alpha;
2110
cristybb503372010-05-27 20:51:26 +00002111 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +00002112 i;
2113
cristy9af9b5d2010-08-15 17:04:28 +00002114 ssize_t
2115 j;
2116
cristy3ed852e2009-09-05 21:47:34 +00002117 pixel=zero;
2118 if (image->matte == MagickFalse)
2119 {
2120 for (i=0; i < n; i++)
2121 {
2122 j=y*(contribution[n-1].pixel-contribution[0].pixel+1)+
2123 (contribution[i].pixel-contribution[0].pixel);
2124 alpha=contribution[i].weight;
2125 pixel.red+=alpha*(p+j)->red;
2126 pixel.green+=alpha*(p+j)->green;
2127 pixel.blue+=alpha*(p+j)->blue;
2128 pixel.opacity+=alpha*(p+j)->opacity;
2129 }
cristyce70c172010-01-07 17:15:30 +00002130 SetRedPixelComponent(q,ClampRedPixelComponent(&pixel));
2131 SetGreenPixelComponent(q,ClampGreenPixelComponent(&pixel));
2132 SetBluePixelComponent(q,ClampBluePixelComponent(&pixel));
2133 SetOpacityPixelComponent(q,ClampOpacityPixelComponent(&pixel));
cristy3ed852e2009-09-05 21:47:34 +00002134 if ((image->colorspace == CMYKColorspace) &&
2135 (resize_image->colorspace == CMYKColorspace))
2136 {
2137 for (i=0; i < n; i++)
2138 {
2139 j=y*(contribution[n-1].pixel-contribution[0].pixel+1)+
2140 (contribution[i].pixel-contribution[0].pixel);
2141 alpha=contribution[i].weight;
2142 pixel.index+=alpha*indexes[j];
2143 }
cristyce70c172010-01-07 17:15:30 +00002144 resize_indexes[y]=(IndexPacket) ClampToQuantum(pixel.index);
cristy3ed852e2009-09-05 21:47:34 +00002145 }
2146 }
2147 else
2148 {
2149 MagickRealType
2150 gamma;
2151
2152 gamma=0.0;
2153 for (i=0; i < n; i++)
2154 {
2155 j=y*(contribution[n-1].pixel-contribution[0].pixel+1)+
2156 (contribution[i].pixel-contribution[0].pixel);
cristy46f08202010-01-10 04:04:21 +00002157 alpha=contribution[i].weight*QuantumScale*
2158 GetAlphaPixelComponent(p+j);
cristy3ed852e2009-09-05 21:47:34 +00002159 pixel.red+=alpha*(p+j)->red;
2160 pixel.green+=alpha*(p+j)->green;
2161 pixel.blue+=alpha*(p+j)->blue;
2162 pixel.opacity+=contribution[i].weight*(p+j)->opacity;
2163 gamma+=alpha;
2164 }
2165 gamma=1.0/(fabs((double) gamma) <= MagickEpsilon ? 1.0 : gamma);
cristyce70c172010-01-07 17:15:30 +00002166 q->red=ClampToQuantum(gamma*GetRedPixelComponent(&pixel));
2167 q->green=ClampToQuantum(gamma*GetGreenPixelComponent(&pixel));
2168 q->blue=ClampToQuantum(gamma*GetBluePixelComponent(&pixel));
2169 SetOpacityPixelComponent(q,ClampOpacityPixelComponent(&pixel));
cristy3ed852e2009-09-05 21:47:34 +00002170 if ((image->colorspace == CMYKColorspace) &&
2171 (resize_image->colorspace == CMYKColorspace))
2172 {
2173 for (i=0; i < n; i++)
2174 {
2175 j=y*(contribution[n-1].pixel-contribution[0].pixel+1)+
2176 (contribution[i].pixel-contribution[0].pixel);
cristy46f08202010-01-10 04:04:21 +00002177 alpha=contribution[i].weight*QuantumScale*
2178 GetAlphaPixelComponent(p+j);
cristyc197d1c2009-10-13 19:56:53 +00002179 pixel.index+=alpha*indexes[j];
cristy3ed852e2009-09-05 21:47:34 +00002180 }
cristyce70c172010-01-07 17:15:30 +00002181 resize_indexes[y]=(IndexPacket) ClampToQuantum(gamma*
2182 GetIndexPixelComponent(&pixel));
cristy3ed852e2009-09-05 21:47:34 +00002183 }
2184 }
2185 if ((resize_image->storage_class == PseudoClass) &&
2186 (image->storage_class == PseudoClass))
2187 {
cristybb503372010-05-27 20:51:26 +00002188 i=(ssize_t) (MagickMin(MagickMax(center,(double) start),(double) stop-
cristy3ed852e2009-09-05 21:47:34 +00002189 1.0)+0.5);
2190 j=y*(contribution[n-1].pixel-contribution[0].pixel+1)+
2191 (contribution[i-start].pixel-contribution[0].pixel);
2192 resize_indexes[y]=indexes[j];
2193 }
2194 q++;
2195 }
2196 if (SyncCacheViewAuthenticPixels(resize_view,exception) == MagickFalse)
2197 status=MagickFalse;
2198 if (image->progress_monitor != (MagickProgressMonitor) NULL)
2199 {
2200 MagickBooleanType
2201 proceed;
2202
cristyb5d5f722009-11-04 03:03:49 +00002203#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristy3ed852e2009-09-05 21:47:34 +00002204 #pragma omp critical (MagickCore_HorizontalFilter)
2205#endif
cristy9af9b5d2010-08-15 17:04:28 +00002206 proceed=SetImageProgress(image,ResizeImageTag,(*offset)++,span);
cristy3ed852e2009-09-05 21:47:34 +00002207 if (proceed == MagickFalse)
2208 status=MagickFalse;
2209 }
2210 }
2211 resize_view=DestroyCacheView(resize_view);
2212 image_view=DestroyCacheView(image_view);
2213 contributions=DestroyContributionThreadSet(contributions);
2214 return(status);
2215}
2216
2217static MagickBooleanType VerticalFilter(const ResizeFilter *resize_filter,
2218 const Image *image,Image *resize_image,const MagickRealType y_factor,
cristy9af9b5d2010-08-15 17:04:28 +00002219 const MagickSizeType span,MagickOffsetType *offset,ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +00002220{
cristyfa112112010-01-04 17:48:07 +00002221 CacheView
2222 *image_view,
2223 *resize_view;
2224
cristy3ed852e2009-09-05 21:47:34 +00002225 ClassType
2226 storage_class;
2227
2228 ContributionInfo
cristyfa112112010-01-04 17:48:07 +00002229 **restrict contributions;
cristy3ed852e2009-09-05 21:47:34 +00002230
cristy3ed852e2009-09-05 21:47:34 +00002231 MagickBooleanType
2232 status;
2233
2234 MagickPixelPacket
2235 zero;
2236
2237 MagickRealType
2238 scale,
2239 support;
2240
cristy9af9b5d2010-08-15 17:04:28 +00002241 ssize_t
2242 y;
2243
cristy3ed852e2009-09-05 21:47:34 +00002244 /*
cristy9af9b5d2010-08-15 17:04:28 +00002245 Apply filter to resize vertically from image to resize image.
cristy3ed852e2009-09-05 21:47:34 +00002246 */
cristy5d824382010-09-06 14:00:17 +00002247 scale=MagickMax(1.0/y_factor+MagickEpsilon,1.0);
cristy3ed852e2009-09-05 21:47:34 +00002248 support=scale*GetResizeFilterSupport(resize_filter);
2249 storage_class=support > 0.5 ? DirectClass : image->storage_class;
2250 if (SetImageStorageClass(resize_image,storage_class) == MagickFalse)
2251 {
2252 InheritException(exception,&resize_image->exception);
2253 return(MagickFalse);
2254 }
2255 if (support < 0.5)
2256 {
2257 /*
nicolas07bac812010-09-19 18:47:02 +00002258 Support too small even for nearest neighbour: Reduce to point
2259 sampling.
cristy3ed852e2009-09-05 21:47:34 +00002260 */
2261 support=(MagickRealType) 0.5;
2262 scale=1.0;
2263 }
2264 contributions=AcquireContributionThreadSet((size_t) (2.0*support+3.0));
2265 if (contributions == (ContributionInfo **) NULL)
2266 {
2267 (void) ThrowMagickException(exception,GetMagickModule(),
2268 ResourceLimitError,"MemoryAllocationFailed","`%s'",image->filename);
2269 return(MagickFalse);
2270 }
2271 status=MagickTrue;
2272 scale=1.0/scale;
2273 (void) ResetMagickMemory(&zero,0,sizeof(zero));
2274 image_view=AcquireCacheView(image);
2275 resize_view=AcquireCacheView(resize_image);
cristyb5d5f722009-11-04 03:03:49 +00002276#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristy3ed852e2009-09-05 21:47:34 +00002277 #pragma omp parallel for shared(status)
2278#endif
cristybb503372010-05-27 20:51:26 +00002279 for (y=0; y < (ssize_t) resize_image->rows; y++)
cristy3ed852e2009-09-05 21:47:34 +00002280 {
cristy3ed852e2009-09-05 21:47:34 +00002281 MagickRealType
2282 center,
2283 density;
2284
2285 register const IndexPacket
cristyc47d1f82009-11-26 01:44:43 +00002286 *restrict indexes;
cristy3ed852e2009-09-05 21:47:34 +00002287
2288 register const PixelPacket
cristyc47d1f82009-11-26 01:44:43 +00002289 *restrict p;
cristy3ed852e2009-09-05 21:47:34 +00002290
cristy03dbbd22010-09-19 23:04:47 +00002291 register ContributionInfo
2292 *restrict contribution;
2293
cristy3ed852e2009-09-05 21:47:34 +00002294 register IndexPacket
cristyc47d1f82009-11-26 01:44:43 +00002295 *restrict resize_indexes;
cristy3ed852e2009-09-05 21:47:34 +00002296
cristy9af9b5d2010-08-15 17:04:28 +00002297 register PixelPacket
2298 *restrict q;
2299
cristybb503372010-05-27 20:51:26 +00002300 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +00002301 x;
2302
cristy9af9b5d2010-08-15 17:04:28 +00002303 ssize_t
2304 n,
2305 start,
2306 stop;
cristy3ed852e2009-09-05 21:47:34 +00002307
2308 if (status == MagickFalse)
2309 continue;
cristy679e6962010-03-18 00:42:45 +00002310 center=(MagickRealType) (y+0.5)/y_factor;
cristybb503372010-05-27 20:51:26 +00002311 start=(ssize_t) MagickMax(center-support+0.5,0.0);
2312 stop=(ssize_t) MagickMin(center+support+0.5,(double) image->rows);
cristy3ed852e2009-09-05 21:47:34 +00002313 density=0.0;
2314 contribution=contributions[GetOpenMPThreadId()];
2315 for (n=0; n < (stop-start); n++)
2316 {
2317 contribution[n].pixel=start+n;
2318 contribution[n].weight=GetResizeFilterWeight(resize_filter,scale*
2319 ((MagickRealType) (start+n)-center+0.5));
2320 density+=contribution[n].weight;
2321 }
2322 if ((density != 0.0) && (density != 1.0))
2323 {
cristybb503372010-05-27 20:51:26 +00002324 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +00002325 i;
2326
2327 /*
2328 Normalize.
2329 */
2330 density=1.0/density;
2331 for (i=0; i < n; i++)
2332 contribution[i].weight*=density;
2333 }
2334 p=GetCacheViewVirtualPixels(image_view,0,contribution[0].pixel,
cristy9af9b5d2010-08-15 17:04:28 +00002335 image->columns,(size_t) (contribution[n-1].pixel-contribution[0].pixel+1),
2336 exception);
cristy3ed852e2009-09-05 21:47:34 +00002337 q=QueueCacheViewAuthenticPixels(resize_view,0,y,resize_image->columns,1,
2338 exception);
2339 if ((p == (const PixelPacket *) NULL) || (q == (PixelPacket *) NULL))
2340 {
2341 status=MagickFalse;
2342 continue;
2343 }
2344 indexes=GetCacheViewVirtualIndexQueue(image_view);
2345 resize_indexes=GetCacheViewAuthenticIndexQueue(resize_view);
cristybb503372010-05-27 20:51:26 +00002346 for (x=0; x < (ssize_t) resize_image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +00002347 {
cristy3ed852e2009-09-05 21:47:34 +00002348 MagickPixelPacket
2349 pixel;
2350
2351 MagickRealType
2352 alpha;
2353
cristybb503372010-05-27 20:51:26 +00002354 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +00002355 i;
2356
cristy9af9b5d2010-08-15 17:04:28 +00002357 ssize_t
2358 j;
2359
cristy3ed852e2009-09-05 21:47:34 +00002360 pixel=zero;
2361 if (image->matte == MagickFalse)
2362 {
2363 for (i=0; i < n; i++)
2364 {
cristybb503372010-05-27 20:51:26 +00002365 j=(ssize_t) ((contribution[i].pixel-contribution[0].pixel)*
cristy3ed852e2009-09-05 21:47:34 +00002366 image->columns+x);
2367 alpha=contribution[i].weight;
2368 pixel.red+=alpha*(p+j)->red;
2369 pixel.green+=alpha*(p+j)->green;
2370 pixel.blue+=alpha*(p+j)->blue;
2371 pixel.opacity+=alpha*(p+j)->opacity;
2372 }
cristyce70c172010-01-07 17:15:30 +00002373 SetRedPixelComponent(q,ClampRedPixelComponent(&pixel));
2374 SetGreenPixelComponent(q,ClampGreenPixelComponent(&pixel));
2375 SetBluePixelComponent(q,ClampBluePixelComponent(&pixel));
2376 SetOpacityPixelComponent(q,ClampOpacityPixelComponent(&pixel));
cristy3ed852e2009-09-05 21:47:34 +00002377 if ((image->colorspace == CMYKColorspace) &&
2378 (resize_image->colorspace == CMYKColorspace))
2379 {
2380 for (i=0; i < n; i++)
2381 {
cristybb503372010-05-27 20:51:26 +00002382 j=(ssize_t) ((contribution[i].pixel-contribution[0].pixel)*
cristy3ed852e2009-09-05 21:47:34 +00002383 image->columns+x);
2384 alpha=contribution[i].weight;
2385 pixel.index+=alpha*indexes[j];
2386 }
cristyce70c172010-01-07 17:15:30 +00002387 resize_indexes[x]=(IndexPacket) ClampToQuantum(pixel.index);
cristy3ed852e2009-09-05 21:47:34 +00002388 }
2389 }
2390 else
2391 {
2392 MagickRealType
2393 gamma;
2394
2395 gamma=0.0;
2396 for (i=0; i < n; i++)
2397 {
cristybb503372010-05-27 20:51:26 +00002398 j=(ssize_t) ((contribution[i].pixel-contribution[0].pixel)*
cristy3ed852e2009-09-05 21:47:34 +00002399 image->columns+x);
cristy46f08202010-01-10 04:04:21 +00002400 alpha=contribution[i].weight*QuantumScale*
2401 GetAlphaPixelComponent(p+j);
cristy3ed852e2009-09-05 21:47:34 +00002402 pixel.red+=alpha*(p+j)->red;
2403 pixel.green+=alpha*(p+j)->green;
2404 pixel.blue+=alpha*(p+j)->blue;
2405 pixel.opacity+=contribution[i].weight*(p+j)->opacity;
2406 gamma+=alpha;
2407 }
2408 gamma=1.0/(fabs((double) gamma) <= MagickEpsilon ? 1.0 : gamma);
cristyce70c172010-01-07 17:15:30 +00002409 q->red=ClampToQuantum(gamma*GetRedPixelComponent(&pixel));
2410 q->green=ClampToQuantum(gamma*GetGreenPixelComponent(&pixel));
2411 q->blue=ClampToQuantum(gamma*GetBluePixelComponent(&pixel));
2412 SetOpacityPixelComponent(q,ClampOpacityPixelComponent(&pixel));
cristy3ed852e2009-09-05 21:47:34 +00002413 if ((image->colorspace == CMYKColorspace) &&
2414 (resize_image->colorspace == CMYKColorspace))
2415 {
2416 for (i=0; i < n; i++)
2417 {
cristybb503372010-05-27 20:51:26 +00002418 j=(ssize_t) ((contribution[i].pixel-contribution[0].pixel)*
cristy3ed852e2009-09-05 21:47:34 +00002419 image->columns+x);
cristy46f08202010-01-10 04:04:21 +00002420 alpha=contribution[i].weight*QuantumScale*
2421 GetAlphaPixelComponent(p+j);
cristy3ed852e2009-09-05 21:47:34 +00002422 pixel.index+=alpha*indexes[j];
2423 }
cristyce70c172010-01-07 17:15:30 +00002424 resize_indexes[x]=(IndexPacket) ClampToQuantum(gamma*
2425 GetIndexPixelComponent(&pixel));
cristy3ed852e2009-09-05 21:47:34 +00002426 }
2427 }
2428 if ((resize_image->storage_class == PseudoClass) &&
2429 (image->storage_class == PseudoClass))
2430 {
cristybb503372010-05-27 20:51:26 +00002431 i=(ssize_t) (MagickMin(MagickMax(center,(double) start),(double) stop-
cristy3ed852e2009-09-05 21:47:34 +00002432 1.0)+0.5);
cristybb503372010-05-27 20:51:26 +00002433 j=(ssize_t) ((contribution[i-start].pixel-contribution[0].pixel)*
cristy3ed852e2009-09-05 21:47:34 +00002434 image->columns+x);
2435 resize_indexes[x]=indexes[j];
2436 }
2437 q++;
2438 }
2439 if (SyncCacheViewAuthenticPixels(resize_view,exception) == MagickFalse)
2440 status=MagickFalse;
2441 if (image->progress_monitor != (MagickProgressMonitor) NULL)
2442 {
2443 MagickBooleanType
2444 proceed;
2445
cristyb5d5f722009-11-04 03:03:49 +00002446#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristy3ed852e2009-09-05 21:47:34 +00002447 #pragma omp critical (MagickCore_VerticalFilter)
2448#endif
cristy9af9b5d2010-08-15 17:04:28 +00002449 proceed=SetImageProgress(image,ResizeImageTag,(*offset)++,span);
cristy3ed852e2009-09-05 21:47:34 +00002450 if (proceed == MagickFalse)
2451 status=MagickFalse;
2452 }
2453 }
2454 resize_view=DestroyCacheView(resize_view);
2455 image_view=DestroyCacheView(image_view);
2456 contributions=DestroyContributionThreadSet(contributions);
2457 return(status);
2458}
2459
cristybb503372010-05-27 20:51:26 +00002460MagickExport Image *ResizeImage(const Image *image,const size_t columns,
2461 const size_t rows,const FilterTypes filter,const double blur,
cristy3ed852e2009-09-05 21:47:34 +00002462 ExceptionInfo *exception)
2463{
2464#define WorkLoadFactor 0.265
2465
2466 FilterTypes
2467 filter_type;
2468
2469 Image
2470 *filter_image,
2471 *resize_image;
2472
cristy9af9b5d2010-08-15 17:04:28 +00002473 MagickOffsetType
2474 offset;
2475
cristy3ed852e2009-09-05 21:47:34 +00002476 MagickRealType
2477 x_factor,
2478 y_factor;
2479
2480 MagickSizeType
2481 span;
2482
2483 MagickStatusType
2484 status;
2485
2486 ResizeFilter
2487 *resize_filter;
2488
cristy3ed852e2009-09-05 21:47:34 +00002489 /*
2490 Acquire resize image.
2491 */
2492 assert(image != (Image *) NULL);
2493 assert(image->signature == MagickSignature);
2494 if (image->debug != MagickFalse)
2495 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
2496 assert(exception != (ExceptionInfo *) NULL);
2497 assert(exception->signature == MagickSignature);
2498 if ((columns == 0) || (rows == 0))
2499 ThrowImageException(ImageError,"NegativeOrZeroImageSize");
2500 if ((columns == image->columns) && (rows == image->rows) &&
2501 (filter == UndefinedFilter) && (blur == 1.0))
2502 return(CloneImage(image,0,0,MagickTrue,exception));
2503 resize_image=CloneImage(image,columns,rows,MagickTrue,exception);
2504 if (resize_image == (Image *) NULL)
2505 return(resize_image);
2506 /*
2507 Acquire resize filter.
2508 */
2509 x_factor=(MagickRealType) columns/(MagickRealType) image->columns;
2510 y_factor=(MagickRealType) rows/(MagickRealType) image->rows;
2511 if ((x_factor*y_factor) > WorkLoadFactor)
2512 filter_image=CloneImage(image,columns,image->rows,MagickTrue,exception);
2513 else
2514 filter_image=CloneImage(image,image->columns,rows,MagickTrue,exception);
2515 if (filter_image == (Image *) NULL)
2516 return(DestroyImage(resize_image));
2517 filter_type=LanczosFilter;
2518 if (filter != UndefinedFilter)
2519 filter_type=filter;
2520 else
2521 if ((x_factor == 1.0) && (y_factor == 1.0))
2522 filter_type=PointFilter;
2523 else
2524 if ((image->storage_class == PseudoClass) ||
2525 (image->matte != MagickFalse) || ((x_factor*y_factor) > 1.0))
2526 filter_type=MitchellFilter;
2527 resize_filter=AcquireResizeFilter(image,filter_type,blur,MagickFalse,
2528 exception);
2529 /*
2530 Resize image.
2531 */
cristy9af9b5d2010-08-15 17:04:28 +00002532 offset=0;
cristy3ed852e2009-09-05 21:47:34 +00002533 if ((x_factor*y_factor) > WorkLoadFactor)
2534 {
2535 span=(MagickSizeType) (filter_image->columns+rows);
2536 status=HorizontalFilter(resize_filter,image,filter_image,x_factor,span,
cristy9af9b5d2010-08-15 17:04:28 +00002537 &offset,exception);
cristy3ed852e2009-09-05 21:47:34 +00002538 status&=VerticalFilter(resize_filter,filter_image,resize_image,y_factor,
cristy9af9b5d2010-08-15 17:04:28 +00002539 span,&offset,exception);
cristy3ed852e2009-09-05 21:47:34 +00002540 }
2541 else
2542 {
2543 span=(MagickSizeType) (filter_image->rows+columns);
2544 status=VerticalFilter(resize_filter,image,filter_image,y_factor,span,
cristy9af9b5d2010-08-15 17:04:28 +00002545 &offset,exception);
cristy3ed852e2009-09-05 21:47:34 +00002546 status&=HorizontalFilter(resize_filter,filter_image,resize_image,x_factor,
cristy9af9b5d2010-08-15 17:04:28 +00002547 span,&offset,exception);
cristy3ed852e2009-09-05 21:47:34 +00002548 }
2549 /*
2550 Free resources.
2551 */
2552 filter_image=DestroyImage(filter_image);
2553 resize_filter=DestroyResizeFilter(resize_filter);
2554 if ((status == MagickFalse) || (resize_image == (Image *) NULL))
2555 return((Image *) NULL);
2556 resize_image->type=image->type;
2557 return(resize_image);
2558}
2559
2560/*
2561%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2562% %
2563% %
2564% %
2565% S a m p l e I m a g e %
2566% %
2567% %
2568% %
2569%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2570%
2571% SampleImage() scales an image to the desired dimensions with pixel
2572% sampling. Unlike other scaling methods, this method does not introduce
2573% any additional color into the scaled image.
2574%
2575% The format of the SampleImage method is:
2576%
cristybb503372010-05-27 20:51:26 +00002577% Image *SampleImage(const Image *image,const size_t columns,
2578% const size_t rows,ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +00002579%
2580% A description of each parameter follows:
2581%
2582% o image: the image.
2583%
2584% o columns: the number of columns in the sampled image.
2585%
2586% o rows: the number of rows in the sampled image.
2587%
2588% o exception: return any errors or warnings in this structure.
2589%
2590*/
cristybb503372010-05-27 20:51:26 +00002591MagickExport Image *SampleImage(const Image *image,const size_t columns,
2592 const size_t rows,ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +00002593{
2594#define SampleImageTag "Sample/Image"
2595
cristyc4c8d132010-01-07 01:58:38 +00002596 CacheView
2597 *image_view,
2598 *sample_view;
2599
cristy3ed852e2009-09-05 21:47:34 +00002600 Image
2601 *sample_image;
2602
cristy3ed852e2009-09-05 21:47:34 +00002603 MagickBooleanType
2604 status;
2605
cristy5f959472010-05-27 22:19:46 +00002606 MagickOffsetType
2607 progress;
2608
cristybb503372010-05-27 20:51:26 +00002609 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +00002610 x;
2611
cristy5f959472010-05-27 22:19:46 +00002612 ssize_t
2613 *x_offset,
2614 y;
2615
cristy3ed852e2009-09-05 21:47:34 +00002616 /*
2617 Initialize sampled image attributes.
2618 */
2619 assert(image != (const Image *) NULL);
2620 assert(image->signature == MagickSignature);
2621 if (image->debug != MagickFalse)
2622 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
2623 assert(exception != (ExceptionInfo *) NULL);
2624 assert(exception->signature == MagickSignature);
2625 if ((columns == 0) || (rows == 0))
2626 ThrowImageException(ImageError,"NegativeOrZeroImageSize");
2627 if ((columns == image->columns) && (rows == image->rows))
2628 return(CloneImage(image,0,0,MagickTrue,exception));
2629 sample_image=CloneImage(image,columns,rows,MagickTrue,exception);
2630 if (sample_image == (Image *) NULL)
2631 return((Image *) NULL);
2632 /*
2633 Allocate scan line buffer and column offset buffers.
2634 */
cristybb503372010-05-27 20:51:26 +00002635 x_offset=(ssize_t *) AcquireQuantumMemory((size_t) sample_image->columns,
cristy3ed852e2009-09-05 21:47:34 +00002636 sizeof(*x_offset));
cristybb503372010-05-27 20:51:26 +00002637 if (x_offset == (ssize_t *) NULL)
cristy3ed852e2009-09-05 21:47:34 +00002638 {
2639 sample_image=DestroyImage(sample_image);
2640 ThrowImageException(ResourceLimitError,"MemoryAllocationFailed");
2641 }
cristybb503372010-05-27 20:51:26 +00002642 for (x=0; x < (ssize_t) sample_image->columns; x++)
2643 x_offset[x]=(ssize_t) (((MagickRealType) x+0.5)*image->columns/
cristy3ed852e2009-09-05 21:47:34 +00002644 sample_image->columns);
2645 /*
2646 Sample each row.
2647 */
2648 status=MagickTrue;
2649 progress=0;
2650 image_view=AcquireCacheView(image);
2651 sample_view=AcquireCacheView(sample_image);
cristyb5d5f722009-11-04 03:03:49 +00002652#if defined(MAGICKCORE_OPENMP_SUPPORT)
2653 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +00002654#endif
cristybb503372010-05-27 20:51:26 +00002655 for (y=0; y < (ssize_t) sample_image->rows; y++)
cristy3ed852e2009-09-05 21:47:34 +00002656 {
cristy3ed852e2009-09-05 21:47:34 +00002657 register const IndexPacket
cristyc47d1f82009-11-26 01:44:43 +00002658 *restrict indexes;
cristy3ed852e2009-09-05 21:47:34 +00002659
2660 register const PixelPacket
cristyc47d1f82009-11-26 01:44:43 +00002661 *restrict p;
cristy3ed852e2009-09-05 21:47:34 +00002662
2663 register IndexPacket
cristyc47d1f82009-11-26 01:44:43 +00002664 *restrict sample_indexes;
cristy3ed852e2009-09-05 21:47:34 +00002665
cristy3ed852e2009-09-05 21:47:34 +00002666 register PixelPacket
cristyc47d1f82009-11-26 01:44:43 +00002667 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +00002668
cristy03dbbd22010-09-19 23:04:47 +00002669 register ssize_t
2670 x;
2671
cristy9af9b5d2010-08-15 17:04:28 +00002672 ssize_t
2673 y_offset;
2674
cristy3ed852e2009-09-05 21:47:34 +00002675 if (status == MagickFalse)
2676 continue;
cristy9af9b5d2010-08-15 17:04:28 +00002677 y_offset=(ssize_t) (((MagickRealType) y+0.5)*image->rows/
2678 sample_image->rows);
cristy3ed852e2009-09-05 21:47:34 +00002679 p=GetCacheViewVirtualPixels(image_view,0,y_offset,image->columns,1,
2680 exception);
2681 q=QueueCacheViewAuthenticPixels(sample_view,0,y,sample_image->columns,1,
2682 exception);
2683 if ((p == (const PixelPacket *) NULL) || (q == (PixelPacket *) NULL))
2684 {
2685 status=MagickFalse;
2686 continue;
2687 }
2688 indexes=GetCacheViewAuthenticIndexQueue(image_view);
2689 sample_indexes=GetCacheViewAuthenticIndexQueue(sample_view);
2690 /*
2691 Sample each column.
2692 */
cristybb503372010-05-27 20:51:26 +00002693 for (x=0; x < (ssize_t) sample_image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +00002694 *q++=p[x_offset[x]];
2695 if ((image->storage_class == PseudoClass) ||
2696 (image->colorspace == CMYKColorspace))
cristybb503372010-05-27 20:51:26 +00002697 for (x=0; x < (ssize_t) sample_image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +00002698 sample_indexes[x]=indexes[x_offset[x]];
2699 if (SyncCacheViewAuthenticPixels(sample_view,exception) == MagickFalse)
2700 status=MagickFalse;
2701 if (image->progress_monitor != (MagickProgressMonitor) NULL)
2702 {
2703 MagickBooleanType
2704 proceed;
2705
cristyb5d5f722009-11-04 03:03:49 +00002706#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristy3ed852e2009-09-05 21:47:34 +00002707 #pragma omp critical (MagickCore_SampleImage)
2708#endif
2709 proceed=SetImageProgress(image,SampleImageTag,progress++,image->rows);
2710 if (proceed == MagickFalse)
2711 status=MagickFalse;
2712 }
2713 }
2714 image_view=DestroyCacheView(image_view);
2715 sample_view=DestroyCacheView(sample_view);
cristybb503372010-05-27 20:51:26 +00002716 x_offset=(ssize_t *) RelinquishMagickMemory(x_offset);
cristy3ed852e2009-09-05 21:47:34 +00002717 sample_image->type=image->type;
2718 return(sample_image);
2719}
2720
2721/*
2722%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2723% %
2724% %
2725% %
2726% S c a l e I m a g e %
2727% %
2728% %
2729% %
2730%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2731%
2732% ScaleImage() changes the size of an image to the given dimensions.
2733%
2734% The format of the ScaleImage method is:
2735%
cristybb503372010-05-27 20:51:26 +00002736% Image *ScaleImage(const Image *image,const size_t columns,
2737% const size_t rows,ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +00002738%
2739% A description of each parameter follows:
2740%
2741% o image: the image.
2742%
2743% o columns: the number of columns in the scaled image.
2744%
2745% o rows: the number of rows in the scaled image.
2746%
2747% o exception: return any errors or warnings in this structure.
2748%
2749*/
cristybb503372010-05-27 20:51:26 +00002750MagickExport Image *ScaleImage(const Image *image,const size_t columns,
2751 const size_t rows,ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +00002752{
2753#define ScaleImageTag "Scale/Image"
2754
cristyed6cb232010-01-20 03:07:53 +00002755 CacheView
2756 *image_view,
2757 *scale_view;
2758
cristy3ed852e2009-09-05 21:47:34 +00002759 Image
2760 *scale_image;
2761
cristy3ed852e2009-09-05 21:47:34 +00002762 MagickBooleanType
2763 next_column,
2764 next_row,
2765 proceed;
2766
2767 MagickPixelPacket
2768 pixel,
2769 *scale_scanline,
2770 *scanline,
2771 *x_vector,
2772 *y_vector,
2773 zero;
2774
cristy3ed852e2009-09-05 21:47:34 +00002775 PointInfo
2776 scale,
2777 span;
2778
cristybb503372010-05-27 20:51:26 +00002779 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +00002780 i;
2781
cristy9af9b5d2010-08-15 17:04:28 +00002782 ssize_t
2783 number_rows,
2784 y;
2785
cristy3ed852e2009-09-05 21:47:34 +00002786 /*
2787 Initialize scaled image attributes.
2788 */
2789 assert(image != (const Image *) NULL);
2790 assert(image->signature == MagickSignature);
2791 if (image->debug != MagickFalse)
2792 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
2793 assert(exception != (ExceptionInfo *) NULL);
2794 assert(exception->signature == MagickSignature);
2795 if ((columns == 0) || (rows == 0))
2796 return((Image *) NULL);
2797 if ((columns == image->columns) && (rows == image->rows))
2798 return(CloneImage(image,0,0,MagickTrue,exception));
2799 scale_image=CloneImage(image,columns,rows,MagickTrue,exception);
2800 if (scale_image == (Image *) NULL)
2801 return((Image *) NULL);
2802 if (SetImageStorageClass(scale_image,DirectClass) == MagickFalse)
2803 {
2804 InheritException(exception,&scale_image->exception);
2805 scale_image=DestroyImage(scale_image);
2806 return((Image *) NULL);
2807 }
2808 /*
2809 Allocate memory.
2810 */
2811 x_vector=(MagickPixelPacket *) AcquireQuantumMemory((size_t) image->columns,
2812 sizeof(*x_vector));
2813 scanline=x_vector;
2814 if (image->rows != scale_image->rows)
2815 scanline=(MagickPixelPacket *) AcquireQuantumMemory((size_t) image->columns,
2816 sizeof(*scanline));
2817 scale_scanline=(MagickPixelPacket *) AcquireQuantumMemory((size_t)
2818 scale_image->columns,sizeof(*scale_scanline));
2819 y_vector=(MagickPixelPacket *) AcquireQuantumMemory((size_t) image->columns,
2820 sizeof(*y_vector));
2821 if ((scanline == (MagickPixelPacket *) NULL) ||
2822 (scale_scanline == (MagickPixelPacket *) NULL) ||
2823 (x_vector == (MagickPixelPacket *) NULL) ||
2824 (y_vector == (MagickPixelPacket *) NULL))
2825 {
2826 scale_image=DestroyImage(scale_image);
2827 ThrowImageException(ResourceLimitError,"MemoryAllocationFailed");
2828 }
2829 /*
2830 Scale image.
2831 */
2832 number_rows=0;
2833 next_row=MagickTrue;
2834 span.y=1.0;
2835 scale.y=(double) scale_image->rows/(double) image->rows;
2836 (void) ResetMagickMemory(y_vector,0,(size_t) image->columns*
2837 sizeof(*y_vector));
2838 GetMagickPixelPacket(image,&pixel);
2839 (void) ResetMagickMemory(&zero,0,sizeof(zero));
2840 i=0;
cristyed6cb232010-01-20 03:07:53 +00002841 image_view=AcquireCacheView(image);
2842 scale_view=AcquireCacheView(scale_image);
cristybb503372010-05-27 20:51:26 +00002843 for (y=0; y < (ssize_t) scale_image->rows; y++)
cristy3ed852e2009-09-05 21:47:34 +00002844 {
2845 register const IndexPacket
cristyc47d1f82009-11-26 01:44:43 +00002846 *restrict indexes;
cristy3ed852e2009-09-05 21:47:34 +00002847
2848 register const PixelPacket
cristyc47d1f82009-11-26 01:44:43 +00002849 *restrict p;
cristy3ed852e2009-09-05 21:47:34 +00002850
2851 register IndexPacket
cristyc47d1f82009-11-26 01:44:43 +00002852 *restrict scale_indexes;
cristy3ed852e2009-09-05 21:47:34 +00002853
cristy3ed852e2009-09-05 21:47:34 +00002854 register MagickPixelPacket
cristyc47d1f82009-11-26 01:44:43 +00002855 *restrict s,
2856 *restrict t;
cristy3ed852e2009-09-05 21:47:34 +00002857
2858 register PixelPacket
cristyc47d1f82009-11-26 01:44:43 +00002859 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +00002860
cristy9af9b5d2010-08-15 17:04:28 +00002861 register ssize_t
2862 x;
2863
cristyed6cb232010-01-20 03:07:53 +00002864 q=QueueCacheViewAuthenticPixels(scale_view,0,y,scale_image->columns,1,
2865 exception);
cristy3ed852e2009-09-05 21:47:34 +00002866 if (q == (PixelPacket *) NULL)
2867 break;
2868 scale_indexes=GetAuthenticIndexQueue(scale_image);
2869 if (scale_image->rows == image->rows)
2870 {
2871 /*
2872 Read a new scanline.
2873 */
cristyed6cb232010-01-20 03:07:53 +00002874 p=GetCacheViewVirtualPixels(image_view,0,i++,image->columns,1,
2875 exception);
cristy3ed852e2009-09-05 21:47:34 +00002876 if (p == (const PixelPacket *) NULL)
2877 break;
cristyed6cb232010-01-20 03:07:53 +00002878 indexes=GetCacheViewVirtualIndexQueue(image_view);
cristybb503372010-05-27 20:51:26 +00002879 for (x=0; x < (ssize_t) image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +00002880 {
cristyce70c172010-01-07 17:15:30 +00002881 x_vector[x].red=(MagickRealType) GetRedPixelComponent(p);
2882 x_vector[x].green=(MagickRealType) GetGreenPixelComponent(p);
2883 x_vector[x].blue=(MagickRealType) GetBluePixelComponent(p);
cristy3ed852e2009-09-05 21:47:34 +00002884 if (image->matte != MagickFalse)
cristyce70c172010-01-07 17:15:30 +00002885 x_vector[x].opacity=(MagickRealType) GetOpacityPixelComponent(p);
cristy3ed852e2009-09-05 21:47:34 +00002886 if (indexes != (IndexPacket *) NULL)
2887 x_vector[x].index=(MagickRealType) indexes[x];
2888 p++;
2889 }
2890 }
2891 else
2892 {
2893 /*
2894 Scale Y direction.
2895 */
2896 while (scale.y < span.y)
2897 {
cristy9af9b5d2010-08-15 17:04:28 +00002898 if ((next_row != MagickFalse) &&
2899 (number_rows < (ssize_t) image->rows))
cristy3ed852e2009-09-05 21:47:34 +00002900 {
2901 /*
2902 Read a new scanline.
2903 */
cristyed6cb232010-01-20 03:07:53 +00002904 p=GetCacheViewVirtualPixels(image_view,0,i++,image->columns,1,
2905 exception);
cristy3ed852e2009-09-05 21:47:34 +00002906 if (p == (const PixelPacket *) NULL)
2907 break;
cristyed6cb232010-01-20 03:07:53 +00002908 indexes=GetCacheViewVirtualIndexQueue(image_view);
cristybb503372010-05-27 20:51:26 +00002909 for (x=0; x < (ssize_t) image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +00002910 {
cristyce70c172010-01-07 17:15:30 +00002911 x_vector[x].red=(MagickRealType) GetRedPixelComponent(p);
2912 x_vector[x].green=(MagickRealType) GetGreenPixelComponent(p);
2913 x_vector[x].blue=(MagickRealType) GetBluePixelComponent(p);
cristy3ed852e2009-09-05 21:47:34 +00002914 if (image->matte != MagickFalse)
cristyed6cb232010-01-20 03:07:53 +00002915 x_vector[x].opacity=(MagickRealType)
cristy9af9b5d2010-08-15 17:04:28 +00002916 GetOpacityPixelComponent(p);
cristy3ed852e2009-09-05 21:47:34 +00002917 if (indexes != (IndexPacket *) NULL)
2918 x_vector[x].index=(MagickRealType) indexes[x];
2919 p++;
2920 }
2921 number_rows++;
2922 }
cristybb503372010-05-27 20:51:26 +00002923 for (x=0; x < (ssize_t) image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +00002924 {
2925 y_vector[x].red+=scale.y*x_vector[x].red;
2926 y_vector[x].green+=scale.y*x_vector[x].green;
2927 y_vector[x].blue+=scale.y*x_vector[x].blue;
2928 if (scale_image->matte != MagickFalse)
2929 y_vector[x].opacity+=scale.y*x_vector[x].opacity;
2930 if (scale_indexes != (IndexPacket *) NULL)
2931 y_vector[x].index+=scale.y*x_vector[x].index;
2932 }
2933 span.y-=scale.y;
2934 scale.y=(double) scale_image->rows/(double) image->rows;
2935 next_row=MagickTrue;
2936 }
cristybb503372010-05-27 20:51:26 +00002937 if ((next_row != MagickFalse) && (number_rows < (ssize_t) image->rows))
cristy3ed852e2009-09-05 21:47:34 +00002938 {
2939 /*
2940 Read a new scanline.
2941 */
cristyed6cb232010-01-20 03:07:53 +00002942 p=GetCacheViewVirtualPixels(image_view,0,i++,image->columns,1,
2943 exception);
cristy3ed852e2009-09-05 21:47:34 +00002944 if (p == (const PixelPacket *) NULL)
2945 break;
cristyed6cb232010-01-20 03:07:53 +00002946 indexes=GetCacheViewVirtualIndexQueue(image_view);
cristybb503372010-05-27 20:51:26 +00002947 for (x=0; x < (ssize_t) image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +00002948 {
cristyce70c172010-01-07 17:15:30 +00002949 x_vector[x].red=(MagickRealType) GetRedPixelComponent(p);
2950 x_vector[x].green=(MagickRealType) GetGreenPixelComponent(p);
2951 x_vector[x].blue=(MagickRealType) GetBluePixelComponent(p);
cristy3ed852e2009-09-05 21:47:34 +00002952 if (image->matte != MagickFalse)
cristy2b726bd2010-01-11 01:05:39 +00002953 x_vector[x].opacity=(MagickRealType)
2954 GetOpacityPixelComponent(p);
cristy3ed852e2009-09-05 21:47:34 +00002955 if (indexes != (IndexPacket *) NULL)
2956 x_vector[x].index=(MagickRealType) indexes[x];
2957 p++;
2958 }
2959 number_rows++;
2960 next_row=MagickFalse;
2961 }
2962 s=scanline;
cristybb503372010-05-27 20:51:26 +00002963 for (x=0; x < (ssize_t) image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +00002964 {
2965 pixel.red=y_vector[x].red+span.y*x_vector[x].red;
2966 pixel.green=y_vector[x].green+span.y*x_vector[x].green;
2967 pixel.blue=y_vector[x].blue+span.y*x_vector[x].blue;
2968 if (image->matte != MagickFalse)
2969 pixel.opacity=y_vector[x].opacity+span.y*x_vector[x].opacity;
2970 if (scale_indexes != (IndexPacket *) NULL)
2971 pixel.index=y_vector[x].index+span.y*x_vector[x].index;
2972 s->red=pixel.red;
2973 s->green=pixel.green;
2974 s->blue=pixel.blue;
2975 if (scale_image->matte != MagickFalse)
2976 s->opacity=pixel.opacity;
2977 if (scale_indexes != (IndexPacket *) NULL)
2978 s->index=pixel.index;
2979 s++;
2980 y_vector[x]=zero;
2981 }
2982 scale.y-=span.y;
2983 if (scale.y <= 0)
2984 {
2985 scale.y=(double) scale_image->rows/(double) image->rows;
2986 next_row=MagickTrue;
2987 }
2988 span.y=1.0;
2989 }
2990 if (scale_image->columns == image->columns)
2991 {
2992 /*
2993 Transfer scanline to scaled image.
2994 */
2995 s=scanline;
cristybb503372010-05-27 20:51:26 +00002996 for (x=0; x < (ssize_t) scale_image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +00002997 {
cristyce70c172010-01-07 17:15:30 +00002998 q->red=ClampToQuantum(s->red);
2999 q->green=ClampToQuantum(s->green);
3000 q->blue=ClampToQuantum(s->blue);
cristy3ed852e2009-09-05 21:47:34 +00003001 if (scale_image->matte != MagickFalse)
cristyce70c172010-01-07 17:15:30 +00003002 q->opacity=ClampToQuantum(s->opacity);
cristy3ed852e2009-09-05 21:47:34 +00003003 if (scale_indexes != (IndexPacket *) NULL)
cristyce70c172010-01-07 17:15:30 +00003004 scale_indexes[x]=(IndexPacket) ClampToQuantum(s->index);
cristy3ed852e2009-09-05 21:47:34 +00003005 q++;
3006 s++;
3007 }
3008 }
3009 else
3010 {
3011 /*
3012 Scale X direction.
3013 */
3014 pixel=zero;
3015 next_column=MagickFalse;
3016 span.x=1.0;
3017 s=scanline;
3018 t=scale_scanline;
cristybb503372010-05-27 20:51:26 +00003019 for (x=0; x < (ssize_t) image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +00003020 {
3021 scale.x=(double) scale_image->columns/(double) image->columns;
3022 while (scale.x >= span.x)
3023 {
3024 if (next_column != MagickFalse)
3025 {
3026 pixel=zero;
3027 t++;
3028 }
3029 pixel.red+=span.x*s->red;
3030 pixel.green+=span.x*s->green;
3031 pixel.blue+=span.x*s->blue;
3032 if (image->matte != MagickFalse)
3033 pixel.opacity+=span.x*s->opacity;
3034 if (scale_indexes != (IndexPacket *) NULL)
3035 pixel.index+=span.x*s->index;
3036 t->red=pixel.red;
3037 t->green=pixel.green;
3038 t->blue=pixel.blue;
3039 if (scale_image->matte != MagickFalse)
3040 t->opacity=pixel.opacity;
3041 if (scale_indexes != (IndexPacket *) NULL)
3042 t->index=pixel.index;
3043 scale.x-=span.x;
3044 span.x=1.0;
3045 next_column=MagickTrue;
3046 }
3047 if (scale.x > 0)
3048 {
3049 if (next_column != MagickFalse)
3050 {
3051 pixel=zero;
3052 next_column=MagickFalse;
3053 t++;
3054 }
3055 pixel.red+=scale.x*s->red;
3056 pixel.green+=scale.x*s->green;
3057 pixel.blue+=scale.x*s->blue;
3058 if (scale_image->matte != MagickFalse)
3059 pixel.opacity+=scale.x*s->opacity;
3060 if (scale_indexes != (IndexPacket *) NULL)
3061 pixel.index+=scale.x*s->index;
3062 span.x-=scale.x;
3063 }
3064 s++;
3065 }
3066 if (span.x > 0)
3067 {
3068 s--;
3069 pixel.red+=span.x*s->red;
3070 pixel.green+=span.x*s->green;
3071 pixel.blue+=span.x*s->blue;
3072 if (scale_image->matte != MagickFalse)
3073 pixel.opacity+=span.x*s->opacity;
3074 if (scale_indexes != (IndexPacket *) NULL)
3075 pixel.index+=span.x*s->index;
3076 }
3077 if ((next_column == MagickFalse) &&
cristybb503372010-05-27 20:51:26 +00003078 ((ssize_t) (t-scale_scanline) < (ssize_t) scale_image->columns))
cristy3ed852e2009-09-05 21:47:34 +00003079 {
3080 t->red=pixel.red;
3081 t->green=pixel.green;
3082 t->blue=pixel.blue;
3083 if (scale_image->matte != MagickFalse)
3084 t->opacity=pixel.opacity;
3085 if (scale_indexes != (IndexPacket *) NULL)
3086 t->index=pixel.index;
3087 }
3088 /*
3089 Transfer scanline to scaled image.
3090 */
3091 t=scale_scanline;
cristybb503372010-05-27 20:51:26 +00003092 for (x=0; x < (ssize_t) scale_image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +00003093 {
cristyce70c172010-01-07 17:15:30 +00003094 q->red=ClampToQuantum(t->red);
3095 q->green=ClampToQuantum(t->green);
3096 q->blue=ClampToQuantum(t->blue);
cristy3ed852e2009-09-05 21:47:34 +00003097 if (scale_image->matte != MagickFalse)
cristyce70c172010-01-07 17:15:30 +00003098 q->opacity=ClampToQuantum(t->opacity);
cristy3ed852e2009-09-05 21:47:34 +00003099 if (scale_indexes != (IndexPacket *) NULL)
cristyce70c172010-01-07 17:15:30 +00003100 scale_indexes[x]=(IndexPacket) ClampToQuantum(t->index);
cristy3ed852e2009-09-05 21:47:34 +00003101 t++;
3102 q++;
3103 }
3104 }
cristyed6cb232010-01-20 03:07:53 +00003105 if (SyncCacheViewAuthenticPixels(scale_view,exception) == MagickFalse)
cristy3ed852e2009-09-05 21:47:34 +00003106 break;
cristy96b16132010-08-29 17:19:52 +00003107 proceed=SetImageProgress(image,ScaleImageTag,(MagickOffsetType) y,
3108 image->rows);
cristy3ed852e2009-09-05 21:47:34 +00003109 if (proceed == MagickFalse)
3110 break;
3111 }
cristyed6cb232010-01-20 03:07:53 +00003112 scale_view=DestroyCacheView(scale_view);
3113 image_view=DestroyCacheView(image_view);
cristy3ed852e2009-09-05 21:47:34 +00003114 /*
3115 Free allocated memory.
3116 */
3117 y_vector=(MagickPixelPacket *) RelinquishMagickMemory(y_vector);
3118 scale_scanline=(MagickPixelPacket *) RelinquishMagickMemory(scale_scanline);
3119 if (scale_image->rows != image->rows)
3120 scanline=(MagickPixelPacket *) RelinquishMagickMemory(scanline);
3121 x_vector=(MagickPixelPacket *) RelinquishMagickMemory(x_vector);
3122 scale_image->type=image->type;
3123 return(scale_image);
3124}
3125
anthony02b4cb42010-10-10 04:54:35 +00003126#if 0
3127 THIS IS NOT USED -- to be removed
cristy3ed852e2009-09-05 21:47:34 +00003128/*
3129%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3130% %
3131% %
3132% %
3133+ S e t R e s i z e F i l t e r S u p p o r t %
3134% %
3135% %
3136% %
3137%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3138%
3139% SetResizeFilterSupport() specifies which IR filter to use to window
3140%
3141% The format of the SetResizeFilterSupport method is:
3142%
3143% void SetResizeFilterSupport(ResizeFilter *resize_filter,
3144% const MagickRealType support)
3145%
3146% A description of each parameter follows:
3147%
3148% o resize_filter: the resize filter.
3149%
3150% o support: the filter spport radius.
3151%
3152*/
3153MagickExport void SetResizeFilterSupport(ResizeFilter *resize_filter,
3154 const MagickRealType support)
3155{
3156 assert(resize_filter != (ResizeFilter *) NULL);
3157 assert(resize_filter->signature == MagickSignature);
3158 resize_filter->support=support;
3159}
anthony02b4cb42010-10-10 04:54:35 +00003160#endif
cristy3ed852e2009-09-05 21:47:34 +00003161
3162/*
3163%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3164% %
3165% %
3166% %
3167% T h u m b n a i l I m a g e %
3168% %
3169% %
3170% %
3171%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3172%
3173% ThumbnailImage() changes the size of an image to the given dimensions and
3174% removes any associated profiles. The goal is to produce small low cost
3175% thumbnail images suited for display on the Web.
3176%
3177% The format of the ThumbnailImage method is:
3178%
cristybb503372010-05-27 20:51:26 +00003179% Image *ThumbnailImage(const Image *image,const size_t columns,
3180% const size_t rows,ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +00003181%
3182% A description of each parameter follows:
3183%
3184% o image: the image.
3185%
3186% o columns: the number of columns in the scaled image.
3187%
3188% o rows: the number of rows in the scaled image.
3189%
3190% o exception: return any errors or warnings in this structure.
3191%
3192*/
cristy9af9b5d2010-08-15 17:04:28 +00003193MagickExport Image *ThumbnailImage(const Image *image,const size_t columns,
3194 const size_t rows,ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +00003195{
3196#define SampleFactor 5
3197
3198 char
3199 value[MaxTextExtent];
3200
3201 const char
3202 *name;
3203
3204 Image
3205 *thumbnail_image;
3206
3207 MagickRealType
3208 x_factor,
3209 y_factor;
3210
cristybb503372010-05-27 20:51:26 +00003211 size_t
cristy3ed852e2009-09-05 21:47:34 +00003212 version;
3213
cristy9af9b5d2010-08-15 17:04:28 +00003214 struct stat
3215 attributes;
3216
cristy3ed852e2009-09-05 21:47:34 +00003217 assert(image != (Image *) NULL);
3218 assert(image->signature == MagickSignature);
3219 if (image->debug != MagickFalse)
3220 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
3221 assert(exception != (ExceptionInfo *) NULL);
3222 assert(exception->signature == MagickSignature);
3223 x_factor=(MagickRealType) columns/(MagickRealType) image->columns;
3224 y_factor=(MagickRealType) rows/(MagickRealType) image->rows;
3225 if ((x_factor*y_factor) > 0.1)
cristyd1bb3bc2010-09-07 00:43:58 +00003226 thumbnail_image=ResizeImage(image,columns,rows,image->filter,image->blur,
3227 exception);
cristy3ed852e2009-09-05 21:47:34 +00003228 else
3229 if (((SampleFactor*columns) < 128) || ((SampleFactor*rows) < 128))
cristyd1bb3bc2010-09-07 00:43:58 +00003230 thumbnail_image=ResizeImage(image,columns,rows,image->filter,
3231 image->blur,exception);
cristy3ed852e2009-09-05 21:47:34 +00003232 else
3233 {
3234 Image
3235 *sample_image;
3236
3237 sample_image=SampleImage(image,SampleFactor*columns,SampleFactor*rows,
3238 exception);
3239 if (sample_image == (Image *) NULL)
3240 return((Image *) NULL);
cristyd1bb3bc2010-09-07 00:43:58 +00003241 thumbnail_image=ResizeImage(sample_image,columns,rows,image->filter,
3242 image->blur,exception);
cristy3ed852e2009-09-05 21:47:34 +00003243 sample_image=DestroyImage(sample_image);
3244 }
3245 if (thumbnail_image == (Image *) NULL)
3246 return(thumbnail_image);
3247 (void) ParseAbsoluteGeometry("0x0+0+0",&thumbnail_image->page);
3248 if (thumbnail_image->matte == MagickFalse)
3249 (void) SetImageAlphaChannel(thumbnail_image,OpaqueAlphaChannel);
3250 thumbnail_image->depth=8;
3251 thumbnail_image->interlace=NoInterlace;
3252 /*
3253 Strip all profiles except color profiles.
3254 */
3255 ResetImageProfileIterator(thumbnail_image);
3256 for (name=GetNextImageProfile(thumbnail_image); name != (const char *) NULL; )
3257 {
3258 if ((LocaleCompare(name,"icc") != 0) && (LocaleCompare(name,"icm") != 0))
3259 {
cristy2b726bd2010-01-11 01:05:39 +00003260 (void) DeleteImageProfile(thumbnail_image,name);
cristy3ed852e2009-09-05 21:47:34 +00003261 ResetImageProfileIterator(thumbnail_image);
3262 }
3263 name=GetNextImageProfile(thumbnail_image);
3264 }
3265 (void) DeleteImageProperty(thumbnail_image,"comment");
3266 (void) CopyMagickString(value,image->magick_filename,MaxTextExtent);
cristy7b63d662009-11-13 01:58:56 +00003267 if (strstr(image->magick_filename,"//") == (char *) NULL)
3268 (void) FormatMagickString(value,MaxTextExtent,"file://%s",
cristy3ed852e2009-09-05 21:47:34 +00003269 image->magick_filename);
3270 (void) SetImageProperty(thumbnail_image,"Thumb::URI",value);
3271 (void) CopyMagickString(value,image->magick_filename,MaxTextExtent);
3272 if (GetPathAttributes(image->filename,&attributes) != MagickFalse)
3273 {
cristye8c25f92010-06-03 00:53:06 +00003274 (void) FormatMagickString(value,MaxTextExtent,"%.20g",(double)
cristy3ed852e2009-09-05 21:47:34 +00003275 attributes.st_mtime);
3276 (void) SetImageProperty(thumbnail_image,"Thumb::MTime",value);
3277 }
cristye8c25f92010-06-03 00:53:06 +00003278 (void) FormatMagickString(value,MaxTextExtent,"%.20g",(double)
cristy3ed852e2009-09-05 21:47:34 +00003279 attributes.st_mtime);
cristyb9080c92009-12-01 20:13:26 +00003280 (void) FormatMagickSize(GetBlobSize(image),MagickFalse,value);
cristy2ce15c92010-03-12 14:03:41 +00003281 (void) ConcatenateMagickString(value,"B",MaxTextExtent);
cristy3ed852e2009-09-05 21:47:34 +00003282 (void) SetImageProperty(thumbnail_image,"Thumb::Size",value);
3283 (void) FormatMagickString(value,MaxTextExtent,"image/%s",image->magick);
3284 LocaleLower(value);
3285 (void) SetImageProperty(thumbnail_image,"Thumb::Mimetype",value);
3286 (void) SetImageProperty(thumbnail_image,"software",
3287 GetMagickVersion(&version));
cristye8c25f92010-06-03 00:53:06 +00003288 (void) FormatMagickString(value,MaxTextExtent,"%.20g",(double)
3289 image->magick_columns);
cristy3ed852e2009-09-05 21:47:34 +00003290 (void) SetImageProperty(thumbnail_image,"Thumb::Image::Width",value);
cristye8c25f92010-06-03 00:53:06 +00003291 (void) FormatMagickString(value,MaxTextExtent,"%.20g",(double)
cristyf2faecf2010-05-28 19:19:36 +00003292 image->magick_rows);
cristy3ed852e2009-09-05 21:47:34 +00003293 (void) SetImageProperty(thumbnail_image,"Thumb::Image::height",value);
cristye8c25f92010-06-03 00:53:06 +00003294 (void) FormatMagickString(value,MaxTextExtent,"%.20g",(double)
3295 GetImageListLength(image));
cristy3ed852e2009-09-05 21:47:34 +00003296 (void) SetImageProperty(thumbnail_image,"Thumb::Document::Pages",value);
3297 return(thumbnail_image);
3298}