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}