1mod animation_encoder;
7#[doc(inline)]
8pub use animation_encoder::*;
9
10mod decoder;
11#[doc(inline)]
12pub use decoder::*;
13
14mod animation_decoder;
15#[doc(inline)]
16pub use animation_decoder::*;
17
18mod encoder;
19#[doc(inline)]
20pub use encoder::*;
21pub use libwebp_sys::WebPEncodingError;
22
23mod shared;
24#[doc(inline)]
25pub use shared::*;
26
27pub use libwebp_sys::WebPConfig;
28
29#[cfg(test)]
30mod tests {
31 use std::ops::Deref;
32
33 use image::*;
34
35 use crate::*;
36
37 fn hsv_to_rgb(h: f64, s: f64, v: f64) -> [u8; 3] {
38 let h = (h - h.floor()) * 6.0;
39 let f = h - h.floor();
40 let u = (v * 255.0) as u8;
41 let p = (v * (1.0 - s) * 255.0) as u8;
42 let q = (v * (1.0 - s * f) * 255.0) as u8;
43 let t = (v * (1.0 - s * (1.0 - f)) * 255.0) as u8;
44
45 match h as u8 {
46 0 => [u, t, p],
47 1 => [q, u, p],
48 2 => [p, u, t],
49 3 => [p, q, u],
50 4 => [t, p, u],
51 5 => [u, p, q],
52 _ => unreachable!("h must be between 0.0 and 1.0"),
53 }
54 }
55
56 fn generate_color_wheel(width: u32, height: u32, background_alpha: bool) -> DynamicImage {
57 let f = |x, y| {
58 let width = width as f64;
59 let height = height as f64;
60
61 let x = x as f64 - width / 2.0;
62 let y = y as f64 - height / 2.0;
63
64 let theta = y.atan2(x);
65 let tau = 2.0 * std::f64::consts::PI;
66 let h = (theta + std::f64::consts::PI) / tau;
67 let s = (4.0 * (x * x + y * y) / width / height).sqrt();
68 let v = 1.0;
69
70 if s > 1.0 {
71 Rgba([0, 0, 0, if background_alpha { 0 } else { 255 }])
72 } else {
73 let [r, g, b] = hsv_to_rgb(h, s, v);
74 Rgba([r, g, b, 255])
75 }
76 };
77
78 DynamicImage::ImageRgba8(ImageBuffer::from_fn(width, height, f))
79 }
80
81 const SIZE: u32 = 96;
82
83 #[test]
84 fn encode_decode() {
85 let test_image_no_alpha = generate_color_wheel(SIZE, SIZE, false);
86 let encoded = Encoder::from_image(&test_image_no_alpha)
87 .unwrap()
88 .encode_lossless();
89
90 let decoded = Decoder::new(encoded.deref())
91 .decode()
92 .unwrap()
93 .to_image()
94 .to_rgb8();
95 assert_eq!(test_image_no_alpha.to_rgb8().deref(), decoded.deref());
96
97 let test_image_alpha = generate_color_wheel(SIZE, SIZE, true);
98 let encoded = Encoder::from_image(&test_image_alpha)
99 .unwrap()
100 .encode_lossless();
101
102 let decoded = Decoder::new(encoded.deref())
103 .decode()
104 .unwrap()
105 .to_image()
106 .to_rgba8();
107
108 fn compare(p1: &Rgba<u8>, p2: &Rgba<u8>) -> bool {
112 if p1.channels()[3] == 0 && p2.channels()[3] == 0 {
114 true
115 } else {
116 p1 == p2
118 }
119 }
120
121 for (p1, p2) in test_image_alpha.to_rgba8().pixels().zip(decoded.pixels()) {
122 assert!(compare(p1, p2))
123 }
124 }
125
126 #[test]
127 fn get_info() {
128 let test_image_no_alpha = generate_color_wheel(SIZE, SIZE, false);
129 let encoded = Encoder::from_image(&test_image_no_alpha)
130 .unwrap()
131 .encode_lossless();
132
133 let features = BitstreamFeatures::new(encoded.deref()).unwrap();
134 assert_eq!(features.width(), SIZE);
135 assert_eq!(features.height(), SIZE);
136 assert!(!features.has_alpha());
137 assert!(!features.has_animation());
138
139 let test_image_alpha = generate_color_wheel(SIZE, SIZE, true);
140 let encoded = Encoder::from_image(&test_image_alpha)
141 .unwrap()
142 .encode_lossless();
143
144 let features = BitstreamFeatures::new(encoded.deref()).unwrap();
145 assert_eq!(features.width(), SIZE);
146 assert_eq!(features.height(), SIZE);
147 assert!(features.has_alpha());
148 assert!(!features.has_animation());
149 }
150
151 #[test]
152 fn anim_encode_decode() {
153 let width = 32u32;
154 let height = 32u32;
155 let mut encode_images = vec![];
156 let mut config = WebPConfig::new().unwrap();
157 config.lossless = 1;
158 config.alpha_compression = 0;
159 config.alpha_filtering = 0;
160 config.quality = 75f32;
161
162 let mut encoder = AnimEncoder::new(width, height, &config);
163 encoder.set_bgcolor([255, 0, 0, 255]);
164 encoder.set_loop_count(3);
165
166 let v = generate_color_wheel(width, height, true);
167 encode_images.push(v);
168
169 let v = generate_color_wheel(width, height, true);
170 encode_images.push(v);
171
172 let v = generate_color_wheel(width, height, true);
173 encode_images.push(v);
174
175 let v = generate_color_wheel(width, height, true);
176 encode_images.push(v);
177
178 let mut t = 1000;
179 for v in encode_images.iter() {
180 encoder.add_frame(AnimFrame::from_image(v, t).unwrap());
181 t += 250;
182 }
183 let webp = encoder.encode();
184 let mut decode_images: Vec<DynamicImage> = vec![];
185 match AnimDecoder::new(&webp).decode() {
186 Ok(frames) => {
187 decode_images.extend((&frames).into_iter().map(|a| (&a).into()));
188 }
189 Err(mes) => {
190 println!("{}", mes);
191 }
192 }
193 let mut encode_rgba = vec![];
194 for v in encode_images.into_iter() {
195 let value = DynamicImage::ImageRgba8(v.to_rgba8());
196 encode_rgba.push(value);
197 }
198 fn compare(p1: &Rgba<u8>, p2: &Rgba<u8>) -> bool {
199 if p1.channels()[3] == 0 && p2.channels()[3] == 0 {
201 true
202 } else {
203 p1 == p2
205 }
206 }
207 for (i1, i2) in encode_rgba.iter().zip(decode_images.iter()) {
208 for (p1, p2) in i1.to_rgba8().pixels().zip(i2.to_rgba8().pixels()) {
209 assert!(compare(p1, p2))
210 }
211 }
212 }
213}