1 /* 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 % Cristy % 17 % July 1992 % 18 % % 19 % % 20 % Copyright 1999-2016 ImageMagick Studio LLC, a non-profit organization % 21 % 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 /* 41 Include declarations. 42 */ 43 #include "MagickCore/studio.h" 44 #include "MagickCore/accelerate-private.h" 45 #include "MagickCore/artifact.h" 46 #include "MagickCore/blob.h" 47 #include "MagickCore/cache.h" 48 #include "MagickCore/cache-view.h" 49 #include "MagickCore/channel.h" 50 #include "MagickCore/color.h" 51 #include "MagickCore/color-private.h" 52 #include "MagickCore/draw.h" 53 #include "MagickCore/exception.h" 54 #include "MagickCore/exception-private.h" 55 #include "MagickCore/gem.h" 56 #include "MagickCore/image.h" 57 #include "MagickCore/image-private.h" 58 #include "MagickCore/list.h" 59 #include "MagickCore/memory_.h" 60 #include "MagickCore/memory-private.h" 61 #include "MagickCore/magick.h" 62 #include "MagickCore/pixel-accessor.h" 63 #include "MagickCore/property.h" 64 #include "MagickCore/monitor.h" 65 #include "MagickCore/monitor-private.h" 66 #include "MagickCore/nt-base-private.h" 67 #include "MagickCore/option.h" 68 #include "MagickCore/pixel.h" 69 #include "MagickCore/pixel-private.h" 70 #include "MagickCore/quantum-private.h" 71 #include "MagickCore/resample.h" 72 #include "MagickCore/resample-private.h" 73 #include "MagickCore/resize.h" 74 #include "MagickCore/resize-private.h" 75 #include "MagickCore/resource_.h" 76 #include "MagickCore/string_.h" 77 #include "MagickCore/string-private.h" 78 #include "MagickCore/thread-private.h" 79 #include "MagickCore/token.h" 80 #include "MagickCore/utility.h" 81 #include "MagickCore/utility-private.h" 82 #include "MagickCore/version.h" 83 #if defined(MAGICKCORE_LQR_DELEGATE) 84 #include <lqr.h> 85 #endif 86 87 /* 89 Typedef declarations. 90 */ 91 struct _ResizeFilter 92 { 93 double 94 (*filter)(const double,const ResizeFilter *), 95 (*window)(const double,const ResizeFilter *), 96 support, /* filter region of support - the filter support limit */ 97 window_support, /* window support, usally equal to support (expert only) */ 98 scale, /* dimension scaling to fit window support (usally 1.0) */ 99 blur, /* x-scale (blur-sharpen) */ 100 coefficient[7]; /* cubic coefficents for BC-cubic filters */ 101 102 ResizeWeightingFunctionType 103 filterWeightingType, 104 windowWeightingType; 105 106 size_t 107 signature; 108 }; 109 110 /* 112 Forward declaractions. 113 */ 114 static double 115 I0(double x), 116 BesselOrderOne(double), 117 Sinc(const double, const ResizeFilter *), 118 SincFast(const double, const ResizeFilter *); 119 120 /* 122 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 123 % % 124 % % 125 % % 126 + F i l t e r F u n c t i o n s % 127 % % 128 % % 129 % % 130 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 131 % 132 % These are the various filter and windowing functions that are provided. 133 % 134 % They are internal to this module only. See AcquireResizeFilterInfo() for 135 % details of the access to these functions, via the GetResizeFilterSupport() 136 % and GetResizeFilterWeight() API interface. 137 % 138 % The individual filter functions have this format... 139 % 140 % static MagickRealtype *FilterName(const double x,const double support) 141 % 142 % A description of each parameter follows: 143 % 144 % o x: the distance from the sampling point generally in the range of 0 to 145 % support. The GetResizeFilterWeight() ensures this a positive value. 146 % 147 % o resize_filter: current filter information. This allows function to 148 % access support, and possibly other pre-calculated information defining 149 % the functions. 150 % 151 */ 152 153 static double Blackman(const double x, 154 const ResizeFilter *magick_unused(resize_filter)) 155 { 156 /* 157 Blackman: 2nd order cosine windowing function: 158 0.42 + 0.5 cos(pi x) + 0.08 cos(2pi x) 159 160 Refactored by Chantal Racette and Nicolas Robidoux to one trig call and 161 five flops. 162 */ 163 const double cosine=cos((double) (MagickPI*x)); 164 magick_unreferenced(resize_filter); 165 return(0.34+cosine*(0.5+cosine*0.16)); 166 } 167 168 static double Bohman(const double x, 169 const ResizeFilter *magick_unused(resize_filter)) 170 { 171 /* 172 Bohman: 2rd Order cosine windowing function: 173 (1-x) cos(pi x) + sin(pi x) / pi. 174 175 Refactored by Nicolas Robidoux to one trig call, one sqrt call, and 7 flops, 176 taking advantage of the fact that the support of Bohman is 1.0 (so that we 177 know that sin(pi x) >= 0). 178 */ 179 const double cosine=cos((double) (MagickPI*x)); 180 const double sine=sqrt(1.0-cosine*cosine); 181 magick_unreferenced(resize_filter); 182 return((1.0-x)*cosine+(1.0/MagickPI)*sine); 183 } 184 185 static double Box(const double magick_unused(x), 186 const ResizeFilter *magick_unused(resize_filter)) 187 { 188 magick_unreferenced(x); 189 magick_unreferenced(resize_filter); 190 191 /* 192 A Box filter is a equal weighting function (all weights equal). 193 DO NOT LIMIT results by support or resize point sampling will work 194 as it requests points beyond its normal 0.0 support size. 195 */ 196 return(1.0); 197 } 198 199 static double Cosine(const double x, 200 const ResizeFilter *magick_unused(resize_filter)) 201 { 202 magick_unreferenced(resize_filter); 203 204 /* 205 Cosine window function: 206 cos((pi/2)*x). 207 */ 208 return((double)cos((double) (MagickPI2*x))); 209 } 210 211 static double CubicBC(const double x,const ResizeFilter *resize_filter) 212 { 213 /* 214 Cubic Filters using B,C determined values: 215 Mitchell-Netravali B = 1/3 C = 1/3 "Balanced" cubic spline filter 216 Catmull-Rom B = 0 C = 1/2 Interpolatory and exact on linears 217 Spline B = 1 C = 0 B-Spline Gaussian approximation 218 Hermite B = 0 C = 0 B-Spline interpolator 219 220 See paper by Mitchell and Netravali, Reconstruction Filters in Computer 221 Graphics Computer Graphics, Volume 22, Number 4, August 1988 222 http://www.cs.utexas.edu/users/fussell/courses/cs384g/lectures/mitchell/ 223 Mitchell.pdf. 224 225 Coefficents are determined from B,C values: 226 P0 = ( 6 - 2*B )/6 = coeff[0] 227 P1 = 0 228 P2 = (-18 +12*B + 6*C )/6 = coeff[1] 229 P3 = ( 12 - 9*B - 6*C )/6 = coeff[2] 230 Q0 = ( 8*B +24*C )/6 = coeff[3] 231 Q1 = ( -12*B -48*C )/6 = coeff[4] 232 Q2 = ( 6*B +30*C )/6 = coeff[5] 233 Q3 = ( - 1*B - 6*C )/6 = coeff[6] 234 235 which are used to define the filter: 236 237 P0 + P1*x + P2*x^2 + P3*x^3 0 <= x < 1 238 Q0 + Q1*x + Q2*x^2 + Q3*x^3 1 <= x < 2 239 240 which ensures function is continuous in value and derivative (slope). 241 */ 242 if (x < 1.0) 243 return(resize_filter->coefficient[0]+x*(x* 244 (resize_filter->coefficient[1]+x*resize_filter->coefficient[2]))); 245 if (x < 2.0) 246 return(resize_filter->coefficient[3]+x*(resize_filter->coefficient[4]+x* 247 (resize_filter->coefficient[5]+x*resize_filter->coefficient[6]))); 248 return(0.0); 249 } 250 251 static double Gaussian(const double x,const ResizeFilter *resize_filter) 252 { 253 /* 254 Gaussian with a sigma = 1/2 (or as user specified) 255 256 Gaussian Formula (1D) ... 257 exp( -(x^2)/((2.0*sigma^2) ) / (sqrt(2*PI)*sigma^2)) 258 259 Gaussian Formula (2D) ... 260 exp( -(x^2+y^2)/(2.0*sigma^2) ) / (PI*sigma^2) ) 261 or for radius 262 exp( -(r^2)/(2.0*sigma^2) ) / (PI*sigma^2) ) 263 264 Note that it is only a change from 1-d to radial form is in the 265 normalization multiplier which is not needed or used when Gaussian is used 266 as a filter. 267 268 The constants are pre-calculated... 269 270 coeff[0]=sigma; 271 coeff[1]=1.0/(2.0*sigma^2); 272 coeff[2]=1.0/(sqrt(2*PI)*sigma^2); 273 274 exp( -coeff[1]*(x^2)) ) * coeff[2]; 275 276 However the multiplier coeff[1] is need, the others are informative only. 277 278 This separates the gaussian 'sigma' value from the 'blur/support' 279 settings allowing for its use in special 'small sigma' gaussians, 280 without the filter 'missing' pixels because the support becomes too 281 small. 282 */ 283 return(exp((double)(-resize_filter->coefficient[1]*x*x))); 284 } 285 286 static double Hann(const double x, 287 const ResizeFilter *magick_unused(resize_filter)) 288 { 289 /* 290 Cosine window function: 291 0.5+0.5*cos(pi*x). 292 */ 293 const double cosine=cos((double) (MagickPI*x)); 294 magick_unreferenced(resize_filter); 295 return(0.5+0.5*cosine); 296 } 297 298 static double Hamming(const double x, 299 const ResizeFilter *magick_unused(resize_filter)) 300 { 301 /* 302 Offset cosine window function: 303 .54 + .46 cos(pi x). 304 */ 305 const double cosine=cos((double) (MagickPI*x)); 306 magick_unreferenced(resize_filter); 307 return(0.54+0.46*cosine); 308 } 309 310 static double Jinc(const double x, 311 const ResizeFilter *magick_unused(resize_filter)) 312 { 313 magick_unreferenced(resize_filter); 314 315 /* 316 See Pratt "Digital Image Processing" p.97 for Jinc/Bessel functions. 317 http://mathworld.wolfram.com/JincFunction.html and page 11 of 318 http://www.ph.ed.ac.uk/%7ewjh/teaching/mo/slides/lens/lens.pdf 319 320 The original "zoom" program by Paul Heckbert called this "Bessel". But 321 really it is more accurately named "Jinc". 322 */ 323 if (x == 0.0) 324 return(0.5*MagickPI); 325 return(BesselOrderOne(MagickPI*x)/x); 326 } 327 328 static double Kaiser(const double x,const ResizeFilter *resize_filter) 329 { 330 /* 331 Kaiser Windowing Function (bessel windowing) 332 333 I0( beta * sqrt( 1-x^2) ) / IO(0) 334 335 Beta (coeff[0]) is a free value from 5 to 8 (defaults to 6.5). 336 However it is typically defined in terms of Alpha*PI 337 338 The normalization factor (coeff[1]) is not actually needed, 339 but without it the filters has a large value at x=0 making it 340 difficult to compare the function with other windowing functions. 341 */ 342 return(resize_filter->coefficient[1]*I0(resize_filter->coefficient[0]* 343 sqrt((double) (1.0-x*x)))); 344 } 345 346 static double Lagrange(const double x,const ResizeFilter *resize_filter) 347 { 348 double 349 value; 350 351 register ssize_t 352 i; 353 354 ssize_t 355 n, 356 order; 357 358 /* 359 Lagrange piecewise polynomial fit of sinc: N is the 'order' of the lagrange 360 function and depends on the overall support window size of the filter. That 361 is: for a support of 2, it gives a lagrange-4 (piecewise cubic function). 362 363 "n" identifies the piece of the piecewise polynomial. 364 365 See Survey: Interpolation Methods, IEEE Transactions on Medical Imaging, 366 Vol 18, No 11, November 1999, p1049-1075, -- Equation 27 on p1064. 367 */ 368 if (x > resize_filter->support) 369 return(0.0); 370 order=(ssize_t) (2.0*resize_filter->window_support); /* number of pieces */ 371 n=(ssize_t) (resize_filter->window_support+x); 372 value=1.0f; 373 for (i=0; i < order; i++) 374 if (i != n) 375 value*=(n-i-x)/(n-i); 376 return(value); 377 } 378 379 static double Quadratic(const double x, 380 const ResizeFilter *magick_unused(resize_filter)) 381 { 382 magick_unreferenced(resize_filter); 383 384 /* 385 2rd order (quadratic) B-Spline approximation of Gaussian. 386 */ 387 if (x < 0.5) 388 return(0.75-x*x); 389 if (x < 1.5) 390 return(0.5*(x-1.5)*(x-1.5)); 391 return(0.0); 392 } 393 394 static double Sinc(const double x, 395 const ResizeFilter *magick_unused(resize_filter)) 396 { 397 magick_unreferenced(resize_filter); 398 399 /* 400 Scaled sinc(x) function using a trig call: 401 sinc(x) == sin(pi x)/(pi x). 402 */ 403 if (x != 0.0) 404 { 405 const double alpha=(double) (MagickPI*x); 406 return(sin((double) alpha)/alpha); 407 } 408 return((double) 1.0); 409 } 410 411 static double SincFast(const double x, 412 const ResizeFilter *magick_unused(resize_filter)) 413 { 414 magick_unreferenced(resize_filter); 415 416 /* 417 Approximations of the sinc function sin(pi x)/(pi x) over the interval 418 [-4,4] constructed by Nicolas Robidoux and Chantal Racette with funding 419 from the Natural Sciences and Engineering Research Council of Canada. 420 421 Although the approximations are polynomials (for low order of 422 approximation) and quotients of polynomials (for higher order of 423 approximation) and consequently are similar in form to Taylor polynomials / 424 Pade approximants, the approximations are computed with a completely 425 different technique. 426 427 Summary: These approximations are "the best" in terms of bang (accuracy) 428 for the buck (flops). More specifically: Among the polynomial quotients 429 that can be computed using a fixed number of flops (with a given "+ - * / 430 budget"), the chosen polynomial quotient is the one closest to the 431 approximated function with respect to maximum absolute relative error over 432 the given interval. 433 434 The Remez algorithm, as implemented in the boost library's minimax package, 435 is the key to the construction: http://www.boost.org/doc/libs/1_36_0/libs/ 436 math/doc/sf_and_dist/html/math_toolkit/backgrounders/remez.html 437 438 If outside of the interval of approximation, use the standard trig formula. 439 */ 440 if (x > 4.0) 441 { 442 const double alpha=(double) (MagickPI*x); 443 return(sin((double) alpha)/alpha); 444 } 445 { 446 /* 447 The approximations only depend on x^2 (sinc is an even function). 448 */ 449 const double xx = x*x; 450 #if MAGICKCORE_QUANTUM_DEPTH <= 8 451 /* 452 Maximum absolute relative error 6.3e-6 < 1/2^17. 453 */ 454 const double c0 = 0.173610016489197553621906385078711564924e-2L; 455 const double c1 = -0.384186115075660162081071290162149315834e-3L; 456 const double c2 = 0.393684603287860108352720146121813443561e-4L; 457 const double c3 = -0.248947210682259168029030370205389323899e-5L; 458 const double c4 = 0.107791837839662283066379987646635416692e-6L; 459 const double c5 = -0.324874073895735800961260474028013982211e-8L; 460 const double c6 = 0.628155216606695311524920882748052490116e-10L; 461 const double c7 = -0.586110644039348333520104379959307242711e-12L; 462 const double p = 463 c0+xx*(c1+xx*(c2+xx*(c3+xx*(c4+xx*(c5+xx*(c6+xx*c7)))))); 464 return((xx-1.0)*(xx-4.0)*(xx-9.0)*(xx-16.0)*p); 465 #elif MAGICKCORE_QUANTUM_DEPTH <= 16 466 /* 467 Max. abs. rel. error 2.2e-8 < 1/2^25. 468 */ 469 const double c0 = 0.173611107357320220183368594093166520811e-2L; 470 const double c1 = -0.384240921114946632192116762889211361285e-3L; 471 const double c2 = 0.394201182359318128221229891724947048771e-4L; 472 const double c3 = -0.250963301609117217660068889165550534856e-5L; 473 const double c4 = 0.111902032818095784414237782071368805120e-6L; 474 const double c5 = -0.372895101408779549368465614321137048875e-8L; 475 const double c6 = 0.957694196677572570319816780188718518330e-10L; 476 const double c7 = -0.187208577776590710853865174371617338991e-11L; 477 const double c8 = 0.253524321426864752676094495396308636823e-13L; 478 const double c9 = -0.177084805010701112639035485248501049364e-15L; 479 const double p = 480 c0+xx*(c1+xx*(c2+xx*(c3+xx*(c4+xx*(c5+xx*(c6+xx*(c7+xx*(c8+xx*c9)))))))); 481 return((xx-1.0)*(xx-4.0)*(xx-9.0)*(xx-16.0)*p); 482 #else 483 /* 484 Max. abs. rel. error 1.2e-12 < 1/2^39. 485 */ 486 const double c0 = 0.173611111110910715186413700076827593074e-2L; 487 const double c1 = -0.289105544717893415815859968653611245425e-3L; 488 const double c2 = 0.206952161241815727624413291940849294025e-4L; 489 const double c3 = -0.834446180169727178193268528095341741698e-6L; 490 const double c4 = 0.207010104171026718629622453275917944941e-7L; 491 const double c5 = -0.319724784938507108101517564300855542655e-9L; 492 const double c6 = 0.288101675249103266147006509214934493930e-11L; 493 const double c7 = -0.118218971804934245819960233886876537953e-13L; 494 const double p = 495 c0+xx*(c1+xx*(c2+xx*(c3+xx*(c4+xx*(c5+xx*(c6+xx*c7)))))); 496 const double d0 = 1.0L; 497 const double d1 = 0.547981619622284827495856984100563583948e-1L; 498 const double d2 = 0.134226268835357312626304688047086921806e-2L; 499 const double d3 = 0.178994697503371051002463656833597608689e-4L; 500 const double d4 = 0.114633394140438168641246022557689759090e-6L; 501 const double q = d0+xx*(d1+xx*(d2+xx*(d3+xx*d4))); 502 return((xx-1.0)*(xx-4.0)*(xx-9.0)*(xx-16.0)/q*p); 503 #endif 504 } 505 } 506 507 static double Triangle(const double x, 508 const ResizeFilter *magick_unused(resize_filter)) 509 { 510 magick_unreferenced(resize_filter); 511 512 /* 513 1st order (linear) B-Spline, bilinear interpolation, Tent 1D filter, or 514 a Bartlett 2D Cone filter. Also used as a Bartlett Windowing function 515 for Sinc(). 516 */ 517 if (x < 1.0) 518 return(1.0-x); 519 return(0.0); 520 } 521 522 static double Welch(const double x, 523 const ResizeFilter *magick_unused(resize_filter)) 524 { 525 magick_unreferenced(resize_filter); 526 527 /* 528 Welch parabolic windowing filter. 529 */ 530 if (x < 1.0) 531 return(1.0-x*x); 532 return(0.0); 533 } 534 535 /* 537 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 538 % % 539 % % 540 % % 541 + A c q u i r e R e s i z e F i l t e r % 542 % % 543 % % 544 % % 545 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 546 % 547 % AcquireResizeFilter() allocates the ResizeFilter structure. Choose from 548 % these filters: 549 % 550 % FIR (Finite impulse Response) Filters 551 % Box Triangle Quadratic 552 % Spline Hermite Catrom 553 % Mitchell 554 % 555 % IIR (Infinite impulse Response) Filters 556 % Gaussian Sinc Jinc (Bessel) 557 % 558 % Windowed Sinc/Jinc Filters 559 % Blackman Bohman Lanczos 560 % Hann Hamming Cosine 561 % Kaiser Welch Parzen 562 % Bartlett 563 % 564 % Special Purpose Filters 565 % Cubic SincFast LanczosSharp Lanczos2 Lanczos2Sharp 566 % Robidoux RobidouxSharp 567 % 568 % The users "-filter" selection is used to lookup the default 'expert' 569 % settings for that filter from a internal table. However any provided 570 % 'expert' settings (see below) may override this selection. 571 % 572 % FIR filters are used as is, and are limited to that filters support window 573 % (unless over-ridden). 'Gaussian' while classed as an IIR filter, is also 574 % simply clipped by its support size (currently 1.5 or approximately 3*sigma 575 % as recommended by many references) 576 % 577 % The special a 'cylindrical' filter flag will promote the default 4-lobed 578 % Windowed Sinc filter to a 3-lobed Windowed Jinc equivalent, which is better 579 % suited to this style of image resampling. This typically happens when using 580 % such a filter for images distortions. 581 % 582 % SPECIFIC FILTERS: 583 % 584 % Directly requesting 'Sinc', 'Jinc' function as a filter will force the use 585 % of function without any windowing, or promotion for cylindrical usage. This 586 % is not recommended, except by image processing experts, especially as part 587 % of expert option filter function selection. 588 % 589 % Two forms of the 'Sinc' function are available: Sinc and SincFast. Sinc is 590 % computed using the traditional sin(pi*x)/(pi*x); it is selected if the user 591 % specifically specifies the use of a Sinc filter. SincFast uses highly 592 % accurate (and fast) polynomial (low Q) and rational (high Q) approximations, 593 % and will be used by default in most cases. 594 % 595 % The Lanczos filter is a special 3-lobed Sinc-windowed Sinc filter (promoted 596 % to Jinc-windowed Jinc for cylindrical (Elliptical Weighted Average) use). 597 % The Sinc version is the most popular windowed filter. 598 % 599 % LanczosSharp is a slightly sharpened (blur=0.9812505644269356 < 1) form of 600 % the Lanczos filter, specifically designed for EWA distortion (as a 601 % Jinc-Jinc); it can also be used as a slightly sharper orthogonal Lanczos 602 % (Sinc-Sinc) filter. The chosen blur value comes as close as possible to 603 % satisfying the following condition without changing the character of the 604 % corresponding EWA filter: 605 % 606 % 'No-Op' Vertical and Horizontal Line Preservation Condition: Images with 607 % only vertical or horizontal features are preserved when performing 'no-op" 608 % with EWA distortion. 609 % 610 % The Lanczos2 and Lanczos2Sharp filters are 2-lobe versions of the Lanczos 611 % filters. The 'sharp' version uses a blur factor of 0.9549963639785485, 612 % again chosen because the resulting EWA filter comes as close as possible to 613 % satisfying the above condition. 614 % 615 % Robidoux is another filter tuned for EWA. It is the Keys cubic filter 616 % defined by B=(228 - 108 sqrt(2))/199. Robidoux satisfies the "'No-Op' 617 % Vertical and Horizontal Line Preservation Condition" exactly, and it 618 % moderately blurs high frequency 'pixel-hash' patterns under no-op. It turns 619 % out to be close to both Mitchell and Lanczos2Sharp. For example, its first 620 % crossing is at (36 sqrt(2) + 123)/(72 sqrt(2) + 47), almost the same as the 621 % first crossing of Mitchell and Lanczos2Sharp. 622 % 623 % RodidouxSharp is a slightly sharper version of Rodidoux, some believe it 624 % is too sharp. It is designed to minimize the maximum possible change in 625 % a pixel value which is at one of the extremes (e.g., 0 or 255) under no-op 626 % conditions. Amazingly Mitchell falls roughly between Rodidoux and 627 % RodidouxSharp, though this seems to have been pure coincidence. 628 % 629 % 'EXPERT' OPTIONS: 630 % 631 % These artifact "defines" are not recommended for production use without 632 % expert knowledge of resampling, filtering, and the effects they have on the 633 % resulting resampled (resized or distorted) image. 634 % 635 % They can be used to override any and all filter default, and it is 636 % recommended you make good use of "filter:verbose" to make sure that the 637 % overall effect of your selection (before and after) is as expected. 638 % 639 % "filter:verbose" controls whether to output the exact results of the 640 % filter selections made, as well as plotting data for graphing the 641 % resulting filter over the filters support range. 642 % 643 % "filter:filter" select the main function associated with this filter 644 % name, as the weighting function of the filter. This can be used to 645 % set a windowing function as a weighting function, for special 646 % purposes, such as graphing. 647 % 648 % If a "filter:window" operation has not been provided, a 'Box' 649 % windowing function will be set to denote that no windowing function is 650 % being used. 651 % 652 % "filter:window" Select this windowing function for the filter. While any 653 % filter could be used as a windowing function, using the 'first lobe' of 654 % that filter over the whole support window, using a non-windowing 655 % function is not advisible. If no weighting filter function is specified 656 % a 'SincFast' filter is used. 657 % 658 % "filter:lobes" Number of lobes to use for the Sinc/Jinc filter. This a 659 % simpler method of setting filter support size that will correctly 660 % handle the Sinc/Jinc switch for an operators filtering requirements. 661 % Only integers should be given. 662 % 663 % "filter:support" Set the support size for filtering to the size given. 664 % This not recommended for Sinc/Jinc windowed filters (lobes should be 665 % used instead). This will override any 'filter:lobes' option. 666 % 667 % "filter:win-support" Scale windowing function to this size instead. This 668 % causes the windowing (or self-windowing Lagrange filter) to act is if 669 % the support window it much much larger than what is actually supplied 670 % to the calling operator. The filter however is still clipped to the 671 % real support size given, by the support range supplied to the caller. 672 % If unset this will equal the normal filter support size. 673 % 674 % "filter:blur" Scale the filter and support window by this amount. A value 675 % of > 1 will generally result in a more blurred image with more ringing 676 % effects, while a value <1 will sharpen the resulting image with more 677 % aliasing effects. 678 % 679 % "filter:sigma" The sigma value to use for the Gaussian filter only. 680 % Defaults to '1/2'. Using a different sigma effectively provides a 681 % method of using the filter as a 'blur' convolution. Particularly when 682 % using it for Distort. 683 % 684 % "filter:b" 685 % "filter:c" Override the preset B,C values for a Cubic filter. 686 % If only one of these are given it is assumes to be a 'Keys' type of 687 % filter such that B+2C=1, where Keys 'alpha' value = C. 688 % 689 % Examples: 690 % 691 % Set a true un-windowed Sinc filter with 10 lobes (very slow): 692 % -define filter:filter=Sinc 693 % -define filter:lobes=8 694 % 695 % Set an 8 lobe Lanczos (Sinc or Jinc) filter: 696 % -filter Lanczos 697 % -define filter:lobes=8 698 % 699 % The format of the AcquireResizeFilter method is: 700 % 701 % ResizeFilter *AcquireResizeFilter(const Image *image, 702 % const FilterType filter_type,const MagickBooleanType cylindrical, 703 % ExceptionInfo *exception) 704 % 705 % A description of each parameter follows: 706 % 707 % o image: the image. 708 % 709 % o filter: the filter type, defining a preset filter, window and support. 710 % The artifact settings listed above will override those selections. 711 % 712 % o blur: blur the filter by this amount, use 1.0 if unknown. Image 713 % artifact "filter:blur" will override this API call usage, including any 714 % internal change (such as for cylindrical usage). 715 % 716 % o radial: use a 1D orthogonal filter (Sinc) or 2D cylindrical (radial) 717 % filter (Jinc). 718 % 719 % o exception: return any errors or warnings in this structure. 720 % 721 */ 722 MagickPrivate ResizeFilter *AcquireResizeFilter(const Image *image, 723 const FilterType filter,const MagickBooleanType cylindrical, 724 ExceptionInfo *exception) 725 { 726 const char 727 *artifact; 728 729 FilterType 730 filter_type, 731 window_type; 732 733 double 734 B, 735 C, 736 value; 737 738 register ResizeFilter 739 *resize_filter; 740 741 /* 742 Table Mapping given Filter, into Weighting and Windowing functions. 743 A 'Box' windowing function means its a simble non-windowed filter. 744 An 'SincFast' filter function could be upgraded to a 'Jinc' filter if a 745 "cylindrical" is requested, unless a 'Sinc' or 'SincFast' filter was 746 specifically requested by the user. 747 748 WARNING: The order of this table must match the order of the FilterType 749 enumeration specified in "resample.h", or the filter names will not match 750 the filter being setup. 751 752 You can check filter setups with the "filter:verbose" expert setting. 753 */ 754 static struct 755 { 756 FilterType 757 filter, 758 window; 759 } const mapping[SentinelFilter] = 760 { 761 { UndefinedFilter, BoxFilter }, /* Undefined (default to Box) */ 762 { PointFilter, BoxFilter }, /* SPECIAL: Nearest neighbour */ 763 { BoxFilter, BoxFilter }, /* Box averaging filter */ 764 { TriangleFilter, BoxFilter }, /* Linear interpolation filter */ 765 { HermiteFilter, BoxFilter }, /* Hermite interpolation filter */ 766 { SincFastFilter, HannFilter }, /* Hann -- cosine-sinc */ 767 { SincFastFilter, HammingFilter }, /* Hamming -- '' variation */ 768 { SincFastFilter, BlackmanFilter }, /* Blackman -- 2*cosine-sinc */ 769 { GaussianFilter, BoxFilter }, /* Gaussian blur filter */ 770 { QuadraticFilter, BoxFilter }, /* Quadratic Gaussian approx */ 771 { CubicFilter, BoxFilter }, /* General Cubic Filter, Spline */ 772 { CatromFilter, BoxFilter }, /* Cubic-Keys interpolator */ 773 { MitchellFilter, BoxFilter }, /* 'Ideal' Cubic-Keys filter */ 774 { JincFilter, BoxFilter }, /* Raw 3-lobed Jinc function */ 775 { SincFilter, BoxFilter }, /* Raw 4-lobed Sinc function */ 776 { SincFastFilter, BoxFilter }, /* Raw fast sinc ("Pade"-type) */ 777 { SincFastFilter, KaiserFilter }, /* Kaiser -- square root-sinc */ 778 { LanczosFilter, WelchFilter }, /* Welch -- parabolic (3 lobe) */ 779 { SincFastFilter, CubicFilter }, /* Parzen -- cubic-sinc */ 780 { SincFastFilter, BohmanFilter }, /* Bohman -- 2*cosine-sinc */ 781 { SincFastFilter, TriangleFilter }, /* Bartlett -- triangle-sinc */ 782 { LagrangeFilter, BoxFilter }, /* Lagrange self-windowing */ 783 { LanczosFilter, LanczosFilter }, /* Lanczos Sinc-Sinc filters */ 784 { LanczosSharpFilter, LanczosSharpFilter }, /* | these require */ 785 { Lanczos2Filter, Lanczos2Filter }, /* | special handling */ 786 { Lanczos2SharpFilter, Lanczos2SharpFilter }, 787 { RobidouxFilter, BoxFilter }, /* Cubic Keys tuned for EWA */ 788 { RobidouxSharpFilter, BoxFilter }, /* Sharper Cubic Keys for EWA */ 789 { LanczosFilter, CosineFilter }, /* Cosine window (3 lobes) */ 790 { SplineFilter, BoxFilter }, /* Spline Cubic Filter */ 791 { LanczosRadiusFilter, LanczosFilter }, /* Lanczos with integer radius */ 792 }; 793 /* 794 Table mapping the filter/window from the above table to an actual function. 795 The default support size for that filter as a weighting function, the range 796 to scale with to use that function as a sinc windowing function, (typ 1.0). 797 798 Note that the filter_type -> function is 1 to 1 except for Sinc(), 799 SincFast(), and CubicBC() functions, which may have multiple filter to 800 function associations. 801 802 See "filter:verbose" handling below for the function -> filter mapping. 803 */ 804 static struct 805 { 806 double 807 (*function)(const double,const ResizeFilter*), 808 support, /* Default lobes/support size of the weighting filter. */ 809 scale, /* Support when function used as a windowing function 810 Typically equal to the location of the first zero crossing. */ 811 B,C; /* BC-spline coefficients, ignored if not a CubicBC filter. */ 812 ResizeWeightingFunctionType weightingFunctionType; 813 } const filters[SentinelFilter] = 814 { 815 /* .--- support window (if used as a Weighting Function) 816 | .--- first crossing (if used as a Windowing Function) 817 | | .--- B value for Cubic Function 818 | | | .---- C value for Cubic Function 819 | | | | */ 820 { Box, 0.5, 0.5, 0.0, 0.0, BoxWeightingFunction }, /* Undefined (default to Box) */ 821 { Box, 0.0, 0.5, 0.0, 0.0, BoxWeightingFunction }, /* Point (special handling) */ 822 { Box, 0.5, 0.5, 0.0, 0.0, BoxWeightingFunction }, /* Box */ 823 { Triangle, 1.0, 1.0, 0.0, 0.0, TriangleWeightingFunction }, /* Triangle */ 824 { CubicBC, 1.0, 1.0, 0.0, 0.0, CubicBCWeightingFunction }, /* Hermite (cubic B=C=0) */ 825 { Hann, 1.0, 1.0, 0.0, 0.0, HannWeightingFunction }, /* Hann, cosine window */ 826 { Hamming, 1.0, 1.0, 0.0, 0.0, HammingWeightingFunction }, /* Hamming, '' variation */ 827 { Blackman, 1.0, 1.0, 0.0, 0.0, BlackmanWeightingFunction }, /* Blackman, 2*cosine window */ 828 { Gaussian, 2.0, 1.5, 0.0, 0.0, GaussianWeightingFunction }, /* Gaussian */ 829 { Quadratic, 1.5, 1.5, 0.0, 0.0, QuadraticWeightingFunction },/* Quadratic gaussian */ 830 { CubicBC, 2.0, 2.0, 1.0, 0.0, CubicBCWeightingFunction }, /* General Cubic Filter */ 831 { CubicBC, 2.0, 1.0, 0.0, 0.5, CubicBCWeightingFunction }, /* Catmull-Rom (B=0,C=1/2) */ 832 { CubicBC, 2.0, 8.0/7.0, 1./3., 1./3., CubicBCWeightingFunction }, /* Mitchell (B=C=1/3) */ 833 { Jinc, 3.0, 1.2196698912665045, 0.0, 0.0, JincWeightingFunction }, /* Raw 3-lobed Jinc */ 834 { Sinc, 4.0, 1.0, 0.0, 0.0, SincWeightingFunction }, /* Raw 4-lobed Sinc */ 835 { SincFast, 4.0, 1.0, 0.0, 0.0, SincFastWeightingFunction }, /* Raw fast sinc ("Pade"-type) */ 836 { Kaiser, 1.0, 1.0, 0.0, 0.0, KaiserWeightingFunction }, /* Kaiser (square root window) */ 837 { Welch, 1.0, 1.0, 0.0, 0.0, WelchWeightingFunction }, /* Welch (parabolic window) */ 838 { CubicBC, 2.0, 2.0, 1.0, 0.0, CubicBCWeightingFunction }, /* Parzen (B-Spline window) */ 839 { Bohman, 1.0, 1.0, 0.0, 0.0, BohmanWeightingFunction }, /* Bohman, 2*Cosine window */ 840 { Triangle, 1.0, 1.0, 0.0, 0.0, TriangleWeightingFunction }, /* Bartlett (triangle window) */ 841 { Lagrange, 2.0, 1.0, 0.0, 0.0, LagrangeWeightingFunction }, /* Lagrange sinc approximation */ 842 { SincFast, 3.0, 1.0, 0.0, 0.0, SincFastWeightingFunction }, /* Lanczos, 3-lobed Sinc-Sinc */ 843 { SincFast, 3.0, 1.0, 0.0, 0.0, SincFastWeightingFunction }, /* Lanczos, Sharpened */ 844 { SincFast, 2.0, 1.0, 0.0, 0.0, SincFastWeightingFunction }, /* Lanczos, 2-lobed */ 845 { SincFast, 2.0, 1.0, 0.0, 0.0, SincFastWeightingFunction }, /* Lanczos2, sharpened */ 846 /* Robidoux: Keys cubic close to Lanczos2D sharpened */ 847 { CubicBC, 2.0, 1.1685777620836932, 848 0.37821575509399867, 0.31089212245300067, CubicBCWeightingFunction }, 849 /* RobidouxSharp: Sharper version of Robidoux */ 850 { CubicBC, 2.0, 1.105822933719019, 851 0.2620145123990142, 0.3689927438004929, CubicBCWeightingFunction }, 852 { Cosine, 1.0, 1.0, 0.0, 0.0, CosineWeightingFunction }, /* Low level cosine window */ 853 { CubicBC, 2.0, 2.0, 1.0, 0.0, CubicBCWeightingFunction }, /* Cubic B-Spline (B=1,C=0) */ 854 { SincFast, 3.0, 1.0, 0.0, 0.0, SincFastWeightingFunction }, /* Lanczos, Interger Radius */ 855 }; 856 /* 857 The known zero crossings of the Jinc() or more accurately the Jinc(x*PI) 858 function being used as a filter. It is used by the "filter:lobes" expert 859 setting and for 'lobes' for Jinc functions in the previous table. This way 860 users do not have to deal with the highly irrational lobe sizes of the Jinc 861 filter. 862 863 Values taken from 864 http://cose.math.bas.bg/webMathematica/webComputing/BesselZeros.jsp 865 using Jv-function with v=1, then dividing by PI. 866 */ 867 static double 868 jinc_zeros[16] = 869 { 870 1.2196698912665045, 871 2.2331305943815286, 872 3.2383154841662362, 873 4.2410628637960699, 874 5.2427643768701817, 875 6.2439216898644877, 876 7.2447598687199570, 877 8.2453949139520427, 878 9.2458926849494673, 879 10.246293348754916, 880 11.246622794877883, 881 12.246898461138105, 882 13.247132522181061, 883 14.247333735806849, 884 15.247508563037300, 885 16.247661874700962 886 }; 887 888 /* 889 Allocate resize filter. 890 */ 891 assert(image != (const Image *) NULL); 892 assert(image->signature == MagickCoreSignature); 893 if (image->debug != MagickFalse) 894 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename); 895 assert(UndefinedFilter < filter && filter < SentinelFilter); 896 assert(exception != (ExceptionInfo *) NULL); 897 assert(exception->signature == MagickCoreSignature); 898 resize_filter=(ResizeFilter *) AcquireMagickMemory(sizeof(*resize_filter)); 899 if (resize_filter == (ResizeFilter *) NULL) 900 ThrowFatalException(ResourceLimitFatalError,"MemoryAllocationFailed"); 901 (void) ResetMagickMemory(resize_filter,0,sizeof(*resize_filter)); 902 /* 903 Defaults for the requested filter. 904 */ 905 filter_type=mapping[filter].filter; 906 window_type=mapping[filter].window; 907 resize_filter->blur=1.0; 908 /* Promote 1D Windowed Sinc Filters to a 2D Windowed Jinc filters */ 909 if ( cylindrical != MagickFalse && (filter_type == SincFastFilter) && 910 (filter != SincFastFilter)) 911 filter_type=JincFilter; /* 1D Windowed Sinc => 2D Windowed Jinc filters */ 912 913 /* Expert filter setting override */ 914 artifact=GetImageArtifact(image,"filter:filter"); 915 if (IsStringTrue(artifact) != MagickFalse) 916 { 917 ssize_t 918 option; 919 920 option=ParseCommandOption(MagickFilterOptions,MagickFalse,artifact); 921 if ((UndefinedFilter < option) && (option < SentinelFilter)) 922 { /* Raw filter request - no window function. */ 923 filter_type=(FilterType) option; 924 window_type=BoxFilter; 925 } 926 /* Filter override with a specific window function. */ 927 artifact=GetImageArtifact(image,"filter:window"); 928 if (artifact != (const char *) NULL) 929 { 930 option=ParseCommandOption(MagickFilterOptions,MagickFalse,artifact); 931 if ((UndefinedFilter < option) && (option < SentinelFilter)) 932 window_type=(FilterType) option; 933 } 934 } 935 else 936 { 937 /* Window specified, but no filter function? Assume Sinc/Jinc. */ 938 artifact=GetImageArtifact(image,"filter:window"); 939 if (artifact != (const char *) NULL) 940 { 941 ssize_t 942 option; 943 944 option=ParseCommandOption(MagickFilterOptions,MagickFalse,artifact); 945 if ((UndefinedFilter < option) && (option < SentinelFilter)) 946 { 947 filter_type= cylindrical != MagickFalse ? JincFilter 948 : SincFastFilter; 949 window_type=(FilterType) option; 950 } 951 } 952 } 953 954 /* Assign the real functions to use for the filters selected. */ 955 resize_filter->filter=filters[filter_type].function; 956 resize_filter->support=filters[filter_type].support; 957 resize_filter->filterWeightingType=filters[filter_type].weightingFunctionType; 958 resize_filter->window=filters[window_type].function; 959 resize_filter->windowWeightingType=filters[window_type].weightingFunctionType; 960 resize_filter->scale=filters[window_type].scale; 961 resize_filter->signature=MagickCoreSignature; 962 963 /* Filter Modifications for orthogonal/cylindrical usage */ 964 if (cylindrical != MagickFalse) 965 switch (filter_type) 966 { 967 case BoxFilter: 968 /* Support for Cylindrical Box should be sqrt(2)/2 */ 969 resize_filter->support=(double) MagickSQ1_2; 970 break; 971 case LanczosFilter: 972 case LanczosSharpFilter: 973 case Lanczos2Filter: 974 case Lanczos2SharpFilter: 975 case LanczosRadiusFilter: 976 resize_filter->filter=filters[JincFilter].function; 977 resize_filter->window=filters[JincFilter].function; 978 resize_filter->scale=filters[JincFilter].scale; 979 /* number of lobes (support window size) remain unchanged */ 980 break; 981 default: 982 break; 983 } 984 /* Global Sharpening (regardless of orthoginal/cylindrical) */ 985 switch (filter_type) 986 { 987 case LanczosSharpFilter: 988 resize_filter->blur *= 0.9812505644269356; 989 break; 990 case Lanczos2SharpFilter: 991 resize_filter->blur *= 0.9549963639785485; 992 break; 993 /* case LanczosRadius: blur adjust is done after lobes */ 994 default: 995 break; 996 } 997 998 /* 999 Expert Option Modifications. 1000 */ 1001 1002 /* User Gaussian Sigma Override - no support change */ 1003 if ((resize_filter->filter == Gaussian) || 1004 (resize_filter->window == Gaussian) ) { 1005 value=0.5; /* guassian sigma default, half pixel */ 1006 artifact=GetImageArtifact(image,"filter:sigma"); 1007 if (artifact != (const char *) NULL) 1008 value=StringToDouble(artifact,(char **) NULL); 1009 /* Define coefficents for Gaussian */ 1010 resize_filter->coefficient[0]=value; /* note sigma too */ 1011 resize_filter->coefficient[1]=PerceptibleReciprocal(2.0*value*value); /* sigma scaling */ 1012 resize_filter->coefficient[2]=PerceptibleReciprocal(Magick2PI*value*value); 1013 /* normalization - not actually needed or used! */ 1014 if ( value > 0.5 ) 1015 resize_filter->support *= 2*value; /* increase support linearly */ 1016 } 1017 1018 /* User Kaiser Alpha Override - no support change */ 1019 if ((resize_filter->filter == Kaiser) || 1020 (resize_filter->window == Kaiser) ) { 1021 value=6.5; /* default beta value for Kaiser bessel windowing function */ 1022 artifact=GetImageArtifact(image,"filter:alpha"); /* FUTURE: depreciate */ 1023 if (artifact != (const char *) NULL) 1024 value=StringToDouble(artifact,(char **) NULL); 1025 artifact=GetImageArtifact(image,"filter:kaiser-beta"); 1026 if (artifact != (const char *) NULL) 1027 value=StringToDouble(artifact,(char **) NULL); 1028 artifact=GetImageArtifact(image,"filter:kaiser-alpha"); 1029 if (artifact != (const char *) NULL) 1030 value=StringToDouble(artifact,(char **) NULL)*MagickPI; 1031 /* Define coefficents for Kaiser Windowing Function */ 1032 resize_filter->coefficient[0]=value; /* alpha */ 1033 resize_filter->coefficient[1]=PerceptibleReciprocal(I0(value)); 1034 /* normalization */ 1035 } 1036 1037 /* Support Overrides */ 1038 artifact=GetImageArtifact(image,"filter:lobes"); 1039 if (artifact != (const char *) NULL) 1040 { 1041 ssize_t 1042 lobes; 1043 1044 lobes=(ssize_t) StringToLong(artifact); 1045 if (lobes < 1) 1046 lobes=1; 1047 resize_filter->support=(double) lobes; 1048 } 1049 if (resize_filter->filter == Jinc) 1050 { 1051 /* 1052 Convert a Jinc function lobes value to a real support value. 1053 */ 1054 if (resize_filter->support > 16) 1055 resize_filter->support=jinc_zeros[15]; /* largest entry in table */ 1056 else 1057 resize_filter->support=jinc_zeros[((long) resize_filter->support)-1]; 1058 /* 1059 Blur this filter so support is a integer value (lobes dependant). 1060 */ 1061 if (filter_type == LanczosRadiusFilter) 1062 resize_filter->blur*=floor(resize_filter->support)/ 1063 resize_filter->support; 1064 } 1065 /* 1066 Expert blur override. 1067 */ 1068 artifact=GetImageArtifact(image,"filter:blur"); 1069 if (artifact != (const char *) NULL) 1070 resize_filter->blur*=StringToDouble(artifact,(char **) NULL); 1071 if (resize_filter->blur < MagickEpsilon) 1072 resize_filter->blur=(double) MagickEpsilon; 1073 /* 1074 Expert override of the support setting. 1075 */ 1076 artifact=GetImageArtifact(image,"filter:support"); 1077 if (artifact != (const char *) NULL) 1078 resize_filter->support=fabs(StringToDouble(artifact,(char **) NULL)); 1079 /* 1080 Scale windowing function separately to the support 'clipping' window 1081 that calling operator is planning to actually use. (Expert override) 1082 */ 1083 resize_filter->window_support=resize_filter->support; /* default */ 1084 artifact=GetImageArtifact(image,"filter:win-support"); 1085 if (artifact != (const char *) NULL) 1086 resize_filter->window_support=fabs(StringToDouble(artifact,(char **) NULL)); 1087 /* 1088 Adjust window function scaling to match windowing support for weighting 1089 function. This avoids a division on every filter call. 1090 */ 1091 resize_filter->scale/=resize_filter->window_support; 1092 /* 1093 * Set Cubic Spline B,C values, calculate Cubic coefficients. 1094 */ 1095 B=0.0; 1096 C=0.0; 1097 if ((resize_filter->filter == CubicBC) || 1098 (resize_filter->window == CubicBC) ) 1099 { 1100 B=filters[filter_type].B; 1101 C=filters[filter_type].C; 1102 if (filters[window_type].function == CubicBC) 1103 { 1104 B=filters[window_type].B; 1105 C=filters[window_type].C; 1106 } 1107 artifact=GetImageArtifact(image,"filter:b"); 1108 if (artifact != (const char *) NULL) 1109 { 1110 B=StringToDouble(artifact,(char **) NULL); 1111 C=(1.0-B)/2.0; /* Calculate C to get a Keys cubic filter. */ 1112 artifact=GetImageArtifact(image,"filter:c"); /* user C override */ 1113 if (artifact != (const char *) NULL) 1114 C=StringToDouble(artifact,(char **) NULL); 1115 } 1116 else 1117 { 1118 artifact=GetImageArtifact(image,"filter:c"); 1119 if (artifact != (const char *) NULL) 1120 { 1121 C=StringToDouble(artifact,(char **) NULL); 1122 B=1.0-2.0*C; /* Calculate B to get a Keys cubic filter. */ 1123 } 1124 } 1125 { 1126 const double 1127 twoB = B+B; 1128 1129 /* 1130 Convert B,C values into Cubic Coefficents. See CubicBC(). 1131 */ 1132 resize_filter->coefficient[0]=1.0-(1.0/3.0)*B; 1133 resize_filter->coefficient[1]=-3.0+twoB+C; 1134 resize_filter->coefficient[2]=2.0-1.5*B-C; 1135 resize_filter->coefficient[3]=(4.0/3.0)*B+4.0*C; 1136 resize_filter->coefficient[4]=-8.0*C-twoB; 1137 resize_filter->coefficient[5]=B+5.0*C; 1138 resize_filter->coefficient[6]=(-1.0/6.0)*B-C; 1139 } 1140 } 1141 1142 /* 1143 Expert Option Request for verbose details of the resulting filter. 1144 */ 1145 #if defined(MAGICKCORE_OPENMP_SUPPORT) 1146 #pragma omp master 1147 { 1148 #endif 1149 if (IsStringTrue(GetImageArtifact(image,"filter:verbose")) != MagickFalse) 1150 { 1151 double 1152 support, 1153 x; 1154 1155 /* 1156 Set the weighting function properly when the weighting function 1157 may not exactly match the filter of the same name. EG: a Point 1158 filter is really uses a Box weighting function with a different 1159 support than is typically used. 1160 */ 1161 if (resize_filter->filter == Box) filter_type=BoxFilter; 1162 if (resize_filter->filter == Sinc) filter_type=SincFilter; 1163 if (resize_filter->filter == SincFast) filter_type=SincFastFilter; 1164 if (resize_filter->filter == Jinc) filter_type=JincFilter; 1165 if (resize_filter->filter == CubicBC) filter_type=CubicFilter; 1166 if (resize_filter->window == Box) window_type=BoxFilter; 1167 if (resize_filter->window == Sinc) window_type=SincFilter; 1168 if (resize_filter->window == SincFast) window_type=SincFastFilter; 1169 if (resize_filter->window == Jinc) window_type=JincFilter; 1170 if (resize_filter->window == CubicBC) window_type=CubicFilter; 1171 /* 1172 Report Filter Details. 1173 */ 1174 support=GetResizeFilterSupport(resize_filter); /* practical_support */ 1175 (void) FormatLocaleFile(stdout, 1176 "# Resampling Filter (for graphing)\n#\n"); 1177 (void) FormatLocaleFile(stdout,"# filter = %s\n", 1178 CommandOptionToMnemonic(MagickFilterOptions,filter_type)); 1179 (void) FormatLocaleFile(stdout,"# window = %s\n", 1180 CommandOptionToMnemonic(MagickFilterOptions,window_type)); 1181 (void) FormatLocaleFile(stdout,"# support = %.*g\n", 1182 GetMagickPrecision(),(double) resize_filter->support); 1183 (void) FormatLocaleFile(stdout,"# window-support = %.*g\n", 1184 GetMagickPrecision(),(double) resize_filter->window_support); 1185 (void) FormatLocaleFile(stdout,"# scale-blur = %.*g\n", 1186 GetMagickPrecision(),(double)resize_filter->blur); 1187 if ((filter_type == GaussianFilter) || (window_type == GaussianFilter)) 1188 (void) FormatLocaleFile(stdout,"# gaussian-sigma = %.*g\n", 1189 GetMagickPrecision(),(double)resize_filter->coefficient[0]); 1190 if ( filter_type == KaiserFilter || window_type == KaiserFilter ) 1191 (void) FormatLocaleFile(stdout,"# kaiser-beta = %.*g\n", 1192 GetMagickPrecision(),(double)resize_filter->coefficient[0]); 1193 (void) FormatLocaleFile(stdout,"# practical-support = %.*g\n", 1194 GetMagickPrecision(), (double)support); 1195 if ( filter_type == CubicFilter || window_type == CubicFilter ) 1196 (void) FormatLocaleFile(stdout,"# B,C = %.*g,%.*g\n", 1197 GetMagickPrecision(),(double)B, GetMagickPrecision(),(double)C); 1198 (void) FormatLocaleFile(stdout,"\n"); 1199 /* 1200 Output values of resulting filter graph -- for graphing filter result. 1201 */ 1202 for (x=0.0; x <= support; x+=0.01f) 1203 (void) FormatLocaleFile(stdout,"%5.2lf\t%.*g\n",x, 1204 GetMagickPrecision(),(double) 1205 GetResizeFilterWeight(resize_filter,x)); 1206 /* 1207 A final value so gnuplot can graph the 'stop' properly. 1208 */ 1209 (void) FormatLocaleFile(stdout,"%5.2lf\t%.*g\n",support, 1210 GetMagickPrecision(),0.0); 1211 } 1212 /* Output the above once only for each image - remove setting */ 1213 (void) DeleteImageArtifact((Image *) image,"filter:verbose"); 1214 #if defined(MAGICKCORE_OPENMP_SUPPORT) 1215 } 1216 #endif 1217 return(resize_filter); 1218 } 1219 1220 /* 1222 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 1223 % % 1224 % % 1225 % % 1226 % A d a p t i v e R e s i z e I m a g e % 1227 % % 1228 % % 1229 % % 1230 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 1231 % 1232 % AdaptiveResizeImage() adaptively resize image with pixel resampling. 1233 % 1234 % This is shortcut function for a fast interpolative resize using mesh 1235 % interpolation. It works well for small resizes of less than +/- 50% 1236 % of the original image size. For larger resizing on images a full 1237 % filtered and slower resize function should be used instead. 1238 % 1239 % The format of the AdaptiveResizeImage method is: 1240 % 1241 % Image *AdaptiveResizeImage(const Image *image,const size_t columns, 1242 % const size_t rows,ExceptionInfo *exception) 1243 % 1244 % A description of each parameter follows: 1245 % 1246 % o image: the image. 1247 % 1248 % o columns: the number of columns in the resized image. 1249 % 1250 % o rows: the number of rows in the resized image. 1251 % 1252 % o exception: return any errors or warnings in this structure. 1253 % 1254 */ 1255 MagickExport Image *AdaptiveResizeImage(const Image *image, 1256 const size_t columns,const size_t rows,ExceptionInfo *exception) 1257 { 1258 Image 1259 *resize_image; 1260 1261 resize_image=InterpolativeResizeImage(image,columns,rows,MeshInterpolatePixel, 1262 exception); 1263 return(resize_image); 1264 } 1265 1266 /* 1268 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 1269 % % 1270 % % 1271 % % 1272 + B e s s e l O r d e r O n e % 1273 % % 1274 % % 1275 % % 1276 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 1277 % 1278 % BesselOrderOne() computes the Bessel function of x of the first kind of 1279 % order 0. This is used to create the Jinc() filter function below. 1280 % 1281 % Reduce x to |x| since j1(x)= -j1(-x), and for x in (0,8] 1282 % 1283 % j1(x) = x*j1(x); 1284 % 1285 % For x in (8,inf) 1286 % 1287 % j1(x) = sqrt(2/(pi*x))*(p1(x)*cos(x1)-q1(x)*sin(x1)) 1288 % 1289 % where x1 = x-3*pi/4. Compute sin(x1) and cos(x1) as follow: 1290 % 1291 % cos(x1) = cos(x)cos(3pi/4)+sin(x)sin(3pi/4) 1292 % = 1/sqrt(2) * (sin(x) - cos(x)) 1293 % sin(x1) = sin(x)cos(3pi/4)-cos(x)sin(3pi/4) 1294 % = -1/sqrt(2) * (sin(x) + cos(x)) 1295 % 1296 % The format of the BesselOrderOne method is: 1297 % 1298 % double BesselOrderOne(double x) 1299 % 1300 % A description of each parameter follows: 1301 % 1302 % o x: double value. 1303 % 1304 */ 1305 1306 #undef I0 1307 static double I0(double x) 1308 { 1309 double 1310 sum, 1311 t, 1312 y; 1313 1314 register ssize_t 1315 i; 1316 1317 /* 1318 Zeroth order Bessel function of the first kind. 1319 */ 1320 sum=1.0; 1321 y=x*x/4.0; 1322 t=y; 1323 for (i=2; t > MagickEpsilon; i++) 1324 { 1325 sum+=t; 1326 t*=y/((double) i*i); 1327 } 1328 return(sum); 1329 } 1330 1331 #undef J1 1332 static double J1(double x) 1333 { 1334 double 1335 p, 1336 q; 1337 1338 register ssize_t 1339 i; 1340 1341 static const double 1342 Pone[] = 1343 { 1344 0.581199354001606143928050809e+21, 1345 -0.6672106568924916298020941484e+20, 1346 0.2316433580634002297931815435e+19, 1347 -0.3588817569910106050743641413e+17, 1348 0.2908795263834775409737601689e+15, 1349 -0.1322983480332126453125473247e+13, 1350 0.3413234182301700539091292655e+10, 1351 -0.4695753530642995859767162166e+7, 1352 0.270112271089232341485679099e+4 1353 }, 1354 Qone[] = 1355 { 1356 0.11623987080032122878585294e+22, 1357 0.1185770712190320999837113348e+20, 1358 0.6092061398917521746105196863e+17, 1359 0.2081661221307607351240184229e+15, 1360 0.5243710262167649715406728642e+12, 1361 0.1013863514358673989967045588e+10, 1362 0.1501793594998585505921097578e+7, 1363 0.1606931573481487801970916749e+4, 1364 0.1e+1 1365 }; 1366 1367 p=Pone[8]; 1368 q=Qone[8]; 1369 for (i=7; i >= 0; i--) 1370 { 1371 p=p*x*x+Pone[i]; 1372 q=q*x*x+Qone[i]; 1373 } 1374 return(p/q); 1375 } 1376 1377 #undef P1 1378 static double P1(double x) 1379 { 1380 double 1381 p, 1382 q; 1383 1384 register ssize_t 1385 i; 1386 1387 static const double 1388 Pone[] = 1389 { 1390 0.352246649133679798341724373e+5, 1391 0.62758845247161281269005675e+5, 1392 0.313539631109159574238669888e+5, 1393 0.49854832060594338434500455e+4, 1394 0.2111529182853962382105718e+3, 1395 0.12571716929145341558495e+1 1396 }, 1397 Qone[] = 1398 { 1399 0.352246649133679798068390431e+5, 1400 0.626943469593560511888833731e+5, 1401 0.312404063819041039923015703e+5, 1402 0.4930396490181088979386097e+4, 1403 0.2030775189134759322293574e+3, 1404 0.1e+1 1405 }; 1406 1407 p=Pone[5]; 1408 q=Qone[5]; 1409 for (i=4; i >= 0; i--) 1410 { 1411 p=p*(8.0/x)*(8.0/x)+Pone[i]; 1412 q=q*(8.0/x)*(8.0/x)+Qone[i]; 1413 } 1414 return(p/q); 1415 } 1416 1417 #undef Q1 1418 static double Q1(double x) 1419 { 1420 double 1421 p, 1422 q; 1423 1424 register ssize_t 1425 i; 1426 1427 static const double 1428 Pone[] = 1429 { 1430 0.3511751914303552822533318e+3, 1431 0.7210391804904475039280863e+3, 1432 0.4259873011654442389886993e+3, 1433 0.831898957673850827325226e+2, 1434 0.45681716295512267064405e+1, 1435 0.3532840052740123642735e-1 1436 }, 1437 Qone[] = 1438 { 1439 0.74917374171809127714519505e+4, 1440 0.154141773392650970499848051e+5, 1441 0.91522317015169922705904727e+4, 1442 0.18111867005523513506724158e+4, 1443 0.1038187585462133728776636e+3, 1444 0.1e+1 1445 }; 1446 1447 p=Pone[5]; 1448 q=Qone[5]; 1449 for (i=4; i >= 0; i--) 1450 { 1451 p=p*(8.0/x)*(8.0/x)+Pone[i]; 1452 q=q*(8.0/x)*(8.0/x)+Qone[i]; 1453 } 1454 return(p/q); 1455 } 1456 1457 static double BesselOrderOne(double x) 1458 { 1459 double 1460 p, 1461 q; 1462 1463 if (x == 0.0) 1464 return(0.0); 1465 p=x; 1466 if (x < 0.0) 1467 x=(-x); 1468 if (x < 8.0) 1469 return(p*J1(x)); 1470 q=sqrt((double) (2.0/(MagickPI*x)))*(P1(x)*(1.0/sqrt(2.0)*(sin((double) x)- 1471 cos((double) x)))-8.0/x*Q1(x)*(-1.0/sqrt(2.0)*(sin((double) x)+ 1472 cos((double) x)))); 1473 if (p < 0.0) 1474 q=(-q); 1475 return(q); 1476 } 1477 1478 /* 1480 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 1481 % % 1482 % % 1483 % % 1484 + D e s t r o y R e s i z e F i l t e r % 1485 % % 1486 % % 1487 % % 1488 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 1489 % 1490 % DestroyResizeFilter() destroy the resize filter. 1491 % 1492 % The format of the DestroyResizeFilter method is: 1493 % 1494 % ResizeFilter *DestroyResizeFilter(ResizeFilter *resize_filter) 1495 % 1496 % A description of each parameter follows: 1497 % 1498 % o resize_filter: the resize filter. 1499 % 1500 */ 1501 MagickPrivate ResizeFilter *DestroyResizeFilter(ResizeFilter *resize_filter) 1502 { 1503 assert(resize_filter != (ResizeFilter *) NULL); 1504 assert(resize_filter->signature == MagickCoreSignature); 1505 resize_filter->signature=(~MagickCoreSignature); 1506 resize_filter=(ResizeFilter *) RelinquishMagickMemory(resize_filter); 1507 return(resize_filter); 1508 } 1509 1510 /* 1512 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 1513 % % 1514 % % 1515 % % 1516 + G e t R e s i z e F i l t e r S u p p o r t % 1517 % % 1518 % % 1519 % % 1520 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 1521 % 1522 % GetResizeFilterSupport() return the current support window size for this 1523 % filter. Note that this may have been enlarged by filter:blur factor. 1524 % 1525 % The format of the GetResizeFilterSupport method is: 1526 % 1527 % double GetResizeFilterSupport(const ResizeFilter *resize_filter) 1528 % 1529 % A description of each parameter follows: 1530 % 1531 % o filter: Image filter to use. 1532 % 1533 */ 1534 1535 MagickPrivate double *GetResizeFilterCoefficient( 1536 const ResizeFilter *resize_filter) 1537 { 1538 assert(resize_filter != (ResizeFilter *) NULL); 1539 assert(resize_filter->signature == MagickCoreSignature); 1540 return((double *) resize_filter->coefficient); 1541 } 1542 1543 MagickPrivate double GetResizeFilterBlur(const ResizeFilter *resize_filter) 1544 { 1545 assert(resize_filter != (ResizeFilter *) NULL); 1546 assert(resize_filter->signature == MagickCoreSignature); 1547 return(resize_filter->blur); 1548 } 1549 1550 MagickPrivate double GetResizeFilterScale(const ResizeFilter *resize_filter) 1551 { 1552 assert(resize_filter != (ResizeFilter *) NULL); 1553 assert(resize_filter->signature == MagickCoreSignature); 1554 return(resize_filter->scale); 1555 } 1556 1557 MagickPrivate double GetResizeFilterWindowSupport( 1558 const ResizeFilter *resize_filter) 1559 { 1560 assert(resize_filter != (ResizeFilter *) NULL); 1561 assert(resize_filter->signature == MagickCoreSignature); 1562 return(resize_filter->window_support); 1563 } 1564 1565 MagickPrivate ResizeWeightingFunctionType GetResizeFilterWeightingType( 1566 const ResizeFilter *resize_filter) 1567 { 1568 assert(resize_filter != (ResizeFilter *) NULL); 1569 assert(resize_filter->signature == MagickCoreSignature); 1570 return(resize_filter->filterWeightingType); 1571 } 1572 1573 MagickPrivate ResizeWeightingFunctionType GetResizeFilterWindowWeightingType( 1574 const ResizeFilter *resize_filter) 1575 { 1576 assert(resize_filter != (ResizeFilter *) NULL); 1577 assert(resize_filter->signature == MagickCoreSignature); 1578 return(resize_filter->windowWeightingType); 1579 } 1580 1581 MagickPrivate double GetResizeFilterSupport(const ResizeFilter *resize_filter) 1582 { 1583 assert(resize_filter != (ResizeFilter *) NULL); 1584 assert(resize_filter->signature == MagickCoreSignature); 1585 return(resize_filter->support*resize_filter->blur); 1586 } 1587 1588 /* 1590 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 1591 % % 1592 % % 1593 % % 1594 + G e t R e s i z e F i l t e r W e i g h t % 1595 % % 1596 % % 1597 % % 1598 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 1599 % 1600 % GetResizeFilterWeight evaluates the specified resize filter at the point x 1601 % which usally lies between zero and the filters current 'support' and 1602 % returns the weight of the filter function at that point. 1603 % 1604 % The format of the GetResizeFilterWeight method is: 1605 % 1606 % double GetResizeFilterWeight(const ResizeFilter *resize_filter, 1607 % const double x) 1608 % 1609 % A description of each parameter follows: 1610 % 1611 % o filter: the filter type. 1612 % 1613 % o x: the point. 1614 % 1615 */ 1616 MagickPrivate double GetResizeFilterWeight(const ResizeFilter *resize_filter, 1617 const double x) 1618 { 1619 double 1620 scale, 1621 weight, 1622 x_blur; 1623 1624 /* 1625 Windowing function - scale the weighting filter by this amount. 1626 */ 1627 assert(resize_filter != (ResizeFilter *) NULL); 1628 assert(resize_filter->signature == MagickCoreSignature); 1629 x_blur=fabs((double) x)/resize_filter->blur; /* X offset with blur scaling */ 1630 if ((resize_filter->window_support < MagickEpsilon) || 1631 (resize_filter->window == Box)) 1632 scale=1.0; /* Point or Box Filter -- avoid division by zero */ 1633 else 1634 { 1635 scale=resize_filter->scale; 1636 scale=resize_filter->window(x_blur*scale,resize_filter); 1637 } 1638 weight=scale*resize_filter->filter(x_blur,resize_filter); 1639 return(weight); 1640 } 1641 1642 /* 1644 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 1645 % % 1646 % % 1647 % % 1648 % I n t e r p o l a t i v e R e s i z e I m a g e % 1649 % % 1650 % % 1651 % % 1652 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 1653 % 1654 % InterpolativeResizeImage() resizes an image using the specified 1655 % interpolation method. 1656 % 1657 % The format of the InterpolativeResizeImage method is: 1658 % 1659 % Image *InterpolativeResizeImage(const Image *image,const size_t columns, 1660 % const size_t rows,const PixelInterpolateMethod method, 1661 % ExceptionInfo *exception) 1662 % 1663 % A description of each parameter follows: 1664 % 1665 % o image: the image. 1666 % 1667 % o columns: the number of columns in the resized image. 1668 % 1669 % o rows: the number of rows in the resized image. 1670 % 1671 % o method: the pixel interpolation method. 1672 % 1673 % o exception: return any errors or warnings in this structure. 1674 % 1675 */ 1676 MagickExport Image *InterpolativeResizeImage(const Image *image, 1677 const size_t columns,const size_t rows,const PixelInterpolateMethod method, 1678 ExceptionInfo *exception) 1679 { 1680 #define InterpolativeResizeImageTag "Resize/Image" 1681 1682 CacheView 1683 *image_view, 1684 *resize_view; 1685 1686 Image 1687 *resize_image; 1688 1689 MagickBooleanType 1690 status; 1691 1692 MagickOffsetType 1693 progress; 1694 1695 PointInfo 1696 scale; 1697 1698 ssize_t 1699 y; 1700 1701 /* 1702 Interpolatively resize image. 1703 */ 1704 assert(image != (const Image *) NULL); 1705 assert(image->signature == MagickCoreSignature); 1706 if (image->debug != MagickFalse) 1707 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename); 1708 assert(exception != (ExceptionInfo *) NULL); 1709 assert(exception->signature == MagickCoreSignature); 1710 if ((columns == 0) || (rows == 0)) 1711 ThrowImageException(ImageError,"NegativeOrZeroImageSize"); 1712 if ((columns == image->columns) && (rows == image->rows)) 1713 return(CloneImage(image,0,0,MagickTrue,exception)); 1714 resize_image=CloneImage(image,columns,rows,MagickTrue,exception); 1715 if (resize_image == (Image *) NULL) 1716 return((Image *) NULL); 1717 if (SetImageStorageClass(resize_image,DirectClass,exception) == MagickFalse) 1718 { 1719 resize_image=DestroyImage(resize_image); 1720 return((Image *) NULL); 1721 } 1722 status=MagickTrue; 1723 progress=0; 1724 image_view=AcquireVirtualCacheView(image,exception); 1725 resize_view=AcquireAuthenticCacheView(resize_image,exception); 1726 scale.x=(double) image->columns/resize_image->columns; 1727 scale.y=(double) image->rows/resize_image->rows; 1728 #if defined(MAGICKCORE_OPENMP_SUPPORT) 1729 #pragma omp parallel for schedule(static,4) shared(progress,status) \ 1730 magick_threads(image,resize_image,resize_image->rows,1) 1731 #endif 1732 for (y=0; y < (ssize_t) resize_image->rows; y++) 1733 { 1734 PointInfo 1735 offset; 1736 1737 register Quantum 1738 *magick_restrict q; 1739 1740 register ssize_t 1741 x; 1742 1743 if (status == MagickFalse) 1744 continue; 1745 q=QueueCacheViewAuthenticPixels(resize_view,0,y,resize_image->columns,1, 1746 exception); 1747 if (q == (Quantum *) NULL) 1748 continue; 1749 offset.y=((double) y+0.5)*scale.y-0.5; 1750 for (x=0; x < (ssize_t) resize_image->columns; x++) 1751 { 1752 register ssize_t 1753 i; 1754 1755 if (GetPixelReadMask(resize_image,q) == 0) 1756 { 1757 q+=GetPixelChannels(resize_image); 1758 continue; 1759 } 1760 for (i=0; i < (ssize_t) GetPixelChannels(image); i++) 1761 { 1762 PixelChannel 1763 channel; 1764 1765 PixelTrait 1766 resize_traits, 1767 traits; 1768 1769 channel=GetPixelChannelChannel(image,i); 1770 traits=GetPixelChannelTraits(image,channel); 1771 resize_traits=GetPixelChannelTraits(resize_image,channel); 1772 if ((traits == UndefinedPixelTrait) || 1773 (resize_traits == UndefinedPixelTrait)) 1774 continue; 1775 offset.x=((double) x+0.5)*scale.x-0.5; 1776 status=InterpolatePixelChannels(image,image_view,resize_image,method, 1777 offset.x,offset.y,q,exception); 1778 } 1779 q+=GetPixelChannels(resize_image); 1780 } 1781 if (SyncCacheViewAuthenticPixels(resize_view,exception) == MagickFalse) 1782 status=MagickFalse; 1783 if (image->progress_monitor != (MagickProgressMonitor) NULL) 1784 { 1785 MagickBooleanType 1786 proceed; 1787 1788 #if defined(MAGICKCORE_OPENMP_SUPPORT) 1789 #pragma omp critical (MagickCore_InterpolativeResizeImage) 1790 #endif 1791 proceed=SetImageProgress(image,InterpolativeResizeImageTag,progress++, 1792 image->rows); 1793 if (proceed == MagickFalse) 1794 status=MagickFalse; 1795 } 1796 } 1797 resize_view=DestroyCacheView(resize_view); 1798 image_view=DestroyCacheView(image_view); 1799 if (status == MagickFalse) 1800 resize_image=DestroyImage(resize_image); 1801 return(resize_image); 1802 } 1803 #if defined(MAGICKCORE_LQR_DELEGATE) 1804 1805 /* 1807 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 1808 % % 1809 % % 1810 % % 1811 % L i q u i d R e s c a l e I m a g e % 1812 % % 1813 % % 1814 % % 1815 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 1816 % 1817 % LiquidRescaleImage() rescales image with seam carving. 1818 % 1819 % The format of the LiquidRescaleImage method is: 1820 % 1821 % Image *LiquidRescaleImage(const Image *image,const size_t columns, 1822 % const size_t rows,const double delta_x,const double rigidity, 1823 % ExceptionInfo *exception) 1824 % 1825 % A description of each parameter follows: 1826 % 1827 % o image: the image. 1828 % 1829 % o columns: the number of columns in the rescaled image. 1830 % 1831 % o rows: the number of rows in the rescaled image. 1832 % 1833 % o delta_x: maximum seam transversal step (0 means straight seams). 1834 % 1835 % o rigidity: introduce a bias for non-straight seams (typically 0). 1836 % 1837 % o exception: return any errors or warnings in this structure. 1838 % 1839 */ 1840 MagickExport Image *LiquidRescaleImage(const Image *image,const size_t columns, 1841 const size_t rows,const double delta_x,const double rigidity, 1842 ExceptionInfo *exception) 1843 { 1844 #define LiquidRescaleImageTag "Rescale/Image" 1845 1846 CacheView 1847 *image_view, 1848 *rescale_view; 1849 1850 gfloat 1851 *packet, 1852 *pixels; 1853 1854 Image 1855 *rescale_image; 1856 1857 int 1858 x_offset, 1859 y_offset; 1860 1861 LqrCarver 1862 *carver; 1863 1864 LqrRetVal 1865 lqr_status; 1866 1867 MagickBooleanType 1868 status; 1869 1870 MemoryInfo 1871 *pixel_info; 1872 1873 register gfloat 1874 *q; 1875 1876 ssize_t 1877 y; 1878 1879 /* 1880 Liquid rescale image. 1881 */ 1882 assert(image != (const Image *) NULL); 1883 assert(image->signature == MagickCoreSignature); 1884 if (image->debug != MagickFalse) 1885 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename); 1886 assert(exception != (ExceptionInfo *) NULL); 1887 assert(exception->signature == MagickCoreSignature); 1888 if ((columns == 0) || (rows == 0)) 1889 ThrowImageException(ImageError,"NegativeOrZeroImageSize"); 1890 if ((columns == image->columns) && (rows == image->rows)) 1891 return(CloneImage(image,0,0,MagickTrue,exception)); 1892 if ((columns <= 2) || (rows <= 2)) 1893 return(ResizeImage(image,columns,rows,image->filter,exception)); 1894 pixel_info=AcquireVirtualMemory(image->columns,image->rows* 1895 GetPixelChannels(image)*sizeof(*pixels)); 1896 if (pixel_info == (MemoryInfo *) NULL) 1897 return((Image *) NULL); 1898 pixels=(gfloat *) GetVirtualMemoryBlob(pixel_info); 1899 status=MagickTrue; 1900 q=pixels; 1901 image_view=AcquireVirtualCacheView(image,exception); 1902 for (y=0; y < (ssize_t) image->rows; y++) 1903 { 1904 register const Quantum 1905 *magick_restrict p; 1906 1907 register ssize_t 1908 x; 1909 1910 if (status == MagickFalse) 1911 continue; 1912 p=GetCacheViewVirtualPixels(image_view,0,y,image->columns,1,exception); 1913 if (p == (const Quantum *) NULL) 1914 { 1915 status=MagickFalse; 1916 continue; 1917 } 1918 for (x=0; x < (ssize_t) image->columns; x++) 1919 { 1920 register ssize_t 1921 i; 1922 1923 for (i=0; i < (ssize_t) GetPixelChannels(image); i++) 1924 *q++=QuantumScale*p[i]; 1925 p+=GetPixelChannels(image); 1926 } 1927 } 1928 image_view=DestroyCacheView(image_view); 1929 carver=lqr_carver_new_ext(pixels,(int) image->columns,(int) image->rows, 1930 (int) GetPixelChannels(image),LQR_COLDEPTH_32F); 1931 if (carver == (LqrCarver *) NULL) 1932 { 1933 pixel_info=RelinquishVirtualMemory(pixel_info); 1934 ThrowImageException(ResourceLimitError,"MemoryAllocationFailed"); 1935 } 1936 lqr_carver_set_preserve_input_image(carver); 1937 lqr_status=lqr_carver_init(carver,(int) delta_x,rigidity); 1938 lqr_status=lqr_carver_resize(carver,(int) columns,(int) rows); 1939 (void) lqr_status; 1940 rescale_image=CloneImage(image,lqr_carver_get_width(carver), 1941 lqr_carver_get_height(carver),MagickTrue,exception); 1942 if (rescale_image == (Image *) NULL) 1943 { 1944 pixel_info=RelinquishVirtualMemory(pixel_info); 1945 return((Image *) NULL); 1946 } 1947 if (SetImageStorageClass(rescale_image,DirectClass,exception) == MagickFalse) 1948 { 1949 pixel_info=RelinquishVirtualMemory(pixel_info); 1950 rescale_image=DestroyImage(rescale_image); 1951 return((Image *) NULL); 1952 } 1953 rescale_view=AcquireAuthenticCacheView(rescale_image,exception); 1954 (void) lqr_carver_scan_reset(carver); 1955 while (lqr_carver_scan_ext(carver,&x_offset,&y_offset,(void **) &packet) != 0) 1956 { 1957 register Quantum 1958 *magick_restrict q; 1959 1960 register ssize_t 1961 i; 1962 1963 q=QueueCacheViewAuthenticPixels(rescale_view,x_offset,y_offset,1,1, 1964 exception); 1965 if (q == (Quantum *) NULL) 1966 break; 1967 for (i=0; i < (ssize_t) GetPixelChannels(image); i++) 1968 { 1969 PixelChannel 1970 channel; 1971 1972 PixelTrait 1973 rescale_traits, 1974 traits; 1975 1976 channel=GetPixelChannelChannel(image,i); 1977 traits=GetPixelChannelTraits(image,channel); 1978 rescale_traits=GetPixelChannelTraits(rescale_image,channel); 1979 if ((traits == UndefinedPixelTrait) || 1980 (rescale_traits == UndefinedPixelTrait)) 1981 continue; 1982 SetPixelChannel(rescale_image,channel,ClampToQuantum(QuantumRange* 1983 packet[i]),q); 1984 } 1985 if (SyncCacheViewAuthenticPixels(rescale_view,exception) == MagickFalse) 1986 break; 1987 } 1988 rescale_view=DestroyCacheView(rescale_view); 1989 pixel_info=RelinquishVirtualMemory(pixel_info); 1990 lqr_carver_destroy(carver); 1991 return(rescale_image); 1992 } 1993 #else 1994 MagickExport Image *LiquidRescaleImage(const Image *image, 1995 const size_t magick_unused(columns),const size_t magick_unused(rows), 1996 const double magick_unused(delta_x),const double magick_unused(rigidity), 1997 ExceptionInfo *exception) 1998 { 1999 assert(image != (const Image *) NULL); 2000 assert(image->signature == MagickCoreSignature); 2001 if (image->debug != MagickFalse) 2002 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename); 2003 assert(exception != (ExceptionInfo *) NULL); 2004 assert(exception->signature == MagickCoreSignature); 2005 (void) ThrowMagickException(exception,GetMagickModule(),MissingDelegateError, 2006 "DelegateLibrarySupportNotBuiltIn","'%s' (LQR)",image->filename); 2007 return((Image *) NULL); 2008 } 2009 #endif 2010 2011 /* 2013 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 2014 % % 2015 % % 2016 % % 2017 % M a g n i f y I m a g e % 2018 % % 2019 % % 2020 % % 2021 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 2022 % 2023 % MagnifyImage() doubles the size of the image with a pixel art scaling 2024 % algorithm. 2025 % 2026 % The format of the MagnifyImage method is: 2027 % 2028 % Image *MagnifyImage(const Image *image,ExceptionInfo *exception) 2029 % 2030 % A description of each parameter follows: 2031 % 2032 % o image: the image. 2033 % 2034 % o exception: return any errors or warnings in this structure. 2035 % 2036 */ 2037 MagickExport Image *MagnifyImage(const Image *image,ExceptionInfo *exception) 2038 { 2039 #define MagnifyImageTag "Magnify/Image" 2040 2041 CacheView 2042 *image_view, 2043 *magnify_view; 2044 2045 Image 2046 *magnify_image; 2047 2048 MagickBooleanType 2049 status; 2050 2051 MagickOffsetType 2052 progress; 2053 2054 ssize_t 2055 y; 2056 2057 /* 2058 Initialize magnified image attributes. 2059 */ 2060 assert(image != (const Image *) NULL); 2061 assert(image->signature == MagickCoreSignature); 2062 if (image->debug != MagickFalse) 2063 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename); 2064 assert(exception != (ExceptionInfo *) NULL); 2065 assert(exception->signature == MagickCoreSignature); 2066 magnify_image=CloneImage(image,2*image->columns,2*image->rows,MagickTrue, 2067 exception); 2068 if (magnify_image == (Image *) NULL) 2069 return((Image *) NULL); 2070 /* 2071 Magnify image. 2072 */ 2073 status=MagickTrue; 2074 progress=0; 2075 image_view=AcquireVirtualCacheView(image,exception); 2076 magnify_view=AcquireAuthenticCacheView(magnify_image,exception); 2077 #if defined(MAGICKCORE_OPENMP_SUPPORT) 2078 #pragma omp parallel for schedule(static,4) shared(progress,status) \ 2079 magick_threads(image,magnify_image,image->rows,1) 2080 #endif 2081 for (y=0; y < (ssize_t) image->rows; y++) 2082 { 2083 register Quantum 2084 *magick_restrict q; 2085 2086 register ssize_t 2087 x; 2088 2089 if (status == MagickFalse) 2090 continue; 2091 q=QueueCacheViewAuthenticPixels(magnify_view,0,2*y,magnify_image->columns,2, 2092 exception); 2093 if (q == (Quantum *) NULL) 2094 { 2095 status=MagickFalse; 2096 continue; 2097 } 2098 /* 2099 Magnify this row of pixels. 2100 */ 2101 for (x=0; x < (ssize_t) image->columns; x++) 2102 { 2103 MagickRealType 2104 intensity[9]; 2105 2106 register const Quantum 2107 *magick_restrict p; 2108 2109 register Quantum 2110 *magick_restrict r; 2111 2112 register ssize_t 2113 i; 2114 2115 size_t 2116 channels; 2117 2118 p=GetCacheViewVirtualPixels(image_view,x-1,y-1,3,3,exception); 2119 if (p == (const Quantum *) NULL) 2120 { 2121 status=MagickFalse; 2122 continue; 2123 } 2124 channels=GetPixelChannels(image); 2125 for (i=0; i < 9; i++) 2126 intensity[i]=GetPixelIntensity(image,p+i*channels); 2127 r=q; 2128 if ((fabs(intensity[1]-intensity[7]) < MagickEpsilon) || 2129 (fabs(intensity[3]-intensity[5]) < MagickEpsilon)) 2130 { 2131 /* 2132 Clone center pixel. 2133 */ 2134 for (i=0; i < (ssize_t) channels; i++) 2135 r[i]=p[4*channels+i]; 2136 r+=GetPixelChannels(magnify_image); 2137 for (i=0; i < (ssize_t) channels; i++) 2138 r[i]=p[4*channels+i]; 2139 r+=GetPixelChannels(magnify_image)*(magnify_image->columns-1); 2140 for (i=0; i < (ssize_t) channels; i++) 2141 r[i]=p[4*channels+i]; 2142 r+=GetPixelChannels(magnify_image); 2143 for (i=0; i < (ssize_t) channels; i++) 2144 r[i]=p[4*channels+i]; 2145 } 2146 else 2147 { 2148 /* 2149 Selectively clone pixel. 2150 */ 2151 if (fabs(intensity[1]-intensity[3]) < MagickEpsilon) 2152 for (i=0; i < (ssize_t) channels; i++) 2153 r[i]=p[3*channels+i]; 2154 else 2155 for (i=0; i < (ssize_t) channels; i++) 2156 r[i]=p[4*channels+i]; 2157 r+=GetPixelChannels(magnify_image); 2158 if (fabs(intensity[1]-intensity[5]) < MagickEpsilon) 2159 for (i=0; i < (ssize_t) channels; i++) 2160 r[i]=p[5*channels+i]; 2161 else 2162 for (i=0; i < (ssize_t) channels; i++) 2163 r[i]=p[4*channels+i]; 2164 r+=GetPixelChannels(magnify_image)*(magnify_image->columns-1); 2165 if (fabs(intensity[3]-intensity[7]) < MagickEpsilon) 2166 for (i=0; i < (ssize_t) channels; i++) 2167 r[i]=p[3*channels+i]; 2168 else 2169 for (i=0; i < (ssize_t) channels; i++) 2170 r[i]=p[4*channels+i]; 2171 r+=GetPixelChannels(magnify_image); 2172 if (fabs(intensity[5]-intensity[7]) < MagickEpsilon) 2173 for (i=0; i < (ssize_t) channels; i++) 2174 r[i]=p[5*channels+i]; 2175 else 2176 for (i=0; i < (ssize_t) channels; i++) 2177 r[i]=p[4*channels+i]; 2178 } 2179 q+=2*GetPixelChannels(magnify_image); 2180 } 2181 if (SyncCacheViewAuthenticPixels(magnify_view,exception) == MagickFalse) 2182 status=MagickFalse; 2183 if (image->progress_monitor != (MagickProgressMonitor) NULL) 2184 { 2185 MagickBooleanType 2186 proceed; 2187 2188 #if defined(MAGICKCORE_OPENMP_SUPPORT) 2189 #pragma omp critical (MagickCore_MagnifyImage) 2190 #endif 2191 proceed=SetImageProgress(image,MagnifyImageTag,progress++,image->rows); 2192 if (proceed == MagickFalse) 2193 status=MagickFalse; 2194 } 2195 } 2196 magnify_view=DestroyCacheView(magnify_view); 2197 image_view=DestroyCacheView(image_view); 2198 if (status == MagickFalse) 2199 magnify_image=DestroyImage(magnify_image); 2200 return(magnify_image); 2201 } 2202 2203 /* 2205 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 2206 % % 2207 % % 2208 % % 2209 % M i n i f y I m a g e % 2210 % % 2211 % % 2212 % % 2213 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 2214 % 2215 % MinifyImage() is a convenience method that scales an image proportionally to 2216 % half its size. 2217 % 2218 % The format of the MinifyImage method is: 2219 % 2220 % Image *MinifyImage(const Image *image,ExceptionInfo *exception) 2221 % 2222 % A description of each parameter follows: 2223 % 2224 % o image: the image. 2225 % 2226 % o exception: return any errors or warnings in this structure. 2227 % 2228 */ 2229 MagickExport Image *MinifyImage(const Image *image,ExceptionInfo *exception) 2230 { 2231 Image 2232 *minify_image; 2233 2234 assert(image != (Image *) NULL); 2235 assert(image->signature == MagickCoreSignature); 2236 if (image->debug != MagickFalse) 2237 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename); 2238 assert(exception != (ExceptionInfo *) NULL); 2239 assert(exception->signature == MagickCoreSignature); 2240 minify_image=ResizeImage(image,image->columns/2,image->rows/2,SplineFilter, 2241 exception); 2242 return(minify_image); 2243 } 2244 2245 /* 2247 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 2248 % % 2249 % % 2250 % % 2251 % R e s a m p l e I m a g e % 2252 % % 2253 % % 2254 % % 2255 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 2256 % 2257 % ResampleImage() resize image in terms of its pixel size, so that when 2258 % displayed at the given resolution it will be the same size in terms of 2259 % real world units as the original image at the original resolution. 2260 % 2261 % The format of the ResampleImage method is: 2262 % 2263 % Image *ResampleImage(Image *image,const double x_resolution, 2264 % const double y_resolution,const FilterType filter, 2265 % ExceptionInfo *exception) 2266 % 2267 % A description of each parameter follows: 2268 % 2269 % o image: the image to be resized to fit the given resolution. 2270 % 2271 % o x_resolution: the new image x resolution. 2272 % 2273 % o y_resolution: the new image y resolution. 2274 % 2275 % o filter: Image filter to use. 2276 % 2277 % o exception: return any errors or warnings in this structure. 2278 % 2279 */ 2280 MagickExport Image *ResampleImage(const Image *image,const double x_resolution, 2281 const double y_resolution,const FilterType filter,ExceptionInfo *exception) 2282 { 2283 #define ResampleImageTag "Resample/Image" 2284 2285 Image 2286 *resample_image; 2287 2288 size_t 2289 height, 2290 width; 2291 2292 /* 2293 Initialize sampled image attributes. 2294 */ 2295 assert(image != (const Image *) NULL); 2296 assert(image->signature == MagickCoreSignature); 2297 if (image->debug != MagickFalse) 2298 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename); 2299 assert(exception != (ExceptionInfo *) NULL); 2300 assert(exception->signature == MagickCoreSignature); 2301 width=(size_t) (x_resolution*image->columns/(image->resolution.x == 0.0 ? 2302 72.0 : image->resolution.x)+0.5); 2303 height=(size_t) (y_resolution*image->rows/(image->resolution.y == 0.0 ? 2304 72.0 : image->resolution.y)+0.5); 2305 resample_image=ResizeImage(image,width,height,filter,exception); 2306 if (resample_image != (Image *) NULL) 2307 { 2308 resample_image->resolution.x=x_resolution; 2309 resample_image->resolution.y=y_resolution; 2310 } 2311 return(resample_image); 2312 } 2313 2314 /* 2316 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 2317 % % 2318 % % 2319 % % 2320 % R e s i z e I m a g e % 2321 % % 2322 % % 2323 % % 2324 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 2325 % 2326 % ResizeImage() scales an image to the desired dimensions, using the given 2327 % filter (see AcquireFilterInfo()). 2328 % 2329 % If an undefined filter is given the filter defaults to Mitchell for a 2330 % colormapped image, a image with a matte channel, or if the image is 2331 % enlarged. Otherwise the filter defaults to a Lanczos. 2332 % 2333 % ResizeImage() was inspired by Paul Heckbert's "zoom" program. 2334 % 2335 % The format of the ResizeImage method is: 2336 % 2337 % Image *ResizeImage(Image *image,const size_t columns,const size_t rows, 2338 % const FilterType filter,ExceptionInfo *exception) 2339 % 2340 % A description of each parameter follows: 2341 % 2342 % o image: the image. 2343 % 2344 % o columns: the number of columns in the scaled image. 2345 % 2346 % o rows: the number of rows in the scaled image. 2347 % 2348 % o filter: Image filter to use. 2349 % 2350 % o exception: return any errors or warnings in this structure. 2351 % 2352 */ 2353 2354 typedef struct _ContributionInfo 2355 { 2356 double 2357 weight; 2358 2359 ssize_t 2360 pixel; 2361 } ContributionInfo; 2362 2363 static ContributionInfo **DestroyContributionThreadSet( 2364 ContributionInfo **contribution) 2365 { 2366 register ssize_t 2367 i; 2368 2369 assert(contribution != (ContributionInfo **) NULL); 2370 for (i=0; i < (ssize_t) GetMagickResourceLimit(ThreadResource); i++) 2371 if (contribution[i] != (ContributionInfo *) NULL) 2372 contribution[i]=(ContributionInfo *) RelinquishAlignedMemory( 2373 contribution[i]); 2374 contribution=(ContributionInfo **) RelinquishMagickMemory(contribution); 2375 return(contribution); 2376 } 2377 2378 static ContributionInfo **AcquireContributionThreadSet(const size_t count) 2379 { 2380 register ssize_t 2381 i; 2382 2383 ContributionInfo 2384 **contribution; 2385 2386 size_t 2387 number_threads; 2388 2389 number_threads=(size_t) GetMagickResourceLimit(ThreadResource); 2390 contribution=(ContributionInfo **) AcquireQuantumMemory(number_threads, 2391 sizeof(*contribution)); 2392 if (contribution == (ContributionInfo **) NULL) 2393 return((ContributionInfo **) NULL); 2394 (void) ResetMagickMemory(contribution,0,number_threads*sizeof(*contribution)); 2395 for (i=0; i < (ssize_t) number_threads; i++) 2396 { 2397 contribution[i]=(ContributionInfo *) MagickAssumeAligned( 2398 AcquireAlignedMemory(count,sizeof(**contribution))); 2399 if (contribution[i] == (ContributionInfo *) NULL) 2400 return(DestroyContributionThreadSet(contribution)); 2401 } 2402 return(contribution); 2403 } 2404 2405 static MagickBooleanType HorizontalFilter(const ResizeFilter *resize_filter, 2406 const Image *image,Image *resize_image,const double x_factor, 2407 const MagickSizeType span,MagickOffsetType *offset,ExceptionInfo *exception) 2408 { 2409 #define ResizeImageTag "Resize/Image" 2410 2411 CacheView 2412 *image_view, 2413 *resize_view; 2414 2415 ClassType 2416 storage_class; 2417 2418 ContributionInfo 2419 **magick_restrict contributions; 2420 2421 MagickBooleanType 2422 status; 2423 2424 double 2425 scale, 2426 support; 2427 2428 ssize_t 2429 x; 2430 2431 /* 2432 Apply filter to resize horizontally from image to resize image. 2433 */ 2434 scale=MagickMax(1.0/x_factor+MagickEpsilon,1.0); 2435 support=scale*GetResizeFilterSupport(resize_filter); 2436 storage_class=support > 0.5 ? DirectClass : image->storage_class; 2437 if (SetImageStorageClass(resize_image,storage_class,exception) == MagickFalse) 2438 return(MagickFalse); 2439 if (support < 0.5) 2440 { 2441 /* 2442 Support too small even for nearest neighbour: Reduce to point sampling. 2443 */ 2444 support=(double) 0.5; 2445 scale=1.0; 2446 } 2447 contributions=AcquireContributionThreadSet((size_t) (2.0*support+3.0)); 2448 if (contributions == (ContributionInfo **) NULL) 2449 { 2450 (void) ThrowMagickException(exception,GetMagickModule(), 2451 ResourceLimitError,"MemoryAllocationFailed","`%s'",image->filename); 2452 return(MagickFalse); 2453 } 2454 status=MagickTrue; 2455 scale=PerceptibleReciprocal(scale); 2456 image_view=AcquireVirtualCacheView(image,exception); 2457 resize_view=AcquireAuthenticCacheView(resize_image,exception); 2458 #if defined(MAGICKCORE_OPENMP_SUPPORT) 2459 #pragma omp parallel for schedule(static,4) shared(status) \ 2460 magick_threads(image,resize_image,resize_image->columns,1) 2461 #endif 2462 for (x=0; x < (ssize_t) resize_image->columns; x++) 2463 { 2464 const int 2465 id = GetOpenMPThreadId(); 2466 2467 double 2468 bisect, 2469 density; 2470 2471 register const Quantum 2472 *magick_restrict p; 2473 2474 register ContributionInfo 2475 *magick_restrict contribution; 2476 2477 register Quantum 2478 *magick_restrict q; 2479 2480 register ssize_t 2481 y; 2482 2483 ssize_t 2484 n, 2485 start, 2486 stop; 2487 2488 if (status == MagickFalse) 2489 continue; 2490 bisect=(double) (x+0.5)/x_factor+MagickEpsilon; 2491 start=(ssize_t) MagickMax(bisect-support+0.5,0.0); 2492 stop=(ssize_t) MagickMin(bisect+support+0.5,(double) image->columns); 2493 density=0.0; 2494 contribution=contributions[id]; 2495 for (n=0; n < (stop-start); n++) 2496 { 2497 contribution[n].pixel=start+n; 2498 contribution[n].weight=GetResizeFilterWeight(resize_filter,scale* 2499 ((double) (start+n)-bisect+0.5)); 2500 density+=contribution[n].weight; 2501 } 2502 if (n == 0) 2503 continue; 2504 if ((density != 0.0) && (density != 1.0)) 2505 { 2506 register ssize_t 2507 i; 2508 2509 /* 2510 Normalize. 2511 */ 2512 density=PerceptibleReciprocal(density); 2513 for (i=0; i < n; i++) 2514 contribution[i].weight*=density; 2515 } 2516 p=GetCacheViewVirtualPixels(image_view,contribution[0].pixel,0,(size_t) 2517 (contribution[n-1].pixel-contribution[0].pixel+1),image->rows,exception); 2518 q=QueueCacheViewAuthenticPixels(resize_view,x,0,1,resize_image->rows, 2519 exception); 2520 if ((p == (const Quantum *) NULL) || (q == (Quantum *) NULL)) 2521 { 2522 status=MagickFalse; 2523 continue; 2524 } 2525 for (y=0; y < (ssize_t) resize_image->rows; y++) 2526 { 2527 register ssize_t 2528 i; 2529 2530 for (i=0; i < (ssize_t) GetPixelChannels(image); i++) 2531 { 2532 double 2533 alpha, 2534 gamma, 2535 pixel; 2536 2537 PixelChannel 2538 channel; 2539 2540 PixelTrait 2541 resize_traits, 2542 traits; 2543 2544 register ssize_t 2545 j; 2546 2547 ssize_t 2548 k; 2549 2550 channel=GetPixelChannelChannel(image,i); 2551 traits=GetPixelChannelTraits(image,channel); 2552 resize_traits=GetPixelChannelTraits(resize_image,channel); 2553 if ((traits == UndefinedPixelTrait) || 2554 (resize_traits == UndefinedPixelTrait)) 2555 continue; 2556 if (((resize_traits & CopyPixelTrait) != 0) || 2557 (GetPixelReadMask(resize_image,q) == 0)) 2558 { 2559 j=(ssize_t) (MagickMin(MagickMax(bisect,(double) start),(double) 2560 stop-1.0)+0.5); 2561 k=y*(contribution[n-1].pixel-contribution[0].pixel+1)+ 2562 (contribution[j-start].pixel-contribution[0].pixel); 2563 SetPixelChannel(resize_image,channel,p[k*GetPixelChannels(image)+i], 2564 q); 2565 continue; 2566 } 2567 pixel=0.0; 2568 if ((resize_traits & BlendPixelTrait) == 0) 2569 { 2570 /* 2571 No alpha blending. 2572 */ 2573 for (j=0; j < n; j++) 2574 { 2575 k=y*(contribution[n-1].pixel-contribution[0].pixel+1)+ 2576 (contribution[j].pixel-contribution[0].pixel); 2577 alpha=contribution[j].weight; 2578 pixel+=alpha*p[k*GetPixelChannels(image)+i]; 2579 } 2580 SetPixelChannel(resize_image,channel,ClampToQuantum(pixel),q); 2581 continue; 2582 } 2583 /* 2584 Alpha blending. 2585 */ 2586 gamma=0.0; 2587 for (j=0; j < n; j++) 2588 { 2589 k=y*(contribution[n-1].pixel-contribution[0].pixel+1)+ 2590 (contribution[j].pixel-contribution[0].pixel); 2591 alpha=contribution[j].weight*QuantumScale* 2592 GetPixelAlpha(image,p+k*GetPixelChannels(image)); 2593 pixel+=alpha*p[k*GetPixelChannels(image)+i]; 2594 gamma+=alpha; 2595 } 2596 gamma=PerceptibleReciprocal(gamma); 2597 SetPixelChannel(resize_image,channel,ClampToQuantum(gamma*pixel),q); 2598 } 2599 q+=GetPixelChannels(resize_image); 2600 } 2601 if (SyncCacheViewAuthenticPixels(resize_view,exception) == MagickFalse) 2602 status=MagickFalse; 2603 if (image->progress_monitor != (MagickProgressMonitor) NULL) 2604 { 2605 MagickBooleanType 2606 proceed; 2607 2608 #if defined(MAGICKCORE_OPENMP_SUPPORT) 2609 #pragma omp critical (MagickCore_HorizontalFilter) 2610 #endif 2611 proceed=SetImageProgress(image,ResizeImageTag,(*offset)++,span); 2612 if (proceed == MagickFalse) 2613 status=MagickFalse; 2614 } 2615 } 2616 resize_view=DestroyCacheView(resize_view); 2617 image_view=DestroyCacheView(image_view); 2618 contributions=DestroyContributionThreadSet(contributions); 2619 return(status); 2620 } 2621 2622 static MagickBooleanType VerticalFilter(const ResizeFilter *resize_filter, 2623 const Image *image,Image *resize_image,const double y_factor, 2624 const MagickSizeType span,MagickOffsetType *offset,ExceptionInfo *exception) 2625 { 2626 CacheView 2627 *image_view, 2628 *resize_view; 2629 2630 ClassType 2631 storage_class; 2632 2633 ContributionInfo 2634 **magick_restrict contributions; 2635 2636 double 2637 scale, 2638 support; 2639 2640 MagickBooleanType 2641 status; 2642 2643 ssize_t 2644 y; 2645 2646 /* 2647 Apply filter to resize vertically from image to resize image. 2648 */ 2649 scale=MagickMax(1.0/y_factor+MagickEpsilon,1.0); 2650 support=scale*GetResizeFilterSupport(resize_filter); 2651 storage_class=support > 0.5 ? DirectClass : image->storage_class; 2652 if (SetImageStorageClass(resize_image,storage_class,exception) == MagickFalse) 2653 return(MagickFalse); 2654 if (support < 0.5) 2655 { 2656 /* 2657 Support too small even for nearest neighbour: Reduce to point sampling. 2658 */ 2659 support=(double) 0.5; 2660 scale=1.0; 2661 } 2662 contributions=AcquireContributionThreadSet((size_t) (2.0*support+3.0)); 2663 if (contributions == (ContributionInfo **) NULL) 2664 { 2665 (void) ThrowMagickException(exception,GetMagickModule(), 2666 ResourceLimitError,"MemoryAllocationFailed","`%s'",image->filename); 2667 return(MagickFalse); 2668 } 2669 status=MagickTrue; 2670 scale=PerceptibleReciprocal(scale); 2671 image_view=AcquireVirtualCacheView(image,exception); 2672 resize_view=AcquireAuthenticCacheView(resize_image,exception); 2673 #if defined(MAGICKCORE_OPENMP_SUPPORT) 2674 #pragma omp parallel for schedule(static,4) shared(status) \ 2675 magick_threads(image,resize_image,resize_image->rows,1) 2676 #endif 2677 for (y=0; y < (ssize_t) resize_image->rows; y++) 2678 { 2679 const int 2680 id = GetOpenMPThreadId(); 2681 2682 double 2683 bisect, 2684 density; 2685 2686 register const Quantum 2687 *magick_restrict p; 2688 2689 register ContributionInfo 2690 *magick_restrict contribution; 2691 2692 register Quantum 2693 *magick_restrict q; 2694 2695 register ssize_t 2696 x; 2697 2698 ssize_t 2699 n, 2700 start, 2701 stop; 2702 2703 if (status == MagickFalse) 2704 continue; 2705 bisect=(double) (y+0.5)/y_factor+MagickEpsilon; 2706 start=(ssize_t) MagickMax(bisect-support+0.5,0.0); 2707 stop=(ssize_t) MagickMin(bisect+support+0.5,(double) image->rows); 2708 density=0.0; 2709 contribution=contributions[id]; 2710 for (n=0; n < (stop-start); n++) 2711 { 2712 contribution[n].pixel=start+n; 2713 contribution[n].weight=GetResizeFilterWeight(resize_filter,scale* 2714 ((double) (start+n)-bisect+0.5)); 2715 density+=contribution[n].weight; 2716 } 2717 if (n == 0) 2718 continue; 2719 if ((density != 0.0) && (density != 1.0)) 2720 { 2721 register ssize_t 2722 i; 2723 2724 /* 2725 Normalize. 2726 */ 2727 density=PerceptibleReciprocal(density); 2728 for (i=0; i < n; i++) 2729 contribution[i].weight*=density; 2730 } 2731 p=GetCacheViewVirtualPixels(image_view,0,contribution[0].pixel, 2732 image->columns,(size_t) (contribution[n-1].pixel-contribution[0].pixel+1), 2733 exception); 2734 q=QueueCacheViewAuthenticPixels(resize_view,0,y,resize_image->columns,1, 2735 exception); 2736 if ((p == (const Quantum *) NULL) || (q == (Quantum *) NULL)) 2737 { 2738 status=MagickFalse; 2739 continue; 2740 } 2741 for (x=0; x < (ssize_t) resize_image->columns; x++) 2742 { 2743 register ssize_t 2744 i; 2745 2746 for (i=0; i < (ssize_t) GetPixelChannels(image); i++) 2747 { 2748 double 2749 alpha, 2750 gamma, 2751 pixel; 2752 2753 PixelChannel 2754 channel; 2755 2756 PixelTrait 2757 resize_traits, 2758 traits; 2759 2760 register ssize_t 2761 j; 2762 2763 ssize_t 2764 k; 2765 2766 channel=GetPixelChannelChannel(image,i); 2767 traits=GetPixelChannelTraits(image,channel); 2768 resize_traits=GetPixelChannelTraits(resize_image,channel); 2769 if ((traits == UndefinedPixelTrait) || 2770 (resize_traits == UndefinedPixelTrait)) 2771 continue; 2772 if (((resize_traits & CopyPixelTrait) != 0) || 2773 (GetPixelReadMask(resize_image,q) == 0)) 2774 { 2775 j=(ssize_t) (MagickMin(MagickMax(bisect,(double) start),(double) 2776 stop-1.0)+0.5); 2777 k=(ssize_t) ((contribution[j-start].pixel-contribution[0].pixel)* 2778 image->columns+x); 2779 SetPixelChannel(resize_image,channel,p[k*GetPixelChannels(image)+i], 2780 q); 2781 continue; 2782 } 2783 pixel=0.0; 2784 if ((resize_traits & BlendPixelTrait) == 0) 2785 { 2786 /* 2787 No alpha blending. 2788 */ 2789 for (j=0; j < n; j++) 2790 { 2791 k=(ssize_t) ((contribution[j].pixel-contribution[0].pixel)* 2792 image->columns+x); 2793 alpha=contribution[j].weight; 2794 pixel+=alpha*p[k*GetPixelChannels(image)+i]; 2795 } 2796 SetPixelChannel(resize_image,channel,ClampToQuantum(pixel),q); 2797 continue; 2798 } 2799 gamma=0.0; 2800 for (j=0; j < n; j++) 2801 { 2802 k=(ssize_t) ((contribution[j].pixel-contribution[0].pixel)* 2803 image->columns+x); 2804 alpha=contribution[j].weight*QuantumScale*GetPixelAlpha(image,p+k* 2805 GetPixelChannels(image)); 2806 pixel+=alpha*p[k*GetPixelChannels(image)+i]; 2807 gamma+=alpha; 2808 } 2809 gamma=PerceptibleReciprocal(gamma); 2810 SetPixelChannel(resize_image,channel,ClampToQuantum(gamma*pixel),q); 2811 } 2812 q+=GetPixelChannels(resize_image); 2813 } 2814 if (SyncCacheViewAuthenticPixels(resize_view,exception) == MagickFalse) 2815 status=MagickFalse; 2816 if (image->progress_monitor != (MagickProgressMonitor) NULL) 2817 { 2818 MagickBooleanType 2819 proceed; 2820 2821 #if defined(MAGICKCORE_OPENMP_SUPPORT) 2822 #pragma omp critical (MagickCore_VerticalFilter) 2823 #endif 2824 proceed=SetImageProgress(image,ResizeImageTag,(*offset)++,span); 2825 if (proceed == MagickFalse) 2826 status=MagickFalse; 2827 } 2828 } 2829 resize_view=DestroyCacheView(resize_view); 2830 image_view=DestroyCacheView(image_view); 2831 contributions=DestroyContributionThreadSet(contributions); 2832 return(status); 2833 } 2834 2835 MagickExport Image *ResizeImage(const Image *image,const size_t columns, 2836 const size_t rows,const FilterType filter,ExceptionInfo *exception) 2837 { 2838 double 2839 x_factor, 2840 y_factor; 2841 2842 FilterType 2843 filter_type; 2844 2845 Image 2846 *filter_image, 2847 *resize_image; 2848 2849 MagickOffsetType 2850 offset; 2851 2852 MagickSizeType 2853 span; 2854 2855 MagickStatusType 2856 status; 2857 2858 ResizeFilter 2859 *resize_filter; 2860 2861 /* 2862 Acquire resize image. 2863 */ 2864 assert(image != (Image *) NULL); 2865 assert(image->signature == MagickCoreSignature); 2866 if (image->debug != MagickFalse) 2867 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename); 2868 assert(exception != (ExceptionInfo *) NULL); 2869 assert(exception->signature == MagickCoreSignature); 2870 if ((columns == 0) || (rows == 0)) 2871 ThrowImageException(ImageError,"NegativeOrZeroImageSize"); 2872 if ((columns == image->columns) && (rows == image->rows) && 2873 (filter == UndefinedFilter)) 2874 return(CloneImage(image,0,0,MagickTrue,exception)); 2875 /* 2876 Acquire resize filter. 2877 */ 2878 x_factor=(double) columns/(double) image->columns; 2879 y_factor=(double) rows/(double) image->rows; 2880 filter_type=LanczosFilter; 2881 if (filter != UndefinedFilter) 2882 filter_type=filter; 2883 else 2884 if ((x_factor == 1.0) && (y_factor == 1.0)) 2885 filter_type=PointFilter; 2886 else 2887 if ((image->storage_class == PseudoClass) || 2888 (image->alpha_trait != UndefinedPixelTrait) || 2889 ((x_factor*y_factor) > 1.0)) 2890 filter_type=MitchellFilter; 2891 resize_filter=AcquireResizeFilter(image,filter_type,MagickFalse,exception); 2892 #if defined(MAGICKCORE_OPENCL_SUPPORT) 2893 resize_image=AccelerateResizeImage(image,columns,rows,resize_filter, 2894 exception); 2895 if (resize_image != (Image *) NULL) 2896 { 2897 resize_filter=DestroyResizeFilter(resize_filter); 2898 return(resize_image); 2899 } 2900 #endif 2901 resize_image=CloneImage(image,columns,rows,MagickTrue,exception); 2902 if (resize_image == (Image *) NULL) 2903 { 2904 resize_filter=DestroyResizeFilter(resize_filter); 2905 return(resize_image); 2906 } 2907 if (x_factor > y_factor) 2908 filter_image=CloneImage(image,columns,image->rows,MagickTrue,exception); 2909 else 2910 filter_image=CloneImage(image,image->columns,rows,MagickTrue,exception); 2911 if (filter_image == (Image *) NULL) 2912 { 2913 resize_filter=DestroyResizeFilter(resize_filter); 2914 return(DestroyImage(resize_image)); 2915 } 2916 /* 2917 Resize image. 2918 */ 2919 offset=0; 2920 if (x_factor > y_factor) 2921 { 2922 span=(MagickSizeType) (filter_image->columns+rows); 2923 status=HorizontalFilter(resize_filter,image,filter_image,x_factor,span, 2924 &offset,exception); 2925 status&=VerticalFilter(resize_filter,filter_image,resize_image,y_factor, 2926 span,&offset,exception); 2927 } 2928 else 2929 { 2930 span=(MagickSizeType) (filter_image->rows+columns); 2931 status=VerticalFilter(resize_filter,image,filter_image,y_factor,span, 2932 &offset,exception); 2933 status&=HorizontalFilter(resize_filter,filter_image,resize_image,x_factor, 2934 span,&offset,exception); 2935 } 2936 /* 2937 Free resources. 2938 */ 2939 filter_image=DestroyImage(filter_image); 2940 resize_filter=DestroyResizeFilter(resize_filter); 2941 if (status == MagickFalse) 2942 { 2943 resize_image=DestroyImage(resize_image); 2944 return((Image *) NULL); 2945 } 2946 resize_image->type=image->type; 2947 return(resize_image); 2948 } 2949 2950 /* 2952 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 2953 % % 2954 % % 2955 % % 2956 % S a m p l e I m a g e % 2957 % % 2958 % % 2959 % % 2960 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 2961 % 2962 % SampleImage() scales an image to the desired dimensions with pixel 2963 % sampling. Unlike other scaling methods, this method does not introduce 2964 % any additional color into the scaled image. 2965 % 2966 % The format of the SampleImage method is: 2967 % 2968 % Image *SampleImage(const Image *image,const size_t columns, 2969 % const size_t rows,ExceptionInfo *exception) 2970 % 2971 % A description of each parameter follows: 2972 % 2973 % o image: the image. 2974 % 2975 % o columns: the number of columns in the sampled image. 2976 % 2977 % o rows: the number of rows in the sampled image. 2978 % 2979 % o exception: return any errors or warnings in this structure. 2980 % 2981 */ 2982 MagickExport Image *SampleImage(const Image *image,const size_t columns, 2983 const size_t rows,ExceptionInfo *exception) 2984 { 2985 #define SampleImageTag "Sample/Image" 2986 2987 CacheView 2988 *image_view, 2989 *sample_view; 2990 2991 Image 2992 *sample_image; 2993 2994 MagickBooleanType 2995 status; 2996 2997 MagickOffsetType 2998 progress; 2999 3000 register ssize_t 3001 x; 3002 3003 ssize_t 3004 *x_offset, 3005 y; 3006 3007 PointInfo 3008 sample_offset; 3009 3010 /* 3011 Initialize sampled image attributes. 3012 */ 3013 assert(image != (const Image *) NULL); 3014 assert(image->signature == MagickCoreSignature); 3015 if (image->debug != MagickFalse) 3016 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename); 3017 assert(exception != (ExceptionInfo *) NULL); 3018 assert(exception->signature == MagickCoreSignature); 3019 if ((columns == 0) || (rows == 0)) 3020 ThrowImageException(ImageError,"NegativeOrZeroImageSize"); 3021 if ((columns == image->columns) && (rows == image->rows)) 3022 return(CloneImage(image,0,0,MagickTrue,exception)); 3023 sample_image=CloneImage(image,columns,rows,MagickTrue,exception); 3024 if (sample_image == (Image *) NULL) 3025 return((Image *) NULL); 3026 /* 3027 Set the sampling offset, default is in the mid-point of sample regions. 3028 */ 3029 sample_offset.x=sample_offset.y=0.5-MagickEpsilon; 3030 { 3031 const char 3032 *value; 3033 3034 value=GetImageArtifact(image,"sample:offset"); 3035 if (value != (char *) NULL) 3036 { 3037 GeometryInfo 3038 geometry_info; 3039 3040 MagickStatusType 3041 flags; 3042 3043 (void) ParseGeometry(value,&geometry_info); 3044 flags=ParseGeometry(value,&geometry_info); 3045 sample_offset.x=sample_offset.y=geometry_info.rho/100.0-MagickEpsilon; 3046 if ((flags & SigmaValue) != 0) 3047 sample_offset.y=geometry_info.sigma/100.0-MagickEpsilon; 3048 } 3049 } 3050 /* 3051 Allocate scan line buffer and column offset buffers. 3052 */ 3053 x_offset=(ssize_t *) AcquireQuantumMemory((size_t) sample_image->columns, 3054 sizeof(*x_offset)); 3055 if (x_offset == (ssize_t *) NULL) 3056 { 3057 sample_image=DestroyImage(sample_image); 3058 ThrowImageException(ResourceLimitError,"MemoryAllocationFailed"); 3059 } 3060 for (x=0; x < (ssize_t) sample_image->columns; x++) 3061 x_offset[x]=(ssize_t) ((((double) x+sample_offset.x)*image->columns)/ 3062 sample_image->columns); 3063 /* 3064 Sample each row. 3065 */ 3066 status=MagickTrue; 3067 progress=0; 3068 image_view=AcquireVirtualCacheView(image,exception); 3069 sample_view=AcquireAuthenticCacheView(sample_image,exception); 3070 #if defined(MAGICKCORE_OPENMP_SUPPORT) 3071 #pragma omp parallel for schedule(static,4) shared(status) \ 3072 magick_threads(image,sample_image,1,1) 3073 #endif 3074 for (y=0; y < (ssize_t) sample_image->rows; y++) 3075 { 3076 register const Quantum 3077 *magick_restrict p; 3078 3079 register Quantum 3080 *magick_restrict q; 3081 3082 register ssize_t 3083 x; 3084 3085 ssize_t 3086 y_offset; 3087 3088 if (status == MagickFalse) 3089 continue; 3090 y_offset=(ssize_t) ((((double) y+sample_offset.y)*image->rows)/ 3091 sample_image->rows); 3092 p=GetCacheViewVirtualPixels(image_view,0,y_offset,image->columns,1, 3093 exception); 3094 q=QueueCacheViewAuthenticPixels(sample_view,0,y,sample_image->columns,1, 3095 exception); 3096 if ((p == (const Quantum *) NULL) || (q == (Quantum *) NULL)) 3097 { 3098 status=MagickFalse; 3099 continue; 3100 } 3101 /* 3102 Sample each column. 3103 */ 3104 for (x=0; x < (ssize_t) sample_image->columns; x++) 3105 { 3106 register ssize_t 3107 i; 3108 3109 if (GetPixelReadMask(sample_image,q) == 0) 3110 { 3111 q+=GetPixelChannels(sample_image); 3112 continue; 3113 } 3114 for (i=0; i < (ssize_t) GetPixelChannels(sample_image); i++) 3115 { 3116 PixelChannel 3117 channel; 3118 3119 PixelTrait 3120 sample_traits, 3121 traits; 3122 3123 channel=GetPixelChannelChannel(image,i); 3124 traits=GetPixelChannelTraits(image,channel); 3125 sample_traits=GetPixelChannelTraits(sample_image,channel); 3126 if ((traits == UndefinedPixelTrait) || 3127 (sample_traits == UndefinedPixelTrait)) 3128 continue; 3129 SetPixelChannel(sample_image,channel,p[x_offset[x]*GetPixelChannels( 3130 image)+i],q); 3131 } 3132 q+=GetPixelChannels(sample_image); 3133 } 3134 if (SyncCacheViewAuthenticPixels(sample_view,exception) == MagickFalse) 3135 status=MagickFalse; 3136 if (image->progress_monitor != (MagickProgressMonitor) NULL) 3137 { 3138 MagickBooleanType 3139 proceed; 3140 3141 #if defined(MAGICKCORE_OPENMP_SUPPORT) 3142 #pragma omp critical (MagickCore_SampleImage) 3143 #endif 3144 proceed=SetImageProgress(image,SampleImageTag,progress++,image->rows); 3145 if (proceed == MagickFalse) 3146 status=MagickFalse; 3147 } 3148 } 3149 image_view=DestroyCacheView(image_view); 3150 sample_view=DestroyCacheView(sample_view); 3151 x_offset=(ssize_t *) RelinquishMagickMemory(x_offset); 3152 sample_image->type=image->type; 3153 if (status == MagickFalse) 3154 sample_image=DestroyImage(sample_image); 3155 return(sample_image); 3156 } 3157 3158 /* 3160 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 3161 % % 3162 % % 3163 % % 3164 % S c a l e I m a g e % 3165 % % 3166 % % 3167 % % 3168 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 3169 % 3170 % ScaleImage() changes the size of an image to the given dimensions. 3171 % 3172 % The format of the ScaleImage method is: 3173 % 3174 % Image *ScaleImage(const Image *image,const size_t columns, 3175 % const size_t rows,ExceptionInfo *exception) 3176 % 3177 % A description of each parameter follows: 3178 % 3179 % o image: the image. 3180 % 3181 % o columns: the number of columns in the scaled image. 3182 % 3183 % o rows: the number of rows in the scaled image. 3184 % 3185 % o exception: return any errors or warnings in this structure. 3186 % 3187 */ 3188 MagickExport Image *ScaleImage(const Image *image,const size_t columns, 3189 const size_t rows,ExceptionInfo *exception) 3190 { 3191 #define ScaleImageTag "Scale/Image" 3192 3193 CacheView 3194 *image_view, 3195 *scale_view; 3196 3197 double 3198 alpha, 3199 pixel[CompositePixelChannel], 3200 *scale_scanline, 3201 *scanline, 3202 *x_vector, 3203 *y_vector; 3204 3205 Image 3206 *scale_image; 3207 3208 MagickBooleanType 3209 next_column, 3210 next_row, 3211 proceed, 3212 status; 3213 3214 PixelChannel 3215 channel; 3216 3217 PixelTrait 3218 scale_traits, 3219 traits; 3220 3221 PointInfo 3222 scale, 3223 span; 3224 3225 register ssize_t 3226 i; 3227 3228 ssize_t 3229 n, 3230 number_rows, 3231 y; 3232 3233 /* 3234 Initialize scaled image attributes. 3235 */ 3236 assert(image != (const Image *) NULL); 3237 assert(image->signature == MagickCoreSignature); 3238 if (image->debug != MagickFalse) 3239 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename); 3240 assert(exception != (ExceptionInfo *) NULL); 3241 assert(exception->signature == MagickCoreSignature); 3242 if ((columns == 0) || (rows == 0)) 3243 ThrowImageException(ImageError,"NegativeOrZeroImageSize"); 3244 if ((columns == image->columns) && (rows == image->rows)) 3245 return(CloneImage(image,0,0,MagickTrue,exception)); 3246 scale_image=CloneImage(image,columns,rows,MagickTrue,exception); 3247 if (scale_image == (Image *) NULL) 3248 return((Image *) NULL); 3249 if (SetImageStorageClass(scale_image,DirectClass,exception) == MagickFalse) 3250 { 3251 scale_image=DestroyImage(scale_image); 3252 return((Image *) NULL); 3253 } 3254 /* 3255 Allocate memory. 3256 */ 3257 x_vector=(double *) AcquireQuantumMemory((size_t) image->columns, 3258 GetPixelChannels(image)*sizeof(*x_vector)); 3259 scanline=x_vector; 3260 if (image->rows != scale_image->rows) 3261 scanline=(double *) AcquireQuantumMemory((size_t) image->columns, 3262 GetPixelChannels(image)*sizeof(*scanline)); 3263 scale_scanline=(double *) AcquireQuantumMemory((size_t) 3264 scale_image->columns,GetPixelChannels(image)*sizeof(*scale_scanline)); 3265 y_vector=(double *) AcquireQuantumMemory((size_t) image->columns, 3266 GetPixelChannels(image)*sizeof(*y_vector)); 3267 if ((scanline == (double *) NULL) || 3268 (scale_scanline == (double *) NULL) || 3269 (x_vector == (double *) NULL) || 3270 (y_vector == (double *) NULL)) 3271 { 3272 scale_image=DestroyImage(scale_image); 3273 ThrowImageException(ResourceLimitError,"MemoryAllocationFailed"); 3274 } 3275 /* 3276 Scale image. 3277 */ 3278 number_rows=0; 3279 next_row=MagickTrue; 3280 span.y=1.0; 3281 scale.y=(double) scale_image->rows/(double) image->rows; 3282 (void) ResetMagickMemory(y_vector,0,(size_t) GetPixelChannels(image)* 3283 image->columns*sizeof(*y_vector)); 3284 n=0; 3285 status=MagickTrue; 3286 image_view=AcquireVirtualCacheView(image,exception); 3287 scale_view=AcquireAuthenticCacheView(scale_image,exception); 3288 for (y=0; y < (ssize_t) scale_image->rows; y++) 3289 { 3290 register const Quantum 3291 *magick_restrict p; 3292 3293 register Quantum 3294 *magick_restrict q; 3295 3296 register ssize_t 3297 x; 3298 3299 if (status == MagickFalse) 3300 break; 3301 q=QueueCacheViewAuthenticPixels(scale_view,0,y,scale_image->columns,1, 3302 exception); 3303 if (q == (Quantum *) NULL) 3304 { 3305 status=MagickFalse; 3306 break; 3307 } 3308 alpha=1.0; 3309 if (scale_image->rows == image->rows) 3310 { 3311 /* 3312 Read a new scanline. 3313 */ 3314 p=GetCacheViewVirtualPixels(image_view,0,n++,image->columns,1, 3315 exception); 3316 if (p == (const Quantum *) NULL) 3317 { 3318 status=MagickFalse; 3319 break; 3320 } 3321 for (x=0; x < (ssize_t) image->columns; x++) 3322 { 3323 if (GetPixelReadMask(image,p) == 0) 3324 { 3325 p+=GetPixelChannels(image); 3326 continue; 3327 } 3328 if (image->alpha_trait != UndefinedPixelTrait) 3329 alpha=QuantumScale*GetPixelAlpha(image,p); 3330 for (i=0; i < (ssize_t) GetPixelChannels(image); i++) 3331 { 3332 PixelChannel channel=GetPixelChannelChannel(image,i); 3333 PixelTrait traits=GetPixelChannelTraits(image,channel); 3334 if ((traits & BlendPixelTrait) == 0) 3335 { 3336 x_vector[x*GetPixelChannels(image)+i]=(double) p[i]; 3337 continue; 3338 } 3339 x_vector[x*GetPixelChannels(image)+i]=alpha*p[i]; 3340 } 3341 p+=GetPixelChannels(image); 3342 } 3343 } 3344 else 3345 { 3346 /* 3347 Scale Y direction. 3348 */ 3349 while (scale.y < span.y) 3350 { 3351 if ((next_row != MagickFalse) && 3352 (number_rows < (ssize_t) image->rows)) 3353 { 3354 /* 3355 Read a new scanline. 3356 */ 3357 p=GetCacheViewVirtualPixels(image_view,0,n++,image->columns,1, 3358 exception); 3359 if (p == (const Quantum *) NULL) 3360 { 3361 status=MagickFalse; 3362 break; 3363 } 3364 for (x=0; x < (ssize_t) image->columns; x++) 3365 { 3366 if (GetPixelReadMask(image,p) == 0) 3367 { 3368 p+=GetPixelChannels(image); 3369 continue; 3370 } 3371 if (image->alpha_trait != UndefinedPixelTrait) 3372 alpha=QuantumScale*GetPixelAlpha(image,p); 3373 for (i=0; i < (ssize_t) GetPixelChannels(image); i++) 3374 { 3375 PixelChannel channel=GetPixelChannelChannel(image,i); 3376 PixelTrait traits=GetPixelChannelTraits(image,channel); 3377 if ((traits & BlendPixelTrait) == 0) 3378 { 3379 x_vector[x*GetPixelChannels(image)+i]=(double) p[i]; 3380 continue; 3381 } 3382 x_vector[x*GetPixelChannels(image)+i]=alpha*p[i]; 3383 } 3384 p+=GetPixelChannels(image); 3385 } 3386 number_rows++; 3387 } 3388 for (x=0; x < (ssize_t) image->columns; x++) 3389 for (i=0; i < (ssize_t) GetPixelChannels(image); i++) 3390 y_vector[x*GetPixelChannels(image)+i]+=scale.y* 3391 x_vector[x*GetPixelChannels(image)+i]; 3392 span.y-=scale.y; 3393 scale.y=(double) scale_image->rows/(double) image->rows; 3394 next_row=MagickTrue; 3395 } 3396 if ((next_row != MagickFalse) && (number_rows < (ssize_t) image->rows)) 3397 { 3398 /* 3399 Read a new scanline. 3400 */ 3401 p=GetCacheViewVirtualPixels(image_view,0,n++,image->columns,1, 3402 exception); 3403 if (p == (const Quantum *) NULL) 3404 { 3405 status=MagickFalse; 3406 break; 3407 } 3408 for (x=0; x < (ssize_t) image->columns; x++) 3409 { 3410 if (GetPixelReadMask(image,p) == 0) 3411 { 3412 p+=GetPixelChannels(image); 3413 continue; 3414 } 3415 if (image->alpha_trait != UndefinedPixelTrait) 3416 alpha=QuantumScale*GetPixelAlpha(image,p); 3417 for (i=0; i < (ssize_t) GetPixelChannels(image); i++) 3418 { 3419 PixelChannel channel=GetPixelChannelChannel(image,i); 3420 PixelTrait traits=GetPixelChannelTraits(image,channel); 3421 if ((traits & BlendPixelTrait) == 0) 3422 { 3423 x_vector[x*GetPixelChannels(image)+i]=(double) p[i]; 3424 continue; 3425 } 3426 x_vector[x*GetPixelChannels(image)+i]=alpha*p[i]; 3427 } 3428 p+=GetPixelChannels(image); 3429 } 3430 number_rows++; 3431 next_row=MagickFalse; 3432 } 3433 for (x=0; x < (ssize_t) image->columns; x++) 3434 { 3435 for (i=0; i < (ssize_t) GetPixelChannels(image); i++) 3436 { 3437 pixel[i]=y_vector[x*GetPixelChannels(image)+i]+span.y* 3438 x_vector[x*GetPixelChannels(image)+i]; 3439 scanline[x*GetPixelChannels(image)+i]=pixel[i]; 3440 y_vector[x*GetPixelChannels(image)+i]=0.0; 3441 } 3442 } 3443 scale.y-=span.y; 3444 if (scale.y <= 0) 3445 { 3446 scale.y=(double) scale_image->rows/(double) image->rows; 3447 next_row=MagickTrue; 3448 } 3449 span.y=1.0; 3450 } 3451 if (scale_image->columns == image->columns) 3452 { 3453 /* 3454 Transfer scanline to scaled image. 3455 */ 3456 for (x=0; x < (ssize_t) scale_image->columns; x++) 3457 { 3458 if (GetPixelReadMask(scale_image,q) == 0) 3459 { 3460 q+=GetPixelChannels(scale_image); 3461 continue; 3462 } 3463 if (image->alpha_trait != UndefinedPixelTrait) 3464 { 3465 alpha=QuantumScale*scanline[x*GetPixelChannels(image)+ 3466 GetPixelChannelOffset(image,AlphaPixelChannel)]; 3467 alpha=PerceptibleReciprocal(alpha); 3468 } 3469 for (i=0; i < (ssize_t) GetPixelChannels(image); i++) 3470 { 3471 channel=GetPixelChannelChannel(image,i); 3472 traits=GetPixelChannelTraits(image,channel); 3473 scale_traits=GetPixelChannelTraits(scale_image,channel); 3474 if ((traits == UndefinedPixelTrait) || 3475 (scale_traits == UndefinedPixelTrait)) 3476 continue; 3477 if ((traits & BlendPixelTrait) == 0) 3478 { 3479 SetPixelChannel(scale_image,channel,ClampToQuantum( 3480 scanline[x*GetPixelChannels(image)+i]),q); 3481 continue; 3482 } 3483 SetPixelChannel(scale_image,channel,ClampToQuantum(alpha*scanline[ 3484 x*GetPixelChannels(image)+i]),q); 3485 } 3486 q+=GetPixelChannels(scale_image); 3487 } 3488 } 3489 else 3490 { 3491 ssize_t 3492 t; 3493 3494 /* 3495 Scale X direction. 3496 */ 3497 for (i=0; i < (ssize_t) GetPixelChannels(image); i++) 3498 pixel[i]=0.0; 3499 next_column=MagickFalse; 3500 span.x=1.0; 3501 t=0; 3502 for (x=0; x < (ssize_t) image->columns; x++) 3503 { 3504 scale.x=(double) scale_image->columns/(double) image->columns; 3505 while (scale.x >= span.x) 3506 { 3507 if (next_column != MagickFalse) 3508 { 3509 for (i=0; i < (ssize_t) GetPixelChannels(image); i++) 3510 pixel[i]=0.0; 3511 t++; 3512 } 3513 for (i=0; i < (ssize_t) GetPixelChannels(image); i++) 3514 { 3515 PixelChannel channel=GetPixelChannelChannel(image,i); 3516 PixelTrait traits=GetPixelChannelTraits(image,channel); 3517 if (traits == UndefinedPixelTrait) 3518 continue; 3519 pixel[i]+=span.x*scanline[x*GetPixelChannels(image)+i]; 3520 scale_scanline[t*GetPixelChannels(image)+i]=pixel[i]; 3521 } 3522 scale.x-=span.x; 3523 span.x=1.0; 3524 next_column=MagickTrue; 3525 } 3526 if (scale.x > 0) 3527 { 3528 if (next_column != MagickFalse) 3529 { 3530 for (i=0; i < (ssize_t) GetPixelChannels(image); i++) 3531 pixel[i]=0.0; 3532 next_column=MagickFalse; 3533 t++; 3534 } 3535 for (i=0; i < (ssize_t) GetPixelChannels(image); i++) 3536 pixel[i]+=scale.x*scanline[x*GetPixelChannels(image)+i]; 3537 span.x-=scale.x; 3538 } 3539 } 3540 if (span.x > 0) 3541 { 3542 for (i=0; i < (ssize_t) GetPixelChannels(image); i++) 3543 pixel[i]+=span.x*scanline[(x-1)*GetPixelChannels(image)+i]; 3544 } 3545 if ((next_column == MagickFalse) && 3546 (t < (ssize_t) scale_image->columns)) 3547 for (i=0; i < (ssize_t) GetPixelChannels(image); i++) 3548 scale_scanline[t*GetPixelChannels(image)+i]=pixel[i]; 3549 /* 3550 Transfer scanline to scaled image. 3551 */ 3552 for (x=0; x < (ssize_t) scale_image->columns; x++) 3553 { 3554 if (GetPixelReadMask(scale_image,q) == 0) 3555 { 3556 q+=GetPixelChannels(scale_image); 3557 continue; 3558 } 3559 if (image->alpha_trait != UndefinedPixelTrait) 3560 { 3561 alpha=QuantumScale*scale_scanline[x*GetPixelChannels(image)+ 3562 GetPixelChannelOffset(image,AlphaPixelChannel)]; 3563 alpha=PerceptibleReciprocal(alpha); 3564 } 3565 for (i=0; i < (ssize_t) GetPixelChannels(image); i++) 3566 { 3567 PixelChannel channel=GetPixelChannelChannel(image,i); 3568 PixelTrait traits=GetPixelChannelTraits(image,channel); 3569 scale_traits=GetPixelChannelTraits(scale_image,channel); 3570 if ((traits == UndefinedPixelTrait) || 3571 (scale_traits == UndefinedPixelTrait)) 3572 continue; 3573 if ((traits & BlendPixelTrait) == 0) 3574 { 3575 SetPixelChannel(scale_image,channel,ClampToQuantum( 3576 scale_scanline[x*GetPixelChannels(image)+i]),q); 3577 continue; 3578 } 3579 SetPixelChannel(scale_image,channel,ClampToQuantum(alpha* 3580 scale_scanline[x*GetPixelChannels(image)+i]),q); 3581 } 3582 q+=GetPixelChannels(scale_image); 3583 } 3584 } 3585 if (SyncCacheViewAuthenticPixels(scale_view,exception) == MagickFalse) 3586 { 3587 status=MagickFalse; 3588 break; 3589 } 3590 proceed=SetImageProgress(image,ScaleImageTag,(MagickOffsetType) y, 3591 image->rows); 3592 if (proceed == MagickFalse) 3593 { 3594 status=MagickFalse; 3595 break; 3596 } 3597 } 3598 scale_view=DestroyCacheView(scale_view); 3599 image_view=DestroyCacheView(image_view); 3600 /* 3601 Free allocated memory. 3602 */ 3603 y_vector=(double *) RelinquishMagickMemory(y_vector); 3604 scale_scanline=(double *) RelinquishMagickMemory(scale_scanline); 3605 if (scale_image->rows != image->rows) 3606 scanline=(double *) RelinquishMagickMemory(scanline); 3607 x_vector=(double *) RelinquishMagickMemory(x_vector); 3608 scale_image->type=image->type; 3609 if (status == MagickFalse) 3610 scale_image=DestroyImage(scale_image); 3611 return(scale_image); 3612 } 3613 3614 /* 3616 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 3617 % % 3618 % % 3619 % % 3620 % T h u m b n a i l I m a g e % 3621 % % 3622 % % 3623 % % 3624 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 3625 % 3626 % ThumbnailImage() changes the size of an image to the given dimensions and 3627 % removes any associated profiles. The goal is to produce small low cost 3628 % thumbnail images suited for display on the Web. 3629 % 3630 % The format of the ThumbnailImage method is: 3631 % 3632 % Image *ThumbnailImage(const Image *image,const size_t columns, 3633 % const size_t rows,ExceptionInfo *exception) 3634 % 3635 % A description of each parameter follows: 3636 % 3637 % o image: the image. 3638 % 3639 % o columns: the number of columns in the scaled image. 3640 % 3641 % o rows: the number of rows in the scaled image. 3642 % 3643 % o exception: return any errors or warnings in this structure. 3644 % 3645 */ 3646 MagickExport Image *ThumbnailImage(const Image *image,const size_t columns, 3647 const size_t rows,ExceptionInfo *exception) 3648 { 3649 #define SampleFactor 5 3650 3651 char 3652 value[MagickPathExtent]; 3653 3654 const char 3655 *name; 3656 3657 Image 3658 *thumbnail_image; 3659 3660 double 3661 x_factor, 3662 y_factor; 3663 3664 size_t 3665 version; 3666 3667 struct stat 3668 attributes; 3669 3670 assert(image != (Image *) NULL); 3671 assert(image->signature == MagickCoreSignature); 3672 if (image->debug != MagickFalse) 3673 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename); 3674 assert(exception != (ExceptionInfo *) NULL); 3675 assert(exception->signature == MagickCoreSignature); 3676 x_factor=(double) columns/(double) image->columns; 3677 y_factor=(double) rows/(double) image->rows; 3678 if ((x_factor*y_factor) > 0.1) 3679 thumbnail_image=ResizeImage(image,columns,rows,image->filter,exception); 3680 else 3681 if (((SampleFactor*columns) < 128) || ((SampleFactor*rows) < 128)) 3682 thumbnail_image=ResizeImage(image,columns,rows,image->filter,exception); 3683 else 3684 { 3685 Image 3686 *sample_image; 3687 3688 sample_image=SampleImage(image,SampleFactor*columns,SampleFactor*rows, 3689 exception); 3690 if (sample_image == (Image *) NULL) 3691 return((Image *) NULL); 3692 thumbnail_image=ResizeImage(sample_image,columns,rows,image->filter, 3693 exception); 3694 sample_image=DestroyImage(sample_image); 3695 } 3696 if (thumbnail_image == (Image *) NULL) 3697 return(thumbnail_image); 3698 (void) ParseAbsoluteGeometry("0x0+0+0",&thumbnail_image->page); 3699 if (thumbnail_image->alpha_trait == UndefinedPixelTrait) 3700 (void) SetImageAlphaChannel(thumbnail_image,OpaqueAlphaChannel,exception); 3701 thumbnail_image->depth=8; 3702 thumbnail_image->interlace=NoInterlace; 3703 /* 3704 Strip all profiles except color profiles. 3705 */ 3706 ResetImageProfileIterator(thumbnail_image); 3707 for (name=GetNextImageProfile(thumbnail_image); name != (const char *) NULL; ) 3708 { 3709 if ((LocaleCompare(name,"icc") != 0) && (LocaleCompare(name,"icm") != 0)) 3710 { 3711 (void) DeleteImageProfile(thumbnail_image,name); 3712 ResetImageProfileIterator(thumbnail_image); 3713 } 3714 name=GetNextImageProfile(thumbnail_image); 3715 } 3716 (void) DeleteImageProperty(thumbnail_image,"comment"); 3717 (void) CopyMagickString(value,image->magick_filename,MagickPathExtent); 3718 if (strstr(image->magick_filename,"//") == (char *) NULL) 3719 (void) FormatLocaleString(value,MagickPathExtent,"file://%s", 3720 image->magick_filename); 3721 (void) SetImageProperty(thumbnail_image,"Thumb::URI",value,exception); 3722 (void) CopyMagickString(value,image->magick_filename,MagickPathExtent); 3723 if ( GetPathAttributes(image->filename,&attributes) != MagickFalse ) 3724 { 3725 (void) FormatLocaleString(value,MagickPathExtent,"%.20g",(double) 3726 attributes.st_mtime); 3727 (void) SetImageProperty(thumbnail_image,"Thumb::MTime",value,exception); 3728 } 3729 (void) FormatLocaleString(value,MagickPathExtent,"%.20g",(double) 3730 attributes.st_mtime); 3731 (void) FormatMagickSize(GetBlobSize(image),MagickFalse,"B",MagickPathExtent, 3732 value); 3733 (void) SetImageProperty(thumbnail_image,"Thumb::Size",value,exception); 3734 (void) FormatLocaleString(value,MagickPathExtent,"image/%s",image->magick); 3735 LocaleLower(value); 3736 (void) SetImageProperty(thumbnail_image,"Thumb::Mimetype",value,exception); 3737 (void) SetImageProperty(thumbnail_image,"software",GetMagickVersion(&version), 3738 exception); 3739 (void) FormatLocaleString(value,MagickPathExtent,"%.20g",(double) 3740 image->magick_columns); 3741 (void) SetImageProperty(thumbnail_image,"Thumb::Image::Width",value, 3742 exception); 3743 (void) FormatLocaleString(value,MagickPathExtent,"%.20g",(double) 3744 image->magick_rows); 3745 (void) SetImageProperty(thumbnail_image,"Thumb::Image::Height",value, 3746 exception); 3747 (void) FormatLocaleString(value,MagickPathExtent,"%.20g",(double) 3748 GetImageListLength(image)); 3749 (void) SetImageProperty(thumbnail_image,"Thumb::Document::Pages",value, 3750 exception); 3751 return(thumbnail_image); 3752 } 3753