Browse Source

Optimize YCbCr image resize

Charlie Vieth 10 years ago
parent
commit
eefd4737aa
6 changed files with 473 additions and 39 deletions
  1. 76
    17
      converter.go
  2. 16
    0
      nearest.go
  3. 16
    0
      nearest_test.go
  4. 18
    22
      resize.go
  5. 226
    0
      ycc.go
  6. 121
    0
      ycc_test.go

+ 76
- 17
converter.go View File

@@ -16,10 +16,7 @@ THIS SOFTWARE.
16 16
 
17 17
 package resize
18 18
 
19
-import (
20
-	"image"
21
-	"image/color"
22
-)
19
+import "image"
23 20
 
24 21
 // Keep value in [0,255] range.
25 22
 func clampUint8(in int32) uint8 {
@@ -257,19 +254,81 @@ func resizeGray16(in *image.Gray16, out *image.Gray16, scale float64, coeffs []i
257 254
 	}
258 255
 }
259 256
 
260
-func convertYCbCrToRGBA(in *image.YCbCr) *image.RGBA {
261
-	out := image.NewRGBA(in.Bounds())
262
-	for y := 0; y < out.Bounds().Dy(); y++ {
263
-		for x := 0; x < out.Bounds().Dx(); x++ {
264
-			p := out.Pix[y*out.Stride+4*x:]
265
-			yi := in.YOffset(x, y)
266
-			ci := in.COffset(x, y)
267
-			r, g, b := color.YCbCrToRGB(in.Y[yi], in.Cb[ci], in.Cr[ci])
268
-			p[0] = r
269
-			p[1] = g
270
-			p[2] = b
271
-			p[3] = 0xff
257
+func resizeYCbCr(in *ycc, out *ycc, scale float64, coeffs []int16, offset []int, filterLength int) {
258
+	oldBounds := in.Bounds()
259
+	newBounds := out.Bounds()
260
+	minX := oldBounds.Min.X * 3
261
+	maxX := (oldBounds.Max.X - oldBounds.Min.X - 1) * 3
262
+
263
+	for x := newBounds.Min.X; x < newBounds.Max.X; x++ {
264
+		row := in.Pix[(x-oldBounds.Min.Y)*in.Stride:]
265
+		for y := newBounds.Min.Y; y < newBounds.Max.Y; y++ {
266
+			var p [3]int32
267
+			var sum int32
268
+			start := offset[y]
269
+			ci := (y - newBounds.Min.Y) * filterLength
270
+			for i := 0; i < filterLength; i++ {
271
+				coeff := coeffs[ci+i]
272
+				if coeff != 0 {
273
+					xi := start + i
274
+					switch {
275
+					case uint(xi) < uint(oldBounds.Max.X):
276
+						xi *= 3
277
+					case xi >= oldBounds.Max.X:
278
+						xi = maxX
279
+					default:
280
+						xi = minX
281
+					}
282
+					p[0] += int32(coeff) * int32(row[xi+0])
283
+					p[1] += int32(coeff) * int32(row[xi+1])
284
+					p[2] += int32(coeff) * int32(row[xi+2])
285
+					sum += int32(coeff)
286
+				}
287
+			}
288
+
289
+			xo := (y-newBounds.Min.Y)*out.Stride + (x-newBounds.Min.X)*3
290
+			out.Pix[xo+0] = clampUint8(p[0] / sum)
291
+			out.Pix[xo+1] = clampUint8(p[1] / sum)
292
+			out.Pix[xo+2] = clampUint8(p[2] / sum)
293
+		}
294
+	}
295
+}
296
+
297
+func nearestYCbCr(in *ycc, out *ycc, scale float64, coeffs []bool, offset []int, filterLength int) {
298
+	oldBounds := in.Bounds()
299
+	newBounds := out.Bounds()
300
+	minX := oldBounds.Min.X * 3
301
+	maxX := (oldBounds.Max.X - oldBounds.Min.X - 1) * 3
302
+
303
+	for x := newBounds.Min.X; x < newBounds.Max.X; x++ {
304
+		row := in.Pix[(x-oldBounds.Min.Y)*in.Stride:]
305
+		for y := newBounds.Min.Y; y < newBounds.Max.Y; y++ {
306
+			var p [3]float32
307
+			var sum float32
308
+			start := offset[y]
309
+			ci := (y - newBounds.Min.Y) * filterLength
310
+			for i := 0; i < filterLength; i++ {
311
+				if coeffs[ci+i] {
312
+					xi := start + i
313
+					switch {
314
+					case uint(xi) < uint(oldBounds.Max.X):
315
+						xi *= 3
316
+					case xi >= oldBounds.Max.X:
317
+						xi = maxX
318
+					default:
319
+						xi = minX
320
+					}
321
+					p[0] += float32(row[xi+0])
322
+					p[1] += float32(row[xi+1])
323
+					p[2] += float32(row[xi+2])
324
+					sum++
325
+				}
326
+			}
327
+
328
+			xo := (y-newBounds.Min.Y)*out.Stride + (x-newBounds.Min.X)*3
329
+			out.Pix[xo+0] = floatToUint8(p[0] / sum)
330
+			out.Pix[xo+1] = floatToUint8(p[1] / sum)
331
+			out.Pix[xo+2] = floatToUint8(p[2] / sum)
272 332
 		}
273 333
 	}
274
-	return out
275 334
 }

+ 16
- 0
nearest.go View File

@@ -1,3 +1,19 @@
1
+/*
2
+Copyright (c) 2014, Charlie Vieth <charlie.vieth@gmail.com>
3
+
4
+Permission to use, copy, modify, and/or distribute this software for any purpose
5
+with or without fee is hereby granted, provided that the above copyright notice
6
+and this permission notice appear in all copies.
7
+
8
+THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
9
+REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND
10
+FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
11
+INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS
12
+OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER
13
+TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF
14
+THIS SOFTWARE.
15
+*/
16
+
1 17
 package resize
2 18
 
3 19
 import "image"

+ 16
- 0
nearest_test.go View File

@@ -1,3 +1,19 @@
1
+/*
2
+Copyright (c) 2014, Charlie Vieth <charlie.vieth@gmail.com>
3
+
4
+Permission to use, copy, modify, and/or distribute this software for any purpose
5
+with or without fee is hereby granted, provided that the above copyright notice
6
+and this permission notice appear in all copies.
7
+
8
+THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
9
+REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND
10
+FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
11
+INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS
12
+OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER
13
+TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF
14
+THIS SOFTWARE.
15
+*/
16
+
1 17
 package resize
2 18
 
3 19
 import "testing"

+ 18
- 22
resize.go View File

@@ -129,35 +129,33 @@ func Resize(width, height uint, img image.Image, interp InterpolationFunction) i
129 129
 	case *image.YCbCr:
130 130
 		// 8-bit precision
131 131
 		// accessing the YCbCr arrays in a tight loop is slow.
132
-		// converting the image before filtering will improve performance.
133
-		inputAsRGBA := convertYCbCrToRGBA(input)
134
-		temp := image.NewRGBA(image.Rect(0, 0, input.Bounds().Dy(), int(width)))
135
-		result := image.NewRGBA(image.Rect(0, 0, int(width), int(height)))
132
+		// converting the image to ycc increases performance by 2x.
133
+		temp := newYCC(image.Rect(0, 0, input.Bounds().Dy(), int(width)), input.SubsampleRatio)
134
+		result := newYCC(image.Rect(0, 0, int(width), int(height)), input.SubsampleRatio)
136 135
 
137
-		// horizontal filter, results in transposed temporary image
138 136
 		coeffs, offset, filterLength := createWeights8(temp.Bounds().Dy(), input.Bounds().Min.X, taps, blur, scaleX, kernel)
137
+		in := imageYCbCrToYCC(input)
139 138
 		wg.Add(cpus)
140 139
 		for i := 0; i < cpus; i++ {
141
-			slice := makeSlice(temp, i, cpus).(*image.RGBA)
140
+			slice := makeSlice(temp, i, cpus).(*ycc)
142 141
 			go func() {
143 142
 				defer wg.Done()
144
-				resizeRGBA(inputAsRGBA, slice, scaleX, coeffs, offset, filterLength)
143
+				resizeYCbCr(in, slice, scaleX, coeffs, offset, filterLength)
145 144
 			}()
146 145
 		}
147 146
 		wg.Wait()
148 147
 
149
-		// horizontal filter on transposed image, result is not transposed
150 148
 		coeffs, offset, filterLength = createWeights8(result.Bounds().Dy(), temp.Bounds().Min.X, taps, blur, scaleY, kernel)
151 149
 		wg.Add(cpus)
152 150
 		for i := 0; i < cpus; i++ {
153
-			slice := makeSlice(result, i, cpus).(*image.RGBA)
151
+			slice := makeSlice(result, i, cpus).(*ycc)
154 152
 			go func() {
155 153
 				defer wg.Done()
156
-				resizeRGBA(temp, slice, scaleY, coeffs, offset, filterLength)
154
+				resizeYCbCr(temp, slice, scaleY, coeffs, offset, filterLength)
157 155
 			}()
158 156
 		}
159 157
 		wg.Wait()
160
-		return result
158
+		return result.YCbCr()
161 159
 	case *image.RGBA64:
162 160
 		// 16-bit precision
163 161
 		temp := image.NewRGBA64(image.Rect(0, 0, input.Bounds().Dy(), int(width)))
@@ -315,35 +313,33 @@ func resizeNearest(width, height uint, scaleX, scaleY float64, img image.Image,
315 313
 	case *image.YCbCr:
316 314
 		// 8-bit precision
317 315
 		// 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)))
316
+		// converting the image to ycc increases performance by 2x.
317
+		temp := newYCC(image.Rect(0, 0, input.Bounds().Dy(), int(width)), input.SubsampleRatio)
318
+		result := newYCC(image.Rect(0, 0, int(width), int(height)), input.SubsampleRatio)
322 319
 
323
-		// horizontal filter, results in transposed temporary image
324 320
 		coeffs, offset, filterLength := createWeightsNearest(temp.Bounds().Dy(), input.Bounds().Min.X, taps, blur, scaleX)
321
+		in := imageYCbCrToYCC(input)
325 322
 		wg.Add(cpus)
326 323
 		for i := 0; i < cpus; i++ {
327
-			slice := makeSlice(temp, i, cpus).(*image.RGBA)
324
+			slice := makeSlice(temp, i, cpus).(*ycc)
328 325
 			go func() {
329 326
 				defer wg.Done()
330
-				nearestRGBA(inputAsRGBA, slice, scaleX, coeffs, offset, filterLength)
327
+				nearestYCbCr(in, slice, scaleX, coeffs, offset, filterLength)
331 328
 			}()
332 329
 		}
333 330
 		wg.Wait()
334 331
 
335
-		// horizontal filter on transposed image, result is not transposed
336 332
 		coeffs, offset, filterLength = createWeightsNearest(result.Bounds().Dy(), temp.Bounds().Min.X, taps, blur, scaleY)
337 333
 		wg.Add(cpus)
338 334
 		for i := 0; i < cpus; i++ {
339
-			slice := makeSlice(result, i, cpus).(*image.RGBA)
335
+			slice := makeSlice(result, i, cpus).(*ycc)
340 336
 			go func() {
341 337
 				defer wg.Done()
342
-				nearestRGBA(temp, slice, scaleY, coeffs, offset, filterLength)
338
+				nearestYCbCr(temp, slice, scaleY, coeffs, offset, filterLength)
343 339
 			}()
344 340
 		}
345 341
 		wg.Wait()
346
-		return result
342
+		return result.YCbCr()
347 343
 	case *image.RGBA64:
348 344
 		// 16-bit precision
349 345
 		temp := image.NewRGBA64(image.Rect(0, 0, input.Bounds().Dy(), int(width)))

+ 226
- 0
ycc.go View File

@@ -0,0 +1,226 @@
1
+/*
2
+Copyright (c) 2014, Charlie Vieth <charlie.vieth@gmail.com>
3
+
4
+Permission to use, copy, modify, and/or distribute this software for any purpose
5
+with or without fee is hereby granted, provided that the above copyright notice
6
+and this permission notice appear in all copies.
7
+
8
+THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
9
+REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND
10
+FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
11
+INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS
12
+OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER
13
+TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF
14
+THIS SOFTWARE.
15
+*/
16
+
17
+package resize
18
+
19
+import (
20
+	"image"
21
+	"image/color"
22
+)
23
+
24
+// ycc is an in memory YCbCr image.  The Y, Cb and Cr samples are held in a
25
+// single slice to increase resizing performance.
26
+type ycc struct {
27
+	// Pix holds the image's pixels, in Y, Cb, Cr order. The pixel at
28
+	// (x, y) starts at Pix[(y-Rect.Min.Y)*Stride + (x-Rect.Min.X)*3].
29
+	Pix []uint8
30
+	// Stride is the Pix stride (in bytes) between vertically adjacent pixels.
31
+	Stride int
32
+	// Rect is the image's bounds.
33
+	Rect image.Rectangle
34
+	// SubsampleRatio is the subsample ratio of the original YCbCr image.
35
+	SubsampleRatio image.YCbCrSubsampleRatio
36
+}
37
+
38
+// PixOffset returns the index of the first element of Pix that corresponds to
39
+// the pixel at (x, y).
40
+func (p *ycc) PixOffset(x, y int) int {
41
+	return (y * p.Stride) + (x * 3)
42
+}
43
+
44
+func (p *ycc) Bounds() image.Rectangle {
45
+	return p.Rect
46
+}
47
+
48
+func (p *ycc) ColorModel() color.Model {
49
+	return color.YCbCrModel
50
+}
51
+
52
+func (p *ycc) At(x, y int) color.Color {
53
+	if !(image.Point{x, y}.In(p.Rect)) {
54
+		return color.YCbCr{}
55
+	}
56
+	i := p.PixOffset(x, y)
57
+	return color.YCbCr{
58
+		p.Pix[i+0],
59
+		p.Pix[i+1],
60
+		p.Pix[i+2],
61
+	}
62
+}
63
+
64
+func (p *ycc) Opaque() bool {
65
+	return true
66
+}
67
+
68
+// SubImage returns an image representing the portion of the image p visible
69
+// through r. The returned value shares pixels with the original image.
70
+func (p *ycc) SubImage(r image.Rectangle) image.Image {
71
+	r = r.Intersect(p.Rect)
72
+	if r.Empty() {
73
+		return &ycc{SubsampleRatio: p.SubsampleRatio}
74
+	}
75
+	i := p.PixOffset(r.Min.X, r.Min.Y)
76
+	return &ycc{
77
+		Pix:            p.Pix[i:],
78
+		Stride:         p.Stride,
79
+		Rect:           r,
80
+		SubsampleRatio: p.SubsampleRatio,
81
+	}
82
+}
83
+
84
+// newYCC returns a new ycc with the given bounds and subsample ratio.
85
+func newYCC(r image.Rectangle, s image.YCbCrSubsampleRatio) *ycc {
86
+	w, h := r.Dx(), r.Dy()
87
+	buf := make([]uint8, 3*w*h)
88
+	return &ycc{Pix: buf, Stride: 3 * w, Rect: r, SubsampleRatio: s}
89
+}
90
+
91
+// YCbCr converts ycc to a YCbCr image with the same subsample ratio
92
+// as the YCbCr image that ycc was generated from.
93
+func (p *ycc) YCbCr() *image.YCbCr {
94
+	ycbcr := image.NewYCbCr(p.Rect, p.SubsampleRatio)
95
+	var off int
96
+
97
+	switch ycbcr.SubsampleRatio {
98
+	case image.YCbCrSubsampleRatio422:
99
+		for y := ycbcr.Rect.Min.Y; y < ycbcr.Rect.Max.Y; y++ {
100
+			yy := (y - ycbcr.Rect.Min.Y) * ycbcr.YStride
101
+			cy := (y - ycbcr.Rect.Min.Y) * ycbcr.CStride
102
+			for x := ycbcr.Rect.Min.X; x < ycbcr.Rect.Max.X; x++ {
103
+				xx := (x - ycbcr.Rect.Min.X)
104
+				yi := yy + xx
105
+				ci := cy + xx/2
106
+				ycbcr.Y[yi] = p.Pix[off+0]
107
+				ycbcr.Cb[ci] = p.Pix[off+1]
108
+				ycbcr.Cr[ci] = p.Pix[off+2]
109
+				off += 3
110
+			}
111
+		}
112
+	case image.YCbCrSubsampleRatio420:
113
+		for y := ycbcr.Rect.Min.Y; y < ycbcr.Rect.Max.Y; y++ {
114
+			yy := (y - ycbcr.Rect.Min.Y) * ycbcr.YStride
115
+			cy := (y/2 - ycbcr.Rect.Min.Y/2) * ycbcr.CStride
116
+			for x := ycbcr.Rect.Min.X; x < ycbcr.Rect.Max.X; x++ {
117
+				xx := (x - ycbcr.Rect.Min.X)
118
+				yi := yy + xx
119
+				ci := cy + xx/2
120
+				ycbcr.Y[yi] = p.Pix[off+0]
121
+				ycbcr.Cb[ci] = p.Pix[off+1]
122
+				ycbcr.Cr[ci] = p.Pix[off+2]
123
+				off += 3
124
+			}
125
+		}
126
+	case image.YCbCrSubsampleRatio440:
127
+		for y := ycbcr.Rect.Min.Y; y < ycbcr.Rect.Max.Y; y++ {
128
+			yy := (y - ycbcr.Rect.Min.Y) * ycbcr.YStride
129
+			cy := (y/2 - ycbcr.Rect.Min.Y/2) * ycbcr.CStride
130
+			for x := ycbcr.Rect.Min.X; x < ycbcr.Rect.Max.X; x++ {
131
+				xx := (x - ycbcr.Rect.Min.X)
132
+				yi := yy + xx
133
+				ci := cy + xx
134
+				ycbcr.Y[yi] = p.Pix[off+0]
135
+				ycbcr.Cb[ci] = p.Pix[off+1]
136
+				ycbcr.Cr[ci] = p.Pix[off+2]
137
+				off += 3
138
+			}
139
+		}
140
+	default:
141
+		// Default to 4:4:4 subsampling.
142
+		for y := ycbcr.Rect.Min.Y; y < ycbcr.Rect.Max.Y; y++ {
143
+			yy := (y - ycbcr.Rect.Min.Y) * ycbcr.YStride
144
+			cy := (y - ycbcr.Rect.Min.Y) * ycbcr.CStride
145
+			for x := ycbcr.Rect.Min.X; x < ycbcr.Rect.Max.X; x++ {
146
+				xx := (x - ycbcr.Rect.Min.X)
147
+				yi := yy + xx
148
+				ci := cy + xx
149
+				ycbcr.Y[yi] = p.Pix[off+0]
150
+				ycbcr.Cb[ci] = p.Pix[off+1]
151
+				ycbcr.Cr[ci] = p.Pix[off+2]
152
+				off += 3
153
+			}
154
+		}
155
+	}
156
+	return ycbcr
157
+}
158
+
159
+// imageYCbCrToYCC converts a YCbCr image to a ycc image for resizing.
160
+func imageYCbCrToYCC(in *image.YCbCr) *ycc {
161
+	w, h := in.Rect.Dx(), in.Rect.Dy()
162
+	buf := make([]uint8, 3*w*h)
163
+	p := ycc{Pix: buf, Stride: 3 * w, Rect: in.Rect, SubsampleRatio: in.SubsampleRatio}
164
+	var off int
165
+
166
+	switch in.SubsampleRatio {
167
+	case image.YCbCrSubsampleRatio422:
168
+		for y := in.Rect.Min.Y; y < in.Rect.Max.Y; y++ {
169
+			yy := (y - in.Rect.Min.Y) * in.YStride
170
+			cy := (y - in.Rect.Min.Y) * in.CStride
171
+			for x := in.Rect.Min.X; x < in.Rect.Max.X; x++ {
172
+				xx := (x - in.Rect.Min.X)
173
+				yi := yy + xx
174
+				ci := cy + xx/2
175
+				p.Pix[off+0] = in.Y[yi]
176
+				p.Pix[off+1] = in.Cb[ci]
177
+				p.Pix[off+2] = in.Cr[ci]
178
+				off += 3
179
+			}
180
+		}
181
+	case image.YCbCrSubsampleRatio420:
182
+		for y := in.Rect.Min.Y; y < in.Rect.Max.Y; y++ {
183
+			yy := (y - in.Rect.Min.Y) * in.YStride
184
+			cy := (y/2 - in.Rect.Min.Y/2) * in.CStride
185
+			for x := in.Rect.Min.X; x < in.Rect.Max.X; x++ {
186
+				xx := (x - in.Rect.Min.X)
187
+				yi := yy + xx
188
+				ci := cy + xx/2
189
+				p.Pix[off+0] = in.Y[yi]
190
+				p.Pix[off+1] = in.Cb[ci]
191
+				p.Pix[off+2] = in.Cr[ci]
192
+				off += 3
193
+			}
194
+		}
195
+	case image.YCbCrSubsampleRatio440:
196
+		for y := in.Rect.Min.Y; y < in.Rect.Max.Y; y++ {
197
+			yy := (y - in.Rect.Min.Y) * in.YStride
198
+			cy := (y/2 - in.Rect.Min.Y/2) * in.CStride
199
+			for x := in.Rect.Min.X; x < in.Rect.Max.X; x++ {
200
+				xx := (x - in.Rect.Min.X)
201
+				yi := yy + xx
202
+				ci := cy + xx
203
+				p.Pix[off+0] = in.Y[yi]
204
+				p.Pix[off+1] = in.Cb[ci]
205
+				p.Pix[off+2] = in.Cr[ci]
206
+				off += 3
207
+			}
208
+		}
209
+	default:
210
+		// Default to 4:4:4 subsampling.
211
+		for y := in.Rect.Min.Y; y < in.Rect.Max.Y; y++ {
212
+			yy := (y - in.Rect.Min.Y) * in.YStride
213
+			cy := (y - in.Rect.Min.Y) * in.CStride
214
+			for x := in.Rect.Min.X; x < in.Rect.Max.X; x++ {
215
+				xx := (x - in.Rect.Min.X)
216
+				yi := yy + xx
217
+				ci := cy + xx
218
+				p.Pix[off+0] = in.Y[yi]
219
+				p.Pix[off+1] = in.Cb[ci]
220
+				p.Pix[off+2] = in.Cr[ci]
221
+				off += 3
222
+			}
223
+		}
224
+	}
225
+	return &p
226
+}

+ 121
- 0
ycc_test.go View File

@@ -0,0 +1,121 @@
1
+/*
2
+Copyright (c) 2014, Charlie Vieth <charlie.vieth@gmail.com>
3
+
4
+Permission to use, copy, modify, and/or distribute this software for any purpose
5
+with or without fee is hereby granted, provided that the above copyright notice
6
+and this permission notice appear in all copies.
7
+
8
+THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
9
+REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND
10
+FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
11
+INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS
12
+OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER
13
+TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF
14
+THIS SOFTWARE.
15
+*/
16
+
17
+package resize
18
+
19
+import (
20
+	"image"
21
+	"testing"
22
+)
23
+
24
+type Image interface {
25
+	image.Image
26
+	SubImage(image.Rectangle) image.Image
27
+}
28
+
29
+func TestImage(t *testing.T) {
30
+	testImage := []Image{
31
+		newYCC(image.Rect(0, 0, 10, 10), image.YCbCrSubsampleRatio420),
32
+		newYCC(image.Rect(0, 0, 10, 10), image.YCbCrSubsampleRatio422),
33
+		newYCC(image.Rect(0, 0, 10, 10), image.YCbCrSubsampleRatio440),
34
+		newYCC(image.Rect(0, 0, 10, 10), image.YCbCrSubsampleRatio444),
35
+	}
36
+	for _, m := range testImage {
37
+		if !image.Rect(0, 0, 10, 10).Eq(m.Bounds()) {
38
+			t.Errorf("%T: want bounds %v, got %v",
39
+				m, image.Rect(0, 0, 10, 10), m.Bounds())
40
+			continue
41
+		}
42
+		m = m.SubImage(image.Rect(3, 2, 9, 8)).(Image)
43
+		if !image.Rect(3, 2, 9, 8).Eq(m.Bounds()) {
44
+			t.Errorf("%T: sub-image want bounds %v, got %v",
45
+				m, image.Rect(3, 2, 9, 8), m.Bounds())
46
+			continue
47
+		}
48
+		// Test that taking an empty sub-image starting at a corner does not panic.
49
+		m.SubImage(image.Rect(0, 0, 0, 0))
50
+		m.SubImage(image.Rect(10, 0, 10, 0))
51
+		m.SubImage(image.Rect(0, 10, 0, 10))
52
+		m.SubImage(image.Rect(10, 10, 10, 10))
53
+	}
54
+}
55
+
56
+func TestConvertYCbCr(t *testing.T) {
57
+	testImage := []Image{
58
+		image.NewYCbCr(image.Rect(0, 0, 50, 50), image.YCbCrSubsampleRatio420),
59
+		image.NewYCbCr(image.Rect(0, 0, 50, 50), image.YCbCrSubsampleRatio422),
60
+		image.NewYCbCr(image.Rect(0, 0, 50, 50), image.YCbCrSubsampleRatio440),
61
+		image.NewYCbCr(image.Rect(0, 0, 50, 50), image.YCbCrSubsampleRatio444),
62
+	}
63
+
64
+	for _, img := range testImage {
65
+		m := img.(*image.YCbCr)
66
+		for y := m.Rect.Min.Y; y < m.Rect.Max.Y; y++ {
67
+			for x := m.Rect.Min.X; x < m.Rect.Max.X; x++ {
68
+				yi := m.YOffset(x, y)
69
+				ci := m.COffset(x, y)
70
+				m.Y[yi] = uint8(16*y + x)
71
+				m.Cb[ci] = uint8(y + 16*x)
72
+				m.Cr[ci] = uint8(y + 16*x)
73
+			}
74
+		}
75
+
76
+		// test conversion from YCbCr to ycc
77
+		yc := imageYCbCrToYCC(m)
78
+		for y := m.Rect.Min.Y; y < m.Rect.Max.Y; y++ {
79
+			for x := m.Rect.Min.X; x < m.Rect.Max.X; x++ {
80
+				ystride := 3 * (m.Rect.Max.X - m.Rect.Min.X)
81
+				xstride := 3
82
+				yi := m.YOffset(x, y)
83
+				ci := m.COffset(x, y)
84
+				si := (y * ystride) + (x * xstride)
85
+				if m.Y[yi] != yc.Pix[si] {
86
+					t.Errorf("Err Y - found: %d expected: %d x: %d y: %d yi: %d si: %d",
87
+						m.Y[yi], yc.Pix[si], x, y, yi, si)
88
+				}
89
+				if m.Cb[ci] != yc.Pix[si+1] {
90
+					t.Errorf("Err Cb - found: %d expected: %d x: %d y: %d ci: %d si: %d",
91
+						m.Cb[ci], yc.Pix[si+1], x, y, ci, si+1)
92
+				}
93
+				if m.Cr[ci] != yc.Pix[si+2] {
94
+					t.Errorf("Err Cr - found: %d expected: %d x: %d y: %d ci: %d si: %d",
95
+						m.Cr[ci], yc.Pix[si+2], x, y, ci, si+2)
96
+				}
97
+			}
98
+		}
99
+
100
+		// test conversion from ycc back to YCbCr
101
+		ym := yc.YCbCr()
102
+		for y := m.Rect.Min.Y; y < m.Rect.Max.Y; y++ {
103
+			for x := m.Rect.Min.X; x < m.Rect.Max.X; x++ {
104
+				yi := m.YOffset(x, y)
105
+				ci := m.COffset(x, y)
106
+				if m.Y[yi] != ym.Y[yi] {
107
+					t.Errorf("Err Y - found: %d expected: %d x: %d y: %d yi: %d",
108
+						m.Y[yi], ym.Y[yi], x, y, yi)
109
+				}
110
+				if m.Cb[ci] != ym.Cb[ci] {
111
+					t.Errorf("Err Cb - found: %d expected: %d x: %d y: %d ci: %d",
112
+						m.Cb[ci], ym.Cb[ci], x, y, ci)
113
+				}
114
+				if m.Cr[ci] != ym.Cr[ci] {
115
+					t.Errorf("Err Cr - found: %d expected: %d x: %d y: %d ci: %d",
116
+						m.Cr[ci], ym.Cr[ci], x, y, ci)
117
+				}
118
+			}
119
+		}
120
+	}
121
+}