blob: e59f8677878a708c1f657ae1cdda89bd9395aadf [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"
57#include "magick/pixel-private.h"
58#include "magick/property.h"
59#include "magick/monitor.h"
60#include "magick/monitor-private.h"
61#include "magick/pixel.h"
62#include "magick/option.h"
63#include "magick/resample.h"
64#include "magick/resize.h"
65#include "magick/resize-private.h"
66#include "magick/string_.h"
cristyf2f27272009-12-17 14:48:46 +000067#include "magick/string-private.h"
cristy3ed852e2009-09-05 21:47:34 +000068#include "magick/thread-private.h"
69#include "magick/utility.h"
70#include "magick/version.h"
71#if defined(MAGICKCORE_LQR_DELEGATE)
72#include <lqr.h>
73#endif
74
75/*
76 Typedef declarations.
77*/
78struct _ResizeFilter
79{
80 MagickRealType
81 (*filter)(const MagickRealType,const ResizeFilter *),
82 (*window)(const MagickRealType,const ResizeFilter *),
cristy33b1c162010-01-23 22:51:51 +000083 support, /* filter region of support - the filter support limit */
84 window_support, /* window support, usally equal to support (expert only) */
85 scale, /* dimension to scale to fit window support (usally 1.0) */
86 blur, /* x-scale (blur-sharpen) */
87 cubic[8]; /* cubic coefficents for smooth Cubic filters */
cristy3ed852e2009-09-05 21:47:34 +000088
89 unsigned long
90 signature;
91};
92
93/*
94 Forward declaractions.
95*/
96static MagickRealType
97 I0(MagickRealType x),
98 BesselOrderOne(MagickRealType);
99
100/*
101%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
102% %
103% %
104% %
105+ F i l t e r F u n c t i o n s %
106% %
107% %
108% %
109%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
110%
cristy33b1c162010-01-23 22:51:51 +0000111% These are the various filter and windowing functions that are provided.
cristy3ed852e2009-09-05 21:47:34 +0000112%
cristy33b1c162010-01-23 22:51:51 +0000113% They are internal to this module only. See AcquireResizeFilterInfo() for
114% details of the access to these functions, via the GetResizeFilterSupport()
115% and GetResizeFilterWeight() API interface.
cristy3ed852e2009-09-05 21:47:34 +0000116%
117% The individual filter functions have this format...
118%
119% static MagickRealtype *FilterName(const MagickRealType x,
120% const MagickRealType support)
121%
cristy33b1c162010-01-23 22:51:51 +0000122% A description of each parameter follows:
cristy3ed852e2009-09-05 21:47:34 +0000123%
cristy33b1c162010-01-23 22:51:51 +0000124% o x: the distance from the sampling point generally in the range of 0 to
125% support. The GetResizeFilterWeight() ensures this a positive value.
126%
127% o resize_filter: current filter information. This allows function to
128% access support, and possibly other pre-calculated information defining
129% the functions.
cristy3ed852e2009-09-05 21:47:34 +0000130%
131*/
132
133static MagickRealType Bessel(const MagickRealType x,
134 const ResizeFilter *magick_unused(resize_filter))
135{
136 /*
cristyf59a8922010-02-28 19:51:23 +0000137 See Pratt "Digital Image Processing" p.97 for Bessel functions.
cristy3ed852e2009-09-05 21:47:34 +0000138
cristy33b1c162010-01-23 22:51:51 +0000139 This function actually a X-scaled Jinc(x) function. See
140 http://mathworld.wolfram.com/JincFunction.html and page 11 of
cristyf59a8922010-02-28 19:51:23 +0000141 http://www.ph.ed.ac.uk/%7ewjh/teaching/mo/slides/lens/lens.pdf.
cristy3ed852e2009-09-05 21:47:34 +0000142 */
143 if (x == 0.0)
144 return((MagickRealType) (MagickPI/4.0));
145 return(BesselOrderOne(MagickPI*x)/(2.0*x));
146}
147
148static MagickRealType Blackman(const MagickRealType x,
cristyce70c172010-01-07 17:15:30 +0000149 const ResizeFilter *magick_unused(resize_filter))
cristy3ed852e2009-09-05 21:47:34 +0000150{
151 /*
152 Blackman: 2rd Order cosine windowing function.
153 */
154 return(0.42+0.5*cos(MagickPI*(double) x)+0.08*cos(2.0*MagickPI*(double) x));
155}
156
157static MagickRealType Bohman(const MagickRealType x,
cristyce70c172010-01-07 17:15:30 +0000158 const ResizeFilter *magick_unused(resize_filter))
cristy3ed852e2009-09-05 21:47:34 +0000159{
160 /*
161 Bohman: 2rd Order cosine windowing function.
162 */
163 return((1-x)*cos(MagickPI*(double) x)+sin(MagickPI*(double) x)/MagickPI);
164}
165
166static MagickRealType Box(const MagickRealType magick_unused(x),
167 const ResizeFilter *magick_unused(resize_filter))
168{
169 /*
170 Just return 1.0, filter will still be clipped by its support window.
171 */
172 return(1.0);
173}
174
175static MagickRealType CubicBC(const MagickRealType x,
176 const ResizeFilter *resize_filter)
177{
178 /*
179 Cubic Filters using B,C determined values:
cristyf59a8922010-02-28 19:51:23 +0000180 Mitchell-Netravali B=1/3 C=1/3 Qualitively ideal Cubic Filter
181 Catmull-Rom B= 0 C=1/2 Cublic Interpolation Function
182 Cubic B-Spline B= 1 C= 0 Spline Approximation of Gaussian
183 Hermite B= 0 C= 0 Quadratic Spline (support = 1)
cristy3ed852e2009-09-05 21:47:34 +0000184
cristy33b1c162010-01-23 22:51:51 +0000185 See paper by Mitchell and Netravali, Reconstruction Filters in Computer
186 Graphics Computer Graphics, Volume 22, Number 4, August 1988
187 http://www.cs.utexas.edu/users/fussell/courses/cs384g/lectures/mitchell/
cristyf59a8922010-02-28 19:51:23 +0000188 Mitchell.pdf.
cristy3ed852e2009-09-05 21:47:34 +0000189
cristy33b1c162010-01-23 22:51:51 +0000190 Coefficents are determined from B,C values:
cristy3ed852e2009-09-05 21:47:34 +0000191 P0 = ( 6 - 2*B )/6
192 P1 = 0
193 P2 = (-18 +12*B + 6*C )/6
194 P3 = ( 12 - 9*B - 6*C )/6
195 Q0 = ( 8*B +24*C )/6
196 Q1 = ( -12*B -48*C )/6
197 Q2 = ( 6*B +30*C )/6
198 Q3 = ( - 1*B - 6*C )/6
199
cristy33b1c162010-01-23 22:51:51 +0000200 which are used to define the filter:
cristyf59a8922010-02-28 19:51:23 +0000201
cristy3ed852e2009-09-05 21:47:34 +0000202 P0 + P1*x + P2*x^2 + P3*x^3 0 <= x < 1
203 Q0 + Q1*x + Q2*x^2 + Q3*x^3 1 <= x <= 2
204
cristy33b1c162010-01-23 22:51:51 +0000205 which ensures function is continuous in value and derivative (slope).
cristy3ed852e2009-09-05 21:47:34 +0000206 */
207 if (x < 1.0)
208 return(resize_filter->cubic[0]+x*(resize_filter->cubic[1]+x*
209 (resize_filter->cubic[2]+x*resize_filter->cubic[3])));
210 if (x < 2.0)
cristy679e6962010-03-18 00:42:45 +0000211 return(resize_filter->cubic[4]+x*(resize_filter->cubic[5]+x*
cristy3ed852e2009-09-05 21:47:34 +0000212 (resize_filter->cubic[6] +x*resize_filter->cubic[7])));
213 return(0.0);
214}
215
216static MagickRealType Gaussian(const MagickRealType x,
217 const ResizeFilter *magick_unused(resize_filter))
218{
219 return(exp((double) (-2.0*x*x))*sqrt(2.0/MagickPI));
220}
221
222static MagickRealType Hanning(const MagickRealType x,
223 const ResizeFilter *magick_unused(resize_filter))
224{
225 /*
226 A Cosine windowing function.
227 */
228 return(0.5+0.5*cos(MagickPI*(double) x));
229}
230
231static MagickRealType Hamming(const MagickRealType x,
232 const ResizeFilter *magick_unused(resize_filter))
233{
234 /*
235 A offset Cosine windowing function.
236 */
237 return(0.54+0.46*cos(MagickPI*(double) x));
238}
239
240static MagickRealType Kaiser(const MagickRealType x,
241 const ResizeFilter *magick_unused(resize_filter))
242{
243#define Alpha 6.5
244#define I0A (1.0/I0(Alpha))
245
246 /*
cristy33b1c162010-01-23 22:51:51 +0000247 Kaiser Windowing Function (bessel windowing): Alpha is a free value from 5
248 to 8 (currently hardcoded to 6.5). Future: make alpha the IOA
249 pre-calculation, a 'expert' setting.
cristy3ed852e2009-09-05 21:47:34 +0000250 */
251 return(I0A*I0(Alpha*sqrt((double) (1.0-x*x))));
252}
253
254static MagickRealType Lagrange(const MagickRealType x,
255 const ResizeFilter *resize_filter)
256{
257 long
258 n,
259 order;
260
261 MagickRealType
262 value;
263
264 register long
265 i;
266
267 /*
cristy33b1c162010-01-23 22:51:51 +0000268 Lagrange Piece-Wise polynomial fit of Sinc: N is the 'order' of the
269 lagrange function and depends on the overall support window size of the
270 filter. That is for a support of 2, gives a lagrange-4 or piece-wise cubic
271 functions.
cristy3ed852e2009-09-05 21:47:34 +0000272
cristy33b1c162010-01-23 22:51:51 +0000273 Note that n is the specific piece of the piece-wise function to calculate.
cristy3ed852e2009-09-05 21:47:34 +0000274
cristy33b1c162010-01-23 22:51:51 +0000275 See Survey: Interpolation Methods, IEEE Transactions on Medical Imaging,
cristyf59a8922010-02-28 19:51:23 +0000276 Vol 18, No 11, November 1999, p1049-1075, -- Equation 27 on p1064.
cristy3ed852e2009-09-05 21:47:34 +0000277 */
278 if (x > resize_filter->support)
279 return(0.0);
280 order=(long) (2.0*resize_filter->window_support); /* number of pieces */
281 n=(long) ((1.0*order)/2.0+x); /* which piece does x belong to */
282 value=1.0f;
283 for (i=0; i < order; i++)
284 if (i != n)
285 value*=(n-i-x)/(n-i);
286 return(value);
287}
288
289static MagickRealType Quadratic(const MagickRealType x,
290 const ResizeFilter *magick_unused(resize_filter))
291{
292 /*
293 2rd order (quadratic) B-Spline approximation of Gaussian.
294 */
295 if (x < 0.5)
296 return(0.75-x*x);
297 if (x < 1.5)
298 return(0.5*(x-1.5)*(x-1.5));
299 return(0.0);
300}
301
302static MagickRealType Sinc(const MagickRealType x,
303 const ResizeFilter *magick_unused(resize_filter))
304{
305 /*
306 This function actually a X-scaled Sinc(x) function.
307 */
308 if (x == 0.0)
309 return(1.0);
310 return(sin(MagickPI*(double) x)/(MagickPI*(double) x));
311}
312
313static MagickRealType Triangle(const MagickRealType x,
314 const ResizeFilter *magick_unused(resize_filter))
315{
316 /*
cristy33b1c162010-01-23 22:51:51 +0000317 1rd order (linear) B-Spline, bilinear interpolation, Tent 1D filter, or a
318 Bartlett 2D Cone filter.
cristy3ed852e2009-09-05 21:47:34 +0000319 */
320 if (x < 1.0)
321 return(1.0-x);
322 return(0.0);
323}
324
325static MagickRealType Welsh(const MagickRealType x,
326 const ResizeFilter *magick_unused(resize_filter))
327{
328 /*
329 Welsh parabolic windowing filter.
330 */
331 if (x < 1.0)
332 return(1.0-x*x);
333 return(0.0);
334}
335
336/*
337%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
338% %
339% %
340% %
341+ A c q u i r e R e s i z e F i l t e r %
342% %
343% %
344% %
345%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
346%
347% AcquireResizeFilter() allocates the ResizeFilter structure. Choose from
348% these filters:
349%
350% FIR (Finite impulse Response) Filters
351% Box Triangle Quadratic
352% Cubic Hermite Catrom
353% Mitchell
354%
355% IIR (Infinite impulse Response) Filters
356% Gaussian Sinc Bessel
357%
358% Windowed Sinc/Bessel Method
359% Blackman Hanning Hamming
360% Kaiser Lancos (Sinc)
361%
362% FIR filters are used as is, and are limited by that filters support window
363% (unless over-ridden). 'Gaussian' while classed as an IIR filter, is also
364% simply clipped by its support size (1.5).
365%
366% Requesting a windowed filter will return either a windowed Sinc, for a one
367% dimentional orthogonal filtering method, such as ResizeImage(), or a
368% windowed Bessel for image operations requiring a two dimentional
369% cylindrical filtering method, such a DistortImage(). Which function is
370% is used set by the "cylindrical" boolean argument.
371%
372% Directly requesting 'Sinc' or 'Bessel' will force the use of that filter
373% function, with a default 'Blackman' windowing method. This not however
374% recommended as it removes the correct filter selection for different
375% filtering image operations. Selecting a window filtering method is better.
376%
cristyf59a8922010-02-28 19:51:23 +0000377% Lanczos is purely special case of a Sinc windowed Sinc, but defaulting to
cristy3ed852e2009-09-05 21:47:34 +0000378% a 3 lobe support, rather that the default 4 lobe support.
379%
380% Special options can be used to override specific, or all the filter
381% settings. However doing so is not advisible unless you have expert
382% knowledge of the use of resampling filtered techniques. Extreme caution is
383% advised.
384%
385% "filter:filter" Select this function as the filter.
386% If a "filter:window" operation is not provided, then no windowing
387% will be performed on the selected filter, (support clipped)
388%
389% This can be used to force the use of a windowing method as filter,
390% request a 'Sinc' filter in a radially filtered operation, or the
391% 'Bessel' filter for a othogonal filtered operation.
392%
393% "filter:window" Select this windowing function for the filter.
394% While any filter could be used as a windowing function,
395% using that filters first lobe over the whole support window,
396% using a non-windowing method is not advisible.
397%
398% "filter:lobes" Number of lobes to use for the Sinc/Bessel filter.
399% This a simper method of setting filter support size that will
400% correctly handle the Sinc/Bessel switch for an operators filtering
401% requirements.
402%
403% "filter:support" Set the support size for filtering to the size given
cristyf59a8922010-02-28 19:51:23 +0000404% This not recommended for Sinc/Bessel windowed filters, but is
cristy3ed852e2009-09-05 21:47:34 +0000405% used for simple filters like FIR filters, and the Gaussian Filter.
406% This will override any 'filter:lobes' option.
407%
408% "filter:blur" Scale the filter and support window by this amount.
409% A value >1 will generally result in a more burred image with
410% more ringing effects, while a value <1 will sharpen the
411% resulting image with more aliasing and Morie effects.
412%
413% "filter:win-support" Scale windowing function to this size instead.
414% This causes the windowing (or self-windowing Lagrange filter)
415% to act is if the support winodw it much much larger than what
416% is actually supplied to the calling operator. The filter however
417% is still clipped to the real support size given. If unset this
418% will equal the normal filter support size.
419%
420% "filter:b"
421% "filter:c" Override the preset B,C values for a Cubic type of filter
422% If only one of these are given it is assumes to be a 'Keys'
423% type of filter such that B+2C=1, where Keys 'alpha' value = C
424%
425% "filter:verbose" Output verbose plotting data for graphing the
426% resulting filter over the whole support range (with blur effect).
427%
428% Set a true un-windowed Sinc filter with 10 lobes (very slow)
429% -set option:filter:filter Sinc
430% -set option:filter:lobes 8
431%
432% For example force an 8 lobe Lanczos (Sinc or Bessel) filter...
433% -filter Lanczos
434% -set option:filter:lobes 8
435%
436% The format of the AcquireResizeFilter method is:
437%
438% ResizeFilter *AcquireResizeFilter(const Image *image,
439% const FilterTypes filter_type, const MagickBooleanType radial,
440% ExceptionInfo *exception)
441%
cristy33b1c162010-01-23 22:51:51 +0000442% A description of each parameter follows:
443%
cristy3ed852e2009-09-05 21:47:34 +0000444% o image: the image.
445%
446% o filter: the filter type, defining a preset filter, window and support.
447%
cristy33b1c162010-01-23 22:51:51 +0000448% o blur: blur the filter by this amount, use 1.0 if unknown. Image
449% artifact "filter:blur" will override this old usage
cristy3ed852e2009-09-05 21:47:34 +0000450%
451% o radial: 1D orthogonal filter (Sinc) or 2D radial filter (Bessel)
452%
453% o exception: return any errors or warnings in this structure.
454%
455*/
456MagickExport ResizeFilter *AcquireResizeFilter(const Image *image,
cristyf59a8922010-02-28 19:51:23 +0000457 const FilterTypes filter,const MagickRealType blur,
cristy3ed852e2009-09-05 21:47:34 +0000458 const MagickBooleanType cylindrical,ExceptionInfo *exception)
459{
460 const char
461 *artifact;
462
463 FilterTypes
464 filter_type,
465 window_type;
466
467 long
cristy33b1c162010-01-23 22:51:51 +0000468 option;
cristy3ed852e2009-09-05 21:47:34 +0000469
470 MagickRealType
471 B,
472 C;
473
474 register ResizeFilter
475 *resize_filter;
476
477 /*
cristy33b1c162010-01-23 22:51:51 +0000478 Table Mapping given Filter, into Weighting and Windowing functions. A
479 'Box' windowing function means its a simble non-windowed filter. A 'Sinc'
480 filter function (must be windowed) could be upgraded to a 'Bessel' filter
481 if a "cylindrical" filter is requested, unless a "Sinc" filter specifically
482 request.
cristy3ed852e2009-09-05 21:47:34 +0000483 */
484 static struct
485 {
486 FilterTypes
487 filter,
488 window;
489 } const mapping[SentinelFilter] =
490 {
491 { UndefinedFilter, BoxFilter }, /* undefined */
492 { PointFilter, BoxFilter }, /* special, nearest-neighbour filter */
493 { BoxFilter, BoxFilter }, /* Box averaging Filter */
494 { TriangleFilter, BoxFilter }, /* Linear Interpolation Filter */
495 { HermiteFilter, BoxFilter }, /* Hermite interpolation filter */
496 { SincFilter, HanningFilter }, /* Hanning -- Cosine-Sinc */
497 { SincFilter, HammingFilter }, /* Hamming -- '' variation */
498 { SincFilter, BlackmanFilter }, /* Blackman -- 2*Cosine-Sinc */
499 { GaussianFilter, BoxFilter }, /* Gaussain Blurring filter */
500 { QuadraticFilter, BoxFilter }, /* Quadratic Gaussian approximation */
501 { CubicFilter, BoxFilter }, /* Cubic Gaussian approximation */
502 { CatromFilter, BoxFilter }, /* Cubic Interpolator */
503 { MitchellFilter, BoxFilter }, /* 'ideal' Cubic Filter */
504 { LanczosFilter, SincFilter }, /* Special, 3 lobed Sinc-Sinc */
505 { BesselFilter, BlackmanFilter }, /* 3 lobed bessel -specific request */
506 { SincFilter, BlackmanFilter }, /* 4 lobed sinc - specific request */
507 { SincFilter, KaiserFilter }, /* Kaiser -- SqRoot-Sinc */
508 { SincFilter, WelshFilter }, /* Welsh -- Parabolic-Sinc */
509 { SincFilter, CubicFilter }, /* Parzen -- Cubic-Sinc */
510 { LagrangeFilter, BoxFilter }, /* Lagrange self-windowing filter */
511 { SincFilter, BohmanFilter }, /* Bohman -- 2*Cosine-Sinc */
512 { SincFilter, TriangleFilter } /* Bartlett -- Triangle-Sinc */
513 };
514 /*
515 Table maping the filter/window function from the above table to the actual
516 filter/window function call to use. The default support size for that
cristy33b1c162010-01-23 22:51:51 +0000517 filter as a weighting function, and the point to scale when that function is
518 used as a windowing function (typ 1.0).
cristy3ed852e2009-09-05 21:47:34 +0000519 */
520 static struct
521 {
522 MagickRealType
523 (*function)(const MagickRealType, const ResizeFilter*),
524 support, /* default support size for function as a filter */
525 scale, /* size windowing function, for scaling windowing function */
526 B,
527 C; /* Cubic Filter factors for a CubicBC function, else ignored */
528 } const filters[SentinelFilter] =
529 {
530 { Box, 0.0f, 0.5f, 0.0f, 0.0f }, /* Undefined */
531 { Box, 0.0f, 0.5f, 0.0f, 0.0f }, /* Point */
532 { Box, 0.5f, 0.5f, 0.0f, 0.0f }, /* Box */
533 { Triangle, 1.0f, 1.0f, 0.0f, 0.0f }, /* Triangle */
534 { CubicBC, 1.0f, 1.0f, 0.0f, 0.0f }, /* Hermite, Cubic B=C=0 */
535 { Hanning, 1.0f, 1.0f, 0.0f, 0.0f }, /* Hanning, Cosine window */
536 { Hamming, 1.0f, 1.0f, 0.0f, 0.0f }, /* Hamming, '' variation */
537 { Blackman, 1.0f, 1.0f, 0.0f, 0.0f }, /* Blackman, 2*cos window */
538 { Gaussian, 1.5f, 1.5f, 0.0f, 0.0f }, /* Gaussian */
539 { Quadratic, 1.5f, 1.5f, 0.0f, 0.0f }, /* Quadratic Gaussian */
540 { CubicBC, 2.0f, 2.0f, 1.0f, 0.0f }, /* B-Spline of Gaussian B=1 C=0 */
541 { CubicBC, 2.0f, 1.0f, 0.0f, 0.5f }, /* Catmull-Rom B=0 C=1/2 */
542 { CubicBC, 2.0f, 1.0f, 1.0f/3.0f, 1.0f/3.0f }, /* Mitchel B=C=1/3 */
543 { Sinc, 3.0f, 1.0f, 0.0f, 0.0f }, /* Lanczos, 3 lobed Sinc-Sinc */
544 { Bessel, 3.2383f,1.2197f,.0f,.0f }, /* 3 lobed Blackman-Bessel */
545 { Sinc, 4.0f, 1.0f, 0.0f, 0.0f }, /* 4 lobed Blackman-Sinc */
546 { Kaiser, 1.0f, 1.0f, 0.0f, 0.0f }, /* Kaiser, sq-root windowing */
547 { Welsh, 1.0f, 1.0f, 0.0f, 0.0f }, /* Welsh, Parabolic windowing */
548 { CubicBC, 2.0f, 2.0f, 1.0f, 0.0f }, /* Parzen, B-Spline windowing */
549 { Lagrange, 2.0f, 1.0f, 0.0f, 0.0f }, /* Lagrangian Filter */
550 { Bohman, 1.0f, 1.0f, 0.0f, 0.0f }, /* Bohman, 2*Cosine windowing */
551 { Triangle, 1.0f, 1.0f, 0.0f, 0.0f } /* Bartlett, Triangle windowing */
552 };
553 /*
cristy33b1c162010-01-23 22:51:51 +0000554 The known zero crossings of the Bessel() or the Jinc(x*PI) function found
555 by using http://cose.math.bas.bg/webMathematica/webComputing/
556 BesselZeros.jsp. For Jv-function with v=1, divide X-roots by PI (tabled
557 below).
cristy3ed852e2009-09-05 21:47:34 +0000558 */
559 static MagickRealType
560 bessel_zeros[16] =
561 {
562 1.21966989126651f,
563 2.23313059438153f,
564 3.23831548416624f,
565 4.24106286379607f,
566 5.24276437687019f,
567 6.24392168986449f,
568 7.24475986871996f,
569 8.24539491395205f,
570 9.24589268494948f,
571 10.2462933487549f,
572 11.2466227948779f,
573 12.2468984611381f,
574 13.2471325221811f,
575 14.2473337358069f,
576 15.2475085630373f,
577 16.247661874701f
578 };
579
cristy33b1c162010-01-23 22:51:51 +0000580 /*
581 Allocate resize filter.
582 */
cristy3ed852e2009-09-05 21:47:34 +0000583 assert(image != (const Image *) NULL);
584 assert(image->signature == MagickSignature);
585 if (image->debug != MagickFalse)
586 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
587 assert(UndefinedFilter < filter && filter < SentinelFilter);
588 assert(exception != (ExceptionInfo *) NULL);
589 assert(exception->signature == MagickSignature);
cristy90823212009-12-12 20:48:33 +0000590 resize_filter=(ResizeFilter *) AcquireAlignedMemory(1,sizeof(*resize_filter));
cristy3ed852e2009-09-05 21:47:34 +0000591 if (resize_filter == (ResizeFilter *) NULL)
592 ThrowFatalException(ResourceLimitFatalError,"MemoryAllocationFailed");
cristy33b1c162010-01-23 22:51:51 +0000593 /*
594 Defaults for the requested filter.
595 */
596 filter_type=mapping[filter].filter;
597 window_type=mapping[filter].window;
598 /*
599 Filter blur -- scaling both filter and support window.
600 */
601 resize_filter->blur=blur;
cristy3ed852e2009-09-05 21:47:34 +0000602 artifact=GetImageArtifact(image,"filter:blur");
603 if (artifact != (const char *) NULL)
cristy33b1c162010-01-23 22:51:51 +0000604 resize_filter->blur=StringToDouble(artifact);
605 if (resize_filter->blur < MagickEpsilon)
606 resize_filter->blur=(MagickRealType) MagickEpsilon;
607 if ((cylindrical != MagickFalse) && (filter != SincFilter))
608 switch (filter_type)
609 {
610 case SincFilter:
611 {
612 /*
613 Promote 1D Sinc Filter to a 2D Bessel filter.
614 */
615 filter_type=BesselFilter;
616 break;
617 }
618 case LanczosFilter:
619 {
620 /*
621 Promote Lanczos (Sinc-Sinc) to Lanczos (Bessel-Bessel).
622 */
623 filter_type=BesselFilter;
624 window_type=BesselFilter;
625 break;
626 }
627 case GaussianFilter:
628 {
629 /*
630 Gaussian is scaled by 4*ln(2) and not 4*sqrt(2/MagickPI) according to
631 Paul Heckbert's paper on EWA resampling.
anthonyc4c86e02010-01-27 09:30:32 +0000632 FUTURE: to be reviewed.
cristy33b1c162010-01-23 22:51:51 +0000633 */
634 resize_filter->blur*=2.0*log(2.0)/sqrt(2.0/MagickPI);
635 break;
636 }
637 case BesselFilter:
638 {
639 /*
640 Filters with a 1.0 zero root crossing by the first bessel zero.
641 */
642 resize_filter->blur*=bessel_zeros[0];
643 break;
644 }
cristya782ecf2010-01-25 02:59:14 +0000645 default:
646 break;
cristy3ed852e2009-09-05 21:47:34 +0000647 }
cristy3ed852e2009-09-05 21:47:34 +0000648 artifact=GetImageArtifact(image,"filter:filter");
cristy33b1c162010-01-23 22:51:51 +0000649 if (artifact != (const char *) NULL)
650 {
651 option=ParseMagickOption(MagickFilterOptions,MagickFalse,
cristy3ed852e2009-09-05 21:47:34 +0000652 artifact);
cristy33b1c162010-01-23 22:51:51 +0000653 if ((UndefinedFilter < option) && (option < SentinelFilter))
654 {
655 /*
656 Raw filter request - no window function.
657 */
658 filter_type=(FilterTypes) option;
659 window_type=BoxFilter;
660 }
661 if (option == LanczosFilter)
662 {
663 /*
664 Lanczos is nor a real filter but a self windowing Sinc/Bessel.
665 */
666 filter_type=cylindrical != MagickFalse ? BesselFilter : LanczosFilter;
667 window_type=cylindrical != MagickFalse ? BesselFilter : SincFilter;
668 }
669 /*
670 Filter overwide with a specific window function.
671 */
672 artifact=GetImageArtifact(image,"filter:window");
673 if (artifact != (const char *) NULL)
674 {
675 option=ParseMagickOption(MagickFilterOptions,MagickFalse,
676 artifact);
677 if ((UndefinedFilter < option) && (option < SentinelFilter))
678 {
679 if (option != LanczosFilter)
680 window_type=(FilterTypes) option;
681 else
682 window_type=cylindrical != MagickFalse ? BesselFilter :
683 SincFilter;
684 }
685 }
cristy3ed852e2009-09-05 21:47:34 +0000686 }
cristy33b1c162010-01-23 22:51:51 +0000687 else
688 {
689 /*
690 Window specified, but no filter function? Assume Sinc/Bessel.
691 */
692 artifact=GetImageArtifact(image,"filter:window");
693 if (artifact != (const char *) NULL)
694 {
695 option=ParseMagickOption(MagickFilterOptions,MagickFalse,
696 artifact);
697 if ((UndefinedFilter < option) && (option < SentinelFilter))
698 {
699 option=cylindrical != MagickFalse ? BesselFilter : SincFilter;
700 if (option != LanczosFilter)
701 window_type=(FilterTypes) option;
702 else
703 window_type=option;
704 }
705 }
706 }
707 resize_filter->filter=filters[filter_type].function;
708 resize_filter->support=filters[filter_type].support;
709 resize_filter->window=filters[window_type].function;
710 resize_filter->scale=filters[window_type].scale;
cristy3ed852e2009-09-05 21:47:34 +0000711 resize_filter->signature=MagickSignature;
cristy33b1c162010-01-23 22:51:51 +0000712 /*
713 Filter support overrides.
714 */
cristy3ed852e2009-09-05 21:47:34 +0000715 artifact=GetImageArtifact(image,"filter:lobes");
cristy33b1c162010-01-23 22:51:51 +0000716 if (artifact != (const char *) NULL)
717 {
718 long
719 lobes;
720
721 lobes=StringToLong(artifact);
722 if (lobes < 1)
723 lobes=1;
724 resize_filter->support=(MagickRealType) lobes;
725 if (filter_type == BesselFilter)
726 {
727 if (lobes > 16)
728 lobes=16;
729 resize_filter->support=bessel_zeros[lobes-1];
730 }
cristy3ed852e2009-09-05 21:47:34 +0000731 }
cristy3ed852e2009-09-05 21:47:34 +0000732 artifact=GetImageArtifact(image,"filter:support");
733 if (artifact != (const char *) NULL)
cristy33b1c162010-01-23 22:51:51 +0000734 resize_filter->support=fabs(StringToDouble(artifact));
735 /*
736 Scale windowing function separatally to the support 'clipping' window
737 that calling operator is planning to actually use.
cristy3ed852e2009-09-05 21:47:34 +0000738 */
cristy33b1c162010-01-23 22:51:51 +0000739 resize_filter->window_support=resize_filter->support;
cristy3ed852e2009-09-05 21:47:34 +0000740 artifact=GetImageArtifact(image,"filter:win-support");
741 if (artifact != (const char *) NULL)
cristy33b1c162010-01-23 22:51:51 +0000742 resize_filter->window_support=fabs(StringToDouble(artifact));
743 /*
744 Set Cubic Spline B,C values, calculate Cubic coefficents.
745 */
cristy3ed852e2009-09-05 21:47:34 +0000746 B=0.0;
747 C=0.0;
cristy33b1c162010-01-23 22:51:51 +0000748 if ((filters[filter_type].function == CubicBC) ||
749 (filters[window_type].function == CubicBC))
750 {
751 if (filters[filter_type].function == CubicBC)
752 {
753 B=filters[filter_type].B;
754 C=filters[filter_type].C;
755 }
756 else
757 if (filters[window_type].function == CubicBC)
758 {
759 B=filters[window_type].B;
760 C=filters[window_type].C;
761 }
762 artifact=GetImageArtifact(image,"filter:b");
cristy3ed852e2009-09-05 21:47:34 +0000763 if (artifact != (const char *) NULL)
cristy33b1c162010-01-23 22:51:51 +0000764 {
765 B=StringToDouble(artifact);
766 C=(1.0-B)/2.0; /* Calculate C as if it is a Keys cubic filter */
767 artifact=GetImageArtifact(image,"filter:c");
768 if (artifact != (const char *) NULL)
769 C=StringToDouble(artifact);
770 }
771 else
772 {
773 artifact=GetImageArtifact(image,"filter:c");
774 if (artifact != (const char *) NULL)
775 {
776 C=StringToDouble(artifact);
777 B=1.0-2.0*C; /* Calculate B as if it is a Keys cubic filter */
778 }
779 }
780 /*
781 Convert B,C values into Cubic Coefficents. See CubicBC()
782 */
783 resize_filter->cubic[0]=(6.0-2.0*B)/6.0;
cristy3ed852e2009-09-05 21:47:34 +0000784 resize_filter->cubic[1]=0.0;
cristy33b1c162010-01-23 22:51:51 +0000785 resize_filter->cubic[2]=(-18.0+12.0*B+6.0*C)/6.0;
786 resize_filter->cubic[3]=(12.0-9.0*B-6.0*C)/6.0;
787 resize_filter->cubic[4]=(8.0*B+24.0*C)/6.0;
788 resize_filter->cubic[5]=(-12.0*B-48.0*C)/6.0;
789 resize_filter->cubic[6]=(6.0*B+30.0*C)/6.0;
790 resize_filter->cubic[7]=(- 1.0*B-6.0*C)/6.0;
cristy3ed852e2009-09-05 21:47:34 +0000791 }
792 artifact=GetImageArtifact(image,"filter:verbose");
793 if (artifact != (const char *) NULL)
794 {
795 double
796 support,
797 x;
798
799 /*
800 Output filter graph -- for graphing filter result.
801 */
802 support=GetResizeFilterSupport(resize_filter);
803 (void) printf("# support = %lg\n",support);
804 for (x=0.0; x <= support; x+=0.01f)
cristy4f3c0be2009-09-12 16:04:05 +0000805 (void) printf("%5.2lf\t%lf\n",x,(double) GetResizeFilterWeight(
806 resize_filter,x));
cristy3ed852e2009-09-05 21:47:34 +0000807 (void) printf("%5.2lf\t%lf\n",support,0.0);
808 }
809 return(resize_filter);
810}
811
812/*
813%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
814% %
815% %
816% %
817% A d a p t i v e R e s i z e I m a g e %
818% %
819% %
820% %
821%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
822%
823% AdaptiveResizeImage() adaptively resize image with pixel resampling.
824%
825% The format of the AdaptiveResizeImage method is:
826%
827% Image *AdaptiveResizeImage(const Image *image,
828% const unsigned long columns,const unsigned long rows,
829% ExceptionInfo *exception)
830%
831% A description of each parameter follows:
832%
833% o image: the image.
834%
835% o columns: the number of columns in the resized image.
836%
837% o rows: the number of rows in the resized image.
838%
839% o exception: return any errors or warnings in this structure.
840%
841*/
842MagickExport Image *AdaptiveResizeImage(const Image *image,
843 const unsigned long columns,const unsigned long rows,ExceptionInfo *exception)
844{
845#define AdaptiveResizeImageTag "Resize/Image"
846
cristyc4c8d132010-01-07 01:58:38 +0000847 CacheView
848 *resize_view;
849
cristy3ed852e2009-09-05 21:47:34 +0000850 Image
851 *resize_image;
852
853 long
854 y;
855
856 MagickBooleanType
857 proceed;
858
859 MagickPixelPacket
860 pixel;
861
862 PointInfo
863 offset;
864
865 ResampleFilter
866 *resample_filter;
867
cristy3ed852e2009-09-05 21:47:34 +0000868 /*
869 Adaptively resize image.
870 */
871 assert(image != (const Image *) NULL);
872 assert(image->signature == MagickSignature);
873 if (image->debug != MagickFalse)
874 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
875 assert(exception != (ExceptionInfo *) NULL);
876 assert(exception->signature == MagickSignature);
877 if ((columns == 0) || (rows == 0))
878 return((Image *) NULL);
879 if ((columns == image->columns) && (rows == image->rows))
880 return(CloneImage(image,0,0,MagickTrue,exception));
881 resize_image=CloneImage(image,columns,rows,MagickTrue,exception);
882 if (resize_image == (Image *) NULL)
883 return((Image *) NULL);
884 if (SetImageStorageClass(resize_image,DirectClass) == MagickFalse)
885 {
886 InheritException(exception,&resize_image->exception);
887 resize_image=DestroyImage(resize_image);
888 return((Image *) NULL);
889 }
890 GetMagickPixelPacket(image,&pixel);
891 resample_filter=AcquireResampleFilter(image,exception);
892 if (image->interpolate == UndefinedInterpolatePixel)
893 (void) SetResampleFilterInterpolateMethod(resample_filter,
894 MeshInterpolatePixel);
895 resize_view=AcquireCacheView(resize_image);
896 for (y=0; y < (long) resize_image->rows; y++)
897 {
898 register IndexPacket
cristyc47d1f82009-11-26 01:44:43 +0000899 *restrict resize_indexes;
cristy3ed852e2009-09-05 21:47:34 +0000900
901 register long
902 x;
903
904 register PixelPacket
cristyc47d1f82009-11-26 01:44:43 +0000905 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +0000906
907 q=QueueCacheViewAuthenticPixels(resize_view,0,y,resize_image->columns,1,
908 exception);
909 if (q == (PixelPacket *) NULL)
910 break;
911 resize_indexes=GetCacheViewAuthenticIndexQueue(resize_view);
912 offset.y=((MagickRealType) y*image->rows/resize_image->rows);
913 for (x=0; x < (long) resize_image->columns; x++)
914 {
915 offset.x=((MagickRealType) x*image->columns/resize_image->columns);
916 (void) ResamplePixelColor(resample_filter,offset.x-0.5,offset.y-0.5,
917 &pixel);
918 SetPixelPacket(resize_image,&pixel,q,resize_indexes+x);
919 q++;
920 }
921 if (SyncCacheViewAuthenticPixels(resize_view,exception) == MagickFalse)
922 break;
923 proceed=SetImageProgress(image,AdaptiveResizeImageTag,y,image->rows);
924 if (proceed == MagickFalse)
925 break;
926 }
927 resample_filter=DestroyResampleFilter(resample_filter);
928 resize_view=DestroyCacheView(resize_view);
929 return(resize_image);
930}
931
932/*
933%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
934% %
935% %
936% %
937+ B e s s e l O r d e r O n e %
938% %
939% %
940% %
941%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
942%
943% BesselOrderOne() computes the Bessel function of x of the first kind of
944% order 0:
945%
946% Reduce x to |x| since j1(x)= -j1(-x), and for x in (0,8]
947%
948% j1(x) = x*j1(x);
949%
950% For x in (8,inf)
951%
952% j1(x) = sqrt(2/(pi*x))*(p1(x)*cos(x1)-q1(x)*sin(x1))
953%
954% where x1 = x-3*pi/4. Compute sin(x1) and cos(x1) as follow:
955%
956% cos(x1) = cos(x)cos(3pi/4)+sin(x)sin(3pi/4)
957% = 1/sqrt(2) * (sin(x) - cos(x))
958% sin(x1) = sin(x)cos(3pi/4)-cos(x)sin(3pi/4)
959% = -1/sqrt(2) * (sin(x) + cos(x))
960%
961% The format of the BesselOrderOne method is:
962%
963% MagickRealType BesselOrderOne(MagickRealType x)
964%
965% A description of each parameter follows:
966%
967% o x: MagickRealType value.
968%
969*/
970
971#undef I0
972static MagickRealType I0(MagickRealType x)
973{
974 MagickRealType
975 sum,
976 t,
977 y;
978
979 register long
980 i;
981
982 /*
983 Zeroth order Bessel function of the first kind.
984 */
985 sum=1.0;
986 y=x*x/4.0;
987 t=y;
988 for (i=2; t > MagickEpsilon; i++)
989 {
990 sum+=t;
991 t*=y/((MagickRealType) i*i);
992 }
993 return(sum);
994}
995
996#undef J1
997static MagickRealType J1(MagickRealType x)
998{
999 MagickRealType
1000 p,
1001 q;
1002
1003 register long
1004 i;
1005
1006 static const double
1007 Pone[] =
1008 {
1009 0.581199354001606143928050809e+21,
1010 -0.6672106568924916298020941484e+20,
1011 0.2316433580634002297931815435e+19,
1012 -0.3588817569910106050743641413e+17,
1013 0.2908795263834775409737601689e+15,
1014 -0.1322983480332126453125473247e+13,
1015 0.3413234182301700539091292655e+10,
1016 -0.4695753530642995859767162166e+7,
1017 0.270112271089232341485679099e+4
1018 },
1019 Qone[] =
1020 {
1021 0.11623987080032122878585294e+22,
1022 0.1185770712190320999837113348e+20,
1023 0.6092061398917521746105196863e+17,
1024 0.2081661221307607351240184229e+15,
1025 0.5243710262167649715406728642e+12,
1026 0.1013863514358673989967045588e+10,
1027 0.1501793594998585505921097578e+7,
1028 0.1606931573481487801970916749e+4,
1029 0.1e+1
1030 };
1031
1032 p=Pone[8];
1033 q=Qone[8];
1034 for (i=7; i >= 0; i--)
1035 {
1036 p=p*x*x+Pone[i];
1037 q=q*x*x+Qone[i];
1038 }
1039 return(p/q);
1040}
1041
1042#undef P1
1043static MagickRealType P1(MagickRealType x)
1044{
1045 MagickRealType
1046 p,
1047 q;
1048
1049 register long
1050 i;
1051
1052 static const double
1053 Pone[] =
1054 {
1055 0.352246649133679798341724373e+5,
1056 0.62758845247161281269005675e+5,
1057 0.313539631109159574238669888e+5,
1058 0.49854832060594338434500455e+4,
1059 0.2111529182853962382105718e+3,
1060 0.12571716929145341558495e+1
1061 },
1062 Qone[] =
1063 {
1064 0.352246649133679798068390431e+5,
1065 0.626943469593560511888833731e+5,
1066 0.312404063819041039923015703e+5,
1067 0.4930396490181088979386097e+4,
1068 0.2030775189134759322293574e+3,
1069 0.1e+1
1070 };
1071
1072 p=Pone[5];
1073 q=Qone[5];
1074 for (i=4; i >= 0; i--)
1075 {
1076 p=p*(8.0/x)*(8.0/x)+Pone[i];
1077 q=q*(8.0/x)*(8.0/x)+Qone[i];
1078 }
1079 return(p/q);
1080}
1081
1082#undef Q1
1083static MagickRealType Q1(MagickRealType x)
1084{
1085 MagickRealType
1086 p,
1087 q;
1088
1089 register long
1090 i;
1091
1092 static const double
1093 Pone[] =
1094 {
1095 0.3511751914303552822533318e+3,
1096 0.7210391804904475039280863e+3,
1097 0.4259873011654442389886993e+3,
1098 0.831898957673850827325226e+2,
1099 0.45681716295512267064405e+1,
1100 0.3532840052740123642735e-1
1101 },
1102 Qone[] =
1103 {
1104 0.74917374171809127714519505e+4,
1105 0.154141773392650970499848051e+5,
1106 0.91522317015169922705904727e+4,
1107 0.18111867005523513506724158e+4,
1108 0.1038187585462133728776636e+3,
1109 0.1e+1
1110 };
1111
1112 p=Pone[5];
1113 q=Qone[5];
1114 for (i=4; i >= 0; i--)
1115 {
1116 p=p*(8.0/x)*(8.0/x)+Pone[i];
1117 q=q*(8.0/x)*(8.0/x)+Qone[i];
1118 }
1119 return(p/q);
1120}
1121
1122static MagickRealType BesselOrderOne(MagickRealType x)
1123{
1124 MagickRealType
1125 p,
1126 q;
1127
1128 if (x == 0.0)
1129 return(0.0);
1130 p=x;
1131 if (x < 0.0)
1132 x=(-x);
1133 if (x < 8.0)
1134 return(p*J1(x));
1135 q=sqrt((double) (2.0/(MagickPI*x)))*(P1(x)*(1.0/sqrt(2.0)*(sin((double) x)-
1136 cos((double) x)))-8.0/x*Q1(x)*(-1.0/sqrt(2.0)*(sin((double) x)+
1137 cos((double) x))));
1138 if (p < 0.0)
1139 q=(-q);
1140 return(q);
1141}
1142
1143/*
1144%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1145% %
1146% %
1147% %
1148+ D e s t r o y R e s i z e F i l t e r %
1149% %
1150% %
1151% %
1152%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1153%
1154% DestroyResizeFilter() destroy the resize filter.
1155%
cristya2ffd7e2010-03-10 20:50:30 +00001156% The format of the DestroyResizeFilter method is:
cristy3ed852e2009-09-05 21:47:34 +00001157%
1158% ResizeFilter *DestroyResizeFilter(ResizeFilter *resize_filter)
1159%
1160% A description of each parameter follows:
1161%
1162% o resize_filter: the resize filter.
1163%
1164*/
1165MagickExport ResizeFilter *DestroyResizeFilter(ResizeFilter *resize_filter)
1166{
1167 assert(resize_filter != (ResizeFilter *) NULL);
1168 assert(resize_filter->signature == MagickSignature);
1169 resize_filter->signature=(~MagickSignature);
1170 resize_filter=(ResizeFilter *) RelinquishMagickMemory(resize_filter);
1171 return(resize_filter);
1172}
1173
1174/*
1175%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1176% %
1177% %
1178% %
1179+ G e t R e s i z e F i l t e r S u p p o r t %
1180% %
1181% %
1182% %
1183%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1184%
1185% GetResizeFilterSupport() return the current support window size for this
1186% filter. Note that this may have been enlarged by filter:blur factor.
1187%
1188% The format of the GetResizeFilterSupport method is:
1189%
1190% MagickRealType GetResizeFilterSupport(const ResizeFilter *resize_filter)
1191%
1192% A description of each parameter follows:
1193%
1194% o filter: Image filter to use.
1195%
1196*/
1197MagickExport MagickRealType GetResizeFilterSupport(
1198 const ResizeFilter *resize_filter)
1199{
1200 assert(resize_filter != (ResizeFilter *) NULL);
1201 assert(resize_filter->signature == MagickSignature);
1202 return(resize_filter->support*resize_filter->blur);
1203}
1204
1205/*
1206%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1207% %
1208% %
1209% %
1210+ G e t R e s i z e F i l t e r W e i g h t %
1211% %
1212% %
1213% %
1214%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1215%
1216% GetResizeFilterWeight evaluates the specified resize filter at the point x
1217% which usally lies between zero and the filters current 'support' and
1218% returns the weight of the filter function at that point.
1219%
1220% The format of the GetResizeFilterWeight method is:
1221%
1222% MagickRealType GetResizeFilterWeight(const ResizeFilter *resize_filter,
1223% const MagickRealType x)
1224%
1225% A description of each parameter follows:
1226%
1227% o filter: the filter type.
1228%
1229% o x: the point.
1230%
1231*/
1232MagickExport MagickRealType GetResizeFilterWeight(
1233 const ResizeFilter *resize_filter,const MagickRealType x)
1234{
1235 MagickRealType
1236 blur,
1237 scale;
1238
1239 /*
1240 Windowing function - scale the weighting filter by this amount.
1241 */
1242 assert(resize_filter != (ResizeFilter *) NULL);
1243 assert(resize_filter->signature == MagickSignature);
1244 blur=fabs(x)/resize_filter->blur; /* X offset with blur scaling */
1245 if ((resize_filter->window_support < MagickEpsilon) ||
1246 (resize_filter->window == Box))
1247 scale=1.0; /* Point/Box Filter -- avoid division by zero */
1248 else
1249 {
1250 scale=resize_filter->scale/resize_filter->window_support;
1251 scale=resize_filter->window(blur*scale,resize_filter);
1252 }
1253 return(scale*resize_filter->filter(blur,resize_filter));
1254}
1255
1256/*
1257%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1258% %
1259% %
1260% %
1261% M a g n i f y I m a g e %
1262% %
1263% %
1264% %
1265%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1266%
1267% MagnifyImage() is a convenience method that scales an image proportionally
1268% to twice its size.
1269%
1270% The format of the MagnifyImage method is:
1271%
1272% Image *MagnifyImage(const Image *image,ExceptionInfo *exception)
1273%
1274% A description of each parameter follows:
1275%
1276% o image: the image.
1277%
1278% o exception: return any errors or warnings in this structure.
1279%
1280*/
1281MagickExport Image *MagnifyImage(const Image *image,ExceptionInfo *exception)
1282{
1283 Image
1284 *magnify_image;
1285
1286 assert(image != (Image *) NULL);
1287 assert(image->signature == MagickSignature);
1288 if (image->debug != MagickFalse)
1289 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
1290 assert(exception != (ExceptionInfo *) NULL);
1291 assert(exception->signature == MagickSignature);
1292 magnify_image=ResizeImage(image,2*image->columns,2*image->rows,CubicFilter,
1293 1.0,exception);
1294 return(magnify_image);
1295}
1296
1297/*
1298%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1299% %
1300% %
1301% %
1302% M i n i f y I m a g e %
1303% %
1304% %
1305% %
1306%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1307%
1308% MinifyImage() is a convenience method that scales an image proportionally
1309% to half its size.
1310%
1311% The format of the MinifyImage method is:
1312%
1313% Image *MinifyImage(const Image *image,ExceptionInfo *exception)
1314%
1315% A description of each parameter follows:
1316%
1317% o image: the image.
1318%
1319% o exception: return any errors or warnings in this structure.
1320%
1321*/
1322MagickExport Image *MinifyImage(const Image *image,ExceptionInfo *exception)
1323{
1324 Image
1325 *minify_image;
1326
1327 assert(image != (Image *) NULL);
1328 assert(image->signature == MagickSignature);
1329 if (image->debug != MagickFalse)
1330 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
1331 assert(exception != (ExceptionInfo *) NULL);
1332 assert(exception->signature == MagickSignature);
1333 minify_image=ResizeImage(image,image->columns/2,image->rows/2,CubicFilter,
1334 1.0,exception);
1335 return(minify_image);
1336}
1337
1338/*
1339%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1340% %
1341% %
1342% %
1343% R e s a m p l e I m a g e %
1344% %
1345% %
1346% %
1347%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1348%
1349% ResampleImage() resize image in terms of its pixel size, so that when
1350% displayed at the given resolution it will be the same size in terms of
1351% real world units as the original image at the original resolution.
1352%
1353% The format of the ResampleImage method is:
1354%
1355% Image *ResampleImage(Image *image,const double x_resolution,
1356% const double y_resolution,const FilterTypes filter,const double blur,
1357% ExceptionInfo *exception)
1358%
1359% A description of each parameter follows:
1360%
1361% o image: the image to be resized to fit the given resolution.
1362%
1363% o x_resolution: the new image x resolution.
1364%
1365% o y_resolution: the new image y resolution.
1366%
1367% o filter: Image filter to use.
1368%
1369% o blur: the blur factor where > 1 is blurry, < 1 is sharp.
1370%
1371*/
1372MagickExport Image *ResampleImage(const Image *image,const double x_resolution,
1373 const double y_resolution,const FilterTypes filter,const double blur,
1374 ExceptionInfo *exception)
1375{
1376#define ResampleImageTag "Resample/Image"
1377
1378 Image
1379 *resample_image;
1380
1381 unsigned long
1382 height,
1383 width;
1384
1385 /*
1386 Initialize sampled image attributes.
1387 */
1388 assert(image != (const Image *) NULL);
1389 assert(image->signature == MagickSignature);
1390 if (image->debug != MagickFalse)
1391 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
1392 assert(exception != (ExceptionInfo *) NULL);
1393 assert(exception->signature == MagickSignature);
1394 width=(unsigned long) (x_resolution*image->columns/
1395 (image->x_resolution == 0.0 ? 72.0 : image->x_resolution)+0.5);
1396 height=(unsigned long) (y_resolution*image->rows/
1397 (image->y_resolution == 0.0 ? 72.0 : image->y_resolution)+0.5);
1398 resample_image=ResizeImage(image,width,height,filter,blur,exception);
1399 if (resample_image != (Image *) NULL)
1400 {
1401 resample_image->x_resolution=x_resolution;
1402 resample_image->y_resolution=y_resolution;
1403 }
1404 return(resample_image);
1405}
1406#if defined(MAGICKCORE_LQR_DELEGATE)
1407
1408/*
1409%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1410% %
1411% %
1412% %
1413% L i q u i d R e s c a l e I m a g e %
1414% %
1415% %
1416% %
1417%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1418%
1419% LiquidRescaleImage() rescales image with seam carving.
1420%
1421% The format of the LiquidRescaleImage method is:
1422%
1423% Image *LiquidRescaleImage(const Image *image,
1424% const unsigned long columns,const unsigned long rows,
1425% const double delta_x,const double rigidity,ExceptionInfo *exception)
1426%
1427% A description of each parameter follows:
1428%
1429% o image: the image.
1430%
1431% o columns: the number of columns in the rescaled image.
1432%
1433% o rows: the number of rows in the rescaled image.
1434%
1435% o delta_x: maximum seam transversal step (0 means straight seams).
1436%
1437% o rigidity: introduce a bias for non-straight seams (typically 0).
1438%
1439% o exception: return any errors or warnings in this structure.
1440%
1441*/
1442MagickExport Image *LiquidRescaleImage(const Image *image,
1443 const unsigned long columns,const unsigned long rows,
1444 const double delta_x,const double rigidity,ExceptionInfo *exception)
1445{
1446#define LiquidRescaleImageTag "Rescale/Image"
1447
1448 const char
1449 *map;
1450
1451 guchar
1452 *packet;
1453
1454 Image
1455 *rescale_image;
1456
1457 int
1458 x,
1459 y;
1460
1461 LqrCarver
1462 *carver;
1463
1464 LqrRetVal
1465 lqr_status;
1466
1467 MagickBooleanType
1468 status;
1469
1470 MagickPixelPacket
1471 pixel;
1472
1473 unsigned char
1474 *pixels;
1475
1476 /*
1477 Liquid rescale image.
1478 */
1479 assert(image != (const Image *) NULL);
1480 assert(image->signature == MagickSignature);
1481 if (image->debug != MagickFalse)
1482 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
1483 assert(exception != (ExceptionInfo *) NULL);
1484 assert(exception->signature == MagickSignature);
1485 if ((columns == 0) || (rows == 0))
1486 return((Image *) NULL);
1487 if ((columns == image->columns) && (rows == image->rows))
1488 return(CloneImage(image,0,0,MagickTrue,exception));
1489 if ((columns <= 2) || (rows <= 2))
1490 return(ZoomImage(image,columns,rows,exception));
1491 if ((columns >= (2*image->columns)) || (rows >= (2*image->rows)))
1492 {
1493 Image
1494 *resize_image;
1495
1496 unsigned long
1497 height,
1498 width;
1499
1500 /*
1501 Honor liquid resize size limitations.
1502 */
1503 for (width=image->columns; columns >= (2*width-1); width*=2);
1504 for (height=image->rows; rows >= (2*height-1); height*=2);
1505 resize_image=ResizeImage(image,width,height,image->filter,image->blur,
1506 exception);
1507 if (resize_image == (Image *) NULL)
1508 return((Image *) NULL);
1509 rescale_image=LiquidRescaleImage(resize_image,columns,rows,delta_x,
1510 rigidity,exception);
1511 resize_image=DestroyImage(resize_image);
1512 return(rescale_image);
1513 }
1514 map="RGB";
1515 if (image->matte == MagickFalse)
1516 map="RGBA";
1517 if (image->colorspace == CMYKColorspace)
1518 {
1519 map="CMYK";
1520 if (image->matte == MagickFalse)
1521 map="CMYKA";
1522 }
1523 pixels=(unsigned char *) AcquireQuantumMemory(image->columns,image->rows*
1524 strlen(map)*sizeof(*pixels));
1525 if (pixels == (unsigned char *) NULL)
1526 return((Image *) NULL);
1527 status=ExportImagePixels(image,0,0,image->columns,image->rows,map,CharPixel,
1528 pixels,exception);
1529 if (status == MagickFalse)
1530 {
1531 pixels=(unsigned char *) RelinquishMagickMemory(pixels);
1532 ThrowImageException(ResourceLimitError,"MemoryAllocationFailed");
1533 }
1534 carver=lqr_carver_new(pixels,image->columns,image->rows,strlen(map));
1535 if (carver == (LqrCarver *) NULL)
1536 {
1537 pixels=(unsigned char *) RelinquishMagickMemory(pixels);
1538 ThrowImageException(ResourceLimitError,"MemoryAllocationFailed");
1539 }
1540 lqr_status=lqr_carver_init(carver,(int) delta_x,rigidity);
1541 lqr_status=lqr_carver_resize(carver,columns,rows);
1542 rescale_image=CloneImage(image,lqr_carver_get_width(carver),
1543 lqr_carver_get_height(carver),MagickTrue,exception);
1544 if (rescale_image == (Image *) NULL)
1545 {
1546 pixels=(unsigned char *) RelinquishMagickMemory(pixels);
1547 return((Image *) NULL);
1548 }
1549 if (SetImageStorageClass(rescale_image,DirectClass) == MagickFalse)
1550 {
1551 InheritException(exception,&rescale_image->exception);
1552 rescale_image=DestroyImage(rescale_image);
1553 return((Image *) NULL);
1554 }
1555 GetMagickPixelPacket(rescale_image,&pixel);
1556 (void) lqr_carver_scan_reset(carver);
1557 while (lqr_carver_scan(carver,&x,&y,&packet) != 0)
1558 {
1559 register IndexPacket
cristyc47d1f82009-11-26 01:44:43 +00001560 *restrict rescale_indexes;
cristy3ed852e2009-09-05 21:47:34 +00001561
1562 register PixelPacket
cristyc47d1f82009-11-26 01:44:43 +00001563 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +00001564
1565 q=QueueAuthenticPixels(rescale_image,x,y,1,1,exception);
1566 if (q == (PixelPacket *) NULL)
1567 break;
1568 rescale_indexes=GetAuthenticIndexQueue(rescale_image);
1569 pixel.red=QuantumRange*(packet[0]/255.0);
1570 pixel.green=QuantumRange*(packet[1]/255.0);
1571 pixel.blue=QuantumRange*(packet[2]/255.0);
1572 if (image->colorspace != CMYKColorspace)
1573 {
1574 if (image->matte == MagickFalse)
1575 pixel.opacity=QuantumRange*(packet[3]/255.0);
1576 }
1577 else
1578 {
1579 pixel.index=QuantumRange*(packet[3]/255.0);
1580 if (image->matte == MagickFalse)
1581 pixel.opacity=QuantumRange*(packet[4]/255.0);
1582 }
1583 SetPixelPacket(rescale_image,&pixel,q,rescale_indexes);
1584 if (SyncAuthenticPixels(rescale_image,exception) == MagickFalse)
1585 break;
1586 }
1587 /*
1588 Relinquish resources.
1589 */
1590 lqr_carver_destroy(carver);
1591 return(rescale_image);
1592}
1593#else
1594MagickExport Image *LiquidRescaleImage(const Image *image,
1595 const unsigned long magick_unused(columns),
1596 const unsigned long magick_unused(rows),const double magick_unused(delta_x),
1597 const double magick_unused(rigidity),ExceptionInfo *exception)
1598{
1599 assert(image != (const Image *) NULL);
1600 assert(image->signature == MagickSignature);
1601 if (image->debug != MagickFalse)
1602 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
1603 assert(exception != (ExceptionInfo *) NULL);
1604 assert(exception->signature == MagickSignature);
1605 (void) ThrowMagickException(exception,GetMagickModule(),MissingDelegateError,
1606 "DelegateLibrarySupportNotBuiltIn","`%s' (LQR)",image->filename);
1607 return((Image *) NULL);
1608}
1609#endif
1610
1611/*
1612%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1613% %
1614% %
1615% %
1616% R e s i z e I m a g e %
1617% %
1618% %
1619% %
1620%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1621%
1622% ResizeImage() scales an image to the desired dimensions, using the given
1623% filter (see AcquireFilterInfo() ).
1624%
1625% If an undefined filter is given the filter defaults to Mitchell for a
1626% colormapped image, a image with a matte channel, or if the image is
1627% enlarged. Otherwise the filter defaults to a Lanczos.
1628%
1629% ResizeImage() was inspired by Paul Heckbert's "zoom" program.
1630%
1631% The format of the ResizeImage method is:
1632%
1633% Image *ResizeImage(Image *image,const unsigned long columns,
1634% const unsigned long rows,const FilterTypes filter,const double blur,
1635% ExceptionInfo *exception)
1636%
1637% A description of each parameter follows:
1638%
1639% o image: the image.
1640%
1641% o columns: the number of columns in the scaled image.
1642%
1643% o rows: the number of rows in the scaled image.
1644%
1645% o filter: Image filter to use.
1646%
1647% o blur: the blur factor where > 1 is blurry, < 1 is sharp.
1648% Typically set this to 1.0.
1649%
1650% o exception: return any errors or warnings in this structure.
1651%
1652*/
1653
1654typedef struct _ContributionInfo
1655{
1656 MagickRealType
1657 weight;
1658
1659 long
1660 pixel;
1661} ContributionInfo;
1662
1663static ContributionInfo **DestroyContributionThreadSet(
1664 ContributionInfo **contribution)
1665{
1666 register long
1667 i;
1668
1669 assert(contribution != (ContributionInfo **) NULL);
1670 for (i=0; i < (long) GetOpenMPMaximumThreads(); i++)
1671 if (contribution[i] != (ContributionInfo *) NULL)
1672 contribution[i]=(ContributionInfo *) RelinquishMagickMemory(
1673 contribution[i]);
1674 contribution=(ContributionInfo **) RelinquishAlignedMemory(contribution);
1675 return(contribution);
1676}
1677
1678static ContributionInfo **AcquireContributionThreadSet(const size_t count)
1679{
1680 register long
1681 i;
1682
1683 ContributionInfo
1684 **contribution;
1685
1686 unsigned long
1687 number_threads;
1688
1689 number_threads=GetOpenMPMaximumThreads();
1690 contribution=(ContributionInfo **) AcquireAlignedMemory(number_threads,
1691 sizeof(*contribution));
1692 if (contribution == (ContributionInfo **) NULL)
1693 return((ContributionInfo **) NULL);
1694 (void) ResetMagickMemory(contribution,0,number_threads*sizeof(*contribution));
1695 for (i=0; i < (long) number_threads; i++)
1696 {
1697 contribution[i]=(ContributionInfo *) AcquireQuantumMemory(count,
1698 sizeof(**contribution));
1699 if (contribution[i] == (ContributionInfo *) NULL)
1700 return(DestroyContributionThreadSet(contribution));
1701 }
1702 return(contribution);
1703}
1704
1705static inline double MagickMax(const double x,const double y)
1706{
1707 if (x > y)
1708 return(x);
1709 return(y);
1710}
1711
1712static inline double MagickMin(const double x,const double y)
1713{
1714 if (x < y)
1715 return(x);
1716 return(y);
1717}
1718
1719static MagickBooleanType HorizontalFilter(const ResizeFilter *resize_filter,
1720 const Image *image,Image *resize_image,const MagickRealType x_factor,
1721 const MagickSizeType span,MagickOffsetType *quantum,ExceptionInfo *exception)
1722{
1723#define ResizeImageTag "Resize/Image"
1724
cristyfa112112010-01-04 17:48:07 +00001725 CacheView
1726 *image_view,
1727 *resize_view;
1728
cristy3ed852e2009-09-05 21:47:34 +00001729 ClassType
1730 storage_class;
1731
1732 ContributionInfo
cristyfa112112010-01-04 17:48:07 +00001733 **restrict contributions;
cristy3ed852e2009-09-05 21:47:34 +00001734
1735 long
1736 x;
1737
1738 MagickBooleanType
1739 status;
1740
1741 MagickPixelPacket
1742 zero;
1743
1744 MagickRealType
1745 scale,
1746 support;
1747
cristy3ed852e2009-09-05 21:47:34 +00001748 /*
1749 Apply filter to resize horizontally from image to resize image.
1750 */
1751 scale=MagickMax(1.0/x_factor,1.0);
1752 support=scale*GetResizeFilterSupport(resize_filter);
1753 storage_class=support > 0.5 ? DirectClass : image->storage_class;
1754 if (SetImageStorageClass(resize_image,storage_class) == MagickFalse)
1755 {
1756 InheritException(exception,&resize_image->exception);
1757 return(MagickFalse);
1758 }
1759 if (support < 0.5)
1760 {
1761 /*
1762 Support too small even for nearest neighbour: reduce to point sampling.
1763 */
1764 support=(MagickRealType) 0.5;
1765 scale=1.0;
1766 }
1767 contributions=AcquireContributionThreadSet((size_t) (2.0*support+3.0));
1768 if (contributions == (ContributionInfo **) NULL)
1769 {
1770 (void) ThrowMagickException(exception,GetMagickModule(),
1771 ResourceLimitError,"MemoryAllocationFailed","`%s'",image->filename);
1772 return(MagickFalse);
1773 }
1774 status=MagickTrue;
1775 scale=1.0/scale;
1776 (void) ResetMagickMemory(&zero,0,sizeof(zero));
1777 image_view=AcquireCacheView(image);
1778 resize_view=AcquireCacheView(resize_image);
cristyb5d5f722009-11-04 03:03:49 +00001779#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristy3ed852e2009-09-05 21:47:34 +00001780 #pragma omp parallel for shared(status)
1781#endif
1782 for (x=0; x < (long) resize_image->columns; x++)
1783 {
1784 long
1785 n,
1786 start,
1787 stop;
1788
1789 MagickRealType
1790 center,
1791 density;
1792
cristyf59a8922010-02-28 19:51:23 +00001793 register ContributionInfo
1794 *restrict contribution;
1795
cristy3ed852e2009-09-05 21:47:34 +00001796 register const IndexPacket
cristyc47d1f82009-11-26 01:44:43 +00001797 *restrict indexes;
cristy3ed852e2009-09-05 21:47:34 +00001798
1799 register const PixelPacket
cristyc47d1f82009-11-26 01:44:43 +00001800 *restrict p;
cristy3ed852e2009-09-05 21:47:34 +00001801
cristy3ed852e2009-09-05 21:47:34 +00001802 register IndexPacket
cristyc47d1f82009-11-26 01:44:43 +00001803 *restrict resize_indexes;
cristy3ed852e2009-09-05 21:47:34 +00001804
1805 register long
1806 y;
1807
1808 register PixelPacket
cristyc47d1f82009-11-26 01:44:43 +00001809 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +00001810
1811 if (status == MagickFalse)
1812 continue;
1813 center=(MagickRealType) (x+0.5)/x_factor;
cristy679e6962010-03-18 00:42:45 +00001814 start=(long) MagickMax(center-support+0.5,0.0);
1815 stop=(long) MagickMin(center+support+0.5,(double) image->columns);
cristy3ed852e2009-09-05 21:47:34 +00001816 density=0.0;
1817 contribution=contributions[GetOpenMPThreadId()];
1818 for (n=0; n < (stop-start); n++)
1819 {
1820 contribution[n].pixel=start+n;
1821 contribution[n].weight=GetResizeFilterWeight(resize_filter,scale*
1822 ((MagickRealType) (start+n)-center+0.5));
1823 density+=contribution[n].weight;
1824 }
1825 if ((density != 0.0) && (density != 1.0))
1826 {
1827 register long
1828 i;
1829
1830 /*
1831 Normalize.
1832 */
1833 density=1.0/density;
1834 for (i=0; i < n; i++)
1835 contribution[i].weight*=density;
1836 }
1837 p=GetCacheViewVirtualPixels(image_view,contribution[0].pixel,0,
1838 (unsigned long) (contribution[n-1].pixel-contribution[0].pixel+1),
1839 image->rows,exception);
1840 q=QueueCacheViewAuthenticPixels(resize_view,x,0,1,resize_image->rows,
1841 exception);
1842 if ((p == (const PixelPacket *) NULL) || (q == (PixelPacket *) NULL))
1843 {
1844 status=MagickFalse;
1845 continue;
1846 }
1847 indexes=GetCacheViewVirtualIndexQueue(image_view);
1848 resize_indexes=GetCacheViewAuthenticIndexQueue(resize_view);
1849 for (y=0; y < (long) resize_image->rows; y++)
1850 {
1851 long
1852 j;
1853
1854 MagickPixelPacket
1855 pixel;
1856
1857 MagickRealType
1858 alpha;
1859
1860 register long
1861 i;
1862
1863 pixel=zero;
1864 if (image->matte == MagickFalse)
1865 {
1866 for (i=0; i < n; i++)
1867 {
1868 j=y*(contribution[n-1].pixel-contribution[0].pixel+1)+
1869 (contribution[i].pixel-contribution[0].pixel);
1870 alpha=contribution[i].weight;
1871 pixel.red+=alpha*(p+j)->red;
1872 pixel.green+=alpha*(p+j)->green;
1873 pixel.blue+=alpha*(p+j)->blue;
1874 pixel.opacity+=alpha*(p+j)->opacity;
1875 }
cristyce70c172010-01-07 17:15:30 +00001876 SetRedPixelComponent(q,ClampRedPixelComponent(&pixel));
1877 SetGreenPixelComponent(q,ClampGreenPixelComponent(&pixel));
1878 SetBluePixelComponent(q,ClampBluePixelComponent(&pixel));
1879 SetOpacityPixelComponent(q,ClampOpacityPixelComponent(&pixel));
cristy3ed852e2009-09-05 21:47:34 +00001880 if ((image->colorspace == CMYKColorspace) &&
1881 (resize_image->colorspace == CMYKColorspace))
1882 {
1883 for (i=0; i < n; i++)
1884 {
1885 j=y*(contribution[n-1].pixel-contribution[0].pixel+1)+
1886 (contribution[i].pixel-contribution[0].pixel);
1887 alpha=contribution[i].weight;
1888 pixel.index+=alpha*indexes[j];
1889 }
cristyce70c172010-01-07 17:15:30 +00001890 resize_indexes[y]=(IndexPacket) ClampToQuantum(pixel.index);
cristy3ed852e2009-09-05 21:47:34 +00001891 }
1892 }
1893 else
1894 {
1895 MagickRealType
1896 gamma;
1897
1898 gamma=0.0;
1899 for (i=0; i < n; i++)
1900 {
1901 j=y*(contribution[n-1].pixel-contribution[0].pixel+1)+
1902 (contribution[i].pixel-contribution[0].pixel);
cristy46f08202010-01-10 04:04:21 +00001903 alpha=contribution[i].weight*QuantumScale*
1904 GetAlphaPixelComponent(p+j);
cristy3ed852e2009-09-05 21:47:34 +00001905 pixel.red+=alpha*(p+j)->red;
1906 pixel.green+=alpha*(p+j)->green;
1907 pixel.blue+=alpha*(p+j)->blue;
1908 pixel.opacity+=contribution[i].weight*(p+j)->opacity;
1909 gamma+=alpha;
1910 }
1911 gamma=1.0/(fabs((double) gamma) <= MagickEpsilon ? 1.0 : gamma);
cristyce70c172010-01-07 17:15:30 +00001912 q->red=ClampToQuantum(gamma*GetRedPixelComponent(&pixel));
1913 q->green=ClampToQuantum(gamma*GetGreenPixelComponent(&pixel));
1914 q->blue=ClampToQuantum(gamma*GetBluePixelComponent(&pixel));
1915 SetOpacityPixelComponent(q,ClampOpacityPixelComponent(&pixel));
cristy3ed852e2009-09-05 21:47:34 +00001916 if ((image->colorspace == CMYKColorspace) &&
1917 (resize_image->colorspace == CMYKColorspace))
1918 {
1919 for (i=0; i < n; i++)
1920 {
1921 j=y*(contribution[n-1].pixel-contribution[0].pixel+1)+
1922 (contribution[i].pixel-contribution[0].pixel);
cristy46f08202010-01-10 04:04:21 +00001923 alpha=contribution[i].weight*QuantumScale*
1924 GetAlphaPixelComponent(p+j);
cristyc197d1c2009-10-13 19:56:53 +00001925 pixel.index+=alpha*indexes[j];
cristy3ed852e2009-09-05 21:47:34 +00001926 }
cristyce70c172010-01-07 17:15:30 +00001927 resize_indexes[y]=(IndexPacket) ClampToQuantum(gamma*
1928 GetIndexPixelComponent(&pixel));
cristy3ed852e2009-09-05 21:47:34 +00001929 }
1930 }
1931 if ((resize_image->storage_class == PseudoClass) &&
1932 (image->storage_class == PseudoClass))
1933 {
1934 i=(long) (MagickMin(MagickMax(center,(double) start),(double) stop-
1935 1.0)+0.5);
1936 j=y*(contribution[n-1].pixel-contribution[0].pixel+1)+
1937 (contribution[i-start].pixel-contribution[0].pixel);
1938 resize_indexes[y]=indexes[j];
1939 }
1940 q++;
1941 }
1942 if (SyncCacheViewAuthenticPixels(resize_view,exception) == MagickFalse)
1943 status=MagickFalse;
1944 if (image->progress_monitor != (MagickProgressMonitor) NULL)
1945 {
1946 MagickBooleanType
1947 proceed;
1948
cristyb5d5f722009-11-04 03:03:49 +00001949#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristy3ed852e2009-09-05 21:47:34 +00001950 #pragma omp critical (MagickCore_HorizontalFilter)
1951#endif
1952 proceed=SetImageProgress(image,ResizeImageTag,(*quantum)++,span);
1953 if (proceed == MagickFalse)
1954 status=MagickFalse;
1955 }
1956 }
1957 resize_view=DestroyCacheView(resize_view);
1958 image_view=DestroyCacheView(image_view);
1959 contributions=DestroyContributionThreadSet(contributions);
1960 return(status);
1961}
1962
1963static MagickBooleanType VerticalFilter(const ResizeFilter *resize_filter,
1964 const Image *image,Image *resize_image,const MagickRealType y_factor,
1965 const MagickSizeType span,MagickOffsetType *quantum,ExceptionInfo *exception)
1966{
cristyfa112112010-01-04 17:48:07 +00001967 CacheView
1968 *image_view,
1969 *resize_view;
1970
cristy3ed852e2009-09-05 21:47:34 +00001971 ClassType
1972 storage_class;
1973
1974 ContributionInfo
cristyfa112112010-01-04 17:48:07 +00001975 **restrict contributions;
cristy3ed852e2009-09-05 21:47:34 +00001976
1977 long
1978 y;
1979
1980 MagickBooleanType
1981 status;
1982
1983 MagickPixelPacket
1984 zero;
1985
1986 MagickRealType
1987 scale,
1988 support;
1989
cristy3ed852e2009-09-05 21:47:34 +00001990 /*
1991 Apply filter to resize vertically from image to resize_image.
1992 */
1993 scale=MagickMax(1.0/y_factor,1.0);
1994 support=scale*GetResizeFilterSupport(resize_filter);
1995 storage_class=support > 0.5 ? DirectClass : image->storage_class;
1996 if (SetImageStorageClass(resize_image,storage_class) == MagickFalse)
1997 {
1998 InheritException(exception,&resize_image->exception);
1999 return(MagickFalse);
2000 }
2001 if (support < 0.5)
2002 {
2003 /*
2004 Support too small even for nearest neighbour: reduce to point sampling.
2005 */
2006 support=(MagickRealType) 0.5;
2007 scale=1.0;
2008 }
2009 contributions=AcquireContributionThreadSet((size_t) (2.0*support+3.0));
2010 if (contributions == (ContributionInfo **) NULL)
2011 {
2012 (void) ThrowMagickException(exception,GetMagickModule(),
2013 ResourceLimitError,"MemoryAllocationFailed","`%s'",image->filename);
2014 return(MagickFalse);
2015 }
2016 status=MagickTrue;
2017 scale=1.0/scale;
2018 (void) ResetMagickMemory(&zero,0,sizeof(zero));
2019 image_view=AcquireCacheView(image);
2020 resize_view=AcquireCacheView(resize_image);
cristyb5d5f722009-11-04 03:03:49 +00002021#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristy3ed852e2009-09-05 21:47:34 +00002022 #pragma omp parallel for shared(status)
2023#endif
2024 for (y=0; y < (long) resize_image->rows; y++)
2025 {
2026 long
2027 n,
2028 start,
2029 stop;
2030
2031 MagickRealType
2032 center,
2033 density;
2034
cristyf59a8922010-02-28 19:51:23 +00002035 register ContributionInfo
2036 *restrict contribution;
2037
cristy3ed852e2009-09-05 21:47:34 +00002038 register const IndexPacket
cristyc47d1f82009-11-26 01:44:43 +00002039 *restrict indexes;
cristy3ed852e2009-09-05 21:47:34 +00002040
2041 register const PixelPacket
cristyc47d1f82009-11-26 01:44:43 +00002042 *restrict p;
cristy3ed852e2009-09-05 21:47:34 +00002043
cristy3ed852e2009-09-05 21:47:34 +00002044 register IndexPacket
cristyc47d1f82009-11-26 01:44:43 +00002045 *restrict resize_indexes;
cristy3ed852e2009-09-05 21:47:34 +00002046
2047 register long
2048 x;
2049
2050 register PixelPacket
cristyc47d1f82009-11-26 01:44:43 +00002051 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +00002052
2053 if (status == MagickFalse)
2054 continue;
cristy679e6962010-03-18 00:42:45 +00002055 center=(MagickRealType) (y+0.5)/y_factor;
2056 start=(long) MagickMax(center-support+0.5,0.0);
2057 stop=(long) MagickMin(center+support+0.5,(double) image->rows);
cristy3ed852e2009-09-05 21:47:34 +00002058 density=0.0;
2059 contribution=contributions[GetOpenMPThreadId()];
2060 for (n=0; n < (stop-start); n++)
2061 {
2062 contribution[n].pixel=start+n;
2063 contribution[n].weight=GetResizeFilterWeight(resize_filter,scale*
2064 ((MagickRealType) (start+n)-center+0.5));
2065 density+=contribution[n].weight;
2066 }
2067 if ((density != 0.0) && (density != 1.0))
2068 {
2069 register long
2070 i;
2071
2072 /*
2073 Normalize.
2074 */
2075 density=1.0/density;
2076 for (i=0; i < n; i++)
2077 contribution[i].weight*=density;
2078 }
2079 p=GetCacheViewVirtualPixels(image_view,0,contribution[0].pixel,
2080 image->columns,(unsigned long) (contribution[n-1].pixel-
2081 contribution[0].pixel+1),exception);
2082 q=QueueCacheViewAuthenticPixels(resize_view,0,y,resize_image->columns,1,
2083 exception);
2084 if ((p == (const PixelPacket *) NULL) || (q == (PixelPacket *) NULL))
2085 {
2086 status=MagickFalse;
2087 continue;
2088 }
2089 indexes=GetCacheViewVirtualIndexQueue(image_view);
2090 resize_indexes=GetCacheViewAuthenticIndexQueue(resize_view);
2091 for (x=0; x < (long) resize_image->columns; x++)
2092 {
2093 long
2094 j;
2095
2096 MagickPixelPacket
2097 pixel;
2098
2099 MagickRealType
2100 alpha;
2101
2102 register long
2103 i;
2104
2105 pixel=zero;
2106 if (image->matte == MagickFalse)
2107 {
2108 for (i=0; i < n; i++)
2109 {
2110 j=(long) ((contribution[i].pixel-contribution[0].pixel)*
2111 image->columns+x);
2112 alpha=contribution[i].weight;
2113 pixel.red+=alpha*(p+j)->red;
2114 pixel.green+=alpha*(p+j)->green;
2115 pixel.blue+=alpha*(p+j)->blue;
2116 pixel.opacity+=alpha*(p+j)->opacity;
2117 }
cristyce70c172010-01-07 17:15:30 +00002118 SetRedPixelComponent(q,ClampRedPixelComponent(&pixel));
2119 SetGreenPixelComponent(q,ClampGreenPixelComponent(&pixel));
2120 SetBluePixelComponent(q,ClampBluePixelComponent(&pixel));
2121 SetOpacityPixelComponent(q,ClampOpacityPixelComponent(&pixel));
cristy3ed852e2009-09-05 21:47:34 +00002122 if ((image->colorspace == CMYKColorspace) &&
2123 (resize_image->colorspace == CMYKColorspace))
2124 {
2125 for (i=0; i < n; i++)
2126 {
2127 j=(long) ((contribution[i].pixel-contribution[0].pixel)*
2128 image->columns+x);
2129 alpha=contribution[i].weight;
2130 pixel.index+=alpha*indexes[j];
2131 }
cristyce70c172010-01-07 17:15:30 +00002132 resize_indexes[x]=(IndexPacket) ClampToQuantum(pixel.index);
cristy3ed852e2009-09-05 21:47:34 +00002133 }
2134 }
2135 else
2136 {
2137 MagickRealType
2138 gamma;
2139
2140 gamma=0.0;
2141 for (i=0; i < n; i++)
2142 {
2143 j=(long) ((contribution[i].pixel-contribution[0].pixel)*
2144 image->columns+x);
cristy46f08202010-01-10 04:04:21 +00002145 alpha=contribution[i].weight*QuantumScale*
2146 GetAlphaPixelComponent(p+j);
cristy3ed852e2009-09-05 21:47:34 +00002147 pixel.red+=alpha*(p+j)->red;
2148 pixel.green+=alpha*(p+j)->green;
2149 pixel.blue+=alpha*(p+j)->blue;
2150 pixel.opacity+=contribution[i].weight*(p+j)->opacity;
2151 gamma+=alpha;
2152 }
2153 gamma=1.0/(fabs((double) gamma) <= MagickEpsilon ? 1.0 : gamma);
cristyce70c172010-01-07 17:15:30 +00002154 q->red=ClampToQuantum(gamma*GetRedPixelComponent(&pixel));
2155 q->green=ClampToQuantum(gamma*GetGreenPixelComponent(&pixel));
2156 q->blue=ClampToQuantum(gamma*GetBluePixelComponent(&pixel));
2157 SetOpacityPixelComponent(q,ClampOpacityPixelComponent(&pixel));
cristy3ed852e2009-09-05 21:47:34 +00002158 if ((image->colorspace == CMYKColorspace) &&
2159 (resize_image->colorspace == CMYKColorspace))
2160 {
2161 for (i=0; i < n; i++)
2162 {
2163 j=(long) ((contribution[i].pixel-contribution[0].pixel)*
2164 image->columns+x);
cristy46f08202010-01-10 04:04:21 +00002165 alpha=contribution[i].weight*QuantumScale*
2166 GetAlphaPixelComponent(p+j);
cristy3ed852e2009-09-05 21:47:34 +00002167 pixel.index+=alpha*indexes[j];
2168 }
cristyce70c172010-01-07 17:15:30 +00002169 resize_indexes[x]=(IndexPacket) ClampToQuantum(gamma*
2170 GetIndexPixelComponent(&pixel));
cristy3ed852e2009-09-05 21:47:34 +00002171 }
2172 }
2173 if ((resize_image->storage_class == PseudoClass) &&
2174 (image->storage_class == PseudoClass))
2175 {
2176 i=(long) (MagickMin(MagickMax(center,(double) start),(double) stop-
2177 1.0)+0.5);
2178 j=(long) ((contribution[i-start].pixel-contribution[0].pixel)*
2179 image->columns+x);
2180 resize_indexes[x]=indexes[j];
2181 }
2182 q++;
2183 }
2184 if (SyncCacheViewAuthenticPixels(resize_view,exception) == MagickFalse)
2185 status=MagickFalse;
2186 if (image->progress_monitor != (MagickProgressMonitor) NULL)
2187 {
2188 MagickBooleanType
2189 proceed;
2190
cristyb5d5f722009-11-04 03:03:49 +00002191#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristy3ed852e2009-09-05 21:47:34 +00002192 #pragma omp critical (MagickCore_VerticalFilter)
2193#endif
2194 proceed=SetImageProgress(image,ResizeImageTag,(*quantum)++,span);
2195 if (proceed == MagickFalse)
2196 status=MagickFalse;
2197 }
2198 }
2199 resize_view=DestroyCacheView(resize_view);
2200 image_view=DestroyCacheView(image_view);
2201 contributions=DestroyContributionThreadSet(contributions);
2202 return(status);
2203}
2204
2205MagickExport Image *ResizeImage(const Image *image,const unsigned long columns,
2206 const unsigned long rows,const FilterTypes filter,const double blur,
2207 ExceptionInfo *exception)
2208{
2209#define WorkLoadFactor 0.265
2210
2211 FilterTypes
2212 filter_type;
2213
2214 Image
2215 *filter_image,
2216 *resize_image;
2217
2218 MagickRealType
2219 x_factor,
2220 y_factor;
2221
2222 MagickSizeType
2223 span;
2224
2225 MagickStatusType
2226 status;
2227
2228 ResizeFilter
2229 *resize_filter;
2230
2231 MagickOffsetType
2232 quantum;
2233
2234 /*
2235 Acquire resize image.
2236 */
2237 assert(image != (Image *) NULL);
2238 assert(image->signature == MagickSignature);
2239 if (image->debug != MagickFalse)
2240 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
2241 assert(exception != (ExceptionInfo *) NULL);
2242 assert(exception->signature == MagickSignature);
2243 if ((columns == 0) || (rows == 0))
2244 ThrowImageException(ImageError,"NegativeOrZeroImageSize");
2245 if ((columns == image->columns) && (rows == image->rows) &&
2246 (filter == UndefinedFilter) && (blur == 1.0))
2247 return(CloneImage(image,0,0,MagickTrue,exception));
2248 resize_image=CloneImage(image,columns,rows,MagickTrue,exception);
2249 if (resize_image == (Image *) NULL)
2250 return(resize_image);
2251 /*
2252 Acquire resize filter.
2253 */
2254 x_factor=(MagickRealType) columns/(MagickRealType) image->columns;
2255 y_factor=(MagickRealType) rows/(MagickRealType) image->rows;
2256 if ((x_factor*y_factor) > WorkLoadFactor)
2257 filter_image=CloneImage(image,columns,image->rows,MagickTrue,exception);
2258 else
2259 filter_image=CloneImage(image,image->columns,rows,MagickTrue,exception);
2260 if (filter_image == (Image *) NULL)
2261 return(DestroyImage(resize_image));
2262 filter_type=LanczosFilter;
2263 if (filter != UndefinedFilter)
2264 filter_type=filter;
2265 else
2266 if ((x_factor == 1.0) && (y_factor == 1.0))
2267 filter_type=PointFilter;
2268 else
2269 if ((image->storage_class == PseudoClass) ||
2270 (image->matte != MagickFalse) || ((x_factor*y_factor) > 1.0))
2271 filter_type=MitchellFilter;
2272 resize_filter=AcquireResizeFilter(image,filter_type,blur,MagickFalse,
2273 exception);
2274 /*
2275 Resize image.
2276 */
2277 quantum=0;
2278 if ((x_factor*y_factor) > WorkLoadFactor)
2279 {
2280 span=(MagickSizeType) (filter_image->columns+rows);
2281 status=HorizontalFilter(resize_filter,image,filter_image,x_factor,span,
2282 &quantum,exception);
2283 status&=VerticalFilter(resize_filter,filter_image,resize_image,y_factor,
2284 span,&quantum,exception);
2285 }
2286 else
2287 {
2288 span=(MagickSizeType) (filter_image->rows+columns);
2289 status=VerticalFilter(resize_filter,image,filter_image,y_factor,span,
2290 &quantum,exception);
2291 status&=HorizontalFilter(resize_filter,filter_image,resize_image,x_factor,
2292 span,&quantum,exception);
2293 }
2294 /*
2295 Free resources.
2296 */
2297 filter_image=DestroyImage(filter_image);
2298 resize_filter=DestroyResizeFilter(resize_filter);
2299 if ((status == MagickFalse) || (resize_image == (Image *) NULL))
2300 return((Image *) NULL);
2301 resize_image->type=image->type;
2302 return(resize_image);
2303}
2304
2305/*
2306%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2307% %
2308% %
2309% %
2310% S a m p l e I m a g e %
2311% %
2312% %
2313% %
2314%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2315%
2316% SampleImage() scales an image to the desired dimensions with pixel
2317% sampling. Unlike other scaling methods, this method does not introduce
2318% any additional color into the scaled image.
2319%
2320% The format of the SampleImage method is:
2321%
2322% Image *SampleImage(const Image *image,const unsigned long columns,
2323% const unsigned long rows,ExceptionInfo *exception)
2324%
2325% A description of each parameter follows:
2326%
2327% o image: the image.
2328%
2329% o columns: the number of columns in the sampled image.
2330%
2331% o rows: the number of rows in the sampled image.
2332%
2333% o exception: return any errors or warnings in this structure.
2334%
2335*/
2336MagickExport Image *SampleImage(const Image *image,const unsigned long columns,
2337 const unsigned long rows,ExceptionInfo *exception)
2338{
2339#define SampleImageTag "Sample/Image"
2340
cristyc4c8d132010-01-07 01:58:38 +00002341 CacheView
2342 *image_view,
2343 *sample_view;
2344
cristy3ed852e2009-09-05 21:47:34 +00002345 Image
2346 *sample_image;
2347
2348 long
2349 progress,
2350 *x_offset,
2351 y;
2352
2353 MagickBooleanType
2354 status;
2355
2356 register long
2357 x;
2358
cristy3ed852e2009-09-05 21:47:34 +00002359 /*
2360 Initialize sampled image attributes.
2361 */
2362 assert(image != (const Image *) NULL);
2363 assert(image->signature == MagickSignature);
2364 if (image->debug != MagickFalse)
2365 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
2366 assert(exception != (ExceptionInfo *) NULL);
2367 assert(exception->signature == MagickSignature);
2368 if ((columns == 0) || (rows == 0))
2369 ThrowImageException(ImageError,"NegativeOrZeroImageSize");
2370 if ((columns == image->columns) && (rows == image->rows))
2371 return(CloneImage(image,0,0,MagickTrue,exception));
2372 sample_image=CloneImage(image,columns,rows,MagickTrue,exception);
2373 if (sample_image == (Image *) NULL)
2374 return((Image *) NULL);
2375 /*
2376 Allocate scan line buffer and column offset buffers.
2377 */
2378 x_offset=(long *) AcquireQuantumMemory((size_t) sample_image->columns,
2379 sizeof(*x_offset));
2380 if (x_offset == (long *) NULL)
2381 {
2382 sample_image=DestroyImage(sample_image);
2383 ThrowImageException(ResourceLimitError,"MemoryAllocationFailed");
2384 }
2385 for (x=0; x < (long) sample_image->columns; x++)
2386 x_offset[x]=(long) (((MagickRealType) x+0.5)*image->columns/
2387 sample_image->columns);
2388 /*
2389 Sample each row.
2390 */
2391 status=MagickTrue;
2392 progress=0;
2393 image_view=AcquireCacheView(image);
2394 sample_view=AcquireCacheView(sample_image);
cristyb5d5f722009-11-04 03:03:49 +00002395#if defined(MAGICKCORE_OPENMP_SUPPORT)
2396 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +00002397#endif
2398 for (y=0; y < (long) sample_image->rows; y++)
2399 {
2400 long
2401 y_offset;
2402
2403 register const IndexPacket
cristyc47d1f82009-11-26 01:44:43 +00002404 *restrict indexes;
cristy3ed852e2009-09-05 21:47:34 +00002405
2406 register const PixelPacket
cristyc47d1f82009-11-26 01:44:43 +00002407 *restrict p;
cristy3ed852e2009-09-05 21:47:34 +00002408
2409 register IndexPacket
cristyc47d1f82009-11-26 01:44:43 +00002410 *restrict sample_indexes;
cristy3ed852e2009-09-05 21:47:34 +00002411
2412 register long
2413 x;
2414
2415 register PixelPacket
cristyc47d1f82009-11-26 01:44:43 +00002416 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +00002417
2418 if (status == MagickFalse)
2419 continue;
cristy679e6962010-03-18 00:42:45 +00002420 y_offset=(long) (((MagickRealType) y+0.5)*image->rows/sample_image->rows);
cristy3ed852e2009-09-05 21:47:34 +00002421 p=GetCacheViewVirtualPixels(image_view,0,y_offset,image->columns,1,
2422 exception);
2423 q=QueueCacheViewAuthenticPixels(sample_view,0,y,sample_image->columns,1,
2424 exception);
2425 if ((p == (const PixelPacket *) NULL) || (q == (PixelPacket *) NULL))
2426 {
2427 status=MagickFalse;
2428 continue;
2429 }
2430 indexes=GetCacheViewAuthenticIndexQueue(image_view);
2431 sample_indexes=GetCacheViewAuthenticIndexQueue(sample_view);
2432 /*
2433 Sample each column.
2434 */
2435 for (x=0; x < (long) sample_image->columns; x++)
2436 *q++=p[x_offset[x]];
2437 if ((image->storage_class == PseudoClass) ||
2438 (image->colorspace == CMYKColorspace))
2439 for (x=0; x < (long) sample_image->columns; x++)
2440 sample_indexes[x]=indexes[x_offset[x]];
2441 if (SyncCacheViewAuthenticPixels(sample_view,exception) == MagickFalse)
2442 status=MagickFalse;
2443 if (image->progress_monitor != (MagickProgressMonitor) NULL)
2444 {
2445 MagickBooleanType
2446 proceed;
2447
cristyb5d5f722009-11-04 03:03:49 +00002448#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristy3ed852e2009-09-05 21:47:34 +00002449 #pragma omp critical (MagickCore_SampleImage)
2450#endif
2451 proceed=SetImageProgress(image,SampleImageTag,progress++,image->rows);
2452 if (proceed == MagickFalse)
2453 status=MagickFalse;
2454 }
2455 }
2456 image_view=DestroyCacheView(image_view);
2457 sample_view=DestroyCacheView(sample_view);
2458 x_offset=(long *) RelinquishMagickMemory(x_offset);
2459 sample_image->type=image->type;
2460 return(sample_image);
2461}
2462
2463/*
2464%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2465% %
2466% %
2467% %
2468% S c a l e I m a g e %
2469% %
2470% %
2471% %
2472%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2473%
2474% ScaleImage() changes the size of an image to the given dimensions.
2475%
2476% The format of the ScaleImage method is:
2477%
2478% Image *ScaleImage(const Image *image,const unsigned long columns,
2479% const unsigned long rows,ExceptionInfo *exception)
2480%
2481% A description of each parameter follows:
2482%
2483% o image: the image.
2484%
2485% o columns: the number of columns in the scaled image.
2486%
2487% o rows: the number of rows in the scaled image.
2488%
2489% o exception: return any errors or warnings in this structure.
2490%
2491*/
2492MagickExport Image *ScaleImage(const Image *image,const unsigned long columns,
2493 const unsigned long rows,ExceptionInfo *exception)
2494{
2495#define ScaleImageTag "Scale/Image"
2496
cristyed6cb232010-01-20 03:07:53 +00002497 CacheView
2498 *image_view,
2499 *scale_view;
2500
cristy3ed852e2009-09-05 21:47:34 +00002501 Image
2502 *scale_image;
2503
2504 long
2505 number_rows,
2506 y;
2507
2508 MagickBooleanType
2509 next_column,
2510 next_row,
2511 proceed;
2512
2513 MagickPixelPacket
2514 pixel,
2515 *scale_scanline,
2516 *scanline,
2517 *x_vector,
2518 *y_vector,
2519 zero;
2520
cristy3ed852e2009-09-05 21:47:34 +00002521 PointInfo
2522 scale,
2523 span;
2524
2525 register long
2526 i;
2527
2528 /*
2529 Initialize scaled image attributes.
2530 */
2531 assert(image != (const Image *) NULL);
2532 assert(image->signature == MagickSignature);
2533 if (image->debug != MagickFalse)
2534 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
2535 assert(exception != (ExceptionInfo *) NULL);
2536 assert(exception->signature == MagickSignature);
2537 if ((columns == 0) || (rows == 0))
2538 return((Image *) NULL);
2539 if ((columns == image->columns) && (rows == image->rows))
2540 return(CloneImage(image,0,0,MagickTrue,exception));
2541 scale_image=CloneImage(image,columns,rows,MagickTrue,exception);
2542 if (scale_image == (Image *) NULL)
2543 return((Image *) NULL);
2544 if (SetImageStorageClass(scale_image,DirectClass) == MagickFalse)
2545 {
2546 InheritException(exception,&scale_image->exception);
2547 scale_image=DestroyImage(scale_image);
2548 return((Image *) NULL);
2549 }
2550 /*
2551 Allocate memory.
2552 */
2553 x_vector=(MagickPixelPacket *) AcquireQuantumMemory((size_t) image->columns,
2554 sizeof(*x_vector));
2555 scanline=x_vector;
2556 if (image->rows != scale_image->rows)
2557 scanline=(MagickPixelPacket *) AcquireQuantumMemory((size_t) image->columns,
2558 sizeof(*scanline));
2559 scale_scanline=(MagickPixelPacket *) AcquireQuantumMemory((size_t)
2560 scale_image->columns,sizeof(*scale_scanline));
2561 y_vector=(MagickPixelPacket *) AcquireQuantumMemory((size_t) image->columns,
2562 sizeof(*y_vector));
2563 if ((scanline == (MagickPixelPacket *) NULL) ||
2564 (scale_scanline == (MagickPixelPacket *) NULL) ||
2565 (x_vector == (MagickPixelPacket *) NULL) ||
2566 (y_vector == (MagickPixelPacket *) NULL))
2567 {
2568 scale_image=DestroyImage(scale_image);
2569 ThrowImageException(ResourceLimitError,"MemoryAllocationFailed");
2570 }
2571 /*
2572 Scale image.
2573 */
2574 number_rows=0;
2575 next_row=MagickTrue;
2576 span.y=1.0;
2577 scale.y=(double) scale_image->rows/(double) image->rows;
2578 (void) ResetMagickMemory(y_vector,0,(size_t) image->columns*
2579 sizeof(*y_vector));
2580 GetMagickPixelPacket(image,&pixel);
2581 (void) ResetMagickMemory(&zero,0,sizeof(zero));
2582 i=0;
cristyed6cb232010-01-20 03:07:53 +00002583 image_view=AcquireCacheView(image);
2584 scale_view=AcquireCacheView(scale_image);
cristy3ed852e2009-09-05 21:47:34 +00002585 for (y=0; y < (long) scale_image->rows; y++)
2586 {
2587 register const IndexPacket
cristyc47d1f82009-11-26 01:44:43 +00002588 *restrict indexes;
cristy3ed852e2009-09-05 21:47:34 +00002589
2590 register const PixelPacket
cristyc47d1f82009-11-26 01:44:43 +00002591 *restrict p;
cristy3ed852e2009-09-05 21:47:34 +00002592
2593 register IndexPacket
cristyc47d1f82009-11-26 01:44:43 +00002594 *restrict scale_indexes;
cristy3ed852e2009-09-05 21:47:34 +00002595
2596 register long
2597 x;
2598
2599 register MagickPixelPacket
cristyc47d1f82009-11-26 01:44:43 +00002600 *restrict s,
2601 *restrict t;
cristy3ed852e2009-09-05 21:47:34 +00002602
2603 register PixelPacket
cristyc47d1f82009-11-26 01:44:43 +00002604 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +00002605
cristyed6cb232010-01-20 03:07:53 +00002606 q=QueueCacheViewAuthenticPixels(scale_view,0,y,scale_image->columns,1,
2607 exception);
cristy3ed852e2009-09-05 21:47:34 +00002608 if (q == (PixelPacket *) NULL)
2609 break;
2610 scale_indexes=GetAuthenticIndexQueue(scale_image);
2611 if (scale_image->rows == image->rows)
2612 {
2613 /*
2614 Read a new scanline.
2615 */
cristyed6cb232010-01-20 03:07:53 +00002616 p=GetCacheViewVirtualPixels(image_view,0,i++,image->columns,1,
2617 exception);
cristy3ed852e2009-09-05 21:47:34 +00002618 if (p == (const PixelPacket *) NULL)
2619 break;
cristyed6cb232010-01-20 03:07:53 +00002620 indexes=GetCacheViewVirtualIndexQueue(image_view);
cristy3ed852e2009-09-05 21:47:34 +00002621 for (x=0; x < (long) image->columns; x++)
2622 {
cristyce70c172010-01-07 17:15:30 +00002623 x_vector[x].red=(MagickRealType) GetRedPixelComponent(p);
2624 x_vector[x].green=(MagickRealType) GetGreenPixelComponent(p);
2625 x_vector[x].blue=(MagickRealType) GetBluePixelComponent(p);
cristy3ed852e2009-09-05 21:47:34 +00002626 if (image->matte != MagickFalse)
cristyce70c172010-01-07 17:15:30 +00002627 x_vector[x].opacity=(MagickRealType) GetOpacityPixelComponent(p);
cristy3ed852e2009-09-05 21:47:34 +00002628 if (indexes != (IndexPacket *) NULL)
2629 x_vector[x].index=(MagickRealType) indexes[x];
2630 p++;
2631 }
2632 }
2633 else
2634 {
2635 /*
2636 Scale Y direction.
2637 */
2638 while (scale.y < span.y)
2639 {
2640 if ((next_row != MagickFalse) && (number_rows < (long) image->rows))
2641 {
2642 /*
2643 Read a new scanline.
2644 */
cristyed6cb232010-01-20 03:07:53 +00002645 p=GetCacheViewVirtualPixels(image_view,0,i++,image->columns,1,
2646 exception);
cristy3ed852e2009-09-05 21:47:34 +00002647 if (p == (const PixelPacket *) NULL)
2648 break;
cristyed6cb232010-01-20 03:07:53 +00002649 indexes=GetCacheViewVirtualIndexQueue(image_view);
cristy3ed852e2009-09-05 21:47:34 +00002650 for (x=0; x < (long) image->columns; x++)
2651 {
cristyce70c172010-01-07 17:15:30 +00002652 x_vector[x].red=(MagickRealType) GetRedPixelComponent(p);
2653 x_vector[x].green=(MagickRealType) GetGreenPixelComponent(p);
2654 x_vector[x].blue=(MagickRealType) GetBluePixelComponent(p);
cristy3ed852e2009-09-05 21:47:34 +00002655 if (image->matte != MagickFalse)
cristyed6cb232010-01-20 03:07:53 +00002656 x_vector[x].opacity=(MagickRealType)
2657 GetOpacityPixelComponent(p);
cristy3ed852e2009-09-05 21:47:34 +00002658 if (indexes != (IndexPacket *) NULL)
2659 x_vector[x].index=(MagickRealType) indexes[x];
2660 p++;
2661 }
2662 number_rows++;
2663 }
2664 for (x=0; x < (long) image->columns; x++)
2665 {
2666 y_vector[x].red+=scale.y*x_vector[x].red;
2667 y_vector[x].green+=scale.y*x_vector[x].green;
2668 y_vector[x].blue+=scale.y*x_vector[x].blue;
2669 if (scale_image->matte != MagickFalse)
2670 y_vector[x].opacity+=scale.y*x_vector[x].opacity;
2671 if (scale_indexes != (IndexPacket *) NULL)
2672 y_vector[x].index+=scale.y*x_vector[x].index;
2673 }
2674 span.y-=scale.y;
2675 scale.y=(double) scale_image->rows/(double) image->rows;
2676 next_row=MagickTrue;
2677 }
2678 if ((next_row != MagickFalse) && (number_rows < (long) image->rows))
2679 {
2680 /*
2681 Read a new scanline.
2682 */
cristyed6cb232010-01-20 03:07:53 +00002683 p=GetCacheViewVirtualPixels(image_view,0,i++,image->columns,1,
2684 exception);
cristy3ed852e2009-09-05 21:47:34 +00002685 if (p == (const PixelPacket *) NULL)
2686 break;
cristyed6cb232010-01-20 03:07:53 +00002687 indexes=GetCacheViewVirtualIndexQueue(image_view);
cristy3ed852e2009-09-05 21:47:34 +00002688 for (x=0; x < (long) image->columns; x++)
2689 {
cristyce70c172010-01-07 17:15:30 +00002690 x_vector[x].red=(MagickRealType) GetRedPixelComponent(p);
2691 x_vector[x].green=(MagickRealType) GetGreenPixelComponent(p);
2692 x_vector[x].blue=(MagickRealType) GetBluePixelComponent(p);
cristy3ed852e2009-09-05 21:47:34 +00002693 if (image->matte != MagickFalse)
cristy2b726bd2010-01-11 01:05:39 +00002694 x_vector[x].opacity=(MagickRealType)
2695 GetOpacityPixelComponent(p);
cristy3ed852e2009-09-05 21:47:34 +00002696 if (indexes != (IndexPacket *) NULL)
2697 x_vector[x].index=(MagickRealType) indexes[x];
2698 p++;
2699 }
2700 number_rows++;
2701 next_row=MagickFalse;
2702 }
2703 s=scanline;
2704 for (x=0; x < (long) image->columns; x++)
2705 {
2706 pixel.red=y_vector[x].red+span.y*x_vector[x].red;
2707 pixel.green=y_vector[x].green+span.y*x_vector[x].green;
2708 pixel.blue=y_vector[x].blue+span.y*x_vector[x].blue;
2709 if (image->matte != MagickFalse)
2710 pixel.opacity=y_vector[x].opacity+span.y*x_vector[x].opacity;
2711 if (scale_indexes != (IndexPacket *) NULL)
2712 pixel.index=y_vector[x].index+span.y*x_vector[x].index;
2713 s->red=pixel.red;
2714 s->green=pixel.green;
2715 s->blue=pixel.blue;
2716 if (scale_image->matte != MagickFalse)
2717 s->opacity=pixel.opacity;
2718 if (scale_indexes != (IndexPacket *) NULL)
2719 s->index=pixel.index;
2720 s++;
2721 y_vector[x]=zero;
2722 }
2723 scale.y-=span.y;
2724 if (scale.y <= 0)
2725 {
2726 scale.y=(double) scale_image->rows/(double) image->rows;
2727 next_row=MagickTrue;
2728 }
2729 span.y=1.0;
2730 }
2731 if (scale_image->columns == image->columns)
2732 {
2733 /*
2734 Transfer scanline to scaled image.
2735 */
2736 s=scanline;
2737 for (x=0; x < (long) scale_image->columns; x++)
2738 {
cristyce70c172010-01-07 17:15:30 +00002739 q->red=ClampToQuantum(s->red);
2740 q->green=ClampToQuantum(s->green);
2741 q->blue=ClampToQuantum(s->blue);
cristy3ed852e2009-09-05 21:47:34 +00002742 if (scale_image->matte != MagickFalse)
cristyce70c172010-01-07 17:15:30 +00002743 q->opacity=ClampToQuantum(s->opacity);
cristy3ed852e2009-09-05 21:47:34 +00002744 if (scale_indexes != (IndexPacket *) NULL)
cristyce70c172010-01-07 17:15:30 +00002745 scale_indexes[x]=(IndexPacket) ClampToQuantum(s->index);
cristy3ed852e2009-09-05 21:47:34 +00002746 q++;
2747 s++;
2748 }
2749 }
2750 else
2751 {
2752 /*
2753 Scale X direction.
2754 */
2755 pixel=zero;
2756 next_column=MagickFalse;
2757 span.x=1.0;
2758 s=scanline;
2759 t=scale_scanline;
2760 for (x=0; x < (long) image->columns; x++)
2761 {
2762 scale.x=(double) scale_image->columns/(double) image->columns;
2763 while (scale.x >= span.x)
2764 {
2765 if (next_column != MagickFalse)
2766 {
2767 pixel=zero;
2768 t++;
2769 }
2770 pixel.red+=span.x*s->red;
2771 pixel.green+=span.x*s->green;
2772 pixel.blue+=span.x*s->blue;
2773 if (image->matte != MagickFalse)
2774 pixel.opacity+=span.x*s->opacity;
2775 if (scale_indexes != (IndexPacket *) NULL)
2776 pixel.index+=span.x*s->index;
2777 t->red=pixel.red;
2778 t->green=pixel.green;
2779 t->blue=pixel.blue;
2780 if (scale_image->matte != MagickFalse)
2781 t->opacity=pixel.opacity;
2782 if (scale_indexes != (IndexPacket *) NULL)
2783 t->index=pixel.index;
2784 scale.x-=span.x;
2785 span.x=1.0;
2786 next_column=MagickTrue;
2787 }
2788 if (scale.x > 0)
2789 {
2790 if (next_column != MagickFalse)
2791 {
2792 pixel=zero;
2793 next_column=MagickFalse;
2794 t++;
2795 }
2796 pixel.red+=scale.x*s->red;
2797 pixel.green+=scale.x*s->green;
2798 pixel.blue+=scale.x*s->blue;
2799 if (scale_image->matte != MagickFalse)
2800 pixel.opacity+=scale.x*s->opacity;
2801 if (scale_indexes != (IndexPacket *) NULL)
2802 pixel.index+=scale.x*s->index;
2803 span.x-=scale.x;
2804 }
2805 s++;
2806 }
2807 if (span.x > 0)
2808 {
2809 s--;
2810 pixel.red+=span.x*s->red;
2811 pixel.green+=span.x*s->green;
2812 pixel.blue+=span.x*s->blue;
2813 if (scale_image->matte != MagickFalse)
2814 pixel.opacity+=span.x*s->opacity;
2815 if (scale_indexes != (IndexPacket *) NULL)
2816 pixel.index+=span.x*s->index;
2817 }
2818 if ((next_column == MagickFalse) &&
2819 ((long) (t-scale_scanline) < (long) scale_image->columns))
2820 {
2821 t->red=pixel.red;
2822 t->green=pixel.green;
2823 t->blue=pixel.blue;
2824 if (scale_image->matte != MagickFalse)
2825 t->opacity=pixel.opacity;
2826 if (scale_indexes != (IndexPacket *) NULL)
2827 t->index=pixel.index;
2828 }
2829 /*
2830 Transfer scanline to scaled image.
2831 */
2832 t=scale_scanline;
2833 for (x=0; x < (long) scale_image->columns; x++)
2834 {
cristyce70c172010-01-07 17:15:30 +00002835 q->red=ClampToQuantum(t->red);
2836 q->green=ClampToQuantum(t->green);
2837 q->blue=ClampToQuantum(t->blue);
cristy3ed852e2009-09-05 21:47:34 +00002838 if (scale_image->matte != MagickFalse)
cristyce70c172010-01-07 17:15:30 +00002839 q->opacity=ClampToQuantum(t->opacity);
cristy3ed852e2009-09-05 21:47:34 +00002840 if (scale_indexes != (IndexPacket *) NULL)
cristyce70c172010-01-07 17:15:30 +00002841 scale_indexes[x]=(IndexPacket) ClampToQuantum(t->index);
cristy3ed852e2009-09-05 21:47:34 +00002842 t++;
2843 q++;
2844 }
2845 }
cristyed6cb232010-01-20 03:07:53 +00002846 if (SyncCacheViewAuthenticPixels(scale_view,exception) == MagickFalse)
cristy3ed852e2009-09-05 21:47:34 +00002847 break;
2848 proceed=SetImageProgress(image,ScaleImageTag,y,image->rows);
2849 if (proceed == MagickFalse)
2850 break;
2851 }
cristyed6cb232010-01-20 03:07:53 +00002852 scale_view=DestroyCacheView(scale_view);
2853 image_view=DestroyCacheView(image_view);
cristy3ed852e2009-09-05 21:47:34 +00002854 /*
2855 Free allocated memory.
2856 */
2857 y_vector=(MagickPixelPacket *) RelinquishMagickMemory(y_vector);
2858 scale_scanline=(MagickPixelPacket *) RelinquishMagickMemory(scale_scanline);
2859 if (scale_image->rows != image->rows)
2860 scanline=(MagickPixelPacket *) RelinquishMagickMemory(scanline);
2861 x_vector=(MagickPixelPacket *) RelinquishMagickMemory(x_vector);
2862 scale_image->type=image->type;
2863 return(scale_image);
2864}
2865
2866/*
2867%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2868% %
2869% %
2870% %
2871+ S e t R e s i z e F i l t e r S u p p o r t %
2872% %
2873% %
2874% %
2875%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2876%
2877% SetResizeFilterSupport() specifies which IR filter to use to window
2878%
2879% The format of the SetResizeFilterSupport method is:
2880%
2881% void SetResizeFilterSupport(ResizeFilter *resize_filter,
2882% const MagickRealType support)
2883%
2884% A description of each parameter follows:
2885%
2886% o resize_filter: the resize filter.
2887%
2888% o support: the filter spport radius.
2889%
2890*/
2891MagickExport void SetResizeFilterSupport(ResizeFilter *resize_filter,
2892 const MagickRealType support)
2893{
2894 assert(resize_filter != (ResizeFilter *) NULL);
2895 assert(resize_filter->signature == MagickSignature);
2896 resize_filter->support=support;
2897}
2898
2899/*
2900%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2901% %
2902% %
2903% %
2904% T h u m b n a i l I m a g e %
2905% %
2906% %
2907% %
2908%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2909%
2910% ThumbnailImage() changes the size of an image to the given dimensions and
2911% removes any associated profiles. The goal is to produce small low cost
2912% thumbnail images suited for display on the Web.
2913%
2914% The format of the ThumbnailImage method is:
2915%
2916% Image *ThumbnailImage(const Image *image,const unsigned long columns,
2917% const unsigned long rows,ExceptionInfo *exception)
2918%
2919% A description of each parameter follows:
2920%
2921% o image: the image.
2922%
2923% o columns: the number of columns in the scaled image.
2924%
2925% o rows: the number of rows in the scaled image.
2926%
2927% o exception: return any errors or warnings in this structure.
2928%
2929*/
2930MagickExport Image *ThumbnailImage(const Image *image,
2931 const unsigned long columns,const unsigned long rows,ExceptionInfo *exception)
2932{
2933#define SampleFactor 5
2934
2935 char
2936 value[MaxTextExtent];
2937
2938 const char
2939 *name;
2940
2941 Image
2942 *thumbnail_image;
2943
2944 MagickRealType
2945 x_factor,
2946 y_factor;
2947
2948 struct stat
2949 attributes;
2950
2951 unsigned long
2952 version;
2953
2954 assert(image != (Image *) NULL);
2955 assert(image->signature == MagickSignature);
2956 if (image->debug != MagickFalse)
2957 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
2958 assert(exception != (ExceptionInfo *) NULL);
2959 assert(exception->signature == MagickSignature);
2960 x_factor=(MagickRealType) columns/(MagickRealType) image->columns;
2961 y_factor=(MagickRealType) rows/(MagickRealType) image->rows;
2962 if ((x_factor*y_factor) > 0.1)
2963 thumbnail_image=ZoomImage(image,columns,rows,exception);
2964 else
2965 if (((SampleFactor*columns) < 128) || ((SampleFactor*rows) < 128))
2966 thumbnail_image=ZoomImage(image,columns,rows,exception);
2967 else
2968 {
2969 Image
2970 *sample_image;
2971
2972 sample_image=SampleImage(image,SampleFactor*columns,SampleFactor*rows,
2973 exception);
2974 if (sample_image == (Image *) NULL)
2975 return((Image *) NULL);
2976 thumbnail_image=ZoomImage(sample_image,columns,rows,exception);
2977 sample_image=DestroyImage(sample_image);
2978 }
2979 if (thumbnail_image == (Image *) NULL)
2980 return(thumbnail_image);
2981 (void) ParseAbsoluteGeometry("0x0+0+0",&thumbnail_image->page);
2982 if (thumbnail_image->matte == MagickFalse)
2983 (void) SetImageAlphaChannel(thumbnail_image,OpaqueAlphaChannel);
2984 thumbnail_image->depth=8;
2985 thumbnail_image->interlace=NoInterlace;
2986 /*
2987 Strip all profiles except color profiles.
2988 */
2989 ResetImageProfileIterator(thumbnail_image);
2990 for (name=GetNextImageProfile(thumbnail_image); name != (const char *) NULL; )
2991 {
2992 if ((LocaleCompare(name,"icc") != 0) && (LocaleCompare(name,"icm") != 0))
2993 {
cristy2b726bd2010-01-11 01:05:39 +00002994 (void) DeleteImageProfile(thumbnail_image,name);
cristy3ed852e2009-09-05 21:47:34 +00002995 ResetImageProfileIterator(thumbnail_image);
2996 }
2997 name=GetNextImageProfile(thumbnail_image);
2998 }
2999 (void) DeleteImageProperty(thumbnail_image,"comment");
3000 (void) CopyMagickString(value,image->magick_filename,MaxTextExtent);
cristy7b63d662009-11-13 01:58:56 +00003001 if (strstr(image->magick_filename,"//") == (char *) NULL)
3002 (void) FormatMagickString(value,MaxTextExtent,"file://%s",
cristy3ed852e2009-09-05 21:47:34 +00003003 image->magick_filename);
3004 (void) SetImageProperty(thumbnail_image,"Thumb::URI",value);
3005 (void) CopyMagickString(value,image->magick_filename,MaxTextExtent);
3006 if (GetPathAttributes(image->filename,&attributes) != MagickFalse)
3007 {
3008 (void) FormatMagickString(value,MaxTextExtent,"%ld",(long)
3009 attributes.st_mtime);
3010 (void) SetImageProperty(thumbnail_image,"Thumb::MTime",value);
3011 }
3012 (void) FormatMagickString(value,MaxTextExtent,"%ld",(long)
3013 attributes.st_mtime);
cristyb9080c92009-12-01 20:13:26 +00003014 (void) FormatMagickSize(GetBlobSize(image),MagickFalse,value);
cristy2ce15c92010-03-12 14:03:41 +00003015 (void) ConcatenateMagickString(value,"B",MaxTextExtent);
cristy3ed852e2009-09-05 21:47:34 +00003016 (void) SetImageProperty(thumbnail_image,"Thumb::Size",value);
3017 (void) FormatMagickString(value,MaxTextExtent,"image/%s",image->magick);
3018 LocaleLower(value);
3019 (void) SetImageProperty(thumbnail_image,"Thumb::Mimetype",value);
3020 (void) SetImageProperty(thumbnail_image,"software",
3021 GetMagickVersion(&version));
3022 (void) FormatMagickString(value,MaxTextExtent,"%lu",image->magick_columns);
3023 (void) SetImageProperty(thumbnail_image,"Thumb::Image::Width",value);
3024 (void) FormatMagickString(value,MaxTextExtent,"%lu",image->magick_rows);
3025 (void) SetImageProperty(thumbnail_image,"Thumb::Image::height",value);
3026 (void) FormatMagickString(value,MaxTextExtent,"%lu",
3027 GetImageListLength(image));
3028 (void) SetImageProperty(thumbnail_image,"Thumb::Document::Pages",value);
3029 return(thumbnail_image);
3030}
3031
3032/*
3033%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3034% %
3035% %
3036% %
3037% Z o o m I m a g e %
3038% %
3039% %
3040% %
3041%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3042%
3043% ZoomImage() creates a new image that is a scaled size of an existing one.
3044% It allocates the memory necessary for the new Image structure and returns a
3045% pointer to the new image. The Point filter gives fast pixel replication,
3046% Triangle is equivalent to bi-linear interpolation, and Mitchel giver slower,
3047% very high-quality results. See Graphic Gems III for details on this
3048% algorithm.
3049%
3050% The filter member of the Image structure specifies which image filter to
3051% use. Blur specifies the blur factor where > 1 is blurry, < 1 is sharp.
3052%
3053% The format of the ZoomImage method is:
3054%
3055% Image *ZoomImage(const Image *image,const unsigned long columns,
3056% const unsigned long rows,ExceptionInfo *exception)
3057%
3058% A description of each parameter follows:
3059%
3060% o image: the image.
3061%
3062% o columns: An integer that specifies the number of columns in the zoom
3063% image.
3064%
3065% o rows: An integer that specifies the number of rows in the scaled
3066% image.
3067%
3068% o exception: return any errors or warnings in this structure.
3069%
3070*/
3071MagickExport Image *ZoomImage(const Image *image,const unsigned long columns,
3072 const unsigned long rows,ExceptionInfo *exception)
3073{
3074 Image
3075 *zoom_image;
3076
3077 assert(image != (const Image *) NULL);
3078 assert(image->signature == MagickSignature);
3079 if (image->debug != MagickFalse)
3080 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
3081 assert(exception != (ExceptionInfo *) NULL);
3082 assert(exception->signature == MagickSignature);
3083 zoom_image=ResizeImage(image,columns,rows,image->filter,image->blur,
3084 exception);
3085 return(zoom_image);
3086}