Compare commits

..

10 commits

Author SHA1 Message Date
Shautvast
16d4306763 readme and page updated 2024-04-10 17:41:51 +02:00
Shautvast
f40db8a474 example update 2024-04-10 16:40:48 +02:00
Shautvast
0c39058288 example 2024-04-10 16:39:59 +02:00
Shautvast
19e74b6dcb renames 2024-04-10 16:22:30 +02:00
Shautvast
26744cd261 more ignores 2024-04-10 16:21:07 +02:00
Shautvast
0c3ae9aa5b updated package.json 2024-04-10 16:20:18 +02:00
Shautvast
6a09094d54 working version 2024-04-10 14:56:02 +02:00
Shautvast
42f2ccffac it's working and performant, only the median algo isn't (performant) 2024-04-05 20:06:58 +02:00
Shautvast
fea1db9377 fixed some bugs 2024-04-01 22:32:56 +02:00
Shautvast
40ebe619f5 wip assembling the puzzle pieces 2024-03-30 16:16:03 +01:00
1794 changed files with 1230 additions and 3496 deletions

3
.gitignore vendored
View file

@ -2,3 +2,6 @@ node_modules/
target/
pkg/
.DS_Store
dist/
*.iml
.idea

View file

@ -1,7 +1,17 @@
**Spiegel** image filter project (Work In Progress)
**Spiegel** image filter project
- rust
- wasm
- rust/webassembly for image processing
- vanilla javascript for the user interface
- no server (just static pages)
Live demo at https://shautvast.github.io/spiegel-demo/
* Sorry for the poor performance, especially on larger images.
* It uses the median image filter from image.rs. That in itself can be pretty slow.
(Although the implementation uses a _sliding window histogram_, which I think is pretty nifty).
* And on top of that, I created this custom flood fill algorithm,
that instead of filling it with with a single color, looks up a sample from the
Spiegel book (that has a corresponding color) and takes the pixels from that.
sample output
![sample](https://github.com/shautvast/spiegel-web/blob/main/webclient/output.jpg)
![sample](https://github.com/shautvast/spiegel-web/blob/main/unsplash.png)

Binary file not shown.

Binary file not shown.

Before

Width:  |  Height:  |  Size: 349 KiB

Binary file not shown.

1
dist/796.bundle.js vendored

File diff suppressed because one or more lines are too long

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 192 KiB

523
dist/bundle.js vendored

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

Binary file not shown.

Binary file not shown.

Before

Width:  |  Height:  |  Size: 224 KiB

Binary file not shown.

29
dist/index.html vendored
View file

@ -1,29 +0,0 @@
<!doctype html>
<html>
<head>
<meta http-equiv="Content-type" content="text/html; charset=utf-8" />
<title>Spiegel</title>
<script defer src="bundle.js"></script></head>
<body>
<div class="default">
<div class="main">
<label>
<h2>Blur factor</h2< /label>
<div class="slidecontainer">
<input type="range" id="slider" value="0" min="0" max="100" class="slider" />
</div>
</label>
<div class="main_content">
<div class="content" id="images">
<div id="image_container"></div>
<canvas id="canvas"></canvas>
</div>
</div>
</div>
</div>
</body>
</html>

File diff suppressed because it is too large Load diff

View file

@ -1,19 +1,21 @@
[package]
edition = "2021"
name = "spiegel-client"
version = "0.1.0"
name = "spiegel"
version = "1.0.0"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
log = "0.4"
image = "0.24.1"
imageproc = "0.23"
image = "0.25"
imageproc = "0.24"
hex = "0.4"
anyhow = "1.0"
photon-rs = { version = "0.3.2", default-features = false }
console_error_panic_hook = { version = "0.1.7", optional = true }
wasm-bindgen = "0.2.78"
include_dir = "0.7.3"
web-sys = "0.3.55"
[lib]
crate-type = ["cdylib", "rlib"]

View file

Before

Width:  |  Height:  |  Size: 6.1 MiB

After

Width:  |  Height:  |  Size: 6.1 MiB

124
image-processor/src/lib.rs Normal file
View file

@ -0,0 +1,124 @@
use image::{GenericImage, GenericImageView, Pixel, Rgba, RgbaImage};
use photon_rs::PhotonImage;
use std::collections::LinkedList;
use image::imageops::FilterType;
mod samples;
use samples::log;
use wasm_bindgen::prelude::*;
static BLACK: Rgba<u8> = Rgba([0, 0, 0, 0]);
#[wasm_bindgen]
pub fn spiegel(photon_image: PhotonImage, median_kernelsize: u32, preview: bool) -> PhotonImage {
samples::read_jpeg_bytes();
let width = photon_image.get_width();
let height = photon_image.get_height();
let raw_pixels = photon_image.get_raw_pixels().to_vec();
let i1 = RgbaImage::from_vec(width, height, raw_pixels).unwrap();
let i2 = if preview {
image::imageops::resize(&i1, u32::min(500, width >> 1), u32::min(500, height >> 1), FilterType::Nearest)
} else {
image::imageops::resize(&i1, u32::min(500, width), u32::min(500, height), FilterType::Nearest)
};
let mut i3 = imageproc::filter::median_filter(&i2, median_kernelsize, median_kernelsize);
let i4 = if !preview {
apply_samples_to_image(&mut i3)
} else {
i3
};
let i5 = image::imageops::resize(&i4, width, height, FilterType::Nearest);
PhotonImage::new(i5.into_raw(), width, height)
}
fn apply_samples_to_image(src: &mut RgbaImage) -> RgbaImage {
let mut out = RgbaImage::new(src.width(), src.height());
unsafe {
for y in 0..src.height() {
log(&format!("{}", y));
for x in 0..src.width() {
if out.unsafe_get_pixel(x, y) == BLACK {
let pixel = src.unsafe_get_pixel(x, y);
if pixel != BLACK {
let sample = samples::get_closest_color(pixel[0], pixel[1], pixel[2])
.image
.as_ref()
.unwrap();
fill(src, sample, &mut out, pixel, x, y);
}
}
}
}
}
out
}
fn fill(
src: &mut RgbaImage,
sample: &RgbaImage,
dest: &mut RgbaImage,
color: Rgba<u8>,
px: u32,
py: u32,
) {
unsafe {
let height = sample.height();
let width = sample.width();
let mut points = LinkedList::new();
if is_same(src.unsafe_get_pixel(px, py), color) {
points.push_back(Coord(px, py));
}
while !points.is_empty() {
if let Some(coord) = points.pop_back() {
let orig_pixel = src.unsafe_get_pixel(coord.0, coord.1);
let x = coord.0;
let y = coord.1;
if src.unsafe_get_pixel(x, y) != BLACK {
if is_same(orig_pixel, color) {
let mut xx = x;
let mut yy = y;
while xx >= width {
xx -= width;
}
while yy >= height {
yy -= height;
}
dest.unsafe_put_pixel(x, y, sample.unsafe_get_pixel(xx, yy));
src.unsafe_put_pixel(x, y, BLACK);
if x > 1 {
points.push_front(Coord(x - 1, y));
}
if y > 1 {
points.push_front(Coord(x, y - 1));
}
if x < src.width() - 1 {
points.push_front(Coord(x + 1, y));
}
if y < src.height() - 1 {
points.push_front(Coord(x, y + 1));
}
}
}
} else {
println!("break");
break;
}
}
}
}
fn is_same(p1: Rgba<u8>, p2: Rgba<u8>) -> bool {
let p1 = p1.channels();
let p2 = p2.channels();
i16::abs(p1[0] as i16 - p2[0] as i16) < 4
&& i16::abs(p1[1] as i16 - p2[1] as i16) < 4
&& i16::abs(p1[2] as i16 - p2[2] as i16) < 4
}
#[derive(Debug)]
struct Coord(u32, u32);

View file

@ -0,0 +1,99 @@
use image::{load_from_memory_with_format, RgbaImage};
use std::sync::OnceLock;
use wasm_bindgen::prelude::*;
use include_dir::{include_dir, Dir, DirEntry};
static mut SAMPLES: OnceLock<Vec<ColorSample>> = OnceLock::new();
static SAMPLES_DIR: Dir = include_dir!("src/samples");
pub fn read_jpeg_bytes() {
unsafe {
SAMPLES.get_or_init(|| {
log("reading image samples");
read_color_samples().unwrap()
});
}
}
pub fn get_closest_color<'a>(r: u8, g: u8, b: u8) -> &'static ColorSample {
unsafe {
let color_samples = SAMPLES.get_mut().unwrap();
let mut closest = None;
let mut min_diff: f32 = 4294967295.0; //0xFFFFFFFF
for sample in color_samples {
let diff = get_distance(sample.r, sample.g, sample.b, r, g, b);
if diff < min_diff {
closest = Some(sample);
min_diff = diff;
}
}
let closest = closest.unwrap();
if closest.image.is_none() {
let sample_image =
load_from_memory_with_format(closest.raw_bytes, image::ImageFormat::Jpeg)
.unwrap()
.to_rgba8();
closest.image = Some(sample_image);
}
closest
}
}
/// returns squared euclidian color distance
/// as if colors were points in 3d space
fn get_distance(r1: u8, g1: u8, b1: u8, r2: u8, g2: u8, b2: u8) -> f32 {
let red_dif = r1 as f32 - r2 as f32;
let green_dif = g1 as f32 - g2 as f32;
let blue_dif = b1 as f32 - b2 as f32;
return red_dif * red_dif + green_dif * green_dif + blue_dif * blue_dif;
}
/// read all sample jpegs into memory
pub fn read_color_samples() -> anyhow::Result<Vec<ColorSample>> {
let mut color_samples: Vec<ColorSample> = Vec::new();
for entry in SAMPLES_DIR.entries() {
if let DirEntry::File(f) = entry {
let filename = entry.path().file_name().unwrap().to_str().unwrap();
let hex_r = &filename[0..2];
let hex_g = &filename[2..4];
let hex_b = &filename[4..6];
color_samples.push(ColorSample {
filename: filename.into(),
r: u8::from_str_radix(&hex_r, 16)?,
g: u8::from_str_radix(&hex_g, 16)?,
b: u8::from_str_radix(&hex_b, 16)?,
raw_bytes: f.contents(),
image: None,
});
}
}
log("Done reading image samples");
Ok(color_samples)
}
#[derive(Debug)]
pub struct ColorSample {
pub filename: String,
pub r: u8,
pub g: u8,
pub b: u8,
pub raw_bytes: &'static [u8],
pub image: Option<RgbaImage>,
}
#[wasm_bindgen]
extern "C" {
#[wasm_bindgen(js_namespace = console)]
pub fn log(s: &str);
#[wasm_bindgen(js_namespace = console, js_name = log)]
fn log_u32(a: u32);
#[wasm_bindgen(js_namespace = console, js_name = log)]
fn log_many(a: &str, b: &str);
}

View file

Before

Width:  |  Height:  |  Size: 20 KiB

After

Width:  |  Height:  |  Size: 20 KiB

View file

Before

Width:  |  Height:  |  Size: 22 KiB

After

Width:  |  Height:  |  Size: 22 KiB

View file

Before

Width:  |  Height:  |  Size: 15 KiB

After

Width:  |  Height:  |  Size: 15 KiB

View file

Before

Width:  |  Height:  |  Size: 16 KiB

After

Width:  |  Height:  |  Size: 16 KiB

View file

Before

Width:  |  Height:  |  Size: 16 KiB

After

Width:  |  Height:  |  Size: 16 KiB

View file

Before

Width:  |  Height:  |  Size: 15 KiB

After

Width:  |  Height:  |  Size: 15 KiB

View file

Before

Width:  |  Height:  |  Size: 17 KiB

After

Width:  |  Height:  |  Size: 17 KiB

View file

Before

Width:  |  Height:  |  Size: 15 KiB

After

Width:  |  Height:  |  Size: 15 KiB

View file

Before

Width:  |  Height:  |  Size: 17 KiB

After

Width:  |  Height:  |  Size: 17 KiB

View file

Before

Width:  |  Height:  |  Size: 16 KiB

After

Width:  |  Height:  |  Size: 16 KiB

View file

Before

Width:  |  Height:  |  Size: 14 KiB

After

Width:  |  Height:  |  Size: 14 KiB

View file

Before

Width:  |  Height:  |  Size: 14 KiB

After

Width:  |  Height:  |  Size: 14 KiB

View file

Before

Width:  |  Height:  |  Size: 18 KiB

After

Width:  |  Height:  |  Size: 18 KiB

View file

Before

Width:  |  Height:  |  Size: 17 KiB

After

Width:  |  Height:  |  Size: 17 KiB

View file

Before

Width:  |  Height:  |  Size: 12 KiB

After

Width:  |  Height:  |  Size: 12 KiB

View file

Before

Width:  |  Height:  |  Size: 44 KiB

After

Width:  |  Height:  |  Size: 44 KiB

View file

Before

Width:  |  Height:  |  Size: 11 KiB

After

Width:  |  Height:  |  Size: 11 KiB

View file

Before

Width:  |  Height:  |  Size: 27 KiB

After

Width:  |  Height:  |  Size: 27 KiB

View file

Before

Width:  |  Height:  |  Size: 12 KiB

After

Width:  |  Height:  |  Size: 12 KiB

View file

Before

Width:  |  Height:  |  Size: 17 KiB

After

Width:  |  Height:  |  Size: 17 KiB

View file

Before

Width:  |  Height:  |  Size: 11 KiB

After

Width:  |  Height:  |  Size: 11 KiB

View file

Before

Width:  |  Height:  |  Size: 16 KiB

After

Width:  |  Height:  |  Size: 16 KiB

View file

Before

Width:  |  Height:  |  Size: 24 KiB

After

Width:  |  Height:  |  Size: 24 KiB

View file

Before

Width:  |  Height:  |  Size: 19 KiB

After

Width:  |  Height:  |  Size: 19 KiB

View file

Before

Width:  |  Height:  |  Size: 13 KiB

After

Width:  |  Height:  |  Size: 13 KiB

View file

Before

Width:  |  Height:  |  Size: 12 KiB

After

Width:  |  Height:  |  Size: 12 KiB

View file

Before

Width:  |  Height:  |  Size: 20 KiB

After

Width:  |  Height:  |  Size: 20 KiB

View file

Before

Width:  |  Height:  |  Size: 21 KiB

After

Width:  |  Height:  |  Size: 21 KiB

View file

Before

Width:  |  Height:  |  Size: 16 KiB

After

Width:  |  Height:  |  Size: 16 KiB

View file

Before

Width:  |  Height:  |  Size: 11 KiB

After

Width:  |  Height:  |  Size: 11 KiB

View file

Before

Width:  |  Height:  |  Size: 23 KiB

After

Width:  |  Height:  |  Size: 23 KiB

View file

Before

Width:  |  Height:  |  Size: 20 KiB

After

Width:  |  Height:  |  Size: 20 KiB

View file

Before

Width:  |  Height:  |  Size: 14 KiB

After

Width:  |  Height:  |  Size: 14 KiB

View file

Before

Width:  |  Height:  |  Size: 18 KiB

After

Width:  |  Height:  |  Size: 18 KiB

View file

Before

Width:  |  Height:  |  Size: 13 KiB

After

Width:  |  Height:  |  Size: 13 KiB

View file

Before

Width:  |  Height:  |  Size: 13 KiB

After

Width:  |  Height:  |  Size: 13 KiB

View file

Before

Width:  |  Height:  |  Size: 19 KiB

After

Width:  |  Height:  |  Size: 19 KiB

View file

Before

Width:  |  Height:  |  Size: 16 KiB

After

Width:  |  Height:  |  Size: 16 KiB

View file

Before

Width:  |  Height:  |  Size: 63 KiB

After

Width:  |  Height:  |  Size: 63 KiB

View file

Before

Width:  |  Height:  |  Size: 24 KiB

After

Width:  |  Height:  |  Size: 24 KiB

View file

Before

Width:  |  Height:  |  Size: 53 KiB

After

Width:  |  Height:  |  Size: 53 KiB

View file

Before

Width:  |  Height:  |  Size: 21 KiB

After

Width:  |  Height:  |  Size: 21 KiB

View file

Before

Width:  |  Height:  |  Size: 32 KiB

After

Width:  |  Height:  |  Size: 32 KiB

View file

Before

Width:  |  Height:  |  Size: 18 KiB

After

Width:  |  Height:  |  Size: 18 KiB

View file

Before

Width:  |  Height:  |  Size: 18 KiB

After

Width:  |  Height:  |  Size: 18 KiB

View file

Before

Width:  |  Height:  |  Size: 19 KiB

After

Width:  |  Height:  |  Size: 19 KiB

View file

Before

Width:  |  Height:  |  Size: 18 KiB

After

Width:  |  Height:  |  Size: 18 KiB

View file

Before

Width:  |  Height:  |  Size: 21 KiB

After

Width:  |  Height:  |  Size: 21 KiB

View file

Before

Width:  |  Height:  |  Size: 18 KiB

After

Width:  |  Height:  |  Size: 18 KiB

View file

Before

Width:  |  Height:  |  Size: 18 KiB

After

Width:  |  Height:  |  Size: 18 KiB

View file

Before

Width:  |  Height:  |  Size: 13 KiB

After

Width:  |  Height:  |  Size: 13 KiB

View file

Before

Width:  |  Height:  |  Size: 17 KiB

After

Width:  |  Height:  |  Size: 17 KiB

View file

Before

Width:  |  Height:  |  Size: 20 KiB

After

Width:  |  Height:  |  Size: 20 KiB

View file

Before

Width:  |  Height:  |  Size: 52 KiB

After

Width:  |  Height:  |  Size: 52 KiB

View file

Before

Width:  |  Height:  |  Size: 19 KiB

After

Width:  |  Height:  |  Size: 19 KiB

View file

Before

Width:  |  Height:  |  Size: 23 KiB

After

Width:  |  Height:  |  Size: 23 KiB

View file

Before

Width:  |  Height:  |  Size: 17 KiB

After

Width:  |  Height:  |  Size: 17 KiB

View file

Before

Width:  |  Height:  |  Size: 18 KiB

After

Width:  |  Height:  |  Size: 18 KiB

View file

Before

Width:  |  Height:  |  Size: 17 KiB

After

Width:  |  Height:  |  Size: 17 KiB

View file

Before

Width:  |  Height:  |  Size: 19 KiB

After

Width:  |  Height:  |  Size: 19 KiB

View file

Before

Width:  |  Height:  |  Size: 14 KiB

After

Width:  |  Height:  |  Size: 14 KiB

View file

Before

Width:  |  Height:  |  Size: 19 KiB

After

Width:  |  Height:  |  Size: 19 KiB

View file

Before

Width:  |  Height:  |  Size: 38 KiB

After

Width:  |  Height:  |  Size: 38 KiB

View file

Before

Width:  |  Height:  |  Size: 23 KiB

After

Width:  |  Height:  |  Size: 23 KiB

View file

Before

Width:  |  Height:  |  Size: 14 KiB

After

Width:  |  Height:  |  Size: 14 KiB

View file

Before

Width:  |  Height:  |  Size: 19 KiB

After

Width:  |  Height:  |  Size: 19 KiB

View file

Before

Width:  |  Height:  |  Size: 18 KiB

After

Width:  |  Height:  |  Size: 18 KiB

View file

Before

Width:  |  Height:  |  Size: 18 KiB

After

Width:  |  Height:  |  Size: 18 KiB

View file

Before

Width:  |  Height:  |  Size: 20 KiB

After

Width:  |  Height:  |  Size: 20 KiB

View file

Before

Width:  |  Height:  |  Size: 23 KiB

After

Width:  |  Height:  |  Size: 23 KiB

View file

Before

Width:  |  Height:  |  Size: 41 KiB

After

Width:  |  Height:  |  Size: 41 KiB

View file

Before

Width:  |  Height:  |  Size: 21 KiB

After

Width:  |  Height:  |  Size: 21 KiB

View file

Before

Width:  |  Height:  |  Size: 18 KiB

After

Width:  |  Height:  |  Size: 18 KiB

View file

Before

Width:  |  Height:  |  Size: 18 KiB

After

Width:  |  Height:  |  Size: 18 KiB

View file

Before

Width:  |  Height:  |  Size: 17 KiB

After

Width:  |  Height:  |  Size: 17 KiB

View file

Before

Width:  |  Height:  |  Size: 18 KiB

After

Width:  |  Height:  |  Size: 18 KiB

View file

Before

Width:  |  Height:  |  Size: 46 KiB

After

Width:  |  Height:  |  Size: 46 KiB

View file

Before

Width:  |  Height:  |  Size: 48 KiB

After

Width:  |  Height:  |  Size: 48 KiB

View file

Before

Width:  |  Height:  |  Size: 21 KiB

After

Width:  |  Height:  |  Size: 21 KiB

View file

Before

Width:  |  Height:  |  Size: 48 KiB

After

Width:  |  Height:  |  Size: 48 KiB

Some files were not shown because too many files have changed in this diff Show more