commit 55f31ea7f7298c3aeb2a7b051784a7ff313f0d35
parent 40afa63773a71fe1e7f88fd6e0676ac4ce26324b
Author: Jack Mordaunt <jackmordaunt.dev@gmail.com>
Date: Wed, 3 Jan 2024 16:26:56 +0800
lib/common: capture unsafe logic
Signed-off-by: Jack Mordaunt <jackmordaunt.dev@gmail.com>
Diffstat:
| A | lib/common/common.go | | | 102 | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ |
1 file changed, 102 insertions(+), 0 deletions(-)
diff --git a/lib/common/common.go b/lib/common/common.go
@@ -0,0 +1,102 @@
+// Package common captures the unsafe orchestration that needs to occur when interacting
+// with libwebp. The function implementations can be swapped out based on the desired
+// implementation.
+package common
+
+import (
+ "fmt"
+ "image"
+ "io"
+ "runtime"
+ "unsafe"
+)
+
+type EncodeFunc func(in uintptr, w int32, h int32, bps int32, q float32, out uintptr) (size uint64)
+
+type DecoderFunc func(data uintptr, data_size uint64, width uintptr, height uintptr) (rgba uintptr)
+
+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 {
+ p := runtime.Pinner{}
+ defer p.Unpin()
+
+ out := new(*byte)
+ pix := unsafe.SliceData(m.Pix)
+
+ p.Pin(pix)
+ p.Pin(out)
+
+ size := enc(
+ uintptr(unsafe.Pointer(pix)),
+ int32(m.Bounds().Dx()),
+ int32(m.Bounds().Dy()),
+ int32(m.Stride),
+ q,
+ uintptr(unsafe.Pointer(out)),
+ )
+
+ if size == 0 {
+ return fmt.Errorf("empty result")
+ }
+
+ if *out == nil {
+ return fmt.Errorf("failed to allocate output buffer")
+ }
+
+ defer free(uintptr(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
+}
+
+// Decode the webp data into an [image.Image].
+func Decode(buf []byte, dec DecoderFunc, free FreeFunc) (image.Image, error) {
+ p := runtime.Pinner{}
+ defer p.Unpin()
+
+ data := unsafe.SliceData(buf)
+ width := new(int)
+ height := new(int)
+
+ p.Pin(data)
+ p.Pin(width)
+ p.Pin(height)
+
+ samples := (*uint8)(unsafe.Pointer(dec(
+ uintptr(unsafe.Pointer(data)),
+ uint64(len(buf)),
+ uintptr(unsafe.Pointer(width)),
+ uintptr(unsafe.Pointer(height)),
+ )))
+
+ if samples == nil || width == nil || height == nil {
+ return nil, fmt.Errorf("failed decoding webp into rgba")
+ }
+
+ defer free(uintptr(unsafe.Pointer(samples)))
+
+ w, h := int(*width), int(*height)
+
+ size := w * h * 4
+
+ pix := make([]uint8, size)
+
+ // PERF: I wonder if we could use a finalizer to free the memory when the image is no
+ // longer reachable. That way we could avoid this copy.
+ copy(pix, unsafe.Slice(samples, size))
+
+ m := &image.RGBA{
+ Pix: pix,
+ Rect: image.Rectangle{Max: image.Point{X: w, Y: h}},
+ Stride: w * 4,
+ }
+
+ return m, nil
+}