1
0
mirror of https://github.com/golang/go synced 2024-11-19 18:54:41 -07:00

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
This commit is contained in:
Nigel Tao 2011-05-16 10:13:17 -07:00
parent 4da5cd4cd6
commit 0d111a62e1
3 changed files with 155 additions and 85 deletions

View File

@ -44,6 +44,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 {
@ -69,7 +78,7 @@ func TestDecode(t *testing.T) {
}
loop:
for _, it := range imageTests {
m, _, err := decode(it.filename)
m, imageFormat, err := decode(it.filename)
if err != nil {
t.Errorf("%s: %v", it.filename, err)
continue 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
}
}
}

View File

@ -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
dcTable = 0
acTable = 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
// 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,9 +96,11 @@ 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
@ -117,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
}
@ -130,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
@ -181,75 +196,87 @@ func (d *decoder) processDQT(n int) os.Error {
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.
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, b block
dc [nComponent]int
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++ {
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 = allZeroes
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
}
@ -265,7 +292,7 @@ func (d *decoder) processSOS(n int) os.Error {
// 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
}
@ -290,15 +317,28 @@ func (d *decoder) processSOS(n int) os.Error {
}
// Perform the inverse DCT and store the MCU component to the image.
switch i {
case 0:
mx0 := h0*mx + (j % 2)
my0 := v0*my + (j / 2)
idct(d.img.Y[8*(my0*d.img.YStride+mx0):], d.img.YStride, &b)
case 1:
idct(d.img.Cb[8*(my*d.img.CStride+mx):], d.img.CStride, &b)
case 2:
idct(d.img.Cr[8*(my*d.img.CStride+mx):], d.img.CStride, &b)
if d.nComp == nGrayComponent {
idct(d.tmp[:64], 8, &b)
// Convert from []uint8 to []image.GrayColor.
p := d.img1.Pix[8*(my*d.img1.Stride+mx):]
for y := 0; y < 8; y++ {
dst := p[y*d.img1.Stride:]
src := d.tmp[8*y:]
for x := 0; x < 8; x++ {
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 i
@ -320,9 +360,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
@ -410,7 +448,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.
@ -426,7 +470,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() {

View File

@ -315,21 +315,21 @@ func (e *encoder) writeDQT() {
// 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.