Skip to main content

webp_rust/decoder/
lossy.rs

1//! Lossy `VP8` decode helpers.
2
3use crate::decoder::alpha::{apply_alpha_plane, decode_alpha_plane};
4use crate::decoder::header::parse_still_webp;
5use crate::decoder::vp8::{parse_macroblock_data, FilterType, MacroBlockData, MacroBlockDataFrame};
6use crate::decoder::vp8i::{
7    WebpFormat, B_DC_PRED, B_HD_PRED, B_HE_PRED, B_HU_PRED, B_LD_PRED, B_RD_PRED, B_TM_PRED,
8    B_VE_PRED, B_VL_PRED, B_VR_PRED, DC_PRED, H_PRED, TM_PRED, V_PRED,
9};
10use crate::decoder::DecoderError;
11
12const VP8_TRANSFORM_AC3_C1: i32 = 20_091;
13const VP8_TRANSFORM_AC3_C2: i32 = 35_468;
14
15const RGB_Y_COEFF: i32 = 19_077;
16const RGB_V_TO_R_COEFF: i32 = 26_149;
17const RGB_U_TO_G_COEFF: i32 = 6_419;
18const RGB_V_TO_G_COEFF: i32 = 13_320;
19const RGB_U_TO_B_COEFF: i32 = 33_050;
20const RGB_R_BIAS: i32 = 14_234;
21const RGB_G_BIAS: i32 = 8_708;
22const RGB_B_BIAS: i32 = 17_685;
23const YUV_FIX2: i32 = 6;
24const YUV_MASK2: i32 = (256 << YUV_FIX2) - 1;
25
26/// Decoded RGBA image.
27#[derive(Debug, Clone, PartialEq, Eq)]
28pub struct DecodedImage {
29    /// Image width in pixels.
30    pub width: usize,
31    /// Image height in pixels.
32    pub height: usize,
33    /// Packed RGBA8 pixels in row-major order.
34    pub rgba: Vec<u8>,
35}
36
37/// Decoded YUV420 image.
38#[derive(Debug, Clone, PartialEq, Eq)]
39pub struct DecodedYuvImage {
40    /// Image width in pixels.
41    pub width: usize,
42    /// Image height in pixels.
43    pub height: usize,
44    /// Y plane stride in bytes.
45    pub y_stride: usize,
46    /// U and V plane stride in bytes.
47    pub uv_stride: usize,
48    /// Y plane data.
49    pub y: Vec<u8>,
50    /// U plane data.
51    pub u: Vec<u8>,
52    /// V plane data.
53    pub v: Vec<u8>,
54}
55
56struct Planes {
57    width: usize,
58    height: usize,
59    y_stride: usize,
60    uv_stride: usize,
61    y: Vec<u8>,
62    u: Vec<u8>,
63    v: Vec<u8>,
64}
65
66impl Planes {
67    fn new(frame: &MacroBlockDataFrame) -> Self {
68        let y_stride = frame.frame.macroblock_width * 16;
69        let uv_stride = frame.frame.macroblock_width * 8;
70        let height = frame.frame.macroblock_height * 16;
71        let uv_height = frame.frame.macroblock_height * 8;
72        Self {
73            width: frame.frame.picture.width as usize,
74            height: frame.frame.picture.height as usize,
75            y_stride,
76            uv_stride,
77            y: vec![0; y_stride * height],
78            u: vec![0; uv_stride * uv_height],
79            v: vec![0; uv_stride * uv_height],
80        }
81    }
82
83    fn y_width(&self) -> usize {
84        self.y_stride
85    }
86
87    fn uv_width(&self) -> usize {
88        self.uv_stride
89    }
90}
91
92#[derive(Debug, Clone, Copy, PartialEq, Eq)]
93struct FilterInfo {
94    f_limit: u8,
95    f_ilevel: u8,
96    f_inner: bool,
97    hev_thresh: u8,
98}
99
100fn abs_diff(a: u8, b: u8) -> i32 {
101    (a as i32 - b as i32).abs()
102}
103
104fn clip_signed(value: i32) -> i32 {
105    value.clamp(-128, 127)
106}
107
108fn clip_filter_value(value: i32) -> i32 {
109    value.clamp(-16, 15)
110}
111
112fn do_filter2(plane: &mut [u8], pos: usize, step: usize) {
113    let p1 = plane[pos - 2 * step] as i32;
114    let p0 = plane[pos - step] as i32;
115    let q0 = plane[pos] as i32;
116    let q1 = plane[pos + step] as i32;
117    let a = 3 * (q0 - p0) + clip_signed(p1 - q1);
118    let a1 = clip_filter_value((a + 4) >> 3);
119    let a2 = clip_filter_value((a + 3) >> 3);
120    plane[pos - step] = clip_byte(p0 + a2);
121    plane[pos] = clip_byte(q0 - a1);
122}
123
124fn do_filter4(plane: &mut [u8], pos: usize, step: usize) {
125    let p1 = plane[pos - 2 * step] as i32;
126    let p0 = plane[pos - step] as i32;
127    let q0 = plane[pos] as i32;
128    let q1 = plane[pos + step] as i32;
129    let a = 3 * (q0 - p0);
130    let a1 = clip_filter_value((a + 4) >> 3);
131    let a2 = clip_filter_value((a + 3) >> 3);
132    let a3 = (a1 + 1) >> 1;
133    plane[pos - 2 * step] = clip_byte(p1 + a3);
134    plane[pos - step] = clip_byte(p0 + a2);
135    plane[pos] = clip_byte(q0 - a1);
136    plane[pos + step] = clip_byte(q1 - a3);
137}
138
139fn do_filter6(plane: &mut [u8], pos: usize, step: usize) {
140    let p2 = plane[pos - 3 * step] as i32;
141    let p1 = plane[pos - 2 * step] as i32;
142    let p0 = plane[pos - step] as i32;
143    let q0 = plane[pos] as i32;
144    let q1 = plane[pos + step] as i32;
145    let q2 = plane[pos + 2 * step] as i32;
146    let a = clip_signed(3 * (q0 - p0) + clip_signed(p1 - q1));
147    let a1 = (27 * a + 63) >> 7;
148    let a2 = (18 * a + 63) >> 7;
149    let a3 = (9 * a + 63) >> 7;
150    plane[pos - 3 * step] = clip_byte(p2 + a3);
151    plane[pos - 2 * step] = clip_byte(p1 + a2);
152    plane[pos - step] = clip_byte(p0 + a1);
153    plane[pos] = clip_byte(q0 - a1);
154    plane[pos + step] = clip_byte(q1 - a2);
155    plane[pos + 2 * step] = clip_byte(q2 - a3);
156}
157
158fn hev(plane: &[u8], pos: usize, step: usize, thresh: i32) -> bool {
159    let p1 = plane[pos - 2 * step];
160    let p0 = plane[pos - step];
161    let q0 = plane[pos];
162    let q1 = plane[pos + step];
163    abs_diff(p1, p0) > thresh || abs_diff(q1, q0) > thresh
164}
165
166fn needs_filter(plane: &[u8], pos: usize, step: usize, thresh: i32) -> bool {
167    let p1 = plane[pos - 2 * step];
168    let p0 = plane[pos - step];
169    let q0 = plane[pos];
170    let q1 = plane[pos + step];
171    4 * abs_diff(p0, q0) + abs_diff(p1, q1) <= thresh
172}
173
174fn needs_filter2(plane: &[u8], pos: usize, step: usize, thresh: i32, inner_thresh: i32) -> bool {
175    let p3 = plane[pos - 4 * step];
176    let p2 = plane[pos - 3 * step];
177    let p1 = plane[pos - 2 * step];
178    let p0 = plane[pos - step];
179    let q0 = plane[pos];
180    let q1 = plane[pos + step];
181    let q2 = plane[pos + 2 * step];
182    let q3 = plane[pos + 3 * step];
183    if 4 * abs_diff(p0, q0) + abs_diff(p1, q1) > thresh {
184        return false;
185    }
186    abs_diff(p3, p2) <= inner_thresh
187        && abs_diff(p2, p1) <= inner_thresh
188        && abs_diff(p1, p0) <= inner_thresh
189        && abs_diff(q3, q2) <= inner_thresh
190        && abs_diff(q2, q1) <= inner_thresh
191        && abs_diff(q1, q0) <= inner_thresh
192}
193
194fn simple_v_filter16(plane: &mut [u8], pos: usize, stride: usize, thresh: i32) {
195    let thresh2 = 2 * thresh + 1;
196    for i in 0..16 {
197        let edge = pos + i;
198        if needs_filter(plane, edge, stride, thresh2) {
199            do_filter2(plane, edge, stride);
200        }
201    }
202}
203
204fn simple_h_filter16(plane: &mut [u8], pos: usize, stride: usize, thresh: i32) {
205    let thresh2 = 2 * thresh + 1;
206    for i in 0..16 {
207        let edge = pos + i * stride;
208        if needs_filter(plane, edge, 1, thresh2) {
209            do_filter2(plane, edge, 1);
210        }
211    }
212}
213
214fn simple_v_filter16i(plane: &mut [u8], mut pos: usize, stride: usize, thresh: i32) {
215    for _ in (1..=3).rev() {
216        pos += 4 * stride;
217        simple_v_filter16(plane, pos, stride, thresh);
218    }
219}
220
221fn simple_h_filter16i(plane: &mut [u8], mut pos: usize, stride: usize, thresh: i32) {
222    for _ in (1..=3).rev() {
223        pos += 4;
224        simple_h_filter16(plane, pos, stride, thresh);
225    }
226}
227
228fn filter_loop26(
229    plane: &mut [u8],
230    mut pos: usize,
231    hstride: usize,
232    vstride: usize,
233    size: usize,
234    thresh: i32,
235    inner_thresh: i32,
236    hev_thresh: i32,
237) {
238    let thresh2 = 2 * thresh + 1;
239    for _ in 0..size {
240        if needs_filter2(plane, pos, hstride, thresh2, inner_thresh) {
241            if hev(plane, pos, hstride, hev_thresh) {
242                do_filter2(plane, pos, hstride);
243            } else {
244                do_filter6(plane, pos, hstride);
245            }
246        }
247        pos += vstride;
248    }
249}
250
251fn filter_loop24(
252    plane: &mut [u8],
253    mut pos: usize,
254    hstride: usize,
255    vstride: usize,
256    size: usize,
257    thresh: i32,
258    inner_thresh: i32,
259    hev_thresh: i32,
260) {
261    let thresh2 = 2 * thresh + 1;
262    for _ in 0..size {
263        if needs_filter2(plane, pos, hstride, thresh2, inner_thresh) {
264            if hev(plane, pos, hstride, hev_thresh) {
265                do_filter2(plane, pos, hstride);
266            } else {
267                do_filter4(plane, pos, hstride);
268            }
269        }
270        pos += vstride;
271    }
272}
273
274fn v_filter16(
275    plane: &mut [u8],
276    pos: usize,
277    stride: usize,
278    thresh: i32,
279    inner_thresh: i32,
280    hev_thresh: i32,
281) {
282    filter_loop26(plane, pos, stride, 1, 16, thresh, inner_thresh, hev_thresh);
283}
284
285fn h_filter16(
286    plane: &mut [u8],
287    pos: usize,
288    stride: usize,
289    thresh: i32,
290    inner_thresh: i32,
291    hev_thresh: i32,
292) {
293    filter_loop26(plane, pos, 1, stride, 16, thresh, inner_thresh, hev_thresh);
294}
295
296fn v_filter16i(
297    plane: &mut [u8],
298    mut pos: usize,
299    stride: usize,
300    thresh: i32,
301    inner_thresh: i32,
302    hev_thresh: i32,
303) {
304    for _ in (1..=3).rev() {
305        pos += 4 * stride;
306        filter_loop24(plane, pos, stride, 1, 16, thresh, inner_thresh, hev_thresh);
307    }
308}
309
310fn h_filter16i(
311    plane: &mut [u8],
312    mut pos: usize,
313    stride: usize,
314    thresh: i32,
315    inner_thresh: i32,
316    hev_thresh: i32,
317) {
318    for _ in (1..=3).rev() {
319        pos += 4;
320        filter_loop24(plane, pos, 1, stride, 16, thresh, inner_thresh, hev_thresh);
321    }
322}
323
324fn v_filter8(
325    plane_u: &mut [u8],
326    plane_v: &mut [u8],
327    pos: usize,
328    stride: usize,
329    thresh: i32,
330    inner_thresh: i32,
331    hev_thresh: i32,
332) {
333    filter_loop26(plane_u, pos, stride, 1, 8, thresh, inner_thresh, hev_thresh);
334    filter_loop26(plane_v, pos, stride, 1, 8, thresh, inner_thresh, hev_thresh);
335}
336
337fn h_filter8(
338    plane_u: &mut [u8],
339    plane_v: &mut [u8],
340    pos: usize,
341    stride: usize,
342    thresh: i32,
343    inner_thresh: i32,
344    hev_thresh: i32,
345) {
346    filter_loop26(plane_u, pos, 1, stride, 8, thresh, inner_thresh, hev_thresh);
347    filter_loop26(plane_v, pos, 1, stride, 8, thresh, inner_thresh, hev_thresh);
348}
349
350fn v_filter8i(
351    plane_u: &mut [u8],
352    plane_v: &mut [u8],
353    pos: usize,
354    stride: usize,
355    thresh: i32,
356    inner_thresh: i32,
357    hev_thresh: i32,
358) {
359    filter_loop24(
360        plane_u,
361        pos + 4 * stride,
362        stride,
363        1,
364        8,
365        thresh,
366        inner_thresh,
367        hev_thresh,
368    );
369    filter_loop24(
370        plane_v,
371        pos + 4 * stride,
372        stride,
373        1,
374        8,
375        thresh,
376        inner_thresh,
377        hev_thresh,
378    );
379}
380
381fn h_filter8i(
382    plane_u: &mut [u8],
383    plane_v: &mut [u8],
384    pos: usize,
385    stride: usize,
386    thresh: i32,
387    inner_thresh: i32,
388    hev_thresh: i32,
389) {
390    filter_loop24(
391        plane_u,
392        pos + 4,
393        1,
394        stride,
395        8,
396        thresh,
397        inner_thresh,
398        hev_thresh,
399    );
400    filter_loop24(
401        plane_v,
402        pos + 4,
403        1,
404        stride,
405        8,
406        thresh,
407        inner_thresh,
408        hev_thresh,
409    );
410}
411
412fn macroblock_filter_info(
413    frame: &MacroBlockDataFrame,
414    macroblock: &MacroBlockData,
415) -> Option<FilterInfo> {
416    let filter = &frame.frame.filter;
417    if filter.filter_type == FilterType::Off {
418        return None;
419    }
420
421    let segment = &frame.frame.segment;
422    let mut base_level = if segment.use_segment {
423        let level = segment.filter_strength[macroblock.header.segment as usize] as i32;
424        if segment.absolute_delta {
425            level
426        } else {
427            level + filter.level as i32
428        }
429    } else {
430        filter.level as i32
431    };
432
433    if filter.use_lf_delta {
434        base_level += filter.ref_lf_delta[0] as i32;
435        if macroblock.header.is_i4x4 {
436            base_level += filter.mode_lf_delta[0] as i32;
437        }
438    }
439
440    let level = base_level.clamp(0, 63);
441    if level == 0 {
442        return None;
443    }
444
445    let mut ilevel = level;
446    if filter.sharpness > 0 {
447        if filter.sharpness > 4 {
448            ilevel >>= 2;
449        } else {
450            ilevel >>= 1;
451        }
452        ilevel = ilevel.min(9 - filter.sharpness as i32);
453    }
454    if ilevel < 1 {
455        ilevel = 1;
456    }
457
458    Some(FilterInfo {
459        f_limit: (2 * level + ilevel) as u8,
460        f_ilevel: ilevel as u8,
461        f_inner: macroblock.header.is_i4x4 || (macroblock.non_zero_y | macroblock.non_zero_uv) != 0,
462        hev_thresh: if level >= 40 {
463            2
464        } else if level >= 15 {
465            1
466        } else {
467            0
468        },
469    })
470}
471
472fn filter_macroblock(
473    frame: &MacroBlockDataFrame,
474    planes: &mut Planes,
475    mb_x: usize,
476    mb_y: usize,
477    macroblock: &MacroBlockData,
478) {
479    let Some(info) = macroblock_filter_info(frame, macroblock) else {
480        return;
481    };
482
483    let y_pos = mb_y * 16 * planes.y_stride + mb_x * 16;
484    let uv_pos = mb_y * 8 * planes.uv_stride + mb_x * 8;
485    let limit = info.f_limit as i32;
486    let inner = info.f_ilevel as i32;
487    let hev = info.hev_thresh as i32;
488
489    match frame.frame.filter.filter_type {
490        FilterType::Off => {}
491        FilterType::Simple => {
492            if mb_x > 0 {
493                simple_h_filter16(&mut planes.y, y_pos, planes.y_stride, limit + 4);
494            }
495            if info.f_inner {
496                simple_h_filter16i(&mut planes.y, y_pos, planes.y_stride, limit);
497            }
498            if mb_y > 0 {
499                simple_v_filter16(&mut planes.y, y_pos, planes.y_stride, limit + 4);
500            }
501            if info.f_inner {
502                simple_v_filter16i(&mut planes.y, y_pos, planes.y_stride, limit);
503            }
504        }
505        FilterType::Complex => {
506            if mb_x > 0 {
507                h_filter16(&mut planes.y, y_pos, planes.y_stride, limit + 4, inner, hev);
508                h_filter8(
509                    &mut planes.u,
510                    &mut planes.v,
511                    uv_pos,
512                    planes.uv_stride,
513                    limit + 4,
514                    inner,
515                    hev,
516                );
517            }
518            if info.f_inner {
519                h_filter16i(&mut planes.y, y_pos, planes.y_stride, limit, inner, hev);
520                h_filter8i(
521                    &mut planes.u,
522                    &mut planes.v,
523                    uv_pos,
524                    planes.uv_stride,
525                    limit,
526                    inner,
527                    hev,
528                );
529            }
530            if mb_y > 0 {
531                v_filter16(&mut planes.y, y_pos, planes.y_stride, limit + 4, inner, hev);
532                v_filter8(
533                    &mut planes.u,
534                    &mut planes.v,
535                    uv_pos,
536                    planes.uv_stride,
537                    limit + 4,
538                    inner,
539                    hev,
540                );
541            }
542            if info.f_inner {
543                v_filter16i(&mut planes.y, y_pos, planes.y_stride, limit, inner, hev);
544                v_filter8i(
545                    &mut planes.u,
546                    &mut planes.v,
547                    uv_pos,
548                    planes.uv_stride,
549                    limit,
550                    inner,
551                    hev,
552                );
553            }
554        }
555    }
556}
557
558fn apply_loop_filter(frame: &MacroBlockDataFrame, planes: &mut Planes) {
559    if frame.frame.filter.filter_type == FilterType::Off {
560        return;
561    }
562
563    for mb_y in 0..frame.frame.macroblock_height {
564        for mb_x in 0..frame.frame.macroblock_width {
565            let macroblock = &frame.macroblocks[mb_y * frame.frame.macroblock_width + mb_x];
566            filter_macroblock(frame, planes, mb_x, mb_y, macroblock);
567        }
568    }
569}
570
571fn mul1(value: i32) -> i32 {
572    ((value * VP8_TRANSFORM_AC3_C1) >> 16) + value
573}
574
575fn mul2(value: i32) -> i32 {
576    (value * VP8_TRANSFORM_AC3_C2) >> 16
577}
578
579fn clip_byte(value: i32) -> u8 {
580    value.clamp(0, 255) as u8
581}
582
583fn avg2(a: u8, b: u8) -> u8 {
584    ((a as u16 + b as u16 + 1) >> 1) as u8
585}
586
587fn avg3(a: u8, b: u8, c: u8) -> u8 {
588    ((a as u16 + 2 * b as u16 + c as u16 + 2) >> 2) as u8
589}
590
591fn top_left_sample(plane: &[u8], stride: usize, x: usize, y: usize) -> u8 {
592    if y == 0 {
593        127
594    } else if x == 0 {
595        129
596    } else {
597        plane[(y - 1) * stride + (x - 1)]
598    }
599}
600
601fn top_samples<const N: usize>(
602    plane: &[u8],
603    stride: usize,
604    plane_width: usize,
605    x: usize,
606    y: usize,
607) -> [u8; N] {
608    let mut out = [0u8; N];
609    if y == 0 {
610        out.fill(127);
611        return out;
612    }
613    let row = (y - 1) * stride;
614    for (i, sample) in out.iter_mut().enumerate() {
615        let src_x = (x + i).min(plane_width - 1);
616        *sample = plane[row + src_x];
617    }
618    out
619}
620
621fn top_samples_luma4(
622    plane: &[u8],
623    stride: usize,
624    plane_width: usize,
625    x: usize,
626    y: usize,
627) -> [u8; 8] {
628    let mut out = [0u8; 8];
629    if y == 0 {
630        out.fill(127);
631        return out;
632    }
633
634    let row = (y - 1) * stride;
635    for (i, sample) in out.iter_mut().enumerate().take(4) {
636        let src_x = (x + i).min(plane_width - 1);
637        *sample = plane[row + src_x];
638    }
639
640    let local_x = x & 15;
641    let local_y = y & 15;
642    if local_x == 12 && local_y != 0 {
643        let macroblock_y = y - local_y;
644        if macroblock_y == 0 {
645            out[4..].fill(127);
646        } else {
647            let top_row = (macroblock_y - 1) * stride;
648            for (i, sample) in out.iter_mut().enumerate().skip(4) {
649                let src_x = (x + i).min(plane_width - 1);
650                *sample = plane[top_row + src_x];
651            }
652        }
653    } else {
654        for (i, sample) in out.iter_mut().enumerate().skip(4) {
655            let src_x = (x + i).min(plane_width - 1);
656            *sample = plane[row + src_x];
657        }
658    }
659    out
660}
661
662fn left_samples<const N: usize>(plane: &[u8], stride: usize, x: usize, y: usize) -> [u8; N] {
663    let mut out = [0u8; N];
664    if x == 0 {
665        out.fill(129);
666        return out;
667    }
668    let src_x = x - 1;
669    for (i, sample) in out.iter_mut().enumerate() {
670        *sample = plane[(y + i) * stride + src_x];
671    }
672    out
673}
674
675fn fill_block(
676    plane: &mut [u8],
677    stride: usize,
678    x: usize,
679    y: usize,
680    width: usize,
681    height: usize,
682    value: u8,
683) {
684    for row in 0..height {
685        let offset = (y + row) * stride + x;
686        plane[offset..offset + width].fill(value);
687    }
688}
689
690fn predict_true_motion(
691    plane: &mut [u8],
692    stride: usize,
693    plane_width: usize,
694    x: usize,
695    y: usize,
696    size: usize,
697) {
698    let top = if size == 4 {
699        top_samples::<4>(plane, stride, plane_width, x, y).to_vec()
700    } else if size == 8 {
701        top_samples::<8>(plane, stride, plane_width, x, y).to_vec()
702    } else {
703        top_samples::<16>(plane, stride, plane_width, x, y).to_vec()
704    };
705    let left = if size == 4 {
706        left_samples::<4>(plane, stride, x, y).to_vec()
707    } else if size == 8 {
708        left_samples::<8>(plane, stride, x, y).to_vec()
709    } else {
710        left_samples::<16>(plane, stride, x, y).to_vec()
711    };
712    let top_left = top_left_sample(plane, stride, x, y) as i32;
713    for row in 0..size {
714        let left_value = left[row] as i32;
715        let offset = (y + row) * stride + x;
716        for col in 0..size {
717            plane[offset + col] = clip_byte(left_value + top[col] as i32 - top_left);
718        }
719    }
720}
721
722fn predict_luma16(
723    plane: &mut [u8],
724    stride: usize,
725    plane_width: usize,
726    x: usize,
727    y: usize,
728    mode: u8,
729) -> Result<(), DecoderError> {
730    match mode {
731        DC_PRED => {
732            let has_top = y > 0;
733            let has_left = x > 0;
734            let value = match (has_top, has_left) {
735                (true, true) => {
736                    let top = top_samples::<16>(plane, stride, plane_width, x, y);
737                    let left = left_samples::<16>(plane, stride, x, y);
738                    let sum_top: u32 = top.into_iter().map(u32::from).sum();
739                    let sum_left: u32 = left.into_iter().map(u32::from).sum();
740                    ((sum_top + sum_left + 16) >> 5) as u8
741                }
742                (true, false) => {
743                    let top = top_samples::<16>(plane, stride, plane_width, x, y);
744                    let sum_top: u32 = top.into_iter().map(u32::from).sum();
745                    ((sum_top + 8) >> 4) as u8
746                }
747                (false, true) => {
748                    let left = left_samples::<16>(plane, stride, x, y);
749                    let sum_left: u32 = left.into_iter().map(u32::from).sum();
750                    ((sum_left + 8) >> 4) as u8
751                }
752                (false, false) => 128,
753            };
754            fill_block(plane, stride, x, y, 16, 16, value);
755        }
756        TM_PRED => predict_true_motion(plane, stride, plane_width, x, y, 16),
757        V_PRED => {
758            let top = top_samples::<16>(plane, stride, plane_width, x, y);
759            for row in 0..16 {
760                let offset = (y + row) * stride + x;
761                plane[offset..offset + 16].copy_from_slice(&top);
762            }
763        }
764        H_PRED => {
765            let left = left_samples::<16>(plane, stride, x, y);
766            for (row, value) in left.into_iter().enumerate() {
767                let offset = (y + row) * stride + x;
768                plane[offset..offset + 16].fill(value);
769            }
770        }
771        _ => return Err(DecoderError::Bitstream("invalid luma prediction mode")),
772    }
773    Ok(())
774}
775
776fn predict_chroma8(
777    plane: &mut [u8],
778    stride: usize,
779    plane_width: usize,
780    x: usize,
781    y: usize,
782    mode: u8,
783) -> Result<(), DecoderError> {
784    match mode {
785        DC_PRED => {
786            let has_top = y > 0;
787            let has_left = x > 0;
788            let value = match (has_top, has_left) {
789                (true, true) => {
790                    let top = top_samples::<8>(plane, stride, plane_width, x, y);
791                    let left = left_samples::<8>(plane, stride, x, y);
792                    let sum_top: u32 = top.into_iter().map(u32::from).sum();
793                    let sum_left: u32 = left.into_iter().map(u32::from).sum();
794                    ((sum_top + sum_left + 8) >> 4) as u8
795                }
796                (true, false) => {
797                    let top = top_samples::<8>(plane, stride, plane_width, x, y);
798                    let sum_top: u32 = top.into_iter().map(u32::from).sum();
799                    ((sum_top + 4) >> 3) as u8
800                }
801                (false, true) => {
802                    let left = left_samples::<8>(plane, stride, x, y);
803                    let sum_left: u32 = left.into_iter().map(u32::from).sum();
804                    ((sum_left + 4) >> 3) as u8
805                }
806                (false, false) => 128,
807            };
808            fill_block(plane, stride, x, y, 8, 8, value);
809        }
810        TM_PRED => predict_true_motion(plane, stride, plane_width, x, y, 8),
811        V_PRED => {
812            let top = top_samples::<8>(plane, stride, plane_width, x, y);
813            for row in 0..8 {
814                let offset = (y + row) * stride + x;
815                plane[offset..offset + 8].copy_from_slice(&top);
816            }
817        }
818        H_PRED => {
819            let left = left_samples::<8>(plane, stride, x, y);
820            for (row, value) in left.into_iter().enumerate() {
821                let offset = (y + row) * stride + x;
822                plane[offset..offset + 8].fill(value);
823            }
824        }
825        _ => return Err(DecoderError::Bitstream("invalid chroma prediction mode")),
826    }
827    Ok(())
828}
829
830fn predict_luma4(
831    plane: &mut [u8],
832    stride: usize,
833    plane_width: usize,
834    x: usize,
835    y: usize,
836    mode: u8,
837) -> Result<(), DecoderError> {
838    let x0 = top_left_sample(plane, stride, x, y);
839    let top = top_samples_luma4(plane, stride, plane_width, x, y);
840    let left = left_samples::<4>(plane, stride, x, y);
841
842    let a = top[0];
843    let b = top[1];
844    let c = top[2];
845    let d = top[3];
846    let e = top[4];
847    let f = top[5];
848    let g = top[6];
849    let h = top[7];
850    let i = left[0];
851    let j = left[1];
852    let k = left[2];
853    let l = left[3];
854
855    let mut block = [0u8; 16];
856    match mode {
857        B_DC_PRED => {
858            let sum_top: u32 = [a, b, c, d].into_iter().map(u32::from).sum();
859            let sum_left: u32 = [i, j, k, l].into_iter().map(u32::from).sum();
860            let dc = ((sum_top + sum_left + 4) >> 3) as u8;
861            block.fill(dc);
862        }
863        B_TM_PRED => {
864            let top_left = x0 as i32;
865            for row in 0..4 {
866                let left_value = left[row] as i32;
867                for col in 0..4 {
868                    block[row * 4 + col] = clip_byte(left_value + top[col] as i32 - top_left);
869                }
870            }
871        }
872        B_VE_PRED => {
873            let vals = [avg3(x0, a, b), avg3(a, b, c), avg3(b, c, d), avg3(c, d, e)];
874            for row in 0..4 {
875                block[row * 4..row * 4 + 4].copy_from_slice(&vals);
876            }
877        }
878        B_HE_PRED => {
879            let vals = [avg3(x0, i, j), avg3(i, j, k), avg3(j, k, l), avg3(k, l, l)];
880            for (row, value) in vals.into_iter().enumerate() {
881                block[row * 4..row * 4 + 4].fill(value);
882            }
883        }
884        B_RD_PRED => {
885            block[12] = avg3(j, k, l);
886            block[13] = avg3(i, j, k);
887            block[8] = block[13];
888            block[14] = avg3(x0, i, j);
889            block[9] = block[14];
890            block[4] = block[14];
891            block[15] = avg3(a, x0, i);
892            block[10] = block[15];
893            block[5] = block[15];
894            block[0] = block[15];
895            block[11] = avg3(b, a, x0);
896            block[6] = block[11];
897            block[1] = block[11];
898            block[7] = avg3(c, b, a);
899            block[2] = block[7];
900            block[3] = avg3(d, c, b);
901        }
902        B_LD_PRED => {
903            block[0] = avg3(a, b, c);
904            block[1] = avg3(b, c, d);
905            block[4] = block[1];
906            block[2] = avg3(c, d, e);
907            block[5] = block[2];
908            block[8] = block[2];
909            block[3] = avg3(d, e, f);
910            block[6] = block[3];
911            block[9] = block[3];
912            block[12] = block[3];
913            block[7] = avg3(e, f, g);
914            block[10] = block[7];
915            block[13] = block[7];
916            block[11] = avg3(f, g, h);
917            block[14] = block[11];
918            block[15] = avg3(g, h, h);
919        }
920        B_VR_PRED => {
921            block[0] = avg2(x0, a);
922            block[9] = block[0];
923            block[1] = avg2(a, b);
924            block[10] = block[1];
925            block[2] = avg2(b, c);
926            block[11] = block[2];
927            block[3] = avg2(c, d);
928            block[12] = avg3(k, j, i);
929            block[8] = avg3(j, i, x0);
930            block[4] = avg3(i, x0, a);
931            block[13] = block[4];
932            block[5] = avg3(x0, a, b);
933            block[14] = block[5];
934            block[6] = avg3(a, b, c);
935            block[15] = block[6];
936            block[7] = avg3(b, c, d);
937        }
938        B_VL_PRED => {
939            block[0] = avg2(a, b);
940            block[1] = avg2(b, c);
941            block[2] = avg2(c, d);
942            block[3] = avg2(d, e);
943            block[4] = avg3(a, b, c);
944            block[5] = avg3(b, c, d);
945            block[6] = avg3(c, d, e);
946            block[7] = avg3(d, e, f);
947            block[8] = block[1];
948            block[9] = block[2];
949            block[10] = block[3];
950            block[11] = avg3(e, f, g);
951            block[12] = block[5];
952            block[13] = block[6];
953            block[14] = block[7];
954            block[15] = avg3(f, g, h);
955        }
956        B_HD_PRED => {
957            block[0] = avg2(i, x0);
958            block[1] = avg3(i, x0, a);
959            block[2] = avg3(x0, a, b);
960            block[3] = avg3(a, b, c);
961            block[4] = avg2(j, i);
962            block[5] = avg3(j, i, x0);
963            block[6] = block[0];
964            block[7] = block[1];
965            block[8] = avg2(k, j);
966            block[9] = avg3(k, j, i);
967            block[10] = block[4];
968            block[11] = block[5];
969            block[12] = avg2(l, k);
970            block[13] = avg3(l, k, j);
971            block[14] = block[8];
972            block[15] = block[9];
973        }
974        B_HU_PRED => {
975            block[0] = avg2(i, j);
976            block[2] = avg2(j, k);
977            block[4] = block[2];
978            block[6] = avg2(k, l);
979            block[8] = block[6];
980            block[1] = avg3(i, j, k);
981            block[3] = avg3(j, k, l);
982            block[5] = block[3];
983            block[7] = avg3(k, l, l);
984            block[9] = block[7];
985            block[11] = l;
986            block[10] = l;
987            block[12] = l;
988            block[13] = l;
989            block[14] = l;
990            block[15] = l;
991        }
992        _ => return Err(DecoderError::Bitstream("invalid 4x4 prediction mode")),
993    }
994
995    for row in 0..4 {
996        let offset = (y + row) * stride + x;
997        plane[offset..offset + 4].copy_from_slice(&block[row * 4..row * 4 + 4]);
998    }
999    Ok(())
1000}
1001
1002fn add_transform(plane: &mut [u8], stride: usize, x: usize, y: usize, coeffs: &[i16]) {
1003    if coeffs.iter().all(|&coeff| coeff == 0) {
1004        return;
1005    }
1006
1007    let mut tmp = [0i32; 16];
1008    for i in 0..4 {
1009        let a = coeffs[i] as i32 + coeffs[8 + i] as i32;
1010        let b = coeffs[i] as i32 - coeffs[8 + i] as i32;
1011        let c = mul2(coeffs[4 + i] as i32) - mul1(coeffs[12 + i] as i32);
1012        let d = mul1(coeffs[4 + i] as i32) + mul2(coeffs[12 + i] as i32);
1013        let base = i * 4;
1014        tmp[base] = a + d;
1015        tmp[base + 1] = b + c;
1016        tmp[base + 2] = b - c;
1017        tmp[base + 3] = a - d;
1018    }
1019
1020    for row in 0..4 {
1021        let dc = tmp[row] + 4;
1022        let a = dc + tmp[8 + row];
1023        let b = dc - tmp[8 + row];
1024        let c = mul2(tmp[4 + row]) - mul1(tmp[12 + row]);
1025        let d = mul1(tmp[4 + row]) + mul2(tmp[12 + row]);
1026        let offset = (y + row) * stride + x;
1027        plane[offset] = clip_byte(plane[offset] as i32 + ((a + d) >> 3));
1028        plane[offset + 1] = clip_byte(plane[offset + 1] as i32 + ((b + c) >> 3));
1029        plane[offset + 2] = clip_byte(plane[offset + 2] as i32 + ((b - c) >> 3));
1030        plane[offset + 3] = clip_byte(plane[offset + 3] as i32 + ((a - d) >> 3));
1031    }
1032}
1033
1034fn reconstruct_macroblock(
1035    planes: &mut Planes,
1036    mb_x: usize,
1037    mb_y: usize,
1038    macroblock: &MacroBlockData,
1039) -> Result<(), DecoderError> {
1040    let y_x = mb_x * 16;
1041    let y_y = mb_y * 16;
1042    let y_width = planes.y_width();
1043    let uv_width = planes.uv_width();
1044
1045    if macroblock.header.is_i4x4 {
1046        for sub_y in 0..4 {
1047            for sub_x in 0..4 {
1048                let block_index = sub_y * 4 + sub_x;
1049                let dst_x = y_x + sub_x * 4;
1050                let dst_y = y_y + sub_y * 4;
1051                predict_luma4(
1052                    &mut planes.y,
1053                    planes.y_stride,
1054                    y_width,
1055                    dst_x,
1056                    dst_y,
1057                    macroblock.header.sub_modes[block_index],
1058                )?;
1059                let coeff_offset = block_index * 16;
1060                add_transform(
1061                    &mut planes.y,
1062                    planes.y_stride,
1063                    dst_x,
1064                    dst_y,
1065                    &macroblock.coeffs[coeff_offset..coeff_offset + 16],
1066                );
1067            }
1068        }
1069    } else {
1070        predict_luma16(
1071            &mut planes.y,
1072            planes.y_stride,
1073            y_width,
1074            y_x,
1075            y_y,
1076            macroblock.header.luma_mode,
1077        )?;
1078        for sub_y in 0..4 {
1079            for sub_x in 0..4 {
1080                let block_index = sub_y * 4 + sub_x;
1081                let coeff_offset = block_index * 16;
1082                add_transform(
1083                    &mut planes.y,
1084                    planes.y_stride,
1085                    y_x + sub_x * 4,
1086                    y_y + sub_y * 4,
1087                    &macroblock.coeffs[coeff_offset..coeff_offset + 16],
1088                );
1089            }
1090        }
1091    }
1092
1093    let uv_x = mb_x * 8;
1094    let uv_y = mb_y * 8;
1095    predict_chroma8(
1096        &mut planes.u,
1097        planes.uv_stride,
1098        uv_width,
1099        uv_x,
1100        uv_y,
1101        macroblock.header.uv_mode,
1102    )?;
1103    predict_chroma8(
1104        &mut planes.v,
1105        planes.uv_stride,
1106        uv_width,
1107        uv_x,
1108        uv_y,
1109        macroblock.header.uv_mode,
1110    )?;
1111    for sub_y in 0..2 {
1112        for sub_x in 0..2 {
1113            let block_index = sub_y * 2 + sub_x;
1114            let dst_x = uv_x + sub_x * 4;
1115            let dst_y = uv_y + sub_y * 4;
1116            let u_offset = 16 * 16 + block_index * 16;
1117            let v_offset = 20 * 16 + block_index * 16;
1118            add_transform(
1119                &mut planes.u,
1120                planes.uv_stride,
1121                dst_x,
1122                dst_y,
1123                &macroblock.coeffs[u_offset..u_offset + 16],
1124            );
1125            add_transform(
1126                &mut planes.v,
1127                planes.uv_stride,
1128                dst_x,
1129                dst_y,
1130                &macroblock.coeffs[v_offset..v_offset + 16],
1131            );
1132        }
1133    }
1134
1135    Ok(())
1136}
1137
1138fn reconstruct_planes(frame: &MacroBlockDataFrame) -> Result<Planes, DecoderError> {
1139    let expected = frame.frame.macroblock_width * frame.frame.macroblock_height;
1140    if frame.macroblocks.len() != expected {
1141        return Err(DecoderError::Bitstream("macroblock count mismatch"));
1142    }
1143
1144    let mut planes = Planes::new(frame);
1145    for mb_y in 0..frame.frame.macroblock_height {
1146        for mb_x in 0..frame.frame.macroblock_width {
1147            let macroblock = &frame.macroblocks[mb_y * frame.frame.macroblock_width + mb_x];
1148            reconstruct_macroblock(&mut planes, mb_x, mb_y, macroblock)?;
1149        }
1150    }
1151    apply_loop_filter(frame, &mut planes);
1152    Ok(planes)
1153}
1154
1155fn mult_hi(value: i32, coeff: i32) -> i32 {
1156    (value * coeff) >> 8
1157}
1158
1159fn clip_rgb(value: i32) -> u8 {
1160    if (value & !YUV_MASK2) == 0 {
1161        (value >> YUV_FIX2) as u8
1162    } else if value < 0 {
1163        0
1164    } else {
1165        255
1166    }
1167}
1168
1169fn write_rgba(yy: u8, u: i32, v: i32, dst: &mut [u8], offset: usize) {
1170    let yy = yy as i32;
1171    dst[offset] = clip_rgb(mult_hi(yy, RGB_Y_COEFF) + mult_hi(v, RGB_V_TO_R_COEFF) - RGB_R_BIAS);
1172    dst[offset + 1] = clip_rgb(
1173        mult_hi(yy, RGB_Y_COEFF) - mult_hi(u, RGB_U_TO_G_COEFF) - mult_hi(v, RGB_V_TO_G_COEFF)
1174            + RGB_G_BIAS,
1175    );
1176    dst[offset + 2] =
1177        clip_rgb(mult_hi(yy, RGB_Y_COEFF) + mult_hi(u, RGB_U_TO_B_COEFF) - RGB_B_BIAS);
1178    dst[offset + 3] = 255;
1179}
1180
1181fn upsample_rgba_line_pair(
1182    top_y: &[u8],
1183    bottom_y: Option<&[u8]>,
1184    top_u: &[u8],
1185    top_v: &[u8],
1186    cur_u: &[u8],
1187    cur_v: &[u8],
1188    rgba: &mut [u8],
1189    top_offset: usize,
1190    bottom_offset: Option<usize>,
1191    len: usize,
1192) {
1193    let last_pixel_pair = (len - 1) >> 1;
1194    let mut tl_u = top_u[0] as i32;
1195    let mut tl_v = top_v[0] as i32;
1196    let mut l_u = cur_u[0] as i32;
1197    let mut l_v = cur_v[0] as i32;
1198
1199    let uv0_u = (3 * tl_u + l_u + 2) >> 2;
1200    let uv0_v = (3 * tl_v + l_v + 2) >> 2;
1201    write_rgba(top_y[0], uv0_u, uv0_v, rgba, top_offset);
1202    if let (Some(row), Some(offset)) = (bottom_y, bottom_offset) {
1203        let uv0_u = (3 * l_u + tl_u + 2) >> 2;
1204        let uv0_v = (3 * l_v + tl_v + 2) >> 2;
1205        write_rgba(row[0], uv0_u, uv0_v, rgba, offset);
1206    }
1207
1208    for x in 1..=last_pixel_pair {
1209        let t_u = top_u[x] as i32;
1210        let t_v = top_v[x] as i32;
1211        let u = cur_u[x] as i32;
1212        let v = cur_v[x] as i32;
1213
1214        let avg_u = tl_u + t_u + l_u + u + 8;
1215        let avg_v = tl_v + t_v + l_v + v + 8;
1216        let diag_12_u = (avg_u + 2 * (t_u + l_u)) >> 3;
1217        let diag_12_v = (avg_v + 2 * (t_v + l_v)) >> 3;
1218        let diag_03_u = (avg_u + 2 * (tl_u + u)) >> 3;
1219        let diag_03_v = (avg_v + 2 * (tl_v + v)) >> 3;
1220
1221        let top_left = (2 * x - 1) * 4;
1222        let top_right = 2 * x * 4;
1223        write_rgba(
1224            top_y[2 * x - 1],
1225            (diag_12_u + tl_u) >> 1,
1226            (diag_12_v + tl_v) >> 1,
1227            rgba,
1228            top_offset + top_left,
1229        );
1230        write_rgba(
1231            top_y[2 * x],
1232            (diag_03_u + t_u) >> 1,
1233            (diag_03_v + t_v) >> 1,
1234            rgba,
1235            top_offset + top_right,
1236        );
1237
1238        if let (Some(row), Some(offset)) = (bottom_y, bottom_offset) {
1239            write_rgba(
1240                row[2 * x - 1],
1241                (diag_03_u + l_u) >> 1,
1242                (diag_03_v + l_v) >> 1,
1243                rgba,
1244                offset + top_left,
1245            );
1246            write_rgba(
1247                row[2 * x],
1248                (diag_12_u + u) >> 1,
1249                (diag_12_v + v) >> 1,
1250                rgba,
1251                offset + top_right,
1252            );
1253        }
1254
1255        tl_u = t_u;
1256        tl_v = t_v;
1257        l_u = u;
1258        l_v = v;
1259    }
1260
1261    if len & 1 == 0 {
1262        let last = (len - 1) * 4;
1263        let uv0_u = (3 * tl_u + l_u + 2) >> 2;
1264        let uv0_v = (3 * tl_v + l_v + 2) >> 2;
1265        write_rgba(top_y[len - 1], uv0_u, uv0_v, rgba, top_offset + last);
1266        if let (Some(row), Some(offset)) = (bottom_y, bottom_offset) {
1267            let uv0_u = (3 * l_u + tl_u + 2) >> 2;
1268            let uv0_v = (3 * l_v + tl_v + 2) >> 2;
1269            write_rgba(row[len - 1], uv0_u, uv0_v, rgba, offset + last);
1270        }
1271    }
1272}
1273
1274fn yuv_to_rgba_fancy(planes: &Planes) -> Vec<u8> {
1275    let mut rgba = vec![0u8; planes.width * planes.height * 4];
1276    if planes.width == 0 || planes.height == 0 {
1277        return rgba;
1278    }
1279
1280    let uv_width = planes.width.div_ceil(2);
1281    let uv_height = planes.height.div_ceil(2);
1282
1283    let top_y = &planes.y[..planes.width];
1284    let top_u = &planes.u[..uv_width];
1285    let top_v = &planes.v[..uv_width];
1286    upsample_rgba_line_pair(
1287        top_y,
1288        None,
1289        top_u,
1290        top_v,
1291        top_u,
1292        top_v,
1293        &mut rgba,
1294        0,
1295        None,
1296        planes.width,
1297    );
1298
1299    for uv_row in 1..uv_height {
1300        let top_row = 2 * uv_row - 1;
1301        let bottom_row = top_row + 1;
1302        if bottom_row >= planes.height {
1303            break;
1304        }
1305        let top_y = &planes.y[top_row * planes.y_stride..top_row * planes.y_stride + planes.width];
1306        let bottom_y =
1307            &planes.y[bottom_row * planes.y_stride..bottom_row * planes.y_stride + planes.width];
1308        let prev_u =
1309            &planes.u[(uv_row - 1) * planes.uv_stride..(uv_row - 1) * planes.uv_stride + uv_width];
1310        let prev_v =
1311            &planes.v[(uv_row - 1) * planes.uv_stride..(uv_row - 1) * planes.uv_stride + uv_width];
1312        let cur_u = &planes.u[uv_row * planes.uv_stride..uv_row * planes.uv_stride + uv_width];
1313        let cur_v = &planes.v[uv_row * planes.uv_stride..uv_row * planes.uv_stride + uv_width];
1314        upsample_rgba_line_pair(
1315            top_y,
1316            Some(bottom_y),
1317            prev_u,
1318            prev_v,
1319            cur_u,
1320            cur_v,
1321            &mut rgba,
1322            top_row * planes.width * 4,
1323            Some(bottom_row * planes.width * 4),
1324            planes.width,
1325        );
1326    }
1327
1328    if planes.height > 1 && planes.height & 1 == 0 {
1329        let last_row = planes.height - 1;
1330        let uv_row = uv_height - 1;
1331        let y_row =
1332            &planes.y[last_row * planes.y_stride..last_row * planes.y_stride + planes.width];
1333        let u_row = &planes.u[uv_row * planes.uv_stride..uv_row * planes.uv_stride + uv_width];
1334        let v_row = &planes.v[uv_row * planes.uv_stride..uv_row * planes.uv_stride + uv_width];
1335        upsample_rgba_line_pair(
1336            y_row,
1337            None,
1338            u_row,
1339            v_row,
1340            u_row,
1341            v_row,
1342            &mut rgba,
1343            last_row * planes.width * 4,
1344            None,
1345            planes.width,
1346        );
1347    }
1348
1349    rgba
1350}
1351
1352fn into_decoded_yuv(planes: Planes) -> DecodedYuvImage {
1353    DecodedYuvImage {
1354        width: planes.width,
1355        height: planes.height,
1356        y_stride: planes.y_stride,
1357        uv_stride: planes.uv_stride,
1358        y: planes.y,
1359        u: planes.u,
1360        v: planes.v,
1361    }
1362}
1363
1364/// Decodes a raw `VP8 ` frame payload to planar YUV420.
1365pub fn decode_lossy_vp8_to_yuv(data: &[u8]) -> Result<DecodedYuvImage, DecoderError> {
1366    let frame = parse_macroblock_data(data)?;
1367    let planes = reconstruct_planes(&frame)?;
1368    Ok(into_decoded_yuv(planes))
1369}
1370
1371/// Decodes a raw `VP8 ` frame payload to RGBA.
1372pub fn decode_lossy_vp8_to_rgba(data: &[u8]) -> Result<DecodedImage, DecoderError> {
1373    let yuv = decode_lossy_vp8_to_yuv(data)?;
1374    Ok(DecodedImage {
1375        width: yuv.width,
1376        height: yuv.height,
1377        rgba: yuv_to_rgba_fancy(&Planes {
1378            width: yuv.width,
1379            height: yuv.height,
1380            y_stride: yuv.y_stride,
1381            uv_stride: yuv.uv_stride,
1382            y: yuv.y,
1383            u: yuv.u,
1384            v: yuv.v,
1385        }),
1386    })
1387}
1388
1389pub(crate) fn apply_lossy_alpha(
1390    image: &mut DecodedImage,
1391    alpha_data: &[u8],
1392) -> Result<(), DecoderError> {
1393    let alpha = decode_alpha_plane(alpha_data, image.width, image.height)?;
1394    apply_alpha_plane(&mut image.rgba, &alpha)
1395}
1396
1397pub(crate) fn decode_lossy_vp8_frame_to_rgba(
1398    data: &[u8],
1399    alpha_data: Option<&[u8]>,
1400) -> Result<DecodedImage, DecoderError> {
1401    let mut image = decode_lossy_vp8_to_rgba(data)?;
1402    if let Some(alpha_data) = alpha_data {
1403        apply_lossy_alpha(&mut image, alpha_data)?;
1404    }
1405    Ok(image)
1406}
1407
1408/// Decodes a still lossy WebP container to RGBA.
1409///
1410/// If an `ALPH` chunk is present, it is decoded and applied to the returned
1411/// RGBA buffer.
1412pub fn decode_lossy_webp_to_rgba(data: &[u8]) -> Result<DecodedImage, DecoderError> {
1413    let parsed = parse_still_webp(data)?;
1414    if parsed.features.format != WebpFormat::Lossy {
1415        return Err(DecoderError::Unsupported(
1416            "only still lossy WebP is supported",
1417        ));
1418    }
1419    decode_lossy_vp8_frame_to_rgba(parsed.image_data, parsed.alpha_data)
1420}
1421
1422/// Decodes a still lossy WebP container to planar YUV420.
1423///
1424/// This helper rejects input with alpha because the return type has no alpha
1425/// channel.
1426pub fn decode_lossy_webp_to_yuv(data: &[u8]) -> Result<DecodedYuvImage, DecoderError> {
1427    let parsed = parse_still_webp(data)?;
1428    if parsed.features.format != WebpFormat::Lossy {
1429        return Err(DecoderError::Unsupported(
1430            "only still lossy WebP is supported",
1431        ));
1432    }
1433    if parsed.alpha_data.is_some() {
1434        return Err(DecoderError::Unsupported("lossy alpha is not implemented"));
1435    }
1436    decode_lossy_vp8_to_yuv(parsed.image_data)
1437}
1438
1439#[cfg(test)]
1440mod tests {
1441    use super::top_samples_luma4;
1442
1443    #[test]
1444    fn top_samples_luma4_uses_macroblock_top_right_for_copy_down() {
1445        let stride = 32usize;
1446        let width = 32usize;
1447        let mut plane = vec![0u8; stride * 32];
1448
1449        plane[19 * stride + 12..19 * stride + 16].copy_from_slice(&[10, 11, 12, 13]);
1450        plane[15 * stride + 16..15 * stride + 20].copy_from_slice(&[20, 21, 22, 23]);
1451
1452        let top = top_samples_luma4(&plane, stride, width, 12, 20);
1453
1454        assert_eq!(top, [10, 11, 12, 13, 20, 21, 22, 23]);
1455    }
1456}