summaryrefslogtreecommitdiffstats
path: root/vendor/golang.org/x/image/webp
diff options
context:
space:
mode:
authorChristopher Speller <crspeller@gmail.com>2016-05-12 23:56:07 -0400
committerChristopher Speller <crspeller@gmail.com>2016-05-12 23:56:07 -0400
commit38ee83e45b4de7edf89bf9f0ef629eb4c6ad0fa8 (patch)
treea4fde09672192b97d453ad605b030bd5a10c5a45 /vendor/golang.org/x/image/webp
parent84d2482ddbff9564c9ad75b2d30af66e3ddfd44d (diff)
downloadchat-38ee83e45b4de7edf89bf9f0ef629eb4c6ad0fa8.tar.gz
chat-38ee83e45b4de7edf89bf9f0ef629eb4c6ad0fa8.tar.bz2
chat-38ee83e45b4de7edf89bf9f0ef629eb4c6ad0fa8.zip
Moving to glide
Diffstat (limited to 'vendor/golang.org/x/image/webp')
-rw-r--r--vendor/golang.org/x/image/webp/decode.go274
-rw-r--r--vendor/golang.org/x/image/webp/decode_test.go294
-rw-r--r--vendor/golang.org/x/image/webp/nycbcra/nycbcra.go194
3 files changed, 762 insertions, 0 deletions
diff --git a/vendor/golang.org/x/image/webp/decode.go b/vendor/golang.org/x/image/webp/decode.go
new file mode 100644
index 000000000..134307e41
--- /dev/null
+++ b/vendor/golang.org/x/image/webp/decode.go
@@ -0,0 +1,274 @@
+// 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 webp implements a decoder for WEBP images.
+//
+// WEBP is defined at:
+// https://developers.google.com/speed/webp/docs/riff_container
+package webp // import "golang.org/x/image/webp"
+
+import (
+ "bytes"
+ "errors"
+ "image"
+ "image/color"
+ "io"
+
+ "golang.org/x/image/riff"
+ "golang.org/x/image/vp8"
+ "golang.org/x/image/vp8l"
+)
+
+var errInvalidFormat = errors.New("webp: invalid format")
+
+var (
+ fccALPH = riff.FourCC{'A', 'L', 'P', 'H'}
+ fccVP8 = riff.FourCC{'V', 'P', '8', ' '}
+ fccVP8L = riff.FourCC{'V', 'P', '8', 'L'}
+ fccVP8X = riff.FourCC{'V', 'P', '8', 'X'}
+ fccWEBP = riff.FourCC{'W', 'E', 'B', 'P'}
+)
+
+func decode(r io.Reader, configOnly bool) (image.Image, image.Config, error) {
+ formType, riffReader, err := riff.NewReader(r)
+ if err != nil {
+ return nil, image.Config{}, err
+ }
+ if formType != fccWEBP {
+ return nil, image.Config{}, errInvalidFormat
+ }
+
+ var (
+ alpha []byte
+ alphaStride int
+ wantAlpha bool
+ widthMinusOne uint32
+ heightMinusOne uint32
+ buf [10]byte
+ )
+ for {
+ chunkID, chunkLen, chunkData, err := riffReader.Next()
+ if err == io.EOF {
+ err = errInvalidFormat
+ }
+ if err != nil {
+ return nil, image.Config{}, err
+ }
+
+ switch chunkID {
+ case fccALPH:
+ if !wantAlpha {
+ return nil, image.Config{}, errInvalidFormat
+ }
+ wantAlpha = false
+ // Read the Pre-processing | Filter | Compression byte.
+ if _, err := io.ReadFull(chunkData, buf[:1]); err != nil {
+ if err == io.EOF {
+ err = errInvalidFormat
+ }
+ return nil, image.Config{}, err
+ }
+ alpha, alphaStride, err = readAlpha(chunkData, widthMinusOne, heightMinusOne, buf[0]&0x03)
+ if err != nil {
+ return nil, image.Config{}, err
+ }
+ unfilterAlpha(alpha, alphaStride, (buf[0]>>2)&0x03)
+
+ case fccVP8:
+ if wantAlpha || int32(chunkLen) < 0 {
+ return nil, image.Config{}, errInvalidFormat
+ }
+ d := vp8.NewDecoder()
+ d.Init(chunkData, int(chunkLen))
+ fh, err := d.DecodeFrameHeader()
+ if err != nil {
+ return nil, image.Config{}, err
+ }
+ if configOnly {
+ return nil, image.Config{
+ ColorModel: color.YCbCrModel,
+ Width: fh.Width,
+ Height: fh.Height,
+ }, nil
+ }
+ m, err := d.DecodeFrame()
+ if err != nil {
+ return nil, image.Config{}, err
+ }
+ if alpha != nil {
+ return &image.NYCbCrA{
+ YCbCr: *m,
+ A: alpha,
+ AStride: alphaStride,
+ }, image.Config{}, nil
+ }
+ return m, image.Config{}, nil
+
+ case fccVP8L:
+ if wantAlpha || alpha != nil {
+ return nil, image.Config{}, errInvalidFormat
+ }
+ if configOnly {
+ c, err := vp8l.DecodeConfig(chunkData)
+ return nil, c, err
+ }
+ m, err := vp8l.Decode(chunkData)
+ return m, image.Config{}, err
+
+ case fccVP8X:
+ if chunkLen != 10 {
+ return nil, image.Config{}, errInvalidFormat
+ }
+ if _, err := io.ReadFull(chunkData, buf[:10]); err != nil {
+ return nil, image.Config{}, err
+ }
+ const (
+ animationBit = 1 << 1
+ xmpMetadataBit = 1 << 2
+ exifMetadataBit = 1 << 3
+ alphaBit = 1 << 4
+ iccProfileBit = 1 << 5
+ )
+ if buf[0] != alphaBit {
+ return nil, image.Config{}, errors.New("webp: non-Alpha VP8X is not implemented")
+ }
+ widthMinusOne = uint32(buf[4]) | uint32(buf[5])<<8 | uint32(buf[6])<<16
+ heightMinusOne = uint32(buf[7]) | uint32(buf[8])<<8 | uint32(buf[9])<<16
+ if configOnly {
+ return nil, image.Config{
+ ColorModel: color.NYCbCrAModel,
+ Width: int(widthMinusOne) + 1,
+ Height: int(heightMinusOne) + 1,
+ }, nil
+ }
+ wantAlpha = true
+
+ default:
+ return nil, image.Config{}, errInvalidFormat
+ }
+ }
+}
+
+func readAlpha(chunkData io.Reader, widthMinusOne, heightMinusOne uint32, compression byte) (
+ alpha []byte, alphaStride int, err error) {
+
+ switch compression {
+ case 0:
+ w := int(widthMinusOne) + 1
+ h := int(heightMinusOne) + 1
+ alpha = make([]byte, w*h)
+ if _, err := io.ReadFull(chunkData, alpha); err != nil {
+ return nil, 0, err
+ }
+ return alpha, w, nil
+
+ case 1:
+ // Read the VP8L-compressed alpha values. First, synthesize a 5-byte VP8L header:
+ // a 1-byte magic number, a 14-bit widthMinusOne, a 14-bit heightMinusOne,
+ // a 1-bit (ignored, zero) alphaIsUsed and a 3-bit (zero) version.
+ // TODO(nigeltao): be more efficient than decoding an *image.NRGBA just to
+ // extract the green values to a separately allocated []byte. Fixing this
+ // will require changes to the vp8l package's API.
+ if widthMinusOne > 0x3fff || heightMinusOne > 0x3fff {
+ return nil, 0, errors.New("webp: invalid format")
+ }
+ alphaImage, err := vp8l.Decode(io.MultiReader(
+ bytes.NewReader([]byte{
+ 0x2f, // VP8L magic number.
+ uint8(widthMinusOne),
+ uint8(widthMinusOne>>8) | uint8(heightMinusOne<<6),
+ uint8(heightMinusOne >> 2),
+ uint8(heightMinusOne >> 10),
+ }),
+ chunkData,
+ ))
+ if err != nil {
+ return nil, 0, err
+ }
+ // The green values of the inner NRGBA image are the alpha values of the
+ // outer NYCbCrA image.
+ pix := alphaImage.(*image.NRGBA).Pix
+ alpha = make([]byte, len(pix)/4)
+ for i := range alpha {
+ alpha[i] = pix[4*i+1]
+ }
+ return alpha, int(widthMinusOne) + 1, nil
+ }
+ return nil, 0, errInvalidFormat
+}
+
+func unfilterAlpha(alpha []byte, alphaStride int, filter byte) {
+ if len(alpha) == 0 || alphaStride == 0 {
+ return
+ }
+ switch filter {
+ case 1: // Horizontal filter.
+ for i := 1; i < alphaStride; i++ {
+ alpha[i] += alpha[i-1]
+ }
+ for i := alphaStride; i < len(alpha); i += alphaStride {
+ // The first column is equivalent to the vertical filter.
+ alpha[i] += alpha[i-alphaStride]
+
+ for j := 1; j < alphaStride; j++ {
+ alpha[i+j] += alpha[i+j-1]
+ }
+ }
+
+ case 2: // Vertical filter.
+ // The first row is equivalent to the horizontal filter.
+ for i := 1; i < alphaStride; i++ {
+ alpha[i] += alpha[i-1]
+ }
+
+ for i := alphaStride; i < len(alpha); i++ {
+ alpha[i] += alpha[i-alphaStride]
+ }
+
+ case 3: // Gradient filter.
+ // The first row is equivalent to the horizontal filter.
+ for i := 1; i < alphaStride; i++ {
+ alpha[i] += alpha[i-1]
+ }
+
+ for i := alphaStride; i < len(alpha); i += alphaStride {
+ // The first column is equivalent to the vertical filter.
+ alpha[i] += alpha[i-alphaStride]
+
+ // The interior is predicted on the three top/left pixels.
+ for j := 1; j < alphaStride; j++ {
+ c := int(alpha[i+j-alphaStride-1])
+ b := int(alpha[i+j-alphaStride])
+ a := int(alpha[i+j-1])
+ x := a + b - c
+ if x < 0 {
+ x = 0
+ } else if x > 255 {
+ x = 255
+ }
+ alpha[i+j] += uint8(x)
+ }
+ }
+ }
+}
+
+// Decode reads a WEBP image from r and returns it as an image.Image.
+func Decode(r io.Reader) (image.Image, error) {
+ m, _, err := decode(r, false)
+ if err != nil {
+ return nil, err
+ }
+ return m, err
+}
+
+// DecodeConfig returns the color model and dimensions of a WEBP image without
+// decoding the entire image.
+func DecodeConfig(r io.Reader) (image.Config, error) {
+ _, c, err := decode(r, true)
+ return c, err
+}
+
+func init() {
+ image.RegisterFormat("webp", "RIFF????WEBPVP8", Decode, DecodeConfig)
+}
diff --git a/vendor/golang.org/x/image/webp/decode_test.go b/vendor/golang.org/x/image/webp/decode_test.go
new file mode 100644
index 000000000..ad65b1088
--- /dev/null
+++ b/vendor/golang.org/x/image/webp/decode_test.go
@@ -0,0 +1,294 @@
+// Copyright 2014 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 webp
+
+import (
+ "bytes"
+ "fmt"
+ "image"
+ "image/png"
+ "io/ioutil"
+ "os"
+ "strings"
+ "testing"
+)
+
+// hex is like fmt.Sprintf("% x", x) but also inserts dots every 16 bytes, to
+// delineate VP8 macroblock boundaries.
+func hex(x []byte) string {
+ buf := new(bytes.Buffer)
+ for len(x) > 0 {
+ n := len(x)
+ if n > 16 {
+ n = 16
+ }
+ fmt.Fprintf(buf, " . % x", x[:n])
+ x = x[n:]
+ }
+ return buf.String()
+}
+
+func testDecodeLossy(t *testing.T, tc string, withAlpha bool) {
+ webpFilename := "../testdata/" + tc + ".lossy.webp"
+ pngFilename := webpFilename + ".ycbcr.png"
+ if withAlpha {
+ webpFilename = "../testdata/" + tc + ".lossy-with-alpha.webp"
+ pngFilename = webpFilename + ".nycbcra.png"
+ }
+
+ f0, err := os.Open(webpFilename)
+ if err != nil {
+ t.Errorf("%s: Open WEBP: %v", tc, err)
+ return
+ }
+ defer f0.Close()
+ img0, err := Decode(f0)
+ if err != nil {
+ t.Errorf("%s: Decode WEBP: %v", tc, err)
+ return
+ }
+
+ var (
+ m0 *image.YCbCr
+ a0 *image.NYCbCrA
+ ok bool
+ )
+ if withAlpha {
+ a0, ok = img0.(*image.NYCbCrA)
+ if ok {
+ m0 = &a0.YCbCr
+ }
+ } else {
+ m0, ok = img0.(*image.YCbCr)
+ }
+ if !ok || m0.SubsampleRatio != image.YCbCrSubsampleRatio420 {
+ t.Errorf("%s: decoded WEBP image is not a 4:2:0 YCbCr or 4:2:0 NYCbCrA", tc)
+ return
+ }
+ // w2 and h2 are the half-width and half-height, rounded up.
+ w, h := m0.Bounds().Dx(), m0.Bounds().Dy()
+ w2, h2 := int((w+1)/2), int((h+1)/2)
+
+ f1, err := os.Open(pngFilename)
+ if err != nil {
+ t.Errorf("%s: Open PNG: %v", tc, err)
+ return
+ }
+ defer f1.Close()
+ img1, err := png.Decode(f1)
+ if err != nil {
+ t.Errorf("%s: Open PNG: %v", tc, err)
+ return
+ }
+
+ // The split-into-YCbCr-planes golden image is a 2*w2 wide and h+h2 high
+ // (or 2*h+h2 high, if with Alpha) gray image arranged in IMC4 format:
+ // YYYY
+ // YYYY
+ // BBRR
+ // AAAA
+ // See http://www.fourcc.org/yuv.php#IMC4
+ pngW, pngH := 2*w2, h+h2
+ if withAlpha {
+ pngH += h
+ }
+ if got, want := img1.Bounds(), image.Rect(0, 0, pngW, pngH); got != want {
+ t.Errorf("%s: bounds0: got %v, want %v", tc, got, want)
+ return
+ }
+ m1, ok := img1.(*image.Gray)
+ if !ok {
+ t.Errorf("%s: decoded PNG image is not a Gray", tc)
+ return
+ }
+
+ type plane struct {
+ name string
+ m0Pix []uint8
+ m0Stride int
+ m1Rect image.Rectangle
+ }
+ planes := []plane{
+ {"Y", m0.Y, m0.YStride, image.Rect(0, 0, w, h)},
+ {"Cb", m0.Cb, m0.CStride, image.Rect(0*w2, h, 1*w2, h+h2)},
+ {"Cr", m0.Cr, m0.CStride, image.Rect(1*w2, h, 2*w2, h+h2)},
+ }
+ if withAlpha {
+ planes = append(planes, plane{
+ "A", a0.A, a0.AStride, image.Rect(0, h+h2, w, 2*h+h2),
+ })
+ }
+
+ for _, plane := range planes {
+ dx := plane.m1Rect.Dx()
+ nDiff, diff := 0, make([]byte, dx)
+ for j, y := 0, plane.m1Rect.Min.Y; y < plane.m1Rect.Max.Y; j, y = j+1, y+1 {
+ got := plane.m0Pix[j*plane.m0Stride:][:dx]
+ want := m1.Pix[y*m1.Stride+plane.m1Rect.Min.X:][:dx]
+ if bytes.Equal(got, want) {
+ continue
+ }
+ nDiff++
+ if nDiff > 10 {
+ t.Errorf("%s: %s plane: more rows differ", tc, plane.name)
+ break
+ }
+ for i := range got {
+ diff[i] = got[i] - want[i]
+ }
+ t.Errorf("%s: %s plane: m0 row %d, m1 row %d\ngot %s\nwant%s\ndiff%s",
+ tc, plane.name, j, y, hex(got), hex(want), hex(diff))
+ }
+ }
+}
+
+func TestDecodeVP8(t *testing.T) {
+ testCases := []string{
+ "blue-purple-pink",
+ "blue-purple-pink-large.no-filter",
+ "blue-purple-pink-large.simple-filter",
+ "blue-purple-pink-large.normal-filter",
+ "video-001",
+ "yellow_rose",
+ }
+
+ for _, tc := range testCases {
+ testDecodeLossy(t, tc, false)
+ }
+}
+
+func TestDecodeVP8XAlpha(t *testing.T) {
+ testCases := []string{
+ "yellow_rose",
+ }
+
+ for _, tc := range testCases {
+ testDecodeLossy(t, tc, true)
+ }
+}
+
+func TestDecodeVP8L(t *testing.T) {
+ testCases := []string{
+ "blue-purple-pink",
+ "blue-purple-pink-large",
+ "gopher-doc.1bpp",
+ "gopher-doc.2bpp",
+ "gopher-doc.4bpp",
+ "gopher-doc.8bpp",
+ "tux",
+ "yellow_rose",
+ }
+
+loop:
+ for _, tc := range testCases {
+ f0, err := os.Open("../testdata/" + tc + ".lossless.webp")
+ if err != nil {
+ t.Errorf("%s: Open WEBP: %v", tc, err)
+ continue
+ }
+ defer f0.Close()
+ img0, err := Decode(f0)
+ if err != nil {
+ t.Errorf("%s: Decode WEBP: %v", tc, err)
+ continue
+ }
+ m0, ok := img0.(*image.NRGBA)
+ if !ok {
+ t.Errorf("%s: WEBP image is %T, want *image.NRGBA", tc, img0)
+ continue
+ }
+
+ f1, err := os.Open("../testdata/" + tc + ".png")
+ if err != nil {
+ t.Errorf("%s: Open PNG: %v", tc, err)
+ continue
+ }
+ defer f1.Close()
+ img1, err := png.Decode(f1)
+ if err != nil {
+ t.Errorf("%s: Decode PNG: %v", tc, err)
+ continue
+ }
+ m1, ok := img1.(*image.NRGBA)
+ if !ok {
+ rgba1, ok := img1.(*image.RGBA)
+ if !ok {
+ t.Fatalf("%s: PNG image is %T, want *image.NRGBA", tc, img1)
+ continue
+ }
+ if !rgba1.Opaque() {
+ t.Fatalf("%s: PNG image is non-opaque *image.RGBA, want *image.NRGBA", tc)
+ continue
+ }
+ // The image is fully opaque, so we can re-interpret the RGBA pixels
+ // as NRGBA pixels.
+ m1 = &image.NRGBA{
+ Pix: rgba1.Pix,
+ Stride: rgba1.Stride,
+ Rect: rgba1.Rect,
+ }
+ }
+
+ b0, b1 := m0.Bounds(), m1.Bounds()
+ if b0 != b1 {
+ t.Errorf("%s: bounds: got %v, want %v", tc, b0, b1)
+ continue
+ }
+ for i := range m0.Pix {
+ if m0.Pix[i] != m1.Pix[i] {
+ y := i / m0.Stride
+ x := (i - y*m0.Stride) / 4
+ i = 4 * (y*m0.Stride + x)
+ t.Errorf("%s: at (%d, %d):\ngot %02x %02x %02x %02x\nwant %02x %02x %02x %02x",
+ tc, x, y,
+ m0.Pix[i+0], m0.Pix[i+1], m0.Pix[i+2], m0.Pix[i+3],
+ m1.Pix[i+0], m1.Pix[i+1], m1.Pix[i+2], m1.Pix[i+3],
+ )
+ continue loop
+ }
+ }
+ }
+}
+
+// TestDecodePartitionTooLarge tests that decoding a malformed WEBP image
+// doesn't try to allocate an unreasonable amount of memory. This WEBP image
+// claims a RIFF chunk length of 0x12345678 bytes (291 MiB) compressed,
+// independent of the actual image size (0 pixels wide * 0 pixels high).
+//
+// This is based on golang.org/issue/10790.
+func TestDecodePartitionTooLarge(t *testing.T) {
+ data := "RIFF\xff\xff\xff\x7fWEBPVP8 " +
+ "\x78\x56\x34\x12" + // RIFF chunk length.
+ "\xbd\x01\x00\x14\x00\x00\xb2\x34\x0a\x9d\x01\x2a\x96\x00\x67\x00"
+ _, err := Decode(strings.NewReader(data))
+ if err == nil {
+ t.Fatal("got nil error, want non-nil")
+ }
+ if got, want := err.Error(), "too much data"; !strings.Contains(got, want) {
+ t.Fatalf("got error %q, want something containing %q", got, want)
+ }
+}
+
+func benchmarkDecode(b *testing.B, filename string) {
+ data, err := ioutil.ReadFile("../testdata/blue-purple-pink-large." + filename + ".webp")
+ if err != nil {
+ b.Fatal(err)
+ }
+ s := string(data)
+ cfg, err := DecodeConfig(strings.NewReader(s))
+ if err != nil {
+ b.Fatal(err)
+ }
+ b.SetBytes(int64(cfg.Width * cfg.Height * 4))
+ b.ResetTimer()
+ for i := 0; i < b.N; i++ {
+ Decode(strings.NewReader(s))
+ }
+}
+
+func BenchmarkDecodeVP8NoFilter(b *testing.B) { benchmarkDecode(b, "no-filter.lossy") }
+func BenchmarkDecodeVP8SimpleFilter(b *testing.B) { benchmarkDecode(b, "simple-filter.lossy") }
+func BenchmarkDecodeVP8NormalFilter(b *testing.B) { benchmarkDecode(b, "normal-filter.lossy") }
+func BenchmarkDecodeVP8L(b *testing.B) { benchmarkDecode(b, "lossless") }
diff --git a/vendor/golang.org/x/image/webp/nycbcra/nycbcra.go b/vendor/golang.org/x/image/webp/nycbcra/nycbcra.go
new file mode 100644
index 000000000..101c41fcf
--- /dev/null
+++ b/vendor/golang.org/x/image/webp/nycbcra/nycbcra.go
@@ -0,0 +1,194 @@
+// Copyright 2014 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 nycbcra provides non-alpha-premultiplied Y'CbCr-with-alpha image and
+// color types.
+//
+// Deprecated: as of Go 1.6. Use the standard image and image/color packages
+// instead.
+package nycbcra // import "golang.org/x/image/webp/nycbcra"
+
+import (
+ "image"
+ "image/color"
+)
+
+func init() {
+ println("The golang.org/x/image/webp/nycbcra package is deprecated, as of Go 1.6. " +
+ "Use the standard image and image/color packages instead.")
+}
+
+// TODO: move this to the standard image and image/color packages, so that the
+// image/draw package can have fast-path code. Moving would rename:
+// nycbcra.Color to color.NYCbCrA
+// nycbcra.ColorModel to color.NYCbCrAModel
+// nycbcra.Image to image.NYCbCrA
+
+// Color represents a non-alpha-premultiplied Y'CbCr-with-alpha color, having
+// 8 bits each for one luma, two chroma and one alpha component.
+type Color struct {
+ color.YCbCr
+ A uint8
+}
+
+func (c Color) RGBA() (r, g, b, a uint32) {
+ r8, g8, b8 := color.YCbCrToRGB(c.Y, c.Cb, c.Cr)
+ a = uint32(c.A) * 0x101
+ r = uint32(r8) * 0x101 * a / 0xffff
+ g = uint32(g8) * 0x101 * a / 0xffff
+ b = uint32(b8) * 0x101 * a / 0xffff
+ return
+}
+
+// ColorModel is the Model for non-alpha-premultiplied Y'CbCr-with-alpha colors.
+var ColorModel color.Model = color.ModelFunc(nYCbCrAModel)
+
+func nYCbCrAModel(c color.Color) color.Color {
+ switch c := c.(type) {
+ case Color:
+ return c
+ case color.YCbCr:
+ return Color{c, 0xff}
+ }
+ r, g, b, a := c.RGBA()
+
+ // Convert from alpha-premultiplied to non-alpha-premultiplied.
+ if a != 0 {
+ r = (r * 0xffff) / a
+ g = (g * 0xffff) / a
+ b = (b * 0xffff) / a
+ }
+
+ y, u, v := color.RGBToYCbCr(uint8(r>>8), uint8(g>>8), uint8(b>>8))
+ return Color{color.YCbCr{Y: y, Cb: u, Cr: v}, uint8(a >> 8)}
+}
+
+// Image is an in-memory image of non-alpha-premultiplied Y'CbCr-with-alpha
+// colors. A and AStride are analogous to the Y and YStride fields of the
+// embedded YCbCr.
+type Image struct {
+ image.YCbCr
+ A []uint8
+ AStride int
+}
+
+func (p *Image) ColorModel() color.Model {
+ return ColorModel
+}
+
+func (p *Image) At(x, y int) color.Color {
+ return p.NYCbCrAAt(x, y)
+}
+
+func (p *Image) NYCbCrAAt(x, y int) Color {
+ if !(image.Point{X: x, Y: y}.In(p.Rect)) {
+ return Color{}
+ }
+ yi := p.YOffset(x, y)
+ ci := p.COffset(x, y)
+ ai := p.AOffset(x, y)
+ return Color{
+ color.YCbCr{
+ Y: p.Y[yi],
+ Cb: p.Cb[ci],
+ Cr: p.Cr[ci],
+ },
+ p.A[ai],
+ }
+}
+
+// AOffset returns the index of the first element of A that corresponds to
+// the pixel at (x, y).
+func (p *Image) AOffset(x, y int) int {
+ return (y-p.Rect.Min.Y)*p.AStride + (x - p.Rect.Min.X)
+}
+
+// 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 *Image) SubImage(r image.Rectangle) image.Image {
+ // TODO: share code with image.NewYCbCr when this type moves into the
+ // standard image package.
+ 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 &Image{
+ YCbCr: image.YCbCr{
+ SubsampleRatio: p.SubsampleRatio,
+ },
+ }
+ }
+ yi := p.YOffset(r.Min.X, r.Min.Y)
+ ci := p.COffset(r.Min.X, r.Min.Y)
+ ai := p.AOffset(r.Min.X, r.Min.Y)
+ return &Image{
+ YCbCr: image.YCbCr{
+ Y: p.Y[yi:],
+ Cb: p.Cb[ci:],
+ Cr: p.Cr[ci:],
+ SubsampleRatio: p.SubsampleRatio,
+ YStride: p.YStride,
+ CStride: p.CStride,
+ Rect: r,
+ },
+ A: p.A[ai:],
+ AStride: p.AStride,
+ }
+}
+
+// Opaque scans the entire image and reports whether it is fully opaque.
+func (p *Image) Opaque() bool {
+ if p.Rect.Empty() {
+ return true
+ }
+ i0, i1 := 0, p.Rect.Dx()
+ for y := p.Rect.Min.Y; y < p.Rect.Max.Y; y++ {
+ for _, a := range p.A[i0:i1] {
+ if a != 0xff {
+ return false
+ }
+ }
+ i0 += p.AStride
+ i1 += p.AStride
+ }
+ return true
+}
+
+// New returns a new Image with the given bounds and subsample ratio.
+func New(r image.Rectangle, subsampleRatio image.YCbCrSubsampleRatio) *Image {
+ // TODO: share code with image.NewYCbCr when this type moves into the
+ // standard image package.
+ w, h, cw, ch := r.Dx(), r.Dy(), 0, 0
+ switch subsampleRatio {
+ case image.YCbCrSubsampleRatio422:
+ cw = (r.Max.X+1)/2 - r.Min.X/2
+ ch = h
+ case image.YCbCrSubsampleRatio420:
+ cw = (r.Max.X+1)/2 - r.Min.X/2
+ ch = (r.Max.Y+1)/2 - r.Min.Y/2
+ case image.YCbCrSubsampleRatio440:
+ cw = w
+ ch = (r.Max.Y+1)/2 - r.Min.Y/2
+ default:
+ // Default to 4:4:4 subsampling.
+ cw = w
+ ch = h
+ }
+ b := make([]byte, 2*w*h+2*cw*ch)
+ // TODO: use s[i:j:k] notation to set the cap.
+ return &Image{
+ YCbCr: image.YCbCr{
+ Y: b[:w*h],
+ Cb: b[w*h+0*cw*ch : w*h+1*cw*ch],
+ Cr: b[w*h+1*cw*ch : w*h+2*cw*ch],
+ SubsampleRatio: subsampleRatio,
+ YStride: w,
+ CStride: cw,
+ Rect: r,
+ },
+ A: b[w*h+2*cw*ch:],
+ AStride: w,
+ }
+}