Skip to content
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
106 changes: 96 additions & 10 deletions src/images.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,20 +14,24 @@ pub fn convert_image(in_path: &Path, out_path: &Path, sys_pal: &Palette) -> Resu
}
let mut img_pal = make_palette(&img, sys_pal).context("detect colors used in the image")?;
let mut out = File::create(out_path).context("create output path")?;
// The magic number. "2"=image, "1"=v1.
write_u8(&mut out, 0x21)?;
let colors = img_pal.len();
if colors <= 2 {
let n_colors = img_pal.len();
if n_colors <= 2 {
if n_colors <= 1 {
println!("⚠️ the image has only one color.");
}
extend_palette(&mut img_pal, sys_pal, 2);
write_image::<1, 8>(out, &img, &img_pal, sys_pal).context("write 1BPP image")
} else if colors <= 4 {
} else if n_colors <= 4 {
extend_palette(&mut img_pal, sys_pal, 4);
write_image::<2, 4>(out, &img, &img_pal, sys_pal).context("write 1BPP image")
} else if colors <= 16 {
} else if n_colors <= 16 {
extend_palette(&mut img_pal, sys_pal, 16);
write_image::<4, 2>(out, &img, &img_pal, sys_pal).context("write 1BPP image")
} else {
let has_transparency = img_pal.iter().any(Option::is_none);
if has_transparency && colors == 17 {
if has_transparency && n_colors == 17 {
bail!("cannot use all 16 colors with transparency, remove one color");
}
bail!("the image has too many colors");
Expand Down Expand Up @@ -75,7 +79,7 @@ fn write_image<const BPP: u8, const PPB: usize>(
Ok(())
}

/// Detect all colors used in the image
/// Detect all colors used in the image.
fn make_palette(img: &RgbaImage, sys_pal: &Palette) -> Result<Vec<Color>> {
let mut palette = Vec::new();
for (x, y, pixel) in img.enumerate_pixels() {
Expand All @@ -98,11 +102,53 @@ fn make_palette(img: &RgbaImage, sys_pal: &Palette) -> Result<Vec<Color>> {
}

/// Add empty colors at the end of the palette to match the BPP size.
///
/// If the given image palette is fully contained within the system palette
/// (after being cut to the expected swaps size), place the colors in the
/// image palette in the same positions as they are in the system palette.
/// This will make it possible to read such images without worrying about
/// applying color swaps.
fn extend_palette(img_pal: &mut Vec<Color>, sys_pal: &Palette, size: usize) {
let n = size - img_pal.len();
for _ in 0..n {
img_pal.push(sys_pal[0]);
if img_pal.len() > size {
return;
}

let sys_pal_prefix = &sys_pal[..size];
if !is_subpalette(img_pal, sys_pal_prefix) {
img_pal.extend_from_slice(&sys_pal[img_pal.len()..size]);
return;
}

// No transparency? Just use the system palette.
let has_transp = img_pal.iter().any(Option::is_none);
if !has_transp {
img_pal.clear();
img_pal.extend(sys_pal_prefix);
return;
}

// Has transparency? Then copy the system palette and poke one hole in it.
let mut new_pal: Vec<Color> = Vec::new();
let mut found_transp = false;
for c in sys_pal_prefix {
if found_transp || img_pal.contains(c) {
new_pal.push(*c);
} else {
new_pal.push(None);
found_transp = true;
}
}
img_pal.copy_from_slice(&new_pal[..]);
}

/// Check if the image palette is fully contained within the given system palette.
fn is_subpalette(img_pal: &[Color], sys_pal: &[Color]) -> bool {
for c in img_pal {
if c.is_some() && !sys_pal.contains(c) {
return false;
}
}
true
}

fn write_u8(f: &mut File, v: u8) -> std::io::Result<()> {
Expand All @@ -123,7 +169,7 @@ fn find_color(palette: &[Color], c: Color) -> u8 {
panic!("color not in the palette")
}

/// Make human-friendly hex representation of the color code.
/// Make human-readable hex representation of the color code.
fn format_color(c: Color) -> String {
match c {
Some(c) => {
Expand Down Expand Up @@ -191,4 +237,44 @@ mod tests {
assert_eq!(pick_transparent(&[c1, c0, None], pal).unwrap(), 2);
assert_eq!(pick_transparent(&[c0, c1, c2, c3, None], pal).unwrap(), 4);
}

#[test]
fn test_extend_palette() {
let pal = SWEETIE16;
let c0 = pal[0];
let c1 = pal[1];
let c2 = pal[2];
let c3 = pal[3];
let c4 = pal[4];

// Already the palette prefix, do nothing.
let mut img_pal = vec![c0, c1];
extend_palette(&mut img_pal, pal, 2);
assert_eq!(img_pal, vec![c0, c1]);

// A prefix but in a wrong order. Fix the order.
let mut img_pal = vec![c1, c0];
extend_palette(&mut img_pal, pal, 2);
assert_eq!(img_pal, vec![c0, c1]);

// Not a prefix and already full. Keep the given palette.
let mut img_pal = vec![c2, c1];
extend_palette(&mut img_pal, pal, 2);
assert_eq!(img_pal, vec![c2, c1]);

// A prefix but too short. Fill the rest.
let mut img_pal = vec![c0, c1];
extend_palette(&mut img_pal, pal, 4);
assert_eq!(img_pal, vec![c0, c1, c2, c3]);

// Within the palette prefix.
let mut img_pal = vec![c2, c1];
extend_palette(&mut img_pal, pal, 4);
assert_eq!(img_pal, vec![c0, c1, c2, c3]);

// Not a prefix but too short. Don't touch the given, fill the rest.
let mut img_pal = vec![c4, c2];
extend_palette(&mut img_pal, pal, 4);
assert_eq!(img_pal, vec![c4, c2, c2, c3]);
}
}