1/* 2Copyright (c) 2012, Jan Schlicht <jan.schlicht@gmail.com> 3 4Permission to use, copy, modify, and/or distribute this software for any purpose 5with or without fee is hereby granted, provided that the above copyright notice 6and this permission notice appear in all copies. 7 8THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH 9REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND 10FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, 11INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS 12OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER 13TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF 14THIS SOFTWARE. 15*/ 16 17// Package resize implements various image resizing methods. 18// 19// The package works with the Image interface described in the image package. 20// Various interpolation methods are provided and multiple processors may be 21// utilized in the computations. 22// 23// Example: 24// imgResized := resize.Resize(1000, 0, imgOld, resize.MitchellNetravali) 25package resize 26 27import ( 28 "image" 29 "runtime" 30 "sync" 31) 32 33// An InterpolationFunction provides the parameters that describe an 34// interpolation kernel. It returns the number of samples to take 35// and the kernel function to use for sampling. 36type InterpolationFunction int 37 38// InterpolationFunction constants 39const ( 40 // Nearest-neighbor interpolation 41 NearestNeighbor InterpolationFunction = iota 42 // Bilinear interpolation 43 Bilinear 44 // Bicubic interpolation (with cubic hermite spline) 45 Bicubic 46 // Mitchell-Netravali interpolation 47 MitchellNetravali 48 // Lanczos interpolation (a=2) 49 Lanczos2 50 // Lanczos interpolation (a=3) 51 Lanczos3 52) 53 54// kernal, returns an InterpolationFunctions taps and kernel. 55func (i InterpolationFunction) kernel() (int, func(float64) float64) { 56 switch i { 57 case Bilinear: 58 return 2, linear 59 case Bicubic: 60 return 4, cubic 61 case MitchellNetravali: 62 return 4, mitchellnetravali 63 case Lanczos2: 64 return 4, lanczos2 65 case Lanczos3: 66 return 6, lanczos3 67 default: 68 // Default to NearestNeighbor. 69 return 2, nearest 70 } 71} 72 73// values <1 will sharpen the image 74var blur = 1.0 75 76// Resize scales an image to new width and height using the interpolation function interp. 77// A new image with the given dimensions will be returned. 78// If one of the parameters width or height is set to 0, its size will be calculated so that 79// the aspect ratio is that of the originating image. 80// The resizing algorithm uses channels for parallel computation. 81// If the input image has width or height of 0, it is returned unchanged. 82func Resize(width, height uint, img image.Image, interp InterpolationFunction) image.Image { 83 scaleX, scaleY := calcFactors(width, height, float64(img.Bounds().Dx()), float64(img.Bounds().Dy())) 84 if width == 0 { 85 width = uint(0.7 + float64(img.Bounds().Dx())/scaleX) 86 } 87 if height == 0 { 88 height = uint(0.7 + float64(img.Bounds().Dy())/scaleY) 89 } 90 91 // Trivial case: return input image 92 if int(width) == img.Bounds().Dx() && int(height) == img.Bounds().Dy() { 93 return img 94 } 95 96 // Input image has no pixels 97 if img.Bounds().Dx() <= 0 || img.Bounds().Dy() <= 0 { 98 return img 99 } 100 101 if interp == NearestNeighbor { 102 return resizeNearest(width, height, scaleX, scaleY, img, interp) 103 } 104 105 taps, kernel := interp.kernel() 106 cpus := runtime.GOMAXPROCS(0) 107 wg := sync.WaitGroup{} 108 109 // Generic access to image.Image is slow in tight loops. 110 // The optimal access has to be determined from the concrete image type. 111 switch input := img.(type) { 112 case *image.RGBA: 113 // 8-bit precision 114 temp := image.NewRGBA(image.Rect(0, 0, input.Bounds().Dy(), int(width))) 115 result := image.NewRGBA(image.Rect(0, 0, int(width), int(height))) 116 117 // horizontal filter, results in transposed temporary image 118 coeffs, offset, filterLength := createWeights8(temp.Bounds().Dy(), taps, blur, scaleX, kernel) 119 wg.Add(cpus) 120 for i := 0; i < cpus; i++ { 121 slice := makeSlice(temp, i, cpus).(*image.RGBA) 122 go func() { 123 defer wg.Done() 124 resizeRGBA(input, slice, scaleX, coeffs, offset, filterLength) 125 }() 126 } 127 wg.Wait() 128 129 // horizontal filter on transposed image, result is not transposed 130 coeffs, offset, filterLength = createWeights8(result.Bounds().Dy(), taps, blur, scaleY, kernel) 131 wg.Add(cpus) 132 for i := 0; i < cpus; i++ { 133 slice := makeSlice(result, i, cpus).(*image.RGBA) 134 go func() { 135 defer wg.Done() 136 resizeRGBA(temp, slice, scaleY, coeffs, offset, filterLength) 137 }() 138 } 139 wg.Wait() 140 return result 141 case *image.NRGBA: 142 // 8-bit precision 143 temp := image.NewRGBA(image.Rect(0, 0, input.Bounds().Dy(), int(width))) 144 result := image.NewRGBA(image.Rect(0, 0, int(width), int(height))) 145 146 // horizontal filter, results in transposed temporary image 147 coeffs, offset, filterLength := createWeights8(temp.Bounds().Dy(), taps, blur, scaleX, kernel) 148 wg.Add(cpus) 149 for i := 0; i < cpus; i++ { 150 slice := makeSlice(temp, i, cpus).(*image.RGBA) 151 go func() { 152 defer wg.Done() 153 resizeNRGBA(input, slice, scaleX, coeffs, offset, filterLength) 154 }() 155 } 156 wg.Wait() 157 158 // horizontal filter on transposed image, result is not transposed 159 coeffs, offset, filterLength = createWeights8(result.Bounds().Dy(), taps, blur, scaleY, kernel) 160 wg.Add(cpus) 161 for i := 0; i < cpus; i++ { 162 slice := makeSlice(result, i, cpus).(*image.RGBA) 163 go func() { 164 defer wg.Done() 165 resizeRGBA(temp, slice, scaleY, coeffs, offset, filterLength) 166 }() 167 } 168 wg.Wait() 169 return result 170 171 case *image.YCbCr: 172 // 8-bit precision 173 // accessing the YCbCr arrays in a tight loop is slow. 174 // converting the image to ycc increases performance by 2x. 175 temp := newYCC(image.Rect(0, 0, input.Bounds().Dy(), int(width)), input.SubsampleRatio) 176 result := newYCC(image.Rect(0, 0, int(width), int(height)), image.YCbCrSubsampleRatio444) 177 178 coeffs, offset, filterLength := createWeights8(temp.Bounds().Dy(), taps, blur, scaleX, kernel) 179 in := imageYCbCrToYCC(input) 180 wg.Add(cpus) 181 for i := 0; i < cpus; i++ { 182 slice := makeSlice(temp, i, cpus).(*ycc) 183 go func() { 184 defer wg.Done() 185 resizeYCbCr(in, slice, scaleX, coeffs, offset, filterLength) 186 }() 187 } 188 wg.Wait() 189 190 coeffs, offset, filterLength = createWeights8(result.Bounds().Dy(), taps, blur, scaleY, kernel) 191 wg.Add(cpus) 192 for i := 0; i < cpus; i++ { 193 slice := makeSlice(result, i, cpus).(*ycc) 194 go func() { 195 defer wg.Done() 196 resizeYCbCr(temp, slice, scaleY, coeffs, offset, filterLength) 197 }() 198 } 199 wg.Wait() 200 return result.YCbCr() 201 case *image.RGBA64: 202 // 16-bit precision 203 temp := image.NewRGBA64(image.Rect(0, 0, input.Bounds().Dy(), int(width))) 204 result := image.NewRGBA64(image.Rect(0, 0, int(width), int(height))) 205 206 // horizontal filter, results in transposed temporary image 207 coeffs, offset, filterLength := createWeights16(temp.Bounds().Dy(), taps, blur, scaleX, kernel) 208 wg.Add(cpus) 209 for i := 0; i < cpus; i++ { 210 slice := makeSlice(temp, i, cpus).(*image.RGBA64) 211 go func() { 212 defer wg.Done() 213 resizeRGBA64(input, slice, scaleX, coeffs, offset, filterLength) 214 }() 215 } 216 wg.Wait() 217 218 // horizontal filter on transposed image, result is not transposed 219 coeffs, offset, filterLength = createWeights16(result.Bounds().Dy(), taps, blur, scaleY, kernel) 220 wg.Add(cpus) 221 for i := 0; i < cpus; i++ { 222 slice := makeSlice(result, i, cpus).(*image.RGBA64) 223 go func() { 224 defer wg.Done() 225 resizeRGBA64(temp, slice, scaleY, coeffs, offset, filterLength) 226 }() 227 } 228 wg.Wait() 229 return result 230 case *image.NRGBA64: 231 // 16-bit precision 232 temp := image.NewRGBA64(image.Rect(0, 0, input.Bounds().Dy(), int(width))) 233 result := image.NewRGBA64(image.Rect(0, 0, int(width), int(height))) 234 235 // horizontal filter, results in transposed temporary image 236 coeffs, offset, filterLength := createWeights16(temp.Bounds().Dy(), taps, blur, scaleX, kernel) 237 wg.Add(cpus) 238 for i := 0; i < cpus; i++ { 239 slice := makeSlice(temp, i, cpus).(*image.RGBA64) 240 go func() { 241 defer wg.Done() 242 resizeNRGBA64(input, slice, scaleX, coeffs, offset, filterLength) 243 }() 244 } 245 wg.Wait() 246 247 // horizontal filter on transposed image, result is not transposed 248 coeffs, offset, filterLength = createWeights16(result.Bounds().Dy(), taps, blur, scaleY, kernel) 249 wg.Add(cpus) 250 for i := 0; i < cpus; i++ { 251 slice := makeSlice(result, i, cpus).(*image.RGBA64) 252 go func() { 253 defer wg.Done() 254 resizeRGBA64(temp, slice, scaleY, coeffs, offset, filterLength) 255 }() 256 } 257 wg.Wait() 258 return result 259 case *image.Gray: 260 // 8-bit precision 261 temp := image.NewGray(image.Rect(0, 0, input.Bounds().Dy(), int(width))) 262 result := image.NewGray(image.Rect(0, 0, int(width), int(height))) 263 264 // horizontal filter, results in transposed temporary image 265 coeffs, offset, filterLength := createWeights8(temp.Bounds().Dy(), taps, blur, scaleX, kernel) 266 wg.Add(cpus) 267 for i := 0; i < cpus; i++ { 268 slice := makeSlice(temp, i, cpus).(*image.Gray) 269 go func() { 270 defer wg.Done() 271 resizeGray(input, slice, scaleX, coeffs, offset, filterLength) 272 }() 273 } 274 wg.Wait() 275 276 // horizontal filter on transposed image, result is not transposed 277 coeffs, offset, filterLength = createWeights8(result.Bounds().Dy(), taps, blur, scaleY, kernel) 278 wg.Add(cpus) 279 for i := 0; i < cpus; i++ { 280 slice := makeSlice(result, i, cpus).(*image.Gray) 281 go func() { 282 defer wg.Done() 283 resizeGray(temp, slice, scaleY, coeffs, offset, filterLength) 284 }() 285 } 286 wg.Wait() 287 return result 288 case *image.Gray16: 289 // 16-bit precision 290 temp := image.NewGray16(image.Rect(0, 0, input.Bounds().Dy(), int(width))) 291 result := image.NewGray16(image.Rect(0, 0, int(width), int(height))) 292 293 // horizontal filter, results in transposed temporary image 294 coeffs, offset, filterLength := createWeights16(temp.Bounds().Dy(), taps, blur, scaleX, kernel) 295 wg.Add(cpus) 296 for i := 0; i < cpus; i++ { 297 slice := makeSlice(temp, i, cpus).(*image.Gray16) 298 go func() { 299 defer wg.Done() 300 resizeGray16(input, slice, scaleX, coeffs, offset, filterLength) 301 }() 302 } 303 wg.Wait() 304 305 // horizontal filter on transposed image, result is not transposed 306 coeffs, offset, filterLength = createWeights16(result.Bounds().Dy(), taps, blur, scaleY, kernel) 307 wg.Add(cpus) 308 for i := 0; i < cpus; i++ { 309 slice := makeSlice(result, i, cpus).(*image.Gray16) 310 go func() { 311 defer wg.Done() 312 resizeGray16(temp, slice, scaleY, coeffs, offset, filterLength) 313 }() 314 } 315 wg.Wait() 316 return result 317 default: 318 // 16-bit precision 319 temp := image.NewRGBA64(image.Rect(0, 0, img.Bounds().Dy(), int(width))) 320 result := image.NewRGBA64(image.Rect(0, 0, int(width), int(height))) 321 322 // horizontal filter, results in transposed temporary image 323 coeffs, offset, filterLength := createWeights16(temp.Bounds().Dy(), taps, blur, scaleX, kernel) 324 wg.Add(cpus) 325 for i := 0; i < cpus; i++ { 326 slice := makeSlice(temp, i, cpus).(*image.RGBA64) 327 go func() { 328 defer wg.Done() 329 resizeGeneric(img, slice, scaleX, coeffs, offset, filterLength) 330 }() 331 } 332 wg.Wait() 333 334 // horizontal filter on transposed image, result is not transposed 335 coeffs, offset, filterLength = createWeights16(result.Bounds().Dy(), taps, blur, scaleY, kernel) 336 wg.Add(cpus) 337 for i := 0; i < cpus; i++ { 338 slice := makeSlice(result, i, cpus).(*image.RGBA64) 339 go func() { 340 defer wg.Done() 341 resizeRGBA64(temp, slice, scaleY, coeffs, offset, filterLength) 342 }() 343 } 344 wg.Wait() 345 return result 346 } 347} 348 349func resizeNearest(width, height uint, scaleX, scaleY float64, img image.Image, interp InterpolationFunction) image.Image { 350 taps, _ := interp.kernel() 351 cpus := runtime.GOMAXPROCS(0) 352 wg := sync.WaitGroup{} 353 354 switch input := img.(type) { 355 case *image.RGBA: 356 // 8-bit precision 357 temp := image.NewRGBA(image.Rect(0, 0, input.Bounds().Dy(), int(width))) 358 result := image.NewRGBA(image.Rect(0, 0, int(width), int(height))) 359 360 // horizontal filter, results in transposed temporary image 361 coeffs, offset, filterLength := createWeightsNearest(temp.Bounds().Dy(), taps, blur, scaleX) 362 wg.Add(cpus) 363 for i := 0; i < cpus; i++ { 364 slice := makeSlice(temp, i, cpus).(*image.RGBA) 365 go func() { 366 defer wg.Done() 367 nearestRGBA(input, slice, scaleX, coeffs, offset, filterLength) 368 }() 369 } 370 wg.Wait() 371 372 // horizontal filter on transposed image, result is not transposed 373 coeffs, offset, filterLength = createWeightsNearest(result.Bounds().Dy(), taps, blur, scaleY) 374 wg.Add(cpus) 375 for i := 0; i < cpus; i++ { 376 slice := makeSlice(result, i, cpus).(*image.RGBA) 377 go func() { 378 defer wg.Done() 379 nearestRGBA(temp, slice, scaleY, coeffs, offset, filterLength) 380 }() 381 } 382 wg.Wait() 383 return result 384 case *image.NRGBA: 385 // 8-bit precision 386 temp := image.NewNRGBA(image.Rect(0, 0, input.Bounds().Dy(), int(width))) 387 result := image.NewNRGBA(image.Rect(0, 0, int(width), int(height))) 388 389 // horizontal filter, results in transposed temporary image 390 coeffs, offset, filterLength := createWeightsNearest(temp.Bounds().Dy(), taps, blur, scaleX) 391 wg.Add(cpus) 392 for i := 0; i < cpus; i++ { 393 slice := makeSlice(temp, i, cpus).(*image.NRGBA) 394 go func() { 395 defer wg.Done() 396 nearestNRGBA(input, slice, scaleX, coeffs, offset, filterLength) 397 }() 398 } 399 wg.Wait() 400 401 // horizontal filter on transposed image, result is not transposed 402 coeffs, offset, filterLength = createWeightsNearest(result.Bounds().Dy(), taps, blur, scaleY) 403 wg.Add(cpus) 404 for i := 0; i < cpus; i++ { 405 slice := makeSlice(result, i, cpus).(*image.NRGBA) 406 go func() { 407 defer wg.Done() 408 nearestNRGBA(temp, slice, scaleY, coeffs, offset, filterLength) 409 }() 410 } 411 wg.Wait() 412 return result 413 case *image.YCbCr: 414 // 8-bit precision 415 // accessing the YCbCr arrays in a tight loop is slow. 416 // converting the image to ycc increases performance by 2x. 417 temp := newYCC(image.Rect(0, 0, input.Bounds().Dy(), int(width)), input.SubsampleRatio) 418 result := newYCC(image.Rect(0, 0, int(width), int(height)), image.YCbCrSubsampleRatio444) 419 420 coeffs, offset, filterLength := createWeightsNearest(temp.Bounds().Dy(), taps, blur, scaleX) 421 in := imageYCbCrToYCC(input) 422 wg.Add(cpus) 423 for i := 0; i < cpus; i++ { 424 slice := makeSlice(temp, i, cpus).(*ycc) 425 go func() { 426 defer wg.Done() 427 nearestYCbCr(in, slice, scaleX, coeffs, offset, filterLength) 428 }() 429 } 430 wg.Wait() 431 432 coeffs, offset, filterLength = createWeightsNearest(result.Bounds().Dy(), taps, blur, scaleY) 433 wg.Add(cpus) 434 for i := 0; i < cpus; i++ { 435 slice := makeSlice(result, i, cpus).(*ycc) 436 go func() { 437 defer wg.Done() 438 nearestYCbCr(temp, slice, scaleY, coeffs, offset, filterLength) 439 }() 440 } 441 wg.Wait() 442 return result.YCbCr() 443 case *image.RGBA64: 444 // 16-bit precision 445 temp := image.NewRGBA64(image.Rect(0, 0, input.Bounds().Dy(), int(width))) 446 result := image.NewRGBA64(image.Rect(0, 0, int(width), int(height))) 447 448 // horizontal filter, results in transposed temporary image 449 coeffs, offset, filterLength := createWeightsNearest(temp.Bounds().Dy(), taps, blur, scaleX) 450 wg.Add(cpus) 451 for i := 0; i < cpus; i++ { 452 slice := makeSlice(temp, i, cpus).(*image.RGBA64) 453 go func() { 454 defer wg.Done() 455 nearestRGBA64(input, slice, scaleX, coeffs, offset, filterLength) 456 }() 457 } 458 wg.Wait() 459 460 // horizontal filter on transposed image, result is not transposed 461 coeffs, offset, filterLength = createWeightsNearest(result.Bounds().Dy(), taps, blur, scaleY) 462 wg.Add(cpus) 463 for i := 0; i < cpus; i++ { 464 slice := makeSlice(result, i, cpus).(*image.RGBA64) 465 go func() { 466 defer wg.Done() 467 nearestRGBA64(temp, slice, scaleY, coeffs, offset, filterLength) 468 }() 469 } 470 wg.Wait() 471 return result 472 case *image.NRGBA64: 473 // 16-bit precision 474 temp := image.NewNRGBA64(image.Rect(0, 0, input.Bounds().Dy(), int(width))) 475 result := image.NewNRGBA64(image.Rect(0, 0, int(width), int(height))) 476 477 // horizontal filter, results in transposed temporary image 478 coeffs, offset, filterLength := createWeightsNearest(temp.Bounds().Dy(), taps, blur, scaleX) 479 wg.Add(cpus) 480 for i := 0; i < cpus; i++ { 481 slice := makeSlice(temp, i, cpus).(*image.NRGBA64) 482 go func() { 483 defer wg.Done() 484 nearestNRGBA64(input, slice, scaleX, coeffs, offset, filterLength) 485 }() 486 } 487 wg.Wait() 488 489 // horizontal filter on transposed image, result is not transposed 490 coeffs, offset, filterLength = createWeightsNearest(result.Bounds().Dy(), taps, blur, scaleY) 491 wg.Add(cpus) 492 for i := 0; i < cpus; i++ { 493 slice := makeSlice(result, i, cpus).(*image.NRGBA64) 494 go func() { 495 defer wg.Done() 496 nearestNRGBA64(temp, slice, scaleY, coeffs, offset, filterLength) 497 }() 498 } 499 wg.Wait() 500 return result 501 case *image.Gray: 502 // 8-bit precision 503 temp := image.NewGray(image.Rect(0, 0, input.Bounds().Dy(), int(width))) 504 result := image.NewGray(image.Rect(0, 0, int(width), int(height))) 505 506 // horizontal filter, results in transposed temporary image 507 coeffs, offset, filterLength := createWeightsNearest(temp.Bounds().Dy(), taps, blur, scaleX) 508 wg.Add(cpus) 509 for i := 0; i < cpus; i++ { 510 slice := makeSlice(temp, i, cpus).(*image.Gray) 511 go func() { 512 defer wg.Done() 513 nearestGray(input, slice, scaleX, coeffs, offset, filterLength) 514 }() 515 } 516 wg.Wait() 517 518 // horizontal filter on transposed image, result is not transposed 519 coeffs, offset, filterLength = createWeightsNearest(result.Bounds().Dy(), taps, blur, scaleY) 520 wg.Add(cpus) 521 for i := 0; i < cpus; i++ { 522 slice := makeSlice(result, i, cpus).(*image.Gray) 523 go func() { 524 defer wg.Done() 525 nearestGray(temp, slice, scaleY, coeffs, offset, filterLength) 526 }() 527 } 528 wg.Wait() 529 return result 530 case *image.Gray16: 531 // 16-bit precision 532 temp := image.NewGray16(image.Rect(0, 0, input.Bounds().Dy(), int(width))) 533 result := image.NewGray16(image.Rect(0, 0, int(width), int(height))) 534 535 // horizontal filter, results in transposed temporary image 536 coeffs, offset, filterLength := createWeightsNearest(temp.Bounds().Dy(), taps, blur, scaleX) 537 wg.Add(cpus) 538 for i := 0; i < cpus; i++ { 539 slice := makeSlice(temp, i, cpus).(*image.Gray16) 540 go func() { 541 defer wg.Done() 542 nearestGray16(input, slice, scaleX, coeffs, offset, filterLength) 543 }() 544 } 545 wg.Wait() 546 547 // horizontal filter on transposed image, result is not transposed 548 coeffs, offset, filterLength = createWeightsNearest(result.Bounds().Dy(), taps, blur, scaleY) 549 wg.Add(cpus) 550 for i := 0; i < cpus; i++ { 551 slice := makeSlice(result, i, cpus).(*image.Gray16) 552 go func() { 553 defer wg.Done() 554 nearestGray16(temp, slice, scaleY, coeffs, offset, filterLength) 555 }() 556 } 557 wg.Wait() 558 return result 559 default: 560 // 16-bit precision 561 temp := image.NewRGBA64(image.Rect(0, 0, img.Bounds().Dy(), int(width))) 562 result := image.NewRGBA64(image.Rect(0, 0, int(width), int(height))) 563 564 // horizontal filter, results in transposed temporary image 565 coeffs, offset, filterLength := createWeightsNearest(temp.Bounds().Dy(), taps, blur, scaleX) 566 wg.Add(cpus) 567 for i := 0; i < cpus; i++ { 568 slice := makeSlice(temp, i, cpus).(*image.RGBA64) 569 go func() { 570 defer wg.Done() 571 nearestGeneric(img, slice, scaleX, coeffs, offset, filterLength) 572 }() 573 } 574 wg.Wait() 575 576 // horizontal filter on transposed image, result is not transposed 577 coeffs, offset, filterLength = createWeightsNearest(result.Bounds().Dy(), taps, blur, scaleY) 578 wg.Add(cpus) 579 for i := 0; i < cpus; i++ { 580 slice := makeSlice(result, i, cpus).(*image.RGBA64) 581 go func() { 582 defer wg.Done() 583 nearestRGBA64(temp, slice, scaleY, coeffs, offset, filterLength) 584 }() 585 } 586 wg.Wait() 587 return result 588 } 589 590} 591 592// Calculates scaling factors using old and new image dimensions. 593func calcFactors(width, height uint, oldWidth, oldHeight float64) (scaleX, scaleY float64) { 594 if width == 0 { 595 if height == 0 { 596 scaleX = 1.0 597 scaleY = 1.0 598 } else { 599 scaleY = oldHeight / float64(height) 600 scaleX = scaleY 601 } 602 } else { 603 scaleX = oldWidth / float64(width) 604 if height == 0 { 605 scaleY = scaleX 606 } else { 607 scaleY = oldHeight / float64(height) 608 } 609 } 610 return 611} 612 613type imageWithSubImage interface { 614 image.Image 615 SubImage(image.Rectangle) image.Image 616} 617 618func makeSlice(img imageWithSubImage, i, n int) image.Image { 619 return img.SubImage(image.Rect(img.Bounds().Min.X, img.Bounds().Min.Y+i*img.Bounds().Dy()/n, img.Bounds().Max.X, img.Bounds().Min.Y+(i+1)*img.Bounds().Dy()/n)) 620} 621