From 983986f23d81eae6ee202bd31383d04e73187536 Mon Sep 17 00:00:00 2001 From: kawakami Date: Thu, 16 May 2019 03:11:44 +0900 Subject: [PATCH] image/gif: fix transparency loss when encoding a wrapped *image.Paletted This keeps transparency of a wrapped image.Image even after it is encoded. Fixes #30995 Change-Id: I1f7ac98b1741f83ed740f6eda6c36b7e9b16e5af Reviewed-on: https://go-review.googlesource.com/c/go/+/177377 Reviewed-by: Hayato Kawakami Reviewed-by: Benny Siegert Run-TryBot: Benny Siegert TryBot-Result: Gobot Gobot --- src/image/gif/writer.go | 14 ++++- src/image/gif/writer_test.go | 81 ++++++++++++++++++++++++++-- src/image/testdata/triangle-001.gif | Bin 0 -> 1476 bytes 3 files changed, 90 insertions(+), 5 deletions(-) create mode 100644 src/image/testdata/triangle-001.gif diff --git a/src/image/gif/writer.go b/src/image/gif/writer.go index 5819df0dd0..7220446de5 100644 --- a/src/image/gif/writer.go +++ b/src/image/gif/writer.go @@ -433,8 +433,18 @@ func Encode(w io.Writer, m image.Image, o *Options) error { opts.Drawer = draw.FloydSteinberg } - pm, ok := m.(*image.Paletted) - if !ok || len(pm.Palette) > opts.NumColors { + pm, _ := m.(*image.Paletted) + if pm == nil { + if cp, ok := m.ColorModel().(color.Palette); ok { + pm = image.NewPaletted(b, cp) + for y := b.Min.Y; y < b.Max.Y; y++ { + for x := b.Min.X; x < b.Max.X; x++ { + pm.Set(x, y, cp.Convert(m.At(x, y))) + } + } + } + } + if pm == nil || len(pm.Palette) > opts.NumColors { // Set pm to be a palettedized copy of m, including its bounds, which // might not start at (0, 0). // diff --git a/src/image/gif/writer_test.go b/src/image/gif/writer_test.go index 91275bb907..0bc24d1bee 100644 --- a/src/image/gif/writer_test.go +++ b/src/image/gif/writer_test.go @@ -48,11 +48,17 @@ func delta(u0, u1 uint32) int64 { // have the same bounds. func averageDelta(m0, m1 image.Image) int64 { b := m0.Bounds() + return averageDeltaBound(m0, m1, b, b) +} + +// averageDeltaBounds returns the average delta in RGB space. The average delta is +// calulated in the specified bounds. +func averageDeltaBound(m0, m1 image.Image, b0, b1 image.Rectangle) int64 { var sum, n int64 - for y := b.Min.Y; y < b.Max.Y; y++ { - for x := b.Min.X; x < b.Max.X; x++ { + for y := b0.Min.Y; y < b0.Max.Y; y++ { + for x := b0.Min.X; x < b0.Max.X; x++ { c0 := m0.At(x, y) - c1 := m1.At(x, y) + c1 := m1.At(x-b0.Min.X+b1.Min.X, y-b0.Min.Y+b1.Min.Y) r0, g0, b0, _ := c0.RGBA() r1, g1, b1, _ := c1.RGBA() sum += delta(r0, r1) @@ -581,6 +587,75 @@ func TestEncodeCroppedSubImages(t *testing.T) { } } +type offsetImage struct { + image.Image + Rect image.Rectangle +} + +func (i offsetImage) Bounds() image.Rectangle { + return i.Rect +} + +func TestEncodeWrappedImage(t *testing.T) { + m0, err := readImg("../testdata/video-001.gif") + if err != nil { + t.Fatalf("readImg: %v", err) + } + + // Case 1: Enocde a wrapped image.Image + buf := new(bytes.Buffer) + w0 := offsetImage{m0, m0.Bounds()} + err = Encode(buf, w0, nil) + if err != nil { + t.Fatalf("Encode: %v", err) + } + w1, err := Decode(buf) + if err != nil { + t.Fatalf("Dencode: %v", err) + } + avgDelta := averageDelta(m0, w1) + if avgDelta > 0 { + t.Fatalf("Wrapped: average delta is too high. expected: 0, got %d", avgDelta) + } + + // Case 2: Enocde a wrapped image.Image with offset + b0 := image.Rectangle{ + Min: image.Point{ + X: 128, + Y: 64, + }, + Max: image.Point{ + X: 256, + Y: 128, + }, + } + w0 = offsetImage{m0, b0} + buf = new(bytes.Buffer) + err = Encode(buf, w0, nil) + if err != nil { + t.Fatalf("Encode: %v", err) + } + w1, err = Decode(buf) + if err != nil { + t.Fatalf("Dencode: %v", err) + } + + b1 := image.Rectangle{ + Min: image.Point{ + X: 0, + Y: 0, + }, + Max: image.Point{ + X: 128, + Y: 64, + }, + } + avgDelta = averageDeltaBound(m0, w1, b0, b1) + if avgDelta > 0 { + t.Fatalf("Wrapped and offset: average delta is too high. expected: 0, got %d", avgDelta) + } +} + func BenchmarkEncode(b *testing.B) { bo := image.Rect(0, 0, 640, 480) rnd := rand.New(rand.NewSource(123)) diff --git a/src/image/testdata/triangle-001.gif b/src/image/testdata/triangle-001.gif new file mode 100644 index 0000000000000000000000000000000000000000..f3d45bbfa431634a26a59bc838fd638e2400e0cb GIT binary patch literal 1476 zcmZ8g2~d+)5MC5IRMH?4bQGx(Mec;dP|!h1NjNMZM-sU#Fd;A*%MnP7T4zGU3ezgk z3RK&%9)QP`LyFQ?2Y-cNt%h(6M=I(lf~BoRgwz;?(tSZ@+V0Go-Tn64@9n>D{_x1K zz@QAUD1s7rV?u}qKE!|k5+D^~Arj(&iy0r75C%~ghXV^wBxb~N%hR?2=5nSDIN4Ck zkCh(^oX6e>n%!HIBG5h)!LqGJYB3^4u92#!oS{rz^WNGZg2l)oR*yVlI$k2Asb5ng z_C_k(0-$r5kpig*e1!QuH`%8D;HP8&*4X0~hx(xyqaLw)7E-g zN}cCloSNrpj=ZE`5x{<&f!REzN<=?;AzLkQ2r=JBa{6f_wV?%PfDp4L`Hn=aGtm>? zb<`SWOsN*?q-O0frJ_BYQIS}NG4V^}2D2w3miS%Gg$5OwgJGPXh3IV#Fj87eve$X@ zdoF@C7<$@;Ks@qha2~Q7OGdMr%uO&-Bb=?!toA`ZsgaoIT4OB=PaxvlUZc5>-~v1} z#6$koNAyU(Fb@xV2$qiiq?-Z14`2_*WIsfgya&nuzaKIS(H?tZ% zuz&%sz|152A^csq58@Z@XJQ>6EP5}KmnT-_NOI+csrk}^47r4v^-)GPD_fcc4AeY~ zynFOm;GutWA9x{t$qwfYlB$`ApQ^$4OHWrH!<^v6XqXdht%Iq(+Vir}$Vzu%W2W@t=$&d` ziLN$jdaTQXqnH@|#ok%>^_EZQF5B$Kd(T8xvD)@9+!D^m)x9fER{Sok5B)Z`>+ZlV zZ9@iC=3?XbH0h{hpixo!!Ykzm*@Y_#&!LIrEAnR(vtJFd)2_$Q(se8KK5^~Ew(N=H zdSCIabSKxc)S$6UZ8b-wxwm&Z`?C}L;tjJ2hTq!jgjadN`~Lbv-5}`@c@GEV^t37- z57rMWlOG&Xy{2E=^YZz~{_=I(bCss)C;2D0Z+B{AE5-+##OE5FJe3m<&K;O@Wq;$o z*YM=%ne3(yJwJXqaKHPot?HWYmFd|Bx-`|??Y|ez*w%qfWnM@b&1)%F#}HK9IJszX^%j>)+>)~5jx;1?*&W!O>~OA5#M~wIb`P*^8SpR4 zY3{6ApZ7iEOR6B!Qkh!Qmy>tDU%#{a{@}y0farZEzFU!USafIkgZ92JhHmd1YI~1g zWb@8Y(n0=4x=Mf6s>8}S!Ydh8ip%~KM2Xd*Wi