go.image/tiff: support 16bit RGB
R=nigeltao, bsiegert CC=golang-dev https://golang.org/cl/13736044
This commit is contained in:
parent
e39b2394e5
commit
b2d744f611
|
@ -198,7 +198,21 @@ func (d *decoder) decode(dst image.Image, xmin, ymin, xmax, ymax int) error {
|
||||||
// Apply horizontal predictor if necessary.
|
// Apply horizontal predictor if necessary.
|
||||||
// In this case, p contains the color difference to the preceding pixel.
|
// In this case, p contains the color difference to the preceding pixel.
|
||||||
// See page 64-65 of the spec.
|
// See page 64-65 of the spec.
|
||||||
if d.firstVal(tPredictor) == prHorizontal && d.bpp == 8 {
|
if d.firstVal(tPredictor) == prHorizontal {
|
||||||
|
if d.bpp == 16 {
|
||||||
|
var off int
|
||||||
|
spp := len(d.features[tBitsPerSample]) // samples per pixel
|
||||||
|
bpp := spp * 2 // bytes per pixel
|
||||||
|
for y := ymin; y < ymax; y++ {
|
||||||
|
off += spp * 2
|
||||||
|
for x := 0; x < (xmax-xmin-1)*bpp; x += 2 {
|
||||||
|
v0 := d.byteOrder.Uint16(d.buf[off-bpp : off-bpp+2])
|
||||||
|
v1 := d.byteOrder.Uint16(d.buf[off : off+2])
|
||||||
|
d.byteOrder.PutUint16(d.buf[off:off+2], v1+v0)
|
||||||
|
off += 2
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if d.bpp == 8 {
|
||||||
var off int
|
var off int
|
||||||
spp := len(d.features[tBitsPerSample]) // samples per pixel
|
spp := len(d.features[tBitsPerSample]) // samples per pixel
|
||||||
for y := ymin; y < ymax; y++ {
|
for y := ymin; y < ymax; y++ {
|
||||||
|
@ -209,6 +223,7 @@ func (d *decoder) decode(dst image.Image, xmin, ymin, xmax, ymax int) error {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
rMaxX := minInt(xmax, dst.Bounds().Max.X)
|
rMaxX := minInt(xmax, dst.Bounds().Max.X)
|
||||||
rMaxY := minInt(ymax, dst.Bounds().Max.Y)
|
rMaxY := minInt(ymax, dst.Bounds().Max.Y)
|
||||||
|
@ -249,6 +264,18 @@ func (d *decoder) decode(dst image.Image, xmin, ymin, xmax, ymax int) error {
|
||||||
d.flushBits()
|
d.flushBits()
|
||||||
}
|
}
|
||||||
case mRGB:
|
case mRGB:
|
||||||
|
if d.bpp == 16 {
|
||||||
|
img := dst.(*image.RGBA64)
|
||||||
|
for y := ymin; y < rMaxY; y++ {
|
||||||
|
for x := xmin; x < rMaxX; x++ {
|
||||||
|
r := d.byteOrder.Uint16(d.buf[d.off+0 : d.off+2])
|
||||||
|
g := d.byteOrder.Uint16(d.buf[d.off+2 : d.off+4])
|
||||||
|
b := d.byteOrder.Uint16(d.buf[d.off+4 : d.off+6])
|
||||||
|
d.off += 6
|
||||||
|
img.SetRGBA64(x, y, color.RGBA64{r, g, b, 0xffff})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
img := dst.(*image.RGBA)
|
img := dst.(*image.RGBA)
|
||||||
for y := ymin; y < rMaxY; y++ {
|
for y := ymin; y < rMaxY; y++ {
|
||||||
min := img.PixOffset(xmin, y)
|
min := img.PixOffset(xmin, y)
|
||||||
|
@ -262,7 +289,21 @@ func (d *decoder) decode(dst image.Image, xmin, ymin, xmax, ymax int) error {
|
||||||
off += 3
|
off += 3
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
case mNRGBA:
|
case mNRGBA:
|
||||||
|
if d.bpp == 16 {
|
||||||
|
img := dst.(*image.NRGBA64)
|
||||||
|
for y := ymin; y < rMaxY; y++ {
|
||||||
|
for x := xmin; x < rMaxX; x++ {
|
||||||
|
r := d.byteOrder.Uint16(d.buf[d.off+0 : d.off+2])
|
||||||
|
g := d.byteOrder.Uint16(d.buf[d.off+2 : d.off+4])
|
||||||
|
b := d.byteOrder.Uint16(d.buf[d.off+4 : d.off+6])
|
||||||
|
a := d.byteOrder.Uint16(d.buf[d.off+6 : d.off+8])
|
||||||
|
d.off += 8
|
||||||
|
img.SetNRGBA64(x, y, color.NRGBA64{r, g, b, a})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
img := dst.(*image.NRGBA)
|
img := dst.(*image.NRGBA)
|
||||||
for y := ymin; y < rMaxY; y++ {
|
for y := ymin; y < rMaxY; y++ {
|
||||||
min := img.PixOffset(xmin, y)
|
min := img.PixOffset(xmin, y)
|
||||||
|
@ -270,7 +311,21 @@ func (d *decoder) decode(dst image.Image, xmin, ymin, xmax, ymax int) error {
|
||||||
buf := d.buf[(y-ymin)*(xmax-xmin)*4 : (y-ymin+1)*(xmax-xmin)*4]
|
buf := d.buf[(y-ymin)*(xmax-xmin)*4 : (y-ymin+1)*(xmax-xmin)*4]
|
||||||
copy(img.Pix[min:max], buf)
|
copy(img.Pix[min:max], buf)
|
||||||
}
|
}
|
||||||
|
}
|
||||||
case mRGBA:
|
case mRGBA:
|
||||||
|
if d.bpp == 16 {
|
||||||
|
img := dst.(*image.RGBA64)
|
||||||
|
for y := ymin; y < rMaxY; y++ {
|
||||||
|
for x := xmin; x < rMaxX; x++ {
|
||||||
|
r := d.byteOrder.Uint16(d.buf[d.off+0 : d.off+2])
|
||||||
|
g := d.byteOrder.Uint16(d.buf[d.off+2 : d.off+4])
|
||||||
|
b := d.byteOrder.Uint16(d.buf[d.off+4 : d.off+6])
|
||||||
|
a := d.byteOrder.Uint16(d.buf[d.off+6 : d.off+8])
|
||||||
|
d.off += 8
|
||||||
|
img.SetRGBA64(x, y, color.RGBA64{r, g, b, a})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
img := dst.(*image.RGBA)
|
img := dst.(*image.RGBA)
|
||||||
for y := ymin; y < rMaxY; y++ {
|
for y := ymin; y < rMaxY; y++ {
|
||||||
min := img.PixOffset(xmin, y)
|
min := img.PixOffset(xmin, y)
|
||||||
|
@ -279,6 +334,7 @@ func (d *decoder) decode(dst image.Image, xmin, ymin, xmax, ymax int) error {
|
||||||
copy(img.Pix[min:max], buf)
|
copy(img.Pix[min:max], buf)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
@ -333,12 +389,19 @@ func newDecoder(r io.Reader) (*decoder, error) {
|
||||||
// Determine the image mode.
|
// Determine the image mode.
|
||||||
switch d.firstVal(tPhotometricInterpretation) {
|
switch d.firstVal(tPhotometricInterpretation) {
|
||||||
case pRGB:
|
case pRGB:
|
||||||
|
if d.bpp == 16 {
|
||||||
|
for _, b := range d.features[tBitsPerSample] {
|
||||||
|
if b != 16 {
|
||||||
|
return nil, FormatError("wrong number of samples for 16bit RGB")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
for _, b := range d.features[tBitsPerSample] {
|
for _, b := range d.features[tBitsPerSample] {
|
||||||
if b != 8 {
|
if b != 8 {
|
||||||
return nil, UnsupportedError("non-8-bit RGB image")
|
return nil, FormatError("wrong number of samples for 8bit RGB")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
d.config.ColorModel = color.RGBAModel
|
|
||||||
// RGB images normally have 3 samples per pixel.
|
// RGB images normally have 3 samples per pixel.
|
||||||
// If there are more, ExtraSamples (p. 31-32 of the spec)
|
// If there are more, ExtraSamples (p. 31-32 of the spec)
|
||||||
// gives their meaning (usually an alpha channel).
|
// gives their meaning (usually an alpha channel).
|
||||||
|
@ -348,13 +411,27 @@ func newDecoder(r io.Reader) (*decoder, error) {
|
||||||
switch len(d.features[tBitsPerSample]) {
|
switch len(d.features[tBitsPerSample]) {
|
||||||
case 3:
|
case 3:
|
||||||
d.mode = mRGB
|
d.mode = mRGB
|
||||||
|
if d.bpp == 16 {
|
||||||
|
d.config.ColorModel = color.RGBA64Model
|
||||||
|
} else {
|
||||||
|
d.config.ColorModel = color.RGBAModel
|
||||||
|
}
|
||||||
case 4:
|
case 4:
|
||||||
switch d.firstVal(tExtraSamples) {
|
switch d.firstVal(tExtraSamples) {
|
||||||
case 1:
|
case 1:
|
||||||
d.mode = mRGBA
|
d.mode = mRGBA
|
||||||
|
if d.bpp == 16 {
|
||||||
|
d.config.ColorModel = color.RGBA64Model
|
||||||
|
} else {
|
||||||
|
d.config.ColorModel = color.RGBAModel
|
||||||
|
}
|
||||||
case 2:
|
case 2:
|
||||||
d.mode = mNRGBA
|
d.mode = mNRGBA
|
||||||
|
if d.bpp == 16 {
|
||||||
|
d.config.ColorModel = color.NRGBA64Model
|
||||||
|
} else {
|
||||||
d.config.ColorModel = color.NRGBAModel
|
d.config.ColorModel = color.NRGBAModel
|
||||||
|
}
|
||||||
default:
|
default:
|
||||||
return nil, FormatError("wrong number of samples for RGB")
|
return nil, FormatError("wrong number of samples for RGB")
|
||||||
}
|
}
|
||||||
|
@ -450,10 +527,18 @@ func Decode(r io.Reader) (img image.Image, err error) {
|
||||||
case mPaletted:
|
case mPaletted:
|
||||||
img = image.NewPaletted(imgRect, d.palette)
|
img = image.NewPaletted(imgRect, d.palette)
|
||||||
case mNRGBA:
|
case mNRGBA:
|
||||||
|
if d.bpp == 16 {
|
||||||
|
img = image.NewNRGBA64(imgRect)
|
||||||
|
} else {
|
||||||
img = image.NewNRGBA(imgRect)
|
img = image.NewNRGBA(imgRect)
|
||||||
|
}
|
||||||
case mRGB, mRGBA:
|
case mRGB, mRGBA:
|
||||||
|
if d.bpp == 16 {
|
||||||
|
img = image.NewRGBA64(imgRect)
|
||||||
|
} else {
|
||||||
img = image.NewRGBA(imgRect)
|
img = image.NewRGBA(imgRect)
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
for i := 0; i < blocksAcross; i++ {
|
for i := 0; i < blocksAcross; i++ {
|
||||||
blkW := blockWidth
|
blkW := blockWidth
|
||||||
|
|
|
@ -105,10 +105,15 @@ func TestDecode(t *testing.T) {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
|
img4, err := load("video-001-16bit.tiff")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
compare(t, img0, img1)
|
compare(t, img0, img1)
|
||||||
compare(t, img0, img2)
|
compare(t, img0, img2)
|
||||||
compare(t, img0, img3)
|
compare(t, img0, img3)
|
||||||
|
compare(t, img0, img4)
|
||||||
}
|
}
|
||||||
|
|
||||||
// TestDecompress tests that decoding some TIFF images that use different
|
// TestDecompress tests that decoding some TIFF images that use different
|
||||||
|
|
|
@ -129,6 +129,43 @@ func encodeRGBA(w io.Writer, pix []uint8, dx, dy, stride int, predictor bool) er
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func encodeRGBA64(w io.Writer, pix []uint8, dx, dy, stride int, predictor bool) error {
|
||||||
|
buf := make([]byte, dx*8)
|
||||||
|
for y := 0; y < dy; y++ {
|
||||||
|
min := y*stride + 0
|
||||||
|
max := y*stride + dx*8
|
||||||
|
off := 0
|
||||||
|
var r0, g0, b0, a0 uint16
|
||||||
|
for i := min; i < max; i += 8 {
|
||||||
|
// An image.RGBA64's Pix is in big-endian order.
|
||||||
|
r1 := uint16(pix[i+0])<<8 | uint16(pix[i+1])
|
||||||
|
g1 := uint16(pix[i+2])<<8 | uint16(pix[i+3])
|
||||||
|
b1 := uint16(pix[i+4])<<8 | uint16(pix[i+5])
|
||||||
|
a1 := uint16(pix[i+6])<<8 | uint16(pix[i+7])
|
||||||
|
if predictor {
|
||||||
|
r0, r1 = r1, r1-r0
|
||||||
|
g0, g1 = g1, g1-g0
|
||||||
|
b0, b1 = b1, b1-b0
|
||||||
|
a0, a1 = a1, a1-a0
|
||||||
|
}
|
||||||
|
// We only write little-endian TIFF files.
|
||||||
|
buf[off+0] = byte(r1)
|
||||||
|
buf[off+1] = byte(r1 >> 8)
|
||||||
|
buf[off+2] = byte(g1)
|
||||||
|
buf[off+3] = byte(g1 >> 8)
|
||||||
|
buf[off+4] = byte(b1)
|
||||||
|
buf[off+5] = byte(b1 >> 8)
|
||||||
|
buf[off+6] = byte(a1)
|
||||||
|
buf[off+7] = byte(a1 >> 8)
|
||||||
|
off += 8
|
||||||
|
}
|
||||||
|
if _, err := w.Write(buf); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
func encode(w io.Writer, m image.Image, predictor bool) error {
|
func encode(w io.Writer, m image.Image, predictor bool) error {
|
||||||
bounds := m.Bounds()
|
bounds := m.Bounds()
|
||||||
buf := make([]byte, 4*bounds.Dx())
|
buf := make([]byte, 4*bounds.Dx())
|
||||||
|
@ -287,6 +324,10 @@ func Encode(w io.Writer, m image.Image, opt *Options) error {
|
||||||
imageLen = d.X * d.Y * 1
|
imageLen = d.X * d.Y * 1
|
||||||
case *image.Gray16:
|
case *image.Gray16:
|
||||||
imageLen = d.X * d.Y * 2
|
imageLen = d.X * d.Y * 2
|
||||||
|
case *image.RGBA64:
|
||||||
|
imageLen = d.X * d.Y * 8
|
||||||
|
case *image.NRGBA64:
|
||||||
|
imageLen = d.X * d.Y * 8
|
||||||
default:
|
default:
|
||||||
imageLen = d.X * d.Y * 4
|
imageLen = d.X * d.Y * 4
|
||||||
}
|
}
|
||||||
|
@ -334,8 +375,15 @@ func Encode(w io.Writer, m image.Image, opt *Options) error {
|
||||||
case *image.NRGBA:
|
case *image.NRGBA:
|
||||||
extrasamples = 2 // Unassociated alpha.
|
extrasamples = 2 // Unassociated alpha.
|
||||||
err = encodeRGBA(dst, m.Pix, d.X, d.Y, m.Stride, predictor)
|
err = encodeRGBA(dst, m.Pix, d.X, d.Y, m.Stride, predictor)
|
||||||
|
case *image.NRGBA64:
|
||||||
|
extrasamples = 2 // Unassociated alpha.
|
||||||
|
bitsPerSample = []uint32{16, 16, 16, 16}
|
||||||
|
err = encodeRGBA64(dst, m.Pix, d.X, d.Y, m.Stride, predictor)
|
||||||
case *image.RGBA:
|
case *image.RGBA:
|
||||||
err = encodeRGBA(dst, m.Pix, d.X, d.Y, m.Stride, predictor)
|
err = encodeRGBA(dst, m.Pix, d.X, d.Y, m.Stride, predictor)
|
||||||
|
case *image.RGBA64:
|
||||||
|
bitsPerSample = []uint32{16, 16, 16, 16}
|
||||||
|
err = encodeRGBA64(dst, m.Pix, d.X, d.Y, m.Stride, predictor)
|
||||||
default:
|
default:
|
||||||
err = encode(dst, m, predictor)
|
err = encode(dst, m, predictor)
|
||||||
}
|
}
|
||||||
|
|
|
@ -17,6 +17,7 @@ var roundtripTests = []struct {
|
||||||
opts *Options
|
opts *Options
|
||||||
}{
|
}{
|
||||||
{"video-001.tiff", nil},
|
{"video-001.tiff", nil},
|
||||||
|
{"video-001-16bit.tiff", nil},
|
||||||
{"video-001-gray.tiff", nil},
|
{"video-001-gray.tiff", nil},
|
||||||
{"video-001-gray-16bit.tiff", nil},
|
{"video-001-gray-16bit.tiff", nil},
|
||||||
{"video-001-paletted.tiff", nil},
|
{"video-001-paletted.tiff", nil},
|
||||||
|
@ -90,3 +91,5 @@ func BenchmarkEncode(b *testing.B) { benchmarkEncode(b, "video-001.tiff"
|
||||||
func BenchmarkEncodePaletted(b *testing.B) { benchmarkEncode(b, "video-001-paletted.tiff", 1) }
|
func BenchmarkEncodePaletted(b *testing.B) { benchmarkEncode(b, "video-001-paletted.tiff", 1) }
|
||||||
func BenchmarkEncodeGray(b *testing.B) { benchmarkEncode(b, "video-001-gray.tiff", 1) }
|
func BenchmarkEncodeGray(b *testing.B) { benchmarkEncode(b, "video-001-gray.tiff", 1) }
|
||||||
func BenchmarkEncodeGray16(b *testing.B) { benchmarkEncode(b, "video-001-gray-16bit.tiff", 2) }
|
func BenchmarkEncodeGray16(b *testing.B) { benchmarkEncode(b, "video-001-gray-16bit.tiff", 2) }
|
||||||
|
func BenchmarkEncodeRGBA(b *testing.B) { benchmarkEncode(b, "video-001.tiff", 4) }
|
||||||
|
func BenchmarkEncodeRGBA64(b *testing.B) { benchmarkEncode(b, "video-001-16bit.tiff", 8) }
|
||||||
|
|
Loading…
Reference in New Issue
Block a user