fast-webp 0.1.1

Fast WebP encoder for Rust image buffers and proxy image payloads.
Documentation
use std::mem::MaybeUninit;

use libwebp_sys::{
    WebPConfig, WebPConfigInitInternal, WebPConfigLosslessPreset, WebPEncode, WebPMemoryWrite,
    WebPMemoryWriter, WebPMemoryWriterClear, WebPMemoryWriterInit, WebPPicture, WebPPictureFree,
    WebPPictureImportRGB, WebPPictureImportRGBA, WebPPictureInitInternal, WebPPreset,
    WebPValidateConfig, WEBP_ENCODER_ABI_VERSION, WEBP_MAX_DIMENSION,
};

use crate::error::WebpError;
use crate::options::WebpOptions;
use crate::pixel::PixelLayout;

const WEBP_METHOD_FASTEST: i32 = 0;

pub fn encode_rgb(
    rgb: &[u8],
    width: u32,
    height: u32,
    options: WebpOptions,
) -> Result<Vec<u8>, WebpError> {
    let geometry = checked_geometry(rgb.len(), width, height, PixelLayout::Rgb, options)?;
    encode_with_libwebp(rgb, geometry, PixelLayout::Rgb, options)
}

pub fn encode_rgba(
    rgba: &[u8],
    width: u32,
    height: u32,
    options: WebpOptions,
) -> Result<Vec<u8>, WebpError> {
    let geometry = checked_geometry(rgba.len(), width, height, PixelLayout::Rgba, options)?;
    encode_with_libwebp(rgba, geometry, PixelLayout::Rgba, options)
}

struct Geometry {
    width: i32,
    height: i32,
    stride: i32,
}

fn checked_geometry(
    actual_len: usize,
    width: u32,
    height: u32,
    layout: PixelLayout,
    options: WebpOptions,
) -> Result<Geometry, WebpError> {
    if !options.quality.is_finite() || !(0.0..=100.0).contains(&options.quality) {
        return Err(WebpError::InvalidQuality(options.quality));
    }

    if width == 0 || height == 0 || width > WEBP_MAX_DIMENSION || height > WEBP_MAX_DIMENSION {
        return Err(WebpError::InvalidDimensions { width, height });
    }

    let channels = layout.channels();
    let expected = usize::try_from(width)
        .ok()
        .and_then(|w| usize::try_from(height).ok().and_then(|h| w.checked_mul(h)))
        .and_then(|pixels| pixels.checked_mul(channels))
        .ok_or(WebpError::InvalidDimensions { width, height })?;

    if actual_len != expected {
        return Err(WebpError::InvalidBufferLength {
            expected,
            actual: actual_len,
        });
    }

    let width_i32 =
        i32::try_from(width).map_err(|_| WebpError::InvalidDimensions { width, height })?;
    let height_i32 =
        i32::try_from(height).map_err(|_| WebpError::InvalidDimensions { width, height })?;
    let stride = width_i32
        .checked_mul(channels as i32)
        .ok_or(WebpError::InvalidDimensions { width, height })?;

    Ok(Geometry {
        width: width_i32,
        height: height_i32,
        stride,
    })
}

fn encode_with_libwebp(
    pixels: &[u8],
    geometry: Geometry,
    layout: PixelLayout,
    options: WebpOptions,
) -> Result<Vec<u8>, WebpError> {
    let config = create_fast_config(options)?;

    unsafe {
        let mut writer = MemoryWriter::new();
        let mut picture = Picture::new(geometry.width, geometry.height)?;
        picture.as_mut().writer = Some(WebPMemoryWrite);
        picture.as_mut().custom_ptr = writer.as_mut_ptr().cast();

        let imported = match layout {
            PixelLayout::Rgb => {
                WebPPictureImportRGB(picture.as_mut(), pixels.as_ptr(), geometry.stride) != 0
            }
            PixelLayout::Rgba => {
                WebPPictureImportRGBA(picture.as_mut(), pixels.as_ptr(), geometry.stride) != 0
            }
        };
        if !imported {
            return Err(WebpError::EncodeWebp);
        }

        if WebPEncode(&config, picture.as_mut()) == 0 {
            return Err(WebpError::EncodeWebp);
        }

        writer.to_vec()
    }
}

fn create_fast_config(options: WebpOptions) -> Result<WebPConfig, WebpError> {
    let mut config = MaybeUninit::<WebPConfig>::uninit();
    let ok = unsafe {
        WebPConfigInitInternal(
            config.as_mut_ptr(),
            WebPPreset::WEBP_PRESET_DEFAULT,
            options.quality,
            WEBP_ENCODER_ABI_VERSION as i32,
        )
    } != 0;
    if !ok {
        return Err(WebpError::EncodeWebp);
    }

    let mut config = unsafe { config.assume_init() };
    config.lossless = i32::from(options.lossless);
    config.quality = options.quality;
    config.method = WEBP_METHOD_FASTEST;
    config.pass = 1;
    config.thread_level = 1;
    config.alpha_quality = 90;

    if options.lossless {
        unsafe {
            WebPConfigLosslessPreset(&mut config, WEBP_METHOD_FASTEST);
        }
        config.method = WEBP_METHOD_FASTEST;
        config.thread_level = 1;
        config.pass = 1;
    }

    if unsafe { WebPValidateConfig(&config) } == 0 {
        return Err(WebpError::EncodeWebp);
    }

    Ok(config)
}

struct Picture {
    picture: WebPPicture,
}

impl Picture {
    fn new(width: i32, height: i32) -> Result<Self, WebpError> {
        let mut picture = MaybeUninit::<WebPPicture>::uninit();
        let ok = unsafe {
            WebPPictureInitInternal(picture.as_mut_ptr(), WEBP_ENCODER_ABI_VERSION as i32)
        } != 0;
        if !ok {
            return Err(WebpError::EncodeWebp);
        }

        let mut picture = unsafe { picture.assume_init() };
        picture.width = width;
        picture.height = height;
        Ok(Self { picture })
    }

    fn as_mut(&mut self) -> &mut WebPPicture {
        &mut self.picture
    }
}

impl Drop for Picture {
    fn drop(&mut self) {
        unsafe {
            WebPPictureFree(&mut self.picture);
        }
    }
}

struct MemoryWriter {
    writer: WebPMemoryWriter,
}

impl MemoryWriter {
    unsafe fn new() -> Self {
        let mut writer = MaybeUninit::<WebPMemoryWriter>::uninit();
        WebPMemoryWriterInit(writer.as_mut_ptr());
        Self {
            writer: writer.assume_init(),
        }
    }

    fn as_mut_ptr(&mut self) -> *mut WebPMemoryWriter {
        &mut self.writer
    }

    unsafe fn to_vec(&self) -> Result<Vec<u8>, WebpError> {
        if self.writer.mem.is_null() || self.writer.size == 0 {
            return Err(WebpError::EncodeWebp);
        }

        Ok(std::slice::from_raw_parts(self.writer.mem, self.writer.size).to_vec())
    }
}

impl Drop for MemoryWriter {
    fn drop(&mut self) {
        unsafe {
            WebPMemoryWriterClear(&mut self.writer);
        }
    }
}