mirror of
https://github.com/golang/go
synced 2024-11-23 20:20:01 -07:00
image/jpeg: support chroma hv values other than 0x11.
The testdata was generated by: convert video-001.png tmp1.tga cjpeg -quality 100 -sample 2x2,1x2,1x2 tmp1.tga > video-001.221212.jpeg djpeg -nosmooth -targa video-001.221212.jpeg > tmp2.tga convert tmp2.tga video-001.221212.png rm tmp1.tga tmp2.tga Change-Id: Ica241dfc19b3eb47ade150bf0432373c6006c38a Reviewed-on: https://go-review.googlesource.com/7264 Reviewed-by: Rob Pike <r@golang.org>
This commit is contained in:
parent
532bc5f11c
commit
782db7fc88
@ -6,6 +6,7 @@ package image_test
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"bufio"
|
"bufio"
|
||||||
|
"fmt"
|
||||||
"image"
|
"image"
|
||||||
"image/color"
|
"image/color"
|
||||||
"os"
|
"os"
|
||||||
@ -32,6 +33,7 @@ var imageTests = []imageTest{
|
|||||||
// JPEG is a lossy format and hence needs a non-zero tolerance.
|
// JPEG is a lossy format and hence needs a non-zero tolerance.
|
||||||
{"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.221212.png", "testdata/video-001.221212.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},
|
{"testdata/video-001.rgb.png", "testdata/video-001.rgb.jpeg", 8 << 8},
|
||||||
// Grayscale images.
|
// Grayscale images.
|
||||||
@ -76,6 +78,11 @@ func withinTolerance(c0, c1 color.Color, tolerance int) bool {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestDecode(t *testing.T) {
|
func TestDecode(t *testing.T) {
|
||||||
|
rgba := func(c color.Color) string {
|
||||||
|
r, g, b, a := c.RGBA()
|
||||||
|
return fmt.Sprintf("rgba = 0x%04x, 0x%04x, 0x%04x, 0x%04x for %T%v", r, g, b, a, c, c)
|
||||||
|
}
|
||||||
|
|
||||||
golden := make(map[string]image.Image)
|
golden := make(map[string]image.Image)
|
||||||
loop:
|
loop:
|
||||||
for _, it := range imageTests {
|
for _, it := range imageTests {
|
||||||
@ -96,13 +103,14 @@ loop:
|
|||||||
}
|
}
|
||||||
b := g.Bounds()
|
b := g.Bounds()
|
||||||
if !b.Eq(m.Bounds()) {
|
if !b.Eq(m.Bounds()) {
|
||||||
t.Errorf("%s: want bounds %v got %v", it.filename, b, m.Bounds())
|
t.Errorf("%s: got bounds %v want %v", it.filename, m.Bounds(), b)
|
||||||
continue loop
|
continue loop
|
||||||
}
|
}
|
||||||
for y := b.Min.Y; y < b.Max.Y; y++ {
|
for y := b.Min.Y; y < b.Max.Y; y++ {
|
||||||
for x := b.Min.X; x < b.Max.X; x++ {
|
for x := b.Min.X; x < b.Max.X; x++ {
|
||||||
if !withinTolerance(g.At(x, y), m.At(x, y), it.tolerance) {
|
if !withinTolerance(g.At(x, y), m.At(x, y), it.tolerance) {
|
||||||
t.Errorf("%s: at (%d, %d), want %v got %v", it.filename, x, y, g.At(x, y), m.At(x, y))
|
t.Errorf("%s: at (%d, %d):\ngot %v\nwant %v",
|
||||||
|
it.filename, x, y, rgba(m.At(x, y)), rgba(g.At(x, y)))
|
||||||
continue loop
|
continue loop
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -26,6 +26,8 @@ type UnsupportedError string
|
|||||||
|
|
||||||
func (e UnsupportedError) Error() string { return "unsupported JPEG feature: " + string(e) }
|
func (e UnsupportedError) Error() string { return "unsupported JPEG feature: " + string(e) }
|
||||||
|
|
||||||
|
var errUnsupportedSubsamplingRatio = UnsupportedError("luma/chroma subsampling ratio")
|
||||||
|
|
||||||
// Component specification, specified in section B.2.2.
|
// Component specification, specified in section B.2.2.
|
||||||
type component struct {
|
type component struct {
|
||||||
h int // Horizontal sampling factor.
|
h int // Horizontal sampling factor.
|
||||||
@ -303,7 +305,7 @@ func (d *decoder) processSOF(n int) error {
|
|||||||
case 6 + 3*4: // YCbCrK or CMYK image.
|
case 6 + 3*4: // YCbCrK or CMYK image.
|
||||||
d.nComp = 4
|
d.nComp = 4
|
||||||
default:
|
default:
|
||||||
return UnsupportedError("SOF has wrong length")
|
return UnsupportedError("number of components")
|
||||||
}
|
}
|
||||||
if err := d.readFull(d.tmp[:n]); err != nil {
|
if err := d.readFull(d.tmp[:n]); err != nil {
|
||||||
return err
|
return err
|
||||||
@ -315,8 +317,9 @@ func (d *decoder) processSOF(n int) 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 int(d.tmp[5]) != d.nComp {
|
if int(d.tmp[5]) != d.nComp {
|
||||||
return UnsupportedError("SOF has wrong number of image components")
|
return FormatError("SOF has wrong length")
|
||||||
}
|
}
|
||||||
|
|
||||||
for i := 0; i < d.nComp; i++ {
|
for i := 0; i < d.nComp; i++ {
|
||||||
d.comp[i].c = d.tmp[6+3*i]
|
d.comp[i].c = d.tmp[6+3*i]
|
||||||
// Section B.2.2 states that "the value of C_i shall be different from
|
// Section B.2.2 states that "the value of C_i shall be different from
|
||||||
@ -328,8 +331,16 @@ func (d *decoder) processSOF(n int) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
d.comp[i].tq = d.tmp[8+3*i]
|
d.comp[i].tq = d.tmp[8+3*i]
|
||||||
|
hv := d.tmp[7+3*i]
|
||||||
if d.nComp == 1 {
|
h, v := int(hv>>4), int(hv&0x0f)
|
||||||
|
if h < 1 || 4 < h || v < 1 || 4 < v {
|
||||||
|
return FormatError("luma/chroma subsampling ratio")
|
||||||
|
}
|
||||||
|
if h == 3 || v == 3 {
|
||||||
|
return errUnsupportedSubsamplingRatio
|
||||||
|
}
|
||||||
|
switch d.nComp {
|
||||||
|
case 1:
|
||||||
// If a JPEG image has only one component, section A.2 says "this data
|
// If a JPEG image has only one component, section A.2 says "this data
|
||||||
// is non-interleaved by definition" and section A.2.2 says "[in this
|
// is non-interleaved by definition" and section A.2.2 says "[in this
|
||||||
// case...] the order of data units within a scan shall be left-to-right
|
// case...] the order of data units within a scan shall be left-to-right
|
||||||
@ -341,27 +352,34 @@ func (d *decoder) processSOF(n int) error {
|
|||||||
// always 1. The component's (h, v) is effectively always (1, 1): even if
|
// always 1. The component's (h, v) is effectively always (1, 1): even if
|
||||||
// the nominal (h, v) is (2, 1), a 20x5 image is encoded in three 8x8
|
// the nominal (h, v) is (2, 1), a 20x5 image is encoded in three 8x8
|
||||||
// MCUs, not two 16x8 MCUs.
|
// MCUs, not two 16x8 MCUs.
|
||||||
d.comp[i].h = 1
|
h, v = 1, 1
|
||||||
d.comp[i].v = 1
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
hv := d.tmp[7+3*i]
|
|
||||||
d.comp[i].h = int(hv >> 4)
|
|
||||||
d.comp[i].v = int(hv & 0x0f)
|
|
||||||
switch d.nComp {
|
|
||||||
case 3:
|
case 3:
|
||||||
// For YCbCr images, we only support 4:4:4, 4:4:0, 4:2:2, 4:2:0,
|
// For YCbCr images, we only support 4:4:4, 4:4:0, 4:2:2, 4:2:0,
|
||||||
// 4:1:1 or 4:1:0 chroma downsampling ratios. This implies that the
|
// 4:1:1 or 4:1:0 chroma subsampling ratios. This implies that the
|
||||||
// (h, v) values for the Y component are either (1, 1), (1, 2),
|
// (h, v) values for the Y component are either (1, 1), (1, 2),
|
||||||
// (2, 1), (2, 2), (4, 1) or (4, 2), and the (h, v) values for the Cr
|
// (2, 1), (2, 2), (4, 1) or (4, 2), and the Y component's values
|
||||||
// and Cb components must be (1, 1).
|
// must be a multiple of the Cb and Cr component's values. We also
|
||||||
if i == 0 {
|
// assume that the two chroma components have the same subsampling
|
||||||
if hv != 0x11 && hv != 0x21 && hv != 0x22 && hv != 0x12 && hv != 0x41 && hv != 0x42 {
|
// ratio.
|
||||||
return UnsupportedError("luma/chroma downsample ratio")
|
switch i {
|
||||||
|
case 0: // Y.
|
||||||
|
// We have already verified, above, that h and v are both
|
||||||
|
// either 1, 2 or 4, so invalid (h, v) combinations are those
|
||||||
|
// with v == 4.
|
||||||
|
if v == 4 {
|
||||||
|
return errUnsupportedSubsamplingRatio
|
||||||
}
|
}
|
||||||
} else if hv != 0x11 {
|
case 1: // Cb.
|
||||||
return UnsupportedError("luma/chroma downsample ratio")
|
if d.comp[0].h%h != 0 || d.comp[0].v%v != 0 {
|
||||||
|
return errUnsupportedSubsamplingRatio
|
||||||
}
|
}
|
||||||
|
case 2: // Cr.
|
||||||
|
if d.comp[1].h != h || d.comp[1].v != v {
|
||||||
|
return errUnsupportedSubsamplingRatio
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
case 4:
|
case 4:
|
||||||
// For 4-component images (either CMYK or YCbCrK), we only support two
|
// For 4-component images (either CMYK or YCbCrK), we only support two
|
||||||
// hv vectors: [0x11 0x11 0x11 0x11] and [0x22 0x11 0x11 0x22].
|
// hv vectors: [0x11 0x11 0x11 0x11] and [0x22 0x11 0x11 0x22].
|
||||||
@ -375,18 +393,21 @@ func (d *decoder) processSOF(n int) error {
|
|||||||
switch i {
|
switch i {
|
||||||
case 0:
|
case 0:
|
||||||
if hv != 0x11 && hv != 0x22 {
|
if hv != 0x11 && hv != 0x22 {
|
||||||
return UnsupportedError("luma/chroma downsample ratio")
|
return errUnsupportedSubsamplingRatio
|
||||||
}
|
}
|
||||||
case 1, 2:
|
case 1, 2:
|
||||||
if hv != 0x11 {
|
if hv != 0x11 {
|
||||||
return UnsupportedError("luma/chroma downsample ratio")
|
return errUnsupportedSubsamplingRatio
|
||||||
}
|
}
|
||||||
case 3:
|
case 3:
|
||||||
if d.comp[0].h != d.comp[3].h || d.comp[0].v != d.comp[3].v {
|
if d.comp[0].h != h || d.comp[0].v != v {
|
||||||
return UnsupportedError("luma/chroma downsample ratio")
|
return errUnsupportedSubsamplingRatio
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
d.comp[i].h = h
|
||||||
|
d.comp[i].v = v
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
@ -9,26 +9,30 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
// makeImg allocates and initializes the destination image.
|
// makeImg allocates and initializes the destination image.
|
||||||
func (d *decoder) makeImg(h0, v0, mxx, myy int) {
|
func (d *decoder) makeImg(mxx, myy int) {
|
||||||
if d.nComp == 1 {
|
if d.nComp == 1 {
|
||||||
m := image.NewGray(image.Rect(0, 0, 8*mxx, 8*myy))
|
m := image.NewGray(image.Rect(0, 0, 8*mxx, 8*myy))
|
||||||
d.img1 = m.SubImage(image.Rect(0, 0, d.width, d.height)).(*image.Gray)
|
d.img1 = m.SubImage(image.Rect(0, 0, d.width, d.height)).(*image.Gray)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
h0 := d.comp[0].h
|
||||||
|
v0 := d.comp[0].v
|
||||||
|
hRatio := h0 / d.comp[1].h
|
||||||
|
vRatio := v0 / d.comp[1].v
|
||||||
var subsampleRatio image.YCbCrSubsampleRatio
|
var subsampleRatio image.YCbCrSubsampleRatio
|
||||||
switch {
|
switch hRatio<<4 | vRatio {
|
||||||
case h0 == 1 && v0 == 1:
|
case 0x11:
|
||||||
subsampleRatio = image.YCbCrSubsampleRatio444
|
subsampleRatio = image.YCbCrSubsampleRatio444
|
||||||
case h0 == 1 && v0 == 2:
|
case 0x12:
|
||||||
subsampleRatio = image.YCbCrSubsampleRatio440
|
subsampleRatio = image.YCbCrSubsampleRatio440
|
||||||
case h0 == 2 && v0 == 1:
|
case 0x21:
|
||||||
subsampleRatio = image.YCbCrSubsampleRatio422
|
subsampleRatio = image.YCbCrSubsampleRatio422
|
||||||
case h0 == 2 && v0 == 2:
|
case 0x22:
|
||||||
subsampleRatio = image.YCbCrSubsampleRatio420
|
subsampleRatio = image.YCbCrSubsampleRatio420
|
||||||
case h0 == 4 && v0 == 1:
|
case 0x41:
|
||||||
subsampleRatio = image.YCbCrSubsampleRatio411
|
subsampleRatio = image.YCbCrSubsampleRatio411
|
||||||
case h0 == 4 && v0 == 2:
|
case 0x42:
|
||||||
subsampleRatio = image.YCbCrSubsampleRatio410
|
subsampleRatio = image.YCbCrSubsampleRatio410
|
||||||
default:
|
default:
|
||||||
panic("unreachable")
|
panic("unreachable")
|
||||||
@ -141,7 +145,7 @@ func (d *decoder) processSOS(n int) error {
|
|||||||
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.img1 == nil && d.img3 == nil {
|
if d.img1 == nil && d.img3 == nil {
|
||||||
d.makeImg(h0, v0, mxx, myy)
|
d.makeImg(mxx, myy)
|
||||||
}
|
}
|
||||||
if d.progressive {
|
if d.progressive {
|
||||||
for i := 0; i < nComp; i++ {
|
for i := 0; i < nComp; i++ {
|
||||||
@ -158,10 +162,8 @@ func (d *decoder) processSOS(n int) error {
|
|||||||
// b is the decoded coefficients, in natural (not zig-zag) order.
|
// b is the decoded coefficients, in natural (not zig-zag) order.
|
||||||
b block
|
b block
|
||||||
dc [maxComponents]int32
|
dc [maxComponents]int32
|
||||||
// bx and by are the location of the current (in terms of 8x8 blocks).
|
// bx and by are the location of the current block, in units of 8x8
|
||||||
// For example, with 4:2:0 chroma subsampling, the block whose top left
|
// blocks: the third block in the first row has (bx, by) = (2, 0).
|
||||||
// pixel co-ordinates are (16, 8) is the third block in the first row:
|
|
||||||
// bx is 2 and by is 0, even though the pixel is in the second MCU.
|
|
||||||
bx, by int
|
bx, by int
|
||||||
blockCount int
|
blockCount int
|
||||||
)
|
)
|
||||||
@ -169,8 +171,10 @@ func (d *decoder) processSOS(n int) error {
|
|||||||
for mx := 0; mx < mxx; mx++ {
|
for mx := 0; mx < mxx; mx++ {
|
||||||
for i := 0; i < nComp; i++ {
|
for i := 0; i < nComp; i++ {
|
||||||
compIndex := scan[i].compIndex
|
compIndex := scan[i].compIndex
|
||||||
|
hi := d.comp[compIndex].h
|
||||||
|
vi := d.comp[compIndex].v
|
||||||
qt := &d.quant[d.comp[compIndex].tq]
|
qt := &d.quant[d.comp[compIndex].tq]
|
||||||
for j := 0; j < d.comp[compIndex].h*d.comp[compIndex].v; j++ {
|
for j := 0; j < hi*vi; j++ {
|
||||||
// The blocks are traversed one MCU at a time. For 4:2:0 chroma
|
// The blocks are traversed one MCU at a time. For 4:2:0 chroma
|
||||||
// subsampling, there are four Y 8x8 blocks in every 16x16 MCU.
|
// subsampling, there are four Y 8x8 blocks in every 16x16 MCU.
|
||||||
//
|
//
|
||||||
@ -197,10 +201,10 @@ func (d *decoder) processSOS(n int) error {
|
|||||||
// 0 1 2
|
// 0 1 2
|
||||||
// 3 4 5
|
// 3 4 5
|
||||||
if nComp != 1 {
|
if nComp != 1 {
|
||||||
bx = d.comp[compIndex].h*mx + j%h0
|
bx = hi*mx + j%hi
|
||||||
by = d.comp[compIndex].v*my + j/h0
|
by = vi*my + j/hi
|
||||||
} else {
|
} else {
|
||||||
q := mxx * d.comp[compIndex].h
|
q := mxx * hi
|
||||||
bx = blockCount % q
|
bx = blockCount % q
|
||||||
by = blockCount / q
|
by = blockCount / q
|
||||||
blockCount++
|
blockCount++
|
||||||
@ -211,7 +215,7 @@ func (d *decoder) processSOS(n int) error {
|
|||||||
|
|
||||||
// Load the previous partially decoded coefficients, if applicable.
|
// Load the previous partially decoded coefficients, if applicable.
|
||||||
if d.progressive {
|
if d.progressive {
|
||||||
b = d.progCoeffs[compIndex][by*mxx*d.comp[compIndex].h+bx]
|
b = d.progCoeffs[compIndex][by*mxx*hi+bx]
|
||||||
} else {
|
} else {
|
||||||
b = block{}
|
b = block{}
|
||||||
}
|
}
|
||||||
@ -284,7 +288,7 @@ func (d *decoder) processSOS(n int) error {
|
|||||||
if d.progressive {
|
if d.progressive {
|
||||||
if zigEnd != blockSize-1 || al != 0 {
|
if zigEnd != blockSize-1 || al != 0 {
|
||||||
// We haven't completely decoded this 8x8 block. Save the coefficients.
|
// We haven't completely decoded this 8x8 block. Save the coefficients.
|
||||||
d.progCoeffs[compIndex][by*mxx*d.comp[compIndex].h+bx] = b
|
d.progCoeffs[compIndex][by*mxx*hi+bx] = b
|
||||||
// At this point, we could execute the rest of the loop body to dequantize and
|
// At this point, we could execute the rest of the loop body to dequantize and
|
||||||
// perform the inverse DCT, to save early stages of a progressive image to the
|
// perform the inverse DCT, to save early stages of a progressive image to the
|
||||||
// *image.YCbCr buffers (the whole point of progressive encoding), but in Go,
|
// *image.YCbCr buffers (the whole point of progressive encoding), but in Go,
|
||||||
|
BIN
src/image/testdata/video-001.221212.jpeg
vendored
Normal file
BIN
src/image/testdata/video-001.221212.jpeg
vendored
Normal file
Binary file not shown.
After Width: | Height: | Size: 19 KiB |
BIN
src/image/testdata/video-001.221212.png
vendored
Normal file
BIN
src/image/testdata/video-001.221212.png
vendored
Normal file
Binary file not shown.
After Width: | Height: | Size: 29 KiB |
Loading…
Reference in New Issue
Block a user