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:
parent
4da5cd4cd6
commit
0d111a62e1
@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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() {
|
||||
|
@ -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.
|
||||
|
Loading…
Reference in New Issue
Block a user