tiff: support writing files with differencing predictor.

This will make more sense after implementing compression.

R=nigeltao
CC=golang-dev
https://golang.org/cl/6567077
This commit is contained in:
Benny Siegert 2012-10-03 10:11:21 +10:00 committed by Nigel Tao
parent 5198c64fc9
commit 41cf08abcf
2 changed files with 60 additions and 27 deletions

View File

@ -53,11 +53,29 @@ func (d byTag) Len() int { return len(d) }
func (d byTag) Less(i, j int) bool { return d[i].tag < d[j].tag } func (d byTag) Less(i, j int) bool { return d[i].tag < d[j].tag }
func (d byTag) Swap(i, j int) { d[i], d[j] = d[j], d[i] } func (d byTag) Swap(i, j int) { d[i], d[j] = d[j], d[i] }
func writeImgData(w io.Writer, m image.Image) error { // writeImgData writes the raw data of m into w, optionally using a
// differencing predictor.
func writeImgData(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())
for y := bounds.Min.Y; y < bounds.Max.Y; y++ { for y := bounds.Min.Y; y < bounds.Max.Y; y++ {
i := 0 i := 0
if predictor {
var r0, g0, b0, a0 uint8
for x := bounds.Min.X; x < bounds.Max.X; x++ {
r, g, b, a := m.At(x, y).RGBA()
r1 := uint8(r >> 8)
g1 := uint8(g >> 8)
b1 := uint8(b >> 8)
a1 := uint8(a >> 8)
buf[i+0] = r1 - r0
buf[i+1] = g1 - g0
buf[i+2] = b1 - b0
buf[i+3] = a1 - a0
i += 4
r0, g0, b0, a0 = r1, g1, b1, a1
}
} else {
for x := bounds.Min.X; x < bounds.Max.X; x++ { for x := bounds.Min.X; x < bounds.Max.X; x++ {
r, g, b, a := m.At(x, y).RGBA() r, g, b, a := m.At(x, y).RGBA()
buf[i+0] = uint8(r >> 8) buf[i+0] = uint8(r >> 8)
@ -66,6 +84,7 @@ func writeImgData(w io.Writer, m image.Image) error {
buf[i+3] = uint8(a >> 8) buf[i+3] = uint8(a >> 8)
i += 4 i += 4
} }
}
if _, err := w.Write(buf); err != nil { if _, err := w.Write(buf); err != nil {
return err return err
} }
@ -73,6 +92,9 @@ func writeImgData(w io.Writer, m image.Image) error {
return nil return nil
} }
// writePix writes the internal byte array of an image to w. It is less general
// but much faster then writeImgData. writePix is used when pix directly
// corresponds to one of the TIFF image types.
func writePix(w io.Writer, pix []byte, nrows, length, stride int) error { func writePix(w io.Writer, pix []byte, nrows, length, stride int) error {
if length == stride { if length == stride {
_, err := w.Write(pix[:nrows*length]) _, err := w.Write(pix[:nrows*length])
@ -162,7 +184,7 @@ func Encode(w io.Writer, m image.Image, opt *Options) error {
predictor = opt.Predictor predictor = opt.Predictor
compression = opt.Compression compression = opt.Compression
} }
if compression != Uncompressed || predictor { if compression != Uncompressed {
return UnsupportedError("compression type") return UnsupportedError("compression type")
} }
@ -181,22 +203,28 @@ func Encode(w io.Writer, m image.Image, opt *Options) error {
if err != nil { if err != nil {
return err return err
} }
var pr uint32 = prNone
extrasamples = 1 // Associated alpha (default).
if predictor {
pr = prHorizontal
err = writeImgData(w, m, predictor)
} else {
switch img := m.(type) { switch img := m.(type) {
case *image.NRGBA: case *image.NRGBA:
extrasamples = 2 // Unassociated alpha. extrasamples = 2 // Unassociated alpha.
off := img.PixOffset(img.Rect.Min.X, img.Rect.Min.Y) off := img.PixOffset(img.Rect.Min.X, img.Rect.Min.Y)
err = writePix(w, img.Pix[off:], img.Rect.Dy(), 4*img.Rect.Dx(), img.Stride) err = writePix(w, img.Pix[off:], img.Rect.Dy(), 4*img.Rect.Dx(), img.Stride)
case *image.RGBA: case *image.RGBA:
extrasamples = 1 // Associated alpha.
off := img.PixOffset(img.Rect.Min.X, img.Rect.Min.Y) off := img.PixOffset(img.Rect.Min.X, img.Rect.Min.Y)
err = writePix(w, img.Pix[off:], img.Rect.Dy(), 4*img.Rect.Dx(), img.Stride) err = writePix(w, img.Pix[off:], img.Rect.Dy(), 4*img.Rect.Dx(), img.Stride)
default: default:
extrasamples = 1 // Associated alpha. err = writeImgData(w, m, predictor)
err = writeImgData(w, m) }
} }
if err != nil { if err != nil {
return err return err
} }
return writeIFD(w, ifdOffset, []ifdEntry{ return writeIFD(w, ifdOffset, []ifdEntry{
{tImageWidth, dtShort, []uint32{uint32(width)}}, {tImageWidth, dtShort, []uint32{uint32(width)}},
{tImageLength, dtShort, []uint32{uint32(height)}}, {tImageLength, dtShort, []uint32{uint32(height)}},
@ -212,6 +240,7 @@ func Encode(w io.Writer, m image.Image, opt *Options) error {
{tXResolution, dtRational, []uint32{72, 1}}, {tXResolution, dtRational, []uint32{72, 1}},
{tYResolution, dtRational, []uint32{72, 1}}, {tYResolution, dtRational, []uint32{72, 1}},
{tResolutionUnit, dtShort, []uint32{resPerInch}}, {tResolutionUnit, dtShort, []uint32{resPerInch}},
{tPredictor, dtShort, []uint32{pr}},
{tExtraSamples, dtShort, []uint32{extrasamples}}, {tExtraSamples, dtShort, []uint32{extrasamples}},
}) })
} }

View File

@ -12,9 +12,13 @@ import (
"testing" "testing"
) )
var roundtripTests = []string{ var roundtripTests = []struct {
"video-001.tiff", filename string
"bw-packbits.tiff", opts *Options
}{
{"video-001.tiff", nil},
{"bw-packbits.tiff", nil},
{"video-001.tiff", &Options{Predictor: true}},
} }
func openImage(filename string) (image.Image, error) { func openImage(filename string) (image.Image, error) {
@ -27,13 +31,13 @@ func openImage(filename string) (image.Image, error) {
} }
func TestRoundtrip(t *testing.T) { func TestRoundtrip(t *testing.T) {
for _, filename := range roundtripTests { for _, rt := range roundtripTests {
img, err := openImage(filename) img, err := openImage(rt.filename)
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
out := new(bytes.Buffer) out := new(bytes.Buffer)
err = Encode(out, img, nil) err = Encode(out, img, rt.opts)
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }