resize.go (14148B)
1 package imaging 2 3 import ( 4 "image" 5 "math" 6 ) 7 8 type indexWeight struct { 9 index int 10 weight float64 11 } 12 13 func precomputeWeights(dstSize, srcSize int, filter ResampleFilter) [][]indexWeight { 14 du := float64(srcSize) / float64(dstSize) 15 scale := du 16 if scale < 1.0 { 17 scale = 1.0 18 } 19 ru := math.Ceil(scale * filter.Support) 20 21 out := make([][]indexWeight, dstSize) 22 tmp := make([]indexWeight, 0, dstSize*int(ru+2)*2) 23 24 for v := 0; v < dstSize; v++ { 25 fu := (float64(v)+0.5)*du - 0.5 26 27 begin := int(math.Ceil(fu - ru)) 28 if begin < 0 { 29 begin = 0 30 } 31 end := int(math.Floor(fu + ru)) 32 if end > srcSize-1 { 33 end = srcSize - 1 34 } 35 36 var sum float64 37 for u := begin; u <= end; u++ { 38 w := filter.Kernel((float64(u) - fu) / scale) 39 if w != 0 { 40 sum += w 41 tmp = append(tmp, indexWeight{index: u, weight: w}) 42 } 43 } 44 if sum != 0 { 45 for i := range tmp { 46 tmp[i].weight /= sum 47 } 48 } 49 50 out[v] = tmp 51 tmp = tmp[len(tmp):] 52 } 53 54 return out 55 } 56 57 // Resize resizes the image to the specified width and height using the specified resampling 58 // filter and returns the transformed image. If one of width or height is 0, the image aspect 59 // ratio is preserved. 60 // 61 // Example: 62 // 63 // dstImage := imaging.Resize(srcImage, 800, 600, imaging.Lanczos) 64 // 65 func Resize(img image.Image, width, height int, filter ResampleFilter) *image.NRGBA { 66 dstW, dstH := width, height 67 if dstW < 0 || dstH < 0 { 68 return &image.NRGBA{} 69 } 70 if dstW == 0 && dstH == 0 { 71 return &image.NRGBA{} 72 } 73 74 srcW := img.Bounds().Dx() 75 srcH := img.Bounds().Dy() 76 if srcW <= 0 || srcH <= 0 { 77 return &image.NRGBA{} 78 } 79 80 // If new width or height is 0 then preserve aspect ratio, minimum 1px. 81 if dstW == 0 { 82 tmpW := float64(dstH) * float64(srcW) / float64(srcH) 83 dstW = int(math.Max(1.0, math.Floor(tmpW+0.5))) 84 } 85 if dstH == 0 { 86 tmpH := float64(dstW) * float64(srcH) / float64(srcW) 87 dstH = int(math.Max(1.0, math.Floor(tmpH+0.5))) 88 } 89 90 if filter.Support <= 0 { 91 // Nearest-neighbor special case. 92 return resizeNearest(img, dstW, dstH) 93 } 94 95 if srcW != dstW && srcH != dstH { 96 return resizeVertical(resizeHorizontal(img, dstW, filter), dstH, filter) 97 } 98 if srcW != dstW { 99 return resizeHorizontal(img, dstW, filter) 100 } 101 if srcH != dstH { 102 return resizeVertical(img, dstH, filter) 103 } 104 return Clone(img) 105 } 106 107 func resizeHorizontal(img image.Image, width int, filter ResampleFilter) *image.NRGBA { 108 src := newScanner(img) 109 dst := image.NewNRGBA(image.Rect(0, 0, width, src.h)) 110 weights := precomputeWeights(width, src.w, filter) 111 parallel(0, src.h, func(ys <-chan int) { 112 scanLine := make([]uint8, src.w*4) 113 for y := range ys { 114 src.scan(0, y, src.w, y+1, scanLine) 115 j0 := y * dst.Stride 116 for x := range weights { 117 var r, g, b, a float64 118 for _, w := range weights[x] { 119 i := w.index * 4 120 s := scanLine[i : i+4 : i+4] 121 aw := float64(s[3]) * w.weight 122 r += float64(s[0]) * aw 123 g += float64(s[1]) * aw 124 b += float64(s[2]) * aw 125 a += aw 126 } 127 if a != 0 { 128 aInv := 1 / a 129 j := j0 + x*4 130 d := dst.Pix[j : j+4 : j+4] 131 d[0] = clamp(r * aInv) 132 d[1] = clamp(g * aInv) 133 d[2] = clamp(b * aInv) 134 d[3] = clamp(a) 135 } 136 } 137 } 138 }) 139 return dst 140 } 141 142 func resizeVertical(img image.Image, height int, filter ResampleFilter) *image.NRGBA { 143 src := newScanner(img) 144 dst := image.NewNRGBA(image.Rect(0, 0, src.w, height)) 145 weights := precomputeWeights(height, src.h, filter) 146 parallel(0, src.w, func(xs <-chan int) { 147 scanLine := make([]uint8, src.h*4) 148 for x := range xs { 149 src.scan(x, 0, x+1, src.h, scanLine) 150 for y := range weights { 151 var r, g, b, a float64 152 for _, w := range weights[y] { 153 i := w.index * 4 154 s := scanLine[i : i+4 : i+4] 155 aw := float64(s[3]) * w.weight 156 r += float64(s[0]) * aw 157 g += float64(s[1]) * aw 158 b += float64(s[2]) * aw 159 a += aw 160 } 161 if a != 0 { 162 aInv := 1 / a 163 j := y*dst.Stride + x*4 164 d := dst.Pix[j : j+4 : j+4] 165 d[0] = clamp(r * aInv) 166 d[1] = clamp(g * aInv) 167 d[2] = clamp(b * aInv) 168 d[3] = clamp(a) 169 } 170 } 171 } 172 }) 173 return dst 174 } 175 176 // resizeNearest is a fast nearest-neighbor resize, no filtering. 177 func resizeNearest(img image.Image, width, height int) *image.NRGBA { 178 dst := image.NewNRGBA(image.Rect(0, 0, width, height)) 179 dx := float64(img.Bounds().Dx()) / float64(width) 180 dy := float64(img.Bounds().Dy()) / float64(height) 181 182 if dx > 1 && dy > 1 { 183 src := newScanner(img) 184 parallel(0, height, func(ys <-chan int) { 185 for y := range ys { 186 srcY := int((float64(y) + 0.5) * dy) 187 dstOff := y * dst.Stride 188 for x := 0; x < width; x++ { 189 srcX := int((float64(x) + 0.5) * dx) 190 src.scan(srcX, srcY, srcX+1, srcY+1, dst.Pix[dstOff:dstOff+4]) 191 dstOff += 4 192 } 193 } 194 }) 195 } else { 196 src := toNRGBA(img) 197 parallel(0, height, func(ys <-chan int) { 198 for y := range ys { 199 srcY := int((float64(y) + 0.5) * dy) 200 srcOff0 := srcY * src.Stride 201 dstOff := y * dst.Stride 202 for x := 0; x < width; x++ { 203 srcX := int((float64(x) + 0.5) * dx) 204 srcOff := srcOff0 + srcX*4 205 copy(dst.Pix[dstOff:dstOff+4], src.Pix[srcOff:srcOff+4]) 206 dstOff += 4 207 } 208 } 209 }) 210 } 211 212 return dst 213 } 214 215 // Fit scales down the image using the specified resample filter to fit the specified 216 // maximum width and height and returns the transformed image. 217 // 218 // Example: 219 // 220 // dstImage := imaging.Fit(srcImage, 800, 600, imaging.Lanczos) 221 // 222 func Fit(img image.Image, width, height int, filter ResampleFilter) *image.NRGBA { 223 maxW, maxH := width, height 224 225 if maxW <= 0 || maxH <= 0 { 226 return &image.NRGBA{} 227 } 228 229 srcBounds := img.Bounds() 230 srcW := srcBounds.Dx() 231 srcH := srcBounds.Dy() 232 233 if srcW <= 0 || srcH <= 0 { 234 return &image.NRGBA{} 235 } 236 237 if srcW <= maxW && srcH <= maxH { 238 return Clone(img) 239 } 240 241 srcAspectRatio := float64(srcW) / float64(srcH) 242 maxAspectRatio := float64(maxW) / float64(maxH) 243 244 var newW, newH int 245 if srcAspectRatio > maxAspectRatio { 246 newW = maxW 247 newH = int(float64(newW) / srcAspectRatio) 248 } else { 249 newH = maxH 250 newW = int(float64(newH) * srcAspectRatio) 251 } 252 253 return Resize(img, newW, newH, filter) 254 } 255 256 // Fill creates an image with the specified dimensions and fills it with the scaled source image. 257 // To achieve the correct aspect ratio without stretching, the source image will be cropped. 258 // 259 // Example: 260 // 261 // dstImage := imaging.Fill(srcImage, 800, 600, imaging.Center, imaging.Lanczos) 262 // 263 func Fill(img image.Image, width, height int, anchor Anchor, filter ResampleFilter) *image.NRGBA { 264 dstW, dstH := width, height 265 266 if dstW <= 0 || dstH <= 0 { 267 return &image.NRGBA{} 268 } 269 270 srcBounds := img.Bounds() 271 srcW := srcBounds.Dx() 272 srcH := srcBounds.Dy() 273 274 if srcW <= 0 || srcH <= 0 { 275 return &image.NRGBA{} 276 } 277 278 if srcW == dstW && srcH == dstH { 279 return Clone(img) 280 } 281 282 if srcW >= 100 && srcH >= 100 { 283 return cropAndResize(img, dstW, dstH, anchor, filter) 284 } 285 return resizeAndCrop(img, dstW, dstH, anchor, filter) 286 } 287 288 // cropAndResize crops the image to the smallest possible size that has the required aspect ratio using 289 // the given anchor point, then scales it to the specified dimensions and returns the transformed image. 290 // 291 // This is generally faster than resizing first, but may result in inaccuracies when used on small source images. 292 func cropAndResize(img image.Image, width, height int, anchor Anchor, filter ResampleFilter) *image.NRGBA { 293 dstW, dstH := width, height 294 295 srcBounds := img.Bounds() 296 srcW := srcBounds.Dx() 297 srcH := srcBounds.Dy() 298 srcAspectRatio := float64(srcW) / float64(srcH) 299 dstAspectRatio := float64(dstW) / float64(dstH) 300 301 var tmp *image.NRGBA 302 if srcAspectRatio < dstAspectRatio { 303 cropH := float64(srcW) * float64(dstH) / float64(dstW) 304 tmp = CropAnchor(img, srcW, int(math.Max(1, cropH)+0.5), anchor) 305 } else { 306 cropW := float64(srcH) * float64(dstW) / float64(dstH) 307 tmp = CropAnchor(img, int(math.Max(1, cropW)+0.5), srcH, anchor) 308 } 309 310 return Resize(tmp, dstW, dstH, filter) 311 } 312 313 // resizeAndCrop resizes the image to the smallest possible size that will cover the specified dimensions, 314 // crops the resized image to the specified dimensions using the given anchor point and returns 315 // the transformed image. 316 func resizeAndCrop(img image.Image, width, height int, anchor Anchor, filter ResampleFilter) *image.NRGBA { 317 dstW, dstH := width, height 318 319 srcBounds := img.Bounds() 320 srcW := srcBounds.Dx() 321 srcH := srcBounds.Dy() 322 srcAspectRatio := float64(srcW) / float64(srcH) 323 dstAspectRatio := float64(dstW) / float64(dstH) 324 325 var tmp *image.NRGBA 326 if srcAspectRatio < dstAspectRatio { 327 tmp = Resize(img, dstW, 0, filter) 328 } else { 329 tmp = Resize(img, 0, dstH, filter) 330 } 331 332 return CropAnchor(tmp, dstW, dstH, anchor) 333 } 334 335 // Thumbnail scales the image up or down using the specified resample filter, crops it 336 // to the specified width and hight and returns the transformed image. 337 // 338 // Example: 339 // 340 // dstImage := imaging.Thumbnail(srcImage, 100, 100, imaging.Lanczos) 341 // 342 func Thumbnail(img image.Image, width, height int, filter ResampleFilter) *image.NRGBA { 343 return Fill(img, width, height, Center, filter) 344 } 345 346 // ResampleFilter specifies a resampling filter to be used for image resizing. 347 // 348 // General filter recommendations: 349 // 350 // - Lanczos 351 // A high-quality resampling filter for photographic images yielding sharp results. 352 // 353 // - CatmullRom 354 // A sharp cubic filter that is faster than Lanczos filter while providing similar results. 355 // 356 // - MitchellNetravali 357 // A cubic filter that produces smoother results with less ringing artifacts than CatmullRom. 358 // 359 // - Linear 360 // Bilinear resampling filter, produces a smooth output. Faster than cubic filters. 361 // 362 // - Box 363 // Simple and fast averaging filter appropriate for downscaling. 364 // When upscaling it's similar to NearestNeighbor. 365 // 366 // - NearestNeighbor 367 // Fastest resampling filter, no antialiasing. 368 // 369 type ResampleFilter struct { 370 Support float64 371 Kernel func(float64) float64 372 } 373 374 // NearestNeighbor is a nearest-neighbor filter (no anti-aliasing). 375 var NearestNeighbor ResampleFilter 376 377 // Box filter (averaging pixels). 378 var Box ResampleFilter 379 380 // Linear filter. 381 var Linear ResampleFilter 382 383 // Hermite cubic spline filter (BC-spline; B=0; C=0). 384 var Hermite ResampleFilter 385 386 // MitchellNetravali is Mitchell-Netravali cubic filter (BC-spline; B=1/3; C=1/3). 387 var MitchellNetravali ResampleFilter 388 389 // CatmullRom is a Catmull-Rom - sharp cubic filter (BC-spline; B=0; C=0.5). 390 var CatmullRom ResampleFilter 391 392 // BSpline is a smooth cubic filter (BC-spline; B=1; C=0). 393 var BSpline ResampleFilter 394 395 // Gaussian is a Gaussian blurring filter. 396 var Gaussian ResampleFilter 397 398 // Bartlett is a Bartlett-windowed sinc filter (3 lobes). 399 var Bartlett ResampleFilter 400 401 // Lanczos filter (3 lobes). 402 var Lanczos ResampleFilter 403 404 // Hann is a Hann-windowed sinc filter (3 lobes). 405 var Hann ResampleFilter 406 407 // Hamming is a Hamming-windowed sinc filter (3 lobes). 408 var Hamming ResampleFilter 409 410 // Blackman is a Blackman-windowed sinc filter (3 lobes). 411 var Blackman ResampleFilter 412 413 // Welch is a Welch-windowed sinc filter (parabolic window, 3 lobes). 414 var Welch ResampleFilter 415 416 // Cosine is a Cosine-windowed sinc filter (3 lobes). 417 var Cosine ResampleFilter 418 419 func bcspline(x, b, c float64) float64 { 420 var y float64 421 x = math.Abs(x) 422 if x < 1.0 { 423 y = ((12-9*b-6*c)*x*x*x + (-18+12*b+6*c)*x*x + (6 - 2*b)) / 6 424 } else if x < 2.0 { 425 y = ((-b-6*c)*x*x*x + (6*b+30*c)*x*x + (-12*b-48*c)*x + (8*b + 24*c)) / 6 426 } 427 return y 428 } 429 430 func sinc(x float64) float64 { 431 if x == 0 { 432 return 1 433 } 434 return math.Sin(math.Pi*x) / (math.Pi * x) 435 } 436 437 func init() { 438 NearestNeighbor = ResampleFilter{ 439 Support: 0.0, // special case - not applying the filter 440 } 441 442 Box = ResampleFilter{ 443 Support: 0.5, 444 Kernel: func(x float64) float64 { 445 x = math.Abs(x) 446 if x <= 0.5 { 447 return 1.0 448 } 449 return 0 450 }, 451 } 452 453 Linear = ResampleFilter{ 454 Support: 1.0, 455 Kernel: func(x float64) float64 { 456 x = math.Abs(x) 457 if x < 1.0 { 458 return 1.0 - x 459 } 460 return 0 461 }, 462 } 463 464 Hermite = ResampleFilter{ 465 Support: 1.0, 466 Kernel: func(x float64) float64 { 467 x = math.Abs(x) 468 if x < 1.0 { 469 return bcspline(x, 0.0, 0.0) 470 } 471 return 0 472 }, 473 } 474 475 MitchellNetravali = ResampleFilter{ 476 Support: 2.0, 477 Kernel: func(x float64) float64 { 478 x = math.Abs(x) 479 if x < 2.0 { 480 return bcspline(x, 1.0/3.0, 1.0/3.0) 481 } 482 return 0 483 }, 484 } 485 486 CatmullRom = ResampleFilter{ 487 Support: 2.0, 488 Kernel: func(x float64) float64 { 489 x = math.Abs(x) 490 if x < 2.0 { 491 return bcspline(x, 0.0, 0.5) 492 } 493 return 0 494 }, 495 } 496 497 BSpline = ResampleFilter{ 498 Support: 2.0, 499 Kernel: func(x float64) float64 { 500 x = math.Abs(x) 501 if x < 2.0 { 502 return bcspline(x, 1.0, 0.0) 503 } 504 return 0 505 }, 506 } 507 508 Gaussian = ResampleFilter{ 509 Support: 2.0, 510 Kernel: func(x float64) float64 { 511 x = math.Abs(x) 512 if x < 2.0 { 513 return math.Exp(-2 * x * x) 514 } 515 return 0 516 }, 517 } 518 519 Bartlett = ResampleFilter{ 520 Support: 3.0, 521 Kernel: func(x float64) float64 { 522 x = math.Abs(x) 523 if x < 3.0 { 524 return sinc(x) * (3.0 - x) / 3.0 525 } 526 return 0 527 }, 528 } 529 530 Lanczos = ResampleFilter{ 531 Support: 3.0, 532 Kernel: func(x float64) float64 { 533 x = math.Abs(x) 534 if x < 3.0 { 535 return sinc(x) * sinc(x/3.0) 536 } 537 return 0 538 }, 539 } 540 541 Hann = ResampleFilter{ 542 Support: 3.0, 543 Kernel: func(x float64) float64 { 544 x = math.Abs(x) 545 if x < 3.0 { 546 return sinc(x) * (0.5 + 0.5*math.Cos(math.Pi*x/3.0)) 547 } 548 return 0 549 }, 550 } 551 552 Hamming = ResampleFilter{ 553 Support: 3.0, 554 Kernel: func(x float64) float64 { 555 x = math.Abs(x) 556 if x < 3.0 { 557 return sinc(x) * (0.54 + 0.46*math.Cos(math.Pi*x/3.0)) 558 } 559 return 0 560 }, 561 } 562 563 Blackman = ResampleFilter{ 564 Support: 3.0, 565 Kernel: func(x float64) float64 { 566 x = math.Abs(x) 567 if x < 3.0 { 568 return sinc(x) * (0.42 - 0.5*math.Cos(math.Pi*x/3.0+math.Pi) + 0.08*math.Cos(2.0*math.Pi*x/3.0)) 569 } 570 return 0 571 }, 572 } 573 574 Welch = ResampleFilter{ 575 Support: 3.0, 576 Kernel: func(x float64) float64 { 577 x = math.Abs(x) 578 if x < 3.0 { 579 return sinc(x) * (1.0 - (x * x / 9.0)) 580 } 581 return 0 582 }, 583 } 584 585 Cosine = ResampleFilter{ 586 Support: 3.0, 587 Kernel: func(x float64) float64 { 588 x = math.Abs(x) 589 if x < 3.0 { 590 return sinc(x) * math.Cos((math.Pi/2.0)*(x/3.0)) 591 } 592 return 0 593 }, 594 } 595 }