gowebp

package module
v0.1.2 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Jun 8, 2026 License: MIT Imports: 16 Imported by: 0

README

Tests Coverage Status Go Reference

gowebp

A pure-Go WebP codec. No libwebp, no cgo, no runtime dependencies beyond golang.org/x/image (which is used for decoding only).

Encodes to either WebP format:

  • VP8L (lossless) — default. Every pixel preserved exactly.
  • VP8 (lossy) — opt-in via Options.Lossy. Full intra-only VP8 keyframe encoder with I16 + B_PRED + UV8 intra prediction, DCT + Walsh-Hadamard transforms, boolean arithmetic coding, loop filter, and reconstruction-based RD mode selection.

Also encodes WebP animations (ANIM/ANMF) with either codec, and preserves alpha channels via ALPH chunks (raw or VP8L-compressed, static or in-animation).

Installation

go get github.com/linzeyan/webp-go

Command-line tool

A gowebp binary converts images to WebP from the shell. Pre-built binaries for Linux, macOS, and Windows are attached to each release, or install from source:

go install github.com/linzeyan/webp-go/cmd/gowebp@latest
gowebp photo.png                 # -> photo.webp (lossless, the default)
gowebp -lossy -q 80 photo.jpg    # -> photo.webp (lossy VP8)
gowebp anim.gif                  # -> anim.webp (animated GIF -> animated WebP)
gowebp a.png b.png c.png         # batch: each -> .webp in place
gowebp -o out/ *.png             # batch into a directory
cat a.png | gowebp - > a.webp    # stdin -> stdout

Inputs may be PNG, JPEG, GIF, or WebP (auto-detected). Flags: -o (output file / directory / - for stdout), -lossy, -lossless, -q (quality 0–100), -m (method/effort 0–6), -near (near-lossless bits), and -max-pixels (reject inputs over N pixels — width × height × frames — to guard against decompression bombs; 0 disables it). Run gowebp -h for the full list.

Usage

Lossless
import "github.com/linzeyan/webp-go"

f, _ := os.Create(name)
defer f.Close()
if err := gowebp.Encode(f, img, nil); err != nil {
    log.Fatal(err)
}

For a smaller (but slower) lossless file, set Method to any positive value. It makes the encoder search for the best predictor tile size and color-cache size, and switch to a palette when the image has 256 or fewer colors (logos, screenshots); the decoded pixels are identical, only the file size changes. Method 0 (the default) keeps the fast path.

NearLossless trades a small, bounded error for smaller files. It is the maximum number of low R/G/B bits the encoder may drop in smooth regions (alpha and edges stay exact); the per-channel error is at most 2^NearLossless - 1. The encoder also keeps the exact encoding when it is smaller, so the output is never larger than plain lossless. It helps most on noisy/photographic content. NearLossless 0 (the default) is exact lossless.

err := gowebp.Encode(f, img, &gowebp.Options{Method: 1})
Lossy
err := gowebp.Encode(f, img, &gowebp.Options{
    Lossy:   true,
    Quality: 75, // 0 (smallest) to 100 (best), default 75
    Method:  4,  // 0 (fastest) to 6 (highest quality), default 0
})
Animation
ani := gowebp.Animation{
    Images:          []image.Image{frame1, frame2, frame3},
    Durations:       []uint{100, 100, 100}, // ms per frame
    Disposals:       []uint{0, 0, 0},       // 0=keep, 1=clear
    LoopCount:       0,                     // 0 = infinite
    BackgroundColor: 0xffffffff,            // BGRA
}
// Pass Options{Lossy: true} for VP8 frames; nil for VP8L.
gowebp.EncodeAll(f, &ani, nil)
Metadata (ICC / EXIF / XMP)

Setting any of ICCProfile, EXIF, or XMP automatically wraps the output in a VP8X container and writes the corresponding chunks in the spec-mandated order (VP8X → ICCP → image data → EXIF → XMP). Works with lossless, lossy, and animated output.

err := gowebp.Encode(f, img, &gowebp.Options{
    ICCProfile: iccBytes, // raw ICC profile
    EXIF:       exifBytes, // raw EXIF block
    XMP:        xmpBytes,  // raw XMP packet
})
Cancellation

