diff --git a/README.md b/README.md index 5e7005c..25d5270 100644 --- a/README.md +++ b/README.md @@ -10,7 +10,9 @@ Any 4.*X*.*Y* firmware, with *X* ≥ 6, will do. ## Supported devices -- *Clara BW* +- *Libra Colour*. +- *Clara Colour*. +- *Clara BW*. - *Elipsa 2E*. - *Clara 2E*. - *Libra 2*. diff --git a/contrib/Settings-sample.toml b/contrib/Settings-sample.toml index c553620..61141b5 100644 --- a/contrib/Settings-sample.toml +++ b/contrib/Settings-sample.toml @@ -184,7 +184,7 @@ notify-success = true size = 2 # The current pen color. # Possible values: 0 … 255. -color = 0 +color = { gray = 0 } # Vary the diameter according to the pen's velocity. dynamic = true # The amplitude of the diameter variation when `dynamic` is set. diff --git a/contrib/plato.sh b/contrib/plato.sh index b2ff513..19dbc8e 100755 --- a/contrib/plato.sh +++ b/contrib/plato.sh @@ -57,7 +57,7 @@ if [ -e "$KOBO_TAG" ] ; then # This is a combination of the information given in `FBInk/fbink_device_id.c` # and `calibre/src/calibre/devices/kobo/driver.py`. case "$MODEL_NUMBER" in - 310|320) PRODUCT_ID=0x4163 ;; # Touch A/B, Touch C + 3[12]0) PRODUCT_ID=0x4163 ;; # Touch A/B, Touch C 330) PRODUCT_ID=0x4173 ;; # Glo 340) PRODUCT_ID=0x4183 ;; # Mini 350) PRODUCT_ID=0x4193 ;; # Aura HD @@ -77,7 +77,9 @@ if [ -e "$KOBO_TAG" ] ; then 388) PRODUCT_ID=0x4234 ;; # Libra 2 386) PRODUCT_ID=0x4235 ;; # Clara 2E 389) PRODUCT_ID=0x4236 ;; # Elipsa 2E - 391) PRODUCT_ID=0x4237 ;; # Clara BW + 390) PRODUCT_ID=0x4237 ;; # Libra Colour + 393) PRODUCT_ID=0x4238 ;; # Clara Colour + 391) PRODUCT_ID=0x4239 ;; # Clara BW *) PRODUCT_ID=0x6666 ;; esac @@ -92,11 +94,11 @@ export LD_LIBRARY_PATH="libs:${LD_LIBRARY_PATH}" if [ "$PLATO_SET_FRAMEBUFFER_DEPTH" ] ; then case "${PRODUCT}:${MODEL_NUMBER}" in - spaBW:*|condor:*|goldfinch:*|io:*|cadmus:*|europa:*|storm:*|frost:*|nova:*|snow:378|star:379) - unset ORIG_BPP + kraken:*|pixie:*|dragon:*|phoenix:*|dahlia:*|alyssum:*|pika:*|daylight:*|star:375|snow:374) + ORIG_BPP=$(./bin/utils/fbdepth -g) ;; *) - ORIG_BPP=$(./bin/utils/fbdepth -g) + unset ORIG_BPP ;; esac fi diff --git a/crates/core/src/color.rs b/crates/core/src/color.rs index 09bf244..2a54e27 100644 --- a/crates/core/src/color.rs +++ b/crates/core/src/color.rs @@ -1,39 +1,119 @@ #![allow(unused)] -pub const GRAY00: u8 = 0x00; -pub const GRAY01: u8 = 0x11; -pub const GRAY02: u8 = 0x22; -pub const GRAY03: u8 = 0x33; -pub const GRAY04: u8 = 0x44; -pub const GRAY05: u8 = 0x55; -pub const GRAY06: u8 = 0x66; -pub const GRAY07: u8 = 0x77; -pub const GRAY08: u8 = 0x88; -pub const GRAY09: u8 = 0x99; -pub const GRAY10: u8 = 0xAA; -pub const GRAY11: u8 = 0xBB; -pub const GRAY12: u8 = 0xCC; -pub const GRAY13: u8 = 0xDD; -pub const GRAY14: u8 = 0xEE; -pub const GRAY15: u8 = 0xFF; - -pub const BLACK: u8 = GRAY00; -pub const WHITE: u8 = GRAY15; - -pub const TEXT_NORMAL: [u8; 3] = [WHITE, BLACK, GRAY08]; -pub const TEXT_BUMP_SMALL: [u8; 3] = [GRAY14, BLACK, GRAY07]; -pub const TEXT_BUMP_LARGE: [u8; 3] = [GRAY11, BLACK, BLACK]; - -pub const TEXT_INVERTED_SOFT: [u8; 3] = [GRAY05, WHITE, WHITE]; -pub const TEXT_INVERTED_HARD: [u8; 3] = [BLACK, WHITE, GRAY06]; - -pub const SEPARATOR_NORMAL: u8 = GRAY10; -pub const SEPARATOR_STRONG: u8 = GRAY07; - -pub const KEYBOARD_BG: u8 = GRAY12; -pub const BATTERY_FILL: u8 = GRAY12; -pub const READING_PROGRESS: u8 = GRAY07; - -pub const PROGRESS_FULL: u8 = GRAY05; -pub const PROGRESS_EMPTY: u8 = GRAY13; -pub const PROGRESS_VALUE: u8 = GRAY06; +use serde::{Serialize, Deserialize}; +use crate::geom::lerp; + +#[derive(Debug, Copy, Clone, Eq, PartialEq, Serialize, Deserialize)] +#[serde(rename_all = "kebab-case")] +pub enum Color { + Gray(u8), + Rgb(u8, u8, u8), +} + +impl Color { + pub fn gray(&self) -> u8 { + match *self { + Color::Gray(level) => level, + Color::Rgb(red, green, blue) => { + (red as f32 * 0.2126 + green as f32 * 0.7152 + blue as f32 * 0.0722) as u8 + }, + } + } + + pub fn rgb(&self) -> [u8; 3] { + match *self { + Color::Gray(level) => [level; 3], + Color::Rgb(red, green, blue) => [red, green, blue], + } + } + + pub fn from_rgb(rgb: &[u8]) -> Color { + Color::Rgb(rgb[0], rgb[1], rgb[2]) + } + + pub fn apply(&self, f: F) -> Color where F: Fn(u8) -> u8 { + match *self { + Color::Gray(level) => Color::Gray(f(level)), + Color::Rgb(red, green, blue) => Color::Rgb(f(red), f(green), f(blue)), + } + } + + pub fn lerp(&self, color: Color, alpha: f32) -> Color { + match (*self, color) { + (Color::Gray(l1), Color::Gray(l2)) => Color::Gray(lerp(l1 as f32, l2 as f32, alpha) as u8), + (Color::Rgb(red, green, blue), Color::Gray(level)) => Color::Rgb(lerp(red as f32, level as f32, alpha) as u8, + lerp(green as f32, level as f32, alpha) as u8, + lerp(blue as f32, level as f32, alpha) as u8), + (Color::Gray(level), Color::Rgb(red, green, blue)) => Color::Rgb(lerp(level as f32, red as f32, alpha) as u8, + lerp(level as f32, green as f32, alpha) as u8, + lerp(level as f32, blue as f32, alpha) as u8), + (Color::Rgb(r1, g1, b1), Color::Rgb(r2, g2, b2)) => Color::Rgb(lerp(r1 as f32, r2 as f32, alpha) as u8, + lerp(g1 as f32, g2 as f32, alpha) as u8, + lerp(b1 as f32, b2 as f32, alpha) as u8), + } + } + + pub fn invert(&mut self) { + match self { + Color::Gray(level) => *level = 255 - *level, + Color::Rgb(red, green, blue) => { + *red = 255 - *red; + *green = 255 - *green; + *blue = 255 - *blue; + }, + } + } + + pub fn shift(&mut self, drift: u8) { + match self { + Color::Gray(level) => *level = level.saturating_sub(drift), + Color::Rgb(red, green, blue) => { + *red = red.saturating_sub(drift); + *green = green.saturating_sub(drift); + *blue = blue.saturating_sub(drift); + }, + } + } +} + +macro_rules! gray { + ($a:expr) => ($crate::color::Color::Gray($a)); +} + +pub const GRAY00: Color = gray!(0x00); +pub const GRAY01: Color = gray!(0x11); +pub const GRAY02: Color = gray!(0x22); +pub const GRAY03: Color = gray!(0x33); +pub const GRAY04: Color = gray!(0x44); +pub const GRAY05: Color = gray!(0x55); +pub const GRAY06: Color = gray!(0x66); +pub const GRAY07: Color = gray!(0x77); +pub const GRAY08: Color = gray!(0x88); +pub const GRAY09: Color = gray!(0x99); +pub const GRAY10: Color = gray!(0xAA); +pub const GRAY11: Color = gray!(0xBB); +pub const GRAY12: Color = gray!(0xCC); +pub const GRAY13: Color = gray!(0xDD); +pub const GRAY14: Color = gray!(0xEE); +pub const GRAY15: Color = gray!(0xFF); + +pub const BLACK: Color = GRAY00; +pub const WHITE: Color = GRAY15; + +pub const TEXT_NORMAL: [Color; 3] = [WHITE, BLACK, GRAY08]; +pub const TEXT_BUMP_SMALL: [Color; 3] = [GRAY14, BLACK, GRAY07]; +pub const TEXT_BUMP_LARGE: [Color; 3] = [GRAY11, BLACK, BLACK]; + +pub const TEXT_INVERTED_SOFT: [Color; 3] = [GRAY05, WHITE, WHITE]; +pub const TEXT_INVERTED_HARD: [Color; 3] = [BLACK, WHITE, GRAY06]; + +pub const SEPARATOR_NORMAL: Color = GRAY10; +pub const SEPARATOR_STRONG: Color = GRAY07; + +pub const KEYBOARD_BG: Color = GRAY12; +pub const BATTERY_FILL: Color = GRAY12; +pub const READING_PROGRESS: Color = GRAY07; + +pub const PROGRESS_FULL: Color = GRAY05; +pub const PROGRESS_EMPTY: Color = GRAY13; +pub const PROGRESS_VALUE: Color = GRAY06; diff --git a/crates/core/src/device.rs b/crates/core/src/device.rs index 141dd03..1bc4db1 100644 --- a/crates/core/src/device.rs +++ b/crates/core/src/device.rs @@ -5,6 +5,8 @@ use crate::input::TouchProto; #[derive(Debug, Copy, Clone, Eq, PartialEq)] pub enum Model { + LibraColour, + ClaraColour, ClaraBW, Elipsa2E, Clara2E, @@ -42,6 +44,8 @@ pub enum Orientation { impl fmt::Display for Model { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { match *self { + Model::LibraColour => write!(f, "Libra Colour"), + Model::ClaraColour => write!(f, "Clara Colour"), Model::ClaraBW => write!(f, "Clara BW"), Model::Elipsa2E => write!(f, "Elipsa 2E"), Model::Clara2E => write!(f, "Clara 2E"), @@ -210,6 +214,18 @@ impl Device { dims: (1072, 1448), dpi: 300, }, + "spaColour" => Device { + model: Model::ClaraColour, + proto: TouchProto::MultiB, + dims: (1072, 1448), + dpi: 300, + }, + "monza" => Device { + model: Model::LibraColour, + proto: TouchProto::MultiB, + dims: (1264, 1680), + dpi: 300, + }, _ => Device { model: if model_number == "320" { Model::TouchC } else { Model::TouchAB }, proto: TouchProto::Single, @@ -219,12 +235,15 @@ impl Device { } } + pub fn color_samples(&self) -> usize { + match self.model { + Model::ClaraColour | Model::LibraColour => 3, + _ => 1, + } + } + pub fn frontlight_kind(&self) -> FrontlightKind { match self.model { - Model::AuraONE | - Model::AuraONELimEd | - Model::AuraH2OEd2V1 | - Model::AuraH2OEd2V2 => FrontlightKind::Natural, Model::ClaraHD | Model::Forma | Model::Forma32GB | @@ -233,7 +252,13 @@ impl Device { Model::Libra2 | Model::Clara2E | Model::Elipsa2E | - Model::ClaraBW => FrontlightKind::Premixed, + Model::ClaraBW | + Model::ClaraColour | + Model::LibraColour => FrontlightKind::Premixed, + Model::AuraONE | + Model::AuraONELimEd | + Model::AuraH2OEd2V1 | + Model::AuraH2OEd2V2 => FrontlightKind::Natural, _ => FrontlightKind::Standard, } } @@ -249,14 +274,14 @@ impl Device { pub fn has_gyroscope(&self) -> bool { matches!(self.model, - Model::Forma | Model::Forma32GB | Model::LibraH2O | - Model::Elipsa | Model::Sage | Model::Libra2 | Model::Elipsa2E) + Model::Forma | Model::Forma32GB | Model::LibraH2O | Model::Elipsa | + Model::Sage | Model::Libra2 | Model::Elipsa2E | Model::LibraColour) } pub fn has_page_turn_buttons(&self) -> bool { matches!(self.model, Model::Forma | Model::Forma32GB | Model::LibraH2O | - Model::Sage | Model::Libra2) + Model::Sage | Model::Libra2 | Model::LibraColour) } pub fn has_power_cover(&self) -> bool { @@ -286,7 +311,9 @@ impl Device { pub fn mark(&self) -> u8 { match self.model { - Model::ClaraBW => 12, + Model::LibraColour => 13, + Model::ClaraBW | + Model::ClaraColour => 12, Model::Elipsa2E => 11, Model::Clara2E => 10, Model::Libra2 => 9, @@ -355,7 +382,8 @@ impl Device { Model::LibraH2O => 0, Model::AuraH2OEd2V1 | Model::Forma | Model::Forma32GB | - Model::Sage | Model::Libra2 | Model::Elipsa2E => 1, + Model::Sage | Model::Libra2 | Model::Elipsa2E | + Model::LibraColour => 1, _ => 3, } } @@ -390,7 +418,8 @@ impl Device { Model::LibraH2O => n ^ 1, Model::Libra2 | Model::Sage | - Model::Elipsa2E => (6 - n) % 4, + Model::Elipsa2E | + Model::LibraColour => (6 - n) % 4, Model::Elipsa => (4 - n) % 4, _ => n, } diff --git a/crates/core/src/document/djvu.rs b/crates/core/src/document/djvu.rs index ff96ced..94c18e1 100644 --- a/crates/core/src/document/djvu.rs +++ b/crates/core/src/document/djvu.rs @@ -111,9 +111,9 @@ impl Document for DjvuDocument { unsafe { ddjvu_document_get_pagenum(self.doc) as usize } } - fn pixmap(&mut self, loc: Location, scale: f32) -> Option<(Pixmap, usize)> { + fn pixmap(&mut self, loc: Location, scale: f32, samples: usize) -> Option<(Pixmap, usize)> { let index = self.resolve_location(loc)? as usize; - self.page(index).and_then(|page| page.pixmap(scale)).map(|pixmap| (pixmap, index)) + self.page(index).and_then(|page| page.pixmap(scale, samples)).map(|pixmap| (pixmap, index)) } fn toc(&mut self) -> Option> { @@ -388,7 +388,7 @@ impl DjvuDocument { } impl<'a> DjvuPage<'a> { - pub fn pixmap(&self, scale: f32) -> Option { + pub fn pixmap(&self, scale: f32, samples: usize) -> Option { unsafe { let (width, height) = self.dims(); let rect = DjvuRect { @@ -398,7 +398,12 @@ impl<'a> DjvuPage<'a> { h: (scale * height as f32) as libc::c_uint, }; - let fmt = ddjvu_format_create(DDJVU_FORMAT_GREY8, 0, ptr::null()); + let style = if samples == 1 { + DDJVU_FORMAT_GREY8 + } else { + DDJVU_FORMAT_RGB24 + }; + let fmt = ddjvu_format_create(style, 0, ptr::null()); if fmt.is_null() { return None; @@ -407,7 +412,7 @@ impl<'a> DjvuPage<'a> { ddjvu_format_set_row_order(fmt, 1); ddjvu_format_set_y_direction(fmt, 1); - let len = (rect.w * rect.h) as usize; + let len = samples * (rect.w * rect.h) as usize; let mut data = Vec::new(); if data.try_reserve_exact(len).is_err() { ddjvu_format_release(fmt); @@ -415,9 +420,10 @@ impl<'a> DjvuPage<'a> { } data.resize(len, 0xff); + let row_size = (samples * rect.w as usize) as libc::c_ulong; ddjvu_page_render(self.page, DDJVU_RENDER_COLOR, &rect, &rect, fmt, - rect.w as libc::c_ulong, data.as_mut_ptr()); + row_size, data.as_mut_ptr()); let job = ddjvu_page_job(self.page); @@ -433,6 +439,7 @@ impl<'a> DjvuPage<'a> { Some(Pixmap { width: rect.w as u32, height: rect.h as u32, + samples, data }) } } diff --git a/crates/core/src/document/epub/mod.rs b/crates/core/src/document/epub/mod.rs index c67ae4b..7b56b5c 100644 --- a/crates/core/src/document/epub/mod.rs +++ b/crates/core/src/document/epub/mod.rs @@ -581,7 +581,7 @@ impl EpubDocument { } impl Document for EpubDocument { - fn preview_pixmap(&mut self, width: f32, height: f32) -> Option { + fn preview_pixmap(&mut self, width: f32, height: f32, samples: usize) -> Option { let opener = PdfOpener::new()?; self.cover_image() .map(|path| self.parent.join(path) @@ -592,14 +592,14 @@ impl Document for EpubDocument { .and_then(|mut doc| { doc.dims(0).and_then(|dims| { let scale = (width / dims.0).min(height / dims.1); - doc.pixmap(Location::Exact(0), scale) + doc.pixmap(Location::Exact(0), scale, samples) }) }) }) .or_else(|| { self.dims(0).and_then(|dims| { let scale = (width / dims.0).min(height / dims.1); - self.pixmap(Location::Exact(0), scale) + self.pixmap(Location::Exact(0), scale, samples) }) }) .map(|(pixmap, _)| pixmap) @@ -854,7 +854,7 @@ impl Document for EpubDocument { }) } - fn pixmap(&mut self, loc: Location, scale: f32) -> Option<(Pixmap, usize)> { + fn pixmap(&mut self, loc: Location, scale: f32, samples: usize) -> Option<(Pixmap, usize)> { if self.spine.is_empty() { return None; } @@ -865,7 +865,7 @@ impl Document for EpubDocument { let page_index = self.page_index(offset, index, start_offset)?; let page = self.cache.get(&index)?.get(page_index)?.clone(); - let pixmap = self.engine.render_page(&page, scale, &mut self.archive)?; + let pixmap = self.engine.render_page(&page, scale, samples, &mut self.archive)?; Some((pixmap, offset)) } diff --git a/crates/core/src/document/html/engine.rs b/crates/core/src/document/html/engine.rs index e592363..ed99e0d 100644 --- a/crates/core/src/document/html/engine.rs +++ b/crates/core/src/document/html/engine.rs @@ -1669,10 +1669,10 @@ impl Engine { merged_items } - pub fn render_page(&mut self, page: &[DrawCommand], scale_factor: f32, resource_fetcher: &mut dyn ResourceFetcher) -> Option { + pub fn render_page(&mut self, page: &[DrawCommand], scale_factor: f32, samples: usize, resource_fetcher: &mut dyn ResourceFetcher) -> Option { let width = (self.dims.0 as f32 * scale_factor) as u32; let height = (self.dims.1 as f32 * scale_factor) as u32; - let mut fb = Pixmap::try_new(width, height)?; + let mut fb = Pixmap::try_new(width, height, samples)?; for dc in page { match dc { @@ -1693,7 +1693,7 @@ impl Engine { if let Some((pixmap, _)) = PdfOpener::new().and_then(|opener| { opener.open_memory(path, &buf) }).and_then(|mut doc| { - doc.pixmap(Location::Exact(0), scale_factor * *scale) + doc.pixmap(Location::Exact(0), scale_factor * *scale, samples) }) { let position = Point::from(scale_factor * Vec2::from(*position)); fb.draw_pixmap(&pixmap, position); diff --git a/crates/core/src/document/html/layout.rs b/crates/core/src/document/html/layout.rs index 0941cad..59c9aee 100644 --- a/crates/core/src/document/html/layout.rs +++ b/crates/core/src/document/html/layout.rs @@ -4,6 +4,7 @@ use std::fmt::Debug; use fxhash::FxHashMap; use lazy_static::lazy_static; use kl_hyphenate::{Standard, Language, Load}; +use crate::color::Color; use crate::geom::{Point, Rectangle, Edge}; use crate::font::{FontFamily, Font, RenderPlan}; pub use crate::metadata::TextAlign; @@ -63,7 +64,7 @@ pub struct StyleData { pub font_weight: FontWeight, pub font_size: f32, pub font_features: Option>, - pub color: u8, + pub color: Color, pub letter_spacing: i32, pub word_spacing: WordSpacing, pub vertical_align: i32, @@ -334,7 +335,7 @@ pub struct TextElement { pub font_size: u32, pub letter_spacing: i32, pub vertical_align: i32, - pub color: u8, + pub color: Color, pub uri: Option, } @@ -370,7 +371,7 @@ pub struct TextCommand { pub font_style: FontStyle, pub font_weight: FontWeight, pub font_size: u32, - pub color: u8, + pub color: Color, pub uri: Option, pub rect: Rectangle, } diff --git a/crates/core/src/document/html/mod.rs b/crates/core/src/document/html/mod.rs index c67e61a..6f3804a 100644 --- a/crates/core/src/document/html/mod.rs +++ b/crates/core/src/document/html/mod.rs @@ -372,11 +372,11 @@ impl Document for HtmlDocument { }).collect(), offset)) } - fn pixmap(&mut self, loc: Location, scale: f32) -> Option<(Pixmap, usize)> { + fn pixmap(&mut self, loc: Location, scale: f32, samples: usize) -> Option<(Pixmap, usize)> { let offset = self.resolve_location(loc)?; let page_index = self.page_index(offset)?; let page = self.pages[page_index].clone(); - let pixmap = self.engine.render_page(&page, scale, &mut self.parent)?; + let pixmap = self.engine.render_page(&page, scale, samples, &mut self.parent)?; Some((pixmap, offset)) } diff --git a/crates/core/src/document/html/parse.rs b/crates/core/src/document/html/parse.rs index 2f9bfef..3dd185e 100644 --- a/crates/core/src/document/html/parse.rs +++ b/crates/core/src/document/html/parse.rs @@ -4,6 +4,7 @@ use super::layout::{FontKind, FontStyle, FontWeight, WordSpacing}; use super::layout::{TextAlign, Display, Float, ListStyleType}; use super::layout::{InlineMaterial, GlueMaterial, PenaltyMaterial}; use crate::geom::Edge; +use crate::color::{Color, BLACK, WHITE}; use crate::unit::{pt_to_px, pc_to_px, mm_to_px, in_to_px}; use crate::unit::{POINTS_PER_INCH, PICAS_PER_INCH, MILLIMETERS_PER_INCH, CENTIMETERS_PER_INCH}; @@ -337,7 +338,7 @@ pub fn parse_font_variant(value: &str) -> Vec { features.into_iter().map(String::from).collect() } -pub fn parse_color(value: &str) -> Option { +pub fn parse_color(value: &str) -> Option { if value.starts_with('#') { if value.len() < 4 { return None; @@ -346,12 +347,12 @@ pub fn parse_color(value: &str) -> Option { let red = u8::from_str_radix(&value[1..=chunk_size].repeat(3 - chunk_size), 16).ok()?; let green = u8::from_str_radix(&value[chunk_size+1..=2*chunk_size].repeat(3 - chunk_size), 16).ok()?; let blue = u8::from_str_radix(&value[2*chunk_size+1..=3*chunk_size].repeat(3 - chunk_size), 16).ok()?; - let color = luma(red as f32, green as f32, blue as f32) as u8; + let color = Color::from_rgb(&[red, green, blue]); Some(color) } else { match value { - "black" => Some(0), - "white" => Some(255), + "black" => Some(BLACK), + "white" => Some(WHITE), "gray" | "grey" => parse_color("#888"), "silver" => parse_color("#c0c0c0"), "red" => parse_color("#f00"), @@ -372,11 +373,6 @@ pub fn parse_color(value: &str) -> Option { } } -#[inline] -fn luma(r: f32, g: f32, b: f32) -> f32 { - r * 0.2126 + g * 0.7152 + b * 0.0722 -} - #[cfg(test)] mod tests { use super::*; @@ -388,10 +384,10 @@ mod tests { let c = parse_color("#0f0"); let d = parse_color("#00f"); let e = parse_color("#fff"); - assert_eq!(a, Some(0)); - assert_eq!(b, Some(54)); - assert_eq!(c, Some(182)); - assert_eq!(d, Some(18)); - assert_eq!(e, Some(255)); + assert_eq!(a, Some(Color::Rgb(0, 0, 0))); + assert_eq!(b, Some(Color::Rgb(255, 0, 0))); + assert_eq!(c, Some(Color::Rgb(0, 255, 0))); + assert_eq!(d, Some(Color::Rgb(0, 0, 255))); + assert_eq!(e, Some(Color::Rgb(255, 255, 255))); } } diff --git a/crates/core/src/document/mod.rs b/crates/core/src/document/mod.rs index 47071fd..f59c1de 100644 --- a/crates/core/src/document/mod.rs +++ b/crates/core/src/document/mod.rs @@ -101,7 +101,7 @@ pub trait Document: Send+Sync { fn links(&mut self, loc: Location) -> Option<(Vec, usize)>; fn images(&mut self, loc: Location) -> Option<(Vec, usize)>; - fn pixmap(&mut self, loc: Location, scale: f32) -> Option<(Pixmap, usize)>; + fn pixmap(&mut self, loc: Location, scale: f32, samples: usize) -> Option<(Pixmap, usize)>; fn layout(&mut self, width: u32, height: u32, font_size: f32, dpi: u16); fn set_font_family(&mut self, family_name: &str, search_path: &str); fn set_margin_width(&mut self, width: i32); @@ -125,10 +125,10 @@ pub trait Document: Send+Sync { Err(format_err!("this document can't be saved")) } - fn preview_pixmap(&mut self, width: f32, height: f32) -> Option { + fn preview_pixmap(&mut self, width: f32, height: f32, samples: usize) -> Option { self.dims(0).and_then(|dims| { let scale = (width / dims.0).min(height / dims.1); - self.pixmap(Location::Exact(0), scale) + self.pixmap(Location::Exact(0), scale, samples) }).map(|(pixmap, _)| pixmap) } diff --git a/crates/core/src/document/pdf.rs b/crates/core/src/document/pdf.rs index e7ce642..1052904 100644 --- a/crates/core/src/document/pdf.rs +++ b/crates/core/src/document/pdf.rs @@ -209,9 +209,9 @@ impl Document for PdfDocument { } } - fn pixmap(&mut self, loc: Location, scale: f32) -> Option<(Pixmap, usize)> { + fn pixmap(&mut self, loc: Location, scale: f32, samples: usize) -> Option<(Pixmap, usize)> { let index = self.resolve_location(loc)?; - self.page(index).and_then(|page| page.pixmap(scale)).map(|pixmap| (pixmap, index)) + self.page(index).and_then(|page| page.pixmap(scale, samples)).map(|pixmap| (pixmap, index)) } fn toc(&mut self) -> Option> { @@ -467,13 +467,18 @@ impl<'a> PdfPage<'a> { } } - pub fn pixmap(&self, scale: f32) -> Option { + pub fn pixmap(&self, scale: f32, color_samples: usize) -> Option { unsafe { let mat = fz_scale(scale as libc::c_float, scale as libc::c_float); + let color_space = if color_samples == 1 { + fz_device_gray(self.ctx.0) + } else { + fz_device_rgb(self.ctx.0) + }; let pixmap = mp_new_pixmap_from_page(self.ctx.0, self.page, mat, - fz_device_gray(self.ctx.0), + color_space, 0); if pixmap.is_null() { return None; @@ -481,18 +486,18 @@ impl<'a> PdfPage<'a> { let width = (*pixmap).w as u32; let height = (*pixmap).h as u32; - let len = (width * height) as usize; - let samples = slice::from_raw_parts((*pixmap).samples, len); + let len = color_samples * (width * height) as usize; + let pixmap_data = slice::from_raw_parts((*pixmap).samples, len); let mut data = Vec::new(); if data.try_reserve(len).is_err() { fz_drop_pixmap(self.ctx.0, pixmap); return None; } - data.extend(samples); + data.extend(pixmap_data); fz_drop_pixmap(self.ctx.0, pixmap); - Some(Pixmap { width, height, data }) + Some(Pixmap { width, height, samples: color_samples, data }) } } diff --git a/crates/core/src/font/mod.rs b/crates/core/src/font/mod.rs index 792bedd..45eca13 100644 --- a/crates/core/src/font/mod.rs +++ b/crates/core/src/font/mod.rs @@ -19,6 +19,7 @@ use anyhow::{Error, format_err}; use thiserror::Error; use globset::Glob; use walkdir::WalkDir; +use crate::color::Color; use crate::geom::{Point, Vec2}; use crate::helpers::IsHidden; use crate::framebuffer::Framebuffer; @@ -1510,7 +1511,7 @@ impl Font { (i, width) } - pub fn render(&mut self, fb: &mut dyn Framebuffer, color: u8, render_plan: &RenderPlan, origin: Point) { + pub fn render(&mut self, fb: &mut dyn Framebuffer, color: Color, render_plan: &RenderPlan, origin: Point) { unsafe { let mut pos = origin; let mut fallback_faces = FxHashMap::default(); diff --git a/crates/core/src/framebuffer/image.rs b/crates/core/src/framebuffer/image.rs index c17b54c..77a1080 100644 --- a/crates/core/src/framebuffer/image.rs +++ b/crates/core/src/framebuffer/image.rs @@ -2,42 +2,46 @@ use std::fs::File; use std::path::Path; use anyhow::{Error, Context, format_err}; use super::{Framebuffer, UpdateMode}; -use crate::color::WHITE; +use crate::color::{Color, WHITE}; use crate::geom::{Rectangle, lerp}; #[derive(Debug, Clone)] pub struct Pixmap { pub width: u32, pub height: u32, + pub samples: usize, pub data: Vec, } impl Pixmap { - pub fn new(width: u32, height: u32) -> Pixmap { - let len = (width * height) as usize; + pub fn new(width: u32, height: u32, samples: usize) -> Pixmap { + let len = samples * (width * height) as usize; Pixmap { width, height, - data: vec![WHITE; len], + samples, + data: vec![WHITE.gray(); len], } } - pub fn try_new(width: u32, height: u32) -> Option { + pub fn try_new(width: u32, height: u32, samples: usize) -> Option { let mut data = Vec::new(); - let len = (width * height) as usize; + let len = samples * (width * height) as usize; data.try_reserve_exact(len).ok()?; - data.resize(len, WHITE); + data.resize(len, WHITE.gray()); Some(Pixmap { width, height, + samples, data, }) } - pub fn empty(width: u32, height: u32) -> Pixmap { + pub fn empty(width: u32, height: u32, samples: usize) -> Pixmap { Pixmap { width, height, + samples, data: Vec::new(), } } @@ -55,34 +59,43 @@ impl Pixmap { let decoder = png::Decoder::new(file); let mut reader = decoder.read_info()?; let info = reader.info(); - let mut pixmap = Pixmap::new(info.width, info.height); + let mut pixmap = Pixmap::new(info.width, info.height, info.color_type.samples()); reader.next_frame(pixmap.data_mut())?; Ok(pixmap) } #[inline] - pub fn get_pixel(&self, x: u32, y: u32) -> u8 { + pub fn get_pixel(&self, x: u32, y: u32) -> Color { if self.data.is_empty() { return WHITE; } - let addr = (y * self.width + x) as usize; - self.data[addr] + let addr = self.samples * (y * self.width + x) as usize; + if self.samples == 1 { + Color::Gray(self.data[addr]) + } else { + Color::from_rgb(&self.data[addr..addr+3]) + } } } impl Framebuffer for Pixmap { - fn set_pixel(&mut self, x: u32, y: u32, color: u8) { + fn set_pixel(&mut self, x: u32, y: u32, color: Color) { if x >= self.width || y >= self.height { return; } if self.data.is_empty() { return; } - let addr = (y * self.width + x) as usize; - self.data[addr] = color; + let addr = self.samples * (y * self.width + x) as usize; + if self.samples == 1 { + self.data[addr] = color.gray(); + } else { + let rgb = color.rgb(); + self.data[addr..addr+3].copy_from_slice(&rgb); + } } - fn set_blended_pixel(&mut self, x: u32, y: u32, color: u8, alpha: f32) { + fn set_blended_pixel(&mut self, x: u32, y: u32, color: Color, alpha: f32) { if alpha >= 1.0 { self.set_pixel(x, y, color); return; @@ -93,9 +106,15 @@ impl Framebuffer for Pixmap { if self.data.is_empty() { return; } - let addr = (y * self.width + x) as usize; - let blended_color = lerp(self.data[addr] as f32, color as f32, alpha) as u8; - self.data[addr] = blended_color; + let addr = self.samples * (y * self.width + x) as usize; + if self.samples == 1 { + self.data[addr] = lerp(self.data[addr] as f32, color.gray() as f32, alpha) as u8; + } else { + let rgb = color.rgb(); + for (i, c) in self.data[addr..addr+3].iter_mut().enumerate() { + *c = lerp(*c as f32, rgb[i] as f32, alpha) as u8; + } + } } fn invert_region(&mut self, rect: &Rectangle) { @@ -104,9 +123,14 @@ impl Framebuffer for Pixmap { } for y in rect.min.y..rect.max.y { for x in rect.min.x..rect.max.x { - let addr = (y * self.width as i32 + x) as usize; - let color = 255 - self.data[addr]; - self.data[addr] = color; + let addr = self.samples * (y * self.width as i32 + x) as usize; + if self.samples == 1 { + self.data[addr] = 255 - self.data[addr]; + } else { + for c in self.data[addr..addr+3].iter_mut() { + *c = 255 - *c; + } + } } } } @@ -117,9 +141,14 @@ impl Framebuffer for Pixmap { } for y in rect.min.y..rect.max.y { for x in rect.min.x..rect.max.x { - let addr = (y * self.width as i32 + x) as usize; - let color = self.data[addr].saturating_sub(drift); - self.data[addr] = color; + let addr = self.samples * (y * self.width as i32 + x) as usize; + if self.samples == 1 { + self.data[addr] = self.data[addr].saturating_sub(drift); + } else { + for c in self.data[addr..addr+3].iter_mut() { + *c = c.saturating_sub(drift); + } + } } } } @@ -140,7 +169,7 @@ impl Framebuffer for Pixmap { let file = File::create(path).with_context(|| format!("can't create output file {}", path))?; let mut encoder = png::Encoder::new(file, width, height); encoder.set_depth(png::BitDepth::Eight); - encoder.set_color(png::ColorType::Grayscale); + encoder.set_color(if self.samples == 3 { png::ColorType::Rgb } else { png::ColorType::Grayscale }); let mut writer = encoder.write_header().with_context(|| format!("can't write PNG header for {}", path))?; writer.write_image_data(&self.data).with_context(|| format!("can't write PNG data to {}", path))?; Ok(()) diff --git a/crates/core/src/framebuffer/kobo1.rs b/crates/core/src/framebuffer/kobo1.rs index d296337..51bd5bc 100644 --- a/crates/core/src/framebuffer/kobo1.rs +++ b/crates/core/src/framebuffer/kobo1.rs @@ -6,6 +6,7 @@ use std::slice; use std::os::unix::io::AsRawFd; use std::ops::Drop; use anyhow::{Error, Context}; +use crate::color::Color; use crate::geom::Rectangle; use crate::device::{CURRENT_DEVICE, Model}; use super::{UpdateMode, Framebuffer}; @@ -41,6 +42,9 @@ pub struct KoboFramebuffer1 { set_pixel_rgb: SetPixelRgb, get_pixel_rgb: GetPixelRgb, as_rgb: AsRgb, + red_index: usize, + green_index: usize, + blue_index: usize, bytes_per_pixel: u8, var_info: VarScreenInfo, fix_info: FixScreenInfo, @@ -77,6 +81,7 @@ impl KoboFramebuffer1 { } else { (set_pixel_rgb_8, get_pixel_rgb_8, as_rgb_8) }; + let red_index = if var_info.red.offset > 0 { 2 } else { 0 }; Ok(KoboFramebuffer1 { file, frame, @@ -90,6 +95,9 @@ impl KoboFramebuffer1 { set_pixel_rgb, get_pixel_rgb, as_rgb, + red_index, + green_index: 1, + blue_index: 2 - red_index, bytes_per_pixel: bytes_per_pixel as u8, var_info, fix_info, @@ -103,29 +111,28 @@ impl KoboFramebuffer1 { } impl Framebuffer for KoboFramebuffer1 { - fn set_pixel(&mut self, x: u32, y: u32, color: u8) { + fn set_pixel(&mut self, x: u32, y: u32, color: Color) { let c = (self.transform)(x, y, color); - (self.set_pixel_rgb)(self, x, y, [c, c, c]); + (self.set_pixel_rgb)(self, x, y, c.rgb()); } - fn set_blended_pixel(&mut self, x: u32, y: u32, color: u8, alpha: f32) { + fn set_blended_pixel(&mut self, x: u32, y: u32, color: Color, alpha: f32) { if alpha >= 1.0 { self.set_pixel(x, y, color); return; } - let rgb = (self.get_pixel_rgb)(self, x, y); - let color_alpha = color as f32 * alpha; - let interp = (color_alpha + (1.0 - alpha) * rgb[0] as f32) as u8; + let background = Color::from_rgb(&(self.get_pixel_rgb)(self, x, y)); + let interp = background.lerp(color, alpha); let c = (self.transform)(x, y, interp); - (self.set_pixel_rgb)(self, x, y, [c, c, c]); + (self.set_pixel_rgb)(self, x, y, c.rgb()); } fn invert_region(&mut self, rect: &Rectangle) { for y in rect.min.y..rect.max.y { for x in rect.min.x..rect.max.x { let rgb = (self.get_pixel_rgb)(self, x as u32, y as u32); - let color = 255 - rgb[0]; - (self.set_pixel_rgb)(self, x as u32, y as u32, [color, color, color]); + let color = [255 - rgb[0], 255 - rgb[1], 255 - rgb[2]]; + (self.set_pixel_rgb)(self, x as u32, y as u32, color); } } } @@ -134,8 +141,8 @@ impl Framebuffer for KoboFramebuffer1 { for y in rect.min.y..rect.max.y { for x in rect.min.x..rect.max.x { let rgb = (self.get_pixel_rgb)(self, x as u32, y as u32); - let color = rgb[0].saturating_sub(drift); - (self.set_pixel_rgb)(self, x as u32, y as u32, [color, color, color]); + let color = [rgb[0].saturating_sub(drift), rgb[1].saturating_sub(drift), rgb[2].saturating_sub(drift)]; + (self.set_pixel_rgb)(self, x as u32, y as u32, color); } } } @@ -144,6 +151,7 @@ impl Framebuffer for KoboFramebuffer1 { fn update(&mut self, rect: &Rectangle, mode: UpdateMode) -> Result { let update_marker = self.token; let mark = CURRENT_DEVICE.mark(); + let color_samples = CURRENT_DEVICE.color_samples(); let mut flags = self.flags; let mut monochrome = self.monochrome; let mut dithered = self.dithered; @@ -151,7 +159,9 @@ impl Framebuffer for KoboFramebuffer1 { let (update_mode, mut waveform_mode) = match mode { UpdateMode::Gui => (UPDATE_MODE_PARTIAL, WAVEFORM_MODE_AUTO), UpdateMode::Partial => { - if mark >= 11 { + if mark >= 12 && color_samples > 1 && dithered { + (UPDATE_MODE_FULL, HWTCON_WAVEFORM_MODE_GLRC16) + } else if mark >= 11 { (UPDATE_MODE_PARTIAL, HWTCON_WAVEFORM_MODE_GLR16) } else if mark >= 7 { (UPDATE_MODE_PARTIAL, NTX_WFM_MODE_GLR16) @@ -164,7 +174,11 @@ impl Framebuffer for KoboFramebuffer1 { }, UpdateMode::Full => { monochrome = false; - (UPDATE_MODE_FULL, NTX_WFM_MODE_GC16) + if mark >= 12 && color_samples > 1 && dithered { + (UPDATE_MODE_FULL, HWTCON_WAVEFORM_MODE_GCC16) + } else { + (UPDATE_MODE_FULL, NTX_WFM_MODE_GC16) + } }, UpdateMode::Fast => { if mark >= 11 { @@ -202,7 +216,7 @@ impl Framebuffer for KoboFramebuffer1 { if self.inverted { if mark >= 11 { - if waveform_mode == HWTCON_WAVEFORM_MODE_GL16 { + if waveform_mode == HWTCON_WAVEFORM_MODE_GLR16 { waveform_mode = HWTCON_WAVEFORM_MODE_GLKW16; } else if waveform_mode == NTX_WFM_MODE_GC16 { waveform_mode = HWTCON_WAVEFORM_MODE_GCK16; @@ -464,10 +478,10 @@ fn set_pixel_rgb_32(fb: &mut KoboFramebuffer1, x: u32, y: u32, rgb: [u8; 3]) { unsafe { let spot = fb.frame.offset(addr) as *mut u8; - *spot.offset(0) = rgb[2]; - *spot.offset(1) = rgb[1]; - *spot.offset(2) = rgb[0]; - // *spot.offset(3) = 0x00; + *spot.offset(0) = rgb[fb.red_index]; + *spot.offset(1) = rgb[fb.green_index]; + *spot.offset(2) = rgb[fb.blue_index]; + // *spot.offset(3) = 0xFF; } } @@ -496,7 +510,9 @@ fn get_pixel_rgb_32(fb: &KoboFramebuffer1, x: u32, y: u32) -> [u8; 3] { (fb.var_info.yoffset as isize + y as isize) * (fb.fix_info.line_length as isize); unsafe { let spot = fb.frame.offset(addr) as *mut u8; - [*spot.offset(2), *spot.offset(1), *spot.offset(0)] + [*spot.offset(fb.red_index as isize), + *spot.offset(fb.green_index as isize), + *spot.offset(fb.blue_index as isize)] } } @@ -530,13 +546,13 @@ fn as_rgb_16(fb: &KoboFramebuffer1) -> Vec { fn as_rgb_32(fb: &KoboFramebuffer1) -> Vec { let (width, height) = fb.dims(); let mut rgb888 = Vec::with_capacity((width * height * 3) as usize); - let bgra8888 = fb.as_bytes(); + let data = fb.as_bytes(); let virtual_width = fb.var_info.xres_virtual as usize; - for (_, bgra) in bgra8888.chunks(4).take(height as usize * virtual_width).enumerate() - .filter(|&(i, _)| i % virtual_width < width as usize) { - let red = bgra[2]; - let green = bgra[1]; - let blue = bgra[0]; + for (_, color) in data.chunks(4).take(height as usize * virtual_width).enumerate() + .filter(|&(i, _)| i % virtual_width < width as usize) { + let red = color[fb.red_index]; + let green = color[fb.green_index]; + let blue = color[fb.blue_index]; rgb888.extend_from_slice(&[red, green, blue]); } rgb888 diff --git a/crates/core/src/framebuffer/kobo2.rs b/crates/core/src/framebuffer/kobo2.rs index bec31af..f6483f2 100644 --- a/crates/core/src/framebuffer/kobo2.rs +++ b/crates/core/src/framebuffer/kobo2.rs @@ -8,6 +8,7 @@ use std::fs::File; use std::os::unix::io::AsRawFd; use std::ops::Drop; use anyhow::{Error, Context}; +use crate::color::Color; use crate::geom::Rectangle; use crate::device::CURRENT_DEVICE; use super::{UpdateMode, Framebuffer}; @@ -220,36 +221,35 @@ impl KoboFramebuffer2 { unsafe { slice::from_raw_parts(self.frame as *const u8, self.frame_size) } } - fn get_pixel(&self, x: u32, y: u32) -> u8 { + fn get_pixel(&self, x: u32, y: u32) -> Color { let addr = (x + y * self.fix_info.line_length) as isize; let c = unsafe { *(self.frame.offset(addr) as *const u8) }; - if self.inverted { + Color::Gray(if self.inverted { 255 - c } else { c - } + }) } } impl Framebuffer for KoboFramebuffer2 { - fn set_pixel(&mut self, x: u32, y: u32, color: u8) { + fn set_pixel(&mut self, x: u32, y: u32, color: Color) { let mut c = (self.transform)(x, y, color); if self.inverted { - c = 255 - c; + c.invert(); } let addr = (x + y * self.fix_info.line_length) as isize; let spot = unsafe { self.frame.offset(addr) as *mut u8 }; - unsafe { *spot = c }; + unsafe { *spot = c.gray() }; } - fn set_blended_pixel(&mut self, x: u32, y: u32, color: u8, alpha: f32) { + fn set_blended_pixel(&mut self, x: u32, y: u32, color: Color, alpha: f32) { if alpha >= 1.0 { self.set_pixel(x, y, color); return; } - let cur = self.get_pixel(x, y); - let color_alpha = color as f32 * alpha; - let interp = (color_alpha + (1.0 - alpha) * cur as f32) as u8; + let background = self.get_pixel(x, y); + let interp = background.lerp(color, alpha); let c = (self.transform)(x, y, interp); self.set_pixel(x, y, c); } @@ -257,8 +257,8 @@ impl Framebuffer for KoboFramebuffer2 { fn invert_region(&mut self, rect: &Rectangle) { for y in rect.min.y..rect.max.y { for x in rect.min.x..rect.max.x { - let cur = self.get_pixel(x as u32, y as u32); - let color = 255 - cur; + let mut color = self.get_pixel(x as u32, y as u32); + color.invert(); self.set_pixel(x as u32, y as u32, color); } } @@ -267,8 +267,8 @@ impl Framebuffer for KoboFramebuffer2 { fn shift_region(&mut self, rect: &Rectangle, drift: u8) { for y in rect.min.y..rect.max.y { for x in rect.min.x..rect.max.x { - let cur = self.get_pixel(x as u32, y as u32); - let color = cur.saturating_sub(drift); + let mut color = self.get_pixel(x as u32, y as u32); + color.shift(drift); self.set_pixel(x as u32, y as u32, color); } } diff --git a/crates/core/src/framebuffer/mod.rs b/crates/core/src/framebuffer/mod.rs index fa1b34d..8e0de94 100644 --- a/crates/core/src/framebuffer/mod.rs +++ b/crates/core/src/framebuffer/mod.rs @@ -10,7 +10,7 @@ mod kobo2; use anyhow::Error; use crate::geom::{Point, Rectangle, surface_area, nearest_segment_point, lerp}; use crate::geom::{CornerSpec, BorderSpec, ColorSource, Vec2}; -use crate::color::{BLACK, WHITE}; +use crate::color::{Color, BLACK, WHITE}; pub use self::kobo1::KoboFramebuffer1; pub use self::kobo2::KoboFramebuffer2; @@ -32,8 +32,8 @@ pub enum UpdateMode { } pub trait Framebuffer { - fn set_pixel(&mut self, x: u32, y: u32, color: u8); - fn set_blended_pixel(&mut self, x: u32, y: u32, color: u8, alpha: f32); + fn set_pixel(&mut self, x: u32, y: u32, color: Color); + fn set_blended_pixel(&mut self, x: u32, y: u32, color: Color, alpha: f32); fn invert_region(&mut self, rect: &Rectangle); fn shift_region(&mut self, rect: &Rectangle, drift: u8); fn update(&mut self, rect: &Rectangle, mode: UpdateMode) -> Result; @@ -74,12 +74,12 @@ pub trait Framebuffer { rect![0, 0, width as i32, height as i32] } - fn clear(&mut self, color: u8) { + fn clear(&mut self, color: Color) { let rect = self.rect(); self.draw_rectangle(&rect, color); } - fn draw_rectangle(&mut self, rect: &Rectangle, color: u8) { + fn draw_rectangle(&mut self, rect: &Rectangle, color: Color) { for y in rect.min.y..rect.max.y { for x in rect.min.x..rect.max.x { self.set_pixel(x as u32, y as u32, color); @@ -87,7 +87,7 @@ pub trait Framebuffer { } } - fn draw_blended_rectangle(&mut self, rect: &Rectangle, color: u8, alpha: f32) { + fn draw_blended_rectangle(&mut self, rect: &Rectangle, color: Color, alpha: f32) { for y in rect.min.y..rect.max.y { for x in rect.min.x..rect.max.x { self.set_blended_pixel(x as u32, y as u32, color, alpha); @@ -148,14 +148,17 @@ pub trait Framebuffer { for x in rect.min.x..rect.max.x { let px = x - rect.min.x + pt.x; let py = y - rect.min.y + pt.y; - let raw_color = pixmap.get_pixel(x as u32, y as u32) as f32; - let color = if raw_color < gray { - (gray * (raw_color / gray).powf(exponent)) as u8 - } else if raw_color > gray { - (gray + rem_gray * ((raw_color - gray) / rem_gray).powf(inv_exponent)) as u8 - } else { - gray as u8 - }; + let raw_color = pixmap.get_pixel(x as u32, y as u32); + let color = raw_color.apply(|comp| { + let c = comp as f32; + if c < gray { + (gray * (c / gray).powf(exponent)) as u8 + } else if c > gray { + (gray + rem_gray * ((c - gray) / rem_gray).powf(inv_exponent)) as u8 + } else { + gray as u8 + } + }); self.set_pixel(px as u32, py as u32, color); } } @@ -179,18 +182,18 @@ pub trait Framebuffer { } } - fn draw_blended_pixmap(&mut self, pixmap: &Pixmap, pt: Point, color: u8) { + fn draw_blended_pixmap(&mut self, pixmap: &Pixmap, pt: Point, color: Color) { for y in 0..pixmap.height { for x in 0..pixmap.width { let px = x + pt.x as u32; let py = y + pt.y as u32; - let alpha = (255.0 - pixmap.get_pixel(x, y) as f32) / 255.0; + let alpha = (255.0 - pixmap.get_pixel(x, y).gray() as f32) / 255.0; self.set_blended_pixel(px as u32, py as u32, color, alpha); } } } - fn draw_rounded_rectangle(&mut self, rect: &Rectangle, corners: &CornerSpec, color: u8) { + fn draw_rounded_rectangle(&mut self, rect: &Rectangle, corners: &CornerSpec, color: Color) { let (nw, ne, se, sw) = match *corners { CornerSpec::Uniform(v) => (v, v, v, v), CornerSpec::North(v) => (v, v, 0, 0), @@ -277,7 +280,7 @@ pub trait Framebuffer { if dist < mid_radius { let delta_dist = small_radius as f32 - dist; alpha = surface_area(delta_dist, angle); - color = lerp(color as f32, border_color as f32, alpha) as u8; + color = color.lerp(border_color, alpha); alpha = 1.0; } else { let delta_dist = dist - radius as f32; @@ -295,7 +298,7 @@ pub trait Framebuffer { } } - fn draw_triangle(&mut self, triangle: &[Point], color: u8) { + fn draw_triangle(&mut self, triangle: &[Point], color: Color) { let mut x_min = ::std::i32::MAX; let mut x_max = ::std::i32::MIN; let mut y_min = ::std::i32::MAX; @@ -363,7 +366,7 @@ pub trait Framebuffer { } } - fn draw_disk(&mut self, center: Point, radius: i32, color: u8) { + fn draw_disk(&mut self, center: Point, radius: i32, color: Color) { let rect = Rectangle::from_disk(center, radius); for y in rect.min.y..rect.max.y { @@ -377,7 +380,7 @@ pub trait Framebuffer { } } - fn draw_segment(&mut self, start: Point, end: Point, start_radius: f32, end_radius: f32, color: u8) { + fn draw_segment(&mut self, start: Point, end: Point, start_radius: f32, end_radius: f32, color: Color) { let rect = Rectangle::from_segment(start, end, start_radius.ceil() as i32, end_radius.ceil() as i32); let a = vec2!(start.x as f32, start.y as f32) + 0.5; let b = vec2!(end.x as f32, end.y as f32) + 0.5; diff --git a/crates/core/src/framebuffer/mxcfb_sys.rs b/crates/core/src/framebuffer/mxcfb_sys.rs index 56fe1b3..83064d5 100644 --- a/crates/core/src/framebuffer/mxcfb_sys.rs +++ b/crates/core/src/framebuffer/mxcfb_sys.rs @@ -153,12 +153,15 @@ pub const EPDC_FLAG_USE_DITHERING_ORDERED: libc::c_int = 3; // pub const EPDC_FLAG_USE_DITHERING_QUANT_ONLY: libc::c_int = 4; /* Mark 11 */ -pub const HWTCON_WAVEFORM_MODE_GL16 :u32 = 3; -pub const HWTCON_WAVEFORM_MODE_GLR16 :u32 = 4; -pub const HWTCON_WAVEFORM_MODE_REAGL :u32 = 4; -pub const HWTCON_WAVEFORM_MODE_A2 :u32 = 6; -pub const HWTCON_WAVEFORM_MODE_GCK16 :u32 = 8; -pub const HWTCON_WAVEFORM_MODE_GLKW16:u32 = 9; +pub const HWTCON_WAVEFORM_MODE_GL16 :u32 = 3; +pub const HWTCON_WAVEFORM_MODE_GLR16 :u32 = 4; +pub const HWTCON_WAVEFORM_MODE_REAGL :u32 = 4; +pub const HWTCON_WAVEFORM_MODE_A2 :u32 = 6; +pub const HWTCON_WAVEFORM_MODE_GCK16 :u32 = 8; +pub const HWTCON_WAVEFORM_MODE_GLKW16:u32 = 9; +/* Mark 12 */ +pub const HWTCON_WAVEFORM_MODE_GCC16 :u32 = 10; +pub const HWTCON_WAVEFORM_MODE_GLRC16:u32 = 11; pub const HWTCON_FLAG_USE_DITHERING: libc::c_uint = 0x1; pub const HWTCON_FLAG_FORCE_A2_OUTPUT: libc::c_uint = 16; diff --git a/crates/core/src/framebuffer/transform.rs b/crates/core/src/framebuffer/transform.rs index 0379b15..35be6a9 100644 --- a/crates/core/src/framebuffer/transform.rs +++ b/crates/core/src/framebuffer/transform.rs @@ -1,7 +1,8 @@ use lazy_static::lazy_static; use super::image::Pixmap; +use crate::color::Color; -pub type ColorTransform = fn(u32, u32, u8) -> u8; +pub type ColorTransform = fn(u32, u32, Color) -> Color; const DITHER_PITCH: u32 = 128; @@ -37,37 +38,39 @@ lazy_static! { // The input color is in {0 .. 255}. // The output color is in G16. // G16 := {17 * i | i ∈ {0 .. 15}}. -pub fn transform_dither_g16(x: u32, y: u32, color: u8) -> u8 { +pub fn transform_dither_g16(x: u32, y: u32, color: Color) -> Color { + let gray = color.gray(); // Get the address of the drift value. let addr = (x % DITHER_PITCH) + (y % DITHER_PITCH) * DITHER_PITCH; // Apply the drift to the input color. - let c = (color as i16 + DITHER_G16_DRIFTS[addr as usize] as i16).clamp(0, 255); + let c = (gray as i16 + DITHER_G16_DRIFTS[addr as usize] as i16).clamp(0, 255); // Compute the distance to the previous color in G16. let d = c % 17; // Return the nearest color in G16. - if d < 9 { + Color::Gray(if d < 9 { (c - d) as u8 } else { (c + (17 - d)) as u8 - } + }) } // Ordered dithering. // The input color is in {0 .. 255}. // The output color is in {0, 255}. -pub fn transform_dither_g2(x: u32, y: u32, color: u8) -> u8 { +pub fn transform_dither_g2(x: u32, y: u32, color: Color) -> Color { + let gray = color.gray(); // Get the address of the drift value. let addr = (x % DITHER_PITCH) + (y % DITHER_PITCH) * DITHER_PITCH; // Apply the drift to the input color. - let c = (color as i16 + DITHER_G2_DRIFTS[addr as usize] as i16).clamp(0, 255); + let c = (gray as i16 + DITHER_G2_DRIFTS[addr as usize] as i16).clamp(0, 255); // Return the nearest color in G2. - if c < 128 { + Color::Gray(if c < 128 { 0 } else { 255 - } + }) } -pub fn transform_identity(_x: u32, _y: u32, color: u8) -> u8 { +pub fn transform_identity(_x: u32, _y: u32, color: Color) -> Color { color } diff --git a/crates/core/src/frontlight/premixed.rs b/crates/core/src/frontlight/premixed.rs index 7a8fde4..53f01ab 100644 --- a/crates/core/src/frontlight/premixed.rs +++ b/crates/core/src/frontlight/premixed.rs @@ -10,7 +10,7 @@ const FRONTLIGHT_WHITE: &str = "/sys/class/backlight/mxc_msp430.0/brightness"; // Forma const FRONTLIGHT_ORANGE_A: &str = "/sys/class/backlight/tlc5947_bl/color"; -// Libra H₂O, Clara HD, Libra 2, Clara BW +// Libra H₂O, Clara HD, Libra 2, Clara BW, Libra Colour, Clara Colour const FRONTLIGHT_ORANGE_B: &str = "/sys/class/backlight/lm3630a_led/color"; // Sage, Libra 2, Clara 2E, Elipsa 2E const FRONTLIGHT_ORANGE_C: &str = "/sys/class/leds/aw99703-bl_FL1/color"; diff --git a/crates/core/src/geom.rs b/crates/core/src/geom.rs index 7a1c618..3b49070 100644 --- a/crates/core/src/geom.rs +++ b/crates/core/src/geom.rs @@ -3,6 +3,7 @@ use serde::{Serialize, Deserialize}; use std::cmp::Ordering; use std::f32::consts; use std::ops::{Add, AddAssign, Sub, SubAssign, Mul, MulAssign, Div, DivAssign}; +use crate::color::Color; #[derive(Debug, Copy, Clone, Eq, PartialEq)] pub enum Dir { @@ -215,19 +216,19 @@ pub enum CornerSpec { } pub trait ColorSource { - fn color(&self, x: i32, y: i32) -> u8; + fn color(&self, x: i32, y: i32) -> Color; } -impl ColorSource for F where F: Fn(i32, i32) -> u8 { +impl ColorSource for F where F: Fn(i32, i32) -> Color { #[inline] - fn color(&self, x: i32, y: i32) -> u8 { + fn color(&self, x: i32, y: i32) -> Color { (self)(x, y) } } -impl ColorSource for u8 { +impl ColorSource for Color { #[inline] - fn color(&self, _: i32, _: i32) -> u8 { + fn color(&self, _: i32, _: i32) -> Color { *self } } @@ -235,7 +236,7 @@ impl ColorSource for u8 { #[derive(Debug, Copy, Clone)] pub struct BorderSpec { pub thickness: u16, - pub color: u8, + pub color: Color, } const HALF_PIXEL_DIAGONAL: f32 = consts::SQRT_2 / 2.0; diff --git a/crates/core/src/lib.rs b/crates/core/src/lib.rs index 61d9032..88896dd 100644 --- a/crates/core/src/lib.rs +++ b/crates/core/src/lib.rs @@ -1,7 +1,7 @@ #[macro_use] pub mod geom; mod unit; -mod color; +pub mod color; pub mod device; pub mod framebuffer; pub mod frontlight; @@ -14,7 +14,6 @@ pub mod document; pub mod library; pub mod view; pub mod metadata; -mod symbolic_path; pub mod rtc; pub mod settings; pub mod font; diff --git a/crates/core/src/settings/mod.rs b/crates/core/src/settings/mod.rs index 3648f19..2cffab5 100644 --- a/crates/core/src/settings/mod.rs +++ b/crates/core/src/settings/mod.rs @@ -9,7 +9,7 @@ use fxhash::FxHashSet; use serde::{Serialize, Deserialize}; use crate::metadata::{SortMethod, TextAlign}; use crate::frontlight::LightLevels; -use crate::color::BLACK; +use crate::color::{Color, BLACK}; use crate::device::CURRENT_DEVICE; use crate::unit::mm_to_px; @@ -213,7 +213,7 @@ pub struct CalculatorSettings { #[serde(default, rename_all = "kebab-case")] pub struct Pen { pub size: i32, - pub color: u8, + pub color: Color, pub dynamic: bool, pub amplitude: f32, pub min_speed: f32, diff --git a/crates/core/src/symbolic_path.rs b/crates/core/src/symbolic_path.rs deleted file mode 100644 index b885467..0000000 --- a/crates/core/src/symbolic_path.rs +++ /dev/null @@ -1,102 +0,0 @@ -pub const PATH_SEPARATOR: char = '.'; - -pub trait SymbolicPath<'a> { - type Owned; - fn parent(&self) -> Option<&Self>; - fn ancestors(&'a self) -> Ancestors<'a>; - fn is_child_of(&self, other: &Self) -> bool; - fn is_descendant_of(&self, other: &Self) -> bool; - fn first_component(&self) -> &Self; - fn last_component(&self) -> &Self; - fn join(&self, other: &Self) -> Self::Owned; - fn depth(&self) -> usize; -} - -pub struct Ancestors<'a> { - next: Option<&'a str>, -} - -impl<'a> Iterator for Ancestors<'a> { - type Item = &'a str; - fn next(&mut self) -> Option<&'a str> { - let next = self.next; - self.next = self.next.and_then(SymbolicPath::parent); - next - } -} - -impl<'a> SymbolicPath<'a> for str { - type Owned = String; - - fn parent(&self) -> Option<&str> { - self.rfind(PATH_SEPARATOR).map(|index| &self[..index]) - } - - fn ancestors(&'a self) -> Ancestors<'a> { - Ancestors { - next: self.parent(), - } - } - - fn is_child_of(&self, other: &str) -> bool { - if let Some(p) = self.parent() { - p == other - } else { - false - } - } - - #[inline] - fn join(&self, other: &str) -> String { - format!("{}{}{}", self, PATH_SEPARATOR, other) - } - - #[inline] - fn is_descendant_of(&self, other: &str) -> bool { - self.starts_with(other) && self[other.len()..].starts_with(PATH_SEPARATOR) - } - - fn first_component(&self) -> &str { - if let Some(index) = self.find(PATH_SEPARATOR) { - &self[..index] - } else { - self - } - } - - fn last_component(&self) -> &str { - if let Some(index) = self.rfind(PATH_SEPARATOR) { - &self[index+1..] - } else { - self - } - } - - fn depth(&self) -> usize { - self.matches(PATH_SEPARATOR).count() - } -} - -#[cfg(test)] -mod tests { - use super::SymbolicPath; - - #[test] - fn relationships() { - assert!("a.b".is_child_of("a")); - assert!(!"a.bb".is_child_of("a.b")); - assert!(!"a.b.c".is_child_of("a")); - assert!(!"a".is_descendant_of("a")); - assert!(!"a.bb".is_descendant_of("a.b")); - assert!("a.b.c".is_descendant_of("a")); - assert!(!"a.b.c".is_descendant_of("b")); - } - - #[test] - fn components() { - assert_eq!("a.b.c".last_component(), "c"); - assert_eq!("a.b.c".first_component(), "a"); - assert_eq!("a".depth(), 0); - assert_eq!("a.b.c".depth(), 2); - } -} diff --git a/crates/core/src/view/dictionary/mod.rs b/crates/core/src/view/dictionary/mod.rs index ba09040..2631124 100644 --- a/crates/core/src/view/dictionary/mod.rs +++ b/crates/core/src/view/dictionary/mod.rs @@ -136,7 +136,7 @@ impl Dictionary { let image_rect = rect![rect.min.x, rect.min.y + 2 * small_height + big_thickness, rect.max.x, rect.max.y - small_height - small_thickness]; - let image = Image::new(image_rect, Pixmap::new(1, 1)); + let image = Image::new(image_rect, Pixmap::new(1, 1, 1)); children.push(Box::new(image) as Box); let mut doc = HtmlDocument::new_from_memory(""); @@ -338,7 +338,7 @@ impl Dictionary { CycleDir::Next => Location::Next(self.location), }; if let Some(image) = self.children[4].downcast_mut::() { - if let Some((pixmap, loc)) = self.doc.pixmap(location, 1.0) { + if let Some((pixmap, loc)) = self.doc.pixmap(location, 1.0, CURRENT_DEVICE.color_samples()) { image.update(pixmap, rq); self.location = loc; } @@ -359,7 +359,7 @@ impl Dictionary { let content = query_to_content(&self.query, &self.language, self.fuzzy, self.target.as_ref(), context); self.doc.update(&content); if let Some(image) = self.children[4].downcast_mut::() { - if let Some((pixmap, loc)) = self.doc.pixmap(Location::Exact(0), 1.0) { + if let Some((pixmap, loc)) = self.doc.pixmap(Location::Exact(0), 1.0, CURRENT_DEVICE.color_samples()) { image.update(pixmap, rq); self.location = loc; } @@ -597,7 +597,7 @@ impl View for Dictionary { rect.max.x, rect.max.y - small_height - small_thickness]; self.doc.layout(image_rect.width(), image_rect.height(), context.settings.dictionary.font_size, dpi); if let Some(image) = self.children[4].downcast_mut::() { - if let Some((pixmap, loc)) = self.doc.pixmap(Location::Exact(self.location), 1.0) { + if let Some((pixmap, loc)) = self.doc.pixmap(Location::Exact(self.location), 1.0, CURRENT_DEVICE.color_samples()) { image.update(pixmap, &mut RenderQueue::new()); self.location = loc; } diff --git a/crates/core/src/view/filler.rs b/crates/core/src/view/filler.rs index e2b38f8..e269873 100644 --- a/crates/core/src/view/filler.rs +++ b/crates/core/src/view/filler.rs @@ -3,16 +3,17 @@ use super::{View, Event, Hub, Bus, Id, ID_FEEDER, RenderQueue}; use crate::geom::Rectangle; use crate::context::Context; use crate::font::Fonts; +use crate::color::Color; pub struct Filler { id: Id, pub rect: Rectangle, children: Vec>, - color: u8, + color: Color, } impl Filler { - pub fn new(rect: Rectangle, color: u8) -> Filler { + pub fn new(rect: Rectangle, color: Color) -> Filler { Filler { id: ID_FEEDER.next(), rect, diff --git a/crates/core/src/view/home/book.rs b/crates/core/src/view/home/book.rs index 58687e8..2cd1d12 100644 --- a/crates/core/src/view/home/book.rs +++ b/crates/core/src/view/home/book.rs @@ -129,7 +129,7 @@ impl View for Book { }).and_then(|mut doc| { doc.dims(0).and_then(|dims| { let scale = (tw as f32 / dims.0).min(th as f32 / dims.1); - doc.pixmap(Location::Exact(0), scale) + doc.pixmap(Location::Exact(0), scale, CURRENT_DEVICE.color_samples()) }) }) { let dx = (tw - pixmap.width as i32) / 2; diff --git a/crates/core/src/view/home/shelf.rs b/crates/core/src/view/home/shelf.rs index e0c1393..a4e1451 100644 --- a/crates/core/src/view/home/shelf.rs +++ b/crates/core/src/view/home/shelf.rs @@ -90,7 +90,7 @@ impl Shelf { // triggered by loading multiple jp2 pixmaps in parallel. let _guard = EXCLUSIVE_ACCESS.lock().unwrap(); open(full_path).and_then(|mut doc| { - doc.preview_pixmap(tw as f32, th as f32) + doc.preview_pixmap(tw as f32, th as f32, CURRENT_DEVICE.color_samples()) }).map(|pixmap| { if pixmap.save(&thumb_path2).is_ok() { hub2.send(Event::RefreshBookPreview(path, Some(PathBuf::from(thumb_path2)))).ok(); diff --git a/crates/core/src/view/icon.rs b/crates/core/src/view/icon.rs index b14794a..ddd3e69 100644 --- a/crates/core/src/view/icon.rs +++ b/crates/core/src/view/icon.rs @@ -7,7 +7,7 @@ use super::{View, Event, Hub, Bus, Id, ID_FEEDER, RenderQueue, RenderData, ViewI use crate::gesture::GestureEvent; use crate::input::{DeviceEvent, FingerStatus}; use crate::document::pdf::PdfOpener; -use crate::color::{TEXT_NORMAL, TEXT_INVERTED_HARD}; +use crate::color::{Color, TEXT_NORMAL, TEXT_INVERTED_HARD}; use crate::unit::scale_by_dpi_raw; use crate::geom::{Rectangle, CornerSpec}; use crate::font::Fonts; @@ -30,7 +30,7 @@ lazy_static! { "align-center", "margin", "plug", "cover", "enclosed_menu", "contrast", "gray"].iter().cloned() { let path = dir.join(&format!("{}.svg", name)); let doc = PdfOpener::new().and_then(|o| o.open(path)).unwrap(); - let pixmap = doc.page(0).and_then(|p| p.pixmap(scale)).unwrap(); + let pixmap = doc.page(0).and_then(|p| p.pixmap(scale, 1)).unwrap(); m.insert(name, pixmap); } m @@ -42,7 +42,7 @@ pub struct Icon { pub rect: Rectangle, children: Vec>, pub name: String, - background: u8, + background: Color, align: Align, corners: Option, event: Event, @@ -64,7 +64,7 @@ impl Icon { } } - pub fn background(mut self, background: u8) -> Icon { + pub fn background(mut self, background: Color) -> Icon { self.background = background; self } diff --git a/crates/core/src/view/intermission.rs b/crates/core/src/view/intermission.rs index 3797bb0..cfdb93e 100644 --- a/crates/core/src/view/intermission.rs +++ b/crates/core/src/view/intermission.rs @@ -94,7 +94,7 @@ impl View for Intermission { let mut doc = open("icons/dodecahedron.svg").unwrap(); let (width, height) = doc.dims(0).unwrap(); let scale = (plan.width as f32 / width.max(height) as f32) / 4.0; - let (pixmap, _) = doc.pixmap(Location::Exact(0), scale).unwrap(); + let (pixmap, _) = doc.pixmap(Location::Exact(0), scale, 1).unwrap(); let dx = (self.rect.width() as i32 - pixmap.width as i32) / 2; let dy = dy + 2 * x_height; let pt = self.rect.min + pt!(dx, dy); @@ -107,7 +107,9 @@ impl View for Intermission { let w_ratio = self.rect.width() as f32 / width; let h_ratio = self.rect.height() as f32 / height; let scale = w_ratio.min(h_ratio); - if let Some((pixmap, _)) = doc.pixmap(Location::Exact(0), scale) { + if let Some((pixmap, _)) = doc.pixmap(Location::Exact(0), + scale, + CURRENT_DEVICE.color_samples()) { let dx = (self.rect.width() as i32 - pixmap.width as i32) / 2; let dy = (self.rect.height() as i32 - pixmap.height as i32) / 2; let pt = self.rect.min + pt!(dx, dy); @@ -122,7 +124,9 @@ impl View for Intermission { }, Message::Cover(ref path) => { if let Some(mut doc) = open(path) { - if let Some(pixmap) = doc.preview_pixmap(self.rect.width() as f32, self.rect.height() as f32) { + if let Some(pixmap) = doc.preview_pixmap(self.rect.width() as f32, + self.rect.height() as f32, + CURRENT_DEVICE.color_samples()) { let dx = (self.rect.width() as i32 - pixmap.width as i32) / 2; let dy = (self.rect.height() as i32 - pixmap.height as i32) / 2; let pt = self.rect.min + pt!(dx, dy); diff --git a/crates/core/src/view/key.rs b/crates/core/src/view/key.rs index 66dd017..2aacca1 100644 --- a/crates/core/src/view/key.rs +++ b/crates/core/src/view/key.rs @@ -5,7 +5,7 @@ use crate::gesture::GestureEvent; use super::{View, Event, ViewId, KeyboardEvent, Hub, Bus, Id, ID_FEEDER, RenderQueue, RenderData, TextKind}; use super::BORDER_RADIUS_LARGE; use super::icon::ICONS_PIXMAPS; -use crate::color::{TEXT_NORMAL, TEXT_INVERTED_HARD, KEYBOARD_BG}; +use crate::color::{Color, TEXT_NORMAL, TEXT_INVERTED_HARD, KEYBOARD_BG}; use crate::font::{Fonts, font_from_style, KBD_CHAR, KBD_LABEL}; use crate::geom::{Rectangle, LinearDir, CornerSpec}; use crate::context::Context; @@ -208,7 +208,7 @@ impl View for Key { fn render(&self, fb: &mut dyn Framebuffer, _rect: Rectangle, fonts: &mut Fonts) { let dpi = CURRENT_DEVICE.dpi; fb.draw_rectangle(&self.rect, KEYBOARD_BG); - let scheme: [u8; 3] = if self.active ^ (self.pressure == 2) { + let scheme: [Color; 3] = if self.active ^ (self.pressure == 2) { TEXT_INVERTED_HARD } else { TEXT_NORMAL diff --git a/crates/core/src/view/mod.rs b/crates/core/src/view/mod.rs index 4efb369..4e8a350 100644 --- a/crates/core/src/view/mod.rs +++ b/crates/core/src/view/mod.rs @@ -53,6 +53,7 @@ use std::fmt::{self, Debug}; use fxhash::FxHashMap; use downcast_rs::{Downcast, impl_downcast}; use crate::font::Fonts; +use crate::color::Color; use crate::document::{Location, TextLocation}; use crate::settings::{ButtonScheme, FirstColumn, SecondColumn, RotationLock}; use crate::metadata::{Info, ZoomMode, ScrollMode, SortMethod, TextAlign, SimpleStatus, PageScheme, Margin}; @@ -568,7 +569,7 @@ pub enum EntryId { Rotate(i8), Launch(AppCmd), SetPenSize(i32), - SetPenColor(u8), + SetPenColor(Color), TogglePenDynamism, ReloadDictionaries, New, diff --git a/crates/core/src/view/reader/mod.rs b/crates/core/src/view/reader/mod.rs index 27c9702..e677440 100644 --- a/crates/core/src/view/reader/mod.rs +++ b/crates/core/src/view/reader/mod.rs @@ -202,7 +202,7 @@ fn scaling_factor(rect: &Rectangle, cropping_margin: &Margin, screen_margin_widt fn build_pixmap(rect: &Rectangle, doc: &mut dyn Document, location: usize) -> (Pixmap, usize) { let scale = scaling_factor(rect, &Margin::default(), 0, doc.dims(location).unwrap(), ZoomMode::FitToPage); - doc.pixmap(Location::Exact(location), scale).unwrap() + doc.pixmap(Location::Exact(location), scale, CURRENT_DEVICE.color_samples()).unwrap() } fn find_cut(frame: &Rectangle, y_pos: i32, scale: f32, dir: LinearDir, lines: &[BoundedText]) -> Option { @@ -471,7 +471,7 @@ impl Reader { let dims = doc.dims(location).unwrap_or((3.0, 4.0)); let screen_margin_width = self.view_port.margin_width; let scale = scaling_factor(&self.rect, &cropping_margin, screen_margin_width, dims, self.view_port.zoom_mode); - if let Some((pixmap, _)) = doc.pixmap(Location::Exact(location), scale) { + if let Some((pixmap, _)) = doc.pixmap(Location::Exact(location), scale, CURRENT_DEVICE.color_samples()) { let frame = rect![(cropping_margin.left * pixmap.width as f32).ceil() as i32, (cropping_margin.top * pixmap.height as f32).ceil() as i32, ((1.0 - cropping_margin.right) * pixmap.width as f32).floor() as i32, @@ -480,7 +480,7 @@ impl Reader { } else { let width = (dims.0 as f32 * scale).max(1.0) as u32; let height = (dims.1 as f32 * scale).max(1.0) as u32; - let pixmap = Pixmap::empty(width, height); + let pixmap = Pixmap::empty(width, height, CURRENT_DEVICE.color_samples()); let frame = pixmap.rect(); self.cache.insert(location, Resource { pixmap, frame, scale }); } diff --git a/crates/core/src/view/sketch/mod.rs b/crates/core/src/view/sketch/mod.rs index 5d7bdab..291d07b 100644 --- a/crates/core/src/view/sketch/mod.rs +++ b/crates/core/src/view/sketch/mod.rs @@ -20,7 +20,7 @@ use crate::settings::{ImportSettings, Pen}; use crate::helpers::IsHidden; use crate::font::Fonts; use crate::unit::scale_by_dpi; -use crate::color::{BLACK, WHITE}; +use crate::color::{Color, BLACK, WHITE}; use crate::context::Context; const FILENAME_PATTERN: &str = "sketch-%Y%m%d_%H%M%S.png"; @@ -77,7 +77,7 @@ impl Sketch { id, rect, children, - pixmap: Pixmap::new(rect.width(), rect.height()), + pixmap: Pixmap::new(rect.width(), rect.height(), 1), fingers: FxHashMap::default(), pen: context.settings.sketch.pen.clone(), save_path, @@ -130,13 +130,14 @@ impl Sketch { ]; for i in 1..=14 { - let c = i * 17; + let level = i * 17; if i % 7 == 1 { colors.push(EntryKind::Separator); } + let color = Color::Gray(level); colors.push(EntryKind::RadioButton(format!("Gray {:02}", i), - EntryId::SetPenColor(c), - self.pen.color == c)); + EntryId::SetPenColor(color), + self.pen.color == color)); } let mut entries = vec![ diff --git a/crates/emulator/src/main.rs b/crates/emulator/src/main.rs index 6afa982..82a1b9f 100644 --- a/crates/emulator/src/main.rs +++ b/crates/emulator/src/main.rs @@ -36,6 +36,7 @@ use plato_core::view::common::{toggle_input_history_menu, toggle_keyboard_layout use plato_core::helpers::{load_toml, save_toml}; use plato_core::settings::{Settings, SETTINGS_PATH, IntermKind}; use plato_core::geom::{Rectangle, Axis}; +use plato_core::color::Color; use plato_core::gesture::{GestureEvent, gesture_events}; use plato_core::device::CURRENT_DEVICE; use plato_core::battery::{Battery, FakeBattery}; @@ -109,13 +110,15 @@ fn code_from_key(key: Scancode) -> Option { struct FBCanvas(WindowCanvas); impl Framebuffer for FBCanvas { - fn set_pixel(&mut self, x: u32, y: u32, color: u8) { - self.0.set_draw_color(SdlColor::RGB(color, color, color)); + fn set_pixel(&mut self, x: u32, y: u32, color: Color) { + let [red, green, blue] = color.rgb(); + self.0.set_draw_color(SdlColor::RGB(red, green, blue)); self.0.draw_point(SdlPoint::new(x as i32, y as i32)).unwrap(); } - fn set_blended_pixel(&mut self, x: u32, y: u32, color: u8, alpha: f32) { - self.0.set_draw_color(SdlColor::RGBA(color, color, color, (alpha * 255.0) as u8)); + fn set_blended_pixel(&mut self, x: u32, y: u32, color: Color, alpha: f32) { + let [red, green, blue] = color.rgb(); + self.0.set_draw_color(SdlColor::RGBA(red, green, blue, (alpha * 255.0) as u8)); self.0.draw_point(SdlPoint::new(x as i32, y as i32)).unwrap(); } @@ -129,7 +132,11 @@ impl Framebuffer for FBCanvas { for x in rect.min.x..rect.max.x { let u = (x - rect.min.x) as u32; let addr = 3 * (v * width + u); - let color = 255 - data[addr as usize]; + let red = data[addr as usize]; + let green = data[(addr+1) as usize]; + let blue = data[(addr+2) as usize]; + let mut color = Color::Rgb(red, green, blue); + color.invert(); self.set_pixel(x as u32, y as u32, color); } } @@ -146,7 +153,11 @@ impl Framebuffer for FBCanvas { for x in rect.min.x..rect.max.x { let u = (x - rect.min.x) as u32; let addr = 3 * (v * width + u); - let color = data[addr as usize].saturating_sub(drift); + let red = data[addr as usize]; + let green = data[(addr+1) as usize]; + let blue = data[(addr+2) as usize]; + let mut color = Color::Rgb(red, green, blue); + color.shift(drift); self.set_pixel(x as u32, y as u32, color); } }