go.image/tiff: use optimized routines for RGBA and NRGBA
Performance for an RGBA image: benchmark old ns/op new ns/op delta BenchmarkEncode 1633495 7897 -99.52% benchmark old MB/s new MB/s speedup BenchmarkEncode 37.83 7824.96 206.85x NRGBA images are now encoded as such, the spec calls it "unassociated alpha". R=nigeltao, rsc CC=golang-dev https://golang.org/cl/6308049
This commit is contained in:
parent
de792da79c
commit
c35a1ecb6c
|
@ -58,14 +58,17 @@ func TestUnpackBits(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func compare(t *testing.T, img0, img1 image.Image) {
|
func compare(t *testing.T, img0, img1 image.Image) {
|
||||||
b := img1.Bounds()
|
b0 := img0.Bounds()
|
||||||
if !b.Eq(img0.Bounds()) {
|
b1 := img1.Bounds()
|
||||||
t.Fatalf("wrong image size: want %s, got %s", img0.Bounds(), b)
|
if b0.Dx() != b1.Dx() || b0.Dy() != b1.Dy() {
|
||||||
|
t.Fatalf("wrong image size: want %s, got %s", b0, b1)
|
||||||
}
|
}
|
||||||
for y := b.Min.Y; y < b.Max.Y; y++ {
|
x1 := b1.Min.X - b0.Min.X
|
||||||
for x := b.Min.X; x < b.Max.X; x++ {
|
y1 := b1.Min.Y - b0.Min.Y
|
||||||
|
for y := b0.Min.Y; y < b0.Max.Y; y++ {
|
||||||
|
for x := b0.Min.X; x < b0.Max.X; x++ {
|
||||||
c0 := img0.At(x, y)
|
c0 := img0.At(x, y)
|
||||||
c1 := img1.At(x, y)
|
c1 := img1.At(x+x1, y+y1)
|
||||||
r0, g0, b0, a0 := c0.RGBA()
|
r0, g0, b0, a0 := c0.RGBA()
|
||||||
r1, g1, b1, a1 := c1.RGBA()
|
r1, g1, b1, a1 := c1.RGBA()
|
||||||
if r0 != r1 || g0 != g1 || b0 != b1 || a0 != a1 {
|
if r0 != r1 || g0 != g1 || b0 != b1 || a0 != a1 {
|
||||||
|
|
|
@ -73,6 +73,20 @@ func writeImgData(w io.Writer, m image.Image) error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func writePix(w io.Writer, pix []byte, nrows, length, stride int) error {
|
||||||
|
if length == stride {
|
||||||
|
_, err := w.Write(pix[:nrows*length])
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
for ; nrows > 0; nrows-- {
|
||||||
|
if _, err := w.Write(pix[:length]); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
pix = pix[stride:]
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
func writeIFD(w io.Writer, ifdOffset int, d []ifdEntry) error {
|
func writeIFD(w io.Writer, ifdOffset int, d []ifdEntry) error {
|
||||||
var buf [ifdLen]byte
|
var buf [ifdLen]byte
|
||||||
// Make space for "pointer area" containing IFD entry data
|
// Make space for "pointer area" containing IFD entry data
|
||||||
|
@ -126,8 +140,9 @@ func writeIFD(w io.Writer, ifdOffset int, d []ifdEntry) error {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
// Encode writes the image m to w in uncompressed RGBA format.
|
// Encode writes the image m to w in uncompressed 8-bit RGBA or NRGBA format.
|
||||||
func Encode(w io.Writer, m image.Image) error {
|
func Encode(w io.Writer, m image.Image) error {
|
||||||
|
var extrasamples uint32
|
||||||
_, err := io.WriteString(w, leHeader)
|
_, err := io.WriteString(w, leHeader)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
|
@ -142,7 +157,19 @@ func Encode(w io.Writer, m image.Image) error {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
err = writeImgData(w, m)
|
switch img := m.(type) {
|
||||||
|
case *image.NRGBA:
|
||||||
|
extrasamples = 2 // Unassociated alpha.
|
||||||
|
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)
|
||||||
|
case *image.RGBA:
|
||||||
|
extrasamples = 1 // Associated alpha.
|
||||||
|
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:
|
||||||
|
extrasamples = 1 // Associated alpha.
|
||||||
|
err = writeImgData(w, m)
|
||||||
|
}
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
@ -161,6 +188,6 @@ func Encode(w io.Writer, m image.Image) 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}},
|
||||||
{tExtraSamples, dtShort, []uint32{1}}, // RGBA.
|
{tExtraSamples, dtShort, []uint32{extrasamples}},
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
|
@ -46,6 +46,24 @@ func TestRoundtrip(t *testing.T) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TestRoundtrip2 tests that encoding and decoding an image whose
|
||||||
|
// origin is not (0, 0) gives the same thing.
|
||||||
|
func TestRoundtrip2(t *testing.T) {
|
||||||
|
m0 := image.NewRGBA(image.Rect(3, 4, 9, 8))
|
||||||
|
for i := range m0.Pix {
|
||||||
|
m0.Pix[i] = byte(i)
|
||||||
|
}
|
||||||
|
out := new(bytes.Buffer)
|
||||||
|
if err := Encode(out, m0); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
m1, err := Decode(&buffer{buf: out.Bytes()})
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
compare(t, m0, m1)
|
||||||
|
}
|
||||||
|
|
||||||
// BenchmarkEncode benchmarks the encoding of an image.
|
// BenchmarkEncode benchmarks the encoding of an image.
|
||||||
func BenchmarkEncode(b *testing.B) {
|
func BenchmarkEncode(b *testing.B) {
|
||||||
img, err := openImage("video-001.tiff")
|
img, err := openImage("video-001.tiff")
|
||||||
|
|
Loading…
Reference in New Issue
Block a user