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