Skip to main content

ithmb_core/enc/
clcl.rs

1// SPDX-License-Identifier: MIT
2// Encoder: CLCL — separate Cb/Cr nibble planes, 2 bytes per pixel
3
4use crate::enc::helpers::{bt601_cb, bt601_cr, bt601_y};
5use crate::pixel_utils::clamp_u8;
6
7/// Encode BGRA pixels to CLCL nibble-chroma planar format.
8///
9/// Output layout (2 Bpp):
10///   Y plane:   `w × h` bytes (full 8-bit luma)
11///   Cb plane:  `(w × h) / 2` bytes (packed nibbles, odd pixel high nibble)
12///   Cr plane:  `(w × h) / 2` bytes (packed nibbles, odd pixel high nibble)
13///
14/// Each chroma nibble is `(value >> 4)` — the top 4 bits.
15#[must_use]
16pub fn encode_clcl(bgra: &[u8], w: i32, h: i32) -> Vec<u8> {
17    let wu = w as usize;
18    let hu = h as usize;
19    let n = wu * hu;
20    let chroma_len = n.div_ceil(2); // ceiling division for nibble packing
21    let mut out = vec![0u8; n + chroma_len + chroma_len];
22
23    // Y plane
24    for (i, chunk) in bgra.chunks_exact(4).take(n).enumerate() {
25        let r = i32::from(chunk[2]);
26        let g = i32::from(chunk[1]);
27        let b = i32::from(chunk[0]);
28        out[i] = clamp_u8(bt601_y(r, g, b));
29    }
30
31    // Cb plane (packed nibbles: odd pixel in high nibble, even in low)
32    let cb_off = n;
33    for i in 0..n {
34        let px = i * 4;
35        let r = i32::from(bgra[px + 2]);
36        let g = i32::from(bgra[px + 1]);
37        let b = i32::from(bgra[px]);
38        let cb_nibble = (clamp_u8(bt601_cb(r, g, b)) >> 4) & 0x0F;
39        let ci = i / 2;
40        if i & 1 == 0 {
41            // Even → low nibble
42            out[cb_off + ci] = cb_nibble;
43        } else {
44            // Odd → high nibble
45            out[cb_off + ci] |= cb_nibble << 4;
46        }
47    }
48
49    // Cr plane (same packing)
50    let cr_off = n + chroma_len;
51    for i in 0..n {
52        let px = i * 4;
53        let r = i32::from(bgra[px + 2]);
54        let g = i32::from(bgra[px + 1]);
55        let b = i32::from(bgra[px]);
56        let cr_nibble = (clamp_u8(bt601_cr(r, g, b)) >> 4) & 0x0F;
57        let ci = i / 2;
58        if i & 1 == 0 {
59            out[cr_off + ci] = cr_nibble;
60        } else {
61            out[cr_off + ci] |= cr_nibble << 4;
62        }
63    }
64
65    out
66}