Browse Source

Optimized Nearest-Neighbor function - 2x faster

Charlie Vieth 10 years ago
parent
commit
427b8d133e
4 changed files with 520 additions and 31 deletions
  1. 27
    2
      filters.go
  2. 228
    0
      nearest.go
  3. 41
    0
      nearest_test.go
  4. 224
    29
      resize.go

+ 27
- 2
filters.go View File

@@ -89,8 +89,9 @@ func createWeights8(dy, minx, filterLength int, blur, scale float64, kernel func
89 89
 	for y := 0; y < dy; y++ {
90 90
 		interpX := scale*(float64(y)+0.5) + float64(minx)
91 91
 		start[y] = int(interpX) - filterLength/2 + 1
92
+		interpX -= float64(start[y])
92 93
 		for i := 0; i < filterLength; i++ {
93
-			in := (interpX - float64(start[y]) - float64(i)) * filterFactor
94
+			in := (interpX - float64(i)) * filterFactor
94 95
 			coeffs[y*filterLength+i] = int16(kernel(in) * 256)
95 96
 		}
96 97
 	}
@@ -108,11 +109,35 @@ func createWeights16(dy, minx, filterLength int, blur, scale float64, kernel fun
108 109
 	for y := 0; y < dy; y++ {
109 110
 		interpX := scale*(float64(y)+0.5) + float64(minx)
110 111
 		start[y] = int(interpX) - filterLength/2 + 1
112
+		interpX -= float64(start[y])
111 113
 		for i := 0; i < filterLength; i++ {
112
-			in := (interpX - float64(start[y]) - float64(i)) * filterFactor
114
+			in := (interpX - float64(i)) * filterFactor
113 115
 			coeffs[y*filterLength+i] = int32(kernel(in) * 65536)
114 116
 		}
115 117
 	}
116 118
 
117 119
 	return coeffs, start, filterLength
118 120
 }
121
+
122
+func createWeightsNearest(dy, minx, filterLength int, blur, scale float64) ([]bool, []int, int) {
123
+	filterLength = filterLength * int(math.Max(math.Ceil(blur*scale), 1))
124
+	filterFactor := math.Min(1./(blur*scale), 1)
125
+
126
+	coeffs := make([]bool, dy*filterLength)
127
+	start := make([]int, dy)
128
+	for y := 0; y < dy; y++ {
129
+		interpX := scale*(float64(y)+0.5) + float64(minx)
130
+		start[y] = int(interpX) - filterLength/2 + 1
131
+		interpX -= float64(start[y])
132
+		for i := 0; i < filterLength; i++ {
133
+			in := (interpX - float64(i)) * filterFactor
134
+			if in >= -0.5 && in < 0.5 {
135
+				coeffs[y*filterLength+i] = true
136
+			} else {
137
+				coeffs[y*filterLength+i] = false
138
+			}
139
+		}
140
+	}
141
+
142
+	return coeffs, start, filterLength
143
+}

+ 228
- 0
nearest.go View File

@@ -0,0 +1,228 @@
1
+package resize
2
+
3
+import "image"
4
+
5
+func floatToUint8(x float32) uint8 {
6
+	// Nearest-neighbor values are always
7
+	// positive no need to check lower-bound.
8
+	if x > 0xfe {
9
+		return 0xff
10
+	}
11
+	return uint8(x)
12
+}
13
+
14
+func floatToUint16(x float32) uint16 {
15
+	if x > 0xfffe {
16
+		return 0xffff
17
+	}
18
+	return uint16(x)
19
+}
20
+
21
+func nearestGeneric(in image.Image, out *image.RGBA64, scale float64, coeffs []bool, offset []int, filterLength int) {
22
+	oldBounds := in.Bounds()
23
+	newBounds := out.Bounds()
24
+
25
+	for x := newBounds.Min.X; x < newBounds.Max.X; x++ {
26
+		for y := newBounds.Min.Y; y < newBounds.Max.Y; y++ {
27
+			var rgba [4]float32
28
+			var sum float32
29
+			start := offset[y]
30
+			ci := (y - newBounds.Min.Y) * filterLength
31
+			for i := 0; i < filterLength; i++ {
32
+				if coeffs[ci+i] {
33
+					xi := start + i
34
+					switch {
35
+					case uint(xi) < uint(oldBounds.Max.X):
36
+						break
37
+					case xi >= oldBounds.Max.X:
38
+						xi = oldBounds.Min.X
39
+					default:
40
+						xi = oldBounds.Max.X - 1
41
+					}
42
+					r, g, b, a := in.At(xi, x).RGBA()
43
+					rgba[0] += float32(r)
44
+					rgba[1] += float32(g)
45
+					rgba[2] += float32(b)
46
+					rgba[3] += float32(a)
47
+					sum++
48
+				}
49
+			}
50
+
51
+			offset := (y-newBounds.Min.Y)*out.Stride + (x-newBounds.Min.X)*8
52
+			value := floatToUint16(rgba[0] / sum)
53
+			out.Pix[offset+0] = uint8(value >> 8)
54
+			out.Pix[offset+1] = uint8(value)
55
+			value = floatToUint16(rgba[1] / sum)
56
+			out.Pix[offset+2] = uint8(value >> 8)
57
+			out.Pix[offset+3] = uint8(value)
58
+			value = floatToUint16(rgba[2] / sum)
59
+			out.Pix[offset+4] = uint8(value >> 8)
60
+			out.Pix[offset+5] = uint8(value)
61
+			value = floatToUint16(rgba[3] / sum)
62
+			out.Pix[offset+6] = uint8(value >> 8)
63
+			out.Pix[offset+7] = uint8(value)
64
+		}
65
+	}
66
+}
67
+
68
+func nearestRGBA(in *image.RGBA, out *image.RGBA, scale float64, coeffs []bool, offset []int, filterLength int) {
69
+	oldBounds := in.Bounds()
70
+	newBounds := out.Bounds()
71
+	minX := oldBounds.Min.X * 4
72
+	maxX := (oldBounds.Max.X - oldBounds.Min.X - 1) * 4
73
+
74
+	for x := newBounds.Min.X; x < newBounds.Max.X; x++ {
75
+		row := in.Pix[(x-oldBounds.Min.Y)*in.Stride:]
76
+		for y := newBounds.Min.Y; y < newBounds.Max.Y; y++ {
77
+			var rgba [4]float32
78
+			var sum float32
79
+			start := offset[y]
80
+			ci := (y - newBounds.Min.Y) * filterLength
81
+			for i := 0; i < filterLength; i++ {
82
+				if coeffs[ci+i] {
83
+					xi := start + i
84
+					switch {
85
+					case uint(xi) < uint(oldBounds.Max.X):
86
+						xi *= 4
87
+					case xi >= oldBounds.Max.X:
88
+						xi = maxX
89
+					default:
90
+						xi = minX
91
+					}
92
+					rgba[0] += float32(row[xi+0])
93
+					rgba[1] += float32(row[xi+1])
94
+					rgba[2] += float32(row[xi+2])
95
+					rgba[3] += float32(row[xi+3])
96
+					sum++
97
+				}
98
+			}
99
+
100
+			xo := (y-newBounds.Min.Y)*out.Stride + (x-newBounds.Min.X)*4
101
+			out.Pix[xo+0] = floatToUint8(rgba[0] / sum)
102
+			out.Pix[xo+1] = floatToUint8(rgba[1] / sum)
103
+			out.Pix[xo+2] = floatToUint8(rgba[2] / sum)
104
+			out.Pix[xo+3] = floatToUint8(rgba[3] / sum)
105
+		}
106
+	}
107
+}
108
+
109
+func nearestRGBA64(in *image.RGBA64, out *image.RGBA64, scale float64, coeffs []bool, offset []int, filterLength int) {
110
+	oldBounds := in.Bounds()
111
+	newBounds := out.Bounds()
112
+	minX := oldBounds.Min.X * 8
113
+	maxX := (oldBounds.Max.X - oldBounds.Min.X - 1) * 8
114
+
115
+	for x := newBounds.Min.X; x < newBounds.Max.X; x++ {
116
+		row := in.Pix[(x-oldBounds.Min.Y)*in.Stride:]
117
+		for y := newBounds.Min.Y; y < newBounds.Max.Y; y++ {
118
+			var rgba [4]float32
119
+			var sum float32
120
+			start := offset[y]
121
+			ci := (y - newBounds.Min.Y) * filterLength
122
+			for i := 0; i < filterLength; i++ {
123
+				if coeffs[ci+i] {
124
+					xi := start + i
125
+					switch {
126
+					case uint(xi) < uint(oldBounds.Max.X):
127
+						xi *= 8
128
+					case xi >= oldBounds.Max.X:
129
+						xi = maxX
130
+					default:
131
+						xi = minX
132
+					}
133
+					rgba[0] += float32(uint16(row[xi+0])<<8 | uint16(row[xi+1]))
134
+					rgba[1] += float32(uint16(row[xi+2])<<8 | uint16(row[xi+3]))
135
+					rgba[2] += float32(uint16(row[xi+4])<<8 | uint16(row[xi+5]))
136
+					rgba[3] += float32(uint16(row[xi+6])<<8 | uint16(row[xi+7]))
137
+					sum++
138
+				}
139
+			}
140
+
141
+			xo := (y-newBounds.Min.Y)*out.Stride + (x-newBounds.Min.X)*8
142
+			value := floatToUint16(rgba[0] / sum)
143
+			out.Pix[xo+0] = uint8(value >> 8)
144
+			out.Pix[xo+1] = uint8(value)
145
+			value = floatToUint16(rgba[1] / sum)
146
+			out.Pix[xo+2] = uint8(value >> 8)
147
+			out.Pix[xo+3] = uint8(value)
148
+			value = floatToUint16(rgba[2] / sum)
149
+			out.Pix[xo+4] = uint8(value >> 8)
150
+			out.Pix[xo+5] = uint8(value)
151
+			value = floatToUint16(rgba[3] / sum)
152
+			out.Pix[xo+6] = uint8(value >> 8)
153
+			out.Pix[xo+7] = uint8(value)
154
+		}
155
+	}
156
+}
157
+
158
+func nearestGray(in *image.Gray, out *image.Gray, scale float64, coeffs []bool, offset []int, filterLength int) {
159
+	oldBounds := in.Bounds()
160
+	newBounds := out.Bounds()
161
+	minX := oldBounds.Min.X
162
+	maxX := (oldBounds.Max.X - oldBounds.Min.X - 1)
163
+
164
+	for x := newBounds.Min.X; x < newBounds.Max.X; x++ {
165
+		row := in.Pix[(x-oldBounds.Min.Y)*in.Stride:]
166
+		for y := newBounds.Min.Y; y < newBounds.Max.Y; y++ {
167
+			var gray float32
168
+			var sum float32
169
+			start := offset[y]
170
+			ci := (y - newBounds.Min.Y) * filterLength
171
+			for i := 0; i < filterLength; i++ {
172
+				if coeffs[ci+i] {
173
+					xi := start + i
174
+					switch {
175
+					case uint(xi) < uint(oldBounds.Max.X):
176
+						break
177
+					case xi >= oldBounds.Max.X:
178
+						xi = maxX
179
+					default:
180
+						xi = minX
181
+					}
182
+					gray += float32(row[xi])
183
+					sum++
184
+				}
185
+			}
186
+
187
+			offset := (y-newBounds.Min.Y)*out.Stride + (x - newBounds.Min.X)
188
+			out.Pix[offset] = floatToUint8(gray / sum)
189
+		}
190
+	}
191
+}
192
+
193
+func nearestGray16(in *image.Gray16, out *image.Gray16, scale float64, coeffs []bool, offset []int, filterLength int) {
194
+	oldBounds := in.Bounds()
195
+	newBounds := out.Bounds()
196
+	minX := oldBounds.Min.X * 2
197
+	maxX := (oldBounds.Max.X - oldBounds.Min.X - 1) * 2
198
+
199
+	for x := newBounds.Min.X; x < newBounds.Max.X; x++ {
200
+		row := in.Pix[(x-oldBounds.Min.Y)*in.Stride:]
201
+		for y := newBounds.Min.Y; y < newBounds.Max.Y; y++ {
202
+			var gray float32
203
+			var sum float32
204
+			start := offset[y]
205
+			ci := (y - newBounds.Min.Y) * filterLength
206
+			for i := 0; i < filterLength; i++ {
207
+				if coeffs[ci+i] {
208
+					xi := start + i
209
+					switch {
210
+					case uint(xi) < uint(oldBounds.Max.X):
211
+						xi *= 2
212
+					case xi >= oldBounds.Max.X:
213
+						xi = maxX
214
+					default:
215
+						xi = minX
216
+					}
217
+					gray += float32(uint16(row[xi+0])<<8 | uint16(row[xi+1]))
218
+					sum++
219
+				}
220
+			}
221
+
222
+			offset := (y-newBounds.Min.Y)*out.Stride + (x-newBounds.Min.X)*2
223
+			value := floatToUint16(gray / sum)
224
+			out.Pix[offset+0] = uint8(value >> 8)
225
+			out.Pix[offset+1] = uint8(value)
226
+		}
227
+	}
228
+}

+ 41
- 0
nearest_test.go View File

@@ -0,0 +1,41 @@
1
+package resize
2
+
3
+import "testing"
4
+
5
+func Test_FloatToUint8(t *testing.T) {
6
+	var testData = []struct {
7
+		in       float32
8
+		expected uint8
9
+	}{
10
+		{0, 0},
11
+		{255, 255},
12
+		{128, 128},
13
+		{1, 1},
14
+		{256, 255},
15
+	}
16
+	for _, test := range testData {
17
+		actual := floatToUint8(test.in)
18
+		if actual != test.expected {
19
+			t.Fail()
20
+		}
21
+	}
22
+}
23
+
24
+func Test_FloatToUint16(t *testing.T) {
25
+	var testData = []struct {
26
+		in       float32
27
+		expected uint16
28
+	}{
29
+		{0, 0},
30
+		{65535, 65535},
31
+		{128, 128},
32
+		{1, 1},
33
+		{65536, 65535},
34
+	}
35
+	for _, test := range testData {
36
+		actual := floatToUint16(test.in)
37
+		if actual != test.expected {
38
+			t.Fail()
39
+		}
40
+	}
41
+}

+ 224
- 29
resize.go View File

@@ -33,36 +33,41 @@ import (
33 33
 // An InterpolationFunction provides the parameters that describe an
34 34
 // interpolation kernel. It returns the number of samples to take
35 35
 // and the kernel function to use for sampling.
36
-type InterpolationFunction func() (int, func(float64) float64)
36
+type InterpolationFunction int
37 37
 
38
-// Nearest-neighbor interpolation
39
-func NearestNeighbor() (int, func(float64) float64) {
40
-	return 2, nearest
41
-}
42
-
43
-// Bilinear interpolation
44
-func Bilinear() (int, func(float64) float64) {
45
-	return 2, linear
46
-}
47
-
48
-// Bicubic interpolation (with cubic hermite spline)
49
-func Bicubic() (int, func(float64) float64) {
50
-	return 4, cubic
51
-}
52
-
53
-// Mitchell-Netravali interpolation
54
-func MitchellNetravali() (int, func(float64) float64) {
55
-	return 4, mitchellnetravali
56
-}
57
-
58
-// Lanczos interpolation (a=2)
59
-func Lanczos2() (int, func(float64) float64) {
60
-	return 4, lanczos2
61
-}
38
+// InterpolationFunction constants
39
+const (
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
+)
62 53
 
63
-// Lanczos interpolation (a=3)
64
-func Lanczos3() (int, func(float64) float64) {
65
-	return 6, lanczos3
54
+// kernal, returns an InterpolationFunctions taps and kernel.
55
+func (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
+	}
66 71
 }
67 72
 
68 73
 // values <1 will sharpen the image
@@ -81,8 +86,11 @@ func Resize(width, height uint, img image.Image, interp InterpolationFunction) i
81 86
 	if height == 0 {
82 87
 		height = uint(0.7 + float64(img.Bounds().Dy())/scaleY)
83 88
 	}
89
+	if interp == NearestNeighbor {
90
+		return resizeNearest(width, height, scaleX, scaleY, img, interp)
91
+	}
84 92
 
85
-	taps, kernel := interp()
93
+	taps, kernel := interp.kernel()
86 94
 	cpus := runtime.NumCPU()
87 95
 	wg := sync.WaitGroup{}
88 96
 
@@ -269,6 +277,193 @@ func Resize(width, height uint, img image.Image, interp InterpolationFunction) i
269 277
 	}
270 278
 }
