fast-webp 0.1.1

Fast WebP encoder for Rust image buffers and proxy image payloads.
Documentation
# fast-webp

`fast-webp` is a small WebP encoder crate for Rust applications that need the
ergonomics of the `webp` crate and a faster hot path.

It is intentionally narrow:

- `RgbImage`, `RgbaImage`, `DynamicImage` -> WebP bytes
- RGB/RGBA byte buffers -> WebP bytes
- optional JPEG/PNG response bytes -> WebP bytes via `image` codecs

It does not try to be an image processing framework. Resize, crop, filters,
metadata editing, animation, and WebP decoding are out of scope for the first
version.

## Why this exists

The upstream [`webp`](https://github.com/jaredforth/webp) crate has a convenient
`Encoder::from_image(&image).encode(quality)` API. Internally it is organized as
a small wrapper around `libwebp-sys`: `src/lib.rs` re-exports modules,
`src/encoder.rs` builds an encoder/picture pipeline, and `src/shared.rs` owns the
WebP memory wrappers.

That shape is good for a general wrapper, but for reverse proxies and HTTP image
conversion we want a shorter path:

```text
DynamicImage/RGB/RGBA/JPEG/PNG -> RGB/RGBA bytes -> WebPEncodeRGB/RGBA -> Vec<u8>
```

So `fast-webp` keeps the public API simple and pushes the encoding hot path
through `libwebp-sys` with a fast encoder configuration:

- `WebPConfig` with `method = 0`
- `pass = 1`
- `thread_level = 1`
- `WebPPictureImportRGB`
- `WebPPictureImportRGBA`
- `WebPEncode`
- `WebPFree`

## Install

```toml
[dependencies]
fast-webp = "0.1"
```

Runtime dependencies are deliberately close to `webp`:

```toml
[dependencies]
libwebp-sys = "0.9.3"
image = { version = "^0.25.0", default-features = false, optional = true }
```

Minimal encoder-only build:

```toml
[dependencies]
fast-webp = { version = "0.1", default-features = false }
```

Only `image` buffer integration:

```toml
[dependencies]
fast-webp = { version = "0.1", default-features = false, features = ["image"] }
```

JPEG/PNG byte conversion through `image` codecs:

```toml
[dependencies]
fast-webp = { version = "0.1", features = ["image-codecs"] }
```

If your proxy already decodes PNG/JPEG with another fast decoder, prefer feeding
the resulting RGB/RGBA buffer into `encode_rgb` or `encode_rgba`. That is the
fast path this crate optimizes.

## Public API

### Raw Buffers

Use this when you already have tightly packed RGB/RGBA pixels.

```rust
use fast_webp::{encode_rgb, encode_rgba, WebpOptions};

let rgb = vec![255, 0, 0, 0, 255, 0, 0, 0, 255, 255, 255, 255];
let webp = encode_rgb(&rgb, 2, 2, WebpOptions::default())?;

let rgba = vec![
    255, 0, 0, 255,
    0, 255, 0, 128,
    0, 0, 255, 255,
    255, 255, 255, 0,
];
let webp_with_alpha = encode_rgba(&rgba, 2, 2, WebpOptions::default())?;
```

The encoder validates:

- quality is finite and in `0.0..=100.0`
- width and height are non-zero
- dimensions are within `WEBP_MAX_DIMENSION`
- `width * height * channels` does not overflow
- input length exactly matches the expected packed buffer length

### image crate

Enable the `image` feature. It is enabled by default.

```rust
use fast_webp::{encode_dynamic_image, WebpOptions};

let image = image::open("input.jpg")?;
let webp = encode_dynamic_image(
    &image,
    WebpOptions {
        quality: 75.0,
        lossless: false,
    },
)?;

std::fs::write("output.webp", webp)?;
```

Direct `RgbImage` / `RgbaImage` entry points avoid unnecessary conversion:

```rust
use fast_webp::{encode_rgb_image, encode_rgba_image, WebpOptions};

let rgb = image::open("photo.jpg")?.to_rgb8();
let webp = encode_rgb_image(&rgb, WebpOptions::default())?;

let rgba = image::open("icon.png")?.to_rgba8();
let webp = encode_rgba_image(&rgba, WebpOptions::default())?;
```

`DynamicImage` handling is deliberately conservative:

- `ImageRgb8` -> RGB encode
- `ImageRgba8` -> RGBA encode
- grayscale -> RGB
- grayscale + alpha -> RGBA
- higher-depth RGB -> `to_rgb8`
- everything else -> `to_rgba8`

### JPEG/PNG bytes

Enable `image-codecs` for this convenience API. This keeps the default crate
small while still allowing a single-call proxy workflow when you want it.

```rust
use fast_webp::{convert_bytes_to_webp, WebpOptions};

let webp = convert_bytes_to_webp(
    &response_body,
    WebpOptions {
        quality: 75.0,
        lossless: false,
    },
)?;
```

Format detection is magic-byte based:

```rust
use fast_webp::{detect_format, InputFormat};

assert_eq!(detect_format(b"\xff\xd8\xff..."), Some(InputFormat::Jpeg));
```

Decoder path:

- JPEG -> `image` JPEG decoder -> `DynamicImage` -> RGB/RGBA encode
- PNG -> `image` PNG decoder -> `DynamicImage` -> RGB/RGBA encode

The 800 ms target for a 4 MB PNG depends mostly on PNG decode time and CPU. The
part controlled by this crate is the WebP encode path after decode: it avoids
the heavier `WebPPicture` setup used by generic wrappers and calls the simple
`WebPEncode*` functions directly. For the fastest proxy path, decode with the
fastest decoder available in your application and call `encode_rgb`/`encode_rgba`.
- PNG grayscale -> RGB
- PNG grayscale + alpha -> RGBA

## Options

```rust
use fast_webp::WebpOptions;

let lossy = WebpOptions {
    quality: 82.0,
    lossless: false,
};

let lossless = WebpOptions {
    quality: 100.0,
    lossless: true,
};
```

For lossless mode, `quality` is still validated for API consistency, but the
simple libwebp lossless functions do not use it.

## File Layout

```text
src/lib.rs          public facade and crate docs
src/options.rs      WebpOptions
src/error.rs        WebpError
src/encode.rs       hot path: validation + fast WebPConfig + libwebp-sys FFI
src/bytes.rs        detect_format + optional image-codecs bytes API
src/image_api.rs    image crate integration
src/pixel.rs        internal pixel layout and grayscale helpers
src/tests.rs        unit tests for public behavior
benches/convert.rs  Criterion comparison with webp crate
examples/           runnable API examples
```

## Examples

```sh
cargo run --example dynamic_image -- input.jpg output.webp
cargo run --features image-codecs --example proxy_bytes -- input.jpg output.webp
cargo run --example raw_buffers -- output.webp
```

## Benchmarks

```sh
cargo bench --features image-codecs
```

Current benchmark groups:

- `webp` crate `DynamicImage -> WebP`
- `fast-webp` `DynamicImage -> WebP`
- `fast-webp` JPEG bytes -> WebP
- `fast-webp` PNG bytes -> WebP

The benchmark generates images in memory so it is reproducible in CI. For proxy
tuning, add your production JPEG/PNG corpus and run the same groups against those
files.

## Release

```sh
cargo fmt --all
cargo clippy --all-targets --all-features -- -D warnings
cargo test --all-features
cargo package --allow-dirty
cargo publish --dry-run
```

Tag the release from the clean commit:

```sh
git tag -a v0.1.0 -m "fast-webp 0.1.0"
git push origin main --tags
```

After crates.io accepts the package:

```sh
cargo publish
```