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:
parent
5198c64fc9
commit
41cf08abcf
|
@ -53,18 +53,37 @@ 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
|
||||||
for x := bounds.Min.X; x < bounds.Max.X; x++ {
|
if predictor {
|
||||||
r, g, b, a := m.At(x, y).RGBA()
|
var r0, g0, b0, a0 uint8
|
||||||
buf[i+0] = uint8(r >> 8)
|
for x := bounds.Min.X; x < bounds.Max.X; x++ {
|
||||||
buf[i+1] = uint8(g >> 8)
|
r, g, b, a := m.At(x, y).RGBA()
|
||||||
buf[i+2] = uint8(b >> 8)
|
r1 := uint8(r >> 8)
|
||||||
buf[i+3] = uint8(a >> 8)
|
g1 := uint8(g >> 8)
|
||||||
i += 4
|
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++ {
|
||||||
|
r, g, b, a := m.At(x, y).RGBA()
|
||||||
|
buf[i+0] = uint8(r >> 8)
|
||||||
|
buf[i+1] = uint8(g >> 8)
|
||||||
|
buf[i+2] = uint8(b >> 8)
|
||||||
|
buf[i+3] = uint8(a >> 8)
|
||||||
|
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
|
||||||
}
|
}
|
||||||
switch img := m.(type) {
|
var pr uint32 = prNone
|
||||||
case *image.NRGBA:
|
extrasamples = 1 // Associated alpha (default).
|
||||||
extrasamples = 2 // Unassociated alpha.
|
if predictor {
|
||||||
off := img.PixOffset(img.Rect.Min.X, img.Rect.Min.Y)
|
pr = prHorizontal
|
||||||
err = writePix(w, img.Pix[off:], img.Rect.Dy(), 4*img.Rect.Dx(), img.Stride)
|
err = writeImgData(w, m, predictor)
|
||||||
case *image.RGBA:
|
} else {
|
||||||
extrasamples = 1 // Associated alpha.
|
switch img := m.(type) {
|
||||||
off := img.PixOffset(img.Rect.Min.X, img.Rect.Min.Y)
|
case *image.NRGBA:
|
||||||
err = writePix(w, img.Pix[off:], img.Rect.Dy(), 4*img.Rect.Dx(), img.Stride)
|
extrasamples = 2 // Unassociated alpha.
|
||||||
default:
|
off := img.PixOffset(img.Rect.Min.X, img.Rect.Min.Y)
|
||||||
extrasamples = 1 // Associated alpha.
|
err = writePix(w, img.Pix[off:], img.Rect.Dy(), 4*img.Rect.Dx(), img.Stride)
|
||||||
err = writeImgData(w, m)
|
case *image.RGBA:
|
||||||
|
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)
|
||||||
|
default:
|
||||||
|
err = writeImgData(w, m, predictor)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
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}},
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
|
@ -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)
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue
Block a user