Skip to main content

webp_rust/decoder/
alpha.rs

1//! Alpha-plane parsing and reconstruction helpers.
2
3use crate::decoder::lossless::decode_lossless_vp8l_to_argb;
4use crate::decoder::DecoderError;
5
6const ALPHA_HEADER_LEN: usize = 1;
7const ALPHA_NO_COMPRESSION: u8 = 0;
8const ALPHA_LOSSLESS_COMPRESSION: u8 = 1;
9const ALPHA_PREPROCESSED_LEVELS: u8 = 2;
10const ALPHA_FILTER_NONE: u8 = 0;
11const ALPHA_FILTER_HORIZONTAL: u8 = 1;
12const ALPHA_FILTER_VERTICAL: u8 = 2;
13const ALPHA_FILTER_GRADIENT: u8 = 3;
14
15/// Parsed one-byte `ALPH` header.
16#[derive(Debug, Clone, Copy, PartialEq, Eq)]
17pub struct AlphaHeader {
18    /// Compression method.
19    pub compression: u8,
20    /// Spatial filter method.
21    pub filter: u8,
22    /// Alpha preprocessing mode.
23    pub preprocessing: u8,
24}
25
26/// Parses the one-byte header that prefixes an `ALPH` payload.
27pub fn parse_alpha_header(data: &[u8]) -> Result<AlphaHeader, DecoderError> {
28    let Some(&header) = data.first() else {
29        return Err(DecoderError::NotEnoughData("ALPH header"));
30    };
31
32    let reserved = header >> 6;
33    if reserved != 0 {
34        return Err(DecoderError::Bitstream("ALPH reserved bits must be zero"));
35    }
36
37    let alpha = AlphaHeader {
38        compression: header & 0x03,
39        filter: (header >> 2) & 0x03,
40        preprocessing: (header >> 4) & 0x03,
41    };
42
43    if alpha.compression > ALPHA_LOSSLESS_COMPRESSION {
44        return Err(DecoderError::Bitstream(
45            "unsupported ALPH compression method",
46        ));
47    }
48    if alpha.preprocessing > ALPHA_PREPROCESSED_LEVELS {
49        return Err(DecoderError::Bitstream(
50            "unsupported ALPH preprocessing mode",
51        ));
52    }
53
54    Ok(alpha)
55}
56
57fn gradient_predictor(left: u8, top: u8, top_left: u8) -> u8 {
58    (left as i32 + top as i32 - top_left as i32).clamp(0, 255) as u8
59}
60
61fn unfilter_row(
62    filter: u8,
63    prev: Option<&[u8]>,
64    deltas: &[u8],
65    out: &mut [u8],
66) -> Result<(), DecoderError> {
67    match filter {
68        ALPHA_FILTER_NONE => {
69            out.copy_from_slice(deltas);
70        }
71        ALPHA_FILTER_HORIZONTAL => {
72            let mut pred = prev.map_or(0, |line| line[0]);
73            for (dst, &delta) in out.iter_mut().zip(deltas.iter()) {
74                *dst = pred.wrapping_add(delta);
75                pred = *dst;
76            }
77        }
78        ALPHA_FILTER_VERTICAL => {
79            if let Some(prev) = prev {
80                for ((dst, &delta), &top) in out.iter_mut().zip(deltas.iter()).zip(prev.iter()) {
81                    *dst = top.wrapping_add(delta);
82                }
83            } else {
84                unfilter_row(ALPHA_FILTER_HORIZONTAL, None, deltas, out)?;
85            }
86        }
87        ALPHA_FILTER_GRADIENT => {
88            if let Some(prev) = prev {
89                let mut top_left = prev[0];
90                let mut left = prev[0];
91                for (x, (dst, &delta)) in out.iter_mut().zip(deltas.iter()).enumerate() {
92                    let top = prev[x];
93                    left = delta.wrapping_add(gradient_predictor(left, top, top_left));
94                    top_left = top;
95                    *dst = left;
96                }
97            } else {
98                unfilter_row(ALPHA_FILTER_HORIZONTAL, None, deltas, out)?;
99            }
100        }
101        _ => return Err(DecoderError::Bitstream("invalid ALPH filter")),
102    }
103    Ok(())
104}
105
106fn unfilter_alpha(
107    alpha: &[u8],
108    filter: u8,
109    width: usize,
110    height: usize,
111) -> Result<Vec<u8>, DecoderError> {
112    let expected_len = width
113        .checked_mul(height)
114        .ok_or(DecoderError::Bitstream("alpha plane size overflow"))?;
115    if alpha.len() < expected_len {
116        return Err(DecoderError::NotEnoughData("alpha plane payload"));
117    }
118
119    let mut decoded = vec![0u8; expected_len];
120    for y in 0..height {
121        let row_start = y * width;
122        let row_end = row_start + width;
123        let (head, tail) = decoded.split_at_mut(row_start);
124        let prev = if y == 0 {
125            None
126        } else {
127            Some(&head[row_start - width..row_start])
128        };
129        unfilter_row(filter, prev, &alpha[row_start..row_end], &mut tail[..width])?;
130    }
131    Ok(decoded)
132}
133
134/// Decodes an `ALPH` payload to a single-channel alpha plane.
135///
136/// The returned buffer contains one alpha byte per pixel in row-major order.
137pub fn decode_alpha_plane(
138    data: &[u8],
139    width: usize,
140    height: usize,
141) -> Result<Vec<u8>, DecoderError> {
142    let header = parse_alpha_header(data)?;
143    let payload = data
144        .get(ALPHA_HEADER_LEN..)
145        .ok_or(DecoderError::NotEnoughData("ALPH payload"))?;
146    let pixel_count = width
147        .checked_mul(height)
148        .ok_or(DecoderError::Bitstream("alpha plane size overflow"))?;
149
150    match header.compression {
151        ALPHA_NO_COMPRESSION => {
152            if payload.len() < pixel_count {
153                return Err(DecoderError::NotEnoughData("ALPH raw payload"));
154            }
155            unfilter_alpha(&payload[..pixel_count], header.filter, width, height)
156        }
157        ALPHA_LOSSLESS_COMPRESSION => {
158            let (decoded_width, decoded_height, argb) = decode_lossless_vp8l_to_argb(payload)?;
159            if decoded_width != width || decoded_height != height {
160                return Err(DecoderError::Bitstream(
161                    "ALPH VP8L dimensions do not match image size",
162                ));
163            }
164            let mut filtered = vec![0u8; pixel_count];
165            for (dst, pixel) in filtered.iter_mut().zip(argb.iter()) {
166                *dst = ((pixel >> 8) & 0xff) as u8;
167            }
168            unfilter_alpha(&filtered, header.filter, width, height)
169        }
170        _ => Err(DecoderError::Bitstream(
171            "unsupported ALPH compression method",
172        )),
173    }
174}
175
176/// Replaces the alpha channel of an RGBA image with a decoded alpha plane.
177pub fn apply_alpha_plane(rgba: &mut [u8], alpha: &[u8]) -> Result<(), DecoderError> {
178    let expected_len = alpha
179        .len()
180        .checked_mul(4)
181        .ok_or(DecoderError::Bitstream("RGBA buffer size overflow"))?;
182    if rgba.len() != expected_len {
183        return Err(DecoderError::InvalidParam(
184            "RGBA buffer length does not match alpha plane",
185        ));
186    }
187
188    for (pixel, &value) in rgba.chunks_exact_mut(4).zip(alpha.iter()) {
189        pixel[3] = value;
190    }
191    Ok(())
192}
193
194#[cfg(test)]
195mod tests {
196    use super::{decode_alpha_plane, ALPHA_FILTER_HORIZONTAL};
197
198    #[test]
199    fn decode_alpha_plane_unfilters_horizontal_rows() {
200        let width = 4usize;
201        let height = 2usize;
202        let plane = [10u8, 20, 25, 40, 5, 7, 9, 11];
203        let mut filtered = Vec::with_capacity(1 + plane.len());
204        filtered.push(ALPHA_FILTER_HORIZONTAL << 2);
205
206        filtered.push(plane[0]);
207        for x in 1..width {
208            filtered.push(plane[x].wrapping_sub(plane[x - 1]));
209        }
210        filtered.push(plane[width].wrapping_sub(plane[0]));
211        for x in 1..width {
212            filtered.push(plane[width + x].wrapping_sub(plane[width + x - 1]));
213        }
214
215        let decoded = decode_alpha_plane(&filtered, width, height).unwrap();
216
217        assert_eq!(decoded, plane);
218    }
219}