1use alloc::vec::Vec;
4#[cfg(any(feature = "encode", feature = "decode"))]
5use rgb::alt::{BGR8, BGRA8};
6#[cfg(any(feature = "encode", feature = "decode"))]
7use rgb::{RGB8, RGBA8};
8use whereat::*;
9
10#[cfg(any(feature = "encode", feature = "decode"))]
12#[doc(hidden)]
13#[derive(Debug, Clone, Copy, PartialEq, Eq)]
14pub enum PixelLayout {
15 Rgba,
17 Bgra,
19 Rgb,
21 Bgr,
23}
24
25#[cfg(any(feature = "encode", feature = "decode"))]
26impl PixelLayout {
27 #[must_use]
29 pub const fn bytes_per_pixel(self) -> usize {
30 match self {
31 PixelLayout::Rgba | PixelLayout::Bgra => 4,
32 PixelLayout::Rgb | PixelLayout::Bgr => 3,
33 }
34 }
35
36 #[must_use]
38 pub const fn has_alpha(self) -> bool {
39 matches!(self, PixelLayout::Rgba | PixelLayout::Bgra)
40 }
41}
42
43#[cfg(feature = "encode")]
49#[doc(hidden)]
50pub trait EncodePixel: Copy + 'static + private::Sealed {
51 const LAYOUT: PixelLayout;
53}
54
55#[cfg(feature = "encode")]
56impl EncodePixel for RGBA8 {
57 const LAYOUT: PixelLayout = PixelLayout::Rgba;
58}
59
60#[cfg(feature = "encode")]
61impl EncodePixel for BGRA8 {
62 const LAYOUT: PixelLayout = PixelLayout::Bgra;
63}
64
65#[cfg(feature = "encode")]
66impl EncodePixel for RGB8 {
67 const LAYOUT: PixelLayout = PixelLayout::Rgb;
68}
69
70#[cfg(feature = "encode")]
71impl EncodePixel for BGR8 {
72 const LAYOUT: PixelLayout = PixelLayout::Bgr;
73}
74
75#[cfg(feature = "decode")]
81#[doc(hidden)]
82pub trait DecodePixel: Copy + 'static + private::Sealed {
83 const LAYOUT: PixelLayout;
85
86 #[doc(hidden)]
91 fn decode_new(data: &[u8]) -> Option<(*mut u8, i32, i32)>;
92
93 #[doc(hidden)]
99 unsafe fn decode_into(data: &[u8], output: *mut u8, output_len: usize, stride: i32) -> bool;
100}
101
102#[cfg(feature = "decode")]
103impl DecodePixel for RGBA8 {
104 const LAYOUT: PixelLayout = PixelLayout::Rgba;
105
106 fn decode_new(data: &[u8]) -> Option<(*mut u8, i32, i32)> {
107 let mut width: i32 = 0;
108 let mut height: i32 = 0;
109 let ptr = unsafe {
110 libwebp_sys::WebPDecodeRGBA(data.as_ptr(), data.len(), &mut width, &mut height)
111 };
112 if ptr.is_null() {
113 None
114 } else {
115 Some((ptr, width, height))
116 }
117 }
118
119 unsafe fn decode_into(data: &[u8], output: *mut u8, output_len: usize, stride: i32) -> bool {
120 let result = unsafe {
122 libwebp_sys::WebPDecodeRGBAInto(data.as_ptr(), data.len(), output, output_len, stride)
123 };
124 !result.is_null()
125 }
126}
127
128#[cfg(feature = "decode")]
129impl DecodePixel for BGRA8 {
130 const LAYOUT: PixelLayout = PixelLayout::Bgra;
131
132 fn decode_new(data: &[u8]) -> Option<(*mut u8, i32, i32)> {
133 let mut width: i32 = 0;
134 let mut height: i32 = 0;
135 let ptr = unsafe {
136 libwebp_sys::WebPDecodeBGRA(data.as_ptr(), data.len(), &mut width, &mut height)
137 };
138 if ptr.is_null() {
139 None
140 } else {
141 Some((ptr, width, height))
142 }
143 }
144
145 unsafe fn decode_into(data: &[u8], output: *mut u8, output_len: usize, stride: i32) -> bool {
146 let result = unsafe {
148 libwebp_sys::WebPDecodeBGRAInto(data.as_ptr(), data.len(), output, output_len, stride)
149 };
150 !result.is_null()
151 }
152}
153
154#[cfg(feature = "decode")]
155impl DecodePixel for RGB8 {
156 const LAYOUT: PixelLayout = PixelLayout::Rgb;
157
158 fn decode_new(data: &[u8]) -> Option<(*mut u8, i32, i32)> {
159 let mut width: i32 = 0;
160 let mut height: i32 = 0;
161 let ptr = unsafe {
162 libwebp_sys::WebPDecodeRGB(data.as_ptr(), data.len(), &mut width, &mut height)
163 };
164 if ptr.is_null() {
165 None
166 } else {
167 Some((ptr, width, height))
168 }
169 }
170
171 unsafe fn decode_into(data: &[u8], output: *mut u8, output_len: usize, stride: i32) -> bool {
172 let result = unsafe {
174 libwebp_sys::WebPDecodeRGBInto(data.as_ptr(), data.len(), output, output_len, stride)
175 };
176 !result.is_null()
177 }
178}
179
180#[cfg(feature = "decode")]
181impl DecodePixel for BGR8 {
182 const LAYOUT: PixelLayout = PixelLayout::Bgr;
183
184 fn decode_new(data: &[u8]) -> Option<(*mut u8, i32, i32)> {
185 let mut width: i32 = 0;
186 let mut height: i32 = 0;
187 let ptr = unsafe {
188 libwebp_sys::WebPDecodeBGR(data.as_ptr(), data.len(), &mut width, &mut height)
189 };
190 if ptr.is_null() {
191 None
192 } else {
193 Some((ptr, width, height))
194 }
195 }
196
197 unsafe fn decode_into(data: &[u8], output: *mut u8, output_len: usize, stride: i32) -> bool {
198 let result = unsafe {
200 libwebp_sys::WebPDecodeBGRInto(data.as_ptr(), data.len(), output, output_len, stride)
201 };
202 !result.is_null()
203 }
204}
205
206#[cfg(any(feature = "encode", feature = "decode"))]
207mod private {
208 use super::*;
209
210 pub trait Sealed {}
211 impl Sealed for RGBA8 {}
212 impl Sealed for BGRA8 {}
213 impl Sealed for RGB8 {}
214 impl Sealed for BGR8 {}
215}
216
217#[derive(Debug, Clone, PartialEq, Eq)]
219#[non_exhaustive]
220pub struct ImageInfo {
221 pub width: u32,
223 pub height: u32,
225 pub has_alpha: bool,
227 pub has_animation: bool,
229 pub frame_count: u32,
231 pub format: BitstreamFormat,
233}
234
235impl ImageInfo {
236 pub fn from_webp(data: &[u8]) -> crate::Result<Self> {
238 let mut width: i32 = 0;
239 let mut height: i32 = 0;
240
241 let result =
242 unsafe { libwebp_sys::WebPGetInfo(data.as_ptr(), data.len(), &mut width, &mut height) };
243
244 if result == 0 {
245 return Err(at!(crate::Error::InvalidWebP));
246 }
247
248 let mut features = core::mem::MaybeUninit::<libwebp_sys::WebPBitstreamFeatures>::zeroed();
253 let status = unsafe {
254 libwebp_sys::WebPGetFeatures(data.as_ptr(), data.len(), features.as_mut_ptr())
255 };
256 if status != libwebp_sys::VP8StatusCode::VP8_STATUS_OK {
257 return Err(at!(crate::Error::DecodeFailed(
258 crate::error::DecodingError::from(status as i32),
259 )));
260 }
261 let features = unsafe { features.assume_init() };
262
263 let format = match features.format {
264 0 => BitstreamFormat::Undefined,
265 1 => BitstreamFormat::Lossy,
266 2 => BitstreamFormat::Lossless,
267 _ => BitstreamFormat::Undefined,
268 };
269
270 Ok(ImageInfo {
271 width: width as u32,
272 height: height as u32,
273 has_alpha: features.has_alpha != 0,
274 has_animation: features.has_animation != 0,
275 frame_count: if features.has_animation != 0 { 0 } else { 1 }, format,
277 })
278 }
279}
280
281#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
283#[non_exhaustive]
284pub enum BitstreamFormat {
285 #[default]
287 Undefined,
288 Lossy,
290 Lossless,
292}
293
294#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
296#[non_exhaustive]
297pub enum ColorMode {
298 #[default]
300 Rgba,
301 Bgra,
303 Argb,
305 Rgb,
307 Bgr,
309 Yuv420,
311 Yuva420,
313}
314
315impl ColorMode {
316 pub fn bytes_per_pixel(self) -> Option<usize> {
318 match self {
319 ColorMode::Rgba | ColorMode::Bgra | ColorMode::Argb => Some(4),
320 ColorMode::Rgb | ColorMode::Bgr => Some(3),
321 ColorMode::Yuv420 | ColorMode::Yuva420 => None, }
323 }
324
325 pub fn has_alpha(self) -> bool {
327 matches!(
328 self,
329 ColorMode::Rgba | ColorMode::Bgra | ColorMode::Argb | ColorMode::Yuva420
330 )
331 }
332
333 pub fn is_yuv(self) -> bool {
335 matches!(self, ColorMode::Yuv420 | ColorMode::Yuva420)
336 }
337}
338
339#[derive(Debug, Clone)]
341pub struct YuvPlanes {
342 pub y: Vec<u8>,
344 pub y_stride: usize,
346 pub u: Vec<u8>,
348 pub u_stride: usize,
350 pub v: Vec<u8>,
352 pub v_stride: usize,
354 pub a: Option<Vec<u8>>,
356 pub a_stride: usize,
358 pub width: u32,
360 pub height: u32,
362}
363
364impl YuvPlanes {
365 #[must_use]
374 pub fn new_checked(width: u32, height: u32, with_alpha: bool) -> Option<Self> {
375 const MAX_DIMENSION: u32 = 16383;
377 if width == 0 || height == 0 || width > MAX_DIMENSION || height > MAX_DIMENSION {
378 return None;
379 }
380
381 let y_stride = width as usize;
382 let uv_stride = (width as usize).div_ceil(2);
383 let uv_height = (height as usize).div_ceil(2);
384
385 let y_size = y_stride.saturating_mul(height as usize);
388 let uv_size = uv_stride.saturating_mul(uv_height);
389
390 Some(Self {
391 y: alloc::vec![0u8; y_size],
392 y_stride,
393 u: alloc::vec![0u8; uv_size],
394 u_stride: uv_stride,
395 v: alloc::vec![0u8; uv_size],
396 v_stride: uv_stride,
397 a: if with_alpha {
398 Some(alloc::vec![0u8; y_size])
399 } else {
400 None
401 },
402 a_stride: y_stride,
403 width,
404 height,
405 })
406 }
407
408 pub fn new(width: u32, height: u32, with_alpha: bool) -> Self {
417 Self::new_checked(width, height, with_alpha)
418 .expect("YuvPlanes::new: dimensions out of range (1..=16383)")
419 }
420
421 pub fn uv_dimensions(&self) -> (u32, u32) {
423 (self.width.div_ceil(2), self.height.div_ceil(2))
424 }
425}
426
427#[derive(Debug, Clone, Copy)]
429pub struct YuvPlanesRef<'a> {
430 pub y: &'a [u8],
432 pub y_stride: usize,
434 pub u: &'a [u8],
436 pub u_stride: usize,
438 pub v: &'a [u8],
440 pub v_stride: usize,
442 pub a: Option<&'a [u8]>,
444 pub a_stride: usize,
446 pub width: u32,
448 pub height: u32,
450}
451
452impl<'a> From<&'a YuvPlanes> for YuvPlanesRef<'a> {
453 fn from(planes: &'a YuvPlanes) -> Self {
454 Self {
455 y: &planes.y,
456 y_stride: planes.y_stride,
457 u: &planes.u,
458 u_stride: planes.u_stride,
459 v: &planes.v,
460 v_stride: planes.v_stride,
461 a: planes.a.as_deref(),
462 a_stride: planes.a_stride,
463 width: planes.width,
464 height: planes.height,
465 }
466 }
467}
468
469pub struct WebPData {
496 ptr: *mut u8,
497 len: usize,
498}
499
500unsafe impl Send for WebPData {}
502unsafe impl Sync for WebPData {}
503
504impl WebPData {
505 #[cfg(feature = "encode")]
513 #[must_use]
514 pub(crate) unsafe fn from_raw(ptr: *mut u8, len: usize) -> Self {
515 Self { ptr, len }
516 }
517
518 #[must_use]
520 pub fn len(&self) -> usize {
521 self.len
522 }
523
524 #[must_use]
526 pub fn is_empty(&self) -> bool {
527 self.len == 0
528 }
529
530 #[must_use]
532 pub fn as_slice(&self) -> &[u8] {
533 if self.ptr.is_null() || self.len == 0 {
534 &[]
535 } else {
536 unsafe { core::slice::from_raw_parts(self.ptr, self.len) }
538 }
539 }
540}
541
542impl Drop for WebPData {
543 fn drop(&mut self) {
544 if !self.ptr.is_null() {
545 unsafe {
547 libwebp_sys::WebPFree(self.ptr as *mut core::ffi::c_void);
548 }
549 }
550 }
551}
552
553impl core::ops::Deref for WebPData {
554 type Target = [u8];
555
556 fn deref(&self) -> &Self::Target {
557 self.as_slice()
558 }
559}
560
561impl AsRef<[u8]> for WebPData {
562 fn as_ref(&self) -> &[u8] {
563 self.as_slice()
564 }
565}
566
567impl From<WebPData> for Vec<u8> {
568 fn from(data: WebPData) -> Self {
569 data.as_slice().to_vec()
570 }
571}
572
573impl core::fmt::Debug for WebPData {
574 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
575 f.debug_struct("WebPData")
576 .field("len", &self.len)
577 .finish_non_exhaustive()
578 }
579}