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}