EncodeContext and EncodeAllContext accept a context.Context and abort with ctx.Err() when it is cancelled. Cancellation is polled before each animation frame and between macroblock rows of a lossy image; a lossless single image runs to completion once started.

err := gowebp.EncodeContext(ctx, f, img, &gowebp.Options{Lossy: true})
Decoding
img, err := gowebp.Decode(r)

Wraps golang.org/x/image/webp. Also provides DecodeIgnoreAlphaFlag for VP8X files where a misset alpha flag trips up the standard decoder.

Lossy method tiers

The Method field picks between different speed/quality/size tradeoffs. Higher values spend more CPU per macroblock to produce smaller or higher-quality output.

Method Strategy Notes
0 I16 with DC_PRED only fastest baseline
1 I16 with 4-mode SSE search +1-3 dB over M0
2 B_PRED with 10 I4 modes per sub-block best on textured content
3 Per-MB I16/B_PRED arbitration (prediction SSE) fast, good default
4 Per-MB arbitration with reconstruction-aware RDO recommended
5 Dual-path (measure both I16 and B_PRED) principled reference
6 Adds trailing-coefficient trellis trim helps on high-freq content

Encoding is goroutine-safe: each Encode/EncodeAll call is self-contained and has no shared mutable state. EncodeAll also encodes its frames concurrently (up to GOMAXPROCS), so multi-frame animations scale across cores while producing byte-identical output.

Benchmarks

Measured on Intel i9-14900K (Linux, Go 1.25) against the 5 natural photos linked from Google's WebP Gallery. Baseline is Go's image/png with png.BestCompression.

Reproduce:

NATIVEWEBP_FETCH=1 go test -run TestGalleryPSNR -v    # populate testdata/
go test -bench=. -benchtime=5x
File sizes (bytes per frame)
Image PNG Lossless Lossy Q=50 M=4 Lossy Q=75 M=3 Lossy Q=75 M=4 Lossy Q=90 M=4
1.png (400×301) 120 188 97 720 16 766 17 648 24 906 43 518
2.png (386×395) 45 659 37 018 15 050 13 808 21 888 33 632
3.png (800×600) 236 018 194 714 80 992 80 552 95 330 130 828
4.png (421×163) 52 681 41 554 27 304 24 838 35 736 47 510
5.png (300×300) 138 919 123 932 74 202 78 300 97 588 133 548
  • Lossless (VP8L) averages 13-23 % smaller than PNG's best compression.
  • Lossy Q=75 M=3 is 1.8×-3.3× smaller than lossless on the same images.
  • Lossy Q=50 M=4 can reach 7× smaller than PNG while still producing visually acceptable output on photos.
Encode time (ns/op)
Image PNG Lossless Lossy Q=75 M=3 Lossy Q=75 M=4
1.png (400×301) 38 034 25 745 30 476 30 629
2.png (386×395) 82 888 29 999 34 298 49 051
3.png (800×600) 152 804 100 415 110 645 117 259
4.png (421×163) 23 707 24 294 17 631 19 426
5.png (300×300) 54 435 20 294 27 094 27 931

All timings in microseconds for brevity: lossless WebP is ≈ 1.5-2.8× faster than PNG across the gallery, while producing smaller files. Lossy encoding is typically within a small constant factor of lossless speed.

Quality (spec-correct Y-PSNR, lossy Q=75 M=3)

Y-PSNR measured against VP8 limited-range BT.601 (what a real decoder shows). Higher is better; >40 dB is visually lossless on photographic content.

Image Y-PSNR
1.png 39.2 dB
2.png 42.2 dB
3.png 43.5 dB
4.png 38.7 dB
5.png 36.7 dB

Threshold floors on these numbers are asserted by TestGalleryPSNR so future encoder changes can't quietly regress quality.

BT.601 color handling

VP8 stores pixels in limited-range BT.601 YCbCr (luma 16-235, chroma 16-240) per RFC 6386. Go's stdlib image/color.YCbCrToRGB uses the JFIF full-range inverse, which shifts the resulting RGB by 2-5 units per channel when applied to VP8 samples.

Decode handles this automatically: for VP8 sources the returned image is a *gowebp.BT601YCbCr (or *gowebp.BT601NYCbCrA when the source has an ALPH chunk) whose At() method uses the spec-correct limited-range BT.601 inverse. Calling img.At(x, y).RGBA() gives the same RGB values a libwebp or browser decoder would produce.