271 279
 
280
+func resizeNearest(width, height uint, scaleX, scaleY float64, img image.Image, interp InterpolationFunction) image.Image {
281
+	taps, _ := interp.kernel()
282
+	cpus := runtime.NumCPU()
283
+	wg := sync.WaitGroup{}
284
+
285
+	switch input := img.(type) {
286
+	case *image.RGBA:
287
+		// 8-bit precision
288
+		temp := image.NewRGBA(image.Rect(0, 0, input.Bounds().Dy(), int(width)))
289
+		result := image.NewRGBA(image.Rect(0, 0, int(width), int(height)))
290
+
291
+		// horizontal filter, results in transposed temporary image
292
+		coeffs, offset, filterLength := createWeightsNearest(temp.Bounds().Dy(), input.Bounds().Min.X, taps, blur, scaleX)
293
+		wg.Add(cpus)
294
+		for i := 0; i < cpus; i++ {
295
+			slice := makeSlice(temp, i, cpus).(*image.RGBA)
296
+			go func() {
297
+				defer wg.Done()
298
+				nearestRGBA(input, slice, scaleX, coeffs, offset, filterLength)
299
+			}()
300
+		}
301
+		wg.Wait()
302
+
303
+		// horizontal filter on transposed image, result is not transposed
304
+		coeffs, offset, filterLength = createWeightsNearest(result.Bounds().Dy(), temp.Bounds().Min.X, taps, blur, scaleY)
305
+		wg.Add(cpus)
306
+		for i := 0; i < cpus; i++ {
307
+			slice := makeSlice(result, i, cpus).(*image.RGBA)
308
+			go func() {
309
+				defer wg.Done()
310
+				nearestRGBA(temp, slice, scaleY, coeffs, offset, filterLength)
311
+			}()
312
+		}
313
+		wg.Wait()
314
+		return result
315
+	case *image.YCbCr:
316
+		// 8-bit precision
317
+		// accessing the YCbCr arrays in a tight loop is slow.
318
+		// converting the image before filtering will improve performance.
319
+		inputAsRGBA := convertYCbCrToRGBA(input)
320
+		temp := image.NewRGBA(image.Rect(0, 0, input.Bounds().Dy(), int(width)))
321
+		result := image.NewRGBA(image.Rect(0, 0, int(width), int(height)))
322
+
323
+		// horizontal filter, results in transposed temporary image
324
+		coeffs, offset, filterLength := createWeightsNearest(temp.Bounds().Dy(), input.Bounds().Min.X, taps, blur, scaleX)
325
+		wg.Add(cpus)
326
+		for i := 0; i < cpus; i++ {
327
+			slice := makeSlice(temp, i, cpus).(*image.RGBA)
328
+			go func() {
329
+				defer wg.Done()
330
+				nearestRGBA(inputAsRGBA, slice, scaleX, coeffs, offset, filterLength)
331
+			}()
332
+		}
333
+		wg.Wait()
334
+
335
+		// horizontal filter on transposed image, result is not transposed
336
+		coeffs, offset, filterLength = createWeightsNearest(result.Bounds().Dy(), temp.Bounds().Min.X, taps, blur, scaleY)
337
+		wg.Add(cpus)
338
+		for i := 0; i < cpus; i++ {
339
+			slice := makeSlice(result, i, cpus).(*image.RGBA)
340
+			go func() {
341
+				defer wg.Done()
342
+				nearestRGBA(temp, slice, scaleY, coeffs, offset, filterLength)
343
+			}()
344
+		}
345
+		wg.Wait()
346
+		return result
347
+	case *image.RGBA64:
348
+		// 16-bit precision
349
+		temp := image.NewRGBA64(image.Rect(0, 0, input.Bounds().Dy(), int(width)))
350
+		result := image.NewRGBA64(image.Rect(0, 0, int(width), int(height)))
351
+
352
+		// horizontal filter, results in transposed temporary image
353
+		coeffs, offset, filterLength := createWeightsNearest(temp.Bounds().Dy(), input.Bounds().Min.X, taps, blur, scaleX)
354
+		wg.Add(cpus)
355
+		for i := 0; i < cpus; i++ {
356
+			slice := makeSlice(temp, i, cpus).(*image.RGBA64)
357
+			go func() {
358
+				defer wg.Done()
359
+				nearestRGBA64(input, slice, scaleX, coeffs, offset, filterLength)
360
+			}()
361
+		}
362
+		wg.Wait()
363
+
364
+		// horizontal filter on transposed image, result is not transposed
365
+		coeffs, offset, filterLength = createWeightsNearest(result.Bounds().Dy(), temp.Bounds().Min.X, taps, blur, scaleY)
366
+		wg.Add(cpus)
367
+		for i := 0; i < cpus; i++ {
368
+			slice := makeSlice(result, i, cpus).(*image.RGBA64)
369
+			go func() {
370
+				defer wg.Done()
371
+				nearestGeneric(temp, slice, scaleY, coeffs, offset, filterLength)
372
+			}()
373
+		}
374
+		wg.Wait()
375
+		return result
376
+	case *image.Gray:
377
+		// 8-bit precision
378
+		temp := image.NewGray(image.Rect(0, 0, input.Bounds().Dy(), int(width)))
379
+		result := image.NewGray(image.Rect(0, 0, int(width), int(height)))
380
+
381
+		// horizontal filter, results in transposed temporary image
382
+		coeffs, offset, filterLength := createWeightsNearest(temp.Bounds().Dy(), input.Bounds().Min.X, taps, blur, scaleX)
383
+		wg.Add(cpus)
384
+		for i := 0; i < cpus; i++ {
385
+			slice := makeSlice(temp, i, cpus).(*image.Gray)
386
+			go func() {
387
+				defer wg.Done()
388
+				nearestGray(input, slice, scaleX, coeffs, offset, filterLength)
389
+			}()
390
+		}
391
+		wg.Wait()
392
+
393
+		// horizontal filter on transposed image, result is not transposed
394
+		coeffs, offset, filterLength = createWeightsNearest(result.Bounds().Dy(), temp.Bounds().Min.X, taps, blur, scaleY)
395
+		wg.Add(cpus)
396
+		for i := 0; i < cpus; i++ {
397
+			slice := makeSlice(result, i, cpus).(*image.Gray)
398
+			go func() {
399
+				defer wg.Done()
400
+				nearestGray(temp, slice, scaleY, coeffs, offset, filterLength)
401
+			}()
402
+		}
403
+		wg.Wait()
404
+		return result
405
+	case *image.Gray16:
406
+		// 16-bit precision
407
+		temp := image.NewGray16(image.Rect(0, 0, input.Bounds().Dy(), int(width)))
408
+		result := image.NewGray16(image.Rect(0, 0, int(width), int(height)))
409
+
410
+		// horizontal filter, results in transposed temporary image
411
+		coeffs, offset, filterLength := createWeightsNearest(temp.Bounds().Dy(), input.Bounds().Min.X, taps, blur, scaleX)
412
+		wg.Add(cpus)
413
+		for i := 0; i < cpus; i++ {
414
+			slice := makeSlice(temp, i, cpus).(*image.Gray16)
415
+			go func() {
416
+				defer wg.Done()
417
+				nearestGray16(input, slice, scaleX, coeffs, offset, filterLength)
418
+			}()
419
+		}
420
+		wg.Wait()
421
+
422
+		// horizontal filter on transposed image, result is not transposed
423
+		coeffs, offset, filterLength = createWeightsNearest(result.Bounds().Dy(), temp.Bounds().Min.X, taps, blur, scaleY)
424
+		wg.Add(cpus)
425
+		for i := 0; i < cpus; i++ {
426
+			slice := makeSlice(result, i, cpus).(*image.Gray16)
427
+			go func() {
428
+				defer wg.Done()
429
+				nearestGray16(temp, slice, scaleY, coeffs, offset, filterLength)
430
+			}()
431
+		}
432
+		wg.Wait()
433
+		return result
434
+	default:
435
+		// 16-bit precision
436
+		temp := image.NewRGBA64(image.Rect(0, 0, img.Bounds().Dy(), int(width)))
437
+		result := image.NewRGBA64(image.Rect(0, 0, int(width), int(height)))
438
+
439
+		// horizontal filter, results in transposed temporary image
440
+		coeffs, offset, filterLength := createWeightsNearest(temp.Bounds().Dy(), img.Bounds().Min.X, taps, blur, scaleX)
441
+		wg.Add(cpus)
442
+		for i := 0; i < cpus; i++ {
443
+			slice := makeSlice(temp, i, cpus).(*image.RGBA64)
444
+			go func() {
445
+				defer wg.Done()
446
+				nearestGeneric(img, slice, scaleX, coeffs, offset, filterLength)
447
+			}()
448
+		}
449
+		wg.Wait()
450
+
451
+		// horizontal filter on transposed image, result is not transposed
452
+		coeffs, offset, filterLength = createWeightsNearest(result.Bounds().Dy(), temp.Bounds().Min.X, taps, blur, scaleY)
453
+		wg.Add(cpus)
454
+		for i := 0; i < cpus; i++ {
455
+			slice := makeSlice(result, i, cpus).(*image.RGBA64)
456
+			go func() {
457
+				defer wg.Done()
458
+				nearestRGBA64(temp, slice, scaleY, coeffs, offset, filterLength)
459
+			}()
460
+		}
461
+		wg.Wait()
462
+		return result
463
+	}
464
+
465
+}
466
+
272 467
 // Calculates scaling factors using old and new image dimensions.
273 468
 func calcFactors(width, height uint, oldWidth, oldHeight float64) (scaleX, scaleY float64) {
274 469
 	if width == 0 {