diff options
Diffstat (limited to 'libgo/go/image')
25 files changed, 2509 insertions, 507 deletions
diff --git a/libgo/go/image/bmp/reader.go b/libgo/go/image/bmp/reader.go new file mode 100644 index 0000000..357da1d --- /dev/null +++ b/libgo/go/image/bmp/reader.go @@ -0,0 +1,151 @@ +// Copyright 2011 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 bmp implements a BMP image decoder. +// +// The BMP specification is at http://www.digicamsoft.com/bmp/bmp.html. +package bmp + +import ( + "image" + "io" + "os" +) + +// ErrUnsupported means that the input BMP image uses a valid but unsupported +// feature. +var ErrUnsupported = os.NewError("bmp: unsupported BMP image") + +func readUint16(b []byte) uint16 { + return uint16(b[0]) | uint16(b[1])<<8 +} + +func readUint32(b []byte) uint32 { + return uint32(b[0]) | uint32(b[1])<<8 | uint32(b[2])<<16 | uint32(b[3])<<24 +} + +// decodePaletted reads an 8 bit-per-pixel BMP image from r. +func decodePaletted(r io.Reader, c image.Config) (image.Image, os.Error) { + var tmp [4]byte + paletted := image.NewPaletted(c.Width, c.Height, c.ColorModel.(image.PalettedColorModel)) + // BMP images are stored bottom-up rather than top-down. + for y := c.Height - 1; y >= 0; y-- { + p := paletted.Pix[y*paletted.Stride : y*paletted.Stride+c.Width] + _, err := io.ReadFull(r, p) + if err != nil { + return nil, err + } + // Each row is 4-byte aligned. + if c.Width%4 != 0 { + _, err := io.ReadFull(r, tmp[:4-c.Width%4]) + if err != nil { + return nil, err + } + } + } + return paletted, nil +} + +// decodeRGBA reads a 24 bit-per-pixel BMP image from r. +func decodeRGBA(r io.Reader, c image.Config) (image.Image, os.Error) { + rgba := image.NewRGBA(c.Width, c.Height) + // There are 3 bytes per pixel, and each row is 4-byte aligned. + b := make([]byte, (3*c.Width+3)&^3) + // BMP images are stored bottom-up rather than top-down. + for y := c.Height - 1; y >= 0; y-- { + _, err := io.ReadFull(r, b) + if err != nil { + return nil, err + } + p := rgba.Pix[y*rgba.Stride : y*rgba.Stride+c.Width*4] + for i, j := 0, 0; i < len(p); i, j = i+4, j+3 { + // BMP images are stored in BGR order rather than RGB order. + p[i+0] = b[j+2] + p[i+1] = b[j+1] + p[i+2] = b[j+0] + p[i+3] = 0xFF + } + } + return rgba, nil +} + +// Decode reads a BMP image from r and returns it as an image.Image. +// Limitation: The file must be 8 or 24 bits per pixel. +func Decode(r io.Reader) (image.Image, os.Error) { + c, err := DecodeConfig(r) + if err != nil { + return nil, err + } + if c.ColorModel == image.RGBAColorModel { + return decodeRGBA(r, c) + } + return decodePaletted(r, c) +} + +// DecodeConfig returns the color model and dimensions of a BMP image without +// decoding the entire image. +// Limitation: The file must be 8 or 24 bits per pixel. +func DecodeConfig(r io.Reader) (config image.Config, err os.Error) { + // We only support those BMP images that are a BITMAPFILEHEADER + // immediately followed by a BITMAPINFOHEADER. + const ( + fileHeaderLen = 14 + infoHeaderLen = 40 + ) + var b [1024]byte + if _, err = io.ReadFull(r, b[:fileHeaderLen+infoHeaderLen]); err != nil { + return + } + if string(b[:2]) != "BM" { + err = os.NewError("bmp: invalid format") + return + } + offset := readUint32(b[10:14]) + if readUint32(b[14:18]) != infoHeaderLen { + err = ErrUnsupported + return + } + width := int(readUint32(b[18:22])) + height := int(readUint32(b[22:26])) + if width < 0 || height < 0 { + err = ErrUnsupported + return + } + // We only support 1 plane, 8 or 24 bits per pixel and no compression. + planes, bpp, compression := readUint16(b[26:28]), readUint16(b[28:30]), readUint32(b[30:34]) + if planes != 1 || compression != 0 { + err = ErrUnsupported + return + } + switch bpp { + case 8: + if offset != fileHeaderLen+infoHeaderLen+256*4 { + err = ErrUnsupported + return + } + _, err = io.ReadFull(r, b[:256*4]) + if err != nil { + return + } + pcm := make(image.PalettedColorModel, 256) + for i := range pcm { + // BMP images are stored in BGR order rather than RGB order. + // Every 4th byte is padding. + pcm[i] = image.RGBAColor{b[4*i+2], b[4*i+1], b[4*i+0], 0xFF} + } + return image.Config{pcm, width, height}, nil + case 24: + if offset != fileHeaderLen+infoHeaderLen { + err = ErrUnsupported + return + } + return image.Config{image.RGBAColorModel, width, height}, nil + } + err = ErrUnsupported + return +} + +func init() { + image.RegisterFormat("bmp", "BM????\x00\x00\x00\x00", Decode, DecodeConfig) +} diff --git a/libgo/go/image/color.go b/libgo/go/image/color.go index c1345c0..501a882 100644 --- a/libgo/go/image/color.go +++ b/libgo/go/image/color.go @@ -4,14 +4,14 @@ package image -// All Colors can convert themselves, with a possible loss of precision, -// to 64-bit alpha-premultiplied RGBA. Each channel value ranges within -// [0, 0xFFFF]. +// Color can convert itself to alpha-premultiplied RGBA, with a possible loss +// of precision. Each value ranges within [0, 0xFFFF], but is represented by a +// uint32 so that multiplying by a blend factor up to 0xFFFF will not overflow. type Color interface { RGBA() (r, g, b, a uint32) } -// An RGBAColor represents a traditional 32-bit alpha-premultiplied color, +// RGBAColor represents a traditional 32-bit alpha-premultiplied color, // having 8 bits for each of red, green, blue and alpha. type RGBAColor struct { R, G, B, A uint8 @@ -29,7 +29,7 @@ func (c RGBAColor) RGBA() (r, g, b, a uint32) { return } -// An RGBA64Color represents a 64-bit alpha-premultiplied color, +// RGBA64Color represents a 64-bit alpha-premultiplied color, // having 16 bits for each of red, green, blue and alpha. type RGBA64Color struct { R, G, B, A uint16 @@ -39,7 +39,7 @@ func (c RGBA64Color) RGBA() (r, g, b, a uint32) { return uint32(c.R), uint32(c.G), uint32(c.B), uint32(c.A) } -// An NRGBAColor represents a non-alpha-premultiplied 32-bit color. +// NRGBAColor represents a non-alpha-premultiplied 32-bit color. type NRGBAColor struct { R, G, B, A uint8 } @@ -62,7 +62,7 @@ func (c NRGBAColor) RGBA() (r, g, b, a uint32) { return } -// An NRGBA64Color represents a non-alpha-premultiplied 64-bit color, +// NRGBA64Color represents a non-alpha-premultiplied 64-bit color, // having 16 bits for each of red, green, blue and alpha. type NRGBA64Color struct { R, G, B, A uint16 @@ -82,7 +82,7 @@ func (c NRGBA64Color) RGBA() (r, g, b, a uint32) { return } -// An AlphaColor represents an 8-bit alpha. +// AlphaColor represents an 8-bit alpha. type AlphaColor struct { A uint8 } @@ -93,7 +93,7 @@ func (c AlphaColor) RGBA() (r, g, b, a uint32) { return a, a, a, a } -// An Alpha16Color represents a 16-bit alpha. +// Alpha16Color represents a 16-bit alpha. type Alpha16Color struct { A uint16 } @@ -103,7 +103,7 @@ func (c Alpha16Color) RGBA() (r, g, b, a uint32) { return a, a, a, a } -// A GrayColor represents an 8-bit grayscale color. +// GrayColor represents an 8-bit grayscale color. type GrayColor struct { Y uint8 } @@ -114,7 +114,7 @@ func (c GrayColor) RGBA() (r, g, b, a uint32) { return y, y, y, 0xffff } -// A Gray16Color represents a 16-bit grayscale color. +// Gray16Color represents a 16-bit grayscale color. type Gray16Color struct { Y uint16 } @@ -124,7 +124,7 @@ func (c Gray16Color) RGBA() (r, g, b, a uint32) { return y, y, y, 0xffff } -// A ColorModel can convert foreign Colors, with a possible loss of precision, +// ColorModel can convert foreign Colors, with a possible loss of precision, // to a Color from its own color model. type ColorModel interface { Convert(c Color) Color diff --git a/libgo/go/image/decode_test.go b/libgo/go/image/decode_test.go index fee537c..540d5ed 100644 --- a/libgo/go/image/decode_test.go +++ b/libgo/go/image/decode_test.go @@ -10,29 +10,34 @@ import ( "os" "testing" - // TODO(nigeltao): implement bmp decoder. + _ "image/bmp" _ "image/gif" _ "image/jpeg" _ "image/png" _ "image/tiff" ) -const goldenFile = "testdata/video-001.png" - type imageTest struct { - filename string - tolerance int + goldenFilename string + filename string + tolerance int } var imageTests = []imageTest{ - //{"testdata/video-001.bmp", 0}, + {"testdata/video-001.png", "testdata/video-001.bmp", 0}, // GIF images are restricted to a 256-color palette and the conversion // to GIF loses significant image quality. - {"testdata/video-001.gif", 64 << 8}, + {"testdata/video-001.png", "testdata/video-001.gif", 64 << 8}, + {"testdata/video-001.png", "testdata/video-001.interlaced.gif", 64 << 8}, + {"testdata/video-001.png", "testdata/video-001.5bpp.gif", 128 << 8}, // JPEG is a lossy format and hence needs a non-zero tolerance. - {"testdata/video-001.jpeg", 8 << 8}, - {"testdata/video-001.png", 0}, - {"testdata/video-001.tiff", 0}, + {"testdata/video-001.png", "testdata/video-001.jpeg", 8 << 8}, + {"testdata/video-001.png", "testdata/video-001.png", 0}, + {"testdata/video-001.png", "testdata/video-001.tiff", 0}, + + // Test grayscale images. + {"testdata/video-005.gray.png", "testdata/video-005.gray.jpeg", 8 << 8}, + {"testdata/video-005.gray.png", "testdata/video-005.gray.png", 0}, } func decode(filename string) (image.Image, string, os.Error) { @@ -44,6 +49,15 @@ func decode(filename string) (image.Image, string, os.Error) { return image.Decode(bufio.NewReader(f)) } +func decodeConfig(filename string) (image.Config, string, os.Error) { + f, err := os.Open(filename) + if err != nil { + return image.Config{}, "", err + } + defer f.Close() + return image.DecodeConfig(bufio.NewReader(f)) +} + func delta(u0, u1 uint32) int { d := int(u0) - int(u1) if d < 0 { @@ -63,29 +77,47 @@ func withinTolerance(c0, c1 image.Color, tolerance int) bool { } func TestDecode(t *testing.T) { - golden, _, err := decode(goldenFile) - if err != nil { - t.Errorf("%s: %v", goldenFile, err) - } + golden := make(map[string]image.Image) loop: for _, it := range imageTests { - m, _, err := decode(it.filename) + g := golden[it.goldenFilename] + if g == nil { + var err os.Error + g, _, err = decode(it.goldenFilename) + if err != nil { + t.Errorf("%s: %v", it.goldenFilename, err) + continue loop + } + golden[it.goldenFilename] = g + } + m, imageFormat, err := decode(it.filename) if err != nil { t.Errorf("%s: %v", it.filename, err) continue loop } - b := golden.Bounds() + b := g.Bounds() if !b.Eq(m.Bounds()) { t.Errorf("%s: want bounds %v got %v", it.filename, b, m.Bounds()) continue loop } for y := b.Min.Y; y < b.Max.Y; y++ { for x := b.Min.X; x < b.Max.X; x++ { - if !withinTolerance(golden.At(x, y), m.At(x, y), it.tolerance) { - t.Errorf("%s: at (%d, %d), want %v got %v", it.filename, x, y, golden.At(x, y), m.At(x, y)) + if !withinTolerance(g.At(x, y), m.At(x, y), it.tolerance) { + t.Errorf("%s: at (%d, %d), want %v got %v", it.filename, x, y, g.At(x, y), m.At(x, y)) continue loop } } } + if imageFormat == "gif" { + // Each frame of a GIF can have a frame-local palette override the + // GIF-global palette. Thus, image.Decode can yield a different ColorModel + // than image.DecodeConfig. + continue + } + c, _, err := decodeConfig(it.filename) + if m.ColorModel() != c.ColorModel { + t.Errorf("%s: color models differ", it.filename) + continue loop + } } } diff --git a/libgo/go/image/draw/bench_test.go b/libgo/go/image/draw/bench_test.go new file mode 100644 index 0000000..a99b408 --- /dev/null +++ b/libgo/go/image/draw/bench_test.go @@ -0,0 +1,206 @@ +// Copyright 2011 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 draw + +import ( + "image" + "image/ycbcr" + "testing" +) + +const ( + dstw, dsth = 640, 480 + srcw, srch = 400, 300 +) + +// bench benchmarks drawing src and mask images onto a dst image with the +// given op and the color models to create those images from. +// The created images' pixels are initialized to non-zero values. +func bench(b *testing.B, dcm, scm, mcm image.ColorModel, op Op) { + b.StopTimer() + + var dst Image + switch dcm { + case image.RGBAColorModel: + dst1 := image.NewRGBA(dstw, dsth) + for y := 0; y < dsth; y++ { + for x := 0; x < dstw; x++ { + dst1.SetRGBA(x, y, image.RGBAColor{ + uint8(5 * x % 0x100), + uint8(7 * y % 0x100), + uint8((7*x + 5*y) % 0x100), + 0xff, + }) + } + } + dst = dst1 + case image.RGBA64ColorModel: + dst1 := image.NewRGBA64(dstw, dsth) + for y := 0; y < dsth; y++ { + for x := 0; x < dstw; x++ { + dst1.SetRGBA64(x, y, image.RGBA64Color{ + uint16(53 * x % 0x10000), + uint16(59 * y % 0x10000), + uint16((59*x + 53*y) % 0x10000), + 0xffff, + }) + } + } + dst = dst1 + default: + panic("unreachable") + } + + var src image.Image + switch scm { + case nil: + src = &image.ColorImage{image.RGBAColor{0x11, 0x22, 0x33, 0xff}} + case image.RGBAColorModel: + src1 := image.NewRGBA(srcw, srch) + for y := 0; y < srch; y++ { + for x := 0; x < srcw; x++ { + src1.SetRGBA(x, y, image.RGBAColor{ + uint8(13 * x % 0x80), + uint8(11 * y % 0x80), + uint8((11*x + 13*y) % 0x80), + 0x7f, + }) + } + } + src = src1 + case image.RGBA64ColorModel: + src1 := image.NewRGBA64(srcw, srch) + for y := 0; y < srch; y++ { + for x := 0; x < srcw; x++ { + src1.SetRGBA64(x, y, image.RGBA64Color{ + uint16(103 * x % 0x8000), + uint16(101 * y % 0x8000), + uint16((101*x + 103*y) % 0x8000), + 0x7fff, + }) + } + } + src = src1 + case image.NRGBAColorModel: + src1 := image.NewNRGBA(srcw, srch) + for y := 0; y < srch; y++ { + for x := 0; x < srcw; x++ { + src1.SetNRGBA(x, y, image.NRGBAColor{ + uint8(13 * x % 0x100), + uint8(11 * y % 0x100), + uint8((11*x + 13*y) % 0x100), + 0x7f, + }) + } + } + src = src1 + case ycbcr.YCbCrColorModel: + yy := make([]uint8, srcw*srch) + cb := make([]uint8, srcw*srch) + cr := make([]uint8, srcw*srch) + for i := range yy { + yy[i] = uint8(3 * i % 0x100) + cb[i] = uint8(5 * i % 0x100) + cr[i] = uint8(7 * i % 0x100) + } + src = &ycbcr.YCbCr{ + Y: yy, + Cb: cb, + Cr: cr, + YStride: srcw, + CStride: srcw, + SubsampleRatio: ycbcr.SubsampleRatio444, + Rect: image.Rect(0, 0, srcw, srch), + } + default: + panic("unreachable") + } + + var mask image.Image + switch mcm { + case nil: + // No-op. + case image.AlphaColorModel: + mask1 := image.NewAlpha(srcw, srch) + for y := 0; y < srch; y++ { + for x := 0; x < srcw; x++ { + a := uint8((23*x + 29*y) % 0x100) + // Glyph masks are typically mostly zero, + // so we only set a quarter of mask1's pixels. + if a >= 0xc0 { + mask1.SetAlpha(x, y, image.AlphaColor{a}) + } + } + } + mask = mask1 + default: + panic("unreachable") + } + + b.StartTimer() + for i := 0; i < b.N; i++ { + // Scatter the destination rectangle to draw into. + x := 3 * i % (dstw - srcw) + y := 7 * i % (dsth - srch) + + DrawMask(dst, dst.Bounds().Add(image.Point{x, y}), src, image.ZP, mask, image.ZP, op) + } +} + +// The BenchmarkFoo functions exercise a drawFoo fast-path function in draw.go. + +func BenchmarkFillOver(b *testing.B) { + bench(b, image.RGBAColorModel, nil, nil, Over) +} + +func BenchmarkFillSrc(b *testing.B) { + bench(b, image.RGBAColorModel, nil, nil, Src) +} + +func BenchmarkCopyOver(b *testing.B) { + bench(b, image.RGBAColorModel, image.RGBAColorModel, nil, Over) +} + +func BenchmarkCopySrc(b *testing.B) { + bench(b, image.RGBAColorModel, image.RGBAColorModel, nil, Src) +} + +func BenchmarkNRGBAOver(b *testing.B) { + bench(b, image.RGBAColorModel, image.NRGBAColorModel, nil, Over) +} + +func BenchmarkNRGBASrc(b *testing.B) { + bench(b, image.RGBAColorModel, image.NRGBAColorModel, nil, Src) +} + +func BenchmarkYCbCr(b *testing.B) { + bench(b, image.RGBAColorModel, ycbcr.YCbCrColorModel, nil, Over) +} + +func BenchmarkGlyphOver(b *testing.B) { + bench(b, image.RGBAColorModel, nil, image.AlphaColorModel, Over) +} + +func BenchmarkRGBA(b *testing.B) { + bench(b, image.RGBAColorModel, image.RGBA64ColorModel, nil, Src) +} + +// The BenchmarkGenericFoo functions exercise the generic, slow-path code. + +func BenchmarkGenericOver(b *testing.B) { + bench(b, image.RGBA64ColorModel, image.RGBA64ColorModel, nil, Over) +} + +func BenchmarkGenericMaskOver(b *testing.B) { + bench(b, image.RGBA64ColorModel, image.RGBA64ColorModel, image.AlphaColorModel, Over) +} + +func BenchmarkGenericSrc(b *testing.B) { + bench(b, image.RGBA64ColorModel, image.RGBA64ColorModel, nil, Src) +} + +func BenchmarkGenericMaskSrc(b *testing.B) { + bench(b, image.RGBA64ColorModel, image.RGBA64ColorModel, image.AlphaColorModel, Src) +} diff --git a/libgo/go/image/draw/clip_test.go b/libgo/go/image/draw/clip_test.go new file mode 100644 index 0000000..db40d82 --- /dev/null +++ b/libgo/go/image/draw/clip_test.go @@ -0,0 +1,193 @@ +// Copyright 2011 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 draw + +import ( + "image" + "testing" +) + +type clipTest struct { + desc string + r, dr, sr, mr image.Rectangle + sp, mp image.Point + nilMask bool + r0 image.Rectangle + sp0, mp0 image.Point +} + +var clipTests = []clipTest{ + // The following tests all have a nil mask. + { + "basic", + image.Rect(0, 0, 100, 100), + image.Rect(0, 0, 100, 100), + image.Rect(0, 0, 100, 100), + image.ZR, + image.ZP, + image.ZP, + true, + image.Rect(0, 0, 100, 100), + image.ZP, + image.ZP, + }, + { + "clip dr", + image.Rect(0, 0, 100, 100), + image.Rect(40, 40, 60, 60), + image.Rect(0, 0, 100, 100), + image.ZR, + image.ZP, + image.ZP, + true, + image.Rect(40, 40, 60, 60), + image.Pt(40, 40), + image.ZP, + }, + { + "clip sr", + image.Rect(0, 0, 100, 100), + image.Rect(0, 0, 100, 100), + image.Rect(20, 20, 80, 80), + image.ZR, + image.ZP, + image.ZP, + true, + image.Rect(20, 20, 80, 80), + image.Pt(20, 20), + image.ZP, + }, + { + "clip dr and sr", + image.Rect(0, 0, 100, 100), + image.Rect(0, 0, 50, 100), + image.Rect(20, 20, 80, 80), + image.ZR, + image.ZP, + image.ZP, + true, + image.Rect(20, 20, 50, 80), + image.Pt(20, 20), + image.ZP, + }, + { + "clip dr and sr, sp outside sr (top-left)", + image.Rect(0, 0, 100, 100), + image.Rect(0, 0, 50, 100), + image.Rect(20, 20, 80, 80), + image.ZR, + image.Pt(15, 8), + image.ZP, + true, + image.Rect(5, 12, 50, 72), + image.Pt(20, 20), + image.ZP, + }, + { + "clip dr and sr, sp outside sr (middle-left)", + image.Rect(0, 0, 100, 100), + image.Rect(0, 0, 50, 100), + image.Rect(20, 20, 80, 80), + image.ZR, + image.Pt(15, 66), + image.ZP, + true, + image.Rect(5, 0, 50, 14), + image.Pt(20, 66), + image.ZP, + }, + { + "clip dr and sr, sp outside sr (bottom-left)", + image.Rect(0, 0, 100, 100), + image.Rect(0, 0, 50, 100), + image.Rect(20, 20, 80, 80), + image.ZR, + image.Pt(15, 91), + image.ZP, + true, + image.ZR, + image.Pt(15, 91), + image.ZP, + }, + { + "clip dr and sr, sp inside sr", + image.Rect(0, 0, 100, 100), + image.Rect(0, 0, 50, 100), + image.Rect(20, 20, 80, 80), + image.ZR, + image.Pt(44, 33), + image.ZP, + true, + image.Rect(0, 0, 36, 47), + image.Pt(44, 33), + image.ZP, + }, + + // The following tests all have a non-nil mask. + { + "basic mask", + image.Rect(0, 0, 80, 80), + image.Rect(20, 0, 100, 80), + image.Rect(0, 0, 50, 49), + image.Rect(0, 0, 46, 47), + image.ZP, + image.ZP, + false, + image.Rect(20, 0, 46, 47), + image.Pt(20, 0), + image.Pt(20, 0), + }, + // TODO(nigeltao): write more tests. +} + +func TestClip(t *testing.T) { + dst0 := image.NewRGBA(100, 100) + src0 := image.NewRGBA(100, 100) + mask0 := image.NewRGBA(100, 100) + for _, c := range clipTests { + dst := dst0.SubImage(c.dr).(*image.RGBA) + src := src0.SubImage(c.sr).(*image.RGBA) + var mask image.Image + if !c.nilMask { + mask = mask0.SubImage(c.mr) + } + r, sp, mp := c.r, c.sp, c.mp + clip(dst, &r, src, &sp, mask, &mp) + + // Check that the actual results equal the expected results. + if !c.r0.Eq(r) { + t.Errorf("%s: clip rectangle want %v got %v", c.desc, c.r0, r) + continue + } + if !c.sp0.Eq(sp) { + t.Errorf("%s: sp want %v got %v", c.desc, c.sp0, sp) + continue + } + if !c.nilMask { + if !c.mp0.Eq(mp) { + t.Errorf("%s: mp want %v got %v", c.desc, c.mp0, mp) + continue + } + } + + // Check that the clipped rectangle is contained by the dst / src / mask + // rectangles, in their respective co-ordinate spaces. + if !r.In(c.dr) { + t.Errorf("%s: c.dr %v does not contain r %v", c.desc, c.dr, r) + } + // sr is r translated into src's co-ordinate space. + sr := r.Add(c.sp.Sub(c.dr.Min)) + if !sr.In(c.sr) { + t.Errorf("%s: c.sr %v does not contain sr %v", c.desc, c.sr, sr) + } + if !c.nilMask { + // mr is r translated into mask's co-ordinate space. + mr := r.Add(c.mp.Sub(c.dr.Min)) + if !mr.In(c.mr) { + t.Errorf("%s: c.mr %v does not contain mr %v", c.desc, c.mr, mr) + } + } + } +} diff --git a/libgo/go/image/draw/draw.go b/libgo/go/image/draw/draw.go new file mode 100644 index 0000000..a748ff8 --- /dev/null +++ b/libgo/go/image/draw/draw.go @@ -0,0 +1,493 @@ +// Copyright 2009 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 draw provides image composition functions +// in the style of the Plan 9 graphics library +// (see http://plan9.bell-labs.com/magic/man2html/2/draw) +// and the X Render extension. +package draw + +import ( + "image" + "image/ycbcr" +) + +// m is the maximum color value returned by image.Color.RGBA. +const m = 1<<16 - 1 + +// Op is a Porter-Duff compositing operator. +type Op int + +const ( + // Over specifies ``(src in mask) over dst''. + Over Op = iota + // Src specifies ``src in mask''. + Src +) + +var zeroColor image.Color = image.AlphaColor{0} + +// A draw.Image is an image.Image with a Set method to change a single pixel. +type Image interface { + image.Image + Set(x, y int, c image.Color) +} + +// Draw calls DrawMask with a nil mask. +func Draw(dst Image, r image.Rectangle, src image.Image, sp image.Point, op Op) { + DrawMask(dst, r, src, sp, nil, image.ZP, op) +} + +// clip clips r against each image's bounds (after translating into the +// destination image's co-ordinate space) and shifts the points sp and mp by +// the same amount as the change in r.Min. +func clip(dst Image, r *image.Rectangle, src image.Image, sp *image.Point, mask image.Image, mp *image.Point) { + orig := r.Min + *r = r.Intersect(dst.Bounds()) + *r = r.Intersect(src.Bounds().Add(orig.Sub(*sp))) + if mask != nil { + *r = r.Intersect(mask.Bounds().Add(orig.Sub(*mp))) + } + dx := r.Min.X - orig.X + dy := r.Min.Y - orig.Y + if dx == 0 && dy == 0 { + return + } + (*sp).X += dx + (*sp).Y += dy + (*mp).X += dx + (*mp).Y += dy +} + +// DrawMask aligns r.Min in dst with sp in src and mp in mask and then replaces the rectangle r +// in dst with the result of a Porter-Duff composition. A nil mask is treated as opaque. +func DrawMask(dst Image, r image.Rectangle, src image.Image, sp image.Point, mask image.Image, mp image.Point, op Op) { + clip(dst, &r, src, &sp, mask, &mp) + if r.Empty() { + return + } + + // Fast paths for special cases. If none of them apply, then we fall back to a general but slow implementation. + if dst0, ok := dst.(*image.RGBA); ok { + if op == Over { + if mask == nil { + switch src0 := src.(type) { + case *image.ColorImage: + drawFillOver(dst0, r, src0) + return + case *image.RGBA: + drawCopyOver(dst0, r, src0, sp) + return + case *image.NRGBA: + drawNRGBAOver(dst0, r, src0, sp) + return + case *ycbcr.YCbCr: + drawYCbCr(dst0, r, src0, sp) + return + } + } else if mask0, ok := mask.(*image.Alpha); ok { + switch src0 := src.(type) { + case *image.ColorImage: + drawGlyphOver(dst0, r, src0, mask0, mp) + return + } + } + } else { + if mask == nil { + switch src0 := src.(type) { + case *image.ColorImage: + drawFillSrc(dst0, r, src0) + return + case *image.RGBA: + drawCopySrc(dst0, r, src0, sp) + return + case *image.NRGBA: + drawNRGBASrc(dst0, r, src0, sp) + return + case *ycbcr.YCbCr: + drawYCbCr(dst0, r, src0, sp) + return + } + } + } + drawRGBA(dst0, r, src, sp, mask, mp, op) + return + } + + x0, x1, dx := r.Min.X, r.Max.X, 1 + y0, y1, dy := r.Min.Y, r.Max.Y, 1 + if image.Image(dst) == src && r.Overlaps(r.Add(sp.Sub(r.Min))) { + // Rectangles overlap: process backward? + if sp.Y < r.Min.Y || sp.Y == r.Min.Y && sp.X < r.Min.X { + x0, x1, dx = x1-1, x0-1, -1 + y0, y1, dy = y1-1, y0-1, -1 + } + } + + var out *image.RGBA64Color + sy := sp.Y + y0 - r.Min.Y + my := mp.Y + y0 - r.Min.Y + for y := y0; y != y1; y, sy, my = y+dy, sy+dy, my+dy { + sx := sp.X + x0 - r.Min.X + mx := mp.X + x0 - r.Min.X + for x := x0; x != x1; x, sx, mx = x+dx, sx+dx, mx+dx { + ma := uint32(m) + if mask != nil { + _, _, _, ma = mask.At(mx, my).RGBA() + } + switch { + case ma == 0: + if op == Over { + // No-op. + } else { + dst.Set(x, y, zeroColor) + } + case ma == m && op == Src: + dst.Set(x, y, src.At(sx, sy)) + default: + sr, sg, sb, sa := src.At(sx, sy).RGBA() + if out == nil { + out = new(image.RGBA64Color) + } + if op == Over { + dr, dg, db, da := dst.At(x, y).RGBA() + a := m - (sa * ma / m) + out.R = uint16((dr*a + sr*ma) / m) + out.G = uint16((dg*a + sg*ma) / m) + out.B = uint16((db*a + sb*ma) / m) + out.A = uint16((da*a + sa*ma) / m) + } else { + out.R = uint16(sr * ma / m) + out.G = uint16(sg * ma / m) + out.B = uint16(sb * ma / m) + out.A = uint16(sa * ma / m) + } + dst.Set(x, y, out) + } + } + } +} + +func drawFillOver(dst *image.RGBA, r image.Rectangle, src *image.ColorImage) { + sr, sg, sb, sa := src.RGBA() + // The 0x101 is here for the same reason as in drawRGBA. + a := (m - sa) * 0x101 + i0 := (r.Min.Y-dst.Rect.Min.Y)*dst.Stride + (r.Min.X-dst.Rect.Min.X)*4 + i1 := i0 + r.Dx()*4 + for y := r.Min.Y; y != r.Max.Y; y++ { + for i := i0; i < i1; i += 4 { + dr := uint32(dst.Pix[i+0]) + dg := uint32(dst.Pix[i+1]) + db := uint32(dst.Pix[i+2]) + da := uint32(dst.Pix[i+3]) + + dst.Pix[i+0] = uint8((dr*a/m + sr) >> 8) + dst.Pix[i+1] = uint8((dg*a/m + sg) >> 8) + dst.Pix[i+2] = uint8((db*a/m + sb) >> 8) + dst.Pix[i+3] = uint8((da*a/m + sa) >> 8) + } + i0 += dst.Stride + i1 += dst.Stride + } +} + +func drawFillSrc(dst *image.RGBA, r image.Rectangle, src *image.ColorImage) { + sr, sg, sb, sa := src.RGBA() + // The built-in copy function is faster than a straightforward for loop to fill the destination with + // the color, but copy requires a slice source. We therefore use a for loop to fill the first row, and + // then use the first row as the slice source for the remaining rows. + i0 := (r.Min.Y-dst.Rect.Min.Y)*dst.Stride + (r.Min.X-dst.Rect.Min.X)*4 + i1 := i0 + r.Dx()*4 + for i := i0; i < i1; i += 4 { + dst.Pix[i+0] = uint8(sr >> 8) + dst.Pix[i+1] = uint8(sg >> 8) + dst.Pix[i+2] = uint8(sb >> 8) + dst.Pix[i+3] = uint8(sa >> 8) + } + firstRow := dst.Pix[i0:i1] + for y := r.Min.Y + 1; y < r.Max.Y; y++ { + i0 += dst.Stride + i1 += dst.Stride + copy(dst.Pix[i0:i1], firstRow) + } +} + +func drawCopyOver(dst *image.RGBA, r image.Rectangle, src *image.RGBA, sp image.Point) { + dx, dy := r.Dx(), r.Dy() + d0 := (r.Min.Y-dst.Rect.Min.Y)*dst.Stride + (r.Min.X-dst.Rect.Min.X)*4 + s0 := (sp.Y-src.Rect.Min.Y)*src.Stride + (sp.X-src.Rect.Min.X)*4 + var ( + ddelta, sdelta int + i0, i1, idelta int + ) + if r.Min.Y < sp.Y || r.Min.Y == sp.Y && r.Min.X <= sp.X { + ddelta = dst.Stride + sdelta = src.Stride + i0, i1, idelta = 0, dx*4, +4 + } else { + // If the source start point is higher than the destination start point, or equal height but to the left, + // then we compose the rows in right-to-left, bottom-up order instead of left-to-right, top-down. + d0 += (dy - 1) * dst.Stride + s0 += (dy - 1) * src.Stride + ddelta = -dst.Stride + sdelta = -src.Stride + i0, i1, idelta = (dx-1)*4, -4, -4 + } + for ; dy > 0; dy-- { + dpix := dst.Pix[d0:] + spix := src.Pix[s0:] + for i := i0; i != i1; i += idelta { + sr := uint32(spix[i+0]) * 0x101 + sg := uint32(spix[i+1]) * 0x101 + sb := uint32(spix[i+2]) * 0x101 + sa := uint32(spix[i+3]) * 0x101 + + dr := uint32(dpix[i+0]) + dg := uint32(dpix[i+1]) + db := uint32(dpix[i+2]) + da := uint32(dpix[i+3]) + + // The 0x101 is here for the same reason as in drawRGBA. + a := (m - sa) * 0x101 + + dpix[i+0] = uint8((dr*a/m + sr) >> 8) + dpix[i+1] = uint8((dg*a/m + sg) >> 8) + dpix[i+2] = uint8((db*a/m + sb) >> 8) + dpix[i+3] = uint8((da*a/m + sa) >> 8) + } + d0 += ddelta + s0 += sdelta + } +} + +func drawCopySrc(dst *image.RGBA, r image.Rectangle, src *image.RGBA, sp image.Point) { + n, dy := 4*r.Dx(), r.Dy() + d0 := (r.Min.Y-dst.Rect.Min.Y)*dst.Stride + (r.Min.X-dst.Rect.Min.X)*4 + s0 := (sp.Y-src.Rect.Min.Y)*src.Stride + (sp.X-src.Rect.Min.X)*4 + var ddelta, sdelta int + if r.Min.Y <= sp.Y { + ddelta = dst.Stride + sdelta = src.Stride + } else { + // If the source start point is higher than the destination start point, then we compose the rows + // in bottom-up order instead of top-down. Unlike the drawCopyOver function, we don't have to + // check the x co-ordinates because the built-in copy function can handle overlapping slices. + d0 += (dy - 1) * dst.Stride + s0 += (dy - 1) * src.Stride + ddelta = -dst.Stride + sdelta = -src.Stride + } + for ; dy > 0; dy-- { + copy(dst.Pix[d0:d0+n], src.Pix[s0:s0+n]) + d0 += ddelta + s0 += sdelta + } +} + +func drawNRGBAOver(dst *image.RGBA, r image.Rectangle, src *image.NRGBA, sp image.Point) { + i0 := (r.Min.X - dst.Rect.Min.X) * 4 + i1 := (r.Max.X - dst.Rect.Min.X) * 4 + si0 := (sp.X - src.Rect.Min.X) * 4 + yMax := r.Max.Y - dst.Rect.Min.Y + + y := r.Min.Y - dst.Rect.Min.Y + sy := sp.Y - src.Rect.Min.Y + for ; y != yMax; y, sy = y+1, sy+1 { + dpix := dst.Pix[y*dst.Stride:] + spix := src.Pix[sy*src.Stride:] + + for i, si := i0, si0; i < i1; i, si = i+4, si+4 { + // Convert from non-premultiplied color to pre-multiplied color. + sa := uint32(spix[si+3]) * 0x101 + sr := uint32(spix[si+0]) * sa / 0xff + sg := uint32(spix[si+1]) * sa / 0xff + sb := uint32(spix[si+2]) * sa / 0xff + + dr := uint32(dpix[i+0]) + dg := uint32(dpix[i+1]) + db := uint32(dpix[i+2]) + da := uint32(dpix[i+3]) + + // The 0x101 is here for the same reason as in drawRGBA. + a := (m - sa) * 0x101 + + dpix[i+0] = uint8((dr*a/m + sr) >> 8) + dpix[i+1] = uint8((dg*a/m + sg) >> 8) + dpix[i+2] = uint8((db*a/m + sb) >> 8) + dpix[i+3] = uint8((da*a/m + sa) >> 8) + } + } +} + +func drawNRGBASrc(dst *image.RGBA, r image.Rectangle, src *image.NRGBA, sp image.Point) { + i0 := (r.Min.X - dst.Rect.Min.X) * 4 + i1 := (r.Max.X - dst.Rect.Min.X) * 4 + si0 := (sp.X - src.Rect.Min.X) * 4 + yMax := r.Max.Y - dst.Rect.Min.Y + + y := r.Min.Y - dst.Rect.Min.Y + sy := sp.Y - src.Rect.Min.Y + for ; y != yMax; y, sy = y+1, sy+1 { + dpix := dst.Pix[y*dst.Stride:] + spix := src.Pix[sy*src.Stride:] + + for i, si := i0, si0; i < i1; i, si = i+4, si+4 { + // Convert from non-premultiplied color to pre-multiplied color. + sa := uint32(spix[si+3]) * 0x101 + sr := uint32(spix[si+0]) * sa / 0xff + sg := uint32(spix[si+1]) * sa / 0xff + sb := uint32(spix[si+2]) * sa / 0xff + + dpix[i+0] = uint8(sr >> 8) + dpix[i+1] = uint8(sg >> 8) + dpix[i+2] = uint8(sb >> 8) + dpix[i+3] = uint8(sa >> 8) + } + } +} + +func drawYCbCr(dst *image.RGBA, r image.Rectangle, src *ycbcr.YCbCr, sp image.Point) { + // A YCbCr image is always fully opaque, and so if the mask is implicitly nil + // (i.e. fully opaque) then the op is effectively always Src. + var ( + yy, cb, cr uint8 + ) + x0 := (r.Min.X - dst.Rect.Min.X) * 4 + x1 := (r.Max.X - dst.Rect.Min.X) * 4 + y0 := r.Min.Y - dst.Rect.Min.Y + y1 := r.Max.Y - dst.Rect.Min.Y + switch src.SubsampleRatio { + case ycbcr.SubsampleRatio422: + for y, sy := y0, sp.Y; y != y1; y, sy = y+1, sy+1 { + dpix := dst.Pix[y*dst.Stride:] + for x, sx := x0, sp.X; x != x1; x, sx = x+4, sx+1 { + i := sx / 2 + yy = src.Y[sy*src.YStride+sx] + cb = src.Cb[sy*src.CStride+i] + cr = src.Cr[sy*src.CStride+i] + rr, gg, bb := ycbcr.YCbCrToRGB(yy, cb, cr) + dpix[x+0] = rr + dpix[x+1] = gg + dpix[x+2] = bb + dpix[x+3] = 255 + } + } + case ycbcr.SubsampleRatio420: + for y, sy := y0, sp.Y; y != y1; y, sy = y+1, sy+1 { + dpix := dst.Pix[y*dst.Stride:] + for x, sx := x0, sp.X; x != x1; x, sx = x+4, sx+1 { + i, j := sx/2, sy/2 + yy = src.Y[sy*src.YStride+sx] + cb = src.Cb[j*src.CStride+i] + cr = src.Cr[j*src.CStride+i] + rr, gg, bb := ycbcr.YCbCrToRGB(yy, cb, cr) + dpix[x+0] = rr + dpix[x+1] = gg + dpix[x+2] = bb + dpix[x+3] = 255 + } + } + default: + // Default to 4:4:4 subsampling. + for y, sy := y0, sp.Y; y != y1; y, sy = y+1, sy+1 { + dpix := dst.Pix[y*dst.Stride:] + for x, sx := x0, sp.X; x != x1; x, sx = x+4, sx+1 { + yy = src.Y[sy*src.YStride+sx] + cb = src.Cb[sy*src.CStride+sx] + cr = src.Cr[sy*src.CStride+sx] + rr, gg, bb := ycbcr.YCbCrToRGB(yy, cb, cr) + dpix[x+0] = rr + dpix[x+1] = gg + dpix[x+2] = bb + dpix[x+3] = 255 + } + } + } +} + +func drawGlyphOver(dst *image.RGBA, r image.Rectangle, src *image.ColorImage, mask *image.Alpha, mp image.Point) { + i0 := (r.Min.Y-dst.Rect.Min.Y)*dst.Stride + (r.Min.X-dst.Rect.Min.X)*4 + i1 := i0 + r.Dx()*4 + mi0 := (mp.Y-mask.Rect.Min.Y)*mask.Stride + mp.X - mask.Rect.Min.X + sr, sg, sb, sa := src.RGBA() + for y, my := r.Min.Y, mp.Y; y != r.Max.Y; y, my = y+1, my+1 { + for i, mi := i0, mi0; i < i1; i, mi = i+4, mi+1 { + ma := uint32(mask.Pix[mi]) + if ma == 0 { + continue + } + ma |= ma << 8 + + dr := uint32(dst.Pix[i+0]) + dg := uint32(dst.Pix[i+1]) + db := uint32(dst.Pix[i+2]) + da := uint32(dst.Pix[i+3]) + + // The 0x101 is here for the same reason as in drawRGBA. + a := (m - (sa * ma / m)) * 0x101 + + dst.Pix[i+0] = uint8((dr*a + sr*ma) / m >> 8) + dst.Pix[i+1] = uint8((dg*a + sg*ma) / m >> 8) + dst.Pix[i+2] = uint8((db*a + sb*ma) / m >> 8) + dst.Pix[i+3] = uint8((da*a + sa*ma) / m >> 8) + } + i0 += dst.Stride + i1 += dst.Stride + mi0 += mask.Stride + } +} + +func drawRGBA(dst *image.RGBA, r image.Rectangle, src image.Image, sp image.Point, mask image.Image, mp image.Point, op Op) { + x0, x1, dx := r.Min.X, r.Max.X, 1 + y0, y1, dy := r.Min.Y, r.Max.Y, 1 + if image.Image(dst) == src && r.Overlaps(r.Add(sp.Sub(r.Min))) { + if sp.Y < r.Min.Y || sp.Y == r.Min.Y && sp.X < r.Min.X { + x0, x1, dx = x1-1, x0-1, -1 + y0, y1, dy = y1-1, y0-1, -1 + } + } + + sy := sp.Y + y0 - r.Min.Y + my := mp.Y + y0 - r.Min.Y + sx0 := sp.X + x0 - r.Min.X + mx0 := mp.X + x0 - r.Min.X + sx1 := sx0 + (x1 - x0) + i0 := (y0-dst.Rect.Min.Y)*dst.Stride + (x0-dst.Rect.Min.X)*4 + di := dx * 4 + for y := y0; y != y1; y, sy, my = y+dy, sy+dy, my+dy { + for i, sx, mx := i0, sx0, mx0; sx != sx1; i, sx, mx = i+di, sx+dx, mx+dx { + ma := uint32(m) + if mask != nil { + _, _, _, ma = mask.At(mx, my).RGBA() + } + sr, sg, sb, sa := src.At(sx, sy).RGBA() + if op == Over { + dr := uint32(dst.Pix[i+0]) + dg := uint32(dst.Pix[i+1]) + db := uint32(dst.Pix[i+2]) + da := uint32(dst.Pix[i+3]) + + // dr, dg, db and da are all 8-bit color at the moment, ranging in [0,255]. + // We work in 16-bit color, and so would normally do: + // dr |= dr << 8 + // and similarly for dg, db and da, but instead we multiply a + // (which is a 16-bit color, ranging in [0,65535]) by 0x101. + // This yields the same result, but is fewer arithmetic operations. + a := (m - (sa * ma / m)) * 0x101 + + dst.Pix[i+0] = uint8((dr*a + sr*ma) / m >> 8) + dst.Pix[i+1] = uint8((dg*a + sg*ma) / m >> 8) + dst.Pix[i+2] = uint8((db*a + sb*ma) / m >> 8) + dst.Pix[i+3] = uint8((da*a + sa*ma) / m >> 8) + + } else { + dst.Pix[i+0] = uint8(sr * ma / m >> 8) + dst.Pix[i+1] = uint8(sg * ma / m >> 8) + dst.Pix[i+2] = uint8(sb * ma / m >> 8) + dst.Pix[i+3] = uint8(sa * ma / m >> 8) + } + } + i0 += dy * dst.Stride + } +} diff --git a/libgo/go/image/draw/draw_test.go b/libgo/go/image/draw/draw_test.go new file mode 100644 index 0000000..55435cc --- /dev/null +++ b/libgo/go/image/draw/draw_test.go @@ -0,0 +1,354 @@ +// Copyright 2010 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 draw + +import ( + "image" + "image/ycbcr" + "testing" +) + +func eq(c0, c1 image.Color) bool { + r0, g0, b0, a0 := c0.RGBA() + r1, g1, b1, a1 := c1.RGBA() + return r0 == r1 && g0 == g1 && b0 == b1 && a0 == a1 +} + +func fillBlue(alpha int) image.Image { + return image.NewColorImage(image.RGBAColor{0, 0, uint8(alpha), uint8(alpha)}) +} + +func fillAlpha(alpha int) image.Image { + return image.NewColorImage(image.AlphaColor{uint8(alpha)}) +} + +func vgradGreen(alpha int) image.Image { + m := image.NewRGBA(16, 16) + for y := 0; y < 16; y++ { + for x := 0; x < 16; x++ { + m.Set(x, y, image.RGBAColor{0, uint8(y * alpha / 15), 0, uint8(alpha)}) + } + } + return m +} + +func vgradAlpha(alpha int) image.Image { + m := image.NewAlpha(16, 16) + for y := 0; y < 16; y++ { + for x := 0; x < 16; x++ { + m.Set(x, y, image.AlphaColor{uint8(y * alpha / 15)}) + } + } + return m +} + +func vgradGreenNRGBA(alpha int) image.Image { + m := image.NewNRGBA(16, 16) + for y := 0; y < 16; y++ { + for x := 0; x < 16; x++ { + m.Set(x, y, image.RGBAColor{0, uint8(y * 0x11), 0, uint8(alpha)}) + } + } + return m +} + +func vgradCr() image.Image { + m := &ycbcr.YCbCr{ + Y: make([]byte, 16*16), + Cb: make([]byte, 16*16), + Cr: make([]byte, 16*16), + YStride: 16, + CStride: 16, + SubsampleRatio: ycbcr.SubsampleRatio444, + Rect: image.Rect(0, 0, 16, 16), + } + for y := 0; y < 16; y++ { + for x := 0; x < 16; x++ { + m.Cr[y*m.CStride+x] = uint8(y * 0x11) + } + } + return m +} + +func hgradRed(alpha int) Image { + m := image.NewRGBA(16, 16) + for y := 0; y < 16; y++ { + for x := 0; x < 16; x++ { + m.Set(x, y, image.RGBAColor{uint8(x * alpha / 15), 0, 0, uint8(alpha)}) + } + } + return m +} + +func gradYellow(alpha int) Image { + m := image.NewRGBA(16, 16) + for y := 0; y < 16; y++ { + for x := 0; x < 16; x++ { + m.Set(x, y, image.RGBAColor{uint8(x * alpha / 15), uint8(y * alpha / 15), 0, uint8(alpha)}) + } + } + return m +} + +type drawTest struct { + desc string + src image.Image + mask image.Image + op Op + expected image.Color +} + +var drawTests = []drawTest{ + // Uniform mask (0% opaque). + {"nop", vgradGreen(255), fillAlpha(0), Over, image.RGBAColor{136, 0, 0, 255}}, + {"clear", vgradGreen(255), fillAlpha(0), Src, image.RGBAColor{0, 0, 0, 0}}, + // Uniform mask (100%, 75%, nil) and uniform source. + // At (x, y) == (8, 8): + // The destination pixel is {136, 0, 0, 255}. + // The source pixel is {0, 0, 90, 90}. + {"fill", fillBlue(90), fillAlpha(255), Over, image.RGBAColor{88, 0, 90, 255}}, + {"fillSrc", fillBlue(90), fillAlpha(255), Src, image.RGBAColor{0, 0, 90, 90}}, + {"fillAlpha", fillBlue(90), fillAlpha(192), Over, image.RGBAColor{100, 0, 68, 255}}, + {"fillAlphaSrc", fillBlue(90), fillAlpha(192), Src, image.RGBAColor{0, 0, 68, 68}}, + {"fillNil", fillBlue(90), nil, Over, image.RGBAColor{88, 0, 90, 255}}, + {"fillNilSrc", fillBlue(90), nil, Src, image.RGBAColor{0, 0, 90, 90}}, + // Uniform mask (100%, 75%, nil) and variable source. + // At (x, y) == (8, 8): + // The destination pixel is {136, 0, 0, 255}. + // The source pixel is {0, 48, 0, 90}. + {"copy", vgradGreen(90), fillAlpha(255), Over, image.RGBAColor{88, 48, 0, 255}}, + {"copySrc", vgradGreen(90), fillAlpha(255), Src, image.RGBAColor{0, 48, 0, 90}}, + {"copyAlpha", vgradGreen(90), fillAlpha(192), Over, image.RGBAColor{100, 36, 0, 255}}, + {"copyAlphaSrc", vgradGreen(90), fillAlpha(192), Src, image.RGBAColor{0, 36, 0, 68}}, + {"copyNil", vgradGreen(90), nil, Over, image.RGBAColor{88, 48, 0, 255}}, + {"copyNilSrc", vgradGreen(90), nil, Src, image.RGBAColor{0, 48, 0, 90}}, + // Uniform mask (100%, 75%, nil) and variable NRGBA source. + // At (x, y) == (8, 8): + // The destination pixel is {136, 0, 0, 255}. + // The source pixel is {0, 136, 0, 90} in NRGBA-space, which is {0, 48, 0, 90} in RGBA-space. + // The result pixel is different than in the "copy*" test cases because of rounding errors. + {"nrgba", vgradGreenNRGBA(90), fillAlpha(255), Over, image.RGBAColor{88, 46, 0, 255}}, + {"nrgbaSrc", vgradGreenNRGBA(90), fillAlpha(255), Src, image.RGBAColor{0, 46, 0, 90}}, + {"nrgbaAlpha", vgradGreenNRGBA(90), fillAlpha(192), Over, image.RGBAColor{100, 34, 0, 255}}, + {"nrgbaAlphaSrc", vgradGreenNRGBA(90), fillAlpha(192), Src, image.RGBAColor{0, 34, 0, 68}}, + {"nrgbaNil", vgradGreenNRGBA(90), nil, Over, image.RGBAColor{88, 46, 0, 255}}, + {"nrgbaNilSrc", vgradGreenNRGBA(90), nil, Src, image.RGBAColor{0, 46, 0, 90}}, + // Uniform mask (100%, 75%, nil) and variable YCbCr source. + // At (x, y) == (8, 8): + // The destination pixel is {136, 0, 0, 255}. + // The source pixel is {0, 0, 136} in YCbCr-space, which is {11, 38, 0, 255} in RGB-space. + {"ycbcr", vgradCr(), fillAlpha(255), Over, image.RGBAColor{11, 38, 0, 255}}, + {"ycbcrSrc", vgradCr(), fillAlpha(255), Src, image.RGBAColor{11, 38, 0, 255}}, + {"ycbcrAlpha", vgradCr(), fillAlpha(192), Over, image.RGBAColor{42, 28, 0, 255}}, + {"ycbcrAlphaSrc", vgradCr(), fillAlpha(192), Src, image.RGBAColor{8, 28, 0, 192}}, + {"ycbcrNil", vgradCr(), nil, Over, image.RGBAColor{11, 38, 0, 255}}, + {"ycbcrNilSrc", vgradCr(), nil, Src, image.RGBAColor{11, 38, 0, 255}}, + // Variable mask and variable source. + // At (x, y) == (8, 8): + // The destination pixel is {136, 0, 0, 255}. + // The source pixel is {0, 0, 255, 255}. + // The mask pixel's alpha is 102, or 40%. + {"generic", fillBlue(255), vgradAlpha(192), Over, image.RGBAColor{81, 0, 102, 255}}, + {"genericSrc", fillBlue(255), vgradAlpha(192), Src, image.RGBAColor{0, 0, 102, 102}}, +} + +func makeGolden(dst image.Image, r image.Rectangle, src image.Image, sp image.Point, mask image.Image, mp image.Point, op Op) image.Image { + // Since golden is a newly allocated image, we don't have to check if the + // input source and mask images and the output golden image overlap. + b := dst.Bounds() + sb := src.Bounds() + mb := image.Rect(-1e9, -1e9, 1e9, 1e9) + if mask != nil { + mb = mask.Bounds() + } + golden := image.NewRGBA(b.Max.X, b.Max.Y) + for y := r.Min.Y; y < r.Max.Y; y++ { + sy := y + sp.Y - r.Min.Y + my := y + mp.Y - r.Min.Y + for x := r.Min.X; x < r.Max.X; x++ { + if !(image.Point{x, y}.In(b)) { + continue + } + sx := x + sp.X - r.Min.X + if !(image.Point{sx, sy}.In(sb)) { + continue + } + mx := x + mp.X - r.Min.X + if !(image.Point{mx, my}.In(mb)) { + continue + } + + const M = 1<<16 - 1 + var dr, dg, db, da uint32 + if op == Over { + dr, dg, db, da = dst.At(x, y).RGBA() + } + sr, sg, sb, sa := src.At(sx, sy).RGBA() + ma := uint32(M) + if mask != nil { + _, _, _, ma = mask.At(mx, my).RGBA() + } + a := M - (sa * ma / M) + golden.Set(x, y, image.RGBA64Color{ + uint16((dr*a + sr*ma) / M), + uint16((dg*a + sg*ma) / M), + uint16((db*a + sb*ma) / M), + uint16((da*a + sa*ma) / M), + }) + } + } + return golden.SubImage(b) +} + +func TestDraw(t *testing.T) { + rr := []image.Rectangle{ + image.Rect(0, 0, 0, 0), + image.Rect(0, 0, 16, 16), + image.Rect(3, 5, 12, 10), + image.Rect(0, 0, 9, 9), + image.Rect(8, 8, 16, 16), + image.Rect(8, 0, 9, 16), + image.Rect(0, 8, 16, 9), + image.Rect(8, 8, 9, 9), + image.Rect(8, 8, 8, 8), + } + for _, r := range rr { + loop: + for _, test := range drawTests { + dst := hgradRed(255).(*image.RGBA).SubImage(r).(Image) + // Draw the (src, mask, op) onto a copy of dst using a slow but obviously correct implementation. + golden := makeGolden(dst, image.Rect(0, 0, 16, 16), test.src, image.ZP, test.mask, image.ZP, test.op) + b := dst.Bounds() + if !b.Eq(golden.Bounds()) { + t.Errorf("draw %v %s: bounds %v versus %v", r, test.desc, dst.Bounds(), golden.Bounds()) + continue + } + // Draw the same combination onto the actual dst using the optimized DrawMask implementation. + DrawMask(dst, image.Rect(0, 0, 16, 16), test.src, image.ZP, test.mask, image.ZP, test.op) + if image.Pt(8, 8).In(r) { + // Check that the resultant pixel at (8, 8) matches what we expect + // (the expected value can be verified by hand). + if !eq(dst.At(8, 8), test.expected) { + t.Errorf("draw %v %s: at (8, 8) %v versus %v", r, test.desc, dst.At(8, 8), test.expected) + continue + } + } + // Check that the resultant dst image matches the golden output. + for y := b.Min.Y; y < b.Max.Y; y++ { + for x := b.Min.X; x < b.Max.X; x++ { + if !eq(dst.At(x, y), golden.At(x, y)) { + t.Errorf("draw %v %s: at (%d, %d), %v versus golden %v", r, test.desc, x, y, dst.At(x, y), golden.At(x, y)) + continue loop + } + } + } + } + } +} + +func TestDrawOverlap(t *testing.T) { + for _, op := range []Op{Over, Src} { + for yoff := -2; yoff <= 2; yoff++ { + loop: + for xoff := -2; xoff <= 2; xoff++ { + m := gradYellow(127).(*image.RGBA) + dst := m.SubImage(image.Rect(5, 5, 10, 10)).(*image.RGBA) + src := m.SubImage(image.Rect(5+xoff, 5+yoff, 10+xoff, 10+yoff)).(*image.RGBA) + b := dst.Bounds() + // Draw the (src, mask, op) onto a copy of dst using a slow but obviously correct implementation. + golden := makeGolden(dst, b, src, src.Bounds().Min, nil, image.ZP, op) + if !b.Eq(golden.Bounds()) { + t.Errorf("drawOverlap xoff=%d,yoff=%d: bounds %v versus %v", xoff, yoff, dst.Bounds(), golden.Bounds()) + continue + } + // Draw the same combination onto the actual dst using the optimized DrawMask implementation. + DrawMask(dst, b, src, src.Bounds().Min, nil, image.ZP, op) + // Check that the resultant dst image matches the golden output. + for y := b.Min.Y; y < b.Max.Y; y++ { + for x := b.Min.X; x < b.Max.X; x++ { + if !eq(dst.At(x, y), golden.At(x, y)) { + t.Errorf("drawOverlap xoff=%d,yoff=%d: at (%d, %d), %v versus golden %v", xoff, yoff, x, y, dst.At(x, y), golden.At(x, y)) + continue loop + } + } + } + } + } + } +} + +// TestNonZeroSrcPt checks drawing with a non-zero src point parameter. +func TestNonZeroSrcPt(t *testing.T) { + a := image.NewRGBA(1, 1) + b := image.NewRGBA(2, 2) + b.Set(0, 0, image.RGBAColor{0, 0, 0, 5}) + b.Set(1, 0, image.RGBAColor{0, 0, 5, 5}) + b.Set(0, 1, image.RGBAColor{0, 5, 0, 5}) + b.Set(1, 1, image.RGBAColor{5, 0, 0, 5}) + Draw(a, image.Rect(0, 0, 1, 1), b, image.Pt(1, 1), Over) + if !eq(image.RGBAColor{5, 0, 0, 5}, a.At(0, 0)) { + t.Errorf("non-zero src pt: want %v got %v", image.RGBAColor{5, 0, 0, 5}, a.At(0, 0)) + } +} + +func TestFill(t *testing.T) { + rr := []image.Rectangle{ + image.Rect(0, 0, 0, 0), + image.Rect(0, 0, 40, 30), + image.Rect(10, 0, 40, 30), + image.Rect(0, 20, 40, 30), + image.Rect(10, 20, 40, 30), + image.Rect(10, 20, 15, 25), + image.Rect(10, 0, 35, 30), + image.Rect(0, 15, 40, 16), + image.Rect(24, 24, 25, 25), + image.Rect(23, 23, 26, 26), + image.Rect(22, 22, 27, 27), + image.Rect(21, 21, 28, 28), + image.Rect(20, 20, 29, 29), + } + for _, r := range rr { + m := image.NewRGBA(40, 30).SubImage(r).(*image.RGBA) + b := m.Bounds() + c := image.RGBAColor{11, 0, 0, 255} + src := &image.ColorImage{c} + check := func(desc string) { + for y := b.Min.Y; y < b.Max.Y; y++ { + for x := b.Min.X; x < b.Max.X; x++ { + if !eq(c, m.At(x, y)) { + t.Errorf("%s fill: at (%d, %d), sub-image bounds=%v: want %v got %v", desc, x, y, r, c, m.At(x, y)) + return + } + } + } + } + // Draw 1 pixel at a time. + for y := b.Min.Y; y < b.Max.Y; y++ { + for x := b.Min.X; x < b.Max.X; x++ { + DrawMask(m, image.Rect(x, y, x+1, y+1), src, image.ZP, nil, image.ZP, Src) + } + } + check("pixel") + // Draw 1 row at a time. + c = image.RGBAColor{0, 22, 0, 255} + src = &image.ColorImage{c} + for y := b.Min.Y; y < b.Max.Y; y++ { + DrawMask(m, image.Rect(b.Min.X, y, b.Max.X, y+1), src, image.ZP, nil, image.ZP, Src) + } + check("row") + // Draw 1 column at a time. + c = image.RGBAColor{0, 0, 33, 255} + src = &image.ColorImage{c} + for x := b.Min.X; x < b.Max.X; x++ { + DrawMask(m, image.Rect(x, b.Min.Y, x+1, b.Max.Y), src, image.ZP, nil, image.ZP, Src) + } + check("column") + // Draw the whole image at once. + c = image.RGBAColor{44, 55, 66, 77} + src = &image.ColorImage{c} + DrawMask(m, b, src, image.ZP, nil, image.ZP, Src) + check("whole") + } +} diff --git a/libgo/go/image/geom.go b/libgo/go/image/geom.go index ccfe9cd..667aee6 100644 --- a/libgo/go/image/geom.go +++ b/libgo/go/image/geom.go @@ -38,6 +38,12 @@ func (p Point) Div(k int) Point { return Point{p.X / k, p.Y / k} } +// In returns whether p is in r. +func (p Point) In(r Rectangle) bool { + return r.Min.X <= p.X && p.X < r.Max.X && + r.Min.Y <= p.Y && p.Y < r.Max.Y +} + // Mod returns the point q in r such that p.X-q.X is a multiple of r's width // and p.Y-q.Y is a multiple of r's height. func (p Point) Mod(r Rectangle) Point { @@ -190,10 +196,15 @@ func (r Rectangle) Overlaps(s Rectangle) bool { r.Min.Y < s.Max.Y && s.Min.Y < r.Max.Y } -// Contains returns whether r contains p. -func (r Rectangle) Contains(p Point) bool { - return p.X >= r.Min.X && p.X < r.Max.X && - p.Y >= r.Min.Y && p.Y < r.Max.Y +// In returns whether every point in r is in s. +func (r Rectangle) In(s Rectangle) bool { + if r.Empty() { + return true + } + // Note that r.Max is an exclusive bound for r, so that r.In(s) + // does not require that r.Max.In(s). + return s.Min.X <= r.Min.X && r.Max.X <= s.Max.X && + s.Min.Y <= r.Min.Y && r.Max.Y <= s.Max.Y } // Canon returns the canonical version of r. The returned rectangle has minimum diff --git a/libgo/go/image/gif/reader.go b/libgo/go/image/gif/reader.go index d37f526..e39b797 100644 --- a/libgo/go/image/gif/reader.go +++ b/libgo/go/image/gif/reader.go @@ -28,7 +28,9 @@ const ( fColorMapFollows = 1 << 7 // Image fields. - ifInterlace = 1 << 6 + ifLocalColorTable = 1 << 7 + ifInterlace = 1 << 6 + ifPixelSizeMask = 7 // Graphic control flags. gcTransparentColorSet = 1 << 0 @@ -94,28 +96,26 @@ type blockReader struct { tmp [256]byte } -func (b *blockReader) Read(p []byte) (n int, err os.Error) { +func (b *blockReader) Read(p []byte) (int, os.Error) { if len(p) == 0 { - return + return 0, nil } - if len(b.slice) > 0 { - n = copy(p, b.slice) - b.slice = b.slice[n:] - return - } - var blockLen uint8 - blockLen, err = b.r.ReadByte() - if err != nil { - return - } - if blockLen == 0 { - return 0, os.EOF - } - b.slice = b.tmp[0:blockLen] - if _, err = io.ReadFull(b.r, b.slice); err != nil { - return + if len(b.slice) == 0 { + blockLen, err := b.r.ReadByte() + if err != nil { + return 0, err + } + if blockLen == 0 { + return 0, os.EOF + } + b.slice = b.tmp[0:blockLen] + if _, err = io.ReadFull(b.r, b.slice); err != nil { + return 0, err + } } - return b.Read(p) + n := copy(p, b.slice) + b.slice = b.slice[n:] + return n, nil } // decode reads a GIF image from r and stores the result in d. @@ -141,8 +141,6 @@ func (d *decoder) decode(r io.Reader, configOnly bool) os.Error { } } - d.image = nil - Loop: for err == nil { var c byte @@ -175,11 +173,10 @@ Loop: if err != nil { return err } - if litWidth > 8 { + if litWidth < 2 || litWidth > 8 { return fmt.Errorf("gif: pixel size in decode out of range: %d", litWidth) } - // A wonderfully Go-like piece of magic. Unfortunately it's only at its - // best for 8-bit pixels. + // A wonderfully Go-like piece of magic. lzwr := lzw.NewReader(&blockReader{r: d.r}, lzw.LSB, int(litWidth)) if _, err = io.ReadFull(lzwr, m.Pix); err != nil { break @@ -191,8 +188,14 @@ Loop: return err } if c != 0 { - return os.ErrorString("gif: extra data after image") + return os.NewError("gif: extra data after image") + } + + // Undo the interlacing if necessary. + if d.imageFields&ifInterlace != 0 { + uninterlace(m) } + d.image = append(d.image, m) d.delay = append(d.delay, d.delayTime) d.delayTime = 0 // TODO: is this correct, or should we hold on to the value? @@ -237,6 +240,9 @@ func (d *decoder) readColorMap() (image.PalettedColorModel, os.Error) { return nil, fmt.Errorf("gif: can't handle %d bits per pixel", d.pixelSize) } numColors := 1 << d.pixelSize + if d.imageFields&ifLocalColorTable != 0 { + numColors = 1 << ((d.imageFields & ifPixelSizeMask) + 1) + } numValues := 3 * numColors _, err := io.ReadFull(d.r, d.tmp[0:numValues]) if err != nil { @@ -275,7 +281,7 @@ func (d *decoder) readExtension() os.Error { return fmt.Errorf("gif: unknown extension 0x%.2x", extension) } if size > 0 { - if _, err := d.r.Read(d.tmp[0:size]); err != nil { + if _, err := io.ReadFull(d.r, d.tmp[0:size]); err != nil { return err } } @@ -323,15 +329,15 @@ func (d *decoder) newImageFromDescriptor() (*image.Paletted, os.Error) { if _, err := io.ReadFull(d.r, d.tmp[0:9]); err != nil { return nil, fmt.Errorf("gif: can't read image descriptor: %s", err) } - _ = int(d.tmp[0]) + int(d.tmp[1])<<8 // TODO: honor left value - _ = int(d.tmp[2]) + int(d.tmp[3])<<8 // TODO: honor top value + left := int(d.tmp[0]) + int(d.tmp[1])<<8 + top := int(d.tmp[2]) + int(d.tmp[3])<<8 width := int(d.tmp[4]) + int(d.tmp[5])<<8 height := int(d.tmp[6]) + int(d.tmp[7])<<8 d.imageFields = d.tmp[8] - if d.imageFields&ifInterlace != 0 { - return nil, os.ErrorString("gif: can't handle interlaced images") - } - return image.NewPaletted(width, height, nil), nil + m := image.NewPaletted(width, height, nil) + // Overwrite the rectangle to take account of left and top. + m.Rect = image.Rect(left, top, left+width, top+height) + return m, nil } func (d *decoder) readBlock() (int, os.Error) { @@ -342,9 +348,39 @@ func (d *decoder) readBlock() (int, os.Error) { return io.ReadFull(d.r, d.tmp[0:n]) } +// interlaceScan defines the ordering for a pass of the interlace algorithm. +type interlaceScan struct { + skip, start int +} + +// interlacing represents the set of scans in an interlaced GIF image. +var interlacing = []interlaceScan{ + {8, 0}, // Group 1 : Every 8th. row, starting with row 0. + {8, 4}, // Group 2 : Every 8th. row, starting with row 4. + {4, 2}, // Group 3 : Every 4th. row, starting with row 2. + {2, 1}, // Group 4 : Every 2nd. row, starting with row 1. +} + +// uninterlace rearranges the pixels in m to account for interlaced input. +func uninterlace(m *image.Paletted) { + var nPix []uint8 + dx := m.Bounds().Dx() + dy := m.Bounds().Dy() + nPix = make([]uint8, dx*dy) + offset := 0 // steps through the input by sequential scan lines. + for _, pass := range interlacing { + nOffset := pass.start * dx // steps through the output as defined by pass. + for y := pass.start; y < dy; y += pass.skip { + copy(nPix[nOffset:nOffset+dx], m.Pix[offset:offset+dx]) + offset += dx + nOffset += dx * pass.skip + } + } + m.Pix = nPix +} + // Decode reads a GIF image from r and returns the first embedded // image as an image.Image. -// Limitation: The file must be 8 bits per pixel and have no interlacing. func Decode(r io.Reader) (image.Image, os.Error) { var d decoder if err := d.decode(r, false); err != nil { @@ -362,7 +398,6 @@ type GIF struct { // DecodeAll reads a GIF image from r and returns the sequential frames // and timing information. -// Limitation: The file must be 8 bits per pixel and have no interlacing. func DecodeAll(r io.Reader) (*GIF, os.Error) { var d decoder if err := d.decode(r, false); err != nil { @@ -376,15 +411,14 @@ func DecodeAll(r io.Reader) (*GIF, os.Error) { return gif, nil } -// DecodeConfig returns the color model and dimensions of a GIF image without -// decoding the entire image. +// DecodeConfig returns the global color model and dimensions of a GIF image +// without decoding the entire image. func DecodeConfig(r io.Reader) (image.Config, os.Error) { var d decoder if err := d.decode(r, true); err != nil { return image.Config{}, err } - colorMap := d.globalColorMap - return image.Config{colorMap, d.width, d.height}, nil + return image.Config{d.globalColorMap, d.width, d.height}, nil } func init() { diff --git a/libgo/go/image/image.go b/libgo/go/image/image.go index 4350acc..11def94 100644 --- a/libgo/go/image/image.go +++ b/libgo/go/image/image.go @@ -5,13 +5,13 @@ // Package image implements a basic 2-D image library. package image -// A Config consists of an image's color model and dimensions. +// Config holds an image's color model and dimensions. type Config struct { ColorModel ColorModel Width, Height int } -// An Image is a finite rectangular grid of Colors drawn from a ColorModel. +// Image is a finite rectangular grid of Colors drawn from a ColorModel. type Image interface { // ColorModel returns the Image's ColorModel. ColorModel() ColorModel @@ -24,10 +24,12 @@ type Image interface { At(x, y int) Color } -// An RGBA is an in-memory image of RGBAColor values. +// RGBA is an in-memory image of RGBAColor values. type RGBA struct { - // Pix holds the image's pixels. The pixel at (x, y) is Pix[y*Stride+x]. - Pix []RGBAColor + // Pix holds the image's pixels, in R, G, B, A order. The pixel at + // (x, y) starts at Pix[(y-Rect.Min.Y)*Stride + (x-Rect.Min.X)*4]. + Pix []uint8 + // Stride is the Pix stride (in bytes) between vertically adjacent pixels. Stride int // Rect is the image's bounds. Rect Rectangle @@ -38,24 +40,52 @@ func (p *RGBA) ColorModel() ColorModel { return RGBAColorModel } func (p *RGBA) Bounds() Rectangle { return p.Rect } func (p *RGBA) At(x, y int) Color { - if !p.Rect.Contains(Point{x, y}) { + if !(Point{x, y}.In(p.Rect)) { return RGBAColor{} } - return p.Pix[y*p.Stride+x] + i := (y-p.Rect.Min.Y)*p.Stride + (x-p.Rect.Min.X)*4 + return RGBAColor{p.Pix[i+0], p.Pix[i+1], p.Pix[i+2], p.Pix[i+3]} } func (p *RGBA) Set(x, y int, c Color) { - if !p.Rect.Contains(Point{x, y}) { + if !(Point{x, y}.In(p.Rect)) { return } - p.Pix[y*p.Stride+x] = toRGBAColor(c).(RGBAColor) + i := (y-p.Rect.Min.Y)*p.Stride + (x-p.Rect.Min.X)*4 + c1 := toRGBAColor(c).(RGBAColor) + p.Pix[i+0] = c1.R + p.Pix[i+1] = c1.G + p.Pix[i+2] = c1.B + p.Pix[i+3] = c1.A } func (p *RGBA) SetRGBA(x, y int, c RGBAColor) { - if !p.Rect.Contains(Point{x, y}) { + if !(Point{x, y}.In(p.Rect)) { return } - p.Pix[y*p.Stride+x] = c + i := (y-p.Rect.Min.Y)*p.Stride + (x-p.Rect.Min.X)*4 + p.Pix[i+0] = c.R + p.Pix[i+1] = c.G + p.Pix[i+2] = c.B + p.Pix[i+3] = c.A +} + +// SubImage returns an image representing the portion of the image p visible +// through r. The returned value shares pixels with the original image. +func (p *RGBA) SubImage(r Rectangle) Image { + r = r.Intersect(p.Rect) + // If r1 and r2 are Rectangles, r1.Intersect(r2) is not guaranteed to be inside + // either r1 or r2 if the intersection is empty. Without explicitly checking for + // this, the Pix[i:] expression below can panic. + if r.Empty() { + return &RGBA{} + } + i := (r.Min.Y-p.Rect.Min.Y)*p.Stride + (r.Min.X-p.Rect.Min.X)*4 + return &RGBA{ + Pix: p.Pix[i:], + Stride: p.Stride, + Rect: r, + } } // Opaque scans the entire image and returns whether or not it is fully opaque. @@ -63,11 +93,10 @@ func (p *RGBA) Opaque() bool { if p.Rect.Empty() { return true } - base := p.Rect.Min.Y * p.Stride - i0, i1 := base+p.Rect.Min.X, base+p.Rect.Max.X + i0, i1 := 3, p.Rect.Dx()*4 for y := p.Rect.Min.Y; y < p.Rect.Max.Y; y++ { - for _, c := range p.Pix[i0:i1] { - if c.A != 0xff { + for i := i0; i < i1; i += 4 { + if p.Pix[i] != 0xff { return false } } @@ -79,14 +108,16 @@ func (p *RGBA) Opaque() bool { // NewRGBA returns a new RGBA with the given width and height. func NewRGBA(w, h int) *RGBA { - buf := make([]RGBAColor, w*h) - return &RGBA{buf, w, Rectangle{ZP, Point{w, h}}} + buf := make([]uint8, 4*w*h) + return &RGBA{buf, 4 * w, Rectangle{ZP, Point{w, h}}} } -// An RGBA64 is an in-memory image of RGBA64Color values. +// RGBA64 is an in-memory image of RGBA64Color values. type RGBA64 struct { - // Pix holds the image's pixels. The pixel at (x, y) is Pix[y*Stride+x]. - Pix []RGBA64Color + // Pix holds the image's pixels, in R, G, B, A order and big-endian format. The pixel at + // (x, y) starts at Pix[(y-Rect.Min.Y)*Stride + (x-Rect.Min.X)*8]. + Pix []uint8 + // Stride is the Pix stride (in bytes) between vertically adjacent pixels. Stride int // Rect is the image's bounds. Rect Rectangle @@ -97,24 +128,65 @@ func (p *RGBA64) ColorModel() ColorModel { return RGBA64ColorModel } func (p *RGBA64) Bounds() Rectangle { return p.Rect } func (p *RGBA64) At(x, y int) Color { - if !p.Rect.Contains(Point{x, y}) { + if !(Point{x, y}.In(p.Rect)) { return RGBA64Color{} } - return p.Pix[y*p.Stride+x] + i := (y-p.Rect.Min.Y)*p.Stride + (x-p.Rect.Min.X)*8 + return RGBA64Color{ + uint16(p.Pix[i+0])<<8 | uint16(p.Pix[i+1]), + uint16(p.Pix[i+2])<<8 | uint16(p.Pix[i+3]), + uint16(p.Pix[i+4])<<8 | uint16(p.Pix[i+5]), + uint16(p.Pix[i+6])<<8 | uint16(p.Pix[i+7]), + } } func (p *RGBA64) Set(x, y int, c Color) { - if !p.Rect.Contains(Point{x, y}) { + if !(Point{x, y}.In(p.Rect)) { return } - p.Pix[y*p.Stride+x] = toRGBA64Color(c).(RGBA64Color) + i := (y-p.Rect.Min.Y)*p.Stride + (x-p.Rect.Min.X)*8 + c1 := toRGBA64Color(c).(RGBA64Color) + p.Pix[i+0] = uint8(c1.R >> 8) + p.Pix[i+1] = uint8(c1.R) + p.Pix[i+2] = uint8(c1.G >> 8) + p.Pix[i+3] = uint8(c1.G) + p.Pix[i+4] = uint8(c1.B >> 8) + p.Pix[i+5] = uint8(c1.B) + p.Pix[i+6] = uint8(c1.A >> 8) + p.Pix[i+7] = uint8(c1.A) } func (p *RGBA64) SetRGBA64(x, y int, c RGBA64Color) { - if !p.Rect.Contains(Point{x, y}) { + if !(Point{x, y}.In(p.Rect)) { return } - p.Pix[y*p.Stride+x] = c + i := (y-p.Rect.Min.Y)*p.Stride + (x-p.Rect.Min.X)*8 + p.Pix[i+0] = uint8(c.R >> 8) + p.Pix[i+1] = uint8(c.R) + p.Pix[i+2] = uint8(c.G >> 8) + p.Pix[i+3] = uint8(c.G) + p.Pix[i+4] = uint8(c.B >> 8) + p.Pix[i+5] = uint8(c.B) + p.Pix[i+6] = uint8(c.A >> 8) + p.Pix[i+7] = uint8(c.A) +} + +// SubImage returns an image representing the portion of the image p visible +// through r. The returned value shares pixels with the original image. +func (p *RGBA64) SubImage(r Rectangle) Image { + r = r.Intersect(p.Rect) + // If r1 and r2 are Rectangles, r1.Intersect(r2) is not guaranteed to be inside + // either r1 or r2 if the intersection is empty. Without explicitly checking for + // this, the Pix[i:] expression below can panic. + if r.Empty() { + return &RGBA64{} + } + i := (r.Min.Y-p.Rect.Min.Y)*p.Stride + (r.Min.X-p.Rect.Min.X)*8 + return &RGBA64{ + Pix: p.Pix[i:], + Stride: p.Stride, + Rect: r, + } } // Opaque scans the entire image and returns whether or not it is fully opaque. @@ -122,11 +194,10 @@ func (p *RGBA64) Opaque() bool { if p.Rect.Empty() { return true } - base := p.Rect.Min.Y * p.Stride - i0, i1 := base+p.Rect.Min.X, base+p.Rect.Max.X + i0, i1 := 6, p.Rect.Dx()*8 for y := p.Rect.Min.Y; y < p.Rect.Max.Y; y++ { - for _, c := range p.Pix[i0:i1] { - if c.A != 0xffff { + for i := i0; i < i1; i += 8 { + if p.Pix[i+0] != 0xff || p.Pix[i+1] != 0xff { return false } } @@ -138,14 +209,16 @@ func (p *RGBA64) Opaque() bool { // NewRGBA64 returns a new RGBA64 with the given width and height. func NewRGBA64(w, h int) *RGBA64 { - pix := make([]RGBA64Color, w*h) - return &RGBA64{pix, w, Rectangle{ZP, Point{w, h}}} + pix := make([]uint8, 8*w*h) + return &RGBA64{pix, 8 * w, Rectangle{ZP, Point{w, h}}} } -// An NRGBA is an in-memory image of NRGBAColor values. +// NRGBA is an in-memory image of NRGBAColor values. type NRGBA struct { - // Pix holds the image's pixels. The pixel at (x, y) is Pix[y*Stride+x]. - Pix []NRGBAColor + // Pix holds the image's pixels, in R, G, B, A order. The pixel at + // (x, y) starts at Pix[(y-Rect.Min.Y)*Stride + (x-Rect.Min.X)*4]. + Pix []uint8 + // Stride is the Pix stride (in bytes) between vertically adjacent pixels. Stride int // Rect is the image's bounds. Rect Rectangle @@ -156,24 +229,52 @@ func (p *NRGBA) ColorModel() ColorModel { return NRGBAColorModel } func (p *NRGBA) Bounds() Rectangle { return p.Rect } func (p *NRGBA) At(x, y int) Color { - if !p.Rect.Contains(Point{x, y}) { + if !(Point{x, y}.In(p.Rect)) { return NRGBAColor{} } - return p.Pix[y*p.Stride+x] + i := (y-p.Rect.Min.Y)*p.Stride + (x-p.Rect.Min.X)*4 + return NRGBAColor{p.Pix[i+0], p.Pix[i+1], p.Pix[i+2], p.Pix[i+3]} } func (p *NRGBA) Set(x, y int, c Color) { - if !p.Rect.Contains(Point{x, y}) { + if !(Point{x, y}.In(p.Rect)) { return } - p.Pix[y*p.Stride+x] = toNRGBAColor(c).(NRGBAColor) + i := (y-p.Rect.Min.Y)*p.Stride + (x-p.Rect.Min.X)*4 + c1 := toNRGBAColor(c).(NRGBAColor) + p.Pix[i+0] = c1.R + p.Pix[i+1] = c1.G + p.Pix[i+2] = c1.B + p.Pix[i+3] = c1.A } func (p *NRGBA) SetNRGBA(x, y int, c NRGBAColor) { - if !p.Rect.Contains(Point{x, y}) { + if !(Point{x, y}.In(p.Rect)) { return } - p.Pix[y*p.Stride+x] = c + i := (y-p.Rect.Min.Y)*p.Stride + (x-p.Rect.Min.X)*4 + p.Pix[i+0] = c.R + p.Pix[i+1] = c.G + p.Pix[i+2] = c.B + p.Pix[i+3] = c.A +} + +// SubImage returns an image representing the portion of the image p visible +// through r. The returned value shares pixels with the original image. +func (p *NRGBA) SubImage(r Rectangle) Image { + r = r.Intersect(p.Rect) + // If r1 and r2 are Rectangles, r1.Intersect(r2) is not guaranteed to be inside + // either r1 or r2 if the intersection is empty. Without explicitly checking for + // this, the Pix[i:] expression below can panic. + if r.Empty() { + return &NRGBA{} + } + i := (r.Min.Y-p.Rect.Min.Y)*p.Stride + (r.Min.X-p.Rect.Min.X)*4 + return &NRGBA{ + Pix: p.Pix[i:], + Stride: p.Stride, + Rect: r, + } } // Opaque scans the entire image and returns whether or not it is fully opaque. @@ -181,11 +282,10 @@ func (p *NRGBA) Opaque() bool { if p.Rect.Empty() { return true } - base := p.Rect.Min.Y * p.Stride - i0, i1 := base+p.Rect.Min.X, base+p.Rect.Max.X + i0, i1 := 3, p.Rect.Dx()*4 for y := p.Rect.Min.Y; y < p.Rect.Max.Y; y++ { - for _, c := range p.Pix[i0:i1] { - if c.A != 0xff { + for i := i0; i < i1; i += 4 { + if p.Pix[i] != 0xff { return false } } @@ -197,14 +297,16 @@ func (p *NRGBA) Opaque() bool { // NewNRGBA returns a new NRGBA with the given width and height. func NewNRGBA(w, h int) *NRGBA { - pix := make([]NRGBAColor, w*h) - return &NRGBA{pix, w, Rectangle{ZP, Point{w, h}}} + pix := make([]uint8, 4*w*h) + return &NRGBA{pix, 4 * w, Rectangle{ZP, Point{w, h}}} } -// An NRGBA64 is an in-memory image of NRGBA64Color values. +// NRGBA64 is an in-memory image of NRGBA64Color values. type NRGBA64 struct { - // Pix holds the image's pixels. The pixel at (x, y) is Pix[y*Stride+x]. - Pix []NRGBA64Color + // Pix holds the image's pixels, in R, G, B, A order and big-endian format. The pixel at + // (x, y) starts at Pix[(y-Rect.Min.Y)*Stride + (x-Rect.Min.X)*8]. + Pix []uint8 + // Stride is the Pix stride (in bytes) between vertically adjacent pixels. Stride int // Rect is the image's bounds. Rect Rectangle @@ -215,24 +317,65 @@ func (p *NRGBA64) ColorModel() ColorModel { return NRGBA64ColorModel } func (p *NRGBA64) Bounds() Rectangle { return p.Rect } func (p *NRGBA64) At(x, y int) Color { - if !p.Rect.Contains(Point{x, y}) { + if !(Point{x, y}.In(p.Rect)) { return NRGBA64Color{} } - return p.Pix[y*p.Stride+x] + i := (y-p.Rect.Min.Y)*p.Stride + (x-p.Rect.Min.X)*8 + return NRGBA64Color{ + uint16(p.Pix[i+0])<<8 | uint16(p.Pix[i+1]), + uint16(p.Pix[i+2])<<8 | uint16(p.Pix[i+3]), + uint16(p.Pix[i+4])<<8 | uint16(p.Pix[i+5]), + uint16(p.Pix[i+6])<<8 | uint16(p.Pix[i+7]), + } } func (p *NRGBA64) Set(x, y int, c Color) { - if !p.Rect.Contains(Point{x, y}) { + if !(Point{x, y}.In(p.Rect)) { return } - p.Pix[y*p.Stride+x] = toNRGBA64Color(c).(NRGBA64Color) + i := (y-p.Rect.Min.Y)*p.Stride + (x-p.Rect.Min.X)*8 + c1 := toNRGBA64Color(c).(NRGBA64Color) + p.Pix[i+0] = uint8(c1.R >> 8) + p.Pix[i+1] = uint8(c1.R) + p.Pix[i+2] = uint8(c1.G >> 8) + p.Pix[i+3] = uint8(c1.G) + p.Pix[i+4] = uint8(c1.B >> 8) + p.Pix[i+5] = uint8(c1.B) + p.Pix[i+6] = uint8(c1.A >> 8) + p.Pix[i+7] = uint8(c1.A) } func (p *NRGBA64) SetNRGBA64(x, y int, c NRGBA64Color) { - if !p.Rect.Contains(Point{x, y}) { + if !(Point{x, y}.In(p.Rect)) { return } - p.Pix[y*p.Stride+x] = c + i := (y-p.Rect.Min.Y)*p.Stride + (x-p.Rect.Min.X)*8 + p.Pix[i+0] = uint8(c.R >> 8) + p.Pix[i+1] = uint8(c.R) + p.Pix[i+2] = uint8(c.G >> 8) + p.Pix[i+3] = uint8(c.G) + p.Pix[i+4] = uint8(c.B >> 8) + p.Pix[i+5] = uint8(c.B) + p.Pix[i+6] = uint8(c.A >> 8) + p.Pix[i+7] = uint8(c.A) +} + +// SubImage returns an image representing the portion of the image p visible +// through r. The returned value shares pixels with the original image. +func (p *NRGBA64) SubImage(r Rectangle) Image { + r = r.Intersect(p.Rect) + // If r1 and r2 are Rectangles, r1.Intersect(r2) is not guaranteed to be inside + // either r1 or r2 if the intersection is empty. Without explicitly checking for + // this, the Pix[i:] expression below can panic. + if r.Empty() { + return &NRGBA64{} + } + i := (r.Min.Y-p.Rect.Min.Y)*p.Stride + (r.Min.X-p.Rect.Min.X)*8 + return &NRGBA64{ + Pix: p.Pix[i:], + Stride: p.Stride, + Rect: r, + } } // Opaque scans the entire image and returns whether or not it is fully opaque. @@ -240,11 +383,10 @@ func (p *NRGBA64) Opaque() bool { if p.Rect.Empty() { return true } - base := p.Rect.Min.Y * p.Stride - i0, i1 := base+p.Rect.Min.X, base+p.Rect.Max.X + i0, i1 := 6, p.Rect.Dx()*8 for y := p.Rect.Min.Y; y < p.Rect.Max.Y; y++ { - for _, c := range p.Pix[i0:i1] { - if c.A != 0xffff { + for i := i0; i < i1; i += 8 { + if p.Pix[i+0] != 0xff || p.Pix[i+1] != 0xff { return false } } @@ -256,14 +398,16 @@ func (p *NRGBA64) Opaque() bool { // NewNRGBA64 returns a new NRGBA64 with the given width and height. func NewNRGBA64(w, h int) *NRGBA64 { - pix := make([]NRGBA64Color, w*h) - return &NRGBA64{pix, w, Rectangle{ZP, Point{w, h}}} + pix := make([]uint8, 8*w*h) + return &NRGBA64{pix, 8 * w, Rectangle{ZP, Point{w, h}}} } -// An Alpha is an in-memory image of AlphaColor values. +// Alpha is an in-memory image of AlphaColor values. type Alpha struct { - // Pix holds the image's pixels. The pixel at (x, y) is Pix[y*Stride+x]. - Pix []AlphaColor + // Pix holds the image's pixels, as alpha values. The pixel at + // (x, y) starts at Pix[(y-Rect.Min.Y)*Stride + (x-Rect.Min.X)*1]. + Pix []uint8 + // Stride is the Pix stride (in bytes) between vertically adjacent pixels. Stride int // Rect is the image's bounds. Rect Rectangle @@ -274,24 +418,45 @@ func (p *Alpha) ColorModel() ColorModel { return AlphaColorModel } func (p *Alpha) Bounds() Rectangle { return p.Rect } func (p *Alpha) At(x, y int) Color { - if !p.Rect.Contains(Point{x, y}) { + if !(Point{x, y}.In(p.Rect)) { return AlphaColor{} } - return p.Pix[y*p.Stride+x] + i := (y-p.Rect.Min.Y)*p.Stride + (x - p.Rect.Min.X) + return AlphaColor{p.Pix[i]} } func (p *Alpha) Set(x, y int, c Color) { - if !p.Rect.Contains(Point{x, y}) { + if !(Point{x, y}.In(p.Rect)) { return } - p.Pix[y*p.Stride+x] = toAlphaColor(c).(AlphaColor) + i := (y-p.Rect.Min.Y)*p.Stride + (x - p.Rect.Min.X) + p.Pix[i] = toAlphaColor(c).(AlphaColor).A } func (p *Alpha) SetAlpha(x, y int, c AlphaColor) { - if !p.Rect.Contains(Point{x, y}) { + if !(Point{x, y}.In(p.Rect)) { return } - p.Pix[y*p.Stride+x] = c + i := (y-p.Rect.Min.Y)*p.Stride + (x - p.Rect.Min.X) + p.Pix[i] = c.A +} + +// SubImage returns an image representing the portion of the image p visible +// through r. The returned value shares pixels with the original image. +func (p *Alpha) SubImage(r Rectangle) Image { + r = r.Intersect(p.Rect) + // If r1 and r2 are Rectangles, r1.Intersect(r2) is not guaranteed to be inside + // either r1 or r2 if the intersection is empty. Without explicitly checking for + // this, the Pix[i:] expression below can panic. + if r.Empty() { + return &Alpha{} + } + i := (r.Min.Y-p.Rect.Min.Y)*p.Stride + (r.Min.X-p.Rect.Min.X)*1 + return &Alpha{ + Pix: p.Pix[i:], + Stride: p.Stride, + Rect: r, + } } // Opaque scans the entire image and returns whether or not it is fully opaque. @@ -299,11 +464,10 @@ func (p *Alpha) Opaque() bool { if p.Rect.Empty() { return true } - base := p.Rect.Min.Y * p.Stride - i0, i1 := base+p.Rect.Min.X, base+p.Rect.Max.X + i0, i1 := 0, p.Rect.Dx() for y := p.Rect.Min.Y; y < p.Rect.Max.Y; y++ { - for _, c := range p.Pix[i0:i1] { - if c.A != 0xff { + for i := i0; i < i1; i++ { + if p.Pix[i] != 0xff { return false } } @@ -315,14 +479,16 @@ func (p *Alpha) Opaque() bool { // NewAlpha returns a new Alpha with the given width and height. func NewAlpha(w, h int) *Alpha { - pix := make([]AlphaColor, w*h) - return &Alpha{pix, w, Rectangle{ZP, Point{w, h}}} + pix := make([]uint8, 1*w*h) + return &Alpha{pix, 1 * w, Rectangle{ZP, Point{w, h}}} } -// An Alpha16 is an in-memory image of Alpha16Color values. +// Alpha16 is an in-memory image of Alpha16Color values. type Alpha16 struct { - // Pix holds the image's pixels. The pixel at (x, y) is Pix[y*Stride+x]. - Pix []Alpha16Color + // Pix holds the image's pixels, as alpha values in big-endian format. The pixel at + // (x, y) starts at Pix[(y-Rect.Min.Y)*Stride + (x-Rect.Min.X)*2]. + Pix []uint8 + // Stride is the Pix stride (in bytes) between vertically adjacent pixels. Stride int // Rect is the image's bounds. Rect Rectangle @@ -333,24 +499,48 @@ func (p *Alpha16) ColorModel() ColorModel { return Alpha16ColorModel } func (p *Alpha16) Bounds() Rectangle { return p.Rect } func (p *Alpha16) At(x, y int) Color { - if !p.Rect.Contains(Point{x, y}) { + if !(Point{x, y}.In(p.Rect)) { return Alpha16Color{} } - return p.Pix[y*p.Stride+x] + i := (y-p.Rect.Min.Y)*p.Stride + (x-p.Rect.Min.X)*2 + return Alpha16Color{uint16(p.Pix[i+0])<<8 | uint16(p.Pix[i+1])} } func (p *Alpha16) Set(x, y int, c Color) { - if !p.Rect.Contains(Point{x, y}) { + if !(Point{x, y}.In(p.Rect)) { return } - p.Pix[y*p.Stride+x] = toAlpha16Color(c).(Alpha16Color) + i := (y-p.Rect.Min.Y)*p.Stride + (x-p.Rect.Min.X)*2 + c1 := toAlpha16Color(c).(Alpha16Color) + p.Pix[i+0] = uint8(c1.A >> 8) + p.Pix[i+1] = uint8(c1.A) } func (p *Alpha16) SetAlpha16(x, y int, c Alpha16Color) { - if !p.Rect.Contains(Point{x, y}) { + if !(Point{x, y}.In(p.Rect)) { return } - p.Pix[y*p.Stride+x] = c + i := (y-p.Rect.Min.Y)*p.Stride + (x-p.Rect.Min.X)*2 + p.Pix[i+0] = uint8(c.A >> 8) + p.Pix[i+1] = uint8(c.A) +} + +// SubImage returns an image representing the portion of the image p visible +// through r. The returned value shares pixels with the original image. +func (p *Alpha16) SubImage(r Rectangle) Image { + r = r.Intersect(p.Rect) + // If r1 and r2 are Rectangles, r1.Intersect(r2) is not guaranteed to be inside + // either r1 or r2 if the intersection is empty. Without explicitly checking for + // this, the Pix[i:] expression below can panic. + if r.Empty() { + return &Alpha16{} + } + i := (r.Min.Y-p.Rect.Min.Y)*p.Stride + (r.Min.X-p.Rect.Min.X)*2 + return &Alpha16{ + Pix: p.Pix[i:], + Stride: p.Stride, + Rect: r, + } } // Opaque scans the entire image and returns whether or not it is fully opaque. @@ -358,11 +548,10 @@ func (p *Alpha16) Opaque() bool { if p.Rect.Empty() { return true } - base := p.Rect.Min.Y * p.Stride - i0, i1 := base+p.Rect.Min.X, base+p.Rect.Max.X + i0, i1 := 0, p.Rect.Dx()*2 for y := p.Rect.Min.Y; y < p.Rect.Max.Y; y++ { - for _, c := range p.Pix[i0:i1] { - if c.A != 0xffff { + for i := i0; i < i1; i += 2 { + if p.Pix[i+0] != 0xff || p.Pix[i+1] != 0xff { return false } } @@ -374,14 +563,16 @@ func (p *Alpha16) Opaque() bool { // NewAlpha16 returns a new Alpha16 with the given width and height. func NewAlpha16(w, h int) *Alpha16 { - pix := make([]Alpha16Color, w*h) - return &Alpha16{pix, w, Rectangle{ZP, Point{w, h}}} + pix := make([]uint8, 2*w*h) + return &Alpha16{pix, 2 * w, Rectangle{ZP, Point{w, h}}} } -// A Gray is an in-memory image of GrayColor values. +// Gray is an in-memory image of GrayColor values. type Gray struct { - // Pix holds the image's pixels. The pixel at (x, y) is Pix[y*Stride+x]. - Pix []GrayColor + // Pix holds the image's pixels, as gray values. The pixel at + // (x, y) starts at Pix[(y-Rect.Min.Y)*Stride + (x-Rect.Min.X)*1]. + Pix []uint8 + // Stride is the Pix stride (in bytes) between vertically adjacent pixels. Stride int // Rect is the image's bounds. Rect Rectangle @@ -392,24 +583,45 @@ func (p *Gray) ColorModel() ColorModel { return GrayColorModel } func (p *Gray) Bounds() Rectangle { return p.Rect } func (p *Gray) At(x, y int) Color { - if !p.Rect.Contains(Point{x, y}) { + if !(Point{x, y}.In(p.Rect)) { return GrayColor{} } - return p.Pix[y*p.Stride+x] + i := (y-p.Rect.Min.Y)*p.Stride + (x - p.Rect.Min.X) + return GrayColor{p.Pix[i]} } func (p *Gray) Set(x, y int, c Color) { - if !p.Rect.Contains(Point{x, y}) { + if !(Point{x, y}.In(p.Rect)) { return } - p.Pix[y*p.Stride+x] = toGrayColor(c).(GrayColor) + i := (y-p.Rect.Min.Y)*p.Stride + (x - p.Rect.Min.X) + p.Pix[i] = toGrayColor(c).(GrayColor).Y } func (p *Gray) SetGray(x, y int, c GrayColor) { - if !p.Rect.Contains(Point{x, y}) { + if !(Point{x, y}.In(p.Rect)) { return } - p.Pix[y*p.Stride+x] = c + i := (y-p.Rect.Min.Y)*p.Stride + (x - p.Rect.Min.X) + p.Pix[i] = c.Y +} + +// SubImage returns an image representing the portion of the image p visible +// through r. The returned value shares pixels with the original image. +func (p *Gray) SubImage(r Rectangle) Image { + r = r.Intersect(p.Rect) + // If r1 and r2 are Rectangles, r1.Intersect(r2) is not guaranteed to be inside + // either r1 or r2 if the intersection is empty. Without explicitly checking for + // this, the Pix[i:] expression below can panic. + if r.Empty() { + return &Gray{} + } + i := (r.Min.Y-p.Rect.Min.Y)*p.Stride + (r.Min.X-p.Rect.Min.X)*1 + return &Gray{ + Pix: p.Pix[i:], + Stride: p.Stride, + Rect: r, + } } // Opaque scans the entire image and returns whether or not it is fully opaque. @@ -419,14 +631,16 @@ func (p *Gray) Opaque() bool { // NewGray returns a new Gray with the given width and height. func NewGray(w, h int) *Gray { - pix := make([]GrayColor, w*h) - return &Gray{pix, w, Rectangle{ZP, Point{w, h}}} + pix := make([]uint8, 1*w*h) + return &Gray{pix, 1 * w, Rectangle{ZP, Point{w, h}}} } -// A Gray16 is an in-memory image of Gray16Color values. +// Gray16 is an in-memory image of Gray16Color values. type Gray16 struct { - // Pix holds the image's pixels. The pixel at (x, y) is Pix[y*Stride+x]. - Pix []Gray16Color + // Pix holds the image's pixels, as gray values in big-endian format. The pixel at + // (x, y) starts at Pix[(y-Rect.Min.Y)*Stride + (x-Rect.Min.X)*2]. + Pix []uint8 + // Stride is the Pix stride (in bytes) between vertically adjacent pixels. Stride int // Rect is the image's bounds. Rect Rectangle @@ -437,24 +651,48 @@ func (p *Gray16) ColorModel() ColorModel { return Gray16ColorModel } func (p *Gray16) Bounds() Rectangle { return p.Rect } func (p *Gray16) At(x, y int) Color { - if !p.Rect.Contains(Point{x, y}) { + if !(Point{x, y}.In(p.Rect)) { return Gray16Color{} } - return p.Pix[y*p.Stride+x] + i := (y-p.Rect.Min.Y)*p.Stride + (x-p.Rect.Min.X)*2 + return Gray16Color{uint16(p.Pix[i+0])<<8 | uint16(p.Pix[i+1])} } func (p *Gray16) Set(x, y int, c Color) { - if !p.Rect.Contains(Point{x, y}) { + if !(Point{x, y}.In(p.Rect)) { return } - p.Pix[y*p.Stride+x] = toGray16Color(c).(Gray16Color) + i := (y-p.Rect.Min.Y)*p.Stride + (x-p.Rect.Min.X)*2 + c1 := toGray16Color(c).(Gray16Color) + p.Pix[i+0] = uint8(c1.Y >> 8) + p.Pix[i+1] = uint8(c1.Y) } func (p *Gray16) SetGray16(x, y int, c Gray16Color) { - if !p.Rect.Contains(Point{x, y}) { + if !(Point{x, y}.In(p.Rect)) { return } - p.Pix[y*p.Stride+x] = c + i := (y-p.Rect.Min.Y)*p.Stride + (x-p.Rect.Min.X)*2 + p.Pix[i+0] = uint8(c.Y >> 8) + p.Pix[i+1] = uint8(c.Y) +} + +// SubImage returns an image representing the portion of the image p visible +// through r. The returned value shares pixels with the original image. +func (p *Gray16) SubImage(r Rectangle) Image { + r = r.Intersect(p.Rect) + // If r1 and r2 are Rectangles, r1.Intersect(r2) is not guaranteed to be inside + // either r1 or r2 if the intersection is empty. Without explicitly checking for + // this, the Pix[i:] expression below can panic. + if r.Empty() { + return &Gray16{} + } + i := (r.Min.Y-p.Rect.Min.Y)*p.Stride + (r.Min.X-p.Rect.Min.X)*2 + return &Gray16{ + Pix: p.Pix[i:], + Stride: p.Stride, + Rect: r, + } } // Opaque scans the entire image and returns whether or not it is fully opaque. @@ -464,11 +702,11 @@ func (p *Gray16) Opaque() bool { // NewGray16 returns a new Gray16 with the given width and height. func NewGray16(w, h int) *Gray16 { - pix := make([]Gray16Color, w*h) - return &Gray16{pix, w, Rectangle{ZP, Point{w, h}}} + pix := make([]uint8, 2*w*h) + return &Gray16{pix, 2 * w, Rectangle{ZP, Point{w, h}}} } -// A PalettedColorModel represents a fixed palette of colors. +// A PalettedColorModel represents a fixed palette of at most 256 colors. type PalettedColorModel []Color func diff(a, b uint32) uint32 { @@ -483,14 +721,19 @@ func (p PalettedColorModel) Convert(c Color) Color { if len(p) == 0 { return nil } + return p[p.Index(c)] +} + +// Index returns the index of the palette color closest to c in Euclidean +// R,G,B space. +func (p PalettedColorModel) Index(c Color) int { cr, cg, cb, _ := c.RGBA() // Shift by 1 bit to avoid potential uint32 overflow in sum-squared-difference. cr >>= 1 cg >>= 1 cb >>= 1 - result := Color(nil) - bestSSD := uint32(1<<32 - 1) - for _, v := range p { + ret, bestSSD := 0, uint32(1<<32-1) + for i, v := range p { vr, vg, vb, _ := v.RGBA() vr >>= 1 vg >>= 1 @@ -498,17 +741,18 @@ func (p PalettedColorModel) Convert(c Color) Color { dr, dg, db := diff(cr, vr), diff(cg, vg), diff(cb, vb) ssd := (dr * dr) + (dg * dg) + (db * db) if ssd < bestSSD { - bestSSD = ssd - result = v + ret, bestSSD = i, ssd } } - return result + return ret } -// A Paletted is an in-memory image backed by a 2-D slice of uint8 values and a PalettedColorModel. +// Paletted is an in-memory image of uint8 indices into a given palette. type Paletted struct { - // Pix holds the image's pixels. The pixel at (x, y) is Pix[y*Stride+x]. - Pix []uint8 + // Pix holds the image's pixels, as palette indices. The pixel at + // (x, y) starts at Pix[(y-Rect.Min.Y)*Stride + (x-Rect.Min.X)*1]. + Pix []uint8 + // Stride is the Pix stride (in bytes) between vertically adjacent pixels. Stride int // Rect is the image's bounds. Rect Rectangle @@ -524,29 +768,73 @@ func (p *Paletted) At(x, y int) Color { if len(p.Palette) == 0 { return nil } - if !p.Rect.Contains(Point{x, y}) { + if !(Point{x, y}.In(p.Rect)) { return p.Palette[0] } - return p.Palette[p.Pix[y*p.Stride+x]] + i := (y-p.Rect.Min.Y)*p.Stride + (x - p.Rect.Min.X) + return p.Palette[p.Pix[i]] +} + +func (p *Paletted) Set(x, y int, c Color) { + if !(Point{x, y}.In(p.Rect)) { + return + } + i := (y-p.Rect.Min.Y)*p.Stride + (x - p.Rect.Min.X) + p.Pix[i] = uint8(p.Palette.Index(c)) } func (p *Paletted) ColorIndexAt(x, y int) uint8 { - if !p.Rect.Contains(Point{x, y}) { + if !(Point{x, y}.In(p.Rect)) { return 0 } - return p.Pix[y*p.Stride+x] + i := (y-p.Rect.Min.Y)*p.Stride + (x - p.Rect.Min.X) + return p.Pix[i] } func (p *Paletted) SetColorIndex(x, y int, index uint8) { - if !p.Rect.Contains(Point{x, y}) { + if !(Point{x, y}.In(p.Rect)) { return } - p.Pix[y*p.Stride+x] = index + i := (y-p.Rect.Min.Y)*p.Stride + (x - p.Rect.Min.X) + p.Pix[i] = index +} + +// SubImage returns an image representing the portion of the image p visible +// through r. The returned value shares pixels with the original image. +func (p *Paletted) SubImage(r Rectangle) Image { + r = r.Intersect(p.Rect) + // If r1 and r2 are Rectangles, r1.Intersect(r2) is not guaranteed to be inside + // either r1 or r2 if the intersection is empty. Without explicitly checking for + // this, the Pix[i:] expression below can panic. + if r.Empty() { + return &Paletted{ + Palette: p.Palette, + } + } + i := (r.Min.Y-p.Rect.Min.Y)*p.Stride + (r.Min.X-p.Rect.Min.X)*1 + return &Paletted{ + Pix: p.Pix[i:], + Stride: p.Stride, + Rect: p.Rect.Intersect(r), + Palette: p.Palette, + } } // Opaque scans the entire image and returns whether or not it is fully opaque. func (p *Paletted) Opaque() bool { - for _, c := range p.Palette { + var present [256]bool + i0, i1 := 0, p.Rect.Dx() + for y := p.Rect.Min.Y; y < p.Rect.Max.Y; y++ { + for _, c := range p.Pix[i0:i1] { + present[c] = true + } + i0 += p.Stride + i1 += p.Stride + } + for i, c := range p.Palette { + if !present[i] { + continue + } _, _, _, a := c.RGBA() if a != 0xffff { return false @@ -557,6 +845,6 @@ func (p *Paletted) Opaque() bool { // NewPaletted returns a new Paletted with the given width, height and palette. func NewPaletted(w, h int, m PalettedColorModel) *Paletted { - pix := make([]uint8, w*h) - return &Paletted{pix, w, Rectangle{ZP, Point{w, h}}, m} + pix := make([]uint8, 1*w*h) + return &Paletted{pix, 1 * w, Rectangle{ZP, Point{w, h}}, m} } diff --git a/libgo/go/image/image_test.go b/libgo/go/image/image_test.go new file mode 100644 index 0000000..9519acf --- /dev/null +++ b/libgo/go/image/image_test.go @@ -0,0 +1,113 @@ +// Copyright 2011 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 image_test + +import ( + . "image" + "testing" +) + +type image interface { + Image + Opaque() bool + Set(int, int, Color) + SubImage(Rectangle) Image +} + +func cmp(t *testing.T, cm ColorModel, c0, c1 Color) bool { + r0, g0, b0, a0 := cm.Convert(c0).RGBA() + r1, g1, b1, a1 := cm.Convert(c1).RGBA() + return r0 == r1 && g0 == g1 && b0 == b1 && a0 == a1 +} + +func TestImage(t *testing.T) { + testImage := []image{ + NewRGBA(10, 10), + NewRGBA64(10, 10), + NewNRGBA(10, 10), + NewNRGBA64(10, 10), + NewAlpha(10, 10), + NewAlpha16(10, 10), + NewGray(10, 10), + NewGray16(10, 10), + NewPaletted(10, 10, PalettedColorModel{ + Transparent, + Opaque, + }), + } + for _, m := range testImage { + if !Rect(0, 0, 10, 10).Eq(m.Bounds()) { + t.Errorf("%T: want bounds %v, got %v", m, Rect(0, 0, 10, 10), m.Bounds()) + continue + } + if !cmp(t, m.ColorModel(), Transparent, m.At(6, 3)) { + t.Errorf("%T: at (6, 3), want a zero color, got %v", m, m.At(6, 3)) + continue + } + m.Set(6, 3, Opaque) + if !cmp(t, m.ColorModel(), Opaque, m.At(6, 3)) { + t.Errorf("%T: at (6, 3), want a non-zero color, got %v", m, m.At(6, 3)) + continue + } + if !m.SubImage(Rect(6, 3, 7, 4)).(image).Opaque() { + t.Errorf("%T: at (6, 3) was not opaque", m) + continue + } + m = m.SubImage(Rect(3, 2, 9, 8)).(image) + if !Rect(3, 2, 9, 8).Eq(m.Bounds()) { + t.Errorf("%T: sub-image want bounds %v, got %v", m, Rect(3, 2, 9, 8), m.Bounds()) + continue + } + if !cmp(t, m.ColorModel(), Opaque, m.At(6, 3)) { + t.Errorf("%T: sub-image at (6, 3), want a non-zero color, got %v", m, m.At(6, 3)) + continue + } + if !cmp(t, m.ColorModel(), Transparent, m.At(3, 3)) { + t.Errorf("%T: sub-image at (3, 3), want a zero color, got %v", m, m.At(3, 3)) + continue + } + m.Set(3, 3, Opaque) + if !cmp(t, m.ColorModel(), Opaque, m.At(3, 3)) { + t.Errorf("%T: sub-image at (3, 3), want a non-zero color, got %v", m, m.At(3, 3)) + continue + } + // Test that taking an empty sub-image starting at a corner does not panic. + m.SubImage(Rect(0, 0, 0, 0)) + m.SubImage(Rect(10, 0, 10, 0)) + m.SubImage(Rect(0, 10, 0, 10)) + m.SubImage(Rect(10, 10, 10, 10)) + } +} + +func Test16BitsPerColorChannel(t *testing.T) { + testColorModel := []ColorModel{ + RGBA64ColorModel, + NRGBA64ColorModel, + Alpha16ColorModel, + Gray16ColorModel, + } + for _, cm := range testColorModel { + c := cm.Convert(RGBA64Color{0x1234, 0x1234, 0x1234, 0x1234}) // Premultiplied alpha. + r, _, _, _ := c.RGBA() + if r != 0x1234 { + t.Errorf("%T: want red value 0x%04x got 0x%04x", c, 0x1234, r) + continue + } + } + testImage := []image{ + NewRGBA64(10, 10), + NewNRGBA64(10, 10), + NewAlpha16(10, 10), + NewGray16(10, 10), + } + for _, m := range testImage { + m.Set(1, 2, NRGBA64Color{0xffff, 0xffff, 0xffff, 0x1357}) // Non-premultiplied alpha. + r, _, _, _ := m.At(1, 2).RGBA() + if r != 0x1357 { + t.Errorf("%T: want red value 0x%04x got 0x%04x", m, 0x1357, r) + continue + } + } +} diff --git a/libgo/go/image/jpeg/idct.go b/libgo/go/image/jpeg/idct.go index e5a2f40..b387dfd 100644 --- a/libgo/go/image/jpeg/idct.go +++ b/libgo/go/image/jpeg/idct.go @@ -2,6 +2,8 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. +package jpeg + // This is a Go translation of idct.c from // // http://standards.iso.org/ittf/PubliclyAvailableStandards/ISO_IEC_13818-4_2004_Conformance_Testing/Video/verifier/mpeg2decode_960109.tar.gz @@ -35,8 +37,6 @@ * */ -package jpeg - const ( w1 = 2841 // 2048*sqrt(2)*cos(1*pi/16) w2 = 2676 // 2048*sqrt(2)*cos(2*pi/16) @@ -55,41 +55,45 @@ const ( r2 = 181 // 256/sqrt(2) ) -// 2-D Inverse Discrete Cosine Transformation, followed by a +128 level shift. +// 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. // -// The input coefficients should already have been multiplied by the appropriate quantization table. -// We use fixed-point computation, with the number of bits for the fractional component varying over the -// intermediate stages. The final values are expected to range within [0, 255], after a +128 level shift. +// The input coefficients should already have been multiplied by the +// appropriate quantization table. We use fixed-point computation, with the +// number of bits for the fractional component varying over the intermediate +// stages. // -// 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(b *block) { +// 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) { // Horizontal 1-D IDCT. for y := 0; y < 8; y++ { // If all the AC components are zero, then the IDCT is trivial. - if b[y*8+1] == 0 && b[y*8+2] == 0 && b[y*8+3] == 0 && - b[y*8+4] == 0 && b[y*8+5] == 0 && b[y*8+6] == 0 && b[y*8+7] == 0 { - dc := b[y*8+0] << 3 - b[y*8+0] = dc - b[y*8+1] = dc - b[y*8+2] = dc - b[y*8+3] = dc - b[y*8+4] = dc - b[y*8+5] = dc - b[y*8+6] = dc - b[y*8+7] = dc + 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 continue } // Prescale. - x0 := (b[y*8+0] << 11) + 128 - x1 := b[y*8+4] << 11 - x2 := b[y*8+6] - x3 := b[y*8+2] - x4 := b[y*8+1] - x5 := b[y*8+7] - x6 := b[y*8+5] - x7 := b[y*8+3] + 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] // Stage 1. x8 := w7 * (x4 + x5) @@ -119,14 +123,14 @@ func idct(b *block) { x4 = (r2*(x4-x5) + 128) >> 8 // Stage 4. - b[8*y+0] = (x7 + x1) >> 8 - b[8*y+1] = (x3 + x2) >> 8 - b[8*y+2] = (x0 + x4) >> 8 - b[8*y+3] = (x8 + x6) >> 8 - b[8*y+4] = (x8 - x6) >> 8 - b[8*y+5] = (x0 - x4) >> 8 - b[8*y+6] = (x3 - x2) >> 8 - b[8*y+7] = (x7 - x1) >> 8 + 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 } // Vertical 1-D IDCT. @@ -136,14 +140,14 @@ func idct(b *block) { // we do not bother to check for the all-zero case. // Prescale. - y0 := (b[8*0+x] << 8) + 8192 - y1 := b[8*4+x] << 8 - y2 := b[8*6+x] - y3 := b[8*2+x] - y4 := b[8*1+x] - y5 := b[8*7+x] - y6 := b[8*5+x] - y7 := b[8*3+x] + y0 := (src[8*0+x] << 8) + 8192 + y1 := src[8*4+x] << 8 + y2 := src[8*6+x] + y3 := src[8*2+x] + y4 := src[8*1+x] + y5 := src[8*7+x] + y6 := src[8*5+x] + y7 := src[8*3+x] // Stage 1. y8 := w7*(y4+y5) + 4 @@ -173,18 +177,28 @@ func idct(b *block) { y4 = (r2*(y4-y5) + 128) >> 8 // Stage 4. - b[8*0+x] = (y7 + y1) >> 14 - b[8*1+x] = (y3 + y2) >> 14 - b[8*2+x] = (y0 + y4) >> 14 - b[8*3+x] = (y8 + y6) >> 14 - b[8*4+x] = (y8 - y6) >> 14 - b[8*5+x] = (y0 - y4) >> 14 - b[8*6+x] = (y3 - y2) >> 14 - b[8*7+x] = (y7 - y1) >> 14 + src[8*0+x] = (y7 + y1) >> 14 + src[8*1+x] = (y3 + y2) >> 14 + src[8*2+x] = (y0 + y4) >> 14 + src[8*3+x] = (y8 + y6) >> 14 + src[8*4+x] = (y8 - y6) >> 14 + src[8*5+x] = (y0 - y4) >> 14 + src[8*6+x] = (y3 - y2) >> 14 + src[8*7+x] = (y7 - y1) >> 14 } - // Level shift. - for i := range *b { - b[i] += 128 + // 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 21a6fff..3f22c52 100644 --- a/libgo/go/image/jpeg/reader.go +++ b/libgo/go/image/jpeg/reader.go @@ -41,16 +41,22 @@ type block [blockSize]int const ( blockSize = 64 // A DCT block is 8x8. - dcTableClass = 0 - acTableClass = 1 - maxTc = 1 - maxTh = 3 - maxTq = 3 - - // We only support 4:4:4, 4:2:2 and 4:2:0 downsampling, and assume that the components are Y, Cb, Cr. - nComponent = 3 - maxH = 2 - maxV = 2 + dcTable = 0 + acTable = 1 + maxTc = 1 + maxTh = 3 + maxTq = 3 + + // A grayscale JPEG image has only a Y component. + nGrayComponent = 1 + // 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 + // number of luma samples per chroma sample is at most 2 in the horizontal + // and 2 in the vertical direction. + maxH = 2 + maxV = 2 ) const ( @@ -90,13 +96,14 @@ type Reader interface { type decoder struct { r Reader width, height int - img *ycbcr.YCbCr + img1 *image.Gray + img3 *ycbcr.YCbCr ri int // Restart Interval. - comps [nComponent]component + nComp int + comp [nColorComponent]component huff [maxTc + 1][maxTh + 1]huffman quant [maxTq + 1]block b bits - blocks [nComponent][maxH * maxV]block tmp [1024]byte } @@ -118,10 +125,15 @@ func (d *decoder) ignore(n int) os.Error { // Specified in section B.2.2. func (d *decoder) processSOF(n int) os.Error { - if n != 6+3*nComponent { + switch n { + case 6 + 3*nGrayComponent: + d.nComp = nGrayComponent + case 6 + 3*nColorComponent: + d.nComp = nColorComponent + default: return UnsupportedError("SOF has wrong length") } - _, err := io.ReadFull(d.r, d.tmp[0:6+3*nComponent]) + _, err := io.ReadFull(d.r, d.tmp[:n]) if err != nil { return err } @@ -131,26 +143,28 @@ func (d *decoder) processSOF(n int) os.Error { } d.height = int(d.tmp[1])<<8 + int(d.tmp[2]) d.width = int(d.tmp[3])<<8 + int(d.tmp[4]) - if d.tmp[5] != nComponent { + if int(d.tmp[5]) != d.nComp { return UnsupportedError("SOF has wrong number of image components") } - for i := 0; i < nComponent; i++ { + for i := 0; i < d.nComp; i++ { hv := d.tmp[7+3*i] - d.comps[i].h = int(hv >> 4) - d.comps[i].v = int(hv & 0x0f) - d.comps[i].c = d.tmp[6+3*i] - d.comps[i].tq = d.tmp[8+3*i] - // We only support YCbCr images, and 4:4:4, 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) values for the Cr and Cb components must be (1, 1). + d.comp[i].h = int(hv >> 4) + d.comp[i].v = int(hv & 0x0f) + d.comp[i].c = d.tmp[6+3*i] + d.comp[i].tq = d.tmp[8+3*i] + if d.nComp == nGrayComponent { + continue + } + // For color images, we only support 4:4:4, 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) + // values for the Cr and Cb components must be (1, 1). if i == 0 { if hv != 0x11 && hv != 0x21 && hv != 0x22 { return UnsupportedError("luma downsample ratio") } - } else { - if hv != 0x11 { - return UnsupportedError("chroma downsample ratio") - } + } else if hv != 0x11 { + return UnsupportedError("chroma downsample ratio") } } return nil @@ -182,110 +196,88 @@ func (d *decoder) processDQT(n int) os.Error { return nil } -// Clip x to the range [0, 255] inclusive. -func clip(x int) uint8 { - if x < 0 { - return 0 - } - if x > 255 { - return 255 +// makeImg allocates and initializes the destination image. +func (d *decoder) makeImg(h0, v0, mxx, myy int) { + if d.nComp == nGrayComponent { + m := image.NewGray(8*mxx, 8*myy) + d.img1 = m.SubImage(image.Rect(0, 0, d.width, d.height)).(*image.Gray) + return } - return uint8(x) -} - -// Store the MCU to the image. -func (d *decoder) storeMCU(mx, my int) { - h0, v0 := d.comps[0].h, d.comps[0].v - // Store the luma blocks. - for v := 0; v < v0; v++ { - for h := 0; h < h0; h++ { - p := 8 * ((v0*my+v)*d.img.YStride + (h0*mx + h)) - for y := 0; y < 8; y++ { - for x := 0; x < 8; x++ { - d.img.Y[p] = clip(d.blocks[0][h0*v+h][8*y+x]) - p++ - } - p += d.img.YStride - 8 - } - } + var subsampleRatio ycbcr.SubsampleRatio + n := h0 * v0 + switch n { + case 1: + subsampleRatio = ycbcr.SubsampleRatio444 + case 2: + subsampleRatio = ycbcr.SubsampleRatio422 + case 4: + subsampleRatio = ycbcr.SubsampleRatio420 + default: + panic("unreachable") } - // Store the chroma blocks. - p := 8 * (my*d.img.CStride + mx) - for y := 0; y < 8; y++ { - for x := 0; x < 8; x++ { - d.img.Cb[p] = clip(d.blocks[1][0][8*y+x]) - d.img.Cr[p] = clip(d.blocks[2][0][8*y+x]) - p++ - } - p += d.img.CStride - 8 + b := make([]byte, mxx*myy*(1*8*8*n+2*8*8)) + d.img3 = &ycbcr.YCbCr{ + Y: b[mxx*myy*(0*8*8*n+0*8*8) : mxx*myy*(1*8*8*n+0*8*8)], + Cb: b[mxx*myy*(1*8*8*n+0*8*8) : mxx*myy*(1*8*8*n+1*8*8)], + Cr: b[mxx*myy*(1*8*8*n+1*8*8) : mxx*myy*(1*8*8*n+2*8*8)], + SubsampleRatio: subsampleRatio, + YStride: mxx * 8 * h0, + CStride: mxx * 8, + Rect: image.Rect(0, 0, d.width, d.height), } } // Specified in section B.2.3. func (d *decoder) processSOS(n int) os.Error { - if n != 4+2*nComponent { + if d.nComp == 0 { + return FormatError("missing SOF marker") + } + if n != 4+2*d.nComp { return UnsupportedError("SOS has wrong length") } - _, err := io.ReadFull(d.r, d.tmp[0:4+2*nComponent]) + _, err := io.ReadFull(d.r, d.tmp[0:4+2*d.nComp]) if err != nil { return err } - if d.tmp[0] != nComponent { + if int(d.tmp[0]) != d.nComp { return UnsupportedError("SOS has wrong number of image components") } - var scanComps [nComponent]struct { + var scan [nColorComponent]struct { td uint8 // DC table selector. ta uint8 // AC table selector. } - for i := 0; i < nComponent; i++ { + for i := 0; i < d.nComp; i++ { cs := d.tmp[1+2*i] // Component selector. - if cs != d.comps[i].c { + if cs != d.comp[i].c { return UnsupportedError("scan components out of order") } - scanComps[i].td = d.tmp[2+2*i] >> 4 - scanComps[i].ta = d.tmp[2+2*i] & 0x0f + scan[i].td = d.tmp[2+2*i] >> 4 + scan[i].ta = d.tmp[2+2*i] & 0x0f } // mxx and myy are the number of MCUs (Minimum Coded Units) in the image. - h0, v0 := d.comps[0].h, d.comps[0].v // The h and v values from the Y components. + h0, v0 := d.comp[0].h, d.comp[0].v // The h and v values from the Y components. mxx := (d.width + 8*h0 - 1) / (8 * h0) myy := (d.height + 8*v0 - 1) / (8 * v0) - if d.img == nil { - var subsampleRatio ycbcr.SubsampleRatio - n := h0 * v0 - switch n { - case 1: - subsampleRatio = ycbcr.SubsampleRatio444 - case 2: - subsampleRatio = ycbcr.SubsampleRatio422 - case 4: - subsampleRatio = ycbcr.SubsampleRatio420 - default: - panic("unreachable") - } - b := make([]byte, mxx*myy*(1*8*8*n+2*8*8)) - d.img = &ycbcr.YCbCr{ - Y: b[mxx*myy*(0*8*8*n+0*8*8) : mxx*myy*(1*8*8*n+0*8*8)], - Cb: b[mxx*myy*(1*8*8*n+0*8*8) : mxx*myy*(1*8*8*n+1*8*8)], - Cr: b[mxx*myy*(1*8*8*n+1*8*8) : mxx*myy*(1*8*8*n+2*8*8)], - SubsampleRatio: subsampleRatio, - YStride: mxx * 8 * h0, - CStride: mxx * 8, - Rect: image.Rect(0, 0, d.width, d.height), - } + if d.img1 == nil && d.img3 == nil { + d.makeImg(h0, v0, mxx, myy) } mcu, expectedRST := 0, uint8(rst0Marker) - var allZeroes block - var dc [nComponent]int + var ( + b block + dc [nColorComponent]int + ) for my := 0; my < myy; my++ { for mx := 0; mx < mxx; mx++ { - for i := 0; i < nComponent; i++ { - qt := &d.quant[d.comps[i].tq] - for j := 0; j < d.comps[i].h*d.comps[i].v; j++ { - d.blocks[i][j] = allZeroes + for i := 0; i < d.nComp; i++ { + qt := &d.quant[d.comp[i].tq] + for j := 0; j < d.comp[i].h*d.comp[i].v; j++ { + // TODO(nigeltao): make this a "var b block" once the compiler's escape + // analysis is good enough to allocate it on the stack, not the heap. + b = block{} // Decode the DC coefficient, as specified in section F.2.2.1. - value, err := d.decodeHuffman(&d.huff[dcTableClass][scanComps[i].td]) + value, err := d.decodeHuffman(&d.huff[dcTable][scan[i].td]) if err != nil { return err } @@ -297,11 +289,11 @@ func (d *decoder) processSOS(n int) os.Error { return err } dc[i] += dcDelta - d.blocks[i][j][0] = dc[i] * qt[0] + b[0] = dc[i] * qt[0] // Decode the AC coefficients, as specified in section F.2.2.2. for k := 1; k < blockSize; k++ { - value, err := d.decodeHuffman(&d.huff[acTableClass][scanComps[i].ta]) + value, err := d.decodeHuffman(&d.huff[acTable][scan[i].ta]) if err != nil { return err } @@ -316,7 +308,7 @@ func (d *decoder) processSOS(n int) os.Error { if err != nil { return err } - d.blocks[i][j][unzig[k]] = ac * qt[k] + b[unzig[k]] = ac * qt[k] } else { if val0 != 0x0f { break @@ -325,10 +317,23 @@ func (d *decoder) processSOS(n int) os.Error { } } - idct(&d.blocks[i][j]) + // Perform the inverse DCT and store the MCU component to the image. + if d.nComp == nGrayComponent { + idct(d.img1.Pix[8*(my*d.img1.Stride+mx):], d.img1.Stride, &b) + } 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) + case 1: + idct(d.img3.Cb[8*(my*d.img3.CStride+mx):], d.img3.CStride, &b) + case 2: + idct(d.img3.Cr[8*(my*d.img3.CStride+mx):], d.img3.CStride, &b) + } + } } // for j } // for i - d.storeMCU(mx, my) mcu++ if d.ri > 0 && mcu%d.ri == 0 && mcu < mxx*myy { // A more sophisticated decoder could use RST[0-7] markers to resynchronize from corrupt input, @@ -347,9 +352,7 @@ func (d *decoder) processSOS(n int) os.Error { // Reset the Huffman decoder. d.b = bits{} // Reset the DC components, as per section F.2.1.3.1. - for i := 0; i < nComponent; i++ { - dc[i] = 0 - } + dc = [nColorComponent]int{} } } // for mx } // for my @@ -437,7 +440,13 @@ func (d *decoder) decode(r io.Reader, configOnly bool) (image.Image, os.Error) { return nil, err } } - return d.img, nil + if d.img1 != nil { + return d.img1, nil + } + if d.img3 != nil { + return d.img3, nil + } + return nil, FormatError("missing SOS marker") } // Decode reads a JPEG image from r and returns it as an image.Image. @@ -453,7 +462,13 @@ func DecodeConfig(r io.Reader) (image.Config, os.Error) { if _, err := d.decode(r, true); err != nil { return image.Config{}, err } - return image.Config{image.RGBAColorModel, d.width, d.height}, nil + switch d.nComp { + case nGrayComponent: + return image.Config{image.GrayColorModel, d.width, d.height}, nil + case nColorComponent: + return image.Config{ycbcr.YCbCrColorModel, d.width, d.height}, nil + } + return image.Config{}, FormatError("missing SOF marker") } func init() { diff --git a/libgo/go/image/jpeg/writer.go b/libgo/go/image/jpeg/writer.go index 52b3dc4..2bb6df5 100644 --- a/libgo/go/image/jpeg/writer.go +++ b/libgo/go/image/jpeg/writer.go @@ -221,8 +221,7 @@ type encoder struct { // buf is a scratch buffer. buf [16]byte // bits and nBits are accumulated bits to write to w. - bits uint32 - nBits uint8 + bits, nBits uint32 // quant is the scaled quantization tables. quant [nQuantIndex][blockSize]byte } @@ -250,7 +249,7 @@ func (e *encoder) writeByte(b byte) { // emit emits the least significant nBits bits of bits to the bitstream. // The precondition is bits < 1<<nBits && nBits <= 16. -func (e *encoder) emit(bits uint32, nBits uint8) { +func (e *encoder) emit(bits, nBits uint32) { nBits += e.nBits bits <<= 32 - nBits bits |= e.bits @@ -269,7 +268,7 @@ func (e *encoder) emit(bits uint32, nBits uint8) { // emitHuff emits the given value with the given Huffman encoder. func (e *encoder) emitHuff(h huffIndex, value int) { x := theHuffmanLUT[h][value] - e.emit(x&(1<<24-1), uint8(x>>24)) + e.emit(x&(1<<24-1), x>>24) } // emitHuffRLE emits a run of runLength copies of value encoded with the given @@ -279,11 +278,11 @@ func (e *encoder) emitHuffRLE(h huffIndex, runLength, value int) { if a < 0 { a, b = -value, value-1 } - var nBits uint8 + var nBits uint32 if a < 0x100 { - nBits = bitCount[a] + nBits = uint32(bitCount[a]) } else { - nBits = 8 + bitCount[a>>8] + nBits = 8 + uint32(bitCount[a>>8]) } e.emitHuff(h, runLength<<4|int(nBits)) if nBits > 0 { @@ -302,34 +301,31 @@ func (e *encoder) writeMarkerHeader(marker uint8, markerlen int) { // writeDQT writes the Define Quantization Table marker. func (e *encoder) writeDQT() { - markerlen := 2 - for _, q := range e.quant { - markerlen += 1 + len(q) - } + markerlen := 2 + int(nQuantIndex)*(1+blockSize) e.writeMarkerHeader(dqtMarker, markerlen) - for i, q := range e.quant { + for i := range e.quant { e.writeByte(uint8(i)) - e.write(q[:]) + e.write(e.quant[i][:]) } } // writeSOF0 writes the Start Of Frame (Baseline) marker. func (e *encoder) writeSOF0(size image.Point) { - markerlen := 8 + 3*nComponent + markerlen := 8 + 3*nColorComponent e.writeMarkerHeader(sof0Marker, markerlen) e.buf[0] = 8 // 8-bit color. e.buf[1] = uint8(size.Y >> 8) e.buf[2] = uint8(size.Y & 0xff) e.buf[3] = uint8(size.X >> 8) e.buf[4] = uint8(size.X & 0xff) - e.buf[5] = nComponent - for i := 0; i < nComponent; i++ { + e.buf[5] = nColorComponent + for i := 0; i < nColorComponent; i++ { e.buf[3*i+6] = uint8(i + 1) // We use 4:2:0 chroma subsampling. e.buf[3*i+7] = "\x22\x11\x11"[i] e.buf[3*i+8] = "\x00\x01\x01"[i] } - e.write(e.buf[:3*(nComponent-1)+9]) + e.write(e.buf[:3*(nColorComponent-1)+9]) } // writeDHT writes the Define Huffman Table marker. @@ -401,14 +397,14 @@ func rgbaToYCbCr(m *image.RGBA, p image.Point, yBlock, cbBlock, crBlock *block) if sj > ymax { sj = ymax } - yoff := sj * m.Stride + offset := (sj-b.Min.Y)*m.Stride - b.Min.X*4 for i := 0; i < 8; i++ { sx := p.X + i if sx > xmax { sx = xmax } - col := &m.Pix[yoff+sx] - yy, cb, cr := ycbcr.RGBToYCbCr(col.R, col.G, col.B) + pix := m.Pix[offset+sx*4:] + yy, cb, cr := ycbcr.RGBToYCbCr(pix[0], pix[1], pix[2]) yBlock[8*j+i] = int(yy) cbBlock[8*j+i] = int(cb) crBlock[8*j+i] = int(cr) diff --git a/libgo/go/image/png/writer.go b/libgo/go/image/png/writer.go index a27586f..55ca97e 100644 --- a/libgo/go/image/png/writer.go +++ b/libgo/go/image/png/writer.go @@ -174,7 +174,7 @@ func (e *encoder) Write(b []byte) (int, os.Error) { // Chooses the filter to use for encoding the current row, and applies it. // The return value is the index of the filter and also of the row in cr that has had it applied. -func filter(cr [][]byte, pr []byte, bpp int) int { +func filter(cr *[nFilter][]byte, pr []byte, bpp int) int { // We try all five filter types, and pick the one that minimizes the sum of absolute differences. // This is the same heuristic that libpng uses, although the filters are attempted in order of // estimated most likely to be minimal (ftUp, ftPaeth, ftNone, ftSub, ftAverage), rather than @@ -275,11 +275,6 @@ func writeImage(w io.Writer, m image.Image, cb int) os.Error { bpp := 0 // Bytes per pixel. - // Used by fast paths for common image types - var paletted *image.Paletted - var rgba *image.RGBA - rgba, _ = m.(*image.RGBA) - switch cb { case cbG8: bpp = 1 @@ -287,7 +282,6 @@ func writeImage(w io.Writer, m image.Image, cb int) os.Error { bpp = 3 case cbP8: bpp = 1 - paletted = m.(*image.Paletted) case cbTCA8: bpp = 4 case cbTC16: @@ -304,7 +298,7 @@ func writeImage(w io.Writer, m image.Image, cb int) os.Error { // The +1 is for the per-row filter type, which is at cr[*][0]. b := m.Bounds() var cr [nFilter][]uint8 - for i := 0; i < len(cr); i++ { + for i := range cr { cr[i] = make([]uint8, 1+bpp*b.Dx()) cr[i][0] = uint8(i) } @@ -312,78 +306,86 @@ func writeImage(w io.Writer, m image.Image, cb int) os.Error { 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 := image.GrayColorModel.Convert(m.At(x, y)).(image.GrayColor) - cr[0][x+1] = c.Y + cr[0][i] = c.Y + i++ } case cbTC8: // We have previously verified that the alpha value is fully opaque. cr0 := cr[0] - if rgba != nil { - yoff := y * rgba.Stride - xoff := 3*b.Min.X + 1 - for _, color := range rgba.Pix[yoff+b.Min.X : yoff+b.Max.X] { - cr0[xoff] = color.R - cr0[xoff+1] = color.G - cr0[xoff+2] = color.B - xoff += 3 + if rgba, _ := m.(*image.RGBA); rgba != nil { + j0 := (y - b.Min.Y) * rgba.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] + i += 3 } } else { for x := b.Min.X; x < b.Max.X; x++ { r, g, b, _ := m.At(x, y).RGBA() - cr0[3*x+1] = uint8(r >> 8) - cr0[3*x+2] = uint8(g >> 8) - cr0[3*x+3] = uint8(b >> 8) + cr0[i+0] = uint8(r >> 8) + cr0[i+1] = uint8(g >> 8) + cr0[i+2] = uint8(b >> 8) + i += 3 } } case cbP8: - rowOffset := y * paletted.Stride - copy(cr[0][b.Min.X+1:], paletted.Pix[rowOffset+b.Min.X:rowOffset+b.Max.X]) + paletted := m.(*image.Paletted) + offset := (y - b.Min.Y) * paletted.Stride + copy(cr[0][1:], paletted.Pix[offset:offset+b.Dx()]) 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 := image.NRGBAColorModel.Convert(m.At(x, y)).(image.NRGBAColor) - cr[0][4*x+1] = c.R - cr[0][4*x+2] = c.G - cr[0][4*x+3] = c.B - cr[0][4*x+4] = c.A + 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++ { c := image.Gray16ColorModel.Convert(m.At(x, y)).(image.Gray16Color) - cr[0][2*x+1] = uint8(c.Y >> 8) - cr[0][2*x+2] = uint8(c.Y) + cr[0][i+0] = uint8(c.Y >> 8) + cr[0][i+1] = uint8(c.Y) + i += 2 } case cbTC16: + // We have previously verified that the alpha value is fully opaque. for x := b.Min.X; x < b.Max.X; x++ { - // We have previously verified that the alpha value is fully opaque. r, g, b, _ := m.At(x, y).RGBA() - cr[0][6*x+1] = uint8(r >> 8) - cr[0][6*x+2] = uint8(r) - cr[0][6*x+3] = uint8(g >> 8) - cr[0][6*x+4] = uint8(g) - cr[0][6*x+5] = uint8(b >> 8) - cr[0][6*x+6] = uint8(b) + cr[0][i+0] = uint8(r >> 8) + cr[0][i+1] = uint8(r) + cr[0][i+2] = uint8(g >> 8) + cr[0][i+3] = uint8(g) + cr[0][i+4] = uint8(b >> 8) + cr[0][i+5] = uint8(b) + i += 6 } case cbTCA16: // 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 := image.NRGBA64ColorModel.Convert(m.At(x, y)).(image.NRGBA64Color) - cr[0][8*x+1] = uint8(c.R >> 8) - cr[0][8*x+2] = uint8(c.R) - cr[0][8*x+3] = uint8(c.G >> 8) - cr[0][8*x+4] = uint8(c.G) - cr[0][8*x+5] = uint8(c.B >> 8) - cr[0][8*x+6] = uint8(c.B) - cr[0][8*x+7] = uint8(c.A >> 8) - cr[0][8*x+8] = uint8(c.A) + cr[0][i+0] = uint8(c.R >> 8) + cr[0][i+1] = uint8(c.R) + cr[0][i+2] = uint8(c.G >> 8) + cr[0][i+3] = uint8(c.G) + cr[0][i+4] = uint8(c.B >> 8) + cr[0][i+5] = uint8(c.B) + cr[0][i+6] = uint8(c.A >> 8) + cr[0][i+7] = uint8(c.A) + i += 8 } } // Apply the filter. - f := filter(cr[0:nFilter], pr, bpp) + f := filter(&cr, pr, bpp) // Write the compressed bytes. _, err = zw.Write(cr[f]) diff --git a/libgo/go/image/png/writer_test.go b/libgo/go/image/png/writer_test.go index 6b054aa..1599791 100644 --- a/libgo/go/image/png/writer_test.go +++ b/libgo/go/image/png/writer_test.go @@ -5,9 +5,9 @@ package png import ( + "bytes" "fmt" "image" - "io" "io/ioutil" "os" "testing" @@ -15,21 +15,38 @@ import ( func diff(m0, m1 image.Image) os.Error { b0, b1 := m0.Bounds(), m1.Bounds() - if !b0.Eq(b1) { + if !b0.Size().Eq(b1.Size()) { return fmt.Errorf("dimensions differ: %v vs %v", b0, b1) } + dx := b1.Min.X - b0.Min.X + dy := b1.Min.Y - b0.Min.Y for y := b0.Min.Y; y < b0.Max.Y; y++ { for x := b0.Min.X; x < b0.Max.X; x++ { - r0, g0, b0, a0 := m0.At(x, y).RGBA() - r1, g1, b1, a1 := m1.At(x, y).RGBA() + c0 := m0.At(x, y) + c1 := m1.At(x+dx, y+dy) + r0, g0, b0, a0 := c0.RGBA() + r1, g1, b1, a1 := c1.RGBA() if r0 != r1 || g0 != g1 || b0 != b1 || a0 != a1 { - return fmt.Errorf("colors differ at (%d, %d): %v vs %v", x, y, m0.At(x, y), m1.At(x, y)) + return fmt.Errorf("colors differ at (%d, %d): %v vs %v", x, y, c0, c1) } } } return nil } +func encodeDecode(m image.Image) (image.Image, os.Error) { + b := bytes.NewBuffer(nil) + err := Encode(b, m) + if err != nil { + return nil, err + } + m, err = Decode(b) + if err != nil { + return nil, err + } + return m, nil +} + func TestWriter(t *testing.T) { // The filenames variable is declared in reader_test.go. names := filenames @@ -44,26 +61,16 @@ func TestWriter(t *testing.T) { t.Error(fn, err) continue } - // Read the image again, and push it through a pipe that encodes at the write end, and decodes at the read end. - pr, pw := io.Pipe() - defer pr.Close() - go func() { - defer pw.Close() - m1, err := readPng(qfn) - if err != nil { - t.Error(fn, err) - return - } - err = Encode(pw, m1) - if err != nil { - t.Error(fn, err) - return - } - }() - m2, err := Decode(pr) + // Read the image again, encode it, and decode it. + m1, err := readPng(qfn) if err != nil { t.Error(fn, err) - continue + return + } + m2, err := encodeDecode(m1) + if err != nil { + t.Error(fn, err) + return } // Compare the two. err = diff(m0, m2) @@ -74,6 +81,26 @@ func TestWriter(t *testing.T) { } } +func TestSubImage(t *testing.T) { + m0 := image.NewRGBA(256, 256) + for y := 0; y < 256; y++ { + for x := 0; x < 256; x++ { + m0.Set(x, y, image.RGBAColor{uint8(x), uint8(y), 0, 255}) + } + } + m0 = m0.SubImage(image.Rect(50, 30, 250, 130)).(*image.RGBA) + m1, err := encodeDecode(m0) + if err != nil { + t.Error(err) + return + } + err = diff(m0, m1) + if err != nil { + t.Error(err) + return + } +} + func BenchmarkEncodePaletted(b *testing.B) { b.StopTimer() img := image.NewPaletted(640, 480, diff --git a/libgo/go/image/testdata/video-001.5bpp.gif b/libgo/go/image/testdata/video-001.5bpp.gif Binary files differnew file mode 100644 index 0000000..ce53104 --- /dev/null +++ b/libgo/go/image/testdata/video-001.5bpp.gif diff --git a/libgo/go/image/testdata/video-001.interlaced.gif b/libgo/go/image/testdata/video-001.interlaced.gif Binary files differnew file mode 100644 index 0000000..590594e --- /dev/null +++ b/libgo/go/image/testdata/video-001.interlaced.gif diff --git a/libgo/go/image/testdata/video-005.gray.jpeg b/libgo/go/image/testdata/video-005.gray.jpeg Binary files differnew file mode 100644 index 0000000..f9d6e5c --- /dev/null +++ b/libgo/go/image/testdata/video-005.gray.jpeg diff --git a/libgo/go/image/testdata/video-005.gray.png b/libgo/go/image/testdata/video-005.gray.png Binary files differnew file mode 100644 index 0000000..0b0ee75 --- /dev/null +++ b/libgo/go/image/testdata/video-005.gray.png diff --git a/libgo/go/image/tiff/consts.go b/libgo/go/image/tiff/consts.go index 761ac9d..169ba27 100644 --- a/libgo/go/image/tiff/consts.go +++ b/libgo/go/image/tiff/consts.go @@ -54,6 +54,7 @@ const ( tPredictor = 317 tColorMap = 320 tExtraSamples = 338 + tSampleFormat = 339 ) // Compression types (defined in various places in the spec and supplements). diff --git a/libgo/go/image/tiff/reader.go b/libgo/go/image/tiff/reader.go index 40f659c..f565266 100644 --- a/libgo/go/image/tiff/reader.go +++ b/libgo/go/image/tiff/reader.go @@ -46,6 +46,11 @@ type decoder struct { mode imageMode features map[int][]uint palette []image.Color + + buf []byte + off int // Current offset in buf. + v uint32 // Buffer value for reading with arbitrary bit depths. + nbits uint // Remaining number of bits in v. } // firstVal returns the first uint of the features entry with the given tag, @@ -133,82 +138,112 @@ func (d *decoder) parseIFD(p []byte) os.Error { 0xffff, } } + case tSampleFormat: + // Page 27 of the spec: If the SampleFormat is present and + // the value is not 1 [= unsigned integer data], a Baseline + // TIFF reader that cannot handle the SampleFormat value + // must terminate the import process gracefully. + val, err := d.ifdUint(p) + if err != nil { + return err + } + for _, v := range val { + if v != 1 { + return UnsupportedError("sample format") + } + } } return nil } -// decode decodes the raw data of an image with 8 bits in each sample. -// It reads from p and writes the strip with ymin <= y < ymax into dst. -func (d *decoder) decode(dst image.Image, p []byte, ymin, ymax int) os.Error { +// readBits reads n bits from the internal buffer starting at the current offset. +func (d *decoder) readBits(n uint) uint32 { + for d.nbits < n { + d.v <<= 8 + d.v |= uint32(d.buf[d.off]) + d.off++ + d.nbits += 8 + } + d.nbits -= n + rv := d.v >> d.nbits + d.v &^= rv << d.nbits + return rv +} + +// flushBits discards the unread bits in the buffer used by readBits. +// It is used at the end of a line. +func (d *decoder) flushBits() { + d.v = 0 + d.nbits = 0 +} + +// decode decodes the raw data of an image. +// It reads from d.buf and writes the strip with ymin <= y < ymax into dst. +func (d *decoder) decode(dst image.Image, ymin, ymax int) os.Error { spp := len(d.features[tBitsPerSample]) // samples per pixel - off := 0 + d.off = 0 width := dst.Bounds().Dx() - if len(p) < spp*(ymax-ymin)*width { - return FormatError("short data strip") - } - // Apply horizontal predictor if necessary. // In this case, p contains the color difference to the preceding pixel. // See page 64-65 of the spec. - if d.firstVal(tPredictor) == prHorizontal { + if d.firstVal(tPredictor) == prHorizontal && d.firstVal(tBitsPerSample) == 8 { for y := ymin; y < ymax; y++ { - off += spp + d.off += spp for x := 0; x < (width-1)*spp; x++ { - p[off] += p[off-spp] - off++ + d.buf[d.off] += d.buf[d.off-spp] + d.off++ } } - off = 0 + d.off = 0 } switch d.mode { - case mGray: - img := dst.(*image.Gray) - for y := ymin; y < ymax; y++ { - for x := img.Rect.Min.X; x < img.Rect.Max.X; x++ { - img.Set(x, y, image.GrayColor{p[off]}) - off += spp - } - } - case mGrayInvert: + case mGray, mGrayInvert: img := dst.(*image.Gray) + bpp := d.firstVal(tBitsPerSample) + max := uint32((1 << bpp) - 1) for y := ymin; y < ymax; y++ { for x := img.Rect.Min.X; x < img.Rect.Max.X; x++ { - img.Set(x, y, image.GrayColor{0xff - p[off]}) - off += spp + v := uint8(d.readBits(bpp) * 0xff / max) + if d.mode == mGrayInvert { + v = 0xff - v + } + img.SetGray(x, y, image.GrayColor{v}) } + d.flushBits() } case mPaletted: img := dst.(*image.Paletted) + bpp := d.firstVal(tBitsPerSample) for y := ymin; y < ymax; y++ { for x := img.Rect.Min.X; x < img.Rect.Max.X; x++ { - img.SetColorIndex(x, y, p[off]) - off += spp + img.SetColorIndex(x, y, uint8(d.readBits(bpp))) } + d.flushBits() } case mRGB: img := dst.(*image.RGBA) for y := ymin; y < ymax; y++ { for x := img.Rect.Min.X; x < img.Rect.Max.X; x++ { - img.Set(x, y, image.RGBAColor{p[off], p[off+1], p[off+2], 0xff}) - off += spp + img.SetRGBA(x, y, image.RGBAColor{d.buf[d.off], d.buf[d.off+1], d.buf[d.off+2], 0xff}) + d.off += spp } } case mNRGBA: img := dst.(*image.NRGBA) for y := ymin; y < ymax; y++ { for x := img.Rect.Min.X; x < img.Rect.Max.X; x++ { - img.Set(x, y, image.NRGBAColor{p[off], p[off+1], p[off+2], p[off+3]}) - off += spp + img.SetNRGBA(x, y, image.NRGBAColor{d.buf[d.off], d.buf[d.off+1], d.buf[d.off+2], d.buf[d.off+3]}) + d.off += spp } } case mRGBA: img := dst.(*image.RGBA) for y := ymin; y < ymax; y++ { for x := img.Rect.Min.X; x < img.Rect.Max.X; x++ { - img.Set(x, y, image.RGBAColor{p[off], p[off+1], p[off+2], p[off+3]}) - off += spp + img.SetRGBA(x, y, image.RGBAColor{d.buf[d.off], d.buf[d.off+1], d.buf[d.off+2], d.buf[d.off+3]}) + d.off += spp } } } @@ -258,9 +293,18 @@ func newDecoder(r io.Reader) (*decoder, os.Error) { d.config.Width = int(d.firstVal(tImageWidth)) d.config.Height = int(d.firstVal(tImageLength)) + if _, ok := d.features[tBitsPerSample]; !ok { + return nil, FormatError("BitsPerSample tag missing") + } + // Determine the image mode. switch d.firstVal(tPhotometricInterpretation) { case pRGB: + for _, b := range d.features[tBitsPerSample] { + if b != 8 { + return nil, UnsupportedError("non-8-bit RGB image") + } + } d.config.ColorModel = image.RGBAColorModel // RGB images normally have 3 samples per pixel. // If there are more, ExtraSamples (p. 31-32 of the spec) @@ -295,15 +339,6 @@ func newDecoder(r io.Reader) (*decoder, os.Error) { return nil, UnsupportedError("color model") } - if _, ok := d.features[tBitsPerSample]; !ok { - return nil, FormatError("BitsPerSample tag missing") - } - for _, b := range d.features[tBitsPerSample] { - if b != 8 { - return nil, UnsupportedError("not an 8-bit image") - } - } - return d, nil } @@ -327,6 +362,10 @@ func Decode(r io.Reader) (img image.Image, err os.Error) { // Check if we have the right number of strips, offsets and counts. rps := int(d.firstVal(tRowsPerStrip)) + if rps == 0 { + // Assume only one strip. + rps = d.config.Height + } numStrips := (d.config.Height + rps - 1) / rps if rps == 0 || len(d.features[tStripOffsets]) < numStrips || len(d.features[tStripByteCounts]) < numStrips { return nil, FormatError("inconsistent header") @@ -343,7 +382,6 @@ func Decode(r io.Reader) (img image.Image, err os.Error) { img = image.NewRGBA(d.config.Width, d.config.Height) } - var p []byte for i := 0; i < numStrips; i++ { ymin := i * rps // The last strip may be shorter. @@ -355,18 +393,18 @@ func Decode(r io.Reader) (img image.Image, err os.Error) { switch d.firstVal(tCompression) { case cNone: // TODO(bsiegert): Avoid copy if r is a tiff.buffer. - p = make([]byte, 0, n) - _, err = d.r.ReadAt(p, offset) + d.buf = make([]byte, n) + _, err = d.r.ReadAt(d.buf, offset) case cLZW: r := lzw.NewReader(io.NewSectionReader(d.r, offset, n), lzw.MSB, 8) - p, err = ioutil.ReadAll(r) + d.buf, err = ioutil.ReadAll(r) r.Close() case cDeflate, cDeflateOld: r, err := zlib.NewReader(io.NewSectionReader(d.r, offset, n)) if err != nil { return nil, err } - p, err = ioutil.ReadAll(r) + d.buf, err = ioutil.ReadAll(r) r.Close() default: err = UnsupportedError("compression") @@ -374,7 +412,7 @@ func Decode(r io.Reader) (img image.Image, err os.Error) { if err != nil { return } - err = d.decode(img, p, ymin, ymin+rps) + err = d.decode(img, ymin, ymin+rps) } return } diff --git a/libgo/go/image/tiff/reader_test.go b/libgo/go/image/tiff/reader_test.go new file mode 100644 index 0000000..f2122c4 --- /dev/null +++ b/libgo/go/image/tiff/reader_test.go @@ -0,0 +1,25 @@ +// Copyright 2011 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 tiff + +import ( + "os" + "testing" +) + +// TestNoRPS tries to decode an image that has no RowsPerStrip tag. +// The tag is mandatory according to the spec but some software omits +// it in the case of a single strip. +func TestNoRPS(t *testing.T) { + f, err := os.Open("testdata/no_rps.tiff") + if err != nil { + t.Fatal(err) + } + defer f.Close() + _, err = Decode(f) + if err != nil { + t.Fatal(err) + } +} diff --git a/libgo/go/image/tiff/testdata/no_rps.tiff b/libgo/go/image/tiff/testdata/no_rps.tiff Binary files differnew file mode 100644 index 0000000..3280cf8 --- /dev/null +++ b/libgo/go/image/tiff/testdata/no_rps.tiff diff --git a/libgo/go/image/ycbcr/ycbcr.go b/libgo/go/image/ycbcr/ycbcr.go index cda4599..f2de3d6 100644 --- a/libgo/go/image/ycbcr/ycbcr.go +++ b/libgo/go/image/ycbcr/ycbcr.go @@ -142,7 +142,7 @@ func (p *YCbCr) Bounds() image.Rectangle { } func (p *YCbCr) At(x, y int) image.Color { - if !p.Rect.Contains(image.Point{x, y}) { + if !(image.Point{x, y}.In(p.Rect)) { return YCbCrColor{} } switch p.SubsampleRatio { @@ -169,6 +169,15 @@ func (p *YCbCr) At(x, y int) image.Color { } } +// SubImage returns an image representing the portion of the image p visible +// through r. The returned value shares pixels with the original image. +func (p *YCbCr) SubImage(r image.Rectangle) image.Image { + q := new(YCbCr) + *q = *p + q.Rect = q.Rect.Intersect(r) + return q +} + func (p *YCbCr) Opaque() bool { return true } |