]>
Commit | Line | Data |
---|---|---|
7a938933 ILT |
1 | // Copyright 2009 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 | ||
9ff56c95 ILT |
5 | // Package jpeg implements a JPEG image decoder and encoder. |
6 | // | |
7 | // JPEG is defined in ITU-T T.81: http://www.w3.org/Graphics/JPEG/itu-t81.pdf. | |
7a938933 ILT |
8 | package jpeg |
9 | ||
7a938933 ILT |
10 | import ( |
11 | "bufio" | |
12 | "image" | |
d8f41257 | 13 | "image/color" |
7a938933 | 14 | "io" |
7a938933 ILT |
15 | ) |
16 | ||
9ff56c95 ILT |
17 | // TODO(nigeltao): fix up the doc comment style so that sentences start with |
18 | // the name of the type or function that they annotate. | |
19 | ||
7a938933 ILT |
20 | // A FormatError reports that the input is not a valid JPEG. |
21 | type FormatError string | |
22 | ||
2fd401c8 | 23 | func (e FormatError) Error() string { return "invalid JPEG format: " + string(e) } |
7a938933 ILT |
24 | |
25 | // An UnsupportedError reports that the input uses a valid but unimplemented JPEG feature. | |
26 | type UnsupportedError string | |
27 | ||
2fd401c8 | 28 | func (e UnsupportedError) Error() string { return "unsupported JPEG feature: " + string(e) } |
7a938933 ILT |
29 | |
30 | // Component specification, specified in section B.2.2. | |
31 | type component struct { | |
9ff56c95 ILT |
32 | h int // Horizontal sampling factor. |
33 | v int // Vertical sampling factor. | |
7a938933 | 34 | c uint8 // Component identifier. |
7a938933 ILT |
35 | tq uint8 // Quantization table destination selector. |
36 | } | |
37 | ||
38 | const ( | |
adb0401d ILT |
39 | dcTable = 0 |
40 | acTable = 1 | |
41 | maxTc = 1 | |
42 | maxTh = 3 | |
43 | maxTq = 3 | |
44 | ||
45 | // A grayscale JPEG image has only a Y component. | |
46 | nGrayComponent = 1 | |
47 | // A color JPEG image has Y, Cb and Cr components. | |
48 | nColorComponent = 3 | |
49 | ||
4ccad563 | 50 | // We only support 4:4:4, 4:4:0, 4:2:2 and 4:2:0 downsampling, and therefore the |
adb0401d ILT |
51 | // number of luma samples per chroma sample is at most 2 in the horizontal |
52 | // and 2 in the vertical direction. | |
53 | maxH = 2 | |
54 | maxV = 2 | |
7a938933 ILT |
55 | ) |
56 | ||
57 | const ( | |
58 | soiMarker = 0xd8 // Start Of Image. | |
59 | eoiMarker = 0xd9 // End Of Image. | |
60 | sof0Marker = 0xc0 // Start Of Frame (Baseline). | |
61 | sof2Marker = 0xc2 // Start Of Frame (Progressive). | |
62 | dhtMarker = 0xc4 // Define Huffman Table. | |
63 | dqtMarker = 0xdb // Define Quantization Table. | |
64 | sosMarker = 0xda // Start Of Scan. | |
65 | driMarker = 0xdd // Define Restart Interval. | |
66 | rst0Marker = 0xd0 // ReSTart (0). | |
67 | rst7Marker = 0xd7 // ReSTart (7). | |
68 | app0Marker = 0xe0 // APPlication specific (0). | |
69 | app15Marker = 0xef // APPlication specific (15). | |
70 | comMarker = 0xfe // COMment. | |
71 | ) | |
72 | ||
bd2e46c8 ILT |
73 | // unzig maps from the zig-zag ordering to the natural ordering. For example, |
74 | // unzig[3] is the column and row of the fourth element in zig-zag order. The | |
75 | // value is 16, which means first column (16%8 == 0) and third row (16/8 == 2). | |
7a938933 ILT |
76 | var unzig = [blockSize]int{ |
77 | 0, 1, 8, 16, 9, 2, 3, 10, | |
78 | 17, 24, 32, 25, 18, 11, 4, 5, | |
79 | 12, 19, 26, 33, 40, 48, 41, 34, | |
80 | 27, 20, 13, 6, 7, 14, 21, 28, | |
81 | 35, 42, 49, 56, 57, 50, 43, 36, | |
82 | 29, 22, 15, 23, 30, 37, 44, 51, | |
83 | 58, 59, 52, 45, 38, 31, 39, 46, | |
84 | 53, 60, 61, 54, 47, 55, 62, 63, | |
85 | } | |
86 | ||
87 | // If the passed in io.Reader does not also have ReadByte, then Decode will introduce its own buffering. | |
88 | type Reader interface { | |
89 | io.Reader | |
2fd401c8 | 90 | ReadByte() (c byte, err error) |
7a938933 ILT |
91 | } |
92 | ||
93 | type decoder struct { | |
94 | r Reader | |
4ccad563 | 95 | b bits |
7a938933 | 96 | width, height int |
adb0401d | 97 | img1 *image.Gray |
df4aa89a | 98 | img3 *image.YCbCr |
7a938933 | 99 | ri int // Restart Interval. |
adb0401d ILT |
100 | nComp int |
101 | comp [nColorComponent]component | |
7a938933 | 102 | huff [maxTc + 1][maxTh + 1]huffman |
bd2e46c8 | 103 | quant [maxTq + 1]block // Quantization tables, in zig-zag order. |
7a938933 ILT |
104 | tmp [1024]byte |
105 | } | |
106 | ||
107 | // Reads and ignores the next n bytes. | |
2fd401c8 | 108 | func (d *decoder) ignore(n int) error { |
7a938933 ILT |
109 | for n > 0 { |
110 | m := len(d.tmp) | |
111 | if m > n { | |
112 | m = n | |
113 | } | |
114 | _, err := io.ReadFull(d.r, d.tmp[0:m]) | |
115 | if err != nil { | |
116 | return err | |
117 | } | |
118 | n -= m | |
119 | } | |
120 | return nil | |
121 | } | |
122 | ||
123 | // Specified in section B.2.2. | |
2fd401c8 | 124 | func (d *decoder) processSOF(n int) error { |
adb0401d ILT |
125 | switch n { |
126 | case 6 + 3*nGrayComponent: | |
127 | d.nComp = nGrayComponent | |
128 | case 6 + 3*nColorComponent: | |
129 | d.nComp = nColorComponent | |
130 | default: | |
7a938933 ILT |
131 | return UnsupportedError("SOF has wrong length") |
132 | } | |
adb0401d | 133 | _, err := io.ReadFull(d.r, d.tmp[:n]) |
7a938933 ILT |
134 | if err != nil { |
135 | return err | |
136 | } | |
137 | // We only support 8-bit precision. | |
138 | if d.tmp[0] != 8 { | |
139 | return UnsupportedError("precision") | |
140 | } | |
141 | d.height = int(d.tmp[1])<<8 + int(d.tmp[2]) | |
142 | d.width = int(d.tmp[3])<<8 + int(d.tmp[4]) | |
adb0401d | 143 | if int(d.tmp[5]) != d.nComp { |
7a938933 ILT |
144 | return UnsupportedError("SOF has wrong number of image components") |
145 | } | |
adb0401d | 146 | for i := 0; i < d.nComp; i++ { |
7a938933 | 147 | hv := d.tmp[7+3*i] |
adb0401d ILT |
148 | d.comp[i].h = int(hv >> 4) |
149 | d.comp[i].v = int(hv & 0x0f) | |
150 | d.comp[i].c = d.tmp[6+3*i] | |
151 | d.comp[i].tq = d.tmp[8+3*i] | |
152 | if d.nComp == nGrayComponent { | |
153 | continue | |
154 | } | |
4ccad563 | 155 | // For color images, we only support 4:4:4, 4:4:0, 4:2:2 or 4:2:0 chroma |
adb0401d | 156 | // downsampling ratios. This implies that the (h, v) values for the Y |
4ccad563 | 157 | // component are either (1, 1), (1, 2), (2, 1) or (2, 2), and the (h, v) |
adb0401d | 158 | // values for the Cr and Cb components must be (1, 1). |
7a938933 | 159 | if i == 0 { |
4ccad563 | 160 | if hv != 0x11 && hv != 0x21 && hv != 0x22 && hv != 0x12 { |
7a938933 ILT |
161 | return UnsupportedError("luma downsample ratio") |
162 | } | |
adb0401d ILT |
163 | } else if hv != 0x11 { |
164 | return UnsupportedError("chroma downsample ratio") | |
7a938933 ILT |
165 | } |
166 | } | |
167 | return nil | |
168 | } | |
169 | ||
170 | // Specified in section B.2.4.1. | |
2fd401c8 | 171 | func (d *decoder) processDQT(n int) error { |
7a938933 ILT |
172 | const qtLength = 1 + blockSize |
173 | for ; n >= qtLength; n -= qtLength { | |
174 | _, err := io.ReadFull(d.r, d.tmp[0:qtLength]) | |
175 | if err != nil { | |
176 | return err | |
177 | } | |
178 | pq := d.tmp[0] >> 4 | |
179 | if pq != 0 { | |
180 | return UnsupportedError("bad Pq value") | |
181 | } | |
182 | tq := d.tmp[0] & 0x0f | |
183 | if tq > maxTq { | |
184 | return FormatError("bad Tq value") | |
185 | } | |
186 | for i := range d.quant[tq] { | |
187 | d.quant[tq][i] = int(d.tmp[i+1]) | |
188 | } | |
189 | } | |
190 | if n != 0 { | |
191 | return FormatError("DQT has wrong length") | |
192 | } | |
193 | return nil | |
194 | } | |
195 | ||
adb0401d ILT |
196 | // makeImg allocates and initializes the destination image. |
197 | func (d *decoder) makeImg(h0, v0, mxx, myy int) { | |
198 | if d.nComp == nGrayComponent { | |
d8f41257 | 199 | m := image.NewGray(image.Rect(0, 0, 8*mxx, 8*myy)) |
adb0401d ILT |
200 | d.img1 = m.SubImage(image.Rect(0, 0, d.width, d.height)).(*image.Gray) |
201 | return | |
7a938933 | 202 | } |
df4aa89a | 203 | var subsampleRatio image.YCbCrSubsampleRatio |
4ccad563 ILT |
204 | switch { |
205 | case h0 == 1 && v0 == 1: | |
df4aa89a | 206 | subsampleRatio = image.YCbCrSubsampleRatio444 |
4ccad563 ILT |
207 | case h0 == 1 && v0 == 2: |
208 | subsampleRatio = image.YCbCrSubsampleRatio440 | |
209 | case h0 == 2 && v0 == 1: | |
df4aa89a | 210 | subsampleRatio = image.YCbCrSubsampleRatio422 |
4ccad563 | 211 | case h0 == 2 && v0 == 2: |
df4aa89a | 212 | subsampleRatio = image.YCbCrSubsampleRatio420 |
adb0401d ILT |
213 | default: |
214 | panic("unreachable") | |
7a938933 | 215 | } |
af92e385 ILT |
216 | m := image.NewYCbCr(image.Rect(0, 0, 8*h0*mxx, 8*v0*myy), subsampleRatio) |
217 | d.img3 = m.SubImage(image.Rect(0, 0, d.width, d.height)).(*image.YCbCr) | |
7a938933 ILT |
218 | } |
219 | ||
220 | // Specified in section B.2.3. | |
2fd401c8 | 221 | func (d *decoder) processSOS(n int) error { |
adb0401d ILT |
222 | if d.nComp == 0 { |
223 | return FormatError("missing SOF marker") | |
224 | } | |
225 | if n != 4+2*d.nComp { | |
7a938933 ILT |
226 | return UnsupportedError("SOS has wrong length") |
227 | } | |
adb0401d | 228 | _, err := io.ReadFull(d.r, d.tmp[0:4+2*d.nComp]) |
7a938933 ILT |
229 | if err != nil { |
230 | return err | |
231 | } | |
adb0401d | 232 | if int(d.tmp[0]) != d.nComp { |
7a938933 ILT |
233 | return UnsupportedError("SOS has wrong number of image components") |
234 | } | |
adb0401d | 235 | var scan [nColorComponent]struct { |
7a938933 ILT |
236 | td uint8 // DC table selector. |
237 | ta uint8 // AC table selector. | |
238 | } | |
adb0401d | 239 | for i := 0; i < d.nComp; i++ { |
7a938933 | 240 | cs := d.tmp[1+2*i] // Component selector. |
adb0401d | 241 | if cs != d.comp[i].c { |
7a938933 ILT |
242 | return UnsupportedError("scan components out of order") |
243 | } | |
adb0401d ILT |
244 | scan[i].td = d.tmp[2+2*i] >> 4 |
245 | scan[i].ta = d.tmp[2+2*i] & 0x0f | |
7a938933 ILT |
246 | } |
247 | // mxx and myy are the number of MCUs (Minimum Coded Units) in the image. | |
adb0401d | 248 | h0, v0 := d.comp[0].h, d.comp[0].v // The h and v values from the Y components. |
9ff56c95 ILT |
249 | mxx := (d.width + 8*h0 - 1) / (8 * h0) |
250 | myy := (d.height + 8*v0 - 1) / (8 * v0) | |
adb0401d ILT |
251 | if d.img1 == nil && d.img3 == nil { |
252 | d.makeImg(h0, v0, mxx, myy) | |
9ff56c95 | 253 | } |
7a938933 ILT |
254 | |
255 | mcu, expectedRST := 0, uint8(rst0Marker) | |
adb0401d ILT |
256 | var ( |
257 | b block | |
258 | dc [nColorComponent]int | |
259 | ) | |
7a938933 ILT |
260 | for my := 0; my < myy; my++ { |
261 | for mx := 0; mx < mxx; mx++ { | |
adb0401d ILT |
262 | for i := 0; i < d.nComp; i++ { |
263 | qt := &d.quant[d.comp[i].tq] | |
264 | for j := 0; j < d.comp[i].h*d.comp[i].v; j++ { | |
265 | // TODO(nigeltao): make this a "var b block" once the compiler's escape | |
266 | // analysis is good enough to allocate it on the stack, not the heap. | |
bd2e46c8 | 267 | // b is in natural (not zig-zag) order. |
adb0401d | 268 | b = block{} |
7a938933 ILT |
269 | |
270 | // Decode the DC coefficient, as specified in section F.2.2.1. | |
adb0401d | 271 | value, err := d.decodeHuffman(&d.huff[dcTable][scan[i].td]) |
7a938933 ILT |
272 | if err != nil { |
273 | return err | |
274 | } | |
275 | if value > 16 { | |
276 | return UnsupportedError("excessive DC component") | |
277 | } | |
278 | dcDelta, err := d.receiveExtend(value) | |
279 | if err != nil { | |
280 | return err | |
281 | } | |
282 | dc[i] += dcDelta | |
adb0401d | 283 | b[0] = dc[i] * qt[0] |
7a938933 ILT |
284 | |
285 | // Decode the AC coefficients, as specified in section F.2.2.2. | |
bd2e46c8 | 286 | for zig := 1; zig < blockSize; zig++ { |
adb0401d | 287 | value, err := d.decodeHuffman(&d.huff[acTable][scan[i].ta]) |
7a938933 ILT |
288 | if err != nil { |
289 | return err | |
290 | } | |
9ff56c95 ILT |
291 | val0 := value >> 4 |
292 | val1 := value & 0x0f | |
293 | if val1 != 0 { | |
bd2e46c8 ILT |
294 | zig += int(val0) |
295 | if zig > blockSize { | |
7a938933 ILT |
296 | return FormatError("bad DCT index") |
297 | } | |
9ff56c95 | 298 | ac, err := d.receiveExtend(val1) |
7a938933 ILT |
299 | if err != nil { |
300 | return err | |
301 | } | |
bd2e46c8 | 302 | b[unzig[zig]] = ac * qt[zig] |
7a938933 | 303 | } else { |
9ff56c95 | 304 | if val0 != 0x0f { |
7a938933 ILT |
305 | break |
306 | } | |
bd2e46c8 | 307 | zig += 0x0f |
7a938933 ILT |
308 | } |
309 | } | |
310 | ||
adb0401d | 311 | // Perform the inverse DCT and store the MCU component to the image. |
4ccad563 ILT |
312 | idct(&b) |
313 | dst, stride := []byte(nil), 0 | |
adb0401d | 314 | if d.nComp == nGrayComponent { |
4ccad563 | 315 | dst, stride = d.img1.Pix[8*(my*d.img1.Stride+mx):], d.img1.Stride |
adb0401d ILT |
316 | } else { |
317 | switch i { | |
318 | case 0: | |
4ccad563 ILT |
319 | mx0, my0 := h0*mx, v0*my |
320 | if h0 == 1 { | |
321 | my0 += j | |
322 | } else { | |
323 | mx0 += j % 2 | |
324 | my0 += j / 2 | |
325 | } | |
326 | dst, stride = d.img3.Y[8*(my0*d.img3.YStride+mx0):], d.img3.YStride | |
adb0401d | 327 | case 1: |
4ccad563 | 328 | dst, stride = d.img3.Cb[8*(my*d.img3.CStride+mx):], d.img3.CStride |
adb0401d | 329 | case 2: |
4ccad563 ILT |
330 | dst, stride = d.img3.Cr[8*(my*d.img3.CStride+mx):], d.img3.CStride |
331 | } | |
332 | } | |
333 | // Level shift by +128, clip to [0, 255], and write to dst. | |
334 | for y := 0; y < 8; y++ { | |
335 | y8 := y * 8 | |
336 | yStride := y * stride | |
337 | for x := 0; x < 8; x++ { | |
338 | c := b[y8+x] | |
339 | if c < -128 { | |
340 | c = 0 | |
341 | } else if c > 127 { | |
342 | c = 255 | |
343 | } else { | |
344 | c += 128 | |
345 | } | |
346 | dst[yStride+x] = uint8(c) | |
adb0401d ILT |
347 | } |
348 | } | |
7a938933 ILT |
349 | } // for j |
350 | } // for i | |
7a938933 ILT |
351 | mcu++ |
352 | if d.ri > 0 && mcu%d.ri == 0 && mcu < mxx*myy { | |
353 | // A more sophisticated decoder could use RST[0-7] markers to resynchronize from corrupt input, | |
354 | // but this one assumes well-formed input, and hence the restart marker follows immediately. | |
355 | _, err := io.ReadFull(d.r, d.tmp[0:2]) | |
356 | if err != nil { | |
357 | return err | |
358 | } | |
359 | if d.tmp[0] != 0xff || d.tmp[1] != expectedRST { | |
360 | return FormatError("bad RST marker") | |
361 | } | |
362 | expectedRST++ | |
363 | if expectedRST == rst7Marker+1 { | |
364 | expectedRST = rst0Marker | |
365 | } | |
366 | // Reset the Huffman decoder. | |
367 | d.b = bits{} | |
368 | // Reset the DC components, as per section F.2.1.3.1. | |
adb0401d | 369 | dc = [nColorComponent]int{} |
7a938933 ILT |
370 | } |
371 | } // for mx | |
372 | } // for my | |
373 | ||
374 | return nil | |
375 | } | |
376 | ||
377 | // Specified in section B.2.4.4. | |
2fd401c8 | 378 | func (d *decoder) processDRI(n int) error { |
7a938933 ILT |
379 | if n != 2 { |
380 | return FormatError("DRI has wrong length") | |
381 | } | |
382 | _, err := io.ReadFull(d.r, d.tmp[0:2]) | |
383 | if err != nil { | |
384 | return err | |
385 | } | |
386 | d.ri = int(d.tmp[0])<<8 + int(d.tmp[1]) | |
387 | return nil | |
388 | } | |
389 | ||
390 | // decode reads a JPEG image from r and returns it as an image.Image. | |
2fd401c8 | 391 | func (d *decoder) decode(r io.Reader, configOnly bool) (image.Image, error) { |
7a938933 ILT |
392 | if rr, ok := r.(Reader); ok { |
393 | d.r = rr | |
394 | } else { | |
395 | d.r = bufio.NewReader(r) | |
396 | } | |
397 | ||
398 | // Check for the Start Of Image marker. | |
399 | _, err := io.ReadFull(d.r, d.tmp[0:2]) | |
400 | if err != nil { | |
401 | return nil, err | |
402 | } | |
403 | if d.tmp[0] != 0xff || d.tmp[1] != soiMarker { | |
404 | return nil, FormatError("missing SOI marker") | |
405 | } | |
406 | ||
407 | // Process the remaining segments until the End Of Image marker. | |
408 | for { | |
409 | _, err := io.ReadFull(d.r, d.tmp[0:2]) | |
410 | if err != nil { | |
411 | return nil, err | |
412 | } | |
413 | if d.tmp[0] != 0xff { | |
414 | return nil, FormatError("missing 0xff marker start") | |
415 | } | |
416 | marker := d.tmp[1] | |
417 | if marker == eoiMarker { // End Of Image. | |
418 | break | |
419 | } | |
bd2e46c8 ILT |
420 | if rst0Marker <= marker && marker <= rst7Marker { |
421 | // Figures B.2 and B.16 of the specification suggest that restart markers should | |
422 | // only occur between Entropy Coded Segments and not after the final ECS. | |
423 | // However, some encoders may generate incorrect JPEGs with a final restart | |
424 | // marker. That restart marker will be seen here instead of inside the processSOS | |
425 | // method, and is ignored as a harmless error. Restart markers have no extra data, | |
426 | // so we check for this before we read the 16-bit length of the segment. | |
427 | continue | |
428 | } | |
7a938933 ILT |
429 | |
430 | // Read the 16-bit length of the segment. The value includes the 2 bytes for the | |
431 | // length itself, so we subtract 2 to get the number of remaining bytes. | |
432 | _, err = io.ReadFull(d.r, d.tmp[0:2]) | |
433 | if err != nil { | |
434 | return nil, err | |
435 | } | |
436 | n := int(d.tmp[0])<<8 + int(d.tmp[1]) - 2 | |
437 | if n < 0 { | |
438 | return nil, FormatError("short segment length") | |
439 | } | |
440 | ||
441 | switch { | |
442 | case marker == sof0Marker: // Start Of Frame (Baseline). | |
443 | err = d.processSOF(n) | |
444 | if configOnly { | |
445 | return nil, err | |
446 | } | |
447 | case marker == sof2Marker: // Start Of Frame (Progressive). | |
448 | err = UnsupportedError("progressive mode") | |
449 | case marker == dhtMarker: // Define Huffman Table. | |
450 | err = d.processDHT(n) | |
451 | case marker == dqtMarker: // Define Quantization Table. | |
452 | err = d.processDQT(n) | |
453 | case marker == sosMarker: // Start Of Scan. | |
454 | err = d.processSOS(n) | |
455 | case marker == driMarker: // Define Restart Interval. | |
456 | err = d.processDRI(n) | |
bd2e46c8 | 457 | case app0Marker <= marker && marker <= app15Marker || marker == comMarker: // APPlication specific, or COMment. |
7a938933 ILT |
458 | err = d.ignore(n) |
459 | default: | |
460 | err = UnsupportedError("unknown marker") | |
461 | } | |
462 | if err != nil { | |
463 | return nil, err | |
464 | } | |
465 | } | |
adb0401d ILT |
466 | if d.img1 != nil { |
467 | return d.img1, nil | |
468 | } | |
469 | if d.img3 != nil { | |
470 | return d.img3, nil | |
471 | } | |
472 | return nil, FormatError("missing SOS marker") | |
7a938933 ILT |
473 | } |
474 | ||
475 | // Decode reads a JPEG image from r and returns it as an image.Image. | |
2fd401c8 | 476 | func Decode(r io.Reader) (image.Image, error) { |
7a938933 ILT |
477 | var d decoder |
478 | return d.decode(r, false) | |
479 | } | |
480 | ||
481 | // DecodeConfig returns the color model and dimensions of a JPEG image without | |
482 | // decoding the entire image. | |
2fd401c8 | 483 | func DecodeConfig(r io.Reader) (image.Config, error) { |
7a938933 ILT |
484 | var d decoder |
485 | if _, err := d.decode(r, true); err != nil { | |
486 | return image.Config{}, err | |
487 | } | |
adb0401d ILT |
488 | switch d.nComp { |
489 | case nGrayComponent: | |
94252f4b ILT |
490 | return image.Config{ |
491 | ColorModel: color.GrayModel, | |
492 | Width: d.width, | |
493 | Height: d.height, | |
494 | }, nil | |
adb0401d | 495 | case nColorComponent: |
94252f4b ILT |
496 | return image.Config{ |
497 | ColorModel: color.YCbCrModel, | |
498 | Width: d.width, | |
499 | Height: d.height, | |
500 | }, nil | |
adb0401d ILT |
501 | } |
502 | return image.Config{}, FormatError("missing SOF marker") | |
7a938933 ILT |
503 | } |
504 | ||
505 | func init() { | |
506 | image.RegisterFormat("jpeg", "\xff\xd8", Decode, DecodeConfig) | |
507 | } |