commit 91d4abc35e3ec60ee61f6ba7456d18c7c6bc8451
parent 55f202710aba7f22fc3fd31da03651c1d48fe100
Author: Jack Mordaunt <jackmordaunt.dev@gmail.com>
Date: Wed, 3 Jan 2024 18:10:14 +0800
all: use NRGBA images (not pre-multiplied)
This is what WebP is expecting.
Signed-off-by: Jack Mordaunt <jackmordaunt.dev@gmail.com>
Diffstat:
7 files changed, 82 insertions(+), 37 deletions(-)
diff --git a/lib/common/common.go b/lib/common/common.go
@@ -18,7 +18,7 @@ type DecoderFunc func(data uintptr, data_size uint64, width uintptr, height uint
type FreeFunc func(uintptr)
// Encode an RGBA webp image into the provided writer.
-func Encode(w io.Writer, m *image.RGBA, q float32, enc EncodeFunc, free FreeFunc) error {
+func Encode(w io.Writer, m *image.NRGBA, q float32, enc EncodeFunc, free FreeFunc) error {
p := runtime.Pinner{}
defer p.Unpin()
@@ -62,8 +62,8 @@ func Decode(buf []byte, dec DecoderFunc, free FreeFunc) (image.Image, error) {
defer p.Unpin()
data := unsafe.SliceData(buf)
- width := new(int)
- height := new(int)
+ width := new(int32)
+ height := new(int32)
p.Pin(data)
p.Pin(width)
@@ -92,7 +92,7 @@ func Decode(buf []byte, dec DecoderFunc, free FreeFunc) (image.Image, error) {
// longer reachable. That way we could avoid this copy.
copy(pix, unsafe.Slice(samples, size))
- m := &image.RGBA{
+ m := &image.NRGBA{
Pix: pix,
Rect: image.Rectangle{Max: image.Point{X: w, Y: h}},
Stride: w * 4,
diff --git a/lib/dynamic/webp/encode_cgo.go b/lib/dynamic/webp/encode_cgo.go
@@ -11,6 +11,6 @@ import (
// HACK: always use lossless when cgo is activated on linux. cgo on linux provokes
// a bug in purego when using float arguments.
-func encodeImpl(w io.Writer, m *image.RGBA, quality float32) error {
+func encodeImpl(w io.Writer, m *image.NRGBA, quality float32) error {
return common.Encode(w, m, quality, wrappedLossless, WebPFree)
}
diff --git a/lib/dynamic/webp/encode_default.go b/lib/dynamic/webp/encode_default.go
@@ -9,7 +9,7 @@ import (
"git.sr.ht/~jackmordaunt/go-libwebp/lib/common"
)
-func encodeImpl(w io.Writer, m *image.RGBA, quality float32) error {
+func encodeImpl(w io.Writer, m *image.NRGBA, quality float32) error {
if quality >= 1.0 {
return common.Encode(w, m, quality, wrappedLossless, WebPFree)
}
diff --git a/lib/dynamic/webp/lib.go b/lib/dynamic/webp/lib.go
@@ -48,7 +48,7 @@ func DecodeImpl(buf []byte) (image.Image, error) {
return common.Decode(buf, WebPDecodeRGBA, WebPFree)
}
-func EncodeImpl(w io.Writer, m *image.RGBA, quality float32) error {
+func EncodeImpl(w io.Writer, m *image.NRGBA, quality float32) error {
return encodeImpl(w, m, quality)
}
diff --git a/lib/transpiled/webp/lib.go b/lib/transpiled/webp/lib.go
@@ -15,7 +15,7 @@ func DecodeImpl(buf []byte) (image.Image, error) {
return common.Decode(buf, makeDecodeImpl(tls), makeFreeImpl(tls))
}
-func EncodeImpl(w io.Writer, m *image.RGBA, quality float32) error {
+func EncodeImpl(w io.Writer, m *image.NRGBA, quality float32) error {
tls := libc.NewTLS()
defer tls.Close()
return common.Encode(w, m, quality, makeEncodeImpl(tls), makeFreeImpl(tls))
diff --git a/webp/encode.go b/webp/encode.go
@@ -55,16 +55,16 @@ type Encoder struct {
}
// Encode specified image as webp to w.
-// If the image is RGBA, the pixel buffer will be encoded directly.
-// If the image is not RGBA, it will be converted to RGBA first.
+// If the image is NRGBA, the pixel buffer will be encoded directly.
+// If the image is not NRGBA, it will be converted to NRGBA first.
func (enc *Encoder) Encode(w io.Writer, m image.Image) error {
if enc.Quality <= 0.0 || enc.Quality > 1 {
enc.Quality = 1.0
}
- if rgba, ok := m.(*image.RGBA); ok {
+ if rgba, ok := m.(*image.NRGBA); ok {
return enc.encode(w, rgba)
}
- rgba := image.NewRGBA(m.Bounds())
+ rgba := image.NewNRGBA(m.Bounds())
b := m.Bounds()
for y := b.Min.Y; y < b.Max.Y; y++ {
for x := b.Min.X; x < b.Max.X; x++ {
@@ -74,7 +74,7 @@ func (enc *Encoder) Encode(w io.Writer, m image.Image) error {
return enc.encode(w, rgba)
}
-func (enc *Encoder) encode(w io.Writer, m *image.RGBA) error {
+func (enc *Encoder) encode(w io.Writer, m *image.NRGBA) error {
if err := dynamic.Init(); err == nil {
return dynamic.EncodeImpl(w, m, enc.Quality)
}
diff --git a/webp/webp_test.go b/webp/webp_test.go
@@ -3,6 +3,7 @@ package webp
import (
"bytes"
_ "embed"
+ "fmt"
"image"
"image/color"
"image/png"
@@ -59,12 +60,31 @@ func assertOutput(t *testing.T, m image.Image, src io.Reader, decode func(io.Rea
for xx := outb.Min.X; xx < outb.Max.X; xx++ {
for yy := outb.Min.Y; yy < outb.Max.Y; yy++ {
if got, want := out.At(xx, yy), m.At(xx, yy); !colorEqual(got, want) {
- t.Fatalf("color mismatch after lossless encode: Point = (%d, %d) Got = %v Want = %v", xx, yy, got, want)
+ save(t, fmt.Sprintf("got-%d_%d.webp", xx, yy), out)
+ save(t, fmt.Sprintf("want-%d_%d.webp", xx, yy), m)
+ t.Fatalf("color mismatch after lossless encode: Point = (%d, %d) Got = %v Want = %v Bounds= %d", xx, yy, got, want, outb)
}
}
}
}
+func save(t *testing.T, name string, m image.Image) {
+ t.Helper()
+
+ if err := func() error {
+ f, err := os.Create(name)
+ if err != nil {
+ return err
+ }
+ defer f.Close()
+ defer f.Sync()
+ return Encode(f, m, Lossless())
+
+ }(); err != nil {
+ t.Error(err)
+ }
+}
+
func colorEqual(left, right color.Color) bool {
lr, lg, lb, la := left.RGBA()
rr, rg, rb, ra := right.RGBA()
@@ -72,40 +92,65 @@ func colorEqual(left, right color.Color) bool {
}
func FuzzEncode(f *testing.F) {
- f.Add(uint16(0), uint16(0), uint16(0), uint16(0), int64(0), float32(0))
- f.Add(uint16(0), uint16(0), uint16(1), uint16(1), int64(1), float32(1))
- f.Add(uint16(0), uint16(0), uint16(1), uint16(1), int64(2), float32(0.5))
- f.Add(uint16(0), uint16(0), uint16(100), uint16(100), int64(3), float32(0.75))
- f.Add(uint16(0), uint16(0), uint16(100), uint16(100), int64(3), float32(0.9))
- f.Add(uint16(0), uint16(0), uint16(100), uint16(100), int64(3), float32(0.95))
- f.Add(uint16(0), uint16(0), uint16(100), uint16(100), int64(3), float32(1.0))
- f.Fuzz(func(t *testing.T, x0, y0, x1, y1 uint16, seed int64, quality float32) {
+ f.Add(uint16(000), uint16(000), int64(0), float32(0.00), true)
+ f.Add(uint16(000), uint16(000), int64(0), float32(0.00), false)
+ f.Add(uint16(001), uint16(001), int64(1), float32(1.00), true)
+ f.Add(uint16(001), uint16(001), int64(1), float32(1.00), false)
+ f.Add(uint16(001), uint16(001), int64(2), float32(0.50), true)
+ f.Add(uint16(001), uint16(001), int64(2), float32(0.50), false)
+ f.Add(uint16(100), uint16(100), int64(3), float32(0.75), true)
+ f.Add(uint16(100), uint16(100), int64(3), float32(0.75), false)
+ f.Add(uint16(100), uint16(100), int64(3), float32(0.90), true)
+ f.Add(uint16(100), uint16(100), int64(3), float32(0.90), false)
+ f.Add(uint16(100), uint16(100), int64(3), float32(0.95), true)
+ f.Add(uint16(100), uint16(100), int64(3), float32(0.95), false)
+ f.Add(uint16(100), uint16(100), int64(3), float32(1.00), true)
+ f.Add(uint16(100), uint16(100), int64(3), float32(1.00), false)
+
+ f.Fuzz(func(t *testing.T, x1, y1 uint16, seed int64, quality float32, lossless bool) {
+ if quality <= 0 || quality > 1 {
+ t.Skip()
+ return
+ }
+
+ if int(x1) <= 0 || int(y1) <= 0 {
+ t.Skip()
+ return
+ }
+
+ t.Logf("(%d, %d), quality=%.2f, lossless=%v", x1, y1, quality, lossless)
+
rng := rand.New(rand.NewSource(seed))
- m := image.NewNRGBA(image.Rect(int(x0), int(y0), int(x1), int(y1)))
- for x := x0; x < x1; x += 1 {
- for y := y0; y < y1; y += 1 {
+
+ m := image.NewNRGBA(image.Rect(0, 0, int(x1), int(y1)))
+
+ for x := uint16(0); x < x1; x += 1 {
+ for y := uint16(0); y < y1; y += 1 {
colors := rng.Uint32()
r := uint8(colors)
g := uint8(colors >> 8)
b := uint8(colors >> 16)
a := uint8(colors >> 24)
- m.Set(int(x), int(y), color.RGBA{R: r, G: g, B: b, A: a})
+ m.SetNRGBA(int(x), int(y), color.NRGBA{R: r, G: g, B: b, A: a})
}
}
- if quality <= 0 || quality > 1 {
- t.Skip()
- return
- }
- if int(x1)-int(x0) <= 0 || int(y1)-int(y0) <= 0 {
- t.Skip()
- return
+
+ opts := []EncodeOption{Quality(quality)}
+
+ if lossless {
+ opts = append(opts, Lossless())
}
+
buf := bytes.NewBuffer(nil)
- if err := Encode(buf, m, Quality(quality)); err != nil {
- t.Errorf("encode error: %v (x0: %d, y0: %d, x1: %d, y1: %d)", err, x0, y0, x1, y1)
+
+ if err := Encode(buf, m, opts...); err != nil {
+ t.Errorf("encode error: %v", err)
}
- if _, err := Decode(buf); err != nil {
- t.Errorf("decode error: %v (x0: %d, y0: %d, x1: %d, y1: %d)", err, x0, y0, x1, y1)
+
+ if lossless {
+ // If lossless, we can do a pixel-wise comparison between the original and the
+ // decoded image.
+ assertOutput(t, m, buf, Decode)
}
})
}