1// Copyright 2011 The Go Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style
3// license that can be found in the LICENSE file.
4
5package webp
6
7import (
8 "bytes"
9 "errors"
10 "image"
11 "image/color"
12 "io"
13
14 "golang.org/x/image/riff"
15 "golang.org/x/image/vp8"
16 "golang.org/x/image/vp8l"
17)
18
19var errInvalidFormat = errors.New("webp: invalid format")
20
21var (
22 fccALPH = riff.FourCC{'A', 'L', 'P', 'H'}
23 fccVP8 = riff.FourCC{'V', 'P', '8', ' '}
24 fccVP8L = riff.FourCC{'V', 'P', '8', 'L'}
25 fccVP8X = riff.FourCC{'V', 'P', '8', 'X'}
26 fccWEBP = riff.FourCC{'W', 'E', 'B', 'P'}
27)
28
29func decode(r io.Reader, configOnly bool) (image.Image, image.Config, error) {
30 formType, riffReader, err := riff.NewReader(r)
31 if err != nil {
32 return nil, image.Config{}, err
33 }
34 if formType != fccWEBP {
35 return nil, image.Config{}, errInvalidFormat
36 }
37
38 var (
39 alpha []byte
40 alphaStride int
41 wantAlpha bool
42 widthMinusOne uint32
43 heightMinusOne uint32
44 buf [10]byte
45 )
46 for {
47 chunkID, chunkLen, chunkData, err := riffReader.Next()
48 if err == io.EOF {
49 err = errInvalidFormat
50 }
51 if err != nil {
52 return nil, image.Config{}, err
53 }
54
55 switch chunkID {
56 case fccALPH:
57 if !wantAlpha {
58 return nil, image.Config{}, errInvalidFormat
59 }
60 wantAlpha = false
61 // Read the Pre-processing | Filter | Compression byte.
62 if _, err := io.ReadFull(chunkData, buf[:1]); err != nil {
63 if err == io.EOF {
64 err = errInvalidFormat
65 }
66 return nil, image.Config{}, err
67 }
68 alpha, alphaStride, err = readAlpha(chunkData, widthMinusOne, heightMinusOne, buf[0]&0x03)
69 if err != nil {
70 return nil, image.Config{}, err
71 }
72 unfilterAlpha(alpha, alphaStride, (buf[0]>>2)&0x03)
73
74 case fccVP8:
75 if wantAlpha || int32(chunkLen) < 0 {
76 return nil, image.Config{}, errInvalidFormat
77 }
78 d := vp8.NewDecoder()
79 d.Init(chunkData, int(chunkLen))
80 fh, err := d.DecodeFrameHeader()
81 if err != nil {
82 return nil, image.Config{}, err
83 }
84 if configOnly {
85 return nil, image.Config{
86 ColorModel: color.YCbCrModel,
87 Width: fh.Width,
88 Height: fh.Height,
89 }, nil
90 }
91 m, err := d.DecodeFrame()
92 if err != nil {
93 return nil, image.Config{}, err
94 }
95 if alpha != nil {
96 return &image.NYCbCrA{
97 YCbCr: *m,
98 A: alpha,
99 AStride: alphaStride,
100 }, image.Config{}, nil
101 }
102 return m, image.Config{}, nil
103
104 case fccVP8L:
105 if wantAlpha || alpha != nil {
106 return nil, image.Config{}, errInvalidFormat
107 }
108 if configOnly {
109 c, err := vp8l.DecodeConfig(chunkData)
110 return nil, c, err
111 }
112 m, err := vp8l.Decode(chunkData)
113 return m, image.Config{}, err
114
115 case fccVP8X:
116 if chunkLen != 10 {
117 return nil, image.Config{}, errInvalidFormat
118 }
119 if _, err := io.ReadFull(chunkData, buf[:10]); err != nil {
120 return nil, image.Config{}, err
121 }
122 const (
123 animationBit = 1 << 1
124 xmpMetadataBit = 1 << 2
125 exifMetadataBit = 1 << 3
126 alphaBit = 1 << 4
127 iccProfileBit = 1 << 5
128 )
129 if buf[0] != alphaBit {
130 return nil, image.Config{}, errors.New("webp: non-Alpha VP8X is not implemented")
131 }
132 widthMinusOne = uint32(buf[4]) | uint32(buf[5])<<8 | uint32(buf[6])<<16
133 heightMinusOne = uint32(buf[7]) | uint32(buf[8])<<8 | uint32(buf[9])<<16
134 if configOnly {
135 return nil, image.Config{
136 ColorModel: color.NYCbCrAModel,
137 Width: int(widthMinusOne) + 1,
138 Height: int(heightMinusOne) + 1,
139 }, nil
140 }
141 wantAlpha = true
142
143 default:
144 return nil, image.Config{}, errInvalidFormat
145 }
146 }
147}
148
149func readAlpha(chunkData io.Reader, widthMinusOne, heightMinusOne uint32, compression byte) (
150 alpha []byte, alphaStride int, err error) {
151
152 switch compression {
153 case 0:
154 w := int(widthMinusOne) + 1
155 h := int(heightMinusOne) + 1
156 alpha = make([]byte, w*h)
157 if _, err := io.ReadFull(chunkData, alpha); err != nil {
158 return nil, 0, err
159 }
160 return alpha, w, nil
161
162 case 1:
163 // Read the VP8L-compressed alpha values. First, synthesize a 5-byte VP8L header:
164 // a 1-byte magic number, a 14-bit widthMinusOne, a 14-bit heightMinusOne,
165 // a 1-bit (ignored, zero) alphaIsUsed and a 3-bit (zero) version.
166 // TODO(nigeltao): be more efficient than decoding an *image.NRGBA just to
167 // extract the green values to a separately allocated []byte. Fixing this
168 // will require changes to the vp8l package's API.
169 if widthMinusOne > 0x3fff || heightMinusOne > 0x3fff {
170 return nil, 0, errors.New("webp: invalid format")
171 }
172 alphaImage, err := vp8l.Decode(io.MultiReader(
173 bytes.NewReader([]byte{
174 0x2f, // VP8L magic number.
175 uint8(widthMinusOne),
176 uint8(widthMinusOne>>8) | uint8(heightMinusOne<<6),
177 uint8(heightMinusOne >> 2),
178 uint8(heightMinusOne >> 10),
179 }),
180 chunkData,
181 ))
182 if err != nil {
183 return nil, 0, err
184 }
185 // The green values of the inner NRGBA image are the alpha values of the
186 // outer NYCbCrA image.
187 pix := alphaImage.(*image.NRGBA).Pix
188 alpha = make([]byte, len(pix)/4)
189 for i := range alpha {
190 alpha[i] = pix[4*i+1]
191 }
192 return alpha, int(widthMinusOne) + 1, nil
193 }
194 return nil, 0, errInvalidFormat
195}
196
197func unfilterAlpha(alpha []byte, alphaStride int, filter byte) {
198 if len(alpha) == 0 || alphaStride == 0 {
199 return
200 }
201 switch filter {
202 case 1: // Horizontal filter.
203 for i := 1; i < alphaStride; i++ {
204 alpha[i] += alpha[i-1]
205 }
206 for i := alphaStride; i < len(alpha); i += alphaStride {
207 // The first column is equivalent to the vertical filter.
208 alpha[i] += alpha[i-alphaStride]
209
210 for j := 1; j < alphaStride; j++ {
211 alpha[i+j] += alpha[i+j-1]
212 }
213 }
214
215 case 2: // Vertical filter.
216 // The first row is equivalent to the horizontal filter.
217 for i := 1; i < alphaStride; i++ {
218 alpha[i] += alpha[i-1]
219 }
220
221 for i := alphaStride; i < len(alpha); i++ {
222 alpha[i] += alpha[i-alphaStride]
223 }
224
225 case 3: // Gradient filter.
226 // The first row is equivalent to the horizontal filter.
227 for i := 1; i < alphaStride; i++ {
228 alpha[i] += alpha[i-1]
229 }
230
231 for i := alphaStride; i < len(alpha); i += alphaStride {
232 // The first column is equivalent to the vertical filter.
233 alpha[i] += alpha[i-alphaStride]
234
235 // The interior is predicted on the three top/left pixels.
236 for j := 1; j < alphaStride; j++ {
237 c := int(alpha[i+j-alphaStride-1])
238 b := int(alpha[i+j-alphaStride])
239 a := int(alpha[i+j-1])
240 x := a + b - c
241 if x < 0 {
242 x = 0
243 } else if x > 255 {
244 x = 255
245 }
246 alpha[i+j] += uint8(x)
247 }
248 }
249 }
250}
251
252// Decode reads a WEBP image from r and returns it as an image.Image.
253func Decode(r io.Reader) (image.Image, error) {
254 m, _, err := decode(r, false)
255 if err != nil {
256 return nil, err
257 }
258 return m, err
259}
260
261// DecodeConfig returns the color model and dimensions of a WEBP image without
262// decoding the entire image.
263func DecodeConfig(r io.Reader) (image.Config, error) {
264 _, c, err := decode(r, true)
265 return c, err
266}
267
268func init() {
269 image.RegisterFormat("webp", "RIFF????WEBPVP8", Decode, DecodeConfig)
270}