From d22be14de0c0ec00cecec0e19a3c5108ed3b2576 Mon Sep 17 00:00:00 2001 From: Sofia Date: Fri, 12 Sep 2025 02:53:10 +0300 Subject: [PATCH] Very nearly get qoi image rendering --- images/Image.qoi | Bin 294 -> 288 bytes images/Image.xcf | Bin 3133 -> 2936 bytes src/display.rs | 12 ++- src/image.rs | 2 - src/main.rs | 25 +++-- src/qoi.rs | 243 +++++++++++++++++++++++++++++++++++++++++++++++ 6 files changed, 268 insertions(+), 14 deletions(-) delete mode 100644 src/image.rs create mode 100644 src/qoi.rs diff --git a/images/Image.qoi b/images/Image.qoi index 4afbcc79cc65a8baa8bb5fdb870f338454615c0a..865ad892dc0a3df8b4481ee6fe4bb2926be257a0 100644 GIT binary patch literal 288 zcmXTS&rD-rU~m9oW`?LM{}{ObpEY`~Ks|hOQRx+hvlc)j$-*~{fV!#bAYwds6Br&K Gzz6`X^^4K~ literal 294 zcmbV{OA5k35JX2ji_{5f?`Z^A;t>OCq9$m58e{y6B5vI1F40Qxv$vsmT|GnBOt;f+ z8DpBI>ZUM_Xa22ip{b34J)|6BLI=s8T+oKk0W$3hrEf-lWgicn-nD&3O9>;yx)F6JuQH OrVQAS+y7SkC%x6 diff --git a/images/Image.xcf b/images/Image.xcf index 372c1c39e44a2bbda71c2315fa8d9c88fd87d855..3346adcc274f7b183c436621e5f493baa3c8d492 100644 GIT binary patch literal 2936 zcmb_eZBNud5H6rX4#hVx@#2TIY7W%4+XF=}cL#z>NHh{&A|`w(?Y6hE?bdcri(m9V znD`U?9mb#GpE+l)cSk{i18**~`|Nh+>2zja&f`HuQrjgTsc9-8iycM$9rP;b&=54& zLC2P13Ke7$^c-jb^hO7x-ISt?-h%#;2h^G^f`Ek*#tJ^Xwj47{GKYNPvO!i>U$5bl zD{WIASRT{7m^(NJ)0s)zBL!z#iR!U2yXTo#xEsN0Q-g-0CT<$aNgWwuVBN0o@w~}7^u>{Iw zF53Zyn@1(DCx`V#`bssW})yF7=bvB?9lF4OX(3Vm8J%Y>Sh z+4;&`zrJr`?(xv_&%)6T{1pp5*--0`=T3;WZT*%zeHpJaSj0^UN!@~~LvSaMZ|^&( z^;t>{^8|-?{X48RgcSNRZlw)arI7BU$urw-VV*;7CI8Hv(`EM{$=NEDalC2ok!+1T zptBW9k4(#9hg|HZ^xO~lup=&d+|7(cF>`rZQzOi6(h!kKnz3I~8&XCU!w3`Ia>Opv z;XE{YHU?}jL#Aasb>Cjlm6|D%duCNe-@)^ z?4Or-)aGRLTW-*WivUnj#+NMny%&pS*uEEsvsvW=^mzTYKyM>n_zEpt(V#ewN4?s? z&?$H&<2(vp*_i7&_%H;uZ+>cXGJ<7oz)3 z@Caq>N7f%`7h!}t0tzzrE9=|sBDAO=W52T={)eagcFfg7`i^GGCr=M?wn3ahZSPo2!3RDzxJ21w6qbn+Z0G+kYEC2ui delta 1141 zcmbW1L2DC16vyAD$tIhou}yZjX=}_jI<^tEG(sw^sG&XdAcAN?#Dk>iw1g(vu$!P2 zVNc>g5M@yCW0Zm*>cP9;z>A+iuinQ0q-oQNr#|@S|K6M3H~coU`^Wy8d$VfAh{%Sb zkOo;q@po?@EG#dsH(H&$zE|g4J)f_6?d`_Syb}=;o3)^e zd;0@4@ll)I*lcxN{It!@PyVm{MYm42f9R{?r#@$GG+KPk=d~{1 zD&9OE|1h54e^Jr$>QN0MVIF0NKDvJJDsE^IV@|wJ6hmX}#Bvq662f?N=tSW-6nV`O zlw~oRVM!Wi22GvOr!gnP;*`fMlf^=e{=js)hV~N1PO=C_kCKgL8*ZU*!n4?J=t%736?@VMrp`oX&OUqv2ikxTg;*ea+}Tfm1B&~vm#w!1qvOOr;BWcF0m<^V>Vr3 z87gD5Fp^`SmY|lPmY|lPKBG@#PDX+nvrGuk63{Xc(h|l_N>EEsW66eFxZH$ivE9%? zjh?DA1D}wfmY{}A32M}q1T}I?f*QFkK@H_3s3oXnfgA~F321p6)DqMZ)YuHv690R3 zpmX!4yinZW>tQR#P@hfXo=kO^k7ZB?KnI%0wFF#seU&6 jQHS&B!!YbpN_{k{1F12Hl-lMfsz{U&spUiOpH%r5kB!!F 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 + } + } +}