Skip to main content

fast_webp/
encode.rs

1use std::mem::MaybeUninit;
2
3use libwebp_sys::{
4    WebPConfig, WebPConfigInitInternal, WebPConfigLosslessPreset, WebPEncode, WebPMemoryWrite,
5    WebPMemoryWriter, WebPMemoryWriterClear, WebPMemoryWriterInit, WebPPicture, WebPPictureFree,
6    WebPPictureImportRGB, WebPPictureImportRGBA, WebPPictureInitInternal, WebPPreset,
7    WebPValidateConfig, WEBP_ENCODER_ABI_VERSION, WEBP_MAX_DIMENSION,
8};
9
10use crate::error::WebpError;
11use crate::options::WebpOptions;
12use crate::pixel::PixelLayout;
13
14const WEBP_METHOD_FASTEST: i32 = 0;
15
16pub fn encode_rgb(
17    rgb: &[u8],
18    width: u32,
19    height: u32,
20    options: WebpOptions,
21) -> Result<Vec<u8>, WebpError> {
22    let geometry = checked_geometry(rgb.len(), width, height, PixelLayout::Rgb, options)?;
23    encode_with_libwebp(rgb, geometry, PixelLayout::Rgb, options)
24}
25
26pub fn encode_rgba(
27    rgba: &[u8],
28    width: u32,
29    height: u32,
30    options: WebpOptions,
31) -> Result<Vec<u8>, WebpError> {
32    let geometry = checked_geometry(rgba.len(), width, height, PixelLayout::Rgba, options)?;
33    encode_with_libwebp(rgba, geometry, PixelLayout::Rgba, options)
34}
35
36struct Geometry {
37    width: i32,
38    height: i32,
39    stride: i32,
40}
41
42fn checked_geometry(
43    actual_len: usize,
44    width: u32,
45    height: u32,
46    layout: PixelLayout,
47    options: WebpOptions,
48) -> Result<Geometry, WebpError> {
49    if !options.quality.is_finite() || !(0.0..=100.0).contains(&options.quality) {
50        return Err(WebpError::InvalidQuality(options.quality));
51    }
52
53    if width == 0 || height == 0 || width > WEBP_MAX_DIMENSION || height > WEBP_MAX_DIMENSION {
54        return Err(WebpError::InvalidDimensions { width, height });
55    }
56
57    let channels = layout.channels();
58    let expected = usize::try_from(width)
59        .ok()
60        .and_then(|w| usize::try_from(height).ok().and_then(|h| w.checked_mul(h)))
61        .and_then(|pixels| pixels.checked_mul(channels))
62        .ok_or(WebpError::InvalidDimensions { width, height })?;
63
64    if actual_len != expected {
65        return Err(WebpError::InvalidBufferLength {
66            expected,
67            actual: actual_len,
68        });
69    }
70
71    let width_i32 =
72        i32::try_from(width).map_err(|_| WebpError::InvalidDimensions { width, height })?;
73    let height_i32 =
74        i32::try_from(height).map_err(|_| WebpError::InvalidDimensions { width, height })?;
75    let stride = width_i32
76        .checked_mul(channels as i32)
77        .ok_or(WebpError::InvalidDimensions { width, height })?;
78
79    Ok(Geometry {
80        width: width_i32,
81        height: height_i32,
82        stride,
83    })
84}
85
86fn encode_with_libwebp(
87    pixels: &[u8],
88    geometry: Geometry,
89    layout: PixelLayout,
90    options: WebpOptions,
91) -> Result<Vec<u8>, WebpError> {
92    let config = create_fast_config(options)?;
93
94    unsafe {
95        let mut writer = MemoryWriter::new();
96        let mut picture = Picture::new(geometry.width, geometry.height)?;
97        picture.as_mut().writer = Some(WebPMemoryWrite);
98        picture.as_mut().custom_ptr = writer.as_mut_ptr().cast();
99
100        let imported = match layout {
101            PixelLayout::Rgb => {
102                WebPPictureImportRGB(picture.as_mut(), pixels.as_ptr(), geometry.stride) != 0
103            }
104            PixelLayout::Rgba => {
105                WebPPictureImportRGBA(picture.as_mut(), pixels.as_ptr(), geometry.stride) != 0
106            }
107        };
108        if !imported {
109            return Err(WebpError::EncodeWebp);
110        }
111
112        if WebPEncode(&config, picture.as_mut()) == 0 {
113            return Err(WebpError::EncodeWebp);
114        }
115
116        writer.to_vec()
117    }
118}
119
120fn create_fast_config(options: WebpOptions) -> Result<WebPConfig, WebpError> {
121    let mut config = MaybeUninit::<WebPConfig>::uninit();
122    let ok = unsafe {
123        WebPConfigInitInternal(
124            config.as_mut_ptr(),
125            WebPPreset::WEBP_PRESET_DEFAULT,
126            options.quality,
127            WEBP_ENCODER_ABI_VERSION as i32,
128        )
129    } != 0;
130    if !ok {
131        return Err(WebpError::EncodeWebp);
132    }
133
134    let mut config = unsafe { config.assume_init() };
135    config.lossless = i32::from(options.lossless);
136    config.quality = options.quality;
137    config.method = WEBP_METHOD_FASTEST;
138    config.pass = 1;
139    config.thread_level = 1;
140    config.alpha_quality = 90;
141
142    if options.lossless {
143        unsafe {
144            WebPConfigLosslessPreset(&mut config, WEBP_METHOD_FASTEST);
145        }
146        config.method = WEBP_METHOD_FASTEST;
147        config.thread_level = 1;
148        config.pass = 1;
149    }
150
151    if unsafe { WebPValidateConfig(&config) } == 0 {
152        return Err(WebpError::EncodeWebp);
153    }
154
155    Ok(config)
156}
157
158struct Picture {
159    picture: WebPPicture,
160}
161
162impl Picture {
163    fn new(width: i32, height: i32) -> Result<Self, WebpError> {
164        let mut picture = MaybeUninit::<WebPPicture>::uninit();
165        let ok = unsafe {
166            WebPPictureInitInternal(picture.as_mut_ptr(), WEBP_ENCODER_ABI_VERSION as i32)
167        } != 0;
168        if !ok {
169            return Err(WebpError::EncodeWebp);
170        }
171
172        let mut picture = unsafe { picture.assume_init() };
173        picture.width = width;
174        picture.height = height;
175        Ok(Self { picture })
176    }
177
178    fn as_mut(&mut self) -> &mut WebPPicture {
179        &mut self.picture
180    }
181}
182
183impl Drop for Picture {
184    fn drop(&mut self) {
185        unsafe {
186            WebPPictureFree(&mut self.picture);
187        }
188    }
189}
190
191struct MemoryWriter {
192    writer: WebPMemoryWriter,
193}
194
195impl MemoryWriter {
196    unsafe fn new() -> Self {
197        let mut writer = MaybeUninit::<WebPMemoryWriter>::uninit();
198        WebPMemoryWriterInit(writer.as_mut_ptr());
199        Self {
200            writer: writer.assume_init(),
201        }
202    }
203
204    fn as_mut_ptr(&mut self) -> *mut WebPMemoryWriter {
205        &mut self.writer
206    }
207
208    unsafe fn to_vec(&self) -> Result<Vec<u8>, WebpError> {
209        if self.writer.mem.is_null() || self.writer.size == 0 {
210            return Err(WebpError::EncodeWebp);
211        }
212
213        Ok(std::slice::from_raw_parts(self.writer.mem, self.writer.size).to_vec())
214    }
215}
216
217impl Drop for MemoryWriter {
218    fn drop(&mut self) {
219        unsafe {
220            WebPMemoryWriterClear(&mut self.writer);
221        }
222    }
223}