aboutsummaryrefslogtreecommitdiff
path: root/libgo/go/image
diff options
context:
space:
mode:
Diffstat (limited to 'libgo/go/image')
-rw-r--r--libgo/go/image/color/palette/gen.go1
-rw-r--r--libgo/go/image/draw/bench_test.go12
-rw-r--r--libgo/go/image/draw/draw.go329
-rw-r--r--libgo/go/image/draw/draw_test.go272
-rw-r--r--libgo/go/image/gif/fuzz_test.go61
-rw-r--r--libgo/go/image/internal/imageutil/gen.go1
-rw-r--r--libgo/go/image/jpeg/fuzz_test.go61
-rw-r--r--libgo/go/image/png/fuzz.go1
-rw-r--r--libgo/go/image/png/fuzz_test.go68
-rw-r--r--libgo/go/image/png/reader.go10
10 files changed, 788 insertions, 28 deletions
diff --git a/libgo/go/image/color/palette/gen.go b/libgo/go/image/color/palette/gen.go
index 7bb257d..be46c57 100644
--- a/libgo/go/image/color/palette/gen.go
+++ b/libgo/go/image/color/palette/gen.go
@@ -3,7 +3,6 @@
// license that can be found in the LICENSE file.
//go:build ignore
-// +build ignore
package main
diff --git a/libgo/go/image/draw/bench_test.go b/libgo/go/image/draw/bench_test.go
index 831fd95..55d25b8 100644
--- a/libgo/go/image/draw/bench_test.go
+++ b/libgo/go/image/draw/bench_test.go
@@ -232,6 +232,18 @@ func BenchmarkGlyphOver(b *testing.B) {
bench(b, color.RGBAModel, nil, color.AlphaModel, Over)
}
+func BenchmarkRGBAMaskOver(b *testing.B) {
+ bench(b, color.RGBAModel, color.RGBAModel, color.AlphaModel, Over)
+}
+
+func BenchmarkGrayMaskOver(b *testing.B) {
+ bench(b, color.RGBAModel, color.GrayModel, color.AlphaModel, Over)
+}
+
+func BenchmarkRGBA64ImageMaskOver(b *testing.B) {
+ bench(b, color.RGBAModel, color.RGBA64Model, color.AlphaModel, Over)
+}
+
func BenchmarkRGBA(b *testing.B) {
bench(b, color.RGBAModel, color.RGBA64Model, nil, Src)
}
diff --git a/libgo/go/image/draw/draw.go b/libgo/go/image/draw/draw.go
index 13f6668..7dd18df 100644
--- a/libgo/go/image/draw/draw.go
+++ b/libgo/go/image/draw/draw.go
@@ -119,7 +119,8 @@ func DrawMask(dst Image, r image.Rectangle, src image.Image, sp image.Point, mas
return
}
- // Fast paths for special cases. If none of them apply, then we fall back to a general but slow implementation.
+ // Fast paths for special cases. If none of them apply, then we fall back
+ // to general but slower implementations.
switch dst0 := dst.(type) {
case *image.RGBA:
if op == Over {
@@ -159,6 +160,17 @@ func DrawMask(dst Image, r image.Rectangle, src image.Image, sp image.Point, mas
case *image.Uniform:
drawGlyphOver(dst0, r, src0, mask0, mp)
return
+ case *image.RGBA:
+ drawRGBAMaskOver(dst0, r, src0, sp, mask0, mp)
+ return
+ case *image.Gray:
+ drawGrayMaskOver(dst0, r, src0, sp, mask0, mp)
+ return
+ // Case order matters. The next case (image.RGBA64Image) is an
+ // interface type that the concrete types above also implement.
+ case image.RGBA64Image:
+ drawRGBA64ImageMaskOver(dst0, r, src0, sp, mask0, mp)
+ return
}
}
} else {
@@ -219,6 +231,88 @@ func DrawMask(dst Image, r image.Rectangle, src image.Image, sp image.Point, mas
y0, y1, dy = y1-1, y0-1, -1
}
+ // FALLBACK1.17
+ //
+ // Try the draw.RGBA64Image and image.RGBA64Image interfaces, part of the
+ // standard library since Go 1.17. These are like the draw.Image and
+ // image.Image interfaces but they can avoid allocations from converting
+ // concrete color types to the color.Color interface type.
+
+ if dst0, _ := dst.(RGBA64Image); dst0 != nil {
+ if src0, _ := src.(image.RGBA64Image); src0 != nil {
+ if mask == nil {
+ sy := sp.Y + y0 - r.Min.Y
+ my := mp.Y + y0 - r.Min.Y
+ for y := y0; y != y1; y, sy, my = y+dy, sy+dy, my+dy {
+ sx := sp.X + x0 - r.Min.X
+ mx := mp.X + x0 - r.Min.X
+ for x := x0; x != x1; x, sx, mx = x+dx, sx+dx, mx+dx {
+ if op == Src {
+ dst0.SetRGBA64(x, y, src0.RGBA64At(sx, sy))
+ } else {
+ srgba := src0.RGBA64At(sx, sy)
+ a := m - uint32(srgba.A)
+ drgba := dst0.RGBA64At(x, y)
+ dst0.SetRGBA64(x, y, color.RGBA64{
+ R: uint16((uint32(drgba.R)*a)/m) + srgba.R,
+ G: uint16((uint32(drgba.G)*a)/m) + srgba.G,
+ B: uint16((uint32(drgba.B)*a)/m) + srgba.B,
+ A: uint16((uint32(drgba.A)*a)/m) + srgba.A,
+ })
+ }
+ }
+ }
+ return
+
+ } else if mask0, _ := mask.(image.RGBA64Image); mask0 != nil {
+ sy := sp.Y + y0 - r.Min.Y
+ my := mp.Y + y0 - r.Min.Y
+ for y := y0; y != y1; y, sy, my = y+dy, sy+dy, my+dy {
+ sx := sp.X + x0 - r.Min.X
+ mx := mp.X + x0 - r.Min.X
+ for x := x0; x != x1; x, sx, mx = x+dx, sx+dx, mx+dx {
+ ma := uint32(mask0.RGBA64At(mx, my).A)
+ switch {
+ case ma == 0:
+ if op == Over {
+ // No-op.
+ } else {
+ dst0.SetRGBA64(x, y, color.RGBA64{})
+ }
+ case ma == m && op == Src:
+ dst0.SetRGBA64(x, y, src0.RGBA64At(sx, sy))
+ default:
+ srgba := src0.RGBA64At(sx, sy)
+ if op == Over {
+ drgba := dst0.RGBA64At(x, y)
+ a := m - (uint32(srgba.A) * ma / m)
+ dst0.SetRGBA64(x, y, color.RGBA64{
+ R: uint16((uint32(drgba.R)*a + uint32(srgba.R)*ma) / m),
+ G: uint16((uint32(drgba.G)*a + uint32(srgba.G)*ma) / m),
+ B: uint16((uint32(drgba.B)*a + uint32(srgba.B)*ma) / m),
+ A: uint16((uint32(drgba.A)*a + uint32(srgba.A)*ma) / m),
+ })
+ } else {
+ dst0.SetRGBA64(x, y, color.RGBA64{
+ R: uint16(uint32(srgba.R) * ma / m),
+ G: uint16(uint32(srgba.G) * ma / m),
+ B: uint16(uint32(srgba.B) * ma / m),
+ A: uint16(uint32(srgba.A) * ma / m),
+ })
+ }
+ }
+ }
+ }
+ return
+ }
+ }
+ }
+
+ // FALLBACK1.0
+ //
+ // If none of the faster code paths above apply, use the draw.Image and
+ // image.Image interfaces, part of the standard library since Go 1.0.
+
var out color.RGBA64
sy := sp.Y + y0 - r.Min.Y
my := mp.Y + y0 - r.Min.Y
@@ -519,6 +613,156 @@ func drawGlyphOver(dst *image.RGBA, r image.Rectangle, src *image.Uniform, mask
}
}
+func drawGrayMaskOver(dst *image.RGBA, r image.Rectangle, src *image.Gray, sp image.Point, mask *image.Alpha, mp image.Point) {
+ x0, x1, dx := r.Min.X, r.Max.X, 1
+ y0, y1, dy := r.Min.Y, r.Max.Y, 1
+ if r.Overlaps(r.Add(sp.Sub(r.Min))) {
+ if sp.Y < r.Min.Y || sp.Y == r.Min.Y && sp.X < r.Min.X {
+ x0, x1, dx = x1-1, x0-1, -1
+ y0, y1, dy = y1-1, y0-1, -1
+ }
+ }
+
+ sy := sp.Y + y0 - r.Min.Y
+ my := mp.Y + y0 - r.Min.Y
+ sx0 := sp.X + x0 - r.Min.X
+ mx0 := mp.X + x0 - r.Min.X
+ sx1 := sx0 + (x1 - x0)
+ i0 := dst.PixOffset(x0, y0)
+ di := dx * 4
+ for y := y0; y != y1; y, sy, my = y+dy, sy+dy, my+dy {
+ for i, sx, mx := i0, sx0, mx0; sx != sx1; i, sx, mx = i+di, sx+dx, mx+dx {
+ mi := mask.PixOffset(mx, my)
+ ma := uint32(mask.Pix[mi])
+ ma |= ma << 8
+ si := src.PixOffset(sx, sy)
+ sy := uint32(src.Pix[si])
+ sy |= sy << 8
+ sa := uint32(0xffff)
+
+ d := dst.Pix[i : i+4 : i+4] // Small cap improves performance, see https://golang.org/issue/27857
+ dr := uint32(d[0])
+ dg := uint32(d[1])
+ db := uint32(d[2])
+ da := uint32(d[3])
+
+ // dr, dg, db and da are all 8-bit color at the moment, ranging in [0,255].
+ // We work in 16-bit color, and so would normally do:
+ // dr |= dr << 8
+ // and similarly for dg, db and da, but instead we multiply a
+ // (which is a 16-bit color, ranging in [0,65535]) by 0x101.
+ // This yields the same result, but is fewer arithmetic operations.
+ a := (m - (sa * ma / m)) * 0x101
+
+ d[0] = uint8((dr*a + sy*ma) / m >> 8)
+ d[1] = uint8((dg*a + sy*ma) / m >> 8)
+ d[2] = uint8((db*a + sy*ma) / m >> 8)
+ d[3] = uint8((da*a + sa*ma) / m >> 8)
+ }
+ i0 += dy * dst.Stride
+ }
+}
+
+func drawRGBAMaskOver(dst *image.RGBA, r image.Rectangle, src *image.RGBA, sp image.Point, mask *image.Alpha, mp image.Point) {
+ x0, x1, dx := r.Min.X, r.Max.X, 1
+ y0, y1, dy := r.Min.Y, r.Max.Y, 1
+ if dst == src && r.Overlaps(r.Add(sp.Sub(r.Min))) {
+ if sp.Y < r.Min.Y || sp.Y == r.Min.Y && sp.X < r.Min.X {
+ x0, x1, dx = x1-1, x0-1, -1
+ y0, y1, dy = y1-1, y0-1, -1
+ }
+ }
+
+ sy := sp.Y + y0 - r.Min.Y
+ my := mp.Y + y0 - r.Min.Y
+ sx0 := sp.X + x0 - r.Min.X
+ mx0 := mp.X + x0 - r.Min.X
+ sx1 := sx0 + (x1 - x0)
+ i0 := dst.PixOffset(x0, y0)
+ di := dx * 4
+ for y := y0; y != y1; y, sy, my = y+dy, sy+dy, my+dy {
+ for i, sx, mx := i0, sx0, mx0; sx != sx1; i, sx, mx = i+di, sx+dx, mx+dx {
+ mi := mask.PixOffset(mx, my)
+ ma := uint32(mask.Pix[mi])
+ ma |= ma << 8
+ si := src.PixOffset(sx, sy)
+ sr := uint32(src.Pix[si+0])
+ sg := uint32(src.Pix[si+1])
+ sb := uint32(src.Pix[si+2])
+ sa := uint32(src.Pix[si+3])
+ sr |= sr << 8
+ sg |= sg << 8
+ sb |= sb << 8
+ sa |= sa << 8
+ d := dst.Pix[i : i+4 : i+4] // Small cap improves performance, see https://golang.org/issue/27857
+ dr := uint32(d[0])
+ dg := uint32(d[1])
+ db := uint32(d[2])
+ da := uint32(d[3])
+
+ // dr, dg, db and da are all 8-bit color at the moment, ranging in [0,255].
+ // We work in 16-bit color, and so would normally do:
+ // dr |= dr << 8
+ // and similarly for dg, db and da, but instead we multiply a
+ // (which is a 16-bit color, ranging in [0,65535]) by 0x101.
+ // This yields the same result, but is fewer arithmetic operations.
+ a := (m - (sa * ma / m)) * 0x101
+
+ d[0] = uint8((dr*a + sr*ma) / m >> 8)
+ d[1] = uint8((dg*a + sg*ma) / m >> 8)
+ d[2] = uint8((db*a + sb*ma) / m >> 8)
+ d[3] = uint8((da*a + sa*ma) / m >> 8)
+ }
+ i0 += dy * dst.Stride
+ }
+}
+
+func drawRGBA64ImageMaskOver(dst *image.RGBA, r image.Rectangle, src image.RGBA64Image, sp image.Point, mask *image.Alpha, mp image.Point) {
+ x0, x1, dx := r.Min.X, r.Max.X, 1
+ y0, y1, dy := r.Min.Y, r.Max.Y, 1
+ if image.Image(dst) == src && r.Overlaps(r.Add(sp.Sub(r.Min))) {
+ if sp.Y < r.Min.Y || sp.Y == r.Min.Y && sp.X < r.Min.X {
+ x0, x1, dx = x1-1, x0-1, -1
+ y0, y1, dy = y1-1, y0-1, -1
+ }
+ }
+
+ sy := sp.Y + y0 - r.Min.Y
+ my := mp.Y + y0 - r.Min.Y
+ sx0 := sp.X + x0 - r.Min.X
+ mx0 := mp.X + x0 - r.Min.X
+ sx1 := sx0 + (x1 - x0)
+ i0 := dst.PixOffset(x0, y0)
+ di := dx * 4
+ for y := y0; y != y1; y, sy, my = y+dy, sy+dy, my+dy {
+ for i, sx, mx := i0, sx0, mx0; sx != sx1; i, sx, mx = i+di, sx+dx, mx+dx {
+ mi := mask.PixOffset(mx, my)
+ ma := uint32(mask.Pix[mi])
+ ma |= ma << 8
+ srgba := src.RGBA64At(sx, sy)
+ d := dst.Pix[i : i+4 : i+4] // Small cap improves performance, see https://golang.org/issue/27857
+ dr := uint32(d[0])
+ dg := uint32(d[1])
+ db := uint32(d[2])
+ da := uint32(d[3])
+
+ // dr, dg, db and da are all 8-bit color at the moment, ranging in [0,255].
+ // We work in 16-bit color, and so would normally do:
+ // dr |= dr << 8
+ // and similarly for dg, db and da, but instead we multiply a
+ // (which is a 16-bit color, ranging in [0,65535]) by 0x101.
+ // This yields the same result, but is fewer arithmetic operations.
+ a := (m - (uint32(srgba.A) * ma / m)) * 0x101
+
+ d[0] = uint8((dr*a + uint32(srgba.R)*ma) / m >> 8)
+ d[1] = uint8((dg*a + uint32(srgba.G)*ma) / m >> 8)
+ d[2] = uint8((db*a + uint32(srgba.B)*ma) / m >> 8)
+ d[3] = uint8((da*a + uint32(srgba.A)*ma) / m >> 8)
+ }
+ i0 += dy * dst.Stride
+ }
+}
+
func drawRGBA(dst *image.RGBA, r image.Rectangle, src image.Image, sp image.Point, mask image.Image, mp image.Point, op Op) {
x0, x1, dx := r.Min.X, r.Max.X, 1
y0, y1, dy := r.Min.Y, r.Max.Y, 1
@@ -536,6 +780,89 @@ func drawRGBA(dst *image.RGBA, r image.Rectangle, src image.Image, sp image.Poin
sx1 := sx0 + (x1 - x0)
i0 := dst.PixOffset(x0, y0)
di := dx * 4
+
+ // Try the image.RGBA64Image interface, part of the standard library since
+ // Go 1.17.
+ //
+ // This optimization is similar to how FALLBACK1.17 optimizes FALLBACK1.0
+ // in DrawMask, except here the concrete type of dst is known to be
+ // *image.RGBA.
+ if src0, _ := src.(image.RGBA64Image); src0 != nil {
+ if mask == nil {
+ if op == Over {
+ for y := y0; y != y1; y, sy, my = y+dy, sy+dy, my+dy {
+ for i, sx, mx := i0, sx0, mx0; sx != sx1; i, sx, mx = i+di, sx+dx, mx+dx {
+ srgba := src0.RGBA64At(sx, sy)
+ d := dst.Pix[i : i+4 : i+4]
+ dr := uint32(d[0])
+ dg := uint32(d[1])
+ db := uint32(d[2])
+ da := uint32(d[3])
+ a := (m - uint32(srgba.A)) * 0x101
+ d[0] = uint8((dr*a/m + uint32(srgba.R)) >> 8)
+ d[1] = uint8((dg*a/m + uint32(srgba.G)) >> 8)
+ d[2] = uint8((db*a/m + uint32(srgba.B)) >> 8)
+ d[3] = uint8((da*a/m + uint32(srgba.A)) >> 8)
+ }
+ i0 += dy * dst.Stride
+ }
+ } else {
+ for y := y0; y != y1; y, sy, my = y+dy, sy+dy, my+dy {
+ for i, sx, mx := i0, sx0, mx0; sx != sx1; i, sx, mx = i+di, sx+dx, mx+dx {
+ srgba := src0.RGBA64At(sx, sy)
+ d := dst.Pix[i : i+4 : i+4]
+ d[0] = uint8(srgba.R >> 8)
+ d[1] = uint8(srgba.G >> 8)
+ d[2] = uint8(srgba.B >> 8)
+ d[3] = uint8(srgba.A >> 8)
+ }
+ i0 += dy * dst.Stride
+ }
+ }
+ return
+
+ } else if mask0, _ := mask.(image.RGBA64Image); mask0 != nil {
+ if op == Over {
+ for y := y0; y != y1; y, sy, my = y+dy, sy+dy, my+dy {
+ for i, sx, mx := i0, sx0, mx0; sx != sx1; i, sx, mx = i+di, sx+dx, mx+dx {
+ ma := uint32(mask0.RGBA64At(mx, my).A)
+ srgba := src0.RGBA64At(sx, sy)
+ d := dst.Pix[i : i+4 : i+4]
+ dr := uint32(d[0])
+ dg := uint32(d[1])
+ db := uint32(d[2])
+ da := uint32(d[3])
+ a := (m - (uint32(srgba.A) * ma / m)) * 0x101
+ d[0] = uint8((dr*a + uint32(srgba.R)*ma) / m >> 8)
+ d[1] = uint8((dg*a + uint32(srgba.G)*ma) / m >> 8)
+ d[2] = uint8((db*a + uint32(srgba.B)*ma) / m >> 8)
+ d[3] = uint8((da*a + uint32(srgba.A)*ma) / m >> 8)
+ }
+ i0 += dy * dst.Stride
+ }
+ } else {
+ for y := y0; y != y1; y, sy, my = y+dy, sy+dy, my+dy {
+ for i, sx, mx := i0, sx0, mx0; sx != sx1; i, sx, mx = i+di, sx+dx, mx+dx {
+ ma := uint32(mask0.RGBA64At(mx, my).A)
+ srgba := src0.RGBA64At(sx, sy)
+ d := dst.Pix[i : i+4 : i+4]
+ d[0] = uint8(uint32(srgba.R) * ma / m >> 8)
+ d[1] = uint8(uint32(srgba.G) * ma / m >> 8)
+ d[2] = uint8(uint32(srgba.B) * ma / m >> 8)
+ d[3] = uint8(uint32(srgba.A) * ma / m >> 8)
+ }
+ i0 += dy * dst.Stride
+ }
+ }
+ return
+ }
+ }
+
+ // Use the image.Image interface, part of the standard library since Go
+ // 1.0.
+ //
+ // This is similar to FALLBACK1.0 in DrawMask, except here the concrete
+ // type of dst is known to be *image.RGBA.
for y := y0; y != y1; y, sy, my = y+dy, sy+dy, my+dy {
for i, sx, mx := i0, sx0, mx0; sx != sx1; i, sx, mx = i+di, sx+dx, mx+dx {
ma := uint32(m)
diff --git a/libgo/go/image/draw/draw_test.go b/libgo/go/image/draw/draw_test.go
index 9c5a118..3be9396 100644
--- a/libgo/go/image/draw/draw_test.go
+++ b/libgo/go/image/draw/draw_test.go
@@ -13,6 +13,172 @@ import (
"testing/quick"
)
+// slowestRGBA is a draw.Image like image.RGBA but it is a different type and
+// therefore does not trigger the draw.go fastest code paths.
+//
+// Unlike slowerRGBA, it does not implement the draw.RGBA64Image interface.
+type slowestRGBA struct {
+ Pix []uint8
+ Stride int
+ Rect image.Rectangle
+}
+
+func (p *slowestRGBA) ColorModel() color.Model { return color.RGBAModel }
+
+func (p *slowestRGBA) Bounds() image.Rectangle { return p.Rect }
+
+func (p *slowestRGBA) At(x, y int) color.Color {
+ return p.RGBA64At(x, y)
+}
+
+func (p *slowestRGBA) RGBA64At(x, y int) color.RGBA64 {
+ if !(image.Point{x, y}.In(p.Rect)) {
+ return color.RGBA64{}
+ }
+ i := p.PixOffset(x, y)
+ s := p.Pix[i : i+4 : i+4] // Small cap improves performance, see https://golang.org/issue/27857
+ r := uint16(s[0])
+ g := uint16(s[1])
+ b := uint16(s[2])
+ a := uint16(s[3])
+ return color.RGBA64{
+ (r << 8) | r,
+ (g << 8) | g,
+ (b << 8) | b,
+ (a << 8) | a,
+ }
+}
+
+func (p *slowestRGBA) Set(x, y int, c color.Color) {
+ if !(image.Point{x, y}.In(p.Rect)) {
+ return
+ }
+ i := p.PixOffset(x, y)
+ c1 := color.RGBAModel.Convert(c).(color.RGBA)
+ s := p.Pix[i : i+4 : i+4] // Small cap improves performance, see https://golang.org/issue/27857
+ s[0] = c1.R
+ s[1] = c1.G
+ s[2] = c1.B
+ s[3] = c1.A
+}
+
+func (p *slowestRGBA) PixOffset(x, y int) int {
+ return (y-p.Rect.Min.Y)*p.Stride + (x-p.Rect.Min.X)*4
+}
+
+func convertToSlowestRGBA(m image.Image) *slowestRGBA {
+ if rgba, ok := m.(*image.RGBA); ok {
+ return &slowestRGBA{
+ Pix: append([]byte(nil), rgba.Pix...),
+ Stride: rgba.Stride,
+ Rect: rgba.Rect,
+ }
+ }
+ rgba := image.NewRGBA(m.Bounds())
+ Draw(rgba, rgba.Bounds(), m, m.Bounds().Min, Src)
+ return &slowestRGBA{
+ Pix: rgba.Pix,
+ Stride: rgba.Stride,
+ Rect: rgba.Rect,
+ }
+}
+
+func init() {
+ var p any = (*slowestRGBA)(nil)
+ if _, ok := p.(RGBA64Image); ok {
+ panic("slowestRGBA should not be an RGBA64Image")
+ }
+}
+
+// slowerRGBA is a draw.Image like image.RGBA but it is a different type and
+// therefore does not trigger the draw.go fastest code paths.
+//
+// Unlike slowestRGBA, it still implements the draw.RGBA64Image interface.
+type slowerRGBA struct {
+ Pix []uint8
+ Stride int
+ Rect image.Rectangle
+}
+
+func (p *slowerRGBA) ColorModel() color.Model { return color.RGBAModel }
+
+func (p *slowerRGBA) Bounds() image.Rectangle { return p.Rect }
+
+func (p *slowerRGBA) At(x, y int) color.Color {
+ return p.RGBA64At(x, y)
+}
+
+func (p *slowerRGBA) RGBA64At(x, y int) color.RGBA64 {
+ if !(image.Point{x, y}.In(p.Rect)) {
+ return color.RGBA64{}
+ }
+ i := p.PixOffset(x, y)
+ s := p.Pix[i : i+4 : i+4] // Small cap improves performance, see https://golang.org/issue/27857
+ r := uint16(s[0])
+ g := uint16(s[1])
+ b := uint16(s[2])
+ a := uint16(s[3])
+ return color.RGBA64{
+ (r << 8) | r,
+ (g << 8) | g,
+ (b << 8) | b,
+ (a << 8) | a,
+ }
+}
+
+func (p *slowerRGBA) Set(x, y int, c color.Color) {
+ if !(image.Point{x, y}.In(p.Rect)) {
+ return
+ }
+ i := p.PixOffset(x, y)
+ c1 := color.RGBAModel.Convert(c).(color.RGBA)
+ s := p.Pix[i : i+4 : i+4] // Small cap improves performance, see https://golang.org/issue/27857
+ s[0] = c1.R
+ s[1] = c1.G
+ s[2] = c1.B
+ s[3] = c1.A
+}
+
+func (p *slowerRGBA) SetRGBA64(x, y int, c color.RGBA64) {
+ if !(image.Point{x, y}.In(p.Rect)) {
+ return
+ }
+ i := p.PixOffset(x, y)
+ s := p.Pix[i : i+4 : i+4] // Small cap improves performance, see https://golang.org/issue/27857
+ s[0] = uint8(c.R >> 8)
+ s[1] = uint8(c.G >> 8)
+ s[2] = uint8(c.B >> 8)
+ s[3] = uint8(c.A >> 8)
+}
+
+func (p *slowerRGBA) PixOffset(x, y int) int {
+ return (y-p.Rect.Min.Y)*p.Stride + (x-p.Rect.Min.X)*4
+}
+
+func convertToSlowerRGBA(m image.Image) *slowerRGBA {
+ if rgba, ok := m.(*image.RGBA); ok {
+ return &slowerRGBA{
+ Pix: append([]byte(nil), rgba.Pix...),
+ Stride: rgba.Stride,
+ Rect: rgba.Rect,
+ }
+ }
+ rgba := image.NewRGBA(m.Bounds())
+ Draw(rgba, rgba.Bounds(), m, m.Bounds().Min, Src)
+ return &slowerRGBA{
+ Pix: rgba.Pix,
+ Stride: rgba.Stride,
+ Rect: rgba.Rect,
+ }
+}
+
+func init() {
+ var p any = (*slowerRGBA)(nil)
+ if _, ok := p.(RGBA64Image); !ok {
+ panic("slowerRGBA should be an RGBA64Image")
+ }
+}
+
func eq(c0, c1 color.Color) bool {
r0, g0, b0, a0 := c0.RGBA()
r1, g1, b1, a1 := c1.RGBA()
@@ -178,6 +344,32 @@ var drawTests = []drawTest{
{"grayAlphaSrc", vgradGray(), fillAlpha(192), Src, color.RGBA{102, 102, 102, 192}},
{"grayNil", vgradGray(), nil, Over, color.RGBA{136, 136, 136, 255}},
{"grayNilSrc", vgradGray(), nil, Src, color.RGBA{136, 136, 136, 255}},
+ // Same again, but with a slowerRGBA source.
+ {"graySlower", convertToSlowerRGBA(vgradGray()), fillAlpha(255),
+ Over, color.RGBA{136, 136, 136, 255}},
+ {"graySrcSlower", convertToSlowerRGBA(vgradGray()), fillAlpha(255),
+ Src, color.RGBA{136, 136, 136, 255}},
+ {"grayAlphaSlower", convertToSlowerRGBA(vgradGray()), fillAlpha(192),
+ Over, color.RGBA{136, 102, 102, 255}},
+ {"grayAlphaSrcSlower", convertToSlowerRGBA(vgradGray()), fillAlpha(192),
+ Src, color.RGBA{102, 102, 102, 192}},
+ {"grayNilSlower", convertToSlowerRGBA(vgradGray()), nil,
+ Over, color.RGBA{136, 136, 136, 255}},
+ {"grayNilSrcSlower", convertToSlowerRGBA(vgradGray()), nil,
+ Src, color.RGBA{136, 136, 136, 255}},
+ // Same again, but with a slowestRGBA source.
+ {"graySlowest", convertToSlowestRGBA(vgradGray()), fillAlpha(255),
+ Over, color.RGBA{136, 136, 136, 255}},
+ {"graySrcSlowest", convertToSlowestRGBA(vgradGray()), fillAlpha(255),
+ Src, color.RGBA{136, 136, 136, 255}},
+ {"grayAlphaSlowest", convertToSlowestRGBA(vgradGray()), fillAlpha(192),
+ Over, color.RGBA{136, 102, 102, 255}},
+ {"grayAlphaSrcSlowest", convertToSlowestRGBA(vgradGray()), fillAlpha(192),
+ Src, color.RGBA{102, 102, 102, 192}},
+ {"grayNilSlowest", convertToSlowestRGBA(vgradGray()), nil,
+ Over, color.RGBA{136, 136, 136, 255}},
+ {"grayNilSrcSlowest", convertToSlowestRGBA(vgradGray()), nil,
+ Src, color.RGBA{136, 136, 136, 255}},
// Uniform mask (100%, 75%, nil) and variable CMYK source.
// At (x, y) == (8, 8):
// The destination pixel is {136, 0, 0, 255}.
@@ -188,13 +380,32 @@ var drawTests = []drawTest{
{"cmykAlphaSrc", vgradMagenta(), fillAlpha(192), Src, color.RGBA{145, 67, 145, 192}},
{"cmykNil", vgradMagenta(), nil, Over, color.RGBA{192, 89, 192, 255}},
{"cmykNilSrc", vgradMagenta(), nil, Src, color.RGBA{192, 89, 192, 255}},
- // Variable mask and variable source.
+ // Variable mask and uniform source.
// At (x, y) == (8, 8):
// The destination pixel is {136, 0, 0, 255}.
// The source pixel is {0, 0, 255, 255}.
// The mask pixel's alpha is 102, or 40%.
{"generic", fillBlue(255), vgradAlpha(192), Over, color.RGBA{81, 0, 102, 255}},
{"genericSrc", fillBlue(255), vgradAlpha(192), Src, color.RGBA{0, 0, 102, 102}},
+ // Same again, but with a slowerRGBA mask.
+ {"genericSlower", fillBlue(255), convertToSlowerRGBA(vgradAlpha(192)),
+ Over, color.RGBA{81, 0, 102, 255}},
+ {"genericSrcSlower", fillBlue(255), convertToSlowerRGBA(vgradAlpha(192)),
+ Src, color.RGBA{0, 0, 102, 102}},
+ // Same again, but with a slowestRGBA mask.
+ {"genericSlowest", fillBlue(255), convertToSlowestRGBA(vgradAlpha(192)),
+ Over, color.RGBA{81, 0, 102, 255}},
+ {"genericSrcSlowest", fillBlue(255), convertToSlowestRGBA(vgradAlpha(192)),
+ Src, color.RGBA{0, 0, 102, 102}},
+ // Variable mask and variable source.
+ // At (x, y) == (8, 8):
+ // The destination pixel is {136, 0, 0, 255}.
+ // The source pixel is:
+ // - {0, 48, 0, 90}.
+ // - {136} in Gray-space, which is {136, 136, 136, 255} in RGBA-space.
+ // The mask pixel's alpha is 102, or 40%.
+ {"rgbaVariableMaskOver", vgradGreen(90), vgradAlpha(192), Over, color.RGBA{117, 19, 0, 255}},
+ {"grayVariableMaskOver", vgradGray(), vgradAlpha(192), Over, color.RGBA{136, 54, 54, 255}},
}
func makeGolden(dst image.Image, r image.Rectangle, src image.Image, sp image.Point, mask image.Image, mp image.Point, op Op) image.Image {
@@ -260,30 +471,45 @@ func TestDraw(t *testing.T) {
for _, r := range rr {
loop:
for _, test := range drawTests {
- dst := hgradRed(255).(*image.RGBA).SubImage(r).(Image)
- // Draw the (src, mask, op) onto a copy of dst using a slow but obviously correct implementation.
- golden := makeGolden(dst, image.Rect(0, 0, 16, 16), test.src, image.ZP, test.mask, image.ZP, test.op)
- b := dst.Bounds()
- if !b.Eq(golden.Bounds()) {
- t.Errorf("draw %v %s: bounds %v versus %v", r, test.desc, dst.Bounds(), golden.Bounds())
- continue
- }
- // Draw the same combination onto the actual dst using the optimized DrawMask implementation.
- DrawMask(dst, image.Rect(0, 0, 16, 16), test.src, image.ZP, test.mask, image.ZP, test.op)
- if image.Pt(8, 8).In(r) {
- // Check that the resultant pixel at (8, 8) matches what we expect
- // (the expected value can be verified by hand).
- if !eq(dst.At(8, 8), test.expected) {
- t.Errorf("draw %v %s: at (8, 8) %v versus %v", r, test.desc, dst.At(8, 8), test.expected)
+ for i := 0; i < 3; i++ {
+ dst := hgradRed(255).(*image.RGBA).SubImage(r).(Image)
+ // For i != 0, substitute a different-typed dst that will take
+ // us off the fastest code paths. We should still get the same
+ // result, in terms of final pixel RGBA values.
+ switch i {
+ case 1:
+ dst = convertToSlowerRGBA(dst)
+ case 2:
+ dst = convertToSlowestRGBA(dst)
+ }
+
+ // Draw the (src, mask, op) onto a copy of dst using a slow but obviously correct implementation.
+ golden := makeGolden(dst, image.Rect(0, 0, 16, 16), test.src, image.ZP, test.mask, image.ZP, test.op)
+ b := dst.Bounds()
+ if !b.Eq(golden.Bounds()) {
+ t.Errorf("draw %v %s on %T: bounds %v versus %v",
+ r, test.desc, dst, dst.Bounds(), golden.Bounds())
continue
}
- }
- // Check that the resultant dst image matches the golden output.
- for y := b.Min.Y; y < b.Max.Y; y++ {
- for x := b.Min.X; x < b.Max.X; x++ {
- if !eq(dst.At(x, y), golden.At(x, y)) {
- t.Errorf("draw %v %s: at (%d, %d), %v versus golden %v", r, test.desc, x, y, dst.At(x, y), golden.At(x, y))
- continue loop
+ // Draw the same combination onto the actual dst using the optimized DrawMask implementation.
+ DrawMask(dst, image.Rect(0, 0, 16, 16), test.src, image.ZP, test.mask, image.ZP, test.op)
+ if image.Pt(8, 8).In(r) {
+ // Check that the resultant pixel at (8, 8) matches what we expect
+ // (the expected value can be verified by hand).
+ if !eq(dst.At(8, 8), test.expected) {
+ t.Errorf("draw %v %s on %T: at (8, 8) %v versus %v",
+ r, test.desc, dst, dst.At(8, 8), test.expected)
+ continue
+ }
+ }
+ // Check that the resultant dst image matches the golden output.
+ for y := b.Min.Y; y < b.Max.Y; y++ {
+ for x := b.Min.X; x < b.Max.X; x++ {
+ if !eq(dst.At(x, y), golden.At(x, y)) {
+ t.Errorf("draw %v %s on %T: at (%d, %d), %v versus golden %v",
+ r, test.desc, dst, x, y, dst.At(x, y), golden.At(x, y))
+ continue loop
+ }
}
}
}
diff --git a/libgo/go/image/gif/fuzz_test.go b/libgo/go/image/gif/fuzz_test.go
new file mode 100644
index 0000000..3ddf15d
--- /dev/null
+++ b/libgo/go/image/gif/fuzz_test.go
@@ -0,0 +1,61 @@
+// Copyright 2021 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package gif
+
+import (
+ "bytes"
+ "image"
+ "os"
+ "path/filepath"
+ "strings"
+ "testing"
+)
+
+func FuzzDecode(f *testing.F) {
+ testdata, err := os.ReadDir("../testdata")
+ if err != nil {
+ f.Fatalf("failed to read testdata directory: %s", err)
+ }
+ for _, de := range testdata {
+ if de.IsDir() || !strings.HasSuffix(de.Name(), ".gif") {
+ continue
+ }
+ b, err := os.ReadFile(filepath.Join("../testdata", de.Name()))
+ if err != nil {
+ f.Fatalf("failed to read testdata: %s", err)
+ }
+ f.Add(b)
+ }
+
+ f.Fuzz(func(t *testing.T, b []byte) {
+ cfg, _, err := image.DecodeConfig(bytes.NewReader(b))
+ if err != nil {
+ return
+ }
+ if cfg.Width*cfg.Height > 1e6 {
+ return
+ }
+ img, typ, err := image.Decode(bytes.NewReader(b))
+ if err != nil || typ != "gif" {
+ return
+ }
+ for q := 1; q <= 256; q++ {
+ var w bytes.Buffer
+ err := Encode(&w, img, &Options{NumColors: q})
+ if err != nil {
+ t.Fatalf("failed to encode valid image: %s", err)
+ }
+ img1, err := Decode(&w)
+ if err != nil {
+ t.Fatalf("failed to decode roundtripped image: %s", err)
+ }
+ got := img1.Bounds()
+ want := img.Bounds()
+ if !got.Eq(want) {
+ t.Fatalf("roundtripped image bounds have changed, got: %v, want: %v", got, want)
+ }
+ }
+ })
+}
diff --git a/libgo/go/image/internal/imageutil/gen.go b/libgo/go/image/internal/imageutil/gen.go
index 38f4130..65e1e30 100644
--- a/libgo/go/image/internal/imageutil/gen.go
+++ b/libgo/go/image/internal/imageutil/gen.go
@@ -3,7 +3,6 @@
// license that can be found in the LICENSE file.
//go:build ignore
-// +build ignore
package main
diff --git a/libgo/go/image/jpeg/fuzz_test.go b/libgo/go/image/jpeg/fuzz_test.go
new file mode 100644
index 0000000..716f06f
--- /dev/null
+++ b/libgo/go/image/jpeg/fuzz_test.go
@@ -0,0 +1,61 @@
+// Copyright 2021 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package jpeg
+
+import (
+ "bytes"
+ "image"
+ "os"
+ "path/filepath"
+ "strings"
+ "testing"
+)
+
+func FuzzDecode(f *testing.F) {
+ testdata, err := os.ReadDir("../testdata")
+ if err != nil {
+ f.Fatalf("failed to read testdata directory: %s", err)
+ }
+ for _, de := range testdata {
+ if de.IsDir() || !strings.HasSuffix(de.Name(), ".jpeg") {
+ continue
+ }
+ b, err := os.ReadFile(filepath.Join("../testdata", de.Name()))
+ if err != nil {
+ f.Fatalf("failed to read testdata: %s", err)
+ }
+ f.Add(b)
+ }
+
+ f.Fuzz(func(t *testing.T, b []byte) {
+ cfg, _, err := image.DecodeConfig(bytes.NewReader(b))
+ if err != nil {
+ return
+ }
+ if cfg.Width*cfg.Height > 1e6 {
+ return
+ }
+ img, typ, err := image.Decode(bytes.NewReader(b))
+ if err != nil || typ != "jpeg" {
+ return
+ }
+ for q := 1; q <= 100; q++ {
+ var w bytes.Buffer
+ err := Encode(&w, img, &Options{Quality: q})
+ if err != nil {
+ t.Fatalf("failed to encode valid image: %s", err)
+ }
+ img1, err := Decode(&w)
+ if err != nil {
+ t.Fatalf("failed to decode roundtripped image: %s", err)
+ }
+ got := img1.Bounds()
+ want := img.Bounds()
+ if !got.Eq(want) {
+ t.Fatalf("roundtripped image bounds have changed, got: %s, want: %s", got, want)
+ }
+ }
+ })
+}
diff --git a/libgo/go/image/png/fuzz.go b/libgo/go/image/png/fuzz.go
index 6508533..688b6c9 100644
--- a/libgo/go/image/png/fuzz.go
+++ b/libgo/go/image/png/fuzz.go
@@ -3,7 +3,6 @@
// license that can be found in the LICENSE file.
//go:build gofuzz
-// +build gofuzz
package png
diff --git a/libgo/go/image/png/fuzz_test.go b/libgo/go/image/png/fuzz_test.go
new file mode 100644
index 0000000..22b3ef0
--- /dev/null
+++ b/libgo/go/image/png/fuzz_test.go
@@ -0,0 +1,68 @@
+// Copyright 2021 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package png
+
+import (
+ "bytes"
+ "image"
+ "os"
+ "path/filepath"
+ "strings"
+ "testing"
+)
+
+func FuzzDecode(f *testing.F) {
+ testdata, err := os.ReadDir("../testdata")
+ if err != nil {
+ f.Fatalf("failed to read testdata directory: %s", err)
+ }
+ for _, de := range testdata {
+ if de.IsDir() || !strings.HasSuffix(de.Name(), ".png") {
+ continue
+ }
+ b, err := os.ReadFile(filepath.Join("../testdata", de.Name()))
+ if err != nil {
+ f.Fatalf("failed to read testdata: %s", err)
+ }
+ f.Add(b)
+ }
+
+ f.Fuzz(func(t *testing.T, b []byte) {
+ cfg, _, err := image.DecodeConfig(bytes.NewReader(b))
+ if err != nil {
+ return
+ }
+ if cfg.Width*cfg.Height > 1e6 {
+ return
+ }
+ img, typ, err := image.Decode(bytes.NewReader(b))
+ if err != nil || typ != "png" {
+ return
+ }
+ levels := []CompressionLevel{
+ DefaultCompression,
+ NoCompression,
+ BestSpeed,
+ BestCompression,
+ }
+ for _, l := range levels {
+ var w bytes.Buffer
+ e := &Encoder{CompressionLevel: l}
+ err = e.Encode(&w, img)
+ if err != nil {
+ t.Fatalf("failed to encode valid image: %s", err)
+ }
+ img1, err := Decode(&w)
+ if err != nil {
+ t.Fatalf("failed to decode roundtripped image: %s", err)
+ }
+ got := img1.Bounds()
+ want := img.Bounds()
+ if !got.Eq(want) {
+ t.Fatalf("roundtripped image bounds have changed, got: %s, want: %s", got, want)
+ }
+ }
+ })
+}
diff --git a/libgo/go/image/png/reader.go b/libgo/go/image/png/reader.go
index 910520b..4c65038 100644
--- a/libgo/go/image/png/reader.go
+++ b/libgo/go/image/png/reader.go
@@ -821,9 +821,17 @@ func (d *decoder) mergePassInto(dst image.Image, src image.Image, pass int) {
dstPix, stride, rect = target.Pix, target.Stride, target.Rect
bytesPerPixel = 8
case *image.Paletted:
- srcPix = src.(*image.Paletted).Pix
+ source := src.(*image.Paletted)
+ srcPix = source.Pix
dstPix, stride, rect = target.Pix, target.Stride, target.Rect
bytesPerPixel = 1
+ if len(target.Palette) < len(source.Palette) {
+ // readImagePass can return a paletted image whose implicit palette
+ // length (one more than the maximum Pix value) is larger than the
+ // explicit palette length (what's in the PLTE chunk). Make the
+ // same adjustment here.
+ target.Palette = source.Palette
+ }
case *image.RGBA:
srcPix = src.(*image.RGBA).Pix
dstPix, stride, rect = target.Pix, target.Stride, target.Rect