implement colour mapping and dithering properly
This commit is contained in:
parent
5916c4af17
commit
fd5f141f19
|
@ -13,7 +13,6 @@ crate-type = ["cdylib"]
|
|||
[dependencies]
|
||||
"base64" = "^0.12.3"
|
||||
"bitsy-parser" = "^0.72.3"
|
||||
"dither" = "1.3.9"
|
||||
"image" = "^0.23.7"
|
||||
"json" = "^0.12.4"
|
||||
"lazy_static" = "^1.4.0"
|
||||
|
|
3
TODO.md
3
TODO.md
|
@ -1,8 +1,7 @@
|
|||
# todo
|
||||
|
||||
* preview
|
||||
* dithering
|
||||
* tests
|
||||
* if image is exactly 128×128, *don't* crop
|
||||
* custom palette
|
||||
* tile reuse
|
||||
* find a way to implement Atkinson dithering instead of CatmullRom
|
||||
|
|
|
@ -0,0 +1,74 @@
|
|||
use image::{Luma, Rgba};
|
||||
|
||||
#[derive(Clone, Copy)]
|
||||
pub struct ColourMap {
|
||||
background: Rgba<u8>,
|
||||
foreground: Rgba<u8>,
|
||||
}
|
||||
|
||||
impl ColourMap {
|
||||
pub(crate) fn from(palette: &bitsy_parser::Palette) -> ColourMap {
|
||||
let background = Rgba::from([
|
||||
palette.colours[0].red,
|
||||
palette.colours[0].green,
|
||||
palette.colours[0].blue,
|
||||
255,
|
||||
]);
|
||||
|
||||
let foreground = Rgba::from([
|
||||
palette.colours[1].red,
|
||||
palette.colours[1].green,
|
||||
palette.colours[1].blue,
|
||||
255,
|
||||
]);
|
||||
|
||||
ColourMap { background, foreground }
|
||||
}
|
||||
}
|
||||
|
||||
fn diff(a: &Rgba<u8>, b:&Rgba<u8>) -> u32 {
|
||||
let diff_red = (a[0] as i16 - b[0] as i16).abs();
|
||||
let diff_green= (a[1] as i16 - b[1] as i16).abs();
|
||||
let diff_blue = (a[2] as i16 - b[2] as i16).abs();
|
||||
|
||||
(diff_red + diff_green + diff_blue) as u32
|
||||
}
|
||||
|
||||
impl image::imageops::colorops::ColorMap for ColourMap {
|
||||
type Color = Rgba<u8>;
|
||||
|
||||
#[inline(always)]
|
||||
fn index_of(&self, color: &Self::Color) -> usize {
|
||||
let diff_background = diff(color, &self.background);
|
||||
let diff_foreground = diff(color, &self.foreground);
|
||||
|
||||
if diff_foreground <= diff_background { 1 } else { 0 }
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
fn lookup(&self, idx: usize) -> Option<Self::Color> {
|
||||
match idx {
|
||||
0 => Some(self.background.into()),
|
||||
1 => Some(self.foreground.into()),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
/// Indicate NeuQuant implements `lookup`.
|
||||
fn has_lookup(&self) -> bool {
|
||||
true
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
fn map_color(&self, color: &mut Self::Color) {
|
||||
let closest = match self.index_of(color) {
|
||||
1 => self.foreground,
|
||||
_ => self.background,
|
||||
};
|
||||
|
||||
color[0] = closest[0];
|
||||
color[1] = closest[1];
|
||||
color[2] = closest[2];
|
||||
color[3] = closest[3];
|
||||
}
|
||||
}
|
59
src/lib.rs
59
src/lib.rs
|
@ -3,11 +3,15 @@
|
|||
use bitsy_parser::game::Game;
|
||||
use bitsy_parser::image::Image;
|
||||
use bitsy_parser::tile::Tile;
|
||||
use image::{GenericImageView, Pixel, DynamicImage};
|
||||
use image::{GenericImageView, Pixel, DynamicImage, GenericImage, ImageBuffer};
|
||||
use lazy_static::lazy_static;
|
||||
use std::sync::Mutex;
|
||||
use wasm_bindgen::prelude::*;
|
||||
use image::imageops::dither;
|
||||
|
||||
mod colour_map;
|
||||
|
||||
use colour_map::ColourMap;
|
||||
use image::imageops::FilterType::CatmullRom;
|
||||
|
||||
const SD: u32 = 8;
|
||||
|
||||
|
@ -156,54 +160,21 @@ fn image_to_base64(image: &DynamicImage) -> String {
|
|||
format!("data:image/png;base64,{}", base64::encode(&bytes))
|
||||
}
|
||||
|
||||
fn colour_difference(compare: image::Rgba<u8>, other: &bitsy_parser::Colour) -> u32 {
|
||||
let diff_red = (compare[0] as i16 - other.red as i16).abs();
|
||||
let diff_green= (compare[1] as i16 - other.green as i16).abs();
|
||||
let diff_blue = (compare[2] as i16 - other.blue as i16).abs();
|
||||
|
||||
(diff_red + diff_green + diff_blue) as u32
|
||||
}
|
||||
|
||||
fn closest_colour(compare: image::Rgba<u8>, colours: &[bitsy_parser::Colour]) -> image::Rgba<u8> {
|
||||
let diff_background = colour_difference(compare, &colours[0]);
|
||||
let diff_foreground = colour_difference(compare, &colours[1]);
|
||||
|
||||
if diff_foreground <= diff_background {
|
||||
image::Rgba::from([colours[1].red, colours[1].green, colours[1].blue, 255])
|
||||
} else {
|
||||
image::Rgba::from([colours[0].red, colours[0].green, colours[0].blue, 255])
|
||||
}
|
||||
}
|
||||
|
||||
fn adjust_brightness(pixel: &mut image::Rgba<u8>, state: &State) -> image::Rgba<u8> {
|
||||
pixel[0] = (pixel[0] as i32 + state.brightness).clamp(0, 255) as u8;
|
||||
pixel[1] = (pixel[1] as i32 + state.brightness).clamp(0, 255) as u8;
|
||||
pixel[2] = (pixel[2] as i32 + state.brightness).clamp(0, 255) as u8;
|
||||
|
||||
*pixel
|
||||
}
|
||||
|
||||
fn render_preview(state: &State) -> DynamicImage {
|
||||
let image = state.image.as_ref().unwrap().clone();
|
||||
let mut preview = image.clone();
|
||||
let mut buffer = state.image.as_ref().unwrap().clone().into_rgba();
|
||||
|
||||
// todo dither
|
||||
// todo get actual chosen palette
|
||||
let colour_map = crate::ColourMap::from(&state.game.as_ref().unwrap().palettes[0]);
|
||||
|
||||
// todo convert to palette colours
|
||||
image::imageops::brighten(&mut buffer, state.brightness);
|
||||
|
||||
// get background and foreground colours from palette
|
||||
let colours = &state.game.as_ref().unwrap().palettes
|
||||
.iter()
|
||||
.find(|palette| &palette.id == state.palette.as_ref().unwrap())
|
||||
.unwrap()
|
||||
.colours[0..2];
|
||||
|
||||
for (x, y, mut pixel) in image.pixels() {
|
||||
// is pixel closer to background or foreground?
|
||||
preview.put_pixel(x, y, closest_colour(adjust_brightness(&mut pixel, &state), colours));
|
||||
if state.dither {
|
||||
image::imageops::dither(&mut buffer, &colour_map);
|
||||
} else {
|
||||
image::imageops::colorops::index_colors(&mut buffer, &colour_map);
|
||||
}
|
||||
|
||||
preview
|
||||
image::DynamicImage::ImageRgba8(buffer)
|
||||
}
|
||||
|
||||
#[wasm_bindgen]
|
||||
|
|
Loading…
Reference in New Issue