Compare commits
2 commits
59886a80fd
...
2011ece21d
Author | SHA1 | Date | |
---|---|---|---|
saji | 2011ece21d | ||
saji | c0060627f4 |
1
.gitignore
vendored
1
.gitignore
vendored
|
@ -1 +1,2 @@
|
||||||
/target
|
/target
|
||||||
|
result
|
||||||
|
|
17
Cargo.lock
generated
17
Cargo.lock
generated
|
@ -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",
|
||||||
]
|
]
|
||||||
|
|
|
@ -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"
|
||||||
|
@ -29,4 +30,4 @@ criterion = { version = "0.5", features = ["html_reports"] }
|
||||||
|
|
||||||
[[bench]]
|
[[bench]]
|
||||||
name = "dithering"
|
name = "dithering"
|
||||||
harness = false
|
harness = false
|
||||||
|
|
|
@ -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");
|
||||||
|
|
|
@ -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};
|
||||||
|
|
|
@ -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
56
src/eink.rs
Normal 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())
|
||||||
|
}
|
|
@ -1 +1,2 @@
|
||||||
pub mod dither;
|
pub mod dither;
|
||||||
|
pub mod eink;
|
||||||
|
|
17
src/main.rs
17
src/main.rs
|
@ -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);
|
||||||
|
|
|
@ -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}"))?
|
||||||
|
|
Loading…
Reference in a new issue