Skip to content

Commit 520fbc2

Browse files
committed
simplify color palettes
1 parent 7e22ba0 commit 520fbc2

2 files changed

Lines changed: 40 additions & 109 deletions

File tree

src/images.rs

Lines changed: 35 additions & 55 deletions
Original file line numberDiff line numberDiff line change
@@ -12,19 +12,8 @@ pub fn convert_image(in_path: &Path, out_path: &Path, sys_pal: &Palette) -> Resu
1212
if img.width() % 8 != 0 {
1313
bail!("image width must be divisible by 8");
1414
}
15-
let img_pal = make_palette(&img, sys_pal).context("detect colors used in the image")?;
15+
let transp = find_unused_color(&img, sys_pal).context("detect colors used in the image")?;
1616
let out = File::create(out_path).context("create output path")?;
17-
18-
let n_colors = img_pal.len();
19-
if n_colors > 16 {
20-
let has_transparency = img_pal.iter().any(Option::is_none);
21-
if has_transparency && n_colors == 17 {
22-
bail!("cannot use all 16 colors with transparency, remove one color");
23-
}
24-
bail!("the image has too many colors");
25-
}
26-
27-
let transp = pick_transparent(&img_pal, sys_pal)?;
2817
write_image(out, &img, sys_pal, transp).context("write image")
2918
}
3019

@@ -55,26 +44,33 @@ fn write_image(mut out: File, img: &RgbaImage, sys_pal: &Palette, transp: u8) ->
5544
Ok(())
5645
}
5746

58-
/// Detect all colors used in the image.
59-
fn make_palette(img: &RgbaImage, sys_pal: &Palette) -> Result<Vec<Color>> {
60-
let mut palette = Vec::new();
47+
/// Find color from the palette not used on the image.
48+
///
49+
/// Additionally ensures that the image uses the given color palette.
50+
fn find_unused_color(img: &RgbaImage, sys_pal: &Palette) -> Result<u8> {
51+
let mut used_colors: Vec<Color> = Vec::new();
52+
let mut has_transp = false;
6153
for (x, y, pixel) in img.enumerate_pixels() {
62-
let color = convert_color(*pixel);
63-
if !palette.contains(&color) {
64-
if color.is_some() && !sys_pal.contains(&color) {
65-
bail!(
66-
"found a color not present in the color palette: {} (at x={x}, y={y})",
67-
format_color(color),
68-
);
69-
}
70-
palette.push(color);
54+
let Some(color) = convert_color(*pixel) else {
55+
has_transp = true;
56+
continue;
57+
};
58+
if !sys_pal.contains(&color) {
59+
bail!(
60+
"found a color not present in the color palette: {} (at x={x}, y={y})",
61+
format_color(color),
62+
);
63+
}
64+
if !used_colors.contains(&color) {
65+
used_colors.push(color);
7166
}
7267
}
73-
palette.sort_by_key(|c| match c {
74-
Some(c) => find_color(sys_pal, *c),
75-
None => 20,
76-
});
77-
Ok(palette)
68+
69+
if has_transp {
70+
pick_transparent(&used_colors, sys_pal)
71+
} else {
72+
Ok(0xff)
73+
}
7874
}
7975

8076
fn write_u8(f: &mut File, v: u8) -> std::io::Result<()> {
@@ -88,7 +84,7 @@ fn write_u16(f: &mut File, v: u16) -> std::io::Result<()> {
8884
/// Find the index of the given color in the given palette.
8985
fn find_color(palette: &Palette, c: Rgb<u8>) -> u8 {
9086
for (color, i) in palette.iter().zip(0u8..) {
91-
if *color == Some(c) {
87+
if *color == c {
9288
return i;
9389
}
9490
}
@@ -97,16 +93,11 @@ fn find_color(palette: &Palette, c: Rgb<u8>) -> u8 {
9793

9894
/// Make human-readable hex representation of the color code.
9995
fn format_color(c: Color) -> String {
100-
match c {
101-
Some(c) => {
102-
let c = c.0;
103-
format!("#{:02X}{:02X}{:02X}", c[0], c[1], c[2])
104-
}
105-
None => "TRANSPARENT".to_string(),
106-
}
96+
let c = c.0;
97+
format!("#{:02X}{:02X}{:02X}", c[0], c[1], c[2])
10798
}
10899

109-
fn convert_color(c: Rgba<u8>) -> Color {
100+
fn convert_color(c: Rgba<u8>) -> Option<Color> {
110101
let alpha = c.0[3];
111102
let is_transparent = alpha < 128;
112103
if is_transparent {
@@ -117,21 +108,13 @@ fn convert_color(c: Rgba<u8>) -> Color {
117108

118109
/// Pick the color to be used to represent transparency
119110
fn pick_transparent(img_pal: &[Color], sys_pal: &Palette) -> Result<u8> {
120-
if img_pal.iter().all(Option::is_some) {
121-
return Ok(0xff); // no transparency needed
122-
}
111+
assert!(img_pal.len() <= sys_pal.len());
123112
for (color, i) in sys_pal.iter().zip(0u8..) {
124113
if !img_pal.contains(color) {
125114
return Ok(i);
126115
}
127116
}
128-
if img_pal.len() > 16 {
129-
bail!("the image cannot contain more than 16 colors")
130-
}
131-
if img_pal.len() == 16 {
132-
bail!("the image cannot contain all 16 colors and transparency")
133-
}
134-
bail!("image contains colors not from the palette")
117+
bail!("cannot use all 16 colors with transparency, remove one color");
135118
}
136119

137120
#[cfg(test)]
@@ -142,8 +125,7 @@ mod tests {
142125

143126
#[test]
144127
fn test_format_color() {
145-
assert_eq!(format_color(None), "ALPHA");
146-
assert_eq!(format_color(Some(Rgb([0x89, 0xab, 0xcd]))), "#89ABCD");
128+
assert_eq!(format_color(Rgb([0x89, 0xab, 0xcd])), "#89ABCD");
147129
}
148130

149131
#[test]
@@ -153,10 +135,8 @@ mod tests {
153135
let c1 = pal[1];
154136
let c2 = pal[2];
155137
let c3 = pal[3];
156-
assert_eq!(pick_transparent(&[c0, c1], pal).unwrap(), 255);
157-
assert_eq!(pick_transparent(&[c0, c1, None], pal).unwrap(), 2);
158-
assert_eq!(pick_transparent(&[c0, None, c1], pal).unwrap(), 2);
159-
assert_eq!(pick_transparent(&[c1, c0, None], pal).unwrap(), 2);
160-
assert_eq!(pick_transparent(&[c0, c1, c2, c3, None], pal).unwrap(), 4);
138+
assert_eq!(pick_transparent(&[c0, c1], pal).unwrap(), 2);
139+
assert_eq!(pick_transparent(&[c1, c0], pal).unwrap(), 2);
140+
assert_eq!(pick_transparent(&[c0, c1, c2, c3], pal).unwrap(), 4);
161141
}
162142
}

src/palettes.rs

Lines changed: 5 additions & 54 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,8 @@ use image::Rgb;
33
use std::collections::HashMap;
44

55
pub type Color = Rgb<u8>;
6-
pub type Palette = [Color; 16];
7-
pub type Palettes = HashMap<String, Palette>;
6+
pub type Palette = [Color];
7+
pub type Palettes = HashMap<String, Vec<Color>>;
88
type RawPalette = HashMap<String, u32>;
99

1010
/// The default color palette (SWEETIE-16).
@@ -64,15 +64,6 @@ static SLSO8: &Palette = &[
6464
Rgb([0xff, 0xaa, 0x5e]), // #ffaa5e
6565
Rgb([0xff, 0xd4, 0xa3]), // #ffd4a3
6666
Rgb([0xff, 0xec, 0xd6]), // #ffecd6
67-
// unused
68-
Rgb([0xff, 0xec, 0xd6]),
69-
Rgb([0xff, 0xec, 0xd6]),
70-
Rgb([0xff, 0xec, 0xd6]),
71-
Rgb([0xff, 0xec, 0xd6]),
72-
Rgb([0xff, 0xec, 0xd6]),
73-
Rgb([0xff, 0xec, 0xd6]),
74-
Rgb([0xff, 0xec, 0xd6]),
75-
Rgb([0xff, 0xec, 0xd6]),
7667
];
7768

7869
/// The Kirokaze Gameboy color palette.
@@ -83,19 +74,6 @@ static GAMEBOY: &Palette = &[
8374
Rgb([0x46, 0x87, 0x8f]), // #46878f: blue
8475
Rgb([0x94, 0xe3, 0x44]), // #94e344: green
8576
Rgb([0xe2, 0xf3, 0xe4]), // #e2f3e4: white
86-
// unused
87-
Rgb([0xe2, 0xf3, 0xe4]),
88-
Rgb([0xe2, 0xf3, 0xe4]),
89-
Rgb([0xe2, 0xf3, 0xe4]),
90-
Rgb([0xe2, 0xf3, 0xe4]),
91-
Rgb([0xe2, 0xf3, 0xe4]),
92-
Rgb([0xe2, 0xf3, 0xe4]),
93-
Rgb([0xe2, 0xf3, 0xe4]),
94-
Rgb([0xe2, 0xf3, 0xe4]),
95-
Rgb([0xe2, 0xf3, 0xe4]),
96-
Rgb([0xe2, 0xf3, 0xe4]),
97-
Rgb([0xe2, 0xf3, 0xe4]),
98-
Rgb([0xe2, 0xf3, 0xe4]),
9977
];
10078

10179
/// WASM-4 color palette.
@@ -106,19 +84,6 @@ static WASM4: &Palette = &[
10684
Rgb([0x86, 0xC0, 0x6C]), // #86C06C: light green
10785
Rgb([0x30, 0x68, 0x50]), // #306850: dark green
10886
Rgb([0x07, 0x18, 0x21]), // #071821: black
109-
// unused
110-
Rgb([0x07, 0x18, 0x21]),
111-
Rgb([0x07, 0x18, 0x21]),
112-
Rgb([0x07, 0x18, 0x21]),
113-
Rgb([0x07, 0x18, 0x21]),
114-
Rgb([0x07, 0x18, 0x21]),
115-
Rgb([0x07, 0x18, 0x21]),
116-
Rgb([0x07, 0x18, 0x21]),
117-
Rgb([0x07, 0x18, 0x21]),
118-
Rgb([0x07, 0x18, 0x21]),
119-
Rgb([0x07, 0x18, 0x21]),
120-
Rgb([0x07, 0x18, 0x21]),
121-
Rgb([0x07, 0x18, 0x21]),
12287
];
12388

12489
pub fn parse_palettes(raws: Option<&HashMap<String, RawPalette>>) -> Result<Palettes> {
@@ -133,7 +98,7 @@ pub fn parse_palettes(raws: Option<&HashMap<String, RawPalette>>) -> Result<Pale
13398
Ok(palettes)
13499
}
135100

136-
fn parse_palette(raw: &RawPalette) -> Result<Palette> {
101+
fn parse_palette(raw: &RawPalette) -> Result<Vec<Color>> {
137102
let len = raw.len();
138103
if len > 16 {
139104
bail!("too many colors")
@@ -154,7 +119,6 @@ fn parse_palette(raw: &RawPalette) -> Result<Palette> {
154119
let color = parse_color(*raw_color)?;
155120
palette.push(color);
156121
}
157-
let palette: Palette = palette.try_into().unwrap();
158122
Ok(palette)
159123
}
160124

@@ -206,31 +170,18 @@ mod tests {
206170
ps.insert("rgb".to_string(), p);
207171
let res = parse_palettes(Some(&ps)).unwrap();
208172
assert_eq!(res.len(), 1);
209-
let exp: Palette = [
173+
let exp: &Palette = &[
210174
Rgb([0xff, 0x00, 0x00]),
211175
Rgb([0x00, 0xff, 0x00]),
212176
Rgb([0x00, 0x00, 0xff]),
213-
None,
214-
None,
215-
None,
216-
None,
217-
None,
218-
None,
219-
None,
220-
None,
221-
None,
222-
None,
223-
None,
224-
None,
225-
None,
226177
];
227178
assert_eq!(*res.get("rgb").unwrap(), exp);
228179
}
229180

230181
#[test]
231182
fn test_get_palette() {
232183
let mut p = Palettes::new();
233-
p.insert("sup".to_string(), *SWEETIE16);
184+
p.insert("sup".to_string(), Vec::from(SWEETIE16));
234185
assert_eq!(get_palette(None, &p).unwrap(), SWEETIE16);
235186
assert_eq!(get_palette(Some("sup"), &p).unwrap(), SWEETIE16);
236187
assert_eq!(get_palette(Some("sweetie16"), &p).unwrap(), SWEETIE16);

0 commit comments

Comments
 (0)