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