common.go (2265B)
1 // Package common captures the unsafe orchestration that needs to occur when interacting 2 // with libwebp. The function implementations can be swapped out based on the desired 3 // implementation. 4 package common 5 6 import ( 7 "fmt" 8 "image" 9 "io" 10 "runtime" 11 "unsafe" 12 ) 13 14 type EncodeFunc func(in uintptr, w int32, h int32, bps int32, q float32, out uintptr) (size uint64) 15 16 type DecoderFunc func(data uintptr, data_size uint64, width uintptr, height uintptr) (rgba uintptr) 17 18 type FreeFunc func(uintptr) 19 20 // Encode an RGBA webp image into the provided writer. 21 func Encode(w io.Writer, m *image.NRGBA, q float32, enc EncodeFunc, free FreeFunc) error { 22 p := runtime.Pinner{} 23 defer p.Unpin() 24 25 out := new(*byte) 26 pix := unsafe.SliceData(m.Pix) 27 28 p.Pin(pix) 29 p.Pin(out) 30 31 size := enc( 32 uintptr(unsafe.Pointer(pix)), 33 int32(m.Bounds().Dx()), 34 int32(m.Bounds().Dy()), 35 int32(m.Stride), 36 q, 37 uintptr(unsafe.Pointer(out)), 38 ) 39 40 if size == 0 { 41 return fmt.Errorf("empty result") 42 } 43 44 if *out == nil { 45 return fmt.Errorf("failed to allocate output buffer") 46 } 47 48 defer free(uintptr(unsafe.Pointer(*out))) 49 50 buf := unsafe.Slice(*out, size) 51 52 if _, err := w.Write(buf); err != nil { 53 return fmt.Errorf("writing the encoded webp: %w", err) 54 } 55 56 return nil 57 } 58 59 // Decode the webp data into an [image.Image]. 60 func Decode(buf []byte, dec DecoderFunc, free FreeFunc) (image.Image, error) { 61 p := runtime.Pinner{} 62 defer p.Unpin() 63 64 data := unsafe.SliceData(buf) 65 width := new(int32) 66 height := new(int32) 67 68 p.Pin(data) 69 p.Pin(width) 70 p.Pin(height) 71 72 samples := (*uint8)(unsafe.Pointer(dec( 73 uintptr(unsafe.Pointer(data)), 74 uint64(len(buf)), 75 uintptr(unsafe.Pointer(width)), 76 uintptr(unsafe.Pointer(height)), 77 ))) 78 79 if samples == nil || width == nil || height == nil { 80 return nil, fmt.Errorf("failed decoding webp into rgba") 81 } 82 83 defer free(uintptr(unsafe.Pointer(samples))) 84 85 w, h := int(*width), int(*height) 86 87 size := w * h * 4 88 89 pix := make([]uint8, size) 90 91 // PERF: I wonder if we could use a finalizer to free the memory when the image is no 92 // longer reachable. That way we could avoid this copy. 93 copy(pix, unsafe.Slice(samples, size)) 94 95 m := &image.NRGBA{ 96 Pix: pix, 97 Rect: image.Rectangle{Max: image.Point{X: w, Y: h}}, 98 Stride: w * 4, 99 } 100 101 return m, nil 102 }