Skip to main content

oxihuman_export/
webp_export.rs

1// Copyright (C) 2026 COOLJAPAN OU (Team KitaSan)
2// SPDX-License-Identifier: Apache-2.0
3#![allow(dead_code)]
4
5//! WebP image stub export.
6
7/// WebP export options.
8#[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/// WebP image stub.
28#[derive(Debug, Clone)]
29pub struct WebpExport {
30    pub options: WebpOptions,
31    pub pixels: Vec<[u8; 4]>,
32}
33
34impl WebpExport {
35    /// Create a new WebP export filled with a solid color.
36    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    /// Pixel count.
49    pub fn pixel_count(&self) -> usize {
50        self.pixels.len()
51    }
52}
53
54/// Estimate WebP compressed size (stub heuristic).
55pub 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
64/// Validate WebP options.
65pub 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
72/// Serialize WebP metadata to JSON (stub).
73pub 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
83/// Average pixel luminance.
84pub 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        /* pixel count matches dimensions */
107        let e = white_export();
108        assert_eq!(e.pixel_count(), 64);
109    }
110
111    #[test]
112    fn test_validate_valid() {
113        /* valid export passes validation */
114        assert!(validate_webp(&white_export()));
115    }
116
117    #[test]
118    fn test_validate_zero_dim() {
119        /* zero dimension fails validation */
120        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        /* byte estimate is positive */
128        assert!(estimate_webp_bytes(&white_export()) > 0);
129    }
130
131    #[test]
132    fn test_lossless_larger_estimate() {
133        /* lossless estimate and lossy estimate are both positive */
134        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        /* white image has near-255 luminance */
144        let e = white_export();
145        assert!(average_luminance(&e) > 200.0);
146    }
147
148    #[test]
149    fn test_metadata_json_contains_quality() {
150        /* metadata JSON contains quality field */
151        let e = white_export();
152        assert!(webp_metadata_json(&e).contains("quality"));
153    }
154
155    #[test]
156    fn test_black_luminance() {
157        /* black image has near-zero luminance */
158        let e = WebpExport::new_solid(4, 4, [0, 0, 0, 255]);
159        assert!(average_luminance(&e) < 1.0);
160    }
161}