diff --git a/images/Image.qoi b/images/Image.qoi index 4afbcc7..865ad89 100644 Binary files a/images/Image.qoi and b/images/Image.qoi differ diff --git a/images/Image.xcf b/images/Image.xcf index 372c1c3..3346adc 100644 Binary files a/images/Image.xcf and b/images/Image.xcf differ diff --git a/src/display.rs b/src/display.rs index edaec65..c187808 100644 --- a/src/display.rs +++ b/src/display.rs @@ -42,6 +42,10 @@ impl Rgb565 { pub fn cyan() -> Rgb565 { Rgb565(0, 255, 255) } + + pub fn qoi_hash(&self) -> usize { + ((self.0 as u32 * 3 + self.1 as u32 * 5 + self.2 as u32 * 7 + 255 * 11) % 64) as usize + } } impl Mul for Rgb565 { @@ -83,7 +87,7 @@ pub struct Display { pub delay: T, } -enum Command { +pub enum Command { NOP = 0x0, SoftReset = 0x1, RddID = 0x4, @@ -138,7 +142,7 @@ enum ColorMode { ColorMode16M = 0x07, } -enum Writeable<'d> { +pub enum Writeable<'d> { Command(Command), Data(&'d [u8]), } @@ -151,7 +155,7 @@ enum Rotation { } impl Display { - fn write(&mut self, writeable: Writeable) { + pub fn write(&mut self, writeable: Writeable) { self.cs.set_low().unwrap(); match writeable { Writeable::Command(cmd) => { @@ -242,7 +246,7 @@ impl Display { self.write(Writeable::Data(&[start1, start2, end1, end2])); } - fn set_window(&mut self, pos0: Position, pos1: Position) { + pub fn set_window(&mut self, pos0: Position, pos1: Position) { self.set_columns(pos0.x, pos1.x); self.set_rows(pos0.y, pos1.y); self.write(Writeable::Command(Command::RamWR)); diff --git a/src/image.rs b/src/image.rs deleted file mode 100644 index 26e84c6..0000000 --- a/src/image.rs +++ /dev/null @@ -1,2 +0,0 @@ -// https://qoiformat.org/qoi-specification.pdf -const image: &[u8; 294] = include_bytes!("../images/Image.qoi"); diff --git a/src/main.rs b/src/main.rs index 71e60bd..05d609f 100644 --- a/src/main.rs +++ b/src/main.rs @@ -3,6 +3,7 @@ */ #![no_std] #![no_main] +#![feature(iter_array_chunks)] use atmega_hal::{ Adc, Usart, @@ -16,12 +17,13 @@ use panic_halt as _; use crate::{ display::{Display, Position, Rgb565}, peripherals::{Button, Knob}, + qoi::draw_image, }; mod display; mod font; -mod image; mod peripherals; +mod qoi; type CoreClock = atmega_hal::clock::MHz8; @@ -96,13 +98,20 @@ fn main() -> ! { idx = (idx + 1) % colors.len(); } - if last_input != input || button_poll { - last_input = input; - display.draw_rect( - Position { x: 0, y: 0 }, - Position { x: 200, y: 200 }, - (colors[idx] * input).as_color(), - ); + // if last_input != input || button_poll { + // last_input = input; + // display.draw_rect( + // Position { x: 0, y: 0 }, + // Position { x: 200, y: 200 }, + // (colors[idx] * input).as_color(), + // ); + // } + + match draw_image(&mut serial, &mut display, Position { x: 0, y: 0 }) { + Ok(_) => ufmt::uwriteln!(serial, "Successfully read QOI").unwrap(), + Err(e) => ufmt::uwriteln!(serial, "Error: {:?}", e).unwrap(), } + + delay.delay_ms(1000); } } diff --git a/src/qoi.rs b/src/qoi.rs new file mode 100644 index 0000000..0d82689 --- /dev/null +++ b/src/qoi.rs @@ -0,0 +1,243 @@ +use core::slice::Iter; + +use atmega_hal::{ + Atmega, Usart, + pac::USART0, + port::{PB3, PB5, PD0, PD1, Pin, PinOps, mode}, +}; +use embedded_hal::delay::DelayNs; +use ufmt::derive::uDebug; + +use crate::{ + CoreClock, + display::{Color, Display, Position, Rgb565, Writeable}, +}; + +// https://qoiformat.org/qoi-specification.pdf +const image: &[u8; 288] = include_bytes!("../images/Image.qoi"); +const BUFFER_SIZE: usize = 64; + +#[derive(Debug, uDebug)] +pub enum QoiErr { + InvalidMagicNumber, + UnexpectedEOF, +} + +pub fn draw_image( + serial: &mut Usart, Pin, CoreClock>, + display: &mut Display, + position: Position, +) -> Result<(), QoiErr> { + let mut iter = image.iter(); + + if let (Some(113), Some(111), Some(105), Some(102)) = + (iter.next(), iter.next(), iter.next(), iter.next()) + { + fn next<'a>(iter: &mut Iter<'a, u8>) -> Result<&'a u8, QoiErr> { + iter.next().ok_or(QoiErr::UnexpectedEOF) + } + + let width = u32::from_be_bytes([ + *next(&mut iter)?, + *next(&mut iter)?, + *next(&mut iter)?, + *next(&mut iter)?, + ]) as u16; + let height = u32::from_be_bytes([ + *next(&mut iter)?, + *next(&mut iter)?, + *next(&mut iter)?, + *next(&mut iter)?, + ]) as u16; + let channels = *next(&mut iter)?; + let colorspace = *next(&mut iter)?; + + ufmt::uwriteln!(serial, "Successfully read QOI header").unwrap(); + + display.set_window( + position, + Position { + x: position.x + width, + y: position.y + height, + }, + ); + + let qoi_iter = QoiIterator::from(iter, serial); + let mut prev_pixels = [0u8; 64]; + + let mut chunks = qoi_iter.array_chunks::(); + while let Some(array) = chunks.next() { + let mut colors = [0u8; BUFFER_SIZE * 2]; + for (i, pixel) in array.iter().enumerate() { + let [c1, c2] = pixel.as_color().bytes; + colors[i * 2] = c1; + colors[i * 2 + 1] = c2; + } + display.write(Writeable::Data(&colors)); + } + if let Some(remainder) = chunks.into_remainder() { + for pixel in remainder { + let [c1, c2] = pixel.as_color().bytes; + display.write(Writeable::Data(&[c1, c2])); + } + } + + // display.draw_rect( + // position, + // Position { + // x: position.x + 100, + // y: position.y + 100, + // }, + // Rgb565::green().as_color(), + // ); + Ok(()) + } else { + Err(QoiErr::InvalidMagicNumber) + } +} + +struct QoiIterator<'a> { + inner: Iter<'a, u8>, + serial: &'a mut Usart, Pin, CoreClock>, + prev_pixels: [Rgb565; 64], + prev_alphas: [u8; 64], + last_pixel: Rgb565, + last_alpha: u8, + repeat: u8, +} + +impl<'a> QoiIterator<'a> { + fn from( + bytes: Iter<'a, u8>, + serial: &'a mut Usart, Pin, CoreClock>, + ) -> Self { + QoiIterator { + inner: bytes, + serial, + prev_pixels: [Rgb565(255, 255, 255); 64], + prev_alphas: [255; 64], + last_pixel: Rgb565(0, 0, 0), + last_alpha: 255, + repeat: 0, + } + } +} + +impl<'a> Iterator for QoiIterator<'a> { + type Item = Rgb565; + + fn next(&mut self) -> Option { + if self.repeat > 0 { + self.repeat -= 1; + return Some(self.last_pixel); + } + if let Some(byte) = self.inner.next() { + let (color, alpha) = if *byte == 0xff { + let red = self.inner.next().unwrap(); + let green = self.inner.next().unwrap(); + let blue = self.inner.next().unwrap(); + let alpha = self.inner.next().unwrap(); + ufmt::uwriteln!(self.serial, "RGBA: {} {} {} {}", red, green, blue, alpha).unwrap(); + (Rgb565(*red, *green, *blue), *alpha) + } else if *byte == 0b11111110 { + let red = self.inner.next().unwrap(); + let green = self.inner.next().unwrap(); + let blue = self.inner.next().unwrap(); + ufmt::uwriteln!(self.serial, "RGB: {} {} {}", red, green, blue).unwrap(); + (Rgb565(*red, *green, *blue), self.last_alpha) + } else { + let tag = (0b11000000 & byte) >> 6; + let data = 0b00111111 & byte; + + ufmt::uwriteln!(self.serial, "Tag: {}", tag).unwrap(); + + if tag == 0 { + ( + self.prev_pixels[data as usize], + self.prev_alphas[data as usize], + ) + } else if tag == 1 { + let dr = ((0b110000 & data) >> 4) as i8 - 2; + let dg = ((0b001100 & data) >> 2) as i8 - 2; + let db = (0b000011 & data) as i8 - 2; + ufmt::uwriteln!( + self.serial, + "Diffs: {} {} {}", + ((0b110000 & data) >> 4), + ((0b001100 & data) >> 2), + (0b000011 & data) + ) + .unwrap(); + ( + Rgb565( + (self.last_pixel.0 as i8).wrapping_add(dr) as u8, + (self.last_pixel.1 as i8).wrapping_add(dg) as u8, + (self.last_pixel.2 as i8).wrapping_add(db) as u8, + ), + self.last_alpha, + ) + } else if tag == 2 { + let second = self.inner.next().unwrap(); + let dg = data as i8 - 32; + let dr_dg = ((0b11110000 & second) >> 4) as i8 - 8; + let db_dg = (0b00001111 & second) as i8 - 8; + let dr = dr_dg + dg; + let db = db_dg + dg; + ufmt::uwriteln!( + self.serial, + "Last: {} {} {}", + self.last_pixel.0 as i8, + self.last_pixel.1 as i8, + self.last_pixel.2 as i8 + ) + .unwrap(); + ufmt::uwriteln!(self.serial, "Values: {} {} {}", dr_dg, dg, db_dg).unwrap(); + ufmt::uwriteln!(self.serial, "Diffs: {} {} {}", dr, dg, db).unwrap(); + ufmt::uwriteln!( + self.serial, + "Res: {} {} {}", + self.last_pixel.0 as i8 + dr, + self.last_pixel.1 as i8 + dg, + self.last_pixel.2 as i8 + db + ) + .unwrap(); + ( + Rgb565( + (self.last_pixel.0 as i8).wrapping_add(dr) as u8, + (self.last_pixel.1 as i8).wrapping_add(dg) as u8, + (self.last_pixel.2 as i8).wrapping_add(db) as u8, + ), + self.last_alpha, + ) + } else if tag == 3 { + // QOI_OP_RUN + self.repeat = data; + ufmt::uwriteln!(self.serial, "Repeat: {}", self.repeat).unwrap(); + (self.last_pixel, self.last_alpha) + } else { + (Rgb565::green(), 255) + } + }; + self.last_pixel = color; + self.last_alpha = alpha; + let hash = + ((color.0 as u32 * 3 + color.1 as u32 * 5 + color.2 as u32 * 7 + alpha as u32 * 11) + % 64) as usize; + + ufmt::uwriteln!( + self.serial, + "Color: {} {} {} {}, Hash: {}", + color.0, + color.1, + color.2, + alpha, + hash + ) + .unwrap(); + self.prev_pixels[hash] = color; + Some(color) + } else { + None + } + } +}