Users who want the raw *image.YCbCr for zero-copy plane access can type-assert to *gowebp.BT601YCbCr and read the embedded field:

img, _ := gowebp.Decode(r)
if y, ok := img.(*gowebp.BT601YCbCr); ok {
    rawYCbCr := y.YCbCr     // *image.YCbCr with limited-range samples
    _ = rawYCbCr
}

VP8L (lossless) sources decode to *image.NRGBA with exact pixel fidelity and don't go through YCbCr, so no wrapping is applied.

Implementation notes

The lossy VP8 encoder lives in internal/vp8enc/ and is a pure-Go implementation of RFC 6386 (The VP8 Data Format and Decoding Guide). The following open-source implementations were consulted as references for bit-exact compatibility; no code was copied, but table values (which are specification constants) were transcribed:

  • golang.org/x/image/vp8 — pure-Go VP8 decoder, used as the round-trip test oracle (BSD-3-Clause).
  • libwebp — the reference C encoder/decoder from Google (BSD-3-Clause).
  • libvpx — VP8/VP9 reference codec (BSD-3-Clause).

Every file in internal/vp8enc/ references the relevant RFC 6386 section and the matching path in the x/image/vp8 decoder, which is the oracle used by the test suite's round-trip integration tests.

Remaining work (tracked in the source, not here): full rate-distortion optimization with calibrated λ; proper Viterbi trellis quantization with context-aware token-tree rate estimation.

License

MIT, same as the upstream project this was forked from (HugoSmits86/nativewebp).

Documentation

Index

Constants

This section is empty.

Variables

View Source
var (
	BT601YCbCrColorModel   = color.ModelFunc(bt601YCbCrModel)
	BT601NYCbCrAColorModel = color.ModelFunc(bt601NYCbCrAModel)
)

BT601YCbCrColorModel and BT601NYCbCrAColorModel convert an arbitrary color into the limited-range BT.601 representation.

Functions

func Decode

func Decode(r io.Reader) (image.Image, error)

Decode reads a WebP image from the provided io.Reader and returns it as an image.Image.

For VP8L (lossless) sources the returned image is an *image.NRGBA (or compatible) from the underlying x/image/webp decoder. For VP8 (lossy) sources it is wrapped as a *BT601YCbCr (or *BT601NYCbCrA when the source has an ALPH chunk) so that img.At(x, y).RGBA() produces the colors a spec-compliant VP8 decoder would show; Go's stdlib color.YCbCrToRGB uses JFIF full-range math which shifts VP8 pixels by several units per channel.

Callers that specifically need the raw *image.YCbCr / *image.NYCbCrA (e.g. for zero-copy plane access) can type-assert to *BT601YCbCr or *BT601NYCbCrA and read the embedded field.

func DecodeConfig

func DecodeConfig(r io.Reader) (image.Config, error)

DecodeConfig reads the image configuration from the provided io.Reader without fully decoding the image.

This function is a wrapper around the underlying WebP decode package (golang.org/x/image/webp) and provides access to the image's metadata, such as its dimensions and color model. It is useful for obtaining image information before performing a full decode.

Parameters:

r - The source io.Reader containing the WebP encoded image.

Returns:

An image.Config containing the image's dimensions and color model, or an error if the configuration cannot be retrieved

func DecodeIgnoreAlphaFlag

func DecodeIgnoreAlphaFlag(r io.Reader) (image.Image, error)

DecodeIgnoreAlphaFlag reads a WebP image from the provided io.Reader and returns it as an image.Image.

This function fixes x/image/webp rejecting VP8L images with the VP8X alpha flag, expecting an ALPHA chunk. VP8L handles transparency internally, and the WebP spec requires the flag for transparency.

This function is a wrapper around the underlying WebP decode package (golang.org/x/image/webp). It supports both lossy and lossless WebP formats, decoding the image accordingly.

Parameters:

r - The source io.Reader containing the WebP encoded image.

Returns:

The decoded image as image.Image or an error if the decoding fails.

func Encode

func Encode(w io.Writer, img image.Image, o *Options) error

Encode writes the provided image.Image to the specified io.Writer in WebP format.

This function always encodes the image using VP8L (lossless WebP). If `UseExtendedFormat` is enabled, it wraps the VP8L frame inside a VP8X container, allowing the use of metadata such as EXIF, ICC color profiles, or XMP metadata.

