diff --git a/assets/colorspace.png b/assets/colorspace.png new file mode 100644 index 0000000..e73512e Binary files /dev/null and b/assets/colorspace.png differ diff --git a/assets/rustlang.bmp b/assets/rustlang.bmp deleted file mode 100644 index e88929f..0000000 Binary files a/assets/rustlang.bmp and /dev/null differ diff --git a/assets/rustlang.png b/assets/rustlang.png new file mode 100644 index 0000000..2cb9c06 Binary files /dev/null and b/assets/rustlang.png differ diff --git a/examples/demo.rs b/examples/demo.rs index b41f1e1..b8cad84 100644 --- a/examples/demo.rs +++ b/examples/demo.rs @@ -35,7 +35,7 @@ use libremarkable::battery; use libremarkable::input::{gpio, multitouch, wacom, InputDevice}; use libremarkable::image; -use libremarkable::image::GenericImage; +use libremarkable::image::{GenericImage, ImageBuffer}; use std::process::Command; @@ -267,7 +267,11 @@ fn on_save_canvas(app: &mut appctx::ApplicationContext, _element: UIElementHandl Err(err) => println!("Failed to dump buffer: {0}", err), Ok(buff) => { let mut hist = SAVED_CANVAS.lock().unwrap(); - *hist = Some(storage::CompressedCanvasState::new(buff)); + *hist = Some(storage::CompressedCanvasState::new( + buff.as_slice(), + CANVAS_REGION.height, + CANVAS_REGION.width, + )); } }; end_bench!(save_canvas); @@ -279,7 +283,10 @@ fn on_zoom_out(app: &mut appctx::ApplicationContext, _element: UIElementHandle) match framebuffer.dump_region(CANVAS_REGION) { Err(err) => println!("Failed to dump buffer: {0}", err), Ok(buff) => { - let resized = image::DynamicImage::ImageLumaA8(buff).resize( + let resized = image::DynamicImage::ImageRgb8( + from_rgb565_to_rgb8(CANVAS_REGION.width, CANVAS_REGION.height, buff.as_slice()) + .unwrap(), + ).resize( (CANVAS_REGION.width as f32 / 1.25f32) as u32, (CANVAS_REGION.height as f32 / 1.25f32) as u32, image::imageops::Nearest, @@ -291,51 +298,66 @@ fn on_zoom_out(app: &mut appctx::ApplicationContext, _element: UIElementHandle) new_image.invert(); // Copy the resized image into the subimage - new_image = image::DynamicImage::ImageLumaA8(new_image.to_luma_alpha()); new_image.copy_from(&resized, CANVAS_REGION.width / 8, CANVAS_REGION.height / 8); - // Restore the new_image but as luma_alpha8. - match framebuffer.restore_region(CANVAS_REGION, &new_image.as_luma_alpha8().unwrap()) { - Err(e) => println!("Error while restoring region: {0}", e), - Ok(_) => { - framebuffer.partial_refresh( - &CANVAS_REGION, - PartialRefreshMode::Async, - waveform_mode::WAVEFORM_MODE_GC16_FAST, - display_temp::TEMP_USE_REMARKABLE_DRAW, - dither_mode::EPDC_FLAG_USE_DITHERING_PASSTHROUGH, - 0, - false, - ); - } - }; + framebuffer.draw_image( + &new_image.as_rgb8().unwrap(), + CANVAS_REGION.top as usize, + CANVAS_REGION.left as usize, + ); + framebuffer.partial_refresh( + &CANVAS_REGION, + PartialRefreshMode::Async, + waveform_mode::WAVEFORM_MODE_GC16_FAST, + display_temp::TEMP_USE_REMARKABLE_DRAW, + dither_mode::EPDC_FLAG_USE_DITHERING_PASSTHROUGH, + 0, + false, + ); } }; end_bench!(zoom_out); } +fn from_rgb565_to_rgb8(w: u32, h: u32, buff: &[u8]) -> Option { + // rgb565 is the input so it is 16bits (2 bytes) per pixel + let input_bytespp = 2; + let input_line_len = w * input_bytespp; + if h * input_line_len != buff.len() as u32 { + return None; + } + Some(ImageBuffer::from_fn(w, h, |x, y| { + let in_index: usize = ((y * input_line_len) + ((input_bytespp * x) as u32)) as usize; + let data = color::NATIVE_COMPONENTS(buff[in_index], buff[in_index + 1]).to_rgb8(); + image::Rgb(data) + })) +} + fn on_blur_canvas(app: &mut appctx::ApplicationContext, _element: UIElementHandle) { start_bench!(stopwatch, blur_canvas); let framebuffer = app.get_framebuffer_ref(); match framebuffer.dump_region(CANVAS_REGION) { Err(err) => println!("Failed to dump buffer: {0}", err), Ok(buff) => { - let mut dynamic = image::DynamicImage::ImageLumaA8(buff); - dynamic = dynamic.blur(0.6f32); - match framebuffer.restore_region(CANVAS_REGION, &dynamic.as_luma_alpha8().unwrap()) { - Err(e) => println!("Error while restoring region: {0}", e), - Ok(_) => { - framebuffer.partial_refresh( - &CANVAS_REGION, - PartialRefreshMode::Async, - waveform_mode::WAVEFORM_MODE_GC16_FAST, - display_temp::TEMP_USE_REMARKABLE_DRAW, - dither_mode::EPDC_FLAG_USE_DITHERING_PASSTHROUGH, - 0, - false, - ); - } - }; + let dynamic = image::DynamicImage::ImageRgb8( + from_rgb565_to_rgb8(CANVAS_REGION.width, CANVAS_REGION.height, buff.as_slice()) + .unwrap(), + ).blur(0.6f32); + + framebuffer.draw_image( + &dynamic.as_rgb8().unwrap(), + CANVAS_REGION.top as usize, + CANVAS_REGION.left as usize, + ); + framebuffer.partial_refresh( + &CANVAS_REGION, + PartialRefreshMode::Async, + waveform_mode::WAVEFORM_MODE_GC16_FAST, + display_temp::TEMP_USE_REMARKABLE_DRAW, + dither_mode::EPDC_FLAG_USE_DITHERING_PASSTHROUGH, + 0, + false, + ); } }; end_bench!(blur_canvas); @@ -567,7 +589,7 @@ fn main() { */ onclick: Some(on_touch_rustlogo), inner: UIElement::Image { - img: image::load_from_memory(include_bytes!("../assets/rustlang.bmp")).unwrap(), + img: image::load_from_memory(include_bytes!("../assets/rustlang.png")).unwrap(), }, ..Default::default() }, @@ -591,6 +613,24 @@ fn main() { }, ); + app.add_element( + "colortest-rgb", + UIElementWrapper { + y: 210, + x: 450, + refresh: UIConstraintRefresh::Refresh, + + onclick: Some(draw_color_test_rgb), + inner: UIElement::Text { + foreground: color::BLACK, + text: "Show RGB Color Test Image".to_owned(), + scale: 35, + border_px: 3, + }, + ..Default::default() + }, + ); + // Zoom Out Button app.add_element( "zoomoutButton", @@ -1010,3 +1050,23 @@ fn main() { app.dispatch_events(true, true, true); clock_thread.join().unwrap(); } + +fn draw_color_test_rgb(app: &mut appctx::ApplicationContext, _element: UIElementHandle) { + let fb = app.get_framebuffer_ref(); + + let img_rgb565 = image::load_from_memory(include_bytes!("../assets/colorspace.png")).unwrap(); + fb.draw_image( + &img_rgb565.as_rgb8().unwrap(), + CANVAS_REGION.top as usize, + CANVAS_REGION.left as usize, + ); + fb.partial_refresh( + &CANVAS_REGION, + PartialRefreshMode::Wait, + waveform_mode::WAVEFORM_MODE_GC16, + display_temp::TEMP_USE_PAPYRUS, + dither_mode::EPDC_FLAG_USE_DITHERING_PASSTHROUGH, + 0, + false, + ); +} diff --git a/src/appctx.rs b/src/appctx.rs index 68b7669..43f2da3 100644 --- a/src/appctx.rs +++ b/src/appctx.rs @@ -257,7 +257,10 @@ impl<'a> ApplicationContext<'a> { refresh: UIConstraintRefresh, ) -> mxcfb_rect { let framebuffer = self.get_framebuffer_ref(); - let draw_area = framebuffer.draw_grayscale_image(&img, y, x); + let draw_area = match img { + image::DynamicImage::ImageRgb8(ref rgb) => framebuffer.draw_image(rgb, y, x), + other => framebuffer.draw_image(&other.to_rgb(), y, x), + }; let marker = match refresh { UIConstraintRefresh::Refresh | UIConstraintRefresh::RefreshAndWait => framebuffer .partial_refresh( diff --git a/src/framebuffer/common.rs b/src/framebuffer/common.rs index 75c67ce..178acc9 100644 --- a/src/framebuffer/common.rs +++ b/src/framebuffer/common.rs @@ -47,7 +47,6 @@ pub enum color { WHITE, NATIVE_COMPONENTS(u8, u8), RGB(u8, u8, u8), - /// 0-255 -- 0 will yield black and 255 will yield white GRAY(u8), } @@ -57,8 +56,25 @@ impl color { color::NATIVE_COMPONENTS(c[0], c[1]) } + pub fn to_rgb565(&self) -> [u8; 2] { + self.as_native() + } + + pub fn to_rgb8(&self) -> [u8; 3] { + // Components reversed because of the device + let components = self.as_native(); + + let mut combined: u16 = (components[1] as u16) << 8; + combined = combined | (components[0] as u16); + + let red = (((combined & 0b1111_1000_0000_0000) >> 11) << 3) as u8; + let green = (((combined & 0b0000_0111_1110_0000) >> 5) << 2) as u8; + let blue = ((combined & 0b0000_0000_0001_1111) << 3) as u8; + + [red, green, blue] + } + pub fn as_native(&self) -> [u8; 2] { - // No need to over-optimize here and return a reference because 4 x u8 (1byte) = 4bytes match self { &color::BLACK => [0x00, 0x00], &color::RED => [0xF8, 0x00], @@ -67,23 +83,21 @@ impl color { &color::WHITE => [0xFF, 0xFF], &color::GRAY(level) => [level, level], &color::NATIVE_COMPONENTS(c1, c2) => [c1, c2], - &color::RGB(r, g, b) => { - // Simply can be referred to as `rgb565_le`. Due to the nature of the display it is a LumaA8. - // - // u8 + u8 for lumaA8 ==> yields 16bits per pixel: + &color::RGB(r8, g8, b8) => { + // Simply can be referred to as `rgb565_le` // // red : offset = 11, length =5, msb_right = 0 // green : offset = 5, length =6, msb_right = 0 // blue : offset = 0, length =5, msb_right = 0 // - let mut red_comp: u16 = r as u16; - red_comp = ((red_comp & 0b11111) << 11).into(); - - let mut out: u16 = red_comp | ((g & 0b111111) << 5) as u16 | (b & 0b11111) as u16; - - let ms: u8 = (out & 0xFF00) as u8; - let ls: u8 = (out & 0x00FF) as u8; - [ms, ls] + let r5 = ((r8 as u16) >> 3) as u8; + let g6 = ((g8 as u16) >> 2) as u8; + let b5 = ((b8 as u16) >> 3) as u8; + + [ + (((g6 & 0b000111) << 5) | b5), + ((r5 << 3) | ((g6 & 0b111000) >> 3)), + ] } } } diff --git a/src/framebuffer/draw.rs b/src/framebuffer/draw.rs index f490528..ec1a747 100644 --- a/src/framebuffer/draw.rs +++ b/src/framebuffer/draw.rs @@ -1,7 +1,6 @@ use std; -use image::DynamicImage; -use image::GenericImage; +use image::RgbImage; use libc; use line_drawing; use rusttype::{point, Scale}; @@ -46,12 +45,12 @@ fn sample_bezier(startpt: (f32, f32), ctrlpt: (f32, f32), endpt: (f32, f32)) -> } impl<'a> framebuffer::FramebufferDraw for core::Framebuffer<'a> { - fn draw_grayscale_image(&mut self, img: &DynamicImage, top: usize, left: usize) -> mxcfb_rect { - for (x, y, pixel) in img.to_luma().enumerate_pixels() { + fn draw_image(&mut self, img: &RgbImage, top: usize, left: usize) -> mxcfb_rect { + for (x, y, pixel) in img.enumerate_pixels() { self.write_pixel( top + y as usize, left + x as usize, - color::GRAY(pixel.data[0]), + color::RGB(pixel.data[0], pixel.data[1], pixel.data[2]), ); } return mxcfb_rect { diff --git a/src/framebuffer/io.rs b/src/framebuffer/io.rs index 6925aee..b609732 100644 --- a/src/framebuffer/io.rs +++ b/src/framebuffer/io.rs @@ -2,8 +2,6 @@ use framebuffer; use framebuffer::common; -use image::{GrayAlphaImage, ImageBuffer}; - impl<'a> framebuffer::FramebufferIO for framebuffer::core::Framebuffer<'a> { fn write_frame(&mut self, frame: &[u8]) { unsafe { @@ -60,7 +58,7 @@ impl<'a> framebuffer::FramebufferIO for framebuffer::core::Framebuffer<'a> { } } - fn dump_region(&self, rect: common::mxcfb_rect) -> Result { + fn dump_region(&self, rect: common::mxcfb_rect) -> Result, &'static str> { if rect.width == 0 || rect.height == 0 { return Err("Unable to dump a region with zero height/width"); } @@ -92,13 +90,14 @@ impl<'a> framebuffer::FramebufferIO for framebuffer::core::Framebuffer<'a> { unsafe { outbuffer.set_len(written); } - return Ok(ImageBuffer::from_raw(rect.width, rect.height, outbuffer).unwrap()); + + return Ok(outbuffer); } fn restore_region( &mut self, rect: common::mxcfb_rect, - data: &GrayAlphaImage, + data: &[u8], ) -> Result { if rect.width == 0 || rect.height == 0 { return Err("Unable to restore a region with zero height/width"); @@ -110,16 +109,24 @@ impl<'a> framebuffer::FramebufferIO for framebuffer::core::Framebuffer<'a> { return Err("Horizontally out of bounds"); } + let bytespp = (self.var_screen_info.bits_per_pixel / 8) as usize; + if data.len() as u32 != rect.width * rect.height * bytespp as u32 { + return Err("Cannot restore region due to mismatched size"); + } + + let line_length = self.fix_screen_info.line_length as u32; + let chunk_size = bytespp * rect.width as usize; + let outbuffer = self.frame.data(); + let inbuffer = data.as_ptr(); let mut written: u32 = 0; for y in 0..rect.height { - for x in 0..rect.width { - self.write_pixel( - (rect.top + y) as usize, - (rect.left + x) as usize, - common::color::from_native(data.get_pixel(x, y).data), - ); - written += 1; + let curr_index = (y + rect.top) * line_length + (bytespp * rect.left as usize) as u32; + unsafe { + outbuffer + .add(curr_index as usize) + .copy_from(inbuffer.add(written as usize), chunk_size); } + written += chunk_size as u32; } return Ok(written); } diff --git a/src/framebuffer/mod.rs b/src/framebuffer/mod.rs index 3703886..44d5387 100644 --- a/src/framebuffer/mod.rs +++ b/src/framebuffer/mod.rs @@ -16,25 +16,22 @@ pub trait FramebufferIO { fn read_pixel(&self, y: usize, x: usize) -> common::color; /// Reads the value at offset `ofst` from the mmapp'ed framebuffer region fn read_offset(&self, ofst: isize) -> u8; - /// Dumps the contents of the specified rectangle into an `image::ImageBuffer, Vec>` - fn dump_region(&self, rect: common::mxcfb_rect) -> Result; - /// Restores into the framebuffer the contents of the specified rectangle from an `image::ImageBuffer, Vec>` + /// Dumps the contents of the specified rectangle into a `Vec` from which + /// you can later create a CompressedCanvasState or pass to restore_region(). + /// The pixel format is rgb565_le. + fn dump_region(&self, rect: common::mxcfb_rect) -> Result, &'static str>; + /// Restores into the framebuffer the contents of the specified rectangle from a u8 slice fn restore_region( &mut self, rect: common::mxcfb_rect, - data: &image::GrayAlphaImage, + data: &[u8], ) -> Result; } pub mod draw; pub trait FramebufferDraw { /// Draws `img` at y=top, x=left coordinates with 1:1 scaling - fn draw_grayscale_image( - &mut self, - img: &image::DynamicImage, - top: usize, - left: usize, - ) -> common::mxcfb_rect; + fn draw_image(&mut self, img: &image::RgbImage, top: usize, left: usize) -> common::mxcfb_rect; /// Draws a straight line fn draw_line( &mut self, diff --git a/src/framebuffer/storage.rs b/src/framebuffer/storage.rs index 2eadc15..2fe9a64 100644 --- a/src/framebuffer/storage.rs +++ b/src/framebuffer/storage.rs @@ -1,8 +1,5 @@ -use zstd; - use std::sync::Arc; - -use image; +use zstd; #[derive(Clone)] pub struct CompressedCanvasState { @@ -27,11 +24,9 @@ pub struct CompressedCanvasState { impl CompressedCanvasState { /// Creates a CompressedCanvasState from the output of FramebufferIO::dump_region(..) /// Consumes the RgbaImage that's provided to it. - pub fn new(img: image::GrayAlphaImage) -> CompressedCanvasState { - let height = img.height(); - let width = img.width(); + pub fn new(img: &[u8], height: u32, width: u32) -> CompressedCanvasState { CompressedCanvasState { - data: zstd::encode_all(&*img.into_vec(), 0).unwrap().into(), + data: zstd::encode_all(img, 0).unwrap().into(), height, width, } @@ -39,8 +34,7 @@ impl CompressedCanvasState { /// Returns an ImageBuffer which can be used to restore the contents of a screen /// region using the FramebufferIO::restore_region(..) - pub fn decompress(&self) -> image::GrayAlphaImage { - let unencoded = zstd::decode_all(&*self.data).unwrap(); - image::GrayAlphaImage::from_raw(self.width, self.height, unencoded).unwrap() + pub fn decompress(&self) -> Vec { + zstd::decode_all(&*self.data).unwrap() } }