diff options
Diffstat (limited to 'libgo/go/image')
-rw-r--r-- | libgo/go/image/jpeg/dct_test.go | 291 | ||||
-rw-r--r-- | libgo/go/image/jpeg/idct.go | 80 | ||||
-rw-r--r-- | libgo/go/image/jpeg/reader.go | 59 | ||||
-rw-r--r-- | libgo/go/image/jpeg/writer_test.go | 28 | ||||
-rw-r--r-- | libgo/go/image/png/paeth.go | 70 | ||||
-rw-r--r-- | libgo/go/image/png/paeth_test.go | 91 | ||||
-rw-r--r-- | libgo/go/image/png/reader.go | 65 | ||||
-rw-r--r-- | libgo/go/image/png/reader_test.go | 54 | ||||
-rw-r--r-- | libgo/go/image/png/writer.go | 105 | ||||
-rw-r--r-- | libgo/go/image/png/writer_test.go | 45 | ||||
-rw-r--r-- | libgo/go/image/ycbcr.go | 9 | ||||
-rw-r--r-- | libgo/go/image/ycbcr_test.go | 1 |
12 files changed, 733 insertions, 165 deletions
diff --git a/libgo/go/image/jpeg/dct_test.go b/libgo/go/image/jpeg/dct_test.go new file mode 100644 index 00000000000..770e274bac0 --- /dev/null +++ b/libgo/go/image/jpeg/dct_test.go @@ -0,0 +1,291 @@ +// Copyright 2012 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package jpeg + +import ( + "bytes" + "fmt" + "math" + "math/rand" + "testing" +) + +func benchmarkDCT(b *testing.B, f func(*block)) { + b.StopTimer() + blocks := make([]block, 0, b.N*len(testBlocks)) + for i := 0; i < b.N; i++ { + blocks = append(blocks, testBlocks[:]...) + } + b.StartTimer() + for i := range blocks { + f(&blocks[i]) + } +} + +func BenchmarkFDCT(b *testing.B) { + benchmarkDCT(b, fdct) +} + +func BenchmarkIDCT(b *testing.B) { + benchmarkDCT(b, idct) +} + +func TestDCT(t *testing.T) { + blocks := make([]block, len(testBlocks)) + copy(blocks, testBlocks[:]) + + // Append some randomly generated blocks of varying sparseness. + r := rand.New(rand.NewSource(123)) + for i := 0; i < 100; i++ { + b := block{} + n := r.Int() % 64 + for j := 0; j < n; j++ { + b[r.Int()%len(b)] = r.Int() % 256 + } + blocks = append(blocks, b) + } + + // Check that the FDCT and IDCT functions are inverses, after a scale and + // level shift. Scaling reduces the rounding errors in the conversion from + // floats to ints. + for i, b := range blocks { + got, want := b, b + for j := range got { + got[j] = (got[j] - 128) * 8 + } + slowFDCT(&got) + slowIDCT(&got) + for j := range got { + got[j] = got[j]/8 + 128 + } + if differ(&got, &want) { + t.Errorf("i=%d: IDCT(FDCT)\nsrc\n%s\ngot\n%s\nwant\n%s\n", i, &b, &got, &want) + } + } + + // Check that the optimized and slow FDCT implementations agree. + // The fdct function already does a scale and level shift. + for i, b := range blocks { + got, want := b, b + fdct(&got) + for j := range want { + want[j] = (want[j] - 128) * 8 + } + slowFDCT(&want) + if differ(&got, &want) { + t.Errorf("i=%d: FDCT\nsrc\n%s\ngot\n%s\nwant\n%s\n", i, &b, &got, &want) + } + } + + // Check that the optimized and slow IDCT implementations agree. + for i, b := range blocks { + got, want := b, b + idct(&got) + slowIDCT(&want) + if differ(&got, &want) { + t.Errorf("i=%d: IDCT\nsrc\n%s\ngot\n%s\nwant\n%s\n", i, &b, &got, &want) + } + } +} + +// differ returns whether any pair-wise elements in b0 and b1 differ by 2 or +// more. That tolerance is because there isn't a single definitive decoding of +// a given JPEG image, even before the YCbCr to RGB conversion; implementations +// can have different IDCT rounding errors. +func differ(b0, b1 *block) bool { + for i := range b0 { + delta := b0[i] - b1[i] + if delta < -2 || +2 < delta { + return true + } + } + return false +} + +// alpha returns 1 if i is 0 and returns √2 otherwise. +func alpha(i int) float64 { + if i == 0 { + return 1 + } + return math.Sqrt2 +} + +// slowFDCT performs the 8*8 2-dimensional forward discrete cosine transform: +// +// dst[u,v] = (1/8) * Σ_x Σ_y alpha(u) * alpha(v) * src[x,y] * +// cos((π/2) * (2*x + 1) * u / 8) * +// cos((π/2) * (2*y + 1) * v / 8) +// +// x and y are in pixel space, and u and v are in transform space. +// +// b acts as both dst and src. +func slowFDCT(b *block) { + var dst [blockSize]float64 + for v := 0; v < 8; v++ { + for u := 0; u < 8; u++ { + sum := 0.0 + for y := 0; y < 8; y++ { + for x := 0; x < 8; x++ { + sum += alpha(u) * alpha(v) * float64(b[8*y+x]) * + math.Cos(math.Pi*float64((2*x+1)*u)/16) * + math.Cos(math.Pi*float64((2*y+1)*v)/16) + } + } + dst[8*v+u] = sum / 8 + } + } + // Convert from float64 to int. + for i := range dst { + b[i] = int(dst[i] + 0.5) + } +} + +// slowIDCT performs the 8*8 2-dimensional inverse discrete cosine transform: +// +// dst[x,y] = (1/8) * Σ_u Σ_v alpha(u) * alpha(v) * src[u,v] * +// cos((π/2) * (2*x + 1) * u / 8) * +// cos((π/2) * (2*y + 1) * v / 8) +// +// x and y are in pixel space, and u and v are in transform space. +// +// b acts as both dst and src. +func slowIDCT(b *block) { + var dst [blockSize]float64 + for y := 0; y < 8; y++ { + for x := 0; x < 8; x++ { + sum := 0.0 + for v := 0; v < 8; v++ { + for u := 0; u < 8; u++ { + sum += alpha(u) * alpha(v) * float64(b[8*v+u]) * + math.Cos(math.Pi*float64((2*x+1)*u)/16) * + math.Cos(math.Pi*float64((2*y+1)*v)/16) + } + } + dst[8*y+x] = sum / 8 + } + } + // Convert from float64 to int. + for i := range dst { + b[i] = int(dst[i] + 0.5) + } +} + +func (b *block) String() string { + s := bytes.NewBuffer(nil) + fmt.Fprintf(s, "{\n") + for y := 0; y < 8; y++ { + fmt.Fprintf(s, "\t") + for x := 0; x < 8; x++ { + fmt.Fprintf(s, "0x%04x, ", uint16(b[8*y+x])) + } + fmt.Fprintln(s) + } + fmt.Fprintf(s, "}") + return s.String() +} + +// testBlocks are the first 10 pre-IDCT blocks from ../testdata/video-001.jpeg. +var testBlocks = [10]block{ + { + 0x7f, 0xf6, 0x01, 0x07, 0xff, 0x00, 0x00, 0x00, + 0xf5, 0x01, 0xfa, 0x01, 0xfe, 0x00, 0x01, 0x00, + 0x05, 0x05, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x01, 0xff, 0xf8, 0x00, 0x01, 0xff, 0x00, 0x00, + 0x00, 0x01, 0x00, 0x01, 0x00, 0xff, 0xff, 0x00, + 0xff, 0x0c, 0x00, 0x00, 0x00, 0x00, 0xff, 0x01, + 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, + 0x01, 0x00, 0x00, 0x01, 0xff, 0x01, 0x00, 0xfe, + }, + { + 0x29, 0x07, 0x00, 0xfc, 0x01, 0x01, 0x00, 0x00, + 0x07, 0x00, 0x03, 0x00, 0x01, 0x00, 0xff, 0xff, + 0xff, 0xfd, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x04, 0x00, 0xff, 0x01, 0x00, 0x00, + 0x01, 0x00, 0x01, 0xff, 0x00, 0x00, 0x00, 0x00, + 0x01, 0xfa, 0x01, 0x00, 0x01, 0x00, 0x01, 0xff, + 0x00, 0x00, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0xff, 0x00, 0xff, 0x00, 0x02, + }, + { + 0xc5, 0xfa, 0x01, 0x00, 0x00, 0x01, 0x00, 0xff, + 0x02, 0xff, 0x01, 0x00, 0x01, 0x00, 0xff, 0x00, + 0xff, 0xff, 0x00, 0xff, 0x01, 0x00, 0x00, 0x00, + 0xff, 0x00, 0x01, 0x00, 0x00, 0x00, 0xff, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, + 0x00, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + }, + { + 0x86, 0x05, 0x00, 0x02, 0x00, 0x00, 0x01, 0x00, + 0xf2, 0x06, 0x00, 0x00, 0x01, 0x02, 0x00, 0x00, + 0xf6, 0xfa, 0xf9, 0x00, 0xff, 0x01, 0x00, 0x00, + 0xf9, 0x00, 0x00, 0xff, 0x00, 0x00, 0x00, 0x00, + 0x00, 0xff, 0x00, 0xff, 0xff, 0xff, 0x00, 0x00, + 0xff, 0x00, 0x00, 0x01, 0x00, 0xff, 0x01, 0x00, + 0x00, 0x00, 0x00, 0xff, 0x00, 0x00, 0x00, 0x01, + 0x00, 0x01, 0xff, 0x01, 0x00, 0xff, 0x00, 0x00, + }, + { + 0x24, 0xfe, 0x00, 0xff, 0x00, 0xff, 0xff, 0x00, + 0x08, 0xfd, 0x00, 0x01, 0x01, 0x00, 0x01, 0x00, + 0x06, 0x03, 0x03, 0xff, 0x00, 0x00, 0x00, 0x00, + 0x04, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, + 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x01, + 0x01, 0x00, 0x01, 0xff, 0x00, 0x01, 0x00, 0x00, + 0x01, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x01, 0x00, 0x01, 0x00, 0x00, 0x00, 0xff, 0x01, + }, + { + 0xcd, 0xff, 0x00, 0x00, 0x00, 0x00, 0x01, 0x01, + 0x03, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x00, 0x00, 0x00, + 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x01, 0x00, 0x00, 0x00, 0x00, 0x01, 0x01, 0x00, + 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, + 0x00, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0xff, + }, + { + 0x81, 0xfe, 0x05, 0xff, 0x01, 0xff, 0x01, 0x00, + 0xef, 0xf9, 0x00, 0xf9, 0x00, 0xff, 0x00, 0xff, + 0x05, 0xf9, 0x00, 0xf8, 0x01, 0xff, 0x01, 0xff, + 0x00, 0xff, 0x07, 0x00, 0x01, 0x00, 0x00, 0x00, + 0x01, 0x00, 0x01, 0x01, 0x00, 0x00, 0x00, 0x00, + 0x01, 0x00, 0x00, 0x00, 0xff, 0xff, 0x00, 0x01, + 0xff, 0x01, 0x01, 0x00, 0xff, 0x00, 0x00, 0x00, + 0x01, 0x01, 0x00, 0xff, 0x00, 0x00, 0x00, 0xff, + }, + { + 0x28, 0x00, 0xfe, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x0b, 0x02, 0x01, 0x03, 0x00, 0xff, 0x00, 0x01, + 0xfe, 0x02, 0x01, 0x03, 0xff, 0x00, 0x00, 0x00, + 0x01, 0x00, 0xfd, 0x00, 0x01, 0x00, 0xff, 0x00, + 0x01, 0xff, 0x00, 0xff, 0x01, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0xff, 0x01, 0x01, 0x00, 0xff, + 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0xff, 0xff, 0x00, 0x00, 0x00, 0xff, 0x00, 0x01, + }, + { + 0xdf, 0xf9, 0xfe, 0x00, 0x03, 0x01, 0xff, 0xff, + 0x04, 0x01, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, + 0xff, 0x01, 0x01, 0x01, 0x00, 0x00, 0x00, 0x01, + 0x00, 0x00, 0xfe, 0x01, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0xff, 0x01, 0x00, 0x00, 0x00, 0x01, + 0xff, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, + 0x00, 0xff, 0x00, 0xff, 0x01, 0x00, 0x00, 0x01, + 0xff, 0xff, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, + }, + { + 0x88, 0xfd, 0x00, 0x00, 0xff, 0x00, 0x01, 0xff, + 0xe1, 0x06, 0x06, 0x01, 0xff, 0x00, 0x01, 0x00, + 0x08, 0x00, 0xfa, 0x00, 0xff, 0xff, 0xff, 0xff, + 0x08, 0x01, 0x00, 0xff, 0x01, 0xff, 0x00, 0x00, + 0xf5, 0xff, 0x00, 0x01, 0xff, 0x01, 0x01, 0x00, + 0xff, 0xff, 0x01, 0xff, 0x01, 0x00, 0x01, 0x00, + 0x00, 0x01, 0x01, 0xff, 0x00, 0xff, 0x00, 0x01, + 0x02, 0x00, 0x00, 0xff, 0xff, 0x00, 0xff, 0x00, + }, +} diff --git a/libgo/go/image/jpeg/idct.go b/libgo/go/image/jpeg/idct.go index b387dfdffd1..92ff1e4b41d 100644 --- a/libgo/go/image/jpeg/idct.go +++ b/libgo/go/image/jpeg/idct.go @@ -37,6 +37,10 @@ package jpeg * */ +const blockSize = 64 // A DCT block is 8x8. + +type block [blockSize]int + const ( w1 = 2841 // 2048*sqrt(2)*cos(1*pi/16) w2 = 2676 // 2048*sqrt(2)*cos(2*pi/16) @@ -55,9 +59,7 @@ const ( r2 = 181 // 256/sqrt(2) ) -// idct performs a 2-D Inverse Discrete Cosine Transformation, followed by a -// +128 level shift and a clip to [0, 255], writing the results to dst. -// stride is the number of elements between successive rows of dst. +// idct performs a 2-D Inverse Discrete Cosine Transformation. // // The input coefficients should already have been multiplied by the // appropriate quantization table. We use fixed-point computation, with the @@ -67,33 +69,34 @@ const ( // For more on the actual algorithm, see Z. Wang, "Fast algorithms for the // discrete W transform and for the discrete Fourier transform", IEEE Trans. on // ASSP, Vol. ASSP- 32, pp. 803-816, Aug. 1984. -func idct(dst []byte, stride int, src *block) { +func idct(src *block) { // Horizontal 1-D IDCT. for y := 0; y < 8; y++ { + y8 := y * 8 // If all the AC components are zero, then the IDCT is trivial. - if src[y*8+1] == 0 && src[y*8+2] == 0 && src[y*8+3] == 0 && - src[y*8+4] == 0 && src[y*8+5] == 0 && src[y*8+6] == 0 && src[y*8+7] == 0 { - dc := src[y*8+0] << 3 - src[y*8+0] = dc - src[y*8+1] = dc - src[y*8+2] = dc - src[y*8+3] = dc - src[y*8+4] = dc - src[y*8+5] = dc - src[y*8+6] = dc - src[y*8+7] = dc + if src[y8+1] == 0 && src[y8+2] == 0 && src[y8+3] == 0 && + src[y8+4] == 0 && src[y8+5] == 0 && src[y8+6] == 0 && src[y8+7] == 0 { + dc := src[y8+0] << 3 + src[y8+0] = dc + src[y8+1] = dc + src[y8+2] = dc + src[y8+3] = dc + src[y8+4] = dc + src[y8+5] = dc + src[y8+6] = dc + src[y8+7] = dc continue } // Prescale. - x0 := (src[y*8+0] << 11) + 128 - x1 := src[y*8+4] << 11 - x2 := src[y*8+6] - x3 := src[y*8+2] - x4 := src[y*8+1] - x5 := src[y*8+7] - x6 := src[y*8+5] - x7 := src[y*8+3] + x0 := (src[y8+0] << 11) + 128 + x1 := src[y8+4] << 11 + x2 := src[y8+6] + x3 := src[y8+2] + x4 := src[y8+1] + x5 := src[y8+7] + x6 := src[y8+5] + x7 := src[y8+3] // Stage 1. x8 := w7 * (x4 + x5) @@ -123,14 +126,14 @@ func idct(dst []byte, stride int, src *block) { x4 = (r2*(x4-x5) + 128) >> 8 // Stage 4. - src[8*y+0] = (x7 + x1) >> 8 - src[8*y+1] = (x3 + x2) >> 8 - src[8*y+2] = (x0 + x4) >> 8 - src[8*y+3] = (x8 + x6) >> 8 - src[8*y+4] = (x8 - x6) >> 8 - src[8*y+5] = (x0 - x4) >> 8 - src[8*y+6] = (x3 - x2) >> 8 - src[8*y+7] = (x7 - x1) >> 8 + src[y8+0] = (x7 + x1) >> 8 + src[y8+1] = (x3 + x2) >> 8 + src[y8+2] = (x0 + x4) >> 8 + src[y8+3] = (x8 + x6) >> 8 + src[y8+4] = (x8 - x6) >> 8 + src[y8+5] = (x0 - x4) >> 8 + src[y8+6] = (x3 - x2) >> 8 + src[y8+7] = (x7 - x1) >> 8 } // Vertical 1-D IDCT. @@ -186,19 +189,4 @@ func idct(dst []byte, stride int, src *block) { src[8*6+x] = (y3 - y2) >> 14 src[8*7+x] = (y7 - y1) >> 14 } - - // Level shift by +128, clip to [0, 255], and write to dst. - for y := 0; y < 8; y++ { - for x := 0; x < 8; x++ { - c := src[y*8+x] - if c < -128 { - c = 0 - } else if c > 127 { - c = 255 - } else { - c += 128 - } - dst[y*stride+x] = uint8(c) - } - } } diff --git a/libgo/go/image/jpeg/reader.go b/libgo/go/image/jpeg/reader.go index 8da3611919e..415b093281d 100644 --- a/libgo/go/image/jpeg/reader.go +++ b/libgo/go/image/jpeg/reader.go @@ -35,11 +35,7 @@ type component struct { tq uint8 // Quantization table destination selector. } -type block [blockSize]int - const ( - blockSize = 64 // A DCT block is 8x8. - dcTable = 0 acTable = 1 maxTc = 1 @@ -51,7 +47,7 @@ const ( // A color JPEG image has Y, Cb and Cr components. nColorComponent = 3 - // We only support 4:4:4, 4:2:2 and 4:2:0 downsampling, and therefore the + // We only support 4:4:4, 4:4:0, 4:2:2 and 4:2:0 downsampling, and therefore the // number of luma samples per chroma sample is at most 2 in the horizontal // and 2 in the vertical direction. maxH = 2 @@ -96,6 +92,7 @@ type Reader interface { type decoder struct { r Reader + b bits width, height int img1 *image.Gray img3 *image.YCbCr @@ -104,7 +101,6 @@ type decoder struct { comp [nColorComponent]component huff [maxTc + 1][maxTh + 1]huffman quant [maxTq + 1]block // Quantization tables, in zig-zag order. - b bits tmp [1024]byte } @@ -156,12 +152,12 @@ func (d *decoder) processSOF(n int) error { if d.nComp == nGrayComponent { continue } - // For color images, we only support 4:4:4, 4:2:2 or 4:2:0 chroma + // For color images, we only support 4:4:4, 4:4:0, 4:2:2 or 4:2:0 chroma // downsampling ratios. This implies that the (h, v) values for the Y - // component are either (1, 1), (2, 1) or (2, 2), and the (h, v) + // component are either (1, 1), (1, 2), (2, 1) or (2, 2), and the (h, v) // values for the Cr and Cb components must be (1, 1). if i == 0 { - if hv != 0x11 && hv != 0x21 && hv != 0x22 { + if hv != 0x11 && hv != 0x21 && hv != 0x22 && hv != 0x12 { return UnsupportedError("luma downsample ratio") } } else if hv != 0x11 { @@ -205,12 +201,14 @@ func (d *decoder) makeImg(h0, v0, mxx, myy int) { return } var subsampleRatio image.YCbCrSubsampleRatio - switch h0 * v0 { - case 1: + switch { + case h0 == 1 && v0 == 1: subsampleRatio = image.YCbCrSubsampleRatio444 - case 2: + case h0 == 1 && v0 == 2: + subsampleRatio = image.YCbCrSubsampleRatio440 + case h0 == 2 && v0 == 1: subsampleRatio = image.YCbCrSubsampleRatio422 - case 4: + case h0 == 2 && v0 == 2: subsampleRatio = image.YCbCrSubsampleRatio420 default: panic("unreachable") @@ -311,18 +309,41 @@ func (d *decoder) processSOS(n int) error { } // Perform the inverse DCT and store the MCU component to the image. + idct(&b) + dst, stride := []byte(nil), 0 if d.nComp == nGrayComponent { - idct(d.img1.Pix[8*(my*d.img1.Stride+mx):], d.img1.Stride, &b) + dst, stride = d.img1.Pix[8*(my*d.img1.Stride+mx):], d.img1.Stride } else { switch i { case 0: - mx0 := h0*mx + (j % 2) - my0 := v0*my + (j / 2) - idct(d.img3.Y[8*(my0*d.img3.YStride+mx0):], d.img3.YStride, &b) + mx0, my0 := h0*mx, v0*my + if h0 == 1 { + my0 += j + } else { + mx0 += j % 2 + my0 += j / 2 + } + dst, stride = d.img3.Y[8*(my0*d.img3.YStride+mx0):], d.img3.YStride case 1: - idct(d.img3.Cb[8*(my*d.img3.CStride+mx):], d.img3.CStride, &b) + dst, stride = d.img3.Cb[8*(my*d.img3.CStride+mx):], d.img3.CStride case 2: - idct(d.img3.Cr[8*(my*d.img3.CStride+mx):], d.img3.CStride, &b) + dst, stride = d.img3.Cr[8*(my*d.img3.CStride+mx):], d.img3.CStride + } + } + // Level shift by +128, clip to [0, 255], and write to dst. + for y := 0; y < 8; y++ { + y8 := y * 8 + yStride := y * stride + for x := 0; x < 8; x++ { + c := b[y8+x] + if c < -128 { + c = 0 + } else if c > 127 { + c = 255 + } else { + c += 128 + } + dst[yStride+x] = uint8(c) } } } // for j diff --git a/libgo/go/image/jpeg/writer_test.go b/libgo/go/image/jpeg/writer_test.go index 8732df8459a..90b89a7b0f1 100644 --- a/libgo/go/image/jpeg/writer_test.go +++ b/libgo/go/image/jpeg/writer_test.go @@ -171,24 +171,38 @@ func TestWriter(t *testing.T) { } } -func BenchmarkEncodeRGBOpaque(b *testing.B) { +func BenchmarkDecode(b *testing.B) { + b.StopTimer() + data, err := ioutil.ReadFile("../testdata/video-001.jpeg") + if err != nil { + b.Fatal(err) + } + cfg, err := DecodeConfig(bytes.NewReader(data)) + if err != nil { + b.Fatal(err) + } + b.SetBytes(int64(cfg.Width * cfg.Height * 4)) + b.StartTimer() + for i := 0; i < b.N; i++ { + Decode(bytes.NewReader(data)) + } +} + +func BenchmarkEncode(b *testing.B) { b.StopTimer() img := image.NewRGBA(image.Rect(0, 0, 640, 480)) - // Set all pixels to 0xFF alpha to force opaque mode. bo := img.Bounds() rnd := rand.New(rand.NewSource(123)) for y := bo.Min.Y; y < bo.Max.Y; y++ { for x := bo.Min.X; x < bo.Max.X; x++ { - img.Set(x, y, color.RGBA{ + img.SetRGBA(x, y, color.RGBA{ uint8(rnd.Intn(256)), uint8(rnd.Intn(256)), uint8(rnd.Intn(256)), - 255}) + 255, + }) } } - if !img.Opaque() { - b.Fatal("expected image to be opaque") - } b.SetBytes(640 * 480 * 4) b.StartTimer() options := &Options{Quality: 90} diff --git a/libgo/go/image/png/paeth.go b/libgo/go/image/png/paeth.go new file mode 100644 index 00000000000..37978aa662d --- /dev/null +++ b/libgo/go/image/png/paeth.go @@ -0,0 +1,70 @@ +// Copyright 2012 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package png + +// paeth implements the Paeth filter function, as per the PNG specification. +func paeth(a, b, c uint8) uint8 { + // This is an optimized version of the sample code in the PNG spec. + // For example, the sample code starts with: + // p := int(a) + int(b) - int(c) + // pa := abs(p - int(a)) + // but the optimized form uses fewer arithmetic operations: + // pa := int(b) - int(c) + // pa = abs(pa) + pc := int(c) + pa := int(b) - pc + pb := int(a) - pc + pc = pa + pb + if pa < 0 { + pa = -pa + } + if pb < 0 { + pb = -pb + } + if pc < 0 { + pc = -pc + } + if pa <= pb && pa <= pc { + return a + } else if pb <= pc { + return b + } + return c +} + +// filterPaeth applies the Paeth filter to the cdat slice. +// cdat is the current row's data, pdat is the previous row's data. +func filterPaeth(cdat, pdat []byte, bytesPerPixel int) { + var a, b, c, pa, pb, pc int + for i := 0; i < bytesPerPixel; i++ { + a, c = 0, 0 + for j := i; j < len(cdat); j += bytesPerPixel { + b = int(pdat[j]) + pa = b - c + pb = a - c + pc = pa + pb + if pa < 0 { + pa = -pa + } + if pb < 0 { + pb = -pb + } + if pc < 0 { + pc = -pc + } + if pa <= pb && pa <= pc { + // No-op. + } else if pb <= pc { + a = b + } else { + a = c + } + a += int(cdat[j]) + a &= 0xff + cdat[j] = uint8(a) + c = b + } + } +} diff --git a/libgo/go/image/png/paeth_test.go b/libgo/go/image/png/paeth_test.go new file mode 100644 index 00000000000..bb084861ae9 --- /dev/null +++ b/libgo/go/image/png/paeth_test.go @@ -0,0 +1,91 @@ +// Copyright 2012 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package png + +import ( + "bytes" + "math/rand" + "testing" +) + +func abs(x int) int { + if x < 0 { + return -x + } + return x +} + +// slowPaeth is a slow but simple implementation of the Paeth function. +// It is a straight port of the sample code in the PNG spec, section 9.4. +func slowPaeth(a, b, c uint8) uint8 { + p := int(a) + int(b) - int(c) + pa := abs(p - int(a)) + pb := abs(p - int(b)) + pc := abs(p - int(c)) + if pa <= pb && pa <= pc { + return a + } else if pb <= pc { + return b + } + return c +} + +// slowFilterPaeth is a slow but simple implementation of func filterPaeth. +func slowFilterPaeth(cdat, pdat []byte, bytesPerPixel int) { + for i := 0; i < bytesPerPixel; i++ { + cdat[i] += paeth(0, pdat[i], 0) + } + for i := bytesPerPixel; i < len(cdat); i++ { + cdat[i] += paeth(cdat[i-bytesPerPixel], pdat[i], pdat[i-bytesPerPixel]) + } +} + +func TestPaeth(t *testing.T) { + for a := 0; a < 256; a += 15 { + for b := 0; b < 256; b += 15 { + for c := 0; c < 256; c += 15 { + got := paeth(uint8(a), uint8(b), uint8(c)) + want := slowPaeth(uint8(a), uint8(b), uint8(c)) + if got != want { + t.Errorf("a, b, c = %d, %d, %d: got %d, want %d", a, b, c, got, want) + } + } + } + } +} + +func BenchmarkPaeth(b *testing.B) { + for i := 0; i < b.N; i++ { + paeth(uint8(i>>16), uint8(i>>8), uint8(i)) + } +} + +func TestPaethDecode(t *testing.T) { + pdat0 := make([]byte, 32) + pdat1 := make([]byte, 32) + pdat2 := make([]byte, 32) + cdat0 := make([]byte, 32) + cdat1 := make([]byte, 32) + cdat2 := make([]byte, 32) + r := rand.New(rand.NewSource(1)) + for bytesPerPixel := 1; bytesPerPixel <= 8; bytesPerPixel++ { + for i := 0; i < 100; i++ { + for j := range pdat0 { + pdat0[j] = uint8(r.Uint32()) + cdat0[j] = uint8(r.Uint32()) + } + copy(pdat1, pdat0) + copy(pdat2, pdat0) + copy(cdat1, cdat0) + copy(cdat2, cdat0) + filterPaeth(cdat1, pdat1, bytesPerPixel) + slowFilterPaeth(cdat2, pdat2, bytesPerPixel) + if !bytes.Equal(cdat1, cdat2) { + t.Errorf("bytesPerPixel: %d\npdat0: % x\ncdat0: % x\ngot: % x\nwant: % x", bytesPerPixel, pdat0, cdat0, cdat1, cdat2) + break + } + } + } +} diff --git a/libgo/go/image/png/reader.go b/libgo/go/image/png/reader.go index fe07d60a91a..b3901b2adf9 100644 --- a/libgo/go/image/png/reader.go +++ b/libgo/go/image/png/reader.go @@ -98,13 +98,6 @@ type UnsupportedError string func (e UnsupportedError) Error() string { return "png: unsupported feature: " + string(e) } -func abs(x int) int { - if x < 0 { - return -x - } - return x -} - func min(a, b int) int { if a < b { return a @@ -233,7 +226,7 @@ func (d *decoder) parsetRNS(length uint32) error { } for i := 0; i < n; i++ { rgba := d.palette[i].(color.RGBA) - d.palette[i] = color.RGBA{rgba.R, rgba.G, rgba.B, d.tmp[i]} + d.palette[i] = color.NRGBA{rgba.R, rgba.G, rgba.B, d.tmp[i]} } case cbGA8, cbGA16, cbTCA8, cbTCA16: return FormatError("tRNS, color type mismatch") @@ -241,20 +234,6 @@ func (d *decoder) parsetRNS(length uint32) error { return d.verifyChecksum() } -// The Paeth filter function, as per the PNG specification. -func paeth(a, b, c uint8) uint8 { - p := int(a) + int(b) - int(c) - pa := abs(p - int(a)) - pb := abs(p - int(b)) - pc := abs(p - int(c)) - if pa <= pb && pa <= pc { - return a - } else if pb <= pc { - return b - } - return c -} - // Read presents one or more IDAT chunks as one continuous stream (minus the // intermediate chunk headers and footers). If the PNG data looked like: // ... len0 IDAT xxx crc0 len1 IDAT yy crc1 len2 IEND crc2 @@ -301,6 +280,7 @@ func (d *decoder) decode() (image.Image, error) { defer r.Close() bitsPerPixel := 0 maxPalette := uint8(0) + pixOffset := 0 var ( gray *image.Gray rgba *image.RGBA @@ -375,8 +355,8 @@ func (d *decoder) decode() (image.Image, error) { cdat[i] += cdat[i-bytesPerPixel] } case ftUp: - for i := 0; i < len(cdat); i++ { - cdat[i] += pdat[i] + for i, p := range pdat { + cdat[i] += p } case ftAverage: for i := 0; i < bytesPerPixel; i++ { @@ -386,12 +366,7 @@ func (d *decoder) decode() (image.Image, error) { cdat[i] += uint8((int(cdat[i-bytesPerPixel]) + int(pdat[i])) / 2) } case ftPaeth: - for i := 0; i < bytesPerPixel; i++ { - cdat[i] += paeth(0, pdat[i], 0) - } - for i := bytesPerPixel; i < len(cdat); i++ { - cdat[i] += paeth(cdat[i-bytesPerPixel], pdat[i], pdat[i-bytesPerPixel]) - } + filterPaeth(cdat, pdat, bytesPerPixel) default: return nil, FormatError("bad filter type") } @@ -423,18 +398,24 @@ func (d *decoder) decode() (image.Image, error) { } } case cbG8: - for x := 0; x < d.width; x++ { - gray.SetGray(x, y, color.Gray{cdat[x]}) - } + copy(gray.Pix[pixOffset:], cdat) + pixOffset += gray.Stride case cbGA8: for x := 0; x < d.width; x++ { ycol := cdat[2*x+0] nrgba.SetNRGBA(x, y, color.NRGBA{ycol, ycol, ycol, cdat[2*x+1]}) } case cbTC8: + pix, i, j := rgba.Pix, pixOffset, 0 for x := 0; x < d.width; x++ { - rgba.SetRGBA(x, y, color.RGBA{cdat[3*x+0], cdat[3*x+1], cdat[3*x+2], 0xff}) + pix[i+0] = cdat[j+0] + pix[i+1] = cdat[j+1] + pix[i+2] = cdat[j+2] + pix[i+3] = 0xff + i += 4 + j += 3 } + pixOffset += rgba.Stride case cbP1: for x := 0; x < d.width; x += 8 { b := cdat[x/8] @@ -472,16 +453,18 @@ func (d *decoder) decode() (image.Image, error) { } } case cbP8: - for x := 0; x < d.width; x++ { - if cdat[x] > maxPalette { - return nil, FormatError("palette index out of range") + if maxPalette != 255 { + for x := 0; x < d.width; x++ { + if cdat[x] > maxPalette { + return nil, FormatError("palette index out of range") + } } - paletted.SetColorIndex(x, y, cdat[x]) } + copy(paletted.Pix[pixOffset:], cdat) + pixOffset += paletted.Stride case cbTCA8: - for x := 0; x < d.width; x++ { - nrgba.SetNRGBA(x, y, color.NRGBA{cdat[4*x+0], cdat[4*x+1], cdat[4*x+2], cdat[4*x+3]}) - } + copy(nrgba.Pix[pixOffset:], cdat) + pixOffset += nrgba.Stride case cbG16: for x := 0; x < d.width; x++ { ycol := uint16(cdat[2*x+0])<<8 | uint16(cdat[2*x+1]) diff --git a/libgo/go/image/png/reader_test.go b/libgo/go/image/png/reader_test.go index 24c4ea44808..8223f521cc3 100644 --- a/libgo/go/image/png/reader_test.go +++ b/libgo/go/image/png/reader_test.go @@ -10,6 +10,7 @@ import ( "image" "image/color" "io" + "io/ioutil" "os" "strings" "testing" @@ -106,13 +107,18 @@ func sng(w io.WriteCloser, filename string, png image.Image) { lastAlpha := -1 io.WriteString(w, "PLTE {\n") for i, c := range cpm { - r, g, b, a := c.RGBA() - if a != 0xffff { + var r, g, b, a uint8 + switch c := c.(type) { + case color.RGBA: + r, g, b, a = c.R, c.G, c.B, 0xff + case color.NRGBA: + r, g, b, a = c.R, c.G, c.B, c.A + default: + panic("unknown palette color type") + } + if a != 0xff { lastAlpha = i } - r >>= 8 - g >>= 8 - b >>= 8 fmt.Fprintf(w, " (%3d,%3d,%3d) # rgb = (0x%02x,0x%02x,0x%02x)\n", r, g, b, r, g, b) } io.WriteString(w, "}\n") @@ -267,3 +273,41 @@ func TestReaderError(t *testing.T) { } } } + +func benchmarkDecode(b *testing.B, filename string, bytesPerPixel int) { + b.StopTimer() + data, err := ioutil.ReadFile(filename) + if err != nil { + b.Fatal(err) + } + s := string(data) + cfg, err := DecodeConfig(strings.NewReader(s)) + if err != nil { + b.Fatal(err) + } + b.SetBytes(int64(cfg.Width * cfg.Height * bytesPerPixel)) + b.StartTimer() + for i := 0; i < b.N; i++ { + Decode(strings.NewReader(s)) + } +} + +func BenchmarkDecodeGray(b *testing.B) { + benchmarkDecode(b, "testdata/benchGray.png", 1) +} + +func BenchmarkDecodeNRGBAGradient(b *testing.B) { + benchmarkDecode(b, "testdata/benchNRGBA-gradient.png", 4) +} + +func BenchmarkDecodeNRGBAOpaque(b *testing.B) { + benchmarkDecode(b, "testdata/benchNRGBA-opaque.png", 4) +} + +func BenchmarkDecodePaletted(b *testing.B) { + benchmarkDecode(b, "testdata/benchPaletted.png", 1) +} + +func BenchmarkDecodeRGB(b *testing.B) { + benchmarkDecode(b, "testdata/benchRGB.png", 4) +} diff --git a/libgo/go/image/png/writer.go b/libgo/go/image/png/writer.go index 57c03792b59..093d47193ba 100644 --- a/libgo/go/image/png/writer.go +++ b/libgo/go/image/png/writer.go @@ -21,7 +21,7 @@ type encoder struct { err error header [8]byte footer [4]byte - tmp [3 * 256]byte + tmp [4 * 256]byte } // Big-endian. @@ -70,7 +70,7 @@ func (e *encoder) writeChunk(b []byte, name string) { e.err = UnsupportedError(name + " chunk is too large: " + strconv.Itoa(len(b))) return } - writeUint32(e.header[0:4], n) + writeUint32(e.header[:4], n) e.header[4] = name[0] e.header[5] = name[1] e.header[6] = name[2] @@ -78,9 +78,9 @@ func (e *encoder) writeChunk(b []byte, name string) { crc := crc32.NewIEEE() crc.Write(e.header[4:8]) crc.Write(b) - writeUint32(e.footer[0:4], crc.Sum32()) + writeUint32(e.footer[:4], crc.Sum32()) - _, e.err = e.w.Write(e.header[0:8]) + _, e.err = e.w.Write(e.header[:8]) if e.err != nil { return } @@ -88,7 +88,7 @@ func (e *encoder) writeChunk(b []byte, name string) { if e.err != nil { return } - _, e.err = e.w.Write(e.footer[0:4]) + _, e.err = e.w.Write(e.footer[:4]) } func (e *encoder) writeIHDR() { @@ -122,36 +122,29 @@ func (e *encoder) writeIHDR() { e.tmp[10] = 0 // default compression method e.tmp[11] = 0 // default filter method e.tmp[12] = 0 // non-interlaced - e.writeChunk(e.tmp[0:13], "IHDR") + e.writeChunk(e.tmp[:13], "IHDR") } -func (e *encoder) writePLTE(p color.Palette) { +func (e *encoder) writePLTEAndTRNS(p color.Palette) { if len(p) < 1 || len(p) > 256 { e.err = FormatError("bad palette length: " + strconv.Itoa(len(p))) return } - for i, c := range p { - r, g, b, _ := c.RGBA() - e.tmp[3*i+0] = uint8(r >> 8) - e.tmp[3*i+1] = uint8(g >> 8) - e.tmp[3*i+2] = uint8(b >> 8) - } - e.writeChunk(e.tmp[0:3*len(p)], "PLTE") -} - -func (e *encoder) maybeWritetRNS(p color.Palette) { last := -1 for i, c := range p { - _, _, _, a := c.RGBA() - if a != 0xffff { + c1 := color.NRGBAModel.Convert(c).(color.NRGBA) + e.tmp[3*i+0] = c1.R + e.tmp[3*i+1] = c1.G + e.tmp[3*i+2] = c1.B + if c1.A != 0xff { last = i } - e.tmp[i] = uint8(a >> 8) + e.tmp[3*256+i] = c1.A } - if last == -1 { - return + e.writeChunk(e.tmp[:3*len(p)], "PLTE") + if last != -1 { + e.writeChunk(e.tmp[3*256:3*256+1+last], "tRNS") } - e.writeChunk(e.tmp[:last+1], "tRNS") } // An encoder is an io.Writer that satisfies writes by writing PNG IDAT chunks, @@ -297,26 +290,42 @@ func writeImage(w io.Writer, m image.Image, cb int) error { } pr := make([]uint8, 1+bpp*b.Dx()) + gray, _ := m.(*image.Gray) + rgba, _ := m.(*image.RGBA) + paletted, _ := m.(*image.Paletted) + nrgba, _ := m.(*image.NRGBA) + for y := b.Min.Y; y < b.Max.Y; y++ { // Convert from colors to bytes. i := 1 switch cb { case cbG8: - for x := b.Min.X; x < b.Max.X; x++ { - c := color.GrayModel.Convert(m.At(x, y)).(color.Gray) - cr[0][i] = c.Y - i++ + if gray != nil { + offset := (y - b.Min.Y) * gray.Stride + copy(cr[0][1:], gray.Pix[offset:offset+b.Dx()]) + } else { + for x := b.Min.X; x < b.Max.X; x++ { + c := color.GrayModel.Convert(m.At(x, y)).(color.Gray) + cr[0][i] = c.Y + i++ + } } case cbTC8: // We have previously verified that the alpha value is fully opaque. cr0 := cr[0] - if rgba, _ := m.(*image.RGBA); rgba != nil { - j0 := (y - b.Min.Y) * rgba.Stride + stride, pix := 0, []byte(nil) + if rgba != nil { + stride, pix = rgba.Stride, rgba.Pix + } else if nrgba != nil { + stride, pix = nrgba.Stride, nrgba.Pix + } + if stride != 0 { + j0 := (y - b.Min.Y) * stride j1 := j0 + b.Dx()*4 for j := j0; j < j1; j += 4 { - cr0[i+0] = rgba.Pix[j+0] - cr0[i+1] = rgba.Pix[j+1] - cr0[i+2] = rgba.Pix[j+2] + cr0[i+0] = pix[j+0] + cr0[i+1] = pix[j+1] + cr0[i+2] = pix[j+2] i += 3 } } else { @@ -329,9 +338,9 @@ func writeImage(w io.Writer, m image.Image, cb int) error { } } case cbP8: - if p, _ := m.(*image.Paletted); p != nil { - offset := (y - b.Min.Y) * p.Stride - copy(cr[0][1:], p.Pix[offset:offset+b.Dx()]) + if paletted != nil { + offset := (y - b.Min.Y) * paletted.Stride + copy(cr[0][1:], paletted.Pix[offset:offset+b.Dx()]) } else { pi := m.(image.PalettedImage) for x := b.Min.X; x < b.Max.X; x++ { @@ -340,14 +349,19 @@ func writeImage(w io.Writer, m image.Image, cb int) error { } } case cbTCA8: - // Convert from image.Image (which is alpha-premultiplied) to PNG's non-alpha-premultiplied. - for x := b.Min.X; x < b.Max.X; x++ { - c := color.NRGBAModel.Convert(m.At(x, y)).(color.NRGBA) - cr[0][i+0] = c.R - cr[0][i+1] = c.G - cr[0][i+2] = c.B - cr[0][i+3] = c.A - i += 4 + if nrgba != nil { + offset := (y - b.Min.Y) * nrgba.Stride + copy(cr[0][1:], nrgba.Pix[offset:offset+b.Dx()*4]) + } else { + // Convert from image.Image (which is alpha-premultiplied) to PNG's non-alpha-premultiplied. + for x := b.Min.X; x < b.Max.X; x++ { + c := color.NRGBAModel.Convert(m.At(x, y)).(color.NRGBA) + cr[0][i+0] = c.R + cr[0][i+1] = c.G + cr[0][i+2] = c.B + cr[0][i+3] = c.A + i += 4 + } } case cbG16: for x := b.Min.X; x < b.Max.X; x++ { @@ -412,7 +426,7 @@ func (e *encoder) writeIDATs() { e.err = bw.Flush() } -func (e *encoder) writeIEND() { e.writeChunk(e.tmp[0:0], "IEND") } +func (e *encoder) writeIEND() { e.writeChunk(nil, "IEND") } // Encode writes the Image m to w in PNG format. Any Image may be encoded, but // images that are not image.NRGBA might be encoded lossily. @@ -460,8 +474,7 @@ func Encode(w io.Writer, m image.Image) error { _, e.err = io.WriteString(w, pngHeader) e.writeIHDR() if pal != nil { - e.writePLTE(pal) - e.maybeWritetRNS(pal) + e.writePLTEAndTRNS(pal) } e.writeIDATs() e.writeIEND() diff --git a/libgo/go/image/png/writer_test.go b/libgo/go/image/png/writer_test.go index 644c4fb44b3..3116fc9ff94 100644 --- a/libgo/go/image/png/writer_test.go +++ b/libgo/go/image/png/writer_test.go @@ -101,6 +101,49 @@ func TestSubImage(t *testing.T) { } } +func BenchmarkEncodeGray(b *testing.B) { + b.StopTimer() + img := image.NewGray(image.Rect(0, 0, 640, 480)) + b.SetBytes(640 * 480 * 1) + b.StartTimer() + for i := 0; i < b.N; i++ { + Encode(ioutil.Discard, img) + } +} + +func BenchmarkEncodeNRGBOpaque(b *testing.B) { + b.StopTimer() + img := image.NewNRGBA(image.Rect(0, 0, 640, 480)) + // Set all pixels to 0xFF alpha to force opaque mode. + bo := img.Bounds() + for y := bo.Min.Y; y < bo.Max.Y; y++ { + for x := bo.Min.X; x < bo.Max.X; x++ { + img.Set(x, y, color.NRGBA{0, 0, 0, 255}) + } + } + if !img.Opaque() { + b.Fatal("expected image to be opaque") + } + b.SetBytes(640 * 480 * 4) + b.StartTimer() + for i := 0; i < b.N; i++ { + Encode(ioutil.Discard, img) + } +} + +func BenchmarkEncodeNRGBA(b *testing.B) { + b.StopTimer() + img := image.NewNRGBA(image.Rect(0, 0, 640, 480)) + if img.Opaque() { + b.Fatal("expected image not to be opaque") + } + b.SetBytes(640 * 480 * 4) + b.StartTimer() + for i := 0; i < b.N; i++ { + Encode(ioutil.Discard, img) + } +} + func BenchmarkEncodePaletted(b *testing.B) { b.StopTimer() img := image.NewPaletted(image.Rect(0, 0, 640, 480), color.Palette{ @@ -138,7 +181,7 @@ func BenchmarkEncodeRGBA(b *testing.B) { b.StopTimer() img := image.NewRGBA(image.Rect(0, 0, 640, 480)) if img.Opaque() { - b.Fatal("expected image to not be opaque") + b.Fatal("expected image not to be opaque") } b.SetBytes(640 * 480 * 4) b.StartTimer() diff --git a/libgo/go/image/ycbcr.go b/libgo/go/image/ycbcr.go index c1a0b666f83..5b73bef7895 100644 --- a/libgo/go/image/ycbcr.go +++ b/libgo/go/image/ycbcr.go @@ -15,6 +15,7 @@ const ( YCbCrSubsampleRatio444 YCbCrSubsampleRatio = iota YCbCrSubsampleRatio422 YCbCrSubsampleRatio420 + YCbCrSubsampleRatio440 ) func (s YCbCrSubsampleRatio) String() string { @@ -25,6 +26,8 @@ func (s YCbCrSubsampleRatio) String() string { return "YCbCrSubsampleRatio422" case YCbCrSubsampleRatio420: return "YCbCrSubsampleRatio420" + case YCbCrSubsampleRatio440: + return "YCbCrSubsampleRatio440" } return "YCbCrSubsampleRatioUnknown" } @@ -39,6 +42,7 @@ func (s YCbCrSubsampleRatio) String() string { // For 4:4:4, CStride == YStride/1 && len(Cb) == len(Cr) == len(Y)/1. // For 4:2:2, CStride == YStride/2 && len(Cb) == len(Cr) == len(Y)/2. // For 4:2:0, CStride == YStride/2 && len(Cb) == len(Cr) == len(Y)/4. +// For 4:4:0, CStride == YStride/1 && len(Cb) == len(Cr) == len(Y)/2. type YCbCr struct { Y, Cb, Cr []uint8 YStride int @@ -82,6 +86,8 @@ func (p *YCbCr) COffset(x, y int) int { return (y-p.Rect.Min.Y)*p.CStride + (x/2 - p.Rect.Min.X/2) case YCbCrSubsampleRatio420: return (y/2-p.Rect.Min.Y/2)*p.CStride + (x/2 - p.Rect.Min.X/2) + case YCbCrSubsampleRatio440: + return (y/2-p.Rect.Min.Y/2)*p.CStride + (x - p.Rect.Min.X) } // Default to 4:4:4 subsampling. return (y-p.Rect.Min.Y)*p.CStride + (x - p.Rect.Min.X) @@ -126,6 +132,9 @@ func NewYCbCr(r Rectangle, subsampleRatio YCbCrSubsampleRatio) *YCbCr { case YCbCrSubsampleRatio420: cw = (r.Max.X+1)/2 - r.Min.X/2 ch = (r.Max.Y+1)/2 - r.Min.Y/2 + case YCbCrSubsampleRatio440: + cw = w + ch = (r.Max.Y+1)/2 - r.Min.Y/2 default: // Default to 4:4:4 subsampling. cw = w diff --git a/libgo/go/image/ycbcr_test.go b/libgo/go/image/ycbcr_test.go index b2373f79ba3..7d1043dd395 100644 --- a/libgo/go/image/ycbcr_test.go +++ b/libgo/go/image/ycbcr_test.go @@ -37,6 +37,7 @@ func TestYCbCr(t *testing.T) { YCbCrSubsampleRatio444, YCbCrSubsampleRatio422, YCbCrSubsampleRatio420, + YCbCrSubsampleRatio440, } deltas := []Point{ Pt(0, 0), |