144 lines
3.7 KiB
Go
144 lines
3.7 KiB
Go
// Package webpenc encodes WebP pictures in pure Go.
|
|
//
|
|
// libwebp.go is libwebp compiled to WebAssembly and translated to Go by
|
|
// wasm2go, taken unchanged (apart from the package name) from
|
|
// github.com/gen2brain/webp; see README.md. Its memory is one Go byte slice
|
|
// and every access to it is length-checked, so a fault in libwebp stays
|
|
// inside that slice. This package only encodes, and only pixels that Go's own
|
|
// decoders produced: it never parses a file someone uploaded. It loads no
|
|
// system library and registers no image format.
|
|
package webpenc
|
|
|
|
import (
|
|
"errors"
|
|
"image"
|
|
"io"
|
|
)
|
|
|
|
var (
|
|
errMemWrite = errors.New("webp: couldn't hand the picture to the encoder")
|
|
errMemRead = errors.New("webp: couldn't read the encoder's output")
|
|
errEncode = errors.New("webp: encoding failed")
|
|
)
|
|
|
|
// Options choose between lossy (photographs) and lossless (screenshots and
|
|
// drawings). Quality is 1-100 for lossy; for lossless it trades time for size.
|
|
type Options struct {
|
|
Quality int
|
|
Lossless bool
|
|
}
|
|
|
|
// Encode writes m as WebP.
|
|
func Encode(w io.Writer, m *image.RGBA, o Options) error {
|
|
q := o.Quality
|
|
if q <= 0 || q > 100 {
|
|
q = 80
|
|
}
|
|
width, height := m.Rect.Dx(), m.Rect.Dy()
|
|
data := m.Pix
|
|
if m.Stride != 4*width || len(data) != 4*width*height {
|
|
c := image.NewRGBA(image.Rect(0, 0, width, height))
|
|
for y := 0; y < height; y++ {
|
|
copy(c.Pix[y*c.Stride:(y+1)*c.Stride], m.Pix[m.PixOffset(m.Rect.Min.X, m.Rect.Min.Y+y):])
|
|
}
|
|
data = c.Pix
|
|
}
|
|
// The RGBA here is premultiplied; libwebp wants straight alpha. Only
|
|
// pixels that are partly transparent differ.
|
|
if !m.Opaque() {
|
|
data = unpremultiply(data)
|
|
}
|
|
|
|
mod := newModule()
|
|
in := mod.Xmalloc(int32(len(data)))
|
|
defer mod.Xfree(in)
|
|
if !mod.write(in, data) {
|
|
return errMemWrite
|
|
}
|
|
sizePtr := mod.Xmalloc(8)
|
|
defer mod.Xfree(sizePtr)
|
|
mod.write(sizePtr, make([]byte, 8)) // size_t is 4 bytes in the module; keep the rest zero
|
|
lossless := int32(0)
|
|
if o.Lossless {
|
|
lossless = 1
|
|
}
|
|
const method = 4 // libwebp's default balance of speed and size
|
|
out := mod.Xencode(in, int32(width), int32(height), sizePtr, 0, int32(q), method, lossless, 0)
|
|
defer mod.Xfree(out)
|
|
size, ok := mod.readUint64(sizePtr)
|
|
if !ok {
|
|
return errMemRead
|
|
}
|
|
if size == 0 {
|
|
return errEncode
|
|
}
|
|
b, ok := mod.read(out, int32(size))
|
|
if !ok {
|
|
return errMemRead
|
|
}
|
|
_, err := w.Write(b)
|
|
return err
|
|
}
|
|
|
|
func unpremultiply(p []byte) []byte {
|
|
out := make([]byte, len(p))
|
|
copy(out, p)
|
|
for i := 0; i+3 < len(out); i += 4 {
|
|
a := uint32(out[i+3])
|
|
if a == 0 || a == 255 {
|
|
continue
|
|
}
|
|
for k := 0; k < 3; k++ {
|
|
out[i+k] = uint8(min(255, (uint32(out[i+k])*255+a/2)/a))
|
|
}
|
|
}
|
|
return out
|
|
}
|
|
|
|
func (m *module) write(ptr int32, data []byte) bool {
|
|
if ptr < 0 || int(ptr)+len(data) > len(m.memory) {
|
|
return false
|
|
}
|
|
copy(m.memory[ptr:], data)
|
|
return true
|
|
}
|
|
|
|
func (m *module) read(ptr, size int32) ([]byte, bool) {
|
|
if ptr < 0 || size < 0 || int(ptr)+int(size) > len(m.memory) {
|
|
return nil, false
|
|
}
|
|
out := make([]byte, size)
|
|
copy(out, m.memory[ptr:ptr+size])
|
|
return out, true
|
|
}
|
|
|
|
func (m *module) readUint64(ptr int32) (uint64, bool) {
|
|
if ptr < 0 || int(ptr)+8 > len(m.memory) {
|
|
return 0, false
|
|
}
|
|
return load64(m.memory[ptr:]), true
|
|
}
|
|
|
|
func newModule() *module {
|
|
mod := newModuleRaw(&wasiHost{})
|
|
mod.X_initialize()
|
|
return mod
|
|
}
|
|
|
|
// wasiHost answers the module's two system calls; libwebp only writes
|
|
// diagnostics, which are dropped.
|
|
type wasiHost struct{ mod *module }
|
|
|
|
func (h *wasiHost) Init(m any) { h.mod = m.(*module) }
|
|
func (h *wasiHost) Xfd_close(fd int32) int32 { return 0 }
|
|
|
|
func (h *wasiHost) Xfd_write(fd, iovs, iovsLen, nwrittenPtr int32) int32 {
|
|
mem := h.mod.memory
|
|
var written uint32
|
|
for i := int32(0); i < iovsLen; i++ {
|
|
written += load32(mem[iovs+i*8+4:])
|
|
}
|
|
store32(mem[nwrittenPtr:], written)
|
|
return 0
|
|
}
|