it's working and performant, only the median algo isn't (performant)
8
.idea/.gitignore
generated
vendored
Normal file
|
|
@ -0,0 +1,8 @@
|
|||
# Default ignored files
|
||||
/shelf/
|
||||
/workspace.xml
|
||||
# Editor-based HTTP Client requests
|
||||
/httpRequests/
|
||||
# Datasource local storage ignored files
|
||||
/dataSources/
|
||||
/dataSources.local.xml
|
||||
6
.idea/misc.xml
generated
Normal file
|
|
@ -0,0 +1,6 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="ProjectRootManager">
|
||||
<output url="file://$PROJECT_DIR$/out" />
|
||||
</component>
|
||||
</project>
|
||||
8
.idea/modules.xml
generated
Normal file
|
|
@ -0,0 +1,8 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="ProjectModuleManager">
|
||||
<modules>
|
||||
<module fileurl="file://$PROJECT_DIR$/webpack_demo.iml" filepath="$PROJECT_DIR$/webpack_demo.iml" />
|
||||
</modules>
|
||||
</component>
|
||||
</project>
|
||||
6
.idea/vcs.xml
generated
Normal file
|
|
@ -0,0 +1,6 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="VcsDirectoryMappings">
|
||||
<mapping directory="" vcs="Git" />
|
||||
</component>
|
||||
</project>
|
||||
6
.idea/zigbrains.xml
generated
Normal file
|
|
@ -0,0 +1,6 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="ZLSSettings">
|
||||
<option name="initialAutodetectHasBeenDone" value="true" />
|
||||
</component>
|
||||
</project>
|
||||
|
|
@ -21,7 +21,7 @@
|
|||
<div class="content" id="images">
|
||||
<div id="image_container"></div>
|
||||
|
||||
<canvas id="canvas"></canvas>
|
||||
<canvas id="canvas" style="visibility: hidden;"></canvas>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
|||
1353
server/Cargo.lock
generated
|
|
@ -1,20 +0,0 @@
|
|||
[package]
|
||||
edition = "2021"
|
||||
name = "spiegel-server"
|
||||
version = "0.1.0"
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[dependencies]
|
||||
axum = { version = "0.7" }
|
||||
serde = { version = "1.0", features = ["derive"] }
|
||||
serde_json = "1.0"
|
||||
tokio = { version = "1.36", features = ["full"] }
|
||||
tracing = "0.1"
|
||||
tracing-subscriber = { version = "0.3", features = ["env-filter"] }
|
||||
image = "0.24.1"
|
||||
anyhow = "1.0"
|
||||
include_dir = "0.7.3"
|
||||
tokio-util = { version = "0.7", features = ["io"] }
|
||||
tower = { version = "0.4", features = ["util"] }
|
||||
tower-http = { version = "0.5.0", features = ["fs", "trace"] }
|
||||
|
|
@ -1,74 +0,0 @@
|
|||
use std::sync::OnceLock;
|
||||
|
||||
use include_dir::{include_dir, Dir, DirEntry};
|
||||
|
||||
static SAMPLES: OnceLock<Vec<ColorSample>> = OnceLock::new();
|
||||
static SAMPLES_DIR: Dir = include_dir!("src/samples");
|
||||
|
||||
pub fn init() {
|
||||
SAMPLES.get_or_init(|| {
|
||||
println!("reading image samples");
|
||||
read_color_samples().unwrap()
|
||||
});
|
||||
}
|
||||
|
||||
pub fn get_closest_color<'a>(color: &String) -> &'a ColorSample {
|
||||
let color_samples = SAMPLES.get().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, color);
|
||||
if diff < min_diff {
|
||||
closest = Some(sample);
|
||||
min_diff = diff;
|
||||
}
|
||||
}
|
||||
|
||||
closest.unwrap()
|
||||
}
|
||||
|
||||
/// returns squared euclidian color distance
|
||||
/// as if colors were points in 3d space
|
||||
fn get_distance(r1: u8, g1: u8, b1: u8, rgb: &String) -> f32 {
|
||||
let r2 = u8::from_str_radix(&rgb[0..2], 16).unwrap();
|
||||
let g2 = u8::from_str_radix(&rgb[2..4], 16).unwrap();
|
||||
let b2 = u8::from_str_radix(&rgb[4..6], 16).unwrap();
|
||||
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 sample_image = f.contents();
|
||||
|
||||
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)?,
|
||||
image: sample_image,
|
||||
});
|
||||
}
|
||||
}
|
||||
println!("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 image: &'static [u8],
|
||||
}
|
||||
|
|
@ -1,33 +0,0 @@
|
|||
use axum::extract::Path;
|
||||
use axum::http::{header, StatusCode};
|
||||
use axum::response::IntoResponse;
|
||||
use axum::{routing::get, Router};
|
||||
use spiegel_server::{get_closest_color, init};
|
||||
|
||||
/// serves images from memory
|
||||
#[tokio::main]
|
||||
async fn main() -> anyhow::Result<()> {
|
||||
tracing_subscriber::fmt::init();
|
||||
init();
|
||||
let app = Router::new().route("/api/color/:rgb_hex", get(fetch_nearest_color));
|
||||
|
||||
let listener = tokio::net::TcpListener::bind("0.0.0.0:3000").await.unwrap();
|
||||
println!("started server on http://0.0.0.0:3000");
|
||||
axum::serve(listener, app).await.unwrap();
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn fetch_nearest_color(
|
||||
Path(rgb_hex): Path<String>,
|
||||
) -> Result<impl IntoResponse, (StatusCode, String)> {
|
||||
if rgb_hex.len() != 6 {
|
||||
return Err((
|
||||
StatusCode::BAD_REQUEST,
|
||||
"input should be color hex, eg AA11CC".into(),
|
||||
));
|
||||
}
|
||||
let closest = get_closest_color(&rgb_hex);
|
||||
|
||||
let headers = [(header::CONTENT_TYPE, "image/jpeg")];
|
||||
Ok((headers, closest.image))
|
||||
}
|
||||
|
Before Width: | Height: | Size: 5.9 MiB After Width: | Height: | Size: 5.9 MiB |
|
|
@ -1,332 +0,0 @@
|
|||
export const filters = (module) => {
|
||||
let filter_dict = {
|
||||
grayscale: function () {
|
||||
return module.grayscale(rust_image);
|
||||
},
|
||||
offset_red: function () {
|
||||
return module.offset(rust_image, 0, 15);
|
||||
},
|
||||
offset_blue: function () {
|
||||
return module.offset(rust_image, 1, 15);
|
||||
},
|
||||
offset_green: function () {
|
||||
return module.offset(rust_image, 2, 15);
|
||||
},
|
||||
primary: function () {
|
||||
return module.primary(rust_image);
|
||||
},
|
||||
solarize: function () {
|
||||
return module.solarize(rust_image);
|
||||
},
|
||||
threshold: function () {
|
||||
return module.threshold(rust_image, 200);
|
||||
},
|
||||
sepia: function () {
|
||||
return module.sepia(rust_image);
|
||||
},
|
||||
decompose_min: function () {
|
||||
return module.decompose_min(rust_image);
|
||||
},
|
||||
decompose_max: function () {
|
||||
return module.decompose_max(rust_image);
|
||||
},
|
||||
grayscale_shades: function () {
|
||||
return module.grayscale_shades(rust_image);
|
||||
},
|
||||
red_channel_grayscale: function () {
|
||||
module.single_channel_grayscale(rust_image, 0);
|
||||
},
|
||||
green_channel_grayscale: function () {
|
||||
module.single_channel_grayscale(rust_image, 1);
|
||||
},
|
||||
blue_channel_grayscale: function () {
|
||||
module.single_channel_grayscale(rust_image, 2);
|
||||
},
|
||||
hue_rotate_hsl: function () {
|
||||
module.hue_rotate_hsl(rust_image, 0.3);
|
||||
},
|
||||
hue_rotate_hsv: function () {
|
||||
module.hue_rotate_hsv(rust_image, 0.3);
|
||||
},
|
||||
hue_rotate_lch: function () {
|
||||
module.hue_rotate_lch(rust_image, 0.3);
|
||||
},
|
||||
lighten_hsl: function () {
|
||||
module.lighten_hsl(rust_image, 0.3);
|
||||
},
|
||||
lighten_hsv: function () {
|
||||
module.lighten_hsv(rust_image, 0.3);
|
||||
},
|
||||
lighten_lch: function () {
|
||||
module.lighten_lch(rust_image, 0.3);
|
||||
},
|
||||
darken_hsl: function () {
|
||||
module.darken_hsl(rust_image, 0.3);
|
||||
},
|
||||
darken_hsv: function () {
|
||||
module.darken_hsv(rust_image, 0.3);
|
||||
},
|
||||
darken_lch: function () {
|
||||
module.darken_lch(rust_image, 0.3);
|
||||
},
|
||||
desaturate_hsl: function () {
|
||||
module.desaturate_hsl(rust_image, 0.3);
|
||||
},
|
||||
desaturate_hsv: function () {
|
||||
module.desaturate_hsv(rust_image, 0.3);
|
||||
},
|
||||
desaturate_lch: function () {
|
||||
module.desaturate_lch(rust_image, 0.3);
|
||||
},
|
||||
saturate_hsl: function () {
|
||||
module.saturate_hsl(rust_image, 0.3);
|
||||
},
|
||||
saturate_hsv: function () {
|
||||
module.saturate_hsv(rust_image, 0.3);
|
||||
},
|
||||
saturate_lch: function () {
|
||||
module.saturate_lch(rust_image, 0.3);
|
||||
},
|
||||
inc_red_channel: function () {
|
||||
return module.alter_red_channel(rust_image, 120);
|
||||
},
|
||||
inc_blue_channel: function () {
|
||||
return module.alter_channel(rust_image, 2, 100);
|
||||
},
|
||||
inc_green_channel: function () {
|
||||
return module.alter_channel(rust_image, 1, 100);
|
||||
},
|
||||
inc_two_channels: function () {
|
||||
return module.alter_channel(rust_image, 1, 30);
|
||||
},
|
||||
dec_red_channel: function () {
|
||||
return module.alter_channel(rust_image, 0, -30);
|
||||
},
|
||||
dec_blue_channel: function () {
|
||||
return module.alter_channel(rust_image, 2, -30);
|
||||
},
|
||||
dec_green_channel: function () {
|
||||
return module.alter_channel(rust_image, 1, -30);
|
||||
},
|
||||
swap_rg_channels: function () {
|
||||
return module.swap_channels(rust_image, 0, 1);
|
||||
},
|
||||
swap_rb_channels: function () {
|
||||
return module.swap_channels(rust_image, 0, 2);
|
||||
},
|
||||
swap_gb_channels: function () {
|
||||
return module.swap_channels(rust_image, 1, 2);
|
||||
},
|
||||
remove_red_channel: function () {
|
||||
return module.remove_red_channel(rust_image, 250);
|
||||
},
|
||||
remove_green_channel: function () {
|
||||
return module.remove_green_channel(rust_image, 250);
|
||||
},
|
||||
remove_blue_channel: function () {
|
||||
return module.remove_blue_channel(rust_image, 250);
|
||||
},
|
||||
emboss: function () {
|
||||
return module.emboss(rust_image);
|
||||
},
|
||||
box_blur: function () {
|
||||
return module.box_blur(rust_image);
|
||||
},
|
||||
sharpen: function () {
|
||||
return module.sharpen(rust_image);
|
||||
},
|
||||
lix: function () {
|
||||
return module.lix(rust_image);
|
||||
},
|
||||
neue: function () {
|
||||
return module.neue(rust_image);
|
||||
},
|
||||
ryo: function () {
|
||||
return module.ryo(rust_image);
|
||||
},
|
||||
gaussian_blur: function () {
|
||||
return module.gaussian_blur(rust_image);
|
||||
},
|
||||
inc_brightness: function () {
|
||||
return module.inc_brightness(rust_image, 20);
|
||||
},
|
||||
inc_lum: function () {
|
||||
return module.inc_luminosity(rust_image);
|
||||
},
|
||||
grayscale_human_corrected: function () {
|
||||
return module.grayscale_human_corrected(rust_image);
|
||||
},
|
||||
blend: function () {
|
||||
return module.blend(rust_image, rust_image2, "over");
|
||||
},
|
||||
overlay: function () {
|
||||
return module.blend(rust_image, rust_image2, "overlay");
|
||||
},
|
||||
atop: function () {
|
||||
return module.blend(rust_image, rust_image2, "atop");
|
||||
},
|
||||
xor: function () {
|
||||
return module.blend(rust_image, rust_image2, "xor");
|
||||
},
|
||||
plus: function () {
|
||||
return module.blend(rust_image, rust_image2, "plus");
|
||||
},
|
||||
multiply: function () {
|
||||
return module.blend(rust_image, rust_image2, "multiply");
|
||||
},
|
||||
burn: function () {
|
||||
return module.blend(rust_image, rust_image2, "burn");
|
||||
},
|
||||
difference: function () {
|
||||
return module.blend(rust_image, rust_image2, "difference");
|
||||
},
|
||||
soft_light: function () {
|
||||
return module.blend(rust_image, rust_image2, "soft_light");
|
||||
},
|
||||
hard_light: function () {
|
||||
return module.blend(rust_image, rust_image2, "hard_light");
|
||||
},
|
||||
dodge: function () {
|
||||
return module.blend(rust_image, rust_image2, "dodge");
|
||||
},
|
||||
exclusion: function () {
|
||||
return module.blend(rust_image, rust_image2, "exclusion");
|
||||
},
|
||||
lighten: function () {
|
||||
return module.blend(rust_image, rust_image2, "lighten");
|
||||
},
|
||||
darken: function () {
|
||||
return module.blend(rust_image, rust_image2, "darken");
|
||||
},
|
||||
watermark: function () {
|
||||
return module.watermark(rust_image, watermark_img, 10, 30);
|
||||
},
|
||||
text: function () {
|
||||
return module.draw_text(rust_image, "welcome to WebAssembly", 10, 20);
|
||||
},
|
||||
text_border: function () {
|
||||
return module.draw_text_with_border(
|
||||
rust_image,
|
||||
"welcome to the edge",
|
||||
10,
|
||||
20,
|
||||
);
|
||||
},
|
||||
test: function () {
|
||||
return module.filter(rust_image, "rosetint");
|
||||
},
|
||||
pink_noise: function () {
|
||||
return module.pink_noise(rust_image);
|
||||
},
|
||||
add_noise_rand: function () {
|
||||
return module.add_noise_rand(rust_image);
|
||||
},
|
||||
blend: function () {
|
||||
return module.blend(rust_image, rust_image2, "over");
|
||||
},
|
||||
overlay: function () {
|
||||
return module.blend(rust_image, rust_image2, "overlay");
|
||||
},
|
||||
atop: function () {
|
||||
return module.blend(rust_image, rust_image2, "atop");
|
||||
},
|
||||
plus: function () {
|
||||
return module.blend(rust_image, rust_image2, "plus");
|
||||
},
|
||||
multiply: function () {
|
||||
return module.blend(rust_image, rust_image2, "multiply");
|
||||
},
|
||||
burn: function () {
|
||||
return module.blend(rust_image, rust_image2, "burn");
|
||||
},
|
||||
difference: function () {
|
||||
return module.blend(rust_image, rust_image2, "difference");
|
||||
},
|
||||
soft_light: function () {
|
||||
return module.blend(rust_image, rust_image2, "soft_light");
|
||||
},
|
||||
hard_light: function () {
|
||||
return module.blend(rust_image, rust_image2, "hard_light");
|
||||
},
|
||||
dodge: function () {
|
||||
return module.blend(rust_image, rust_image2, "dodge");
|
||||
},
|
||||
exclusion: function () {
|
||||
return module.blend(rust_image, rust_image2, "exclusion");
|
||||
},
|
||||
lighten: function () {
|
||||
return module.blend(rust_image, rust_image2, "lighten");
|
||||
},
|
||||
darken: function () {
|
||||
return module.blend(rust_image, rust_image2, "darken");
|
||||
},
|
||||
watermark: function () {
|
||||
return module.watermark(rust_image, watermark_img, 10, 30);
|
||||
},
|
||||
text: function () {
|
||||
return module.draw_text(rust_image, "welcome to WebAssembly", 10, 20);
|
||||
},
|
||||
text_border: function () {
|
||||
return module.draw_text_with_border(
|
||||
rust_image,
|
||||
"welcome to the edge",
|
||||
10,
|
||||
20,
|
||||
);
|
||||
},
|
||||
blend: function () {
|
||||
return module.blend(rust_image, rust_image2, "over");
|
||||
},
|
||||
overlay: function () {
|
||||
return module.blend(rust_image, rust_image2, "overlay");
|
||||
},
|
||||
atop: function () {
|
||||
return module.blend(rust_image, rust_image2, "atop");
|
||||
},
|
||||
plus: function () {
|
||||
return module.blend(rust_image, rust_image2, "plus");
|
||||
},
|
||||
multiply: function () {
|
||||
return module.blend(rust_image, rust_image2, "multiply");
|
||||
},
|
||||
burn: function () {
|
||||
return module.blend(rust_image, rust_image2, "burn");
|
||||
},
|
||||
difference: function () {
|
||||
return module.blend(rust_image, rust_image2, "difference");
|
||||
},
|
||||
soft_light: function () {
|
||||
return module.blend(rust_image, rust_image2, "soft_light");
|
||||
},
|
||||
hard_light: function () {
|
||||
return module.blend(rust_image, rust_image2, "hard_light");
|
||||
},
|
||||
dodge: function () {
|
||||
return module.blend(rust_image, rust_image2, "dodge");
|
||||
},
|
||||
exclusion: function () {
|
||||
return module.blend(rust_image, rust_image2, "exclusion");
|
||||
},
|
||||
lighten: function () {
|
||||
return module.blend(rust_image, rust_image2, "lighten");
|
||||
},
|
||||
darken: function () {
|
||||
return module.blend(rust_image, rust_image2, "darken");
|
||||
},
|
||||
watermark: function () {
|
||||
return module.watermark(rust_image, watermark_img, 10, 30);
|
||||
},
|
||||
text: function () {
|
||||
return module.draw_text(rust_image, "welcome to WebAssembly", 10, 20);
|
||||
},
|
||||
text_border: function () {
|
||||
return module.draw_text_with_border(
|
||||
rust_image,
|
||||
"welcome to the edge",
|
||||
10,
|
||||
20,
|
||||
);
|
||||
},
|
||||
};
|
||||
return filter_dict;
|
||||
};
|
||||
|
|
@ -17,6 +17,7 @@ import("../../webclient/pkg").then((module) => {
|
|||
slider.value = 0;
|
||||
|
||||
function filterImage(event) {
|
||||
ctx.drawImage(sourceImage, 0, 0);
|
||||
let sliderValue = parseInt(event.target.value);
|
||||
blur_factor = sliderValue / 5;
|
||||
let rust_image = module.open_image(canvas, ctx);
|
||||
|
|
@ -26,10 +27,13 @@ import("../../webclient/pkg").then((module) => {
|
|||
|
||||
module.spiegel(rust_image, blur_factor);
|
||||
module.putImageData(canvas, ctx, rust_image);
|
||||
const msg = document.getElementById("msg");
|
||||
if (msg) {
|
||||
msg.remove();
|
||||
}
|
||||
const image_container = document.getElementById("image_container");
|
||||
let rect = image_container.getBoundingClientRect();
|
||||
canvas.setAttribute(
|
||||
"style",
|
||||
`visibility:visible;position:absolute;top:${rect.top};z-index:100`,
|
||||
);
|
||||
// image_container.setAttribute("style", "visibility:hidden");
|
||||
}
|
||||
|
||||
function setUpCanvas() {
|
||||
|
|
|
|||
895
webclient/Cargo.lock
generated
|
|
@ -14,9 +14,7 @@ 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"
|
||||
# wasm-bindgen-futures = "0.4"
|
||||
reqwest-wasm = "0.11"
|
||||
futures = "0.3"
|
||||
include_dir = "0.7.3"
|
||||
|
||||
[lib]
|
||||
crate-type = ["cdylib", "rlib"]
|
||||
|
|
|
|||
|
|
@ -1,14 +1,12 @@
|
|||
use anyhow::Error;
|
||||
use futures::executor::block_on;
|
||||
use image::io::Reader as ImageReader;
|
||||
use image::{GenericImageView, ImageBuffer, Pixel, Rgb, RgbImage, Rgba, RgbaImage};
|
||||
use image::{GenericImage, GenericImageView, Pixel, Rgba, RgbaImage};
|
||||
use photon_rs::PhotonImage;
|
||||
use std::collections::HashSet;
|
||||
use std::io::Cursor;
|
||||
use std::collections::LinkedList;
|
||||
mod quantizer;
|
||||
mod samples;
|
||||
|
||||
use samples::log;
|
||||
use wasm_bindgen::prelude::*;
|
||||
static BLACK: Rgba<u8> = Rgba([0, 0, 0, 0]);
|
||||
|
||||
/// Apply a median filter
|
||||
///
|
||||
|
|
@ -36,124 +34,71 @@ pub fn median(photon_image: &mut PhotonImage, x_radius: u32, y_radius: u32) {
|
|||
|
||||
#[wasm_bindgen]
|
||||
pub fn spiegel(photon_image: &mut PhotonImage, median_kernelsize: u32) {
|
||||
// let width = photon_image.get_width();
|
||||
// let height = photon_image.get_height();
|
||||
|
||||
// let raw_pixels = photon_image.get_raw_pixels().to_vec(); //argh!, slice should work but doesn't
|
||||
// let rs_image = RgbImage::from_vec(width, height, raw_pixels).unwrap();
|
||||
|
||||
// // // // println!("applying gaussian blur filter");
|
||||
// // let gauss = imageproc::filter::gaussian_blur_f32(rs_image, 4.0);
|
||||
// // // println!("applying median filter");
|
||||
// let median = imageproc::filter::median_filter(&rs_image, median_kernelsize, median_kernelsize)
|
||||
// .into_raw();
|
||||
// // // println!("applying color quantization filter");
|
||||
// // // let quantized = quantizer::quantize(&median, 256);
|
||||
|
||||
// // // println!("applying samples");
|
||||
// // let out = block_on(apply_samples_to_image(&mut median));
|
||||
|
||||
// *photon_image = PhotonImage::new(median, width, height);
|
||||
//
|
||||
let width = photon_image.get_width();
|
||||
let height = photon_image.get_height();
|
||||
|
||||
if width == 0 || height == 0 {
|
||||
return;
|
||||
}
|
||||
|
||||
samples::init();
|
||||
let raw_pixels = photon_image.get_raw_pixels().to_vec(); //argh!, slice should work but doesn't
|
||||
let rs_image = RgbaImage::from_vec(width, height, raw_pixels).unwrap();
|
||||
|
||||
let mut filtered =
|
||||
imageproc::filter::median_filter(&rs_image, median_kernelsize, median_kernelsize);
|
||||
let out = block_on(apply_samples_to_image(&mut filtered));
|
||||
|
||||
let out = RgbaImage::from_vec(width, height, raw_pixels).unwrap();
|
||||
log(&format!("stort"));
|
||||
// let out = imageproc::filter::gaussian_blur_f32(&rs_image, 3.0);
|
||||
// log(&format!("gaussian done"));
|
||||
// let mut out = quantizer::quantize(&out, 256);
|
||||
let mut out = imageproc::filter::median_filter(&out, median_kernelsize, median_kernelsize);
|
||||
//
|
||||
log(&format!("median done"));
|
||||
let out = apply_samples_to_image(&mut out);
|
||||
log(&format!("applying done"));
|
||||
*photon_image = PhotonImage::new(out.into_raw(), width, height);
|
||||
}
|
||||
|
||||
async fn apply_samples_to_image(src: &mut RgbaImage) -> RgbaImage {
|
||||
log("applying");
|
||||
let mut imgbuf = RgbaImage::new(src.width(), src.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() {
|
||||
for x in 0..src.width() {
|
||||
let pixel = &src.unsafe_get_pixel(x, y);
|
||||
if imgbuf.unsafe_get_pixel(x, y).channels() == [0, 0, 0] {
|
||||
if let Ok(sample) = get_image(pixel).await {
|
||||
fill(src, sample, &mut imgbuf, pixel, x, y);
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
imgbuf
|
||||
}
|
||||
|
||||
#[wasm_bindgen]
|
||||
extern "C" {
|
||||
// Use `js_namespace` here to bind `console.log(..)` instead of just
|
||||
// `log(..)`
|
||||
#[wasm_bindgen(js_namespace = console)]
|
||||
fn log(s: &str);
|
||||
|
||||
// The `console.log` is quite polymorphic, so we can bind it with multiple
|
||||
// signatures. Note that we need to use `js_name` to ensure we always call
|
||||
// `log` in JS.
|
||||
#[wasm_bindgen(js_namespace = console, js_name = log)]
|
||||
fn log_u32(a: u32);
|
||||
|
||||
// Multiple arguments too!
|
||||
#[wasm_bindgen(js_namespace = console, js_name = log)]
|
||||
fn log_many(a: &str, b: &str);
|
||||
}
|
||||
|
||||
async fn get_image(pixel: &Rgba<u8>) -> anyhow::Result<RgbaImage, Error> {
|
||||
let rgb = format!("{:02X?}{:02X?}{:02X?}", pixel[0], pixel[1], pixel[2]);
|
||||
log(&format!("get {}", rgb));
|
||||
|
||||
let bytes = reqwest_wasm::get(format!("/api/color/{}", rgb))
|
||||
.await?
|
||||
.bytes()
|
||||
.await?
|
||||
.to_vec();
|
||||
|
||||
Ok(ImageReader::new(Cursor::new(bytes))
|
||||
.with_guessed_format()
|
||||
.unwrap()
|
||||
.decode()?
|
||||
.as_rgba8()
|
||||
.unwrap()
|
||||
.clone())
|
||||
|
||||
// should probably cache it
|
||||
out
|
||||
}
|
||||
|
||||
fn fill(
|
||||
src: &mut RgbaImage,
|
||||
sample: RgbaImage,
|
||||
sample: &RgbaImage,
|
||||
dest: &mut RgbaImage,
|
||||
color: &Rgba<u8>,
|
||||
color: Rgba<u8>,
|
||||
px: u32,
|
||||
py: u32,
|
||||
) {
|
||||
if color.channels() == [0, 0, 0, 0] {
|
||||
return;
|
||||
}
|
||||
unsafe {
|
||||
let height = sample.height();
|
||||
let width = sample.width();
|
||||
let mut points = List::new();
|
||||
if is_same(src.get_pixel(px, py), &color) {
|
||||
points.push(Point { x: px, y: py });
|
||||
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(point) = points.pop() {
|
||||
let orig_pixel = src.get_pixel(point.x, point.y);
|
||||
let x = point.x;
|
||||
let y = point.y;
|
||||
if src.get_pixel(x, y).channels() != [0, 0, 0] {
|
||||
if is_same(orig_pixel, &color) {
|
||||
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 {
|
||||
|
|
@ -162,19 +107,19 @@ fn fill(
|
|||
while yy >= height {
|
||||
yy -= height;
|
||||
}
|
||||
dest.put_pixel(x, y, *sample.get_pixel(xx, yy));
|
||||
src.put_pixel(x, y, Rgba([0, 0, 0, 0]));
|
||||
dest.unsafe_put_pixel(x, y, sample.unsafe_get_pixel(xx, yy));
|
||||
src.unsafe_put_pixel(x, y, BLACK);
|
||||
if x > 1 {
|
||||
points.push(Point::new(x - 1, y));
|
||||
points.push_front(Coord(x - 1, y));
|
||||
}
|
||||
if y > 1 {
|
||||
points.push(Point::new(x, y - 1));
|
||||
points.push_front(Coord(x, y - 1));
|
||||
}
|
||||
if x < src.width() - 1 {
|
||||
points.push(Point::new(x + 1, y));
|
||||
points.push_front(Coord(x + 1, y));
|
||||
}
|
||||
if y < src.height() - 1 {
|
||||
points.push(Point::new(x, y + 1));
|
||||
points.push_front(Coord(x, y + 1));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -184,8 +129,9 @@ fn fill(
|
|||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn is_same(p1: &Rgba<u8>, p2: &Rgba<u8>) -> bool {
|
||||
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
|
||||
|
|
@ -193,86 +139,5 @@ fn is_same(p1: &Rgba<u8>, p2: &Rgba<u8>) -> bool {
|
|||
&& i16::abs(p1[2] as i16 - p2[2] as i16) < 4
|
||||
}
|
||||
|
||||
fn get_distance(r: u8, g: u8, b: u8, c2: &Rgba<u8>) -> f32 {
|
||||
let red_dif = r as f32 - c2.channels()[0] as f32;
|
||||
let green_dif = g as f32 - c2.channels()[1] as f32;
|
||||
let blue_dif = b as f32 - c2.channels()[2] as f32;
|
||||
// ignore alpha channel
|
||||
return f32::sqrt(red_dif * red_dif + green_dif * green_dif + blue_dif * blue_dif);
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
struct Point<T> {
|
||||
x: T,
|
||||
y: T,
|
||||
}
|
||||
|
||||
impl<T> Point<T> {
|
||||
fn new(x: T, y: T) -> Self {
|
||||
Self { x, y }
|
||||
}
|
||||
}
|
||||
|
||||
struct ColorSample {
|
||||
r: u8,
|
||||
g: u8,
|
||||
b: u8,
|
||||
image: RgbImage,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
struct List {
|
||||
head: Option<Box<Node>>,
|
||||
}
|
||||
|
||||
impl List {
|
||||
fn new() -> Self {
|
||||
Self { head: None }
|
||||
}
|
||||
fn push(&mut self, point: Point<u32>) {
|
||||
let new_node = Box::new(Node {
|
||||
value: point,
|
||||
next: self.head.take(),
|
||||
});
|
||||
|
||||
self.head = Some(new_node);
|
||||
}
|
||||
|
||||
fn pop(&mut self) -> Option<Point<u32>> {
|
||||
self.head.take().map(|node| {
|
||||
self.head = node.next;
|
||||
node.value
|
||||
})
|
||||
}
|
||||
|
||||
fn is_empty(&self) -> bool {
|
||||
self.head.is_none()
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
struct Node {
|
||||
value: Point<u32>,
|
||||
next: Option<Box<Node>>,
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test() {
|
||||
let raw_pix = vec![
|
||||
134, 122, 131, 255, 131, 131, 139, 255, 135, 134, 137, 255, 138, 134, 130, 255, 126,
|
||||
125, 119, 255, 131, 134, 129, 255, 137, 134, 132, 255, 130, 126, 130, 255, 132, 125,
|
||||
132, 255, 122, 142, 129, 255, 134, 135, 128, 255, 138, 120, 125, 255, 125, 134, 110,
|
||||
255, 121, 122, 137, 255, 141, 140, 141, 255, 125, 144, 120, 255,
|
||||
];
|
||||
|
||||
let photon_image = PhotonImage::new(raw_pix, 4, 4);
|
||||
|
||||
let colors = determine_colors(&photon_image);
|
||||
println!("{:?}", colors);
|
||||
}
|
||||
}
|
||||
struct Coord(u32, u32);
|
||||
|
|
|
|||
|
|
@ -1,10 +1,10 @@
|
|||
use std::{cell::RefCell, rc::Rc};
|
||||
|
||||
use image::{Pixel, Rgb, RgbImage};
|
||||
use image::{Pixel, Rgba, RgbaImage};
|
||||
|
||||
const MAX_LEVEL: usize = 5;
|
||||
|
||||
pub(crate) fn quantize(image: &RgbImage, num_colors: usize) -> RgbImage {
|
||||
pub(crate) fn quantize(image: &RgbaImage, num_colors: usize) -> RgbaImage {
|
||||
let mut quantizer = OctTreeQuantizer::new(num_colors);
|
||||
quantizer.quantize(image)
|
||||
}
|
||||
|
|
@ -33,7 +33,7 @@ impl OctTreeQuantizer {
|
|||
new_quantizer
|
||||
}
|
||||
|
||||
pub fn quantize(&mut self, image: &RgbImage) -> RgbImage {
|
||||
pub fn quantize(&mut self, image: &RgbaImage) -> RgbaImage {
|
||||
for pixel in image.pixels() {
|
||||
self.insert_color(pixel, Rc::clone(&self.root));
|
||||
|
||||
|
|
@ -47,7 +47,7 @@ impl OctTreeQuantizer {
|
|||
}
|
||||
let table = self.build_color_table();
|
||||
|
||||
let mut imgbuf = RgbImage::new(image.width(), image.height());
|
||||
let mut imgbuf = RgbaImage::new(image.width(), image.height());
|
||||
for (x, y, pixel) in image.enumerate_pixels() {
|
||||
if let Some(index) = self.get_index_for_color(pixel, &self.root) {
|
||||
let color = &table[index];
|
||||
|
|
@ -97,12 +97,12 @@ impl OctTreeQuantizer {
|
|||
get_index_for_color(&self, color, 0, node)
|
||||
}
|
||||
|
||||
fn build_color_table(&mut self) -> Vec<Option<Rgb<u8>>> {
|
||||
fn build_color_table(&mut self) -> Vec<Option<Rgba<u8>>> {
|
||||
//nested function that is called recursively
|
||||
fn build_color_table(
|
||||
quantizer: &mut OctTreeQuantizer,
|
||||
node: &Rc<RefCell<OctTreeNode>>,
|
||||
table: &mut Vec<Option<Rgb<u8>>>,
|
||||
table: &mut Vec<Option<Rgba<u8>>>,
|
||||
index: usize,
|
||||
) -> usize {
|
||||
if quantizer.colors > quantizer.maximum_colors {
|
||||
|
|
@ -112,10 +112,11 @@ impl OctTreeQuantizer {
|
|||
{
|
||||
let node = node.borrow();
|
||||
let count = node.count;
|
||||
table[index] = Some(Rgb::from([
|
||||
table[index] = Some(Rgba::from([
|
||||
(node.total_red / count as u32) as u8,
|
||||
(node.total_green / count as u32) as u8,
|
||||
(node.total_blue / count as u32) as u8,
|
||||
255,
|
||||
]));
|
||||
}
|
||||
node.borrow_mut().index = index;
|
||||
|
|
@ -138,7 +139,7 @@ impl OctTreeQuantizer {
|
|||
}
|
||||
}
|
||||
|
||||
let mut table: Vec<Option<Rgb<u8>>> = vec![None; self.colors];
|
||||
let mut table: Vec<Option<Rgba<u8>>> = vec![None; self.colors];
|
||||
let node = Rc::clone(&self.root);
|
||||
build_color_table(self, &node, &mut table, 0);
|
||||
table
|
||||
|
|
|
|||
|
|
@ -1,230 +1,99 @@
|
|||
use image::{load_from_memory_with_format, RgbaImage};
|
||||
use std::sync::OnceLock;
|
||||
use wasm_bindgen::prelude::*;
|
||||
|
||||
static MEM: OnceLock<Vec<&'static str>> = OnceLock::new();
|
||||
use include_dir::{include_dir, Dir, DirEntry};
|
||||
|
||||
pub fn samples() -> &'static Vec<&'static str> {
|
||||
MEM.get_or_init(|| {
|
||||
vec![
|
||||
"12110f", "131211", "21201b", "21201d", "212213", "22211b", "23221d", "23221f",
|
||||
"24231d", "ede4d0", "ede4d2", "ede5b8", "edea91", "edea92", "eee6d1", "efe2b2",
|
||||
"252520", "262527", "262628", "272826", "291f19", "292927", "292a29", "2a2a28",
|
||||
"2a3960", "2a3961", "2b3a5f", "2c2c24", "2c3b64", "2c3c40", "2e2d26", "2e2d28",
|
||||
"2e2d29", "30301c", "303030", "303131", "303632", "31302b", "313131", "32373a",
|
||||
"32373b", "333a73", "334840", "343a6f", "343b73", "344845", "353420", "35342f",
|
||||
"353a39", "353b43", "363531", "363c71", "36414c", "364968", "373530", "373a54",
|
||||
"373b46", "373d2f", "373d47", "373d71", "382f1f", "383732", "383831", "383832",
|
||||
"383a46", "383c4f", "383d48", "384142", "38495a", "384b51", "393b39", "394235",
|
||||
"3a4042", "3a4242", "3b4435", "3b4a6b", "3b4f6b", "3c3b34", "3c4d5f", "3d342a",
|
||||
"3d4343", "3d4a63", "3e3524", "3e3830", "3e3e32", "3e3e4a", "3e3f27", "3e4543",
|
||||
"3f3f3a", "3f404f", "3f423b", "3f4b56", "3f527a", "3f5352", "40434b", "404358",
|
||||
"413327", "413d2c", "413f56", "41403d", "414646", "41494e", "424b4a", "43423c",
|
||||
"43475c", "434943", "434a54", "435969", "44414a", "444933", "444d4e", "453a2c",
|
||||
"453b2f", "454048", "454534", "45453e", "454c48", "454c4c", "463728", "463e36",
|
||||
"464226", "464540", "464832", "464e4d", "473e2f", "47443b", "474550", "475141",
|
||||
"475237", "475f6e", "484841", "484846", "484853", "484b43", "484c49", "485162",
|
||||
"49323c", "493c3b", "494941", "494a3b", "495950", "495c62", "495d6d", "4a4052",
|
||||
"4a4326", "4a4a42", "4a4c3f", "4a4c41", "4a5050", "4a5159", "4a5f6e", "4b3541",
|
||||
"4b4737", "4b4941", "4b4b44", "4b4b57", "4b4c4b", "4b4d4c", "4b4f41", "4c4b57",
|
||||
"4c4f59", "4c505b", "4d4533", "4d4b4b", "4d4b58", "4d4d44", "4d4d45", "4d4e47",
|
||||
"4d4f39", "4d545d", "4d5554", "4d5656", "4d616e", "4e3b2e", "4e3d1d", "4e4752",
|
||||
"4e4f4b", "4e5454", "4f3124", "4f3d29", "4f4735", "4f4b47", "4f4e43", "4f4f4e",
|
||||
"4f5145", "4f514f", "4f584f", "4f5857", "50281e", "50393a", "504543", "504729",
|
||||
"504f3b", "505052", "505440", "505542", "50574e", "505968", "50656a", "513c2e",
|
||||
"514131", "514636", "514c39", "515145", "515241", "515762", "515a57", "51646b",
|
||||
"52524a", "525a61", "525c55", "534836", "534a31", "535250", "535959", "544b3b",
|
||||
"544c3a", "545447", "54544c", "545531", "545650", "545863", "553d28", "55442a",
|
||||
"554936", "554a39", "554e30", "554e31", "555133", "555556", "555f54", "55698e",
|
||||
"556f73", "56422d", "564635", "565648", "565656", "565843", "565e46", "566448",
|
||||
"566454", "573823", "57422c", "574642", "574e3e", "575651", "57574f", "57584b",
|
||||
"575d54", "583f2d", "58462c", "584c3c", "58503f", "585348", "585763", "585a56",
|
||||
"585f69", "594a35", "59594d", "596063", "596068", "596562", "596e6f", "5a4826",
|
||||
"5a4a38", "5a4a3b", "5a552e", "5a5b4e", "5a5f49", "5a6168", "5a624d", "5a6674",
|
||||
"5b4332", "5b4636", "5b492c", "5b4a22", "5b5237", "5b542f", "5b5b50", "5b626c",
|
||||
"5b6651", "5b666e", "5c4331", "5c462e", "5c4831", "5c4a37", "5c4f2b", "5c534e",
|
||||
"5c5c5c", "5c5f68", "5c655f", "5c6f70", "5d3c2b", "5d3c2c", "5d4520", "5d4625",
|
||||
"5d495c", "5d4b31", "5d4b32", "5d4f4a", "5d5539", "5d5644", "5d5d55", "5d5e4f",
|
||||
"5e3d30", "5e3f32", "5e4625", "5e4c29", "5e4c3a", "5e4d34", "5e504c", "5e685d",
|
||||
"5e693e", "5e6b51", "5f402b", "5f4827", "5f4e28", "5f5443", "5f544f", "5f5741",
|
||||
"5f5a33", "5f6259", "5f664f", "5f6670", "5f6768", "5f6a4a", "60432b", "60452b",
|
||||
"604931", "60503a", "606a3e", "607c7b", "614336", "616451", "616839", "616a69",
|
||||
"616d9d", "61786a", "617963", "622f24", "623a26", "623a27", "623e2b", "624035",
|
||||
"62432b", "62492e", "62502f", "625a3a", "625b45", "626361", "63453b", "636547",
|
||||
"636563", "636744", "63674c", "636a61", "636c51", "638f7c", "638f7d", "643327",
|
||||
"64391f", "64482c", "644e3b", "644f2e", "645466", "64586c", "645a70", "645e37",
|
||||
"64645b", "646463", "653933", "654131", "654333", "654438", "65473a", "654f30",
|
||||
"654f42", "655230", "65532e", "655f4e", "656160", "656366", "656559", "656c64",
|
||||
"656c72", "656c75", "65705a", "663429", "664720", "66513d", "665d50", "665e4c",
|
||||
"666047", "66655e", "667e69", "673d2a", "674154", "674232", "674737", "674a2c",
|
||||
"674d37", "67514b", "675339", "675a3e", "675d51", "676568", "676b48", "676b5b",
|
||||
"678353", "682626", "682726", "683326", "683849", "683d2a", "684230", "684635",
|
||||
"684d3f", "68533a", "685340", "68604e", "686148", "68716a", "68726a", "68807d",
|
||||
"691e2c", "69432f", "694d24", "695857", "696051", "696343", "696960", "696f47",
|
||||
"697c90", "6a1f2d", "6a4029", "6a4125", "6a4428", "6a4631", "6a493a", "6a4a29",
|
||||
"6a4e21", "6a4e2f", "6a6a62", "6a7360", "6b2231", "6b3a21", "6b4026", "6b523b",
|
||||
"6b5933", "6b6251", "6b6f5c", "6b705a", "6b7375", "6c312d", "6c3727", "6c372b",
|
||||
"6c392a", "6c412b", "6c4331", "6c4738", "6c5928", "6c5937", "6c5a37", "6c5b34",
|
||||
"6c5e64", "6c616a", "6c633a", "6c6641", "6c7370", "6c8c6e", "6d4624", "6d4834",
|
||||
"6d492a", "6d4f23", "6d5058", "6d522d", "6d5732", "6d5a31", "6d5a33", "6d5a43",
|
||||
"6d6d65", "6d6e5f", "6d8759", "6e4028", "6e4624", "6e4a2e", "6e5034", "6e5631",
|
||||
"6e5b60", "6e635f", "6e6a3b", "6e7251", "6e7776", "6e8a7f", "6f4b39", "6f5233",
|
||||
"6f542f", "6f5830", "6f5936", "6f5958", "6f5d3a", "6f5e33", "6f6072", "6f7d76",
|
||||
"6f828c", "6f8f71", "70391d", "704022", "704620", "704f27", "705022", "70543b",
|
||||
"705528", "70554f", "70673e", "70693e", "706b45", "707369", "70796e", "70916b",
|
||||
"71301e", "714728", "714730", "714831", "714a30", "715c2d", "716068", "716235",
|
||||
"716857", "716a3c", "723932", "72422c", "72492d", "725627", "725733", "725922",
|
||||
"725a22", "725f40", "72603c", "72613a", "72726a", "73342e", "733834", "733e24",
|
||||
"735544", "73572d", "735b5b", "735e4e", "737365", "73794b", "73795b", "737963",
|
||||
"737f77", "742e34", "742f32", "74352d", "743d2e", "744833", "744f35", "745045",
|
||||
"745325", "745739", "74573c", "746234", "746264", "746457", "74705d", "747848",
|
||||
"747d78", "753034", "753d1f", "755d30", "756535", "756a44", "757144", "757365",
|
||||
"757568", "7583a6", "758d72", "763d34", "764650", "764d31", "765c4e", "766452",
|
||||
"766f52", "767241", "767244", "767251", "76766d", "773a3a", "774320", "774429",
|
||||
"77452d", "774b34", "775245", "77593a", "775c2f", "77653f", "776541", "777568",
|
||||
"777767", "783620", "783836", "78383b", "78452a", "78472f", "78482f", "785237",
|
||||
"785433", "785737", "785825", "785f3f", "78612c", "786539", "786879", "787245",
|
||||
"78766b", "787870", "787e67", "793135", "793620", "79383b", "79404d", "794456",
|
||||
"795b29", "79616b", "796853", "79827e", "7a3638", "7a3c48", "7a432c", "7a452d",
|
||||
"7a492d", "7a4b2f", "7a5628", "7a5b29", "7a5d24", "7a6950", "7a754a", "7a7e67",
|
||||
"7a8347", "7a8665", "7b3a27", "7b3c48", "7b3f21", "7b472a", "7b5139", "7b5c2e",
|
||||
"7b5f39", "7b604e", "7b6125", "7b6224", "7b625c", "7b6656", "7b6944", "7b7563",
|
||||
"7b7a6b", "7b7c6f", "7c3b34", "7c3e2a", "7c402d", "7c452b", "7c4542", "7c4a30",
|
||||
"7c4b2e", "7c5032", "7c5437", "7c5928", "7c5e31", "7c8164", "7c8379", "7c867f",
|
||||
"7c8c5d", "7d4624", "7d4d34", "7d5645", "7d5926", "7d5928", "7d602e", "7d6134",
|
||||
"7d806b", "7d8577", "7d877c", "7d877d", "7d8965", "7e4120", "7e472f", "7e4925",
|
||||
"7e4a37", "7e562d", "7e592d", "7e5d28", "7e7266", "7e726e", "7e7763", "7e7764",
|
||||
"7e7964", "7e887d", "7e8c71", "7e8e52", "7e9b69", "7f401e", "7f4838", "7f4d37",
|
||||
"7f5639", "7f5827", "7f5e60", "7f604d", "7f6156", "7f633d", "7f6b2e", "7f7347",
|
||||
"7f827a", "7f846a", "803d29", "803f2b", "80403b", "804937", "804b3d", "804e29",
|
||||
"804e2c", "805130", "805335", "805441", "805644", "805d44", "806051", "806c51",
|
||||
"807a63", "807c53", "807e71", "80855b", "808c68", "813a26", "813e31", "813f31",
|
||||
"814f30", "815230", "815737", "81583d", "815a32", "815b3d", "816b5b", "81734d",
|
||||
"817b5a", "81826e", "818370", "824432", "824e2c", "825037", "82552e", "82562e",
|
||||
"825e50", "826130", "826243", "826321", "826d37", "827443", "82846f", "828b5e",
|
||||
"829399", "833128", "834b31", "835231", "835341", "835747", "835949", "835f56",
|
||||
"836559", "836934", "836e42", "83856c", "838b4d", "84492a", "844d2b", "844f39",
|
||||
"845028", "845231", "845439", "84644f", "84655a", "847b47", "847e68", "84a162",
|
||||
"84a163", "853022", "85481e", "854854", "854c58", "855437", "855535", "85553e",
|
||||
"855b1f", "855e2e", "855e51", "856128", "856836", "856b35", "857a66", "85887c",
|
||||
"859156", "85b0ad", "863d37", "864536", "864a2b", "864c25", "86532e", "865a33",
|
||||
"866345", "866c3b", "866d35", "866e4a", "867348", "868159", "868550", "868c61",
|
||||
"869e61", "873e4f", "874724", "874e26", "874e39", "87522e", "87523c", "875844",
|
||||
"875b30", "875b3f", "875c4a", "875d34", "875d41", "876330", "877163", "87744e",
|
||||
"87744f", "877a54", "877d72", "877f6c", "87b1ad", "884834", "884e2c", "885837",
|
||||
"885c39", "885f41", "886055", "88685d", "88692f", "886a49", "886b31", "886e68",
|
||||
"886f30", "88775d", "887a4a", "887e72", "888168", "88835b", "888492", "8895a0",
|
||||
"894b36", "895c34", "895e4a", "896156", "896d46", "897232", "89726f", "89785b",
|
||||
"8a4a32", "8a5933", "8a5a42", "8a5f30", "8a633b", "8a6727", "8a6a2e", "8a7e46",
|
||||
"8a855b", "8a8e65", "8a9355", "8b4d30", "8b5745", "8b5a49", "8b604f", "8b6c63",
|
||||
"8b773d", "8b7d8e", "8ba784", "8c4d36", "8c552d", "8c5d44", "8c6249", "8c6853",
|
||||
"8c6b34", "8c6c26", "8c6e55", "8c6f36", "8c7754", "8c7d4d", "8c833b", "8c846e",
|
||||
"8c8568", "8c8762", "8c935c", "8c9a6e", "8d4328", "8d532b", "8d5634", "8d5734",
|
||||
"8d5937", "8d6349", "8d643c", "8d6535", "8d692e", "8d7262", "8d7539", "8d7760",
|
||||
"8d895a", "8d96a8", "8d978c", "8d9f7f", "8da96c", "8db29b", "8e4326", "8e4640",
|
||||
"8e4e2f", "8e5635", "8e714f", "8e783a", "8e7e54", "8e866f", "8e8955", "8e8b57",
|
||||
"8e8e4c", "8f4326", "8f4f2e", "8f542c", "8f5624", "8f5a2d", "8f5c37", "8f6141",
|
||||
"8f653b", "8f6659", "8f7065", "8f735c", "8f7631", "8f783b", "8f8f4b", "8fa27e",
|
||||
"90442f", "905121", "905229", "90523b", "905337", "905d3e", "906140", "906a41",
|
||||
"907741", "907b69", "908972", "90996b", "909971", "909c6e", "90aa84", "90ab6f",
|
||||
"913826", "914c27", "914f39", "915352", "915539", "915729", "91603c", "916947",
|
||||
"917849", "917a35", "91804b", "91967c", "91ac8c", "924c50", "92522e", "925428",
|
||||
"925b36", "925b39", "926649", "92694e", "926a4d", "926b42", "926d31", "926d32",
|
||||
"927339", "927a51", "927c32", "92834a", "928652", "934753", "934b26", "934b2d",
|
||||
"935826", "935c33", "936f34", "93724b", "93844d", "939254", "939977", "93a27b",
|
||||
"94533d", "94542f", "945528", "945732", "94573e", "945935", "945940", "94653a",
|
||||
"947454", "947836", "94894c", "948a62", "94915a", "949872", "949b73", "949c64",
|
||||
"949c68", "949d70", "949f72", "94af8c", "94b294", "955234", "95532e", "95532f",
|
||||
"95602a", "956a47", "957044", "957a52", "959c79", "959c7b", "964920", "965a37",
|
||||
"96602a", "966449", "966a46", "966e34", "967758", "967938", "969e70", "96a67f",
|
||||
"975457", "975b29", "976b46", "976c5c", "976e38", "976e50", "977145", "977332",
|
||||
"978560", "97a472", "97a965", "98472f", "98532d", "985a34", "986547", "986b34",
|
||||
"986c54", "98733a", "987a37", "987b6d", "988042", "98915b", "989864", "996a38",
|
||||
"996d41", "997054", "997242", "99915e", "999d7e", "99a096", "9a4c2d", "9a5a2e",
|
||||
"9a5a32", "9a5d2e", "9a642b", "9a661e", "9a7243", "9a7529", "9a772d", "9a7738",
|
||||
"9a7d63", "9a814f", "9a8b94", "9a8c56", "9a8e49", "9a934d", "9a9c71", "9ab594",
|
||||
"9ab595", "9b4a34", "9b502f", "9b5436", "9b5b2e", "9b5d49", "9b6435", "9b6647",
|
||||
"9b6a38", "9b7139", "9b734a", "9b7836", "9b7a44", "9b8352", "9b9559", "9c5121",
|
||||
"9c5932", "9c5d58", "9c6625", "9c6d3d", "9c7644", "9c7737", "9c7b5b", "9c7f3d",
|
||||
"9c8445", "9d3c25", "9d482c", "9d5131", "9d592b", "9d6031", "9d6d38", "9d7451",
|
||||
"9d754c", "9d7e49", "9d803c", "9d8f96", "9d903c", "9d9149", "9d9686", "9da3ad",
|
||||
"9e5137", "9e531f", "9e552d", "9e595c", "9e666b", "9e7849", "9e795c", "9e7b39",
|
||||
"9e8557", "9e9aa8", "9e9b67", "9f4e29", "9f5661", "9f5c3c", "9f6025", "9f632e",
|
||||
"9f633b", "9f642c", "9f673b", "9f6c3b", "9f714b", "9f7354", "9f774d", "9f792f",
|
||||
"9f7e3d", "9f7e6f", "9f804b", "9f876a", "9f9346", "9f9787", "9f9f95", "9fb168",
|
||||
"a04225", "a0462e", "a05227", "a05639", "a0623a", "a06343", "a07154", "a07244",
|
||||
"a08a43", "a0ad87", "a1442a", "a14729", "a15e29", "a16427", "a16730", "a16739",
|
||||
"a16d30", "a17034", "a17b4b", "a17e34", "a19258", "a25031", "a26320", "a26e41",
|
||||
"a2742b", "a28359", "a2844a", "a29eab", "a2a15f", "a2aa7d", "a3432b", "a34a2e",
|
||||
"a35a32", "a35c2a", "a3602a", "a3696e", "a3743a", "a37442", "a3753a", "a37636",
|
||||
"a3773d", "a38f3e", "a39251", "a39d7c", "a3ab84", "a44e2b", "a45132", "a45b20",
|
||||
"a46b35", "a46c77", "a4734d", "a4772e", "a47949", "a47e4f", "a47f5b", "a48455",
|
||||
"a48d5f", "a4a16c", "a4ad80", "a55825", "a56130", "a5613b", "a56639", "a56768",
|
||||
"a5693e", "a56a32", "a57042", "a57b3a", "a57c41", "a5947f", "a66139", "a66827",
|
||||
"a6722c", "a67555", "a67746", "a67a35", "a6814b", "a6823f", "a68630", "a69d54",
|
||||
"a7651c", "a76534", "a76834", "a76a31", "a77039", "a77459", "a7802e", "a7804e",
|
||||
"a78152", "a78336", "a78a54", "a78d5a", "a78e6c", "a79455", "a79f8d", "a7a4af",
|
||||
"a84d2d", "a85431", "a8623a", "a8775b", "a88057", "a8825c", "a88544", "a89671",
|
||||
"a94126", "a94c2f", "a95723", "a95923", "a96d33", "a97a3f", "a97b5b", "a9977e",
|
||||
"a99ba4", "a9a6a9", "aa4226", "aa4a22", "aa4b2e", "aa4d27", "aa5225", "aa6d2c",
|
||||
"aa6e1c", "aa883c", "aa8a75", "aa9144", "aa9163", "aaa6a7", "ab4a2c", "ab5312",
|
||||
"ab734f", "ab7e3d", "ab8146", "ab8d59", "ab954f", "ab965f", "ab994a", "ac5918",
|
||||
"ac682a", "ac6a2f", "ac6b72", "ac795c", "ac7a47", "ac7b5c", "ac8558", "ac8943",
|
||||
"ac8a44", "ac9459", "ad5e27", "ad6b21", "ad6e35", "ad7028", "ad793b", "ad7d3b",
|
||||
"ad8037", "ad8c3b", "ad8c61", "ad924f", "ada874", "adb588", "adc4a9", "ae5032",
|
||||
"ae7024", "ae7945", "ae812e", "ae8241", "ae8654", "ae8667", "ae895b", "ae8b5c",
|
||||
"ae8c36", "ae943c", "ae9546", "af7734", "af8860", "af9747", "af9a62", "af9d7f",
|
||||
"afb486", "afba86", "b04d2c", "b0883e", "b08e45", "b08f53", "b09050", "b0913a",
|
||||
"b0a64a", "b0ba8d", "b1513a", "b15732", "b1682d", "b16a2b", "b18540", "b18f5e",
|
||||
"b19458", "b19955", "b19c5c", "b1a55d", "b1bf9a", "b1c4a6", "b2543f", "b25528",
|
||||
"b26e43", "b2713a", "b27223", "b2731f", "b27329", "b27536", "b27836", "b27b28",
|
||||
"b27f27", "b28429", "b28738", "b28741", "b2894c", "b28f41", "b2944f", "b29b50",
|
||||
"b29c52", "b2a36f", "b2babb", "b2c199", "b34b30", "b37229", "b37944", "b37a47",
|
||||
"b38840", "b3a48c", "b46e1c", "b47149", "b4752a", "b4802f", "b4833c", "b49446",
|
||||
"b49a67", "b49e50", "b49f5c", "b4a04c", "b58a3c", "b58c44", "b58d6d", "b58e47",
|
||||
"b59b53", "b59d56", "b5a05c", "b5b7b4", "b5c6ad", "b6612e", "b68431", "b68b64",
|
||||
"b68f44", "b69642", "b6974a", "b6a56b", "b6b689", "b75129", "b7512e", "b77434",
|
||||
"b7752d", "b78f51", "b79454", "b7975b", "b79939", "b79c53", "b7a04f", "b7a256",
|
||||
"b85132", "b87e42", "b8834a", "b89169", "b89170", "b89248", "b89542", "b89a4c",
|
||||
"b89b5a", "b9581a", "b95d38", "b96c30", "b98138", "b9874d", "b9916a", "b99450",
|
||||
"b99642", "b99843", "b99b4c", "b9a783", "b9a890", "b9bd97", "b9bf95", "ba832c",
|
||||
"ba8a4b", "ba9a5a", "baa053", "bac8af", "bb7d34", "bb8c6a", "bb9235", "bb923d",
|
||||
"bb9b48", "bb9c4d", "bba14d", "bba25d", "bba362", "bba557", "bbab6a", "bc6d23",
|
||||
"bc6f36", "bc7f4f", "bc867e", "bc8e47", "bc9241", "bc9777", "bc9878", "bca967",
|
||||
"bcb9be", "bcc29c", "bd8443", "bd8c46", "bd902b", "bda452", "bdab84", "be5531",
|
||||
"be7330", "be7a2f", "be7e28", "be8355", "be872d", "be8829", "be9876", "be9e48",
|
||||
"bea043", "bea451", "beaa79", "beb98a", "bec298", "becbb2", "bf6d24", "bf8853",
|
||||
"bf8d32", "bf9871", "bf9a6d", "bf9e3f", "bfa065", "bfa383", "bfa862", "bfaa59",
|
||||
"c05d3e", "c0754c", "c07a44", "c07c39", "c07d4f", "c0862f", "c08b2e", "c09198",
|
||||
"c0949d", "c09735", "c09739", "c09e4a", "c0a751", "c0ab58", "c17e32", "c18b59",
|
||||
"c18e5e", "c19e35", "c1a54d", "c1a873", "c1ac5c", "c28321", "c29231", "c29f3d",
|
||||
"c29f5c", "c2a451", "c2cfb5", "c35b3c", "c37f47", "c3882f", "c38b2a", "c39368",
|
||||
"c39585", "c39a40", "c3a544", "c3a57a", "c3a64d", "c3a862", "c3b381", "c47b1c",
|
||||
"c4a249", "c4a75c", "c4a84d", "c4a986", "c4ac78", "c5674d", "c58b35", "c58c2c",
|
||||
"c5a33f", "c5a956", "c5b177", "c5b697", "c5c776", "c67634", "c6b686", "c76e4a",
|
||||
"c78a59", "c78b24", "c78c23", "c78e2d", "c7a84b", "c7b683", "c86646", "c8751b",
|
||||
"c8b26b", "c8b468", "c8b58f", "c96e2c", "c96e33", "c98e34", "c98f2d", "c9953f",
|
||||
"c9a955", "c9b35f", "c9c495", "ca8d59", "ca8f49", "ca943f", "caa863", "caaf62",
|
||||
"cab978", "cab98c", "cab997", "cac1c3", "cad3bb", "cb8427", "cb8727", "cb8b2c",
|
||||
"cb9138", "cbad8a", "cbbc83", "cbbc8b", "cbd5bd", "cc933b", "ccb084", "ccb486",
|
||||
"ccbc72", "ccd2b8", "cd8926", "cd8b5a", "cd8d27", "cd912f", "cd9e41", "cda24b",
|
||||
"cdac5e", "cdac81", "cdaf4d", "cdb65c", "cdb777", "cdbe77", "cdd2b8", "ce7837",
|
||||
"ce783d", "ce8826", "ce9150", "cebe70", "cf781b", "cf7836", "cf922e", "cfae3c",
|
||||
"cfb376", "cfbf83", "d07a39", "d08a56", "d0ae49", "d0b340", "d0b98b", "d0ba62",
|
||||
"d19d70", "d1a849", "d1ab4d", "d1ad50", "d1b14a", "d1b577", "d1b86c", "d1c384",
|
||||
"d29c48", "d2a230", "d2ac4d", "d2b88a", "d2c38c", "d3791b", "d4895f", "d49b62",
|
||||
"d4af4f", "d4bf71", "d4c779", "d57a5b", "d5a376", "d5aa57", "d5b877", "d5bc6f",
|
||||
"d5c09f", "d5c170", "d5c678", "d5c793", "d69649", "d69b72", "d6b664", "d6c3a9",
|
||||
"d7b647", "d7c189", "d7c797", "d8a677", "d8ba4f", "d8c27e", "d8c991", "d8caa9",
|
||||
"d9a268", "d9b849", "d9cb9a", "d9cc65", "daa456", "dab948", "dabb4b", "dac66f",
|
||||
"dac8a4", "daca75", "dacc9a", "dace67", "db9a7c", "dbba4c", "dbca80", "dbce69",
|
||||
"dbd07c", "dcbf6f", "dcc267", "dcd068", "ddb784", "ddc663", "ddd069", "dea94f",
|
||||
"decfa8", "ded18c", "ded1a5", "ded298", "e0c980", "e0d2a5", "e19846", "e1b384",
|
||||
"e1cb56", "e1d3a2", "e1d487", "e2cc6d", "e2d786", "e2d8b6", "e3d162", "e3d4bd",
|
||||
"e3d6ae", "e4a14a", "e4d7a6", "e4d97a", "e5d6a1", "e5d7b2", "e5d89d", "e5d8b1",
|
||||
"e6af8c", "e6cf6b", "e6d888", "e6d988", "e6d9b1", "e7c782", "e7d9a6", "e7db7e",
|
||||
"e7dfc8", "e8ae8a", "e8dab3", "e8db90", "e8ddba", "e8de98", "e9cfa9", "e9de97",
|
||||
"e9e488", "eac551", "eae27f", "eae2c2", "eae37f", "ebc851", "ebcc88", "ebd3ad",
|
||||
"ebe083", "ebe688", "ecc69d", "ece2bb", "ece2d0", "edd75e", "edde8f", "ede0a9",
|
||||
"ede4a5", "f0e5bc", "f0e788",
|
||||
]
|
||||
})
|
||||
static mut SAMPLES: OnceLock<Vec<ColorSample>> = OnceLock::new();
|
||||
static SAMPLES_DIR: Dir = include_dir!("src/samples");
|
||||
|
||||
pub fn init() {
|
||||
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);
|
||||
}
|
||||
|
|
|
|||
|
Before Width: | Height: | Size: 20 KiB After Width: | Height: | Size: 20 KiB |
|
Before Width: | Height: | Size: 22 KiB After Width: | Height: | Size: 22 KiB |
|
Before Width: | Height: | Size: 15 KiB After Width: | Height: | Size: 15 KiB |
|
Before Width: | Height: | Size: 16 KiB After Width: | Height: | Size: 16 KiB |
|
Before Width: | Height: | Size: 16 KiB After Width: | Height: | Size: 16 KiB |
|
Before Width: | Height: | Size: 15 KiB After Width: | Height: | Size: 15 KiB |
|
Before Width: | Height: | Size: 17 KiB After Width: | Height: | Size: 17 KiB |
|
Before Width: | Height: | Size: 15 KiB After Width: | Height: | Size: 15 KiB |
|
Before Width: | Height: | Size: 17 KiB After Width: | Height: | Size: 17 KiB |
|
Before Width: | Height: | Size: 16 KiB After Width: | Height: | Size: 16 KiB |
|
Before Width: | Height: | Size: 14 KiB After Width: | Height: | Size: 14 KiB |
|
Before Width: | Height: | Size: 14 KiB After Width: | Height: | Size: 14 KiB |
|
Before Width: | Height: | Size: 18 KiB After Width: | Height: | Size: 18 KiB |
|
Before Width: | Height: | Size: 17 KiB After Width: | Height: | Size: 17 KiB |
|
Before Width: | Height: | Size: 12 KiB After Width: | Height: | Size: 12 KiB |
|
Before Width: | Height: | Size: 44 KiB After Width: | Height: | Size: 44 KiB |
|
Before Width: | Height: | Size: 11 KiB After Width: | Height: | Size: 11 KiB |
|
Before Width: | Height: | Size: 27 KiB After Width: | Height: | Size: 27 KiB |
|
Before Width: | Height: | Size: 12 KiB After Width: | Height: | Size: 12 KiB |
|
Before Width: | Height: | Size: 17 KiB After Width: | Height: | Size: 17 KiB |
|
Before Width: | Height: | Size: 11 KiB After Width: | Height: | Size: 11 KiB |
|
Before Width: | Height: | Size: 16 KiB After Width: | Height: | Size: 16 KiB |
|
Before Width: | Height: | Size: 24 KiB After Width: | Height: | Size: 24 KiB |
|
Before Width: | Height: | Size: 19 KiB After Width: | Height: | Size: 19 KiB |
|
Before Width: | Height: | Size: 13 KiB After Width: | Height: | Size: 13 KiB |
|
Before Width: | Height: | Size: 12 KiB After Width: | Height: | Size: 12 KiB |
|
Before Width: | Height: | Size: 20 KiB After Width: | Height: | Size: 20 KiB |
|
Before Width: | Height: | Size: 21 KiB After Width: | Height: | Size: 21 KiB |
|
Before Width: | Height: | Size: 16 KiB After Width: | Height: | Size: 16 KiB |
|
Before Width: | Height: | Size: 11 KiB After Width: | Height: | Size: 11 KiB |
|
Before Width: | Height: | Size: 23 KiB After Width: | Height: | Size: 23 KiB |
|
Before Width: | Height: | Size: 20 KiB After Width: | Height: | Size: 20 KiB |
|
Before Width: | Height: | Size: 14 KiB After Width: | Height: | Size: 14 KiB |
|
Before Width: | Height: | Size: 18 KiB After Width: | Height: | Size: 18 KiB |
|
Before Width: | Height: | Size: 13 KiB After Width: | Height: | Size: 13 KiB |
|
Before Width: | Height: | Size: 13 KiB After Width: | Height: | Size: 13 KiB |
|
Before Width: | Height: | Size: 19 KiB After Width: | Height: | Size: 19 KiB |
|
Before Width: | Height: | Size: 16 KiB After Width: | Height: | Size: 16 KiB |
|
Before Width: | Height: | Size: 63 KiB After Width: | Height: | Size: 63 KiB |
|
Before Width: | Height: | Size: 24 KiB After Width: | Height: | Size: 24 KiB |
|
Before Width: | Height: | Size: 53 KiB After Width: | Height: | Size: 53 KiB |
|
Before Width: | Height: | Size: 21 KiB After Width: | Height: | Size: 21 KiB |
|
Before Width: | Height: | Size: 32 KiB After Width: | Height: | Size: 32 KiB |
|
Before Width: | Height: | Size: 18 KiB After Width: | Height: | Size: 18 KiB |
|
Before Width: | Height: | Size: 18 KiB After Width: | Height: | Size: 18 KiB |
|
Before Width: | Height: | Size: 19 KiB After Width: | Height: | Size: 19 KiB |
|
Before Width: | Height: | Size: 18 KiB After Width: | Height: | Size: 18 KiB |
|
Before Width: | Height: | Size: 21 KiB After Width: | Height: | Size: 21 KiB |
|
Before Width: | Height: | Size: 18 KiB After Width: | Height: | Size: 18 KiB |
|
Before Width: | Height: | Size: 18 KiB After Width: | Height: | Size: 18 KiB |
|
Before Width: | Height: | Size: 13 KiB After Width: | Height: | Size: 13 KiB |
|
Before Width: | Height: | Size: 17 KiB After Width: | Height: | Size: 17 KiB |
|
Before Width: | Height: | Size: 20 KiB After Width: | Height: | Size: 20 KiB |
|
Before Width: | Height: | Size: 52 KiB After Width: | Height: | Size: 52 KiB |
|
Before Width: | Height: | Size: 19 KiB After Width: | Height: | Size: 19 KiB |
|
Before Width: | Height: | Size: 23 KiB After Width: | Height: | Size: 23 KiB |
|
Before Width: | Height: | Size: 17 KiB After Width: | Height: | Size: 17 KiB |
|
Before Width: | Height: | Size: 18 KiB After Width: | Height: | Size: 18 KiB |
|
Before Width: | Height: | Size: 17 KiB After Width: | Height: | Size: 17 KiB |
|
Before Width: | Height: | Size: 19 KiB After Width: | Height: | Size: 19 KiB |
|
Before Width: | Height: | Size: 14 KiB After Width: | Height: | Size: 14 KiB |
|
Before Width: | Height: | Size: 19 KiB After Width: | Height: | Size: 19 KiB |
|
Before Width: | Height: | Size: 38 KiB After Width: | Height: | Size: 38 KiB |
|
Before Width: | Height: | Size: 23 KiB After Width: | Height: | Size: 23 KiB |
|
Before Width: | Height: | Size: 14 KiB After Width: | Height: | Size: 14 KiB |
|
Before Width: | Height: | Size: 19 KiB After Width: | Height: | Size: 19 KiB |
|
Before Width: | Height: | Size: 18 KiB After Width: | Height: | Size: 18 KiB |
|
Before Width: | Height: | Size: 18 KiB After Width: | Height: | Size: 18 KiB |
|
Before Width: | Height: | Size: 20 KiB After Width: | Height: | Size: 20 KiB |
|
Before Width: | Height: | Size: 23 KiB After Width: | Height: | Size: 23 KiB |
|
Before Width: | Height: | Size: 41 KiB After Width: | Height: | Size: 41 KiB |
|
Before Width: | Height: | Size: 21 KiB After Width: | Height: | Size: 21 KiB |
|
Before Width: | Height: | Size: 18 KiB After Width: | Height: | Size: 18 KiB |
|
Before Width: | Height: | Size: 18 KiB After Width: | Height: | Size: 18 KiB |
|
Before Width: | Height: | Size: 17 KiB After Width: | Height: | Size: 17 KiB |
|
Before Width: | Height: | Size: 18 KiB After Width: | Height: | Size: 18 KiB |
|
Before Width: | Height: | Size: 46 KiB After Width: | Height: | Size: 46 KiB |
|
Before Width: | Height: | Size: 48 KiB After Width: | Height: | Size: 48 KiB |
|
Before Width: | Height: | Size: 21 KiB After Width: | Height: | Size: 21 KiB |
|
Before Width: | Height: | Size: 48 KiB After Width: | Height: | Size: 48 KiB |
|
Before Width: | Height: | Size: 18 KiB After Width: | Height: | Size: 18 KiB |
|
Before Width: | Height: | Size: 19 KiB After Width: | Height: | Size: 19 KiB |