Commit 0d111a62 authored by Nigel Tao's avatar Nigel Tao

image/jpeg: decode grayscale images, not just color images.

Also add an image package test that DecodeConfig returns the same
ColorModel as what Decode would.

R=r, r
CC=golang-dev
https://golang.org/cl/4529065
parent 4da5cd4c
...@@ -44,6 +44,15 @@ func decode(filename string) (image.Image, string, os.Error) { ...@@ -44,6 +44,15 @@ func decode(filename string) (image.Image, string, os.Error) {
return image.Decode(bufio.NewReader(f)) 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 { func delta(u0, u1 uint32) int {
d := int(u0) - int(u1) d := int(u0) - int(u1)
if d < 0 { if d < 0 {
...@@ -69,7 +78,7 @@ func TestDecode(t *testing.T) { ...@@ -69,7 +78,7 @@ func TestDecode(t *testing.T) {
} }
loop: loop:
for _, it := range imageTests { for _, it := range imageTests {
m, _, err := decode(it.filename) m, imageFormat, err := decode(it.filename)
if err != nil { if err != nil {
t.Errorf("%s: %v", it.filename, err) t.Errorf("%s: %v", it.filename, err)
continue loop continue loop
...@@ -87,5 +96,16 @@ loop: ...@@ -87,5 +96,16 @@ 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
}
} }
} }
...@@ -41,16 +41,22 @@ type block [blockSize]int ...@@ -41,16 +41,22 @@ type block [blockSize]int
const ( const (
blockSize = 64 // A DCT block is 8x8. blockSize = 64 // A DCT block is 8x8.
dcTableClass = 0 dcTable = 0
acTableClass = 1 acTable = 1
maxTc = 1 maxTc = 1
maxTh = 3 maxTh = 3
maxTq = 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. // A grayscale JPEG image has only a Y component.
nComponent = 3 nGrayComponent = 1
maxH = 2 // A color JPEG image has Y, Cb and Cr components.
maxV = 2 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 ( const (
...@@ -90,9 +96,11 @@ type Reader interface { ...@@ -90,9 +96,11 @@ type Reader interface {
type decoder struct { type decoder struct {
r Reader r Reader
width, height int width, height int
img *ycbcr.YCbCr img1 *image.Gray
img3 *ycbcr.YCbCr
ri int // Restart Interval. ri int // Restart Interval.
comps [nComponent]component nComp int
comp [nColorComponent]component
huff [maxTc + 1][maxTh + 1]huffman huff [maxTc + 1][maxTh + 1]huffman
quant [maxTq + 1]block quant [maxTq + 1]block
b bits b bits
...@@ -117,10 +125,15 @@ func (d *decoder) ignore(n int) os.Error { ...@@ -117,10 +125,15 @@ func (d *decoder) ignore(n int) os.Error {
// Specified in section B.2.2. // Specified in section B.2.2.
func (d *decoder) processSOF(n int) os.Error { 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") 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 { if err != nil {
return err return err
} }
...@@ -130,26 +143,28 @@ func (d *decoder) processSOF(n int) os.Error { ...@@ -130,26 +143,28 @@ func (d *decoder) processSOF(n int) os.Error {
} }
d.height = int(d.tmp[1])<<8 + int(d.tmp[2]) d.height = int(d.tmp[1])<<8 + int(d.tmp[2])
d.width = int(d.tmp[3])<<8 + int(d.tmp[4]) 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") 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] hv := d.tmp[7+3*i]
d.comps[i].h = int(hv >> 4) d.comp[i].h = int(hv >> 4)
d.comps[i].v = int(hv & 0x0f) d.comp[i].v = int(hv & 0x0f)
d.comps[i].c = d.tmp[6+3*i] d.comp[i].c = d.tmp[6+3*i]
d.comps[i].tq = d.tmp[8+3*i] d.comp[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 if d.nComp == nGrayComponent {
// the (h, v) values for the Y component are either (1, 1), (2, 1) or (2, 2), and the continue
// (h, v) values for the Cr and Cb components must be (1, 1). }
// 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 i == 0 {
if hv != 0x11 && hv != 0x21 && hv != 0x22 { if hv != 0x11 && hv != 0x21 && hv != 0x22 {
return UnsupportedError("luma downsample ratio") return UnsupportedError("luma downsample ratio")
} }
} else { } else if hv != 0x11 {
if hv != 0x11 { return UnsupportedError("chroma downsample ratio")
return UnsupportedError("chroma downsample ratio")
}
} }
} }
return nil return nil
...@@ -181,75 +196,87 @@ func (d *decoder) processDQT(n int) os.Error { ...@@ -181,75 +196,87 @@ func (d *decoder) processDQT(n int) os.Error {
return nil return nil
} }
// makeImg allocates and initializes the destination image.
func (d *decoder) makeImg(h0, v0, mxx, myy int) {
if d.nComp == nGrayComponent {
d.img1 = image.NewGray(8*mxx, 8*myy)
return
}
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.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. // Specified in section B.2.3.
func (d *decoder) processSOS(n int) os.Error { 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") 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 { if err != nil {
return err return err
} }
if d.tmp[0] != nComponent { if int(d.tmp[0]) != d.nComp {
return UnsupportedError("SOS has wrong number of image components") return UnsupportedError("SOS has wrong number of image components")
} }
var scanComps [nComponent]struct { var scan [nColorComponent]struct {
td uint8 // DC table selector. td uint8 // DC table selector.
ta uint8 // AC 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. 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") return UnsupportedError("scan components out of order")
} }
scanComps[i].td = d.tmp[2+2*i] >> 4 scan[i].td = d.tmp[2+2*i] >> 4
scanComps[i].ta = d.tmp[2+2*i] & 0x0f scan[i].ta = d.tmp[2+2*i] & 0x0f
} }
// mxx and myy are the number of MCUs (Minimum Coded Units) in the image. // 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) mxx := (d.width + 8*h0 - 1) / (8 * h0)
myy := (d.height + 8*v0 - 1) / (8 * v0) myy := (d.height + 8*v0 - 1) / (8 * v0)
if d.img == nil { if d.img1 == nil && d.img3 == nil {
var subsampleRatio ycbcr.SubsampleRatio d.makeImg(h0, v0, mxx, myy)
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),
}
} }
mcu, expectedRST := 0, uint8(rst0Marker) mcu, expectedRST := 0, uint8(rst0Marker)
var ( var (
allZeroes, b block b block
dc [nComponent]int dc [nColorComponent]int
) )
for my := 0; my < myy; my++ { for my := 0; my < myy; my++ {
for mx := 0; mx < mxx; mx++ { for mx := 0; mx < mxx; mx++ {
for i := 0; i < nComponent; i++ { for i := 0; i < d.nComp; i++ {
qt := &d.quant[d.comps[i].tq] qt := &d.quant[d.comp[i].tq]
for j := 0; j < d.comps[i].h*d.comps[i].v; j++ { 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 // 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. // analysis is good enough to allocate it on the stack, not the heap.
b = allZeroes b = block{}
// Decode the DC coefficient, as specified in section F.2.2.1. // 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 { if err != nil {
return err return err
} }
...@@ -265,7 +292,7 @@ func (d *decoder) processSOS(n int) os.Error { ...@@ -265,7 +292,7 @@ func (d *decoder) processSOS(n int) os.Error {
// Decode the AC coefficients, as specified in section F.2.2.2. // Decode the AC coefficients, as specified in section F.2.2.2.
for k := 1; k < blockSize; k++ { 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 { if err != nil {
return err return err
} }
...@@ -290,15 +317,28 @@ func (d *decoder) processSOS(n int) os.Error { ...@@ -290,15 +317,28 @@ func (d *decoder) processSOS(n int) os.Error {
} }
// Perform the inverse DCT and store the MCU component to the image. // Perform the inverse DCT and store the MCU component to the image.
switch i { if d.nComp == nGrayComponent {
case 0: idct(d.tmp[:64], 8, &b)
mx0 := h0*mx + (j % 2) // Convert from []uint8 to []image.GrayColor.
my0 := v0*my + (j / 2) p := d.img1.Pix[8*(my*d.img1.Stride+mx):]
idct(d.img.Y[8*(my0*d.img.YStride+mx0):], d.img.YStride, &b) for y := 0; y < 8; y++ {
case 1: dst := p[y*d.img1.Stride:]
idct(d.img.Cb[8*(my*d.img.CStride+mx):], d.img.CStride, &b) src := d.tmp[8*y:]
case 2: for x := 0; x < 8; x++ {
idct(d.img.Cr[8*(my*d.img.CStride+mx):], d.img.CStride, &b) dst[x] = image.GrayColor{src[x]}
}
}
} 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 j
} // for i } // for i
...@@ -320,9 +360,7 @@ func (d *decoder) processSOS(n int) os.Error { ...@@ -320,9 +360,7 @@ func (d *decoder) processSOS(n int) os.Error {
// Reset the Huffman decoder. // Reset the Huffman decoder.
d.b = bits{} d.b = bits{}
// Reset the DC components, as per section F.2.1.3.1. // Reset the DC components, as per section F.2.1.3.1.
for i := 0; i < nComponent; i++ { dc = [nColorComponent]int{}
dc[i] = 0
}
} }
} // for mx } // for mx
} // for my } // for my
...@@ -410,7 +448,13 @@ func (d *decoder) decode(r io.Reader, configOnly bool) (image.Image, os.Error) { ...@@ -410,7 +448,13 @@ func (d *decoder) decode(r io.Reader, configOnly bool) (image.Image, os.Error) {
return nil, err 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. // Decode reads a JPEG image from r and returns it as an image.Image.
...@@ -426,7 +470,13 @@ func DecodeConfig(r io.Reader) (image.Config, os.Error) { ...@@ -426,7 +470,13 @@ func DecodeConfig(r io.Reader) (image.Config, os.Error) {
if _, err := d.decode(r, true); err != nil { if _, err := d.decode(r, true); err != nil {
return image.Config{}, err 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() { func init() {
......
...@@ -315,21 +315,21 @@ func (e *encoder) writeDQT() { ...@@ -315,21 +315,21 @@ func (e *encoder) writeDQT() {
// writeSOF0 writes the Start Of Frame (Baseline) marker. // writeSOF0 writes the Start Of Frame (Baseline) marker.
func (e *encoder) writeSOF0(size image.Point) { func (e *encoder) writeSOF0(size image.Point) {
markerlen := 8 + 3*nComponent markerlen := 8 + 3*nColorComponent
e.writeMarkerHeader(sof0Marker, markerlen) e.writeMarkerHeader(sof0Marker, markerlen)
e.buf[0] = 8 // 8-bit color. e.buf[0] = 8 // 8-bit color.
e.buf[1] = uint8(size.Y >> 8) e.buf[1] = uint8(size.Y >> 8)
e.buf[2] = uint8(size.Y & 0xff) e.buf[2] = uint8(size.Y & 0xff)
e.buf[3] = uint8(size.X >> 8) e.buf[3] = uint8(size.X >> 8)
e.buf[4] = uint8(size.X & 0xff) e.buf[4] = uint8(size.X & 0xff)
e.buf[5] = nComponent e.buf[5] = nColorComponent
for i := 0; i < nComponent; i++ { for i := 0; i < nColorComponent; i++ {
e.buf[3*i+6] = uint8(i + 1) e.buf[3*i+6] = uint8(i + 1)
// We use 4:2:0 chroma subsampling. // We use 4:2:0 chroma subsampling.
e.buf[3*i+7] = "\x22\x11\x11"[i] e.buf[3*i+7] = "\x22\x11\x11"[i]
e.buf[3*i+8] = "\x00\x01\x01"[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. // writeDHT writes the Define Huffman Table marker.
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment