11use crate :: palettes:: { Color , Palette } ;
22use anyhow:: { Context , Result , bail} ;
3- use image:: { Pixel , Rgba , RgbaImage } ;
3+ use image:: { Pixel , Rgb , Rgba , RgbaImage } ;
44use std:: fs:: File ;
55use std:: io:: Write ;
66use std:: path:: Path ;
@@ -12,65 +12,30 @@ 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 mut img_pal = make_palette ( & img, sys_pal) . context ( "detect colors used in the image" ) ?;
16- let mut out = File :: create ( out_path) . context ( "create output path" ) ?;
17- // The magic number. "2"=image, "1"=v1.
18- write_u8 ( & mut out, 0x21 ) ?;
19- let n_colors = img_pal. len ( ) ;
20- if n_colors <= 2 {
21- if n_colors <= 1 {
22- println ! ( "⚠️ the image has only one color." ) ;
23- }
24- extend_palette ( & mut img_pal, sys_pal, 2 ) ;
25- write_image :: < 1 , 8 > ( out, & img, & img_pal, sys_pal) . context ( "write 1BPP image" )
26- } else if n_colors <= 4 {
27- extend_palette ( & mut img_pal, sys_pal, 4 ) ;
28- write_image :: < 2 , 4 > ( out, & img, & img_pal, sys_pal) . context ( "write 1BPP image" )
29- } else if n_colors <= 16 {
30- extend_palette ( & mut img_pal, sys_pal, 16 ) ;
31- write_image :: < 4 , 2 > ( out, & img, & img_pal, sys_pal) . context ( "write 1BPP image" )
32- } else {
33- let has_transparency = img_pal. iter ( ) . any ( Option :: is_none) ;
34- if has_transparency && n_colors == 17 {
35- bail ! ( "cannot use all 16 colors with transparency, remove one color" ) ;
36- }
37- bail ! ( "the image has too many colors" ) ;
38- }
15+ let transp = find_unused_color ( & img, sys_pal) . context ( "detect colors used in the image" ) ?;
16+ let out = File :: create ( out_path) . context ( "create output path" ) ?;
17+ write_image ( out, & img, sys_pal, transp) . context ( "write image" )
3918}
4019
41- fn write_image < const BPP : u8 , const PPB : usize > (
42- mut out : File ,
43- img : & RgbaImage ,
44- img_pal : & [ Color ] ,
45- sys_pal : & Palette ,
46- ) -> Result < ( ) > {
47- write_u8 ( & mut out, BPP ) ?; // BPP
20+ fn write_image ( mut out : File , img : & RgbaImage , sys_pal : & Palette , transp : u8 ) -> Result < ( ) > {
21+ const BPP : u8 = 4 ;
22+ const PPB : usize = 2 ;
23+
4824 let Ok ( width) = u16:: try_from ( img. width ( ) ) else {
4925 bail ! ( "the image is too big" )
5026 } ;
27+ write_u8 ( & mut out, 0x22 ) ?; // magic number
5128 write_u16 ( & mut out, width) ?; // image width
52- let transparent = pick_transparent ( img_pal, sys_pal) ?;
53- write_u8 ( & mut out, transparent) ?; // transparent color
29+ write_u8 ( & mut out, transp) ?; // transparent color
5430
55- // palette swaps
56- let mut byte = 0 ;
57- debug_assert ! ( img_pal. len( ) == 2 || img_pal. len( ) == 4 || img_pal. len( ) == 16 ) ;
58- for ( i, color) in img_pal. iter ( ) . enumerate ( ) {
59- let index = match color {
60- Some ( color) => find_color ( sys_pal, Some ( * color) ) ,
61- None => transparent,
62- } ;
63- byte = ( byte << 4 ) | index;
64- if i % 2 == 1 {
65- write_u8 ( & mut out, byte) ?;
66- }
67- }
68-
69- // image raw packed bytes
31+ // Pixel values.
7032 let mut byte: u8 = 0 ;
7133 for ( i, pixel) in img. pixels ( ) . enumerate ( ) {
7234 let color = convert_color ( * pixel) ;
73- let raw_color = find_color ( img_pal, color) ;
35+ let raw_color = match color {
36+ Some ( color) => find_color ( sys_pal, color) ,
37+ None => transp,
38+ } ;
7439 byte = ( byte << BPP ) | raw_color;
7540 if ( i + 1 ) % PPB == 0 {
7641 write_u8 ( & mut out, byte) ?;
@@ -79,77 +44,33 @@ fn write_image<const BPP: u8, const PPB: usize>(
7944 Ok ( ( ) )
8045}
8146
82- /// Detect all colors used in the image.
83- fn make_palette ( img : & RgbaImage , sys_pal : & Palette ) -> Result < Vec < Color > > {
84- 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 ;
8553 for ( x, y, pixel) in img. enumerate_pixels ( ) {
86- let color = convert_color ( * pixel) ;
87- if !palette . contains ( & color ) {
88- if color . is_some ( ) && !sys_pal . contains ( & color ) {
89- bail ! (
90- "found a color not present in the color palette: {} (at x={x}, y={y})" ,
91- format_color ( color ) ,
92- ) ;
93- }
94- 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+ ) ;
9563 }
96- }
97- palette. sort_by_key ( |c| match c {
98- Some ( c) => find_color ( sys_pal, Some ( * c) ) ,
99- None => 20 ,
100- } ) ;
101- Ok ( palette)
102- }
103-
104- /// Add empty colors at the end of the palette to match the BPP size.
105- ///
106- /// If the given image palette is fully contained within the system palette
107- /// (after being cut to the expected swaps size), place the colors in the
108- /// image palette in the same positions as they are in the system palette.
109- /// This will make it possible to read such images without worrying about
110- /// applying color swaps.
111- fn extend_palette ( img_pal : & mut Vec < Color > , sys_pal : & Palette , size : usize ) {
112- if img_pal. len ( ) > size {
113- return ;
114- }
115-
116- let sys_pal_prefix = & sys_pal[ ..size] ;
117- if !is_subpalette ( img_pal, sys_pal_prefix) {
118- img_pal. extend_from_slice ( & sys_pal[ img_pal. len ( ) ..size] ) ;
119- return ;
120- }
121-
122- // No transparency? Just use the system palette.
123- let has_transp = img_pal. iter ( ) . any ( Option :: is_none) ;
124- if !has_transp {
125- img_pal. clear ( ) ;
126- img_pal. extend ( sys_pal_prefix) ;
127- return ;
128- }
129-
130- // Has transparency? Then copy the system palette and poke one hole in it.
131- let mut new_pal: Vec < Color > = Vec :: new ( ) ;
132- let mut found_transp = false ;
133- for c in sys_pal_prefix {
134- if found_transp || img_pal. contains ( c) {
135- new_pal. push ( * c) ;
136- } else {
137- new_pal. push ( None ) ;
138- found_transp = true ;
64+ if !used_colors. contains ( & color) {
65+ used_colors. push ( color) ;
13966 }
14067 }
141- img_pal. clear ( ) ;
142- img_pal. extend ( new_pal) ;
143- }
14468
145- /// Check if the image palette is fully contained within the given system palette.
146- fn is_subpalette ( img_pal : & [ Color ] , sys_pal : & [ Color ] ) -> bool {
147- for c in img_pal {
148- if c. is_some ( ) && !sys_pal. contains ( c) {
149- return false ;
150- }
69+ if has_transp {
70+ pick_transparent ( & used_colors, sys_pal)
71+ } else {
72+ Ok ( 0xff )
15173 }
152- true
15374}
15475
15576fn write_u8 ( f : & mut File , v : u8 ) -> std:: io:: Result < ( ) > {
@@ -161,7 +82,7 @@ fn write_u16(f: &mut File, v: u16) -> std::io::Result<()> {
16182}
16283
16384/// Find the index of the given color in the given palette.
164- fn find_color ( palette : & [ Color ] , c : Color ) -> u8 {
85+ fn find_color ( palette : & Palette , c : Rgb < u8 > ) -> u8 {
16586 for ( color, i) in palette. iter ( ) . zip ( 0u8 ..) {
16687 if * color == c {
16788 return i;
@@ -172,45 +93,35 @@ fn find_color(palette: &[Color], c: Color) -> u8 {
17293
17394/// Make human-readable hex representation of the color code.
17495fn format_color ( c : Color ) -> String {
175- match c {
176- Some ( c) => {
177- let c = c. 0 ;
178- format ! ( "#{:02X}{:02X}{:02X}" , c[ 0 ] , c[ 1 ] , c[ 2 ] )
179- }
180- None => "ALPHA" . to_string ( ) ,
181- }
96+ let c = c. 0 ;
97+ format ! ( "#{:02X}{:02X}{:02X}" , c[ 0 ] , c[ 1 ] , c[ 2 ] )
18298}
18399
184- fn convert_color ( c : Rgba < u8 > ) -> Color {
185- if is_transparent ( c) {
100+ fn convert_color ( c : Rgba < u8 > ) -> Option < Color > {
101+ let alpha = c. 0 [ 3 ] ;
102+ let is_transparent = alpha < 128 ;
103+ if is_transparent {
186104 return None ;
187105 }
188106 Some ( c. to_rgb ( ) )
189107}
190108
191- const fn is_transparent ( c : Rgba < u8 > ) -> bool {
192- let alpha = c. 0 [ 3 ] ;
193- alpha < 128
194- }
195-
196109/// Pick the color to be used to represent transparency
197110fn pick_transparent ( img_pal : & [ Color ] , sys_pal : & Palette ) -> Result < u8 > {
198- if img_pal. iter ( ) . all ( Option :: is_some) {
199- // no transparency needed
200- return Ok ( 17 ) ;
201- }
111+ assert ! ( img_pal. len( ) <= sys_pal. len( ) ) ;
112+ assert ! ( sys_pal. len( ) <= 16 ) ;
202113 for ( color, i) in sys_pal. iter ( ) . zip ( 0u8 ..) {
203114 if !img_pal. contains ( color) {
204115 return Ok ( i) ;
205116 }
206117 }
207- if img_pal . len ( ) > 16 {
208- bail ! ( "the image cannot contain more than 16 colors" )
118+ if sys_pal . len ( ) == 16 {
119+ bail ! ( "cannot use all 16 colors with transparency, remove one color" ) ;
209120 }
210- if img_pal . len ( ) == 16 {
211- bail ! ( "an image cannot contain all 16 colors and transparency" )
212- }
213- bail ! ( "image contains colors not from the palette" )
121+ // If the system palette has less than 16 colors,
122+ // any of the colors outside the palette
123+ // can be used for transparency. We use 15.
124+ Ok ( 0xf )
214125}
215126
216127#[ cfg( test) ]
@@ -221,8 +132,7 @@ mod tests {
221132
222133 #[ test]
223134 fn test_format_color ( ) {
224- assert_eq ! ( format_color( None ) , "ALPHA" ) ;
225- assert_eq ! ( format_color( Some ( Rgb ( [ 0x89 , 0xab , 0xcd ] ) ) ) , "#89ABCD" ) ;
135+ assert_eq ! ( format_color( Rgb ( [ 0x89 , 0xab , 0xcd ] ) ) , "#89ABCD" ) ;
226136 }
227137
228138 #[ test]
@@ -232,50 +142,8 @@ mod tests {
232142 let c1 = pal[ 1 ] ;
233143 let c2 = pal[ 2 ] ;
234144 let c3 = pal[ 3 ] ;
235- assert_eq ! ( pick_transparent( & [ c0, c1] , pal) . unwrap( ) , 17 ) ;
236- assert_eq ! ( pick_transparent( & [ c0, c1, None ] , pal) . unwrap( ) , 2 ) ;
237- assert_eq ! ( pick_transparent( & [ c0, None , c1] , pal) . unwrap( ) , 2 ) ;
238- assert_eq ! ( pick_transparent( & [ c1, c0, None ] , pal) . unwrap( ) , 2 ) ;
239- assert_eq ! ( pick_transparent( & [ c0, c1, c2, c3, None ] , pal) . unwrap( ) , 4 ) ;
240- }
241-
242- #[ test]
243- fn test_extend_palette ( ) {
244- let pal = SWEETIE16 ;
245- let c0 = pal[ 0 ] ;
246- let c1 = pal[ 1 ] ;
247- let c2 = pal[ 2 ] ;
248- let c3 = pal[ 3 ] ;
249- let c4 = pal[ 4 ] ;
250-
251- // Already the palette prefix, do nothing.
252- let mut img_pal = vec ! [ c0, c1] ;
253- extend_palette ( & mut img_pal, pal, 2 ) ;
254- assert_eq ! ( img_pal, vec![ c0, c1] ) ;
255-
256- // A prefix but in a wrong order. Fix the order.
257- let mut img_pal = vec ! [ c1, c0] ;
258- extend_palette ( & mut img_pal, pal, 2 ) ;
259- assert_eq ! ( img_pal, vec![ c0, c1] ) ;
260-
261- // Not a prefix and already full. Keep the given palette.
262- let mut img_pal = vec ! [ c2, c1] ;
263- extend_palette ( & mut img_pal, pal, 2 ) ;
264- assert_eq ! ( img_pal, vec![ c2, c1] ) ;
265-
266- // A prefix but too short. Fill the rest.
267- let mut img_pal = vec ! [ c0, c1] ;
268- extend_palette ( & mut img_pal, pal, 4 ) ;
269- assert_eq ! ( img_pal, vec![ c0, c1, c2, c3] ) ;
270-
271- // Within the palette prefix.
272- let mut img_pal = vec ! [ c2, c1] ;
273- extend_palette ( & mut img_pal, pal, 4 ) ;
274- assert_eq ! ( img_pal, vec![ c0, c1, c2, c3] ) ;
275-
276- // Not a prefix but too short. Don't touch the given, fill the rest.
277- let mut img_pal = vec ! [ c4, c2] ;
278- extend_palette ( & mut img_pal, pal, 4 ) ;
279- assert_eq ! ( img_pal, vec![ c4, c2, c2, c3] ) ;
145+ assert_eq ! ( pick_transparent( & [ c0, c1] , pal) . unwrap( ) , 2 ) ;
146+ assert_eq ! ( pick_transparent( & [ c1, c0] , pal) . unwrap( ) , 2 ) ;
147+ assert_eq ! ( pick_transparent( & [ c0, c1, c2, c3] , pal) . unwrap( ) , 4 ) ;
280148 }
281149}
0 commit comments