ljpeg 0.1.2

Fast, no dependency lossless JPEG decoder and encoder
Documentation
use crate::pumps::BitPump;

use alloc::{vec, vec::Vec};

const DECODE_CACHE_BITS: u32 = 13;

#[derive(Debug)]
pub(crate) struct HuffTable {
    // These two fields directly represent the contents of a JPEG DHT marker
    bits: [u32; 17],
    huffval: [u32; 256],

    // The max number of bits in a huffman code and the table that converts those
    // bits into how many bits to consume and the decoded length and shift
    nbits: u32,
    hufftable: Vec<(u8, u8)>,

    // A pregenerated table that goes straight to decoding a diff without first
    // finding a length, fetching bits, and sign extending them. The table is
    // sized by DECODE_CACHE_BITS and can have 99%+ hit rate with 13 bits
    decodecache: [Option<(u8, i16)>; 1 << DECODE_CACHE_BITS],
}

struct MockPump {
    bits: u64,
    nbits: u32,
}

impl MockPump {
    pub fn empty() -> Self {
        MockPump { bits: 0, nbits: 0 }
    }

    pub fn set(&mut self, bits: u32, nbits: u32) {
        self.bits = (bits as u64) << 32;
        self.nbits = nbits + 32;
    }

    pub fn validbits(&self) -> i32 {
        self.nbits as i32 - 32
    }
}

impl BitPump for MockPump {
    fn peek_bits(&mut self, num: u32) -> u32 {
        (self.bits >> (self.nbits - num)) as u32
    }

    fn consume_bits(&mut self, num: u32) {
        self.nbits -= num;
        self.bits &= (1 << self.nbits) - 1;
    }
}

impl HuffTable {
    pub(crate) const fn empty() -> HuffTable {
        HuffTable {
            bits: [0; 17],
            huffval: [0; 256],

            nbits: 0,
            hufftable: Vec::new(),
            decodecache: [None; 1 << DECODE_CACHE_BITS],
        }
    }

    pub(crate) fn new(bits: [u32; 17], huffval: [u32; 256]) -> HuffTable {
        let mut tbl = HuffTable {
            bits,
            huffval,

            nbits: 0,
            hufftable: Vec::new(),
            decodecache: [None; 1 << DECODE_CACHE_BITS],
        };
        tbl.initialize();
        tbl
    }

    pub(crate) fn initialize(&mut self) {
        // Find out the max code length and allocate a table with that size
        self.nbits = 16;
        for i in 0..16 {
            if self.bits[16 - i] != 0 {
                break;
            }
            self.nbits -= 1;
        }
        self.hufftable = vec![(0, 0); 1 << self.nbits];

        // Fill in the table itself
        let mut h = 0;
        let mut pos = 0;
        for len in 0..self.nbits {
            for _ in 0..self.bits[len as usize + 1] {
                for _ in 0..(1 << (self.nbits - len - 1)) {
                    self.hufftable[h] = (len as u8 + 1, self.huffval[pos] as u8);
                    h += 1;
                }
                pos += 1;
            }
        }

        // Create the decode cache by running the slow code over all the possible
        // values DECODE_CACHE_BITS wide
        let mut pump = MockPump::empty();
        for i in 0..(1 << DECODE_CACHE_BITS) {
            pump.set(i, DECODE_CACHE_BITS);
            let (mut bits, decode) = self.huff_decode_slow(&mut pump);
            if pump.validbits() >= 0 {
                if decode == -32768 {
                    debug_assert!(bits > 16);
                    bits -= 16;
                }
                self.decodecache[i as usize] = Some((bits, decode as i16));
            }
        }
    }

    #[inline]
    pub(crate) fn huff_decode(&self, pump: &mut impl BitPump) -> i32 {
        let code = pump.peek_bits(DECODE_CACHE_BITS) as usize;
        if let Some((bits, decode)) = self.decodecache[code] {
            pump.consume_bits(bits as u32);
            decode as i32
        } else {
            let decode = self.huff_decode_slow(pump);
            decode.1
        }
    }

    #[inline]
    pub(crate) fn huff_decode_slow(&self, pump: &mut impl BitPump) -> (u8, i32) {
        let len = self.huff_len(pump);
        (len.0 + len.1, self.huff_diff(pump, len))
    }

    #[inline]
    pub(crate) fn huff_len(&self, pump: &mut impl BitPump) -> (u8, u8) {
        let code = pump.peek_bits(self.nbits) as usize;
        let (bits, len) = self.hufftable[code];
        pump.consume_bits(bits as u32);
        (bits, len)
    }

    #[inline]
    pub(crate) fn huff_diff(&self, pump: &mut impl BitPump, input: (u8, u8)) -> i32 {
        let (_, len) = input;

        match len {
            0 => 0,
            16 => -32768,
            len => {
                // decode the difference and extend sign bit
                let mut diff = pump.get_bits(len as u32) as i32;
                if (diff & (1 << (len - 1))) == 0 {
                    diff -= (1 << len) - 1;
                }
                diff
            }
        }
    }
}