go-libwebp

Experimental translation from libwebp to Go source.
Log | Files | Refs | README | LICENSE

commit e4a3608cbc8d9fe44478cce306710d3e07b7a1e7
parent 82741fa8693af21b7c819edd160275b50302b60e
Author: Jack Mordaunt <jackmordaunt.dev@gmail.com>
Date:   Wed,  3 Jan 2024 16:37:45 +0800

webp: use new lib API

Signed-off-by: Jack Mordaunt <jackmordaunt.dev@gmail.com>

Diffstat:
Mwebp/encode.go | 50++++----------------------------------------------
Mwebp/webp_test.go | 34++++++++++++++++++++++++++++------
2 files changed, 32 insertions(+), 52 deletions(-)

diff --git a/webp/encode.go b/webp/encode.go @@ -1,11 +1,8 @@ package webp import ( - "fmt" "image" "io" - "runtime" - "unsafe" dynamic "git.sr.ht/~jackmordaunt/go-libwebp/lib/dynamic/webp" transpiled "git.sr.ht/~jackmordaunt/go-libwebp/lib/transpiled/webp" @@ -18,7 +15,7 @@ func Encode(w io.Writer, m image.Image, opt ...EncodeOption) error { op(&enc) } if enc.Quality <= 0.0 { - enc.Quality = 0.1 + enc.Quality = 0.9 } if enc.Quality > 1.0 { enc.Quality = 1.0 @@ -50,7 +47,7 @@ func Lossless() EncodeOption { // Encoder implements webp encoding of an image. type Encoder struct { // Quality is in the range (0,1]. Values outside of this - // range will be treated as 1. + // range will be treated as 1. Default 0.9. Quality float32 // Lossless indicates whether to use the lossless compression // strategy. If true, the Quality field is ignored. @@ -60,7 +57,6 @@ 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. -// Not threadsafe. func (enc *Encoder) Encode(w io.Writer, m image.Image) error { if enc.Quality <= 0.0 || enc.Quality > 1 { enc.Quality = 1.0 @@ -80,45 +76,7 @@ func (enc *Encoder) Encode(w io.Writer, m image.Image) error { func (enc *Encoder) encode(w io.Writer, m *image.RGBA) error { if err := dynamic.Init(); err == nil { - return encode(w, m, enc.Quality, dynamic.EncodeImpl, dynamic.FreeImpl) + return dynamic.EncodeImpl(w, m, enc.Quality) } - return encode(w, m, enc.Quality, transpiled.EncodeImpl, transpiled.FreeImpl) -} - -// encode an RGBA image losslessly into the provided writer. -func encode(w io.Writer, m *image.RGBA, q float32, enc encodeFunc, free freeFunc) error { - out := new(*byte) - - p := runtime.Pinner{} - defer p.Unpin() - - p.Pin(&m.Pix[0]) - p.Pin(out) - - size := enc( - unsafe.Pointer(&m.Pix[0]), - int32(m.Bounds().Dx()), - int32(m.Bounds().Dy()), - int32(m.Stride), - q, - unsafe.Pointer(out), - ) - - if size == 0 { - return fmt.Errorf("empty result") - } - - if *out == nil { - return fmt.Errorf("failed to allocate output buffer") - } - - defer free(unsafe.Pointer(*out)) - - buf := unsafe.Slice(*out, size) - - if _, err := w.Write(buf); err != nil { - return fmt.Errorf("writing the encoded webp: %w", err) - } - - return nil + return transpiled.EncodeImpl(w, m, enc.Quality) } diff --git a/webp/webp_test.go b/webp/webp_test.go @@ -6,10 +6,13 @@ import ( "image" "image/color" "image/png" + "io" "math/rand" "os" "path/filepath" "testing" + + stdwebp "golang.org/x/image/webp" ) //go:embed testdata/golden-in.png @@ -24,17 +27,30 @@ func TestLossless(t *testing.T) { t.Fatalf("decoding image: %v", err) } - buf := bytes.NewBuffer(nil) + buf1 := bytes.NewBuffer(nil) + buf2 := bytes.NewBuffer(nil) - if err := Encode(buf, m, Lossless()); err != nil { + if err := Encode(io.MultiWriter(buf1, buf2), m, Lossless()); err != nil { t.Fatalf("encoding webp: %v", err) } - if err := os.WriteFile(filepath.Join("testdata", "golden-got.webp"), buf.Bytes(), 0o644); err != nil { + if err := os.WriteFile(filepath.Join("testdata", "golden-got.webp"), buf1.Bytes(), 0o644); err != nil { t.Errorf("writing output png: %v", err) } - out, err := Decode(buf) + t.Run("stdlib webp outupt", func(t *testing.T) { + assertOutput(t, m, buf1, stdwebp.Decode) + }) + + t.Run("transpiled webp output", func(t *testing.T) { + assertOutput(t, m, buf2, Decode) + }) +} + +func assertOutput(t *testing.T, m image.Image, src io.Reader, decode func(io.Reader) (image.Image, error)) { + t.Helper() + + out, err := decode(src) if err != nil { t.Fatalf("decoding webp: %v", err) } @@ -42,13 +58,19 @@ func TestLossless(t *testing.T) { outb := out.Bounds() for xx := outb.Min.X; xx < outb.Max.X; xx++ { for yy := outb.Min.Y; yy < outb.Max.Y; yy++ { - if out.At(xx, yy) != m.At(xx, yy) { - t.Errorf("color mismatch after lossless encode: (%d, %d)", xx, 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) } } } } +func colorEqual(left, right color.Color) bool { + lr, lg, lb, la := left.RGBA() + rr, rg, rb, ra := right.RGBA() + return lr == rr && lg == rg && lb == rb && la == ra +} + 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))