Compare commits

...

2 commits

Author SHA1 Message Date
saji 2011ece21d add toml; move eink-specific palettes to separate file
All checks were successful
cargo_test_bench / Run Tests (push) Successful in 1m28s
cargo_test_bench / Run Benchmarks (push) Successful in 2m18s
2024-07-31 20:20:22 -05:00
saji c0060627f4 add result/ to gitignore 2024-07-31 20:20:05 -05:00
10 changed files with 95 additions and 68 deletions

1
.gitignore vendored
View file

@ -1 +1,2 @@
/target /target
result

17
Cargo.lock generated
View file

@ -1420,6 +1420,7 @@ dependencies = [
"strum", "strum",
"thiserror", "thiserror",
"tokio", "tokio",
"toml",
"tower-http", "tower-http",
"tracing", "tracing",
"tracing-subscriber", "tracing-subscriber",
@ -2057,9 +2058,9 @@ dependencies = [
[[package]] [[package]]
name = "toml" name = "toml"
version = "0.8.16" version = "0.8.19"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "81967dd0dd2c1ab0bc3468bd7caecc32b8a4aa47d0c8c695d8c2b2108168d62c" checksum = "a1ed1f98e3fdc28d6d910e6737ae6ab1a93bf1985935a1193e68f93eeb68d24e"
dependencies = [ dependencies = [
"serde", "serde",
"serde_spanned", "serde_spanned",
@ -2069,18 +2070,18 @@ dependencies = [
[[package]] [[package]]
name = "toml_datetime" name = "toml_datetime"
version = "0.6.7" version = "0.6.8"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f8fb9f64314842840f1d940ac544da178732128f1c78c21772e876579e0da1db" checksum = "0dd7358ecb8fc2f8d014bf86f6f638ce72ba252a2c3a2572f2a795f1d23efb41"
dependencies = [ dependencies = [
"serde", "serde",
] ]
[[package]] [[package]]
name = "toml_edit" name = "toml_edit"
version = "0.22.17" version = "0.22.20"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8d9f8729f5aea9562aac1cc0441f5d6de3cff1ee0c5d67293eeca5eb36ee7c16" checksum = "583c44c02ad26b0c3f3066fe629275e50627026c51ac2e595cca4c230ce1ce1d"
dependencies = [ dependencies = [
"indexmap", "indexmap",
"serde", "serde",
@ -2434,9 +2435,9 @@ checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec"
[[package]] [[package]]
name = "winnow" name = "winnow"
version = "0.6.16" version = "0.6.18"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b480ae9340fc261e6be3e95a1ba86d54ae3f9171132a73ce8d4bbaf68339507c" checksum = "68a9bda4691f099d435ad181000724da8e5899daa10713c2d432552b9ccd3a6f"
dependencies = [ dependencies = [
"memchr", "memchr",
] ]

View file

@ -20,6 +20,7 @@ serde = { version = "1.0.204", features = ["derive"] }
strum = { version = "0.26.3", features = ["derive"] } strum = { version = "0.26.3", features = ["derive"] }
thiserror = "1.0.63" thiserror = "1.0.63"
tokio = { version = "1.38.1", features = ["full"] } tokio = { version = "1.38.1", features = ["full"] }
toml = "0.8.19"
tower-http = { version = "0.5.2", features = ["trace"] } tower-http = { version = "0.5.2", features = ["trace"] }
tracing = "0.1.40" tracing = "0.1.40"
tracing-subscriber = "0.3.18" tracing-subscriber = "0.3.18"

View file

@ -1,6 +1,7 @@
use criterion::{black_box, criterion_group, criterion_main, BenchmarkId, Criterion}; use criterion::{black_box, criterion_group, criterion_main, BenchmarkId, Criterion};
use image::{ImageReader, RgbImage}; use image::{ImageReader, RgbImage};
use pi_frame_server::dither::{DitherMethod, DitheredImage, Palette}; use pi_frame_server::dither::{DitherMethod, DitheredImage};
use pi_frame_server::eink::Palette;
fn criterion_benchmark(c: &mut Criterion) { fn criterion_benchmark(c: &mut Criterion) {
let mut group = c.benchmark_group("dithering_benchmark"); let mut group = c.benchmark_group("dithering_benchmark");

View file

@ -1,5 +1,6 @@
use crate::display::EInkPanel; use crate::display::EInkPanel;
use crate::dither::{DitherMethod, Palette, DitheredImage}; use crate::dither::{DitherMethod, DitheredImage};
use crate::eink::Palette;
use axum::async_trait; use axum::async_trait;
use axum::extract::{FromRequest, Multipart, State}; use axum::extract::{FromRequest, Multipart, State};
use axum::http::{header, StatusCode}; use axum::http::{header, StatusCode};

View file

@ -7,45 +7,6 @@ use image::Rgb as imgRgb;
use palette::color_difference::{Ciede2000, HyAb}; use palette::color_difference::{Ciede2000, HyAb};
use palette::{cast::FromComponents, IntoColor, Lab, Srgb}; use palette::{cast::FromComponents, IntoColor, Lab, Srgb};
/// Palette used on the display; pixels can be one of these colors.
///
/// The RGB values are slightly adjusted to improve accuracy.
const DISPLAY_PALETTE: [Srgb; 7] = [
Srgb::new(0.047, 0.047, 0.055), // Black
Srgb::new(0.824, 0.824, 0.816), // White
Srgb::new(0.118, 0.376, 0.122), // Green
Srgb::new(0.114, 0.118, 0.667), // Blue
Srgb::new(0.549, 0.106, 0.114), // Red
Srgb::new(0.827, 0.788, 0.239), // Yellow
Srgb::new(0.757, 0.443, 0.165), // Orange
];
const SIMPLE_PALETTE: [Srgb; 7] = [
Srgb::new(0.0, 0.0, 0.0), // Black
Srgb::new(1.0, 1.0, 1.0), // White
Srgb::new(0.0, 1.0, 0.0), // Green
Srgb::new(0.0, 0.0, 1.0), // Blue
Srgb::new(1.0, 0.0, 0.0), // Red
Srgb::new(1.0, 1.0, 0.0), // Yellow
Srgb::new(0.757, 0.443, 0.165), // Orange
];
#[derive(strum::EnumString, Serialize, Deserialize, PartialEq, Eq, Debug)]
pub enum Palette {
Default,
Simple,
}
impl Palette {
#[must_use]
pub const fn value(&self) -> &[Srgb] {
match self {
Self::Default => &DISPLAY_PALETTE,
Self::Simple => &SIMPLE_PALETTE, // FIXME: use simple pallete based on binary.
}
}
}
#[derive( #[derive(
strum::EnumString, strum::Display, Serialize, Deserialize, PartialEq, Eq, Debug, Clone, strum::EnumString, strum::Display, Serialize, Deserialize, PartialEq, Eq, Debug, Clone,
)] )]
@ -121,12 +82,6 @@ impl DitheredImage {
} }
} }
impl Default for DitheredImage {
fn default() -> Self {
Self::new(800, 480, DISPLAY_PALETTE.to_vec())
}
}
pub trait Ditherer { pub trait Ditherer {
fn dither(&mut self, img: &RgbImage, output: &mut DitheredImage); fn dither(&mut self, img: &RgbImage, output: &mut DitheredImage);
} }
@ -138,7 +93,11 @@ fn nearest_neighbor(input_color: Lab, palette: &[Lab]) -> (u8, Lab) {
.iter() .iter()
.enumerate() .enumerate()
.map(|(idx, p_color)| { .map(|(idx, p_color)| {
(idx, input_color.difference(*p_color), input_color - *p_color) (
idx,
input_color.difference(*p_color),
input_color - *p_color,
)
}) })
.min_by(|(_, a, _), (_, b, _)| a.total_cmp(b)) .min_by(|(_, a, _), (_, b, _)| a.total_cmp(b))
.expect("Should always find a color"); .expect("Should always find a color");
@ -170,13 +129,7 @@ const fn coord_to_idx(x: u32, y: u32, xsize: u32) -> usize {
/// Compute the error-adjusted new lab value based on the error value of the currently scanned /// Compute the error-adjusted new lab value based on the error value of the currently scanned
/// pixel multiplied by a scalar factor. /// pixel multiplied by a scalar factor.
fn compute_error_adjusted_color(orig: &Lab, err: &Lab, weight: f32) -> Lab { fn compute_error_adjusted_color(orig: &Lab, err: &Lab, weight: f32) -> Lab {
let (orig_l, orig_a, orig_b) = orig.into_components(); *orig + *err * weight
let (err_l, err_a, err_b) = err.into_components();
Lab::from_components((
err_l.mul_add(weight, orig_l), // scalar * err_l + p_l
err_a.mul_add(weight, orig_a),
err_b.mul_add(weight, orig_b),
))
} }
/// ``DiffusionPoint`` is part of the diffusion matrix, represented by a shift in x and y and an error /// ``DiffusionPoint`` is part of the diffusion matrix, represented by a shift in x and y and an error

56
src/eink.rs Normal file
View file

@ -0,0 +1,56 @@
use palette::Srgb;
use serde::{Deserialize, Serialize};
use crate::dither::DitheredImage;
/// Palette used on the display; pixels can be one of these colors.
///
/// The RGB values are slightly adjusted to improve accuracy.
const DISPLAY_PALETTE: [Srgb; 7] = [
Srgb::new(0.047, 0.047, 0.055), // Black
Srgb::new(0.824, 0.824, 0.816), // White
Srgb::new(0.118, 0.376, 0.122), // Green
Srgb::new(0.114, 0.118, 0.667), // Blue
Srgb::new(0.549, 0.106, 0.114), // Red
Srgb::new(0.827, 0.788, 0.239), // Yellow
Srgb::new(0.757, 0.443, 0.165), // Orange
];
/// A more primitive palette that doesn't reflect reality. Here for posterity.
/// This is based on the datasheet, not on empirical testing
const SIMPLE_PALETTE: [Srgb; 7] = [
Srgb::new(0.0, 0.0, 0.0), // Black
Srgb::new(1.0, 1.0, 1.0), // White
Srgb::new(0.0, 1.0, 0.0), // Green
Srgb::new(0.0, 0.0, 1.0), // Blue
Srgb::new(1.0, 0.0, 0.0), // Red
Srgb::new(1.0, 1.0, 0.0), // Yellow
Srgb::new(0.757, 0.443, 0.165), // Orange
];
#[derive(strum::EnumString, Serialize, Deserialize, PartialEq, Eq, Debug)]
pub enum Palette {
Default,
Simple,
}
impl Palette {
#[must_use]
pub const fn value(&self) -> &[Srgb] {
match self {
Self::Default => &DISPLAY_PALETTE,
Self::Simple => &SIMPLE_PALETTE,
}
}
}
/// Construct a dithered image for the EINK display using the default palette and correct
/// resolution.
#[must_use]
pub fn new_image() -> DitheredImage {
DitheredImage::new(800,480, DISPLAY_PALETTE.to_vec())
}

View file

@ -1 +1,2 @@
pub mod dither; pub mod dither;
pub mod eink;

View file

@ -2,15 +2,27 @@ pub mod api;
pub mod display; pub mod display;
pub mod dither; pub mod dither;
pub mod errors; pub mod errors;
pub mod eink;
use std::path::PathBuf; use std::path::PathBuf;
use serde::Deserialize;
use toml;
use crate::display::{EInkPanel, FakeEInk, Wrapper}; use crate::display::{EInkPanel, FakeEInk, Wrapper};
use crate::dither::{DitherMethod, DitheredImage, Palette}; use crate::dither::{DitherMethod, DitheredImage};
use crate::eink::Palette;
use clap::{Args, Parser, Subcommand}; use clap::{Args, Parser, Subcommand};
use image::RgbImage; use image::RgbImage;
use tracing::{error, info}; use tracing::{error, info};
/// Application config, including sqlite db path, scan folders, and scheduling.
#[derive(Deserialize)]
struct Config {
}
/// Display images on E-Ink Displays /// Display images on E-Ink Displays
#[derive(Debug, Parser)] #[derive(Debug, Parser)]
#[command(version, about)] #[command(version, about)]
@ -43,7 +55,6 @@ struct ConvertArgs {
async fn main() -> anyhow::Result<()> { async fn main() -> anyhow::Result<()> {
tracing_subscriber::fmt::init(); tracing_subscriber::fmt::init();
let cli = Cli::parse(); let cli = Cli::parse();
println!("CLI {cli:?}");
match cli.command { match cli.command {
Command::Convert(a) => { Command::Convert(a) => {
@ -66,7 +77,7 @@ async fn main() -> anyhow::Result<()> {
error!("HI"); error!("HI");
let mut display = FakeEInk {}; let mut display = FakeEInk {};
let mut eink_buf = DitheredImage::default(); let mut eink_buf = crate::eink::new_image();
let mut dither = DitherMethod::Atkinson.get_ditherer(); let mut dither = DitherMethod::Atkinson.get_ditherer();
dither.dither(&img, &mut eink_buf); dither.dither(&img, &mut eink_buf);

View file

@ -6,7 +6,8 @@
use anyhow::Result; use anyhow::Result;
use image::{ImageReader, RgbImage}; use image::{ImageReader, RgbImage};
use pi_frame_server::dither::{DitherMethod, DitheredImage, Palette}; use pi_frame_server::dither::{DitherMethod, DitheredImage};
use pi_frame_server::eink::Palette;
fn compare_original(sample_file: &str, reference_file: &str, method: &DitherMethod) -> Result<()> { fn compare_original(sample_file: &str, reference_file: &str, method: &DitherMethod) -> Result<()> {
let image: RgbImage = ImageReader::open(format!("samples/{sample_file}"))? let image: RgbImage = ImageReader::open(format!("samples/{sample_file}"))?