Compare commits
2 commits
59886a80fd
...
2011ece21d
Author | SHA1 | Date | |
---|---|---|---|
saji | 2011ece21d | ||
saji | c0060627f4 |
1
.gitignore
vendored
1
.gitignore
vendored
|
@ -1 +1,2 @@
|
|||
/target
|
||||
result
|
||||
|
|
17
Cargo.lock
generated
17
Cargo.lock
generated
|
@ -1420,6 +1420,7 @@ dependencies = [
|
|||
"strum",
|
||||
"thiserror",
|
||||
"tokio",
|
||||
"toml",
|
||||
"tower-http",
|
||||
"tracing",
|
||||
"tracing-subscriber",
|
||||
|
@ -2057,9 +2058,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "toml"
|
||||
version = "0.8.16"
|
||||
version = "0.8.19"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "81967dd0dd2c1ab0bc3468bd7caecc32b8a4aa47d0c8c695d8c2b2108168d62c"
|
||||
checksum = "a1ed1f98e3fdc28d6d910e6737ae6ab1a93bf1985935a1193e68f93eeb68d24e"
|
||||
dependencies = [
|
||||
"serde",
|
||||
"serde_spanned",
|
||||
|
@ -2069,18 +2070,18 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "toml_datetime"
|
||||
version = "0.6.7"
|
||||
version = "0.6.8"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f8fb9f64314842840f1d940ac544da178732128f1c78c21772e876579e0da1db"
|
||||
checksum = "0dd7358ecb8fc2f8d014bf86f6f638ce72ba252a2c3a2572f2a795f1d23efb41"
|
||||
dependencies = [
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "toml_edit"
|
||||
version = "0.22.17"
|
||||
version = "0.22.20"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8d9f8729f5aea9562aac1cc0441f5d6de3cff1ee0c5d67293eeca5eb36ee7c16"
|
||||
checksum = "583c44c02ad26b0c3f3066fe629275e50627026c51ac2e595cca4c230ce1ce1d"
|
||||
dependencies = [
|
||||
"indexmap",
|
||||
"serde",
|
||||
|
@ -2434,9 +2435,9 @@ checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec"
|
|||
|
||||
[[package]]
|
||||
name = "winnow"
|
||||
version = "0.6.16"
|
||||
version = "0.6.18"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b480ae9340fc261e6be3e95a1ba86d54ae3f9171132a73ce8d4bbaf68339507c"
|
||||
checksum = "68a9bda4691f099d435ad181000724da8e5899daa10713c2d432552b9ccd3a6f"
|
||||
dependencies = [
|
||||
"memchr",
|
||||
]
|
||||
|
|
|
@ -20,6 +20,7 @@ serde = { version = "1.0.204", features = ["derive"] }
|
|||
strum = { version = "0.26.3", features = ["derive"] }
|
||||
thiserror = "1.0.63"
|
||||
tokio = { version = "1.38.1", features = ["full"] }
|
||||
toml = "0.8.19"
|
||||
tower-http = { version = "0.5.2", features = ["trace"] }
|
||||
tracing = "0.1.40"
|
||||
tracing-subscriber = "0.3.18"
|
||||
|
@ -29,4 +30,4 @@ criterion = { version = "0.5", features = ["html_reports"] }
|
|||
|
||||
[[bench]]
|
||||
name = "dithering"
|
||||
harness = false
|
||||
harness = false
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
use criterion::{black_box, criterion_group, criterion_main, BenchmarkId, Criterion};
|
||||
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) {
|
||||
let mut group = c.benchmark_group("dithering_benchmark");
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
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::extract::{FromRequest, Multipart, State};
|
||||
use axum::http::{header, StatusCode};
|
||||
|
|
|
@ -7,45 +7,6 @@ use image::Rgb as imgRgb;
|
|||
use palette::color_difference::{Ciede2000, HyAb};
|
||||
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(
|
||||
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 {
|
||||
fn dither(&mut self, img: &RgbImage, output: &mut DitheredImage);
|
||||
}
|
||||
|
@ -138,7 +93,11 @@ fn nearest_neighbor(input_color: Lab, palette: &[Lab]) -> (u8, Lab) {
|
|||
.iter()
|
||||
.enumerate()
|
||||
.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))
|
||||
.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
|
||||
/// pixel multiplied by a scalar factor.
|
||||
fn compute_error_adjusted_color(orig: &Lab, err: &Lab, weight: f32) -> Lab {
|
||||
let (orig_l, orig_a, orig_b) = orig.into_components();
|
||||
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),
|
||||
))
|
||||
*orig + *err * weight
|
||||
}
|
||||
|
||||
/// ``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 eink;
|
||||
|
|
17
src/main.rs
17
src/main.rs
|
@ -2,15 +2,27 @@ pub mod api;
|
|||
pub mod display;
|
||||
pub mod dither;
|
||||
pub mod errors;
|
||||
pub mod eink;
|
||||
|
||||
use std::path::PathBuf;
|
||||
use serde::Deserialize;
|
||||
use toml;
|
||||
|
||||
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 image::RgbImage;
|
||||
use tracing::{error, info};
|
||||
|
||||
|
||||
|
||||
/// Application config, including sqlite db path, scan folders, and scheduling.
|
||||
#[derive(Deserialize)]
|
||||
struct Config {
|
||||
|
||||
}
|
||||
|
||||
/// Display images on E-Ink Displays
|
||||
#[derive(Debug, Parser)]
|
||||
#[command(version, about)]
|
||||
|
@ -43,7 +55,6 @@ struct ConvertArgs {
|
|||
async fn main() -> anyhow::Result<()> {
|
||||
tracing_subscriber::fmt::init();
|
||||
let cli = Cli::parse();
|
||||
println!("CLI {cli:?}");
|
||||
|
||||
match cli.command {
|
||||
Command::Convert(a) => {
|
||||
|
@ -66,7 +77,7 @@ async fn main() -> anyhow::Result<()> {
|
|||
error!("HI");
|
||||
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();
|
||||
|
||||
dither.dither(&img, &mut eink_buf);
|
||||
|
|
|
@ -6,7 +6,8 @@
|
|||
|
||||
use anyhow::Result;
|
||||
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<()> {
|
||||
let image: RgbImage = ImageReader::open(format!("samples/{sample_file}"))?
|
||||
|
|
Loading…
Reference in a new issue