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