if nrgba != nil {
offset := (y - b.Min.Y) * nrgba.Stride
copy(cr[0][1:], nrgba.Pix[offset:offset+b.Dx()*4])
+ } else if rgba != nil {
+ dst := cr[0][1:]
+ src := rgba.Pix[rgba.PixOffset(b.Min.X, y):rgba.PixOffset(b.Max.X, y)]
+ for ; len(src) >= 4; dst, src = dst[4:], src[4:] {
+ d := (*[4]byte)(dst)
+ s := (*[4]byte)(src)
+ if s[3] == 0x00 {
+ d[0] = 0
+ d[1] = 0
+ d[2] = 0
+ d[3] = 0
+ } else if s[3] == 0xff {
+ copy(d[:], s[:])
+ } else {
+ // This code does the same as color.NRGBAModel.Convert(
+ // rgba.At(x, y)).(color.NRGBA) but with no extra memory
+ // allocations or interface/function call overhead.
+ //
+ // The multiplier m combines 0x101 (which converts
+ // 8-bit color to 16-bit color) and 0xffff (which, when
+ // combined with the division-by-a, converts from
+ // alpha-premultiplied to non-alpha-premultiplied).
+ const m = 0x101 * 0xffff
+ a := uint32(s[3]) * 0x101
+ d[0] = uint8((uint32(s[0]) * m / a) >> 8)
+ d[1] = uint8((uint32(s[1]) * m / a) >> 8)
+ d[2] = uint8((uint32(s[2]) * m / a) >> 8)
+ d[3] = s[3]
+ }
+ }
} else {
// Convert from image.Image (which is alpha-premultiplied) to PNG's non-alpha-premultiplied.
for x := b.Min.X; x < b.Max.X; x++ {
"fmt"
"image"
"image/color"
+ "image/draw"
"io"
"testing"
)
r0, g0, b0, a0 := c0.RGBA()
r1, g1, b1, a1 := c1.RGBA()
if r0 != r1 || g0 != g1 || b0 != b1 || a0 != a1 {
- return fmt.Errorf("colors differ at (%d, %d): %v vs %v", x, y, c0, c1)
+ return fmt.Errorf("colors differ at (%d, %d): %T%v vs %T%v", x, y, c0, c0, c1, c1)
}
}
}
return Decode(&b)
}
+func convertToNRGBA(m image.Image) *image.NRGBA {
+ b := m.Bounds()
+ ret := image.NewNRGBA(b)
+ draw.Draw(ret, b, m, b.Min, draw.Src)
+ return ret
+}
+
func TestWriter(t *testing.T) {
// The filenames variable is declared in reader_test.go.
names := filenames
}
}
+func TestWriteRGBA(t *testing.T) {
+ const width, height = 640, 480
+ transparentImg := image.NewRGBA(image.Rect(0, 0, width, height))
+ opaqueImg := image.NewRGBA(image.Rect(0, 0, width, height))
+ mixedImg := image.NewRGBA(image.Rect(0, 0, width, height))
+ translucentImg := image.NewRGBA(image.Rect(0, 0, width, height))
+ for y := 0; y < height; y++ {
+ for x := 0; x < width; x++ {
+ opaqueColor := color.RGBA{uint8(x), uint8(y), uint8(y + x), 255}
+ translucentColor := color.RGBA{uint8(x) % 128, uint8(y) % 128, uint8(y+x) % 128, 128}
+ opaqueImg.Set(x, y, opaqueColor)
+ translucentImg.Set(x, y, translucentColor)
+ if y%2 == 0 {
+ mixedImg.Set(x, y, opaqueColor)
+ }
+ }
+ }
+
+ testCases := []struct {
+ name string
+ img image.Image
+ }{
+ {"Transparent RGBA", transparentImg},
+ {"Opaque RGBA", opaqueImg},
+ {"50/50 Transparent/Opaque RGBA", mixedImg},
+ {"RGBA with variable alpha", translucentImg},
+ }
+
+ for _, tc := range testCases {
+ t.Run(tc.name, func(t *testing.T) {
+ m0 := tc.img
+ m1, err := encodeDecode(m0)
+ if err != nil {
+ t.Fatal(err)
+ }
+ err = diff(convertToNRGBA(m0), m1)
+ if err != nil {
+ t.Error(err)
+ }
+ })
+ }
+}
+
func BenchmarkEncodeGray(b *testing.B) {
img := image.NewGray(image.Rect(0, 0, 640, 480))
b.SetBytes(640 * 480 * 1)
}
func BenchmarkEncodeRGBA(b *testing.B) {
- img := image.NewRGBA(image.Rect(0, 0, 640, 480))
+ const width, height = 640, 480
+ img := image.NewRGBA(image.Rect(0, 0, width, height))
+ for y := 0; y < height; y++ {
+ for x := 0; x < width; x++ {
+ percent := (x + y) % 100
+ switch {
+ case percent < 10: // 10% of pixels are translucent (have alpha >0 and <255)
+ img.Set(x, y, color.NRGBA{uint8(x), uint8(y), uint8(x * y), uint8(percent)})
+ case percent < 40: // 30% of pixels are transparent (have alpha == 0)
+ img.Set(x, y, color.NRGBA{uint8(x), uint8(y), uint8(x * y), 0})
+ default: // 60% of pixels are opaque (have alpha == 255)
+ img.Set(x, y, color.NRGBA{uint8(x), uint8(y), uint8(x * y), 255})
+ }
+ }
+ }
if img.Opaque() {
b.Fatal("expected image not to be opaque")
}
- b.SetBytes(640 * 480 * 4)
+ b.SetBytes(width * height * 4)
b.ReportAllocs()
b.ResetTimer()
for i := 0; i < b.N; i++ {