webp_screenshot_rust/encoder/
webp.rs1use crate::{
4 encoder::{EncoderStats, ImageEncoder},
5 error::{EncodingError, EncodingResult},
6 types::{PixelFormat, RawImage, WebPConfig},
7};
8
9use std::time::Instant;
10
11pub use crate::types::WebPConfig as EncoderOptions;
13
14pub struct WebPEncoder {
16 stats: EncoderStats,
17}
18
19impl Default for WebPEncoder {
20 fn default() -> Self {
21 Self::new()
22 }
23}
24
25impl WebPEncoder {
26 pub fn new() -> Self {
28 Self {
29 stats: EncoderStats::default(),
30 }
31 }
32
33 pub fn stats(&self) -> &EncoderStats {
35 &self.stats
36 }
37
38 pub fn encode(&mut self, image: &RawImage, config: &WebPConfig) -> EncodingResult<Vec<u8>> {
40 let start_time = Instant::now();
41
42 config.validate()
44 .map_err(|e| EncodingError::InvalidConfiguration(e))?;
45
46 if image.width == 0 || image.height == 0 {
48 return Err(EncodingError::InvalidDimensions {
49 width: image.width,
50 height: image.height,
51 });
52 }
53
54 let result = self.encode_with_format(image, config)?;
56
57 let encoding_time_ms = start_time.elapsed().as_secs_f64() * 1000.0;
59 self.stats.update(image.size(), result.len(), encoding_time_ms);
60
61 Ok(result)
62 }
63
64 fn encode_with_format(&self, image: &RawImage, config: &WebPConfig) -> EncodingResult<Vec<u8>> {
66 let encoded = match image.format {
68 PixelFormat::RGBA8 => {
69 let encoder = webp::Encoder::from_rgba(&image.data, image.width, image.height);
70 if config.lossless {
71 encoder.encode_lossless()
72 } else {
73 encoder.encode(config.quality as f32)
74 }
75 }
76 PixelFormat::RGB8 => {
77 let encoder = webp::Encoder::from_rgb(&image.data, image.width, image.height);
78 if config.lossless {
79 encoder.encode_lossless()
80 } else {
81 encoder.encode(config.quality as f32)
82 }
83 }
84 PixelFormat::BGRA8 => {
85 let mut rgba_data = image.data.clone();
87 self.convert_bgra_to_rgba_inplace(&mut rgba_data);
88 let encoder = webp::Encoder::from_rgba(&rgba_data, image.width, image.height);
89 if config.lossless {
90 encoder.encode_lossless()
91 } else {
92 encoder.encode(config.quality as f32)
93 }
94 }
95 PixelFormat::BGR8 => {
96 let mut rgb_data = image.data.clone();
98 self.convert_bgr_to_rgb_inplace(&mut rgb_data);
99 let encoder = webp::Encoder::from_rgb(&rgb_data, image.width, image.height);
100 if config.lossless {
101 encoder.encode_lossless()
102 } else {
103 encoder.encode(config.quality as f32)
104 }
105 }
106 _ => {
107 return Err(EncodingError::UnsupportedFormat(
108 format!("Unsupported pixel format: {:?}", image.format)
109 ));
110 }
111 };
112
113 Ok(encoded.to_vec())
114 }
115
116 fn convert_bgra_to_rgba_inplace(&self, data: &mut [u8]) {
118 for chunk in data.chunks_exact_mut(4) {
119 chunk.swap(0, 2); }
121 }
122
123 fn convert_bgr_to_rgb_inplace(&self, data: &mut [u8]) {
125 for chunk in data.chunks_exact_mut(3) {
126 chunk.swap(0, 2); }
128 }
129
130 pub fn supports_format(&self, format: PixelFormat) -> bool {
132 matches!(
133 format,
134 PixelFormat::RGBA8 | PixelFormat::RGB8 | PixelFormat::BGRA8 | PixelFormat::BGR8
135 )
136 }
137
138 pub fn capabilities(&self) -> Vec<&'static str> {
140 vec![
141 "WebP encoding",
142 "Lossy compression",
143 "Lossless compression",
144 "RGBA/RGB support",
145 "BGRA/BGR conversion",
146 "Quality control",
147 ]
148 }
149}
150
151impl ImageEncoder for WebPEncoder {
152 fn encode(&self, image: &RawImage, config: &WebPConfig) -> EncodingResult<Vec<u8>> {
153 self.encode_with_format(image, config)
155 }
156
157 fn name(&self) -> &str {
158 "WebPEncoder"
159 }
160
161 fn supports_format(&self, format: PixelFormat) -> bool {
162 self.supports_format(format)
163 }
164}
165
166#[cfg(test)]
167mod tests {
168 use super::*;
169
170 #[test]
171 fn test_encoder_creation() {
172 let encoder = WebPEncoder::new();
173 assert_eq!(encoder.stats.images_encoded, 0);
174 }
175
176 #[test]
177 fn test_format_support() {
178 let encoder = WebPEncoder::new();
179 assert!(encoder.supports_format(PixelFormat::RGBA8));
180 assert!(encoder.supports_format(PixelFormat::RGB8));
181 assert!(encoder.supports_format(PixelFormat::BGRA8));
182 assert!(encoder.supports_format(PixelFormat::BGR8));
183 }
184
185 #[test]
186 fn test_config_validation() {
187 let mut config = WebPConfig::default();
188 assert!(config.validate().is_ok());
189
190 config.quality = 101;
191 assert!(config.validate().is_err());
192 }
193
194 #[test]
195 fn test_encode_rgba() {
196 let encoder = WebPEncoder::new();
197 let test_image = RawImage {
198 data: vec![255u8; 100 * 100 * 4],
199 width: 100,
200 height: 100,
201 format: PixelFormat::RGBA8,
202 stride: 100 * 4,
203 };
204
205 let config = WebPConfig::default();
206 let result = encoder.encode(&test_image, &config);
207 assert!(result.is_ok());
208
209 let webp_data = result.unwrap();
210 assert!(!webp_data.is_empty());
211 }
212}