Skip to main content

libbitsub_core/
utils.rs

1//! Utility functions for binary reading and color conversion.
2
3use byteorder::{BigEndian, ReadBytesExt};
4use std::io::{Cursor, Read};
5
6/// Binary reader wrapper for big-endian data (used in PGS).
7pub struct BigEndianReader<'a> {
8    cursor: Cursor<&'a [u8]>,
9}
10
11impl<'a> BigEndianReader<'a> {
12    pub fn new(data: &'a [u8]) -> Self {
13        Self {
14            cursor: Cursor::new(data),
15        }
16    }
17
18    #[inline]
19    pub fn position(&self) -> usize {
20        self.cursor.position() as usize
21    }
22
23    #[inline]
24    pub fn set_position(&mut self, pos: usize) {
25        self.cursor.set_position(pos as u64);
26    }
27
28    #[inline]
29    pub fn remaining(&self) -> usize {
30        self.cursor.get_ref().len() - self.position()
31    }
32
33    #[inline]
34    pub fn read_u8(&mut self) -> Option<u8> {
35        self.cursor.read_u8().ok()
36    }
37
38    #[inline]
39    pub fn read_u16(&mut self) -> Option<u16> {
40        self.cursor.read_u16::<BigEndian>().ok()
41    }
42
43    #[inline]
44    pub fn read_u24(&mut self) -> Option<u32> {
45        let mut buf = [0u8; 3];
46        self.cursor.read_exact(&mut buf).ok()?;
47        Some(((buf[0] as u32) << 16) | ((buf[1] as u32) << 8) | (buf[2] as u32))
48    }
49
50    #[inline]
51    pub fn read_u32(&mut self) -> Option<u32> {
52        self.cursor.read_u32::<BigEndian>().ok()
53    }
54
55    #[inline]
56    pub fn read_bytes(&mut self, len: usize) -> Option<Vec<u8>> {
57        let mut buf = vec![0u8; len];
58        self.cursor.read_exact(&mut buf).ok()?;
59        Some(buf)
60    }
61
62    #[inline]
63    pub fn skip(&mut self, len: usize) -> bool {
64        let new_pos = self.position() + len;
65        if new_pos <= self.cursor.get_ref().len() {
66            self.cursor.set_position(new_pos as u64);
67            true
68        } else {
69            false
70        }
71    }
72}
73
74/// Convert YCbCr to RGBA (packed as u32 in little-endian: ABGR layout for canvas).
75/// This matches the JavaScript implementation exactly.
76#[inline]
77pub fn ycbcr_to_rgba(y: u8, cb: u8, cr: u8, a: u8) -> u32 {
78    let y = y as f32;
79    let cb = (cb as f32) - 128.0;
80    let cr = (cr as f32) - 128.0;
81
82    let r = clamp((y + 1.40200 * cr).round() as i32, 0, 255) as u8;
83    let g = clamp((y - 0.34414 * cb - 0.71414 * cr).round() as i32, 0, 255) as u8;
84    let b = clamp((y + 1.77200 * cb).round() as i32, 0, 255) as u8;
85
86    // Pack as RGBA (for ImageData which expects [R, G, B, A] bytes)
87    // In little-endian memory: byte order is R, G, B, A
88    u32::from_le_bytes([r, g, b, a])
89}
90
91/// Convert RGB to packed RGBA u32.
92#[inline]
93pub fn rgb_to_rgba(r: u8, g: u8, b: u8, a: u8) -> u32 {
94    u32::from_le_bytes([r, g, b, a])
95}
96
97#[inline]
98pub fn clamp<T: Ord>(value: T, min: T, max: T) -> T {
99    if value < min {
100        min
101    } else if value > max {
102        max
103    } else {
104        value
105    }
106}
107
108/// Fast binary search for finding timestamp index.
109pub fn binary_search_timestamp(timestamps: &[u32], target: u32) -> usize {
110    if timestamps.is_empty() {
111        return 0;
112    }
113
114    let mut low = 0;
115    let mut high = timestamps.len();
116
117    while low < high {
118        let mid = low + (high - low) / 2;
119        if timestamps[mid] <= target {
120            low = mid + 1;
121        } else {
122            high = mid;
123        }
124    }
125
126    if low > 0 { low - 1 } else { 0 }
127}
128
129#[cfg(test)]
130mod tests {
131    use super::*;
132
133    #[test]
134    fn test_binary_search_timestamp() {
135        let timestamps = vec![0, 1000, 2000, 3000, 4000];
136
137        assert_eq!(binary_search_timestamp(&timestamps, 0), 0);
138        assert_eq!(binary_search_timestamp(&timestamps, 500), 0);
139        assert_eq!(binary_search_timestamp(&timestamps, 1000), 1);
140        assert_eq!(binary_search_timestamp(&timestamps, 1500), 1);
141        assert_eq!(binary_search_timestamp(&timestamps, 4500), 4);
142    }
143
144    #[test]
145    fn test_ycbcr_to_rgba() {
146        // White (Y=255, Cb=128, Cr=128) -> RGB(255, 255, 255)
147        let white = ycbcr_to_rgba(255, 128, 128, 255);
148        let bytes = white.to_le_bytes();
149        assert_eq!(bytes[0], 255); // R
150        assert_eq!(bytes[1], 255); // G
151        assert_eq!(bytes[2], 255); // B
152        assert_eq!(bytes[3], 255); // A
153    }
154}