Note: VP8L already supports transparency, so VP8X is **not required** for alpha support.

Parameters:

w   - The destination writer where the encoded WebP image will be written.
img - The input image to be encoded.
o   - Pointer to Options containing encoding settings:
      - UseExtendedFormat: If true, wraps the image in a VP8X container to enable
        extended WebP features like metadata.

Returns:

An error if encoding fails or writing to the io.Writer encounters an issue.

func EncodeAll

func EncodeAll(w io.Writer, ani *Animation, o *Options) error

EncodeAll writes the provided animation sequence to the specified io.Writer in WebP format.

This function encodes a list of frames as a WebP animation using the VP8X container, which supports features like looping, frame timing, disposal methods, and background color settings. Each frame is individually compressed using the VP8L (lossless) format.

Note: Even if `UseExtendedFormat` is not explicitly set, animations always use the VP8X container because it is required for WebP animation support.

Parameters:

w   - The destination writer where the encoded WebP animation will be written.
ani - Pointer to Animation containing the frames and animation settings:
      - Images: List of frames to encode.
      - Durations: Display times for each frame in milliseconds.
      - Disposals: Disposal methods after frame display (keep or clear).
      - LoopCount: Number of times the animation should loop (0 = infinite).
      - BackgroundColor: Background color for the canvas, used when clearing.
o   - Pointer to Options containing additional encoding settings:
      - UseExtendedFormat: Currently unused for animations, but accepted for consistency.

Returns:

An error if encoding fails or writing to the io.Writer encounters an issue.

func EncodeAllContext

func EncodeAllContext(ctx context.Context, w io.Writer, ani *Animation, o *Options) error

EncodeAllContext behaves like EncodeAll but aborts with ctx.Err() when ctx is cancelled. Cancellation is checked before each frame is encoded, which is the granularity that matters for long multi-frame animations.

func EncodeContext

func EncodeContext(ctx context.Context, w io.Writer, img image.Image, o *Options) error

EncodeContext behaves like Encode but aborts early, returning ctx.Err(), when ctx is cancelled. Cancellation is checked before encoding starts; a lossy single-image encode is additionally polled between macroblock rows. A lossless single-image encode runs to completion once started (it is fast; guard against oversized inputs with an explicit size limit instead).

Types

type Animation

type Animation struct {
	Images          []image.Image
	Durations       []uint
	Disposals       []uint
	LoopCount       uint16
	BackgroundColor uint32
}

Animation holds configuration settings for WebP animations.

It allows encoding a sequence of frames with individual timing and disposal options, supporting features like looping and background color settings.

Fields:

  • Images: A list of frames to be displayed in sequence.
  • Durations: Timing for each frame in milliseconds, matching the Images slice.
  • Disposals: Disposal methods for frames after display; 0 = keep, 1 = clear to background.
  • LoopCount: Number of times the animation should repeat; 0 means infinite looping.
  • BackgroundColor: Canvas background color in BGRA order, used for clear operations.

type BT601NYCbCrA

type BT601NYCbCrA struct {
	*image.NYCbCrA
}

BT601NYCbCrA wraps an *image.NYCbCrA for sources with an ALPH chunk.

func (*BT601NYCbCrA) At

func (b *BT601NYCbCrA) At(x, y int) color.Color

func (*BT601NYCbCrA) BT601NYCbCrAAt

func (b *BT601NYCbCrA) BT601NYCbCrAAt(x, y int) BT601NYCbCrAColor

func (*BT601NYCbCrA) ColorModel

func (b *BT601NYCbCrA) ColorModel() color.Model

type BT601NYCbCrAColor

type BT601NYCbCrAColor struct {
	BT601YCbCrColor
	A uint8
}

BT601NYCbCrAColor is BT601YCbCrColor with a non-premultiplied alpha channel. Used by the WebP decoder output when the source has an ALPH chunk.

func (BT601NYCbCrAColor) RGBA

func (c BT601NYCbCrAColor) RGBA() (r, g, b, a uint32)

RGBA returns premultiplied 16-bit components.

type BT601YCbCr

type BT601YCbCr struct {
	*image.YCbCr
}

BT601YCbCr wraps an *image.YCbCr whose samples are in VP8's limited-range BT.601. It forwards everything to the embedded image except At(), which returns BT601YCbCrColor for spec-correct RGB conversion.

Callers who specifically want the raw stdlib YCbCr (e.g. for fast plane-level access) can dereference the embedded *image.YCbCr field directly.

func (*BT601YCbCr) At

func (b *BT601YCbCr) At(x, y int) color.Color

func (*BT601YCbCr) BT601YCbCrAt

func (b *BT601YCbCr) BT601YCbCrAt(x, y int) BT601YCbCrColor

BT601YCbCrAt is a typed helper that avoids the color.Color interface allocation when callers know they want BT601YCbCrColor.

func (*BT601YCbCr) ColorModel

func (b *BT601YCbCr) ColorModel() color.Model

type BT601YCbCrColor

type BT601YCbCrColor struct {
	Y, Cb, Cr uint8
}

BT601YCbCrColor is a single pixel in VP8 limited-range BT.601 color space (luma 16–235, chroma 16–240), the color space WebP files actually store per RFC 6386.

It exists because Go's stdlib `image/color.YCbCrToRGB` applies the JFIF (full-range) inverse even to values that were produced under limited-range encoding, which shifts the resulting RGB by 2–5 units per channel. BT601YCbCrColor.RGBA() uses the spec-correct inverse:

R = clip((298·(Y-16) + 409·(Cr-128) + 128) >> 8)
G = clip((298·(Y-16) -  100·(Cb-128) - 208·(Cr-128) + 128) >> 8)
B = clip((298·(Y-16) + 516·(Cb-128) + 128) >> 8)

Decode returns images whose pixels are BT601YCbCrColor (wrapped in BT601YCbCr or BT601NYCbCrA) for VP8 sources, so calling img.At(x, y).RGBA() produces the colors a real VP8 decoder (libwebp, browsers) would show.

func (BT601YCbCrColor) RGBA

func (c BT601YCbCrColor) RGBA() (r, g, b, a uint32)

RGBA implements color.Color using the VP8 spec's limited-range BT.601 inverse. Returns premultiplied 16-bit components (α is always 0xffff for this type).

type Options

type Options struct {
	UseExtendedFormat bool
	Lossy             bool
	Quality           float32
	Method            int
	ICCProfile        []byte
	EXIF              []byte
	XMP               []byte
	NearLossless      int
}

Options holds configuration settings for WebP encoding.

Fields:

  • UseExtendedFormat: If true, wraps the VP8L frame inside a VP8X container to enable metadata support. This does not affect image compression or encoding itself, as VP8L remains the encoding format. Setting any of ICCProfile/EXIF/XMP also forces the VP8X container automatically.
  • Lossy: If true, encode as VP8 (lossy). Otherwise (default) encode as VP8L (lossless), preserving existing behavior byte-for-byte.
  • Quality: Lossy quality in [0, 100]. Higher values preserve more detail. Default (when zero) is 75. Ignored when Lossy is false.
  • Method: For lossy, the speed/quality tradeoff in [0, 6]; higher values spend more time searching for better compression (default 4). For lossless, Method doubles as an effort knob: 0 keeps the fast default output, while any positive value searches for the smallest predictor tile size and color-cache size (slower, smaller; the decoded pixels are unchanged either way).
  • ICCProfile: Raw ICC color-profile bytes. When non-empty they are emitted as an ICCP chunk (before the image data) inside a VP8X container. Empty means no profile is written.
  • EXIF: Raw EXIF metadata bytes, emitted as an EXIF chunk after the image data. Empty means no EXIF chunk is written.
  • XMP: Raw XMP metadata bytes, emitted as an "XMP " chunk after the image data. Empty means no XMP chunk is written.
  • NearLossless: Lossless-path only. 0 (default) is exact lossless. A positive value is the maximum number of low bits the encoder may drop from R/G/B in smooth regions (alpha and edges are kept exact), trading a small, bounded per-channel error for smaller files. Sensible range is 1..5; larger values lose more. Ignored when Lossy is true.

Directories

Path Synopsis
cmd
gowebp command
Command gowebp converts images to WebP from the shell.
Command gowebp converts images to WebP from the shell.
internal
vp8enc
Package vp8enc implements a VP8 (lossy) keyframe encoder for WebP.
Package vp8enc implements a VP8 (lossy) keyframe encoder for WebP.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL