oxihuman_export/
webp_export.rs1#![allow(dead_code)]
4
5#[derive(Debug, Clone)]
9pub struct WebpOptions {
10 pub width: u32,
11 pub height: u32,
12 pub quality: f32,
13 pub lossless: bool,
14}
15
16impl Default for WebpOptions {
17 fn default() -> Self {
18 Self {
19 width: 512,
20 height: 512,
21 quality: 80.0,
22 lossless: false,
23 }
24 }
25}
26
27#[derive(Debug, Clone)]
29pub struct WebpExport {
30 pub options: WebpOptions,
31 pub pixels: Vec<[u8; 4]>,
32}
33
34impl WebpExport {
35 pub fn new_solid(width: u32, height: u32, color: [u8; 4]) -> Self {
37 let pixels = vec![color; (width * height) as usize];
38 Self {
39 options: WebpOptions {
40 width,
41 height,
42 ..Default::default()
43 },
44 pixels,
45 }
46 }
47
48 pub fn pixel_count(&self) -> usize {
50 self.pixels.len()
51 }
52}
53
54pub fn estimate_webp_bytes(export: &WebpExport) -> usize {
56 let raw = export.pixel_count() * 4;
57 if export.options.lossless {
58 raw / 2
59 } else {
60 ((raw as f32 * export.options.quality / 100.0) as usize).max(1)
61 }
62}
63
64pub fn validate_webp(export: &WebpExport) -> bool {
66 export.options.width > 0
67 && export.options.height > 0
68 && (0.0..=100.0).contains(&export.options.quality)
69 && export.pixels.len() == (export.options.width * export.options.height) as usize
70}
71
72pub fn webp_metadata_json(export: &WebpExport) -> String {
74 format!(
75 "{{\"width\":{},\"height\":{},\"quality\":{:.1},\"lossless\":{}}}",
76 export.options.width,
77 export.options.height,
78 export.options.quality,
79 export.options.lossless
80 )
81}
82
83pub fn average_luminance(export: &WebpExport) -> f32 {
85 if export.pixels.is_empty() {
86 return 0.0;
87 }
88 let sum: f32 = export
89 .pixels
90 .iter()
91 .map(|p| 0.2126 * p[0] as f32 + 0.7152 * p[1] as f32 + 0.0722 * p[2] as f32)
92 .sum();
93 sum / export.pixels.len() as f32
94}
95
96#[cfg(test)]
97mod tests {
98 use super::*;
99
100 fn white_export() -> WebpExport {
101 WebpExport::new_solid(8, 8, [255, 255, 255, 255])
102 }
103
104 #[test]
105 fn test_pixel_count() {
106 let e = white_export();
108 assert_eq!(e.pixel_count(), 64);
109 }
110
111 #[test]
112 fn test_validate_valid() {
113 assert!(validate_webp(&white_export()));
115 }
116
117 #[test]
118 fn test_validate_zero_dim() {
119 let mut e = white_export();
121 e.options.width = 0;
122 assert!(!validate_webp(&e));
123 }
124
125 #[test]
126 fn test_estimate_webp_bytes_positive() {
127 assert!(estimate_webp_bytes(&white_export()) > 0);
129 }
130
131 #[test]
132 fn test_lossless_larger_estimate() {
133 let mut e = white_export();
135 let lossy = estimate_webp_bytes(&e);
136 e.options.lossless = true;
137 let lossless = estimate_webp_bytes(&e);
138 assert!(lossless > 0 && lossy > 0);
139 }
140
141 #[test]
142 fn test_average_luminance_white() {
143 let e = white_export();
145 assert!(average_luminance(&e) > 200.0);
146 }
147
148 #[test]
149 fn test_metadata_json_contains_quality() {
150 let e = white_export();
152 assert!(webp_metadata_json(&e).contains("quality"));
153 }
154
155 #[test]
156 fn test_black_luminance() {
157 let e = WebpExport::new_solid(4, 4, [0, 0, 0, 255]);
159 assert!(average_luminance(&e) < 1.0);
160 }
161}