Commit cc009687 authored by Nigel Tao's avatar Nigel Tao

image/jpeg: support RGB JPEG images.

The testdata was generated by:
convert video-001.png tmp1.tga
cjpeg -rgb -sample 2x2,1x1,1x1 tmp1.tga > video-001.rgb.jpeg
djpeg -nosmooth -targa video-001.rgb.jpeg > tmp2.tga
convert tmp2.tga video-001.rgb.png
rm tmp1.tga tmp2.tga

Change-Id: I5da0591b9005c1c75e807311f157d385e0e20a38
Reviewed-on: https://go-review.googlesource.com/6910Reviewed-by: default avatarRob Pike <r@golang.org>
parent d328756a
...@@ -33,6 +33,7 @@ var imageTests = []imageTest{ ...@@ -33,6 +33,7 @@ var imageTests = []imageTest{
{"testdata/video-001.png", "testdata/video-001.jpeg", 8 << 8}, {"testdata/video-001.png", "testdata/video-001.jpeg", 8 << 8},
{"testdata/video-001.png", "testdata/video-001.progressive.jpeg", 8 << 8}, {"testdata/video-001.png", "testdata/video-001.progressive.jpeg", 8 << 8},
{"testdata/video-001.cmyk.png", "testdata/video-001.cmyk.jpeg", 8 << 8}, {"testdata/video-001.cmyk.png", "testdata/video-001.cmyk.jpeg", 8 << 8},
{"testdata/video-001.rgb.png", "testdata/video-001.rgb.jpeg", 8 << 8},
// Grayscale images. // Grayscale images.
{"testdata/video-005.gray.png", "testdata/video-005.gray.jpeg", 8 << 8}, {"testdata/video-005.gray.png", "testdata/video-005.gray.jpeg", 8 << 8},
{"testdata/video-005.gray.png", "testdata/video-005.gray.png", 0}, {"testdata/video-005.gray.png", "testdata/video-005.gray.png", 0},
......
...@@ -126,6 +126,7 @@ type decoder struct { ...@@ -126,6 +126,7 @@ type decoder struct {
ri int // Restart Interval. ri int // Restart Interval.
nComp int nComp int
progressive bool progressive bool
jfif bool
adobeTransformValid bool adobeTransformValid bool
adobeTransform uint8 adobeTransform uint8
eobRun uint16 // End-of-Band run, specified in section G.1.2.2. eobRun uint16 // End-of-Band run, specified in section G.1.2.2.
...@@ -297,7 +298,7 @@ func (d *decoder) processSOF(n int) error { ...@@ -297,7 +298,7 @@ func (d *decoder) processSOF(n int) error {
switch n { switch n {
case 6 + 3*1: // Grayscale image. case 6 + 3*1: // Grayscale image.
d.nComp = 1 d.nComp = 1
case 6 + 3*3: // YCbCr image. (TODO(nigeltao): or RGB image.) case 6 + 3*3: // YCbCr or RGB image.
d.nComp = 3 d.nComp = 3
case 6 + 3*4: // YCbCrK or CMYK image. case 6 + 3*4: // YCbCrK or CMYK image.
d.nComp = 4 d.nComp = 4
...@@ -448,6 +449,23 @@ func (d *decoder) processDRI(n int) error { ...@@ -448,6 +449,23 @@ func (d *decoder) processDRI(n int) error {
return nil return nil
} }
func (d *decoder) processApp0Marker(n int) error {
if n < 5 {
return d.ignore(n)
}
if err := d.readFull(d.tmp[:5]); err != nil {
return err
}
n -= 5
d.jfif = d.tmp[0] == 'J' && d.tmp[1] == 'F' && d.tmp[2] == 'I' && d.tmp[3] == 'F' && d.tmp[4] == '\x00'
if n > 0 {
return d.ignore(n)
}
return nil
}
func (d *decoder) processApp14Marker(n int) error { func (d *decoder) processApp14Marker(n int) error {
if n < 12 { if n < 12 {
return d.ignore(n) return d.ignore(n)
...@@ -553,17 +571,34 @@ func (d *decoder) decode(r io.Reader, configOnly bool) (image.Image, error) { ...@@ -553,17 +571,34 @@ func (d *decoder) decode(r io.Reader, configOnly bool) (image.Image, error) {
case sof0Marker, sof1Marker, sof2Marker: case sof0Marker, sof1Marker, sof2Marker:
d.progressive = marker == sof2Marker d.progressive = marker == sof2Marker
err = d.processSOF(n) err = d.processSOF(n)
if configOnly { if configOnly && d.jfif {
return nil, err return nil, err
} }
case dhtMarker: case dhtMarker:
if configOnly {
err = d.ignore(n)
} else {
err = d.processDHT(n) err = d.processDHT(n)
}
case dqtMarker: case dqtMarker:
if configOnly {
err = d.ignore(n)
} else {
err = d.processDQT(n) err = d.processDQT(n)
}
case sosMarker: case sosMarker:
if configOnly {
return nil, nil
}
err = d.processSOS(n) err = d.processSOS(n)
case driMarker: case driMarker:
if configOnly {
err = d.ignore(n)
} else {
err = d.processDRI(n) err = d.processDRI(n)
}
case app0Marker:
err = d.processApp0Marker(n)
case app14Marker: case app14Marker:
err = d.processApp14Marker(n) err = d.processApp14Marker(n)
default: default:
...@@ -585,6 +620,8 @@ func (d *decoder) decode(r io.Reader, configOnly bool) (image.Image, error) { ...@@ -585,6 +620,8 @@ func (d *decoder) decode(r io.Reader, configOnly bool) (image.Image, error) {
if d.img3 != nil { if d.img3 != nil {
if d.blackPix != nil { if d.blackPix != nil {
return d.applyBlack() return d.applyBlack()
} else if d.isRGB() {
return d.convertToRGB()
} }
return d.img3, nil return d.img3, nil
} }
...@@ -663,6 +700,36 @@ func (d *decoder) applyBlack() (image.Image, error) { ...@@ -663,6 +700,36 @@ func (d *decoder) applyBlack() (image.Image, error) {
return img, nil return img, nil
} }
func (d *decoder) isRGB() bool {
if d.jfif {
return false
}
if d.adobeTransformValid && d.adobeTransform == adobeTransformUnknown {
// http://www.sno.phy.queensu.ca/~phil/exiftool/TagNames/JPEG.html#Adobe
// says that 0 means Unknown (and in practice RGB) and 1 means YCbCr.
return true
}
return d.comp[0].c == 'R' && d.comp[1].c == 'G' && d.comp[2].c == 'B'
}
func (d *decoder) convertToRGB() (image.Image, error) {
cScale := d.comp[0].h / d.comp[1].h
bounds := d.img3.Bounds()
img := image.NewRGBA(bounds)
for y := bounds.Min.Y; y < bounds.Max.Y; y++ {
po := img.PixOffset(bounds.Min.X, y)
yo := d.img3.YOffset(bounds.Min.X, y)
co := d.img3.COffset(bounds.Min.X, y)
for i, iMax := 0, bounds.Max.X-bounds.Min.X; i < iMax; i++ {
img.Pix[po+4*i+0] = d.img3.Y[yo+i]
img.Pix[po+4*i+1] = d.img3.Cb[co+i/cScale]
img.Pix[po+4*i+2] = d.img3.Cr[co+i/cScale]
img.Pix[po+4*i+3] = 255
}
}
return img, nil
}
// drawYCbCr is the non-exported drawYCbCr function copy/pasted from the // drawYCbCr is the non-exported drawYCbCr function copy/pasted from the
// image/draw package. It is copy/pasted because it doesn't seem right for the // image/draw package. It is copy/pasted because it doesn't seem right for the
// image/jpeg package to depend on image/draw. // image/jpeg package to depend on image/draw.
...@@ -760,8 +827,12 @@ func DecodeConfig(r io.Reader) (image.Config, error) { ...@@ -760,8 +827,12 @@ func DecodeConfig(r io.Reader) (image.Config, error) {
Height: d.height, Height: d.height,
}, nil }, nil
case 3: case 3:
cm := color.YCbCrModel
if d.isRGB() {
cm = color.RGBAModel
}
return image.Config{ return image.Config{
ColorModel: color.YCbCrModel, // TODO(nigeltao): support RGB JPEGs. ColorModel: cm,
Width: d.width, Width: d.width,
Height: d.height, Height: d.height,
}, nil }, nil
......
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