11use crate :: dirs:: { Dirs , ALL_DIRS , CARDINAL_DIRS } ;
22use crate :: { error:: DmiError , ztxt, RawDmi , RawDmiMetadata } ;
3+ use :: png:: { ColorType , Decoder , Transformations } ;
34use image:: codecs:: png;
4- use image:: { imageops, DynamicImage } ;
5+ use image:: { imageops, RgbaImage } ;
56use std:: collections:: HashMap ;
67use std:: io:: prelude:: * ;
78use std:: io:: Cursor ;
@@ -208,30 +209,105 @@ impl Icon {
208209 }
209210
210211 fn load_internal < R : Read + Seek > ( reader : R , load_images : bool ) -> Result < Icon , DmiError > {
211- let ( base_image , dmi_meta ) = if load_images {
212+ let ( dmi_meta , rgba_bytes ) = if load_images {
212213 let raw_dmi = RawDmi :: load ( reader) ?;
213- let mut rawdmi_temp = vec ! [ ] ;
214- raw_dmi. save ( & mut rawdmi_temp) ?;
215- let chunk_ztxt = match raw_dmi. chunk_ztxt {
216- Some ( chunk) => chunk,
217- None => {
218- return Err ( DmiError :: Generic ( String :: from (
219- "Error loading icon: no zTXt chunk found." ,
220- ) ) )
214+
215+ let mut total_bytes = 45 ;
216+ if let Some ( chunk_plte) = & raw_dmi. chunk_plte {
217+ total_bytes += chunk_plte. data . len ( ) + 12
218+ }
219+ if let Some ( other_chunks) = & raw_dmi. other_chunks {
220+ for chunk in other_chunks {
221+ total_bytes += chunk. data . len ( ) + 12
222+ }
223+ }
224+ for idat in & raw_dmi. chunks_idat {
225+ total_bytes += idat. data . len ( ) + 12 ;
226+ }
227+
228+ // Reconstruct the PNG
229+ let mut png_data = Vec :: with_capacity ( total_bytes) ;
230+ png_data. extend_from_slice ( & raw_dmi. header ) ;
231+ png_data. extend_from_slice ( & raw_dmi. chunk_ihdr . data_length ) ;
232+ png_data. extend_from_slice ( & raw_dmi. chunk_ihdr . chunk_type ) ;
233+ png_data. extend_from_slice ( & raw_dmi. chunk_ihdr . data ) ;
234+ png_data. extend_from_slice ( & raw_dmi. chunk_ihdr . crc ) ;
235+ if let Some ( plte) = & raw_dmi. chunk_plte {
236+ png_data. extend_from_slice ( & plte. data_length ) ;
237+ png_data. extend_from_slice ( & plte. chunk_type ) ;
238+ png_data. extend_from_slice ( & plte. data ) ;
239+ png_data. extend_from_slice ( & plte. crc ) ;
240+ }
241+ if let Some ( other_chunks) = & raw_dmi. other_chunks {
242+ for chunk in other_chunks {
243+ png_data. extend_from_slice ( & chunk. data_length ) ;
244+ png_data. extend_from_slice ( & chunk. chunk_type ) ;
245+ png_data. extend_from_slice ( & chunk. data ) ;
246+ png_data. extend_from_slice ( & chunk. crc ) ;
247+ }
248+ }
249+ for idat in & raw_dmi. chunks_idat {
250+ png_data. extend_from_slice ( & idat. data_length ) ;
251+ png_data. extend_from_slice ( & idat. chunk_type ) ;
252+ png_data. extend_from_slice ( & idat. data ) ;
253+ png_data. extend_from_slice ( & idat. crc ) ;
254+ }
255+ png_data. extend_from_slice ( & raw_dmi. chunk_iend . data_length ) ;
256+ png_data. extend_from_slice ( & raw_dmi. chunk_iend . chunk_type ) ;
257+ png_data. extend_from_slice ( & raw_dmi. chunk_iend . crc ) ;
258+
259+ let mut png_decoder = Decoder :: new ( std:: io:: Cursor :: new ( png_data) ) ;
260+ png_decoder. set_transformations ( Transformations :: EXPAND | Transformations :: ALPHA ) ;
261+ let mut png_reader = png_decoder. read_info ( ) ?;
262+ let mut rgba_buf = vec ! [ 0u8 ; png_reader. output_buffer_size( ) ] ;
263+ let info = png_reader. next_frame ( & mut rgba_buf) ?;
264+
265+ // EXPAND and ALPHA do not expand grayscale images into RGBA. We can just do this manually.
266+ match info. color_type {
267+ ColorType :: GrayscaleAlpha => {
268+ if rgba_buf. len ( ) as u32 != info. width * info. height * 2 {
269+ return Err ( DmiError :: Generic ( String :: from ( "GrayscaleAlpha buffer length mismatch" ) ) ) ;
270+ }
271+ let mut new_buf = Vec :: with_capacity ( ( info. width * info. height * 4 ) as usize ) ;
272+ for chunk in rgba_buf. chunks ( 2 ) {
273+ let gray = chunk[ 0 ] ;
274+ let alpha = chunk[ 1 ] ;
275+ new_buf. push ( gray) ;
276+ new_buf. push ( gray) ;
277+ new_buf. push ( gray) ;
278+ new_buf. push ( alpha) ;
279+ }
280+ rgba_buf = new_buf;
281+ }
282+ ColorType :: Grayscale => {
283+ if rgba_buf. len ( ) as u32 != info. width * info. height {
284+ return Err ( DmiError :: Generic ( String :: from ( "Grayscale buffer length mismatch" ) ) ) ;
285+ }
286+ let mut new_buf = Vec :: with_capacity ( ( info. width * info. height * 4 ) as usize ) ;
287+ for gray in rgba_buf {
288+ new_buf. push ( gray) ;
289+ new_buf. push ( gray) ;
290+ new_buf. push ( gray) ;
291+ new_buf. push ( 255 ) ;
292+ }
293+ rgba_buf = new_buf;
294+ }
295+ ColorType :: Rgba => { }
296+ _ => {
297+ return Err ( DmiError :: Generic ( format ! ( "Unsupported ColorType (must be RGBA or convertible to RGBA): {:#?}" , info. color_type) ) ) ;
221298 }
299+ }
300+
301+ let dmi_meta = RawDmiMetadata {
302+ chunk_ihdr : raw_dmi. chunk_ihdr ,
303+ chunk_ztxt : raw_dmi. chunk_ztxt . ok_or_else ( || {
304+ DmiError :: Generic ( String :: from ( "Error loading icon: no zTXt chunk found." ) )
305+ } ) ?,
222306 } ;
223- (
224- Some ( image:: load_from_memory_with_format (
225- & rawdmi_temp,
226- image:: ImageFormat :: Png ,
227- ) ?) ,
228- RawDmiMetadata {
229- chunk_ihdr : raw_dmi. chunk_ihdr ,
230- chunk_ztxt,
231- } ,
232- )
307+
308+ ( dmi_meta, Some ( rgba_buf) )
233309 } else {
234- ( None , RawDmi :: load_meta ( reader) ?)
310+ ( RawDmi :: load_meta ( reader) ?, None )
235311 } ;
236312
237313 let chunk_ztxt = & dmi_meta. chunk_ztxt ;
@@ -315,7 +391,7 @@ impl Icon {
315391 "\t dirs" => dirs = Some ( value. parse :: < u8 > ( ) ?) ,
316392 "\t frames" => frames = Some ( value. parse :: < u32 > ( ) ?) ,
317393 "\t delay" => {
318- let mut delay_vector = vec ! [ ] ;
394+ let mut delay_vector = Vec :: with_capacity ( frames . unwrap_or ( 0 ) as usize ) ;
319395 let text_delays = value. split_terminator ( ',' ) ;
320396 for text_entry in text_delays {
321397 delay_vector. push ( text_entry. parse :: < f32 > ( ) ?) ;
@@ -369,14 +445,32 @@ impl Icon {
369445 return Err ( DmiError :: Generic ( format ! ( "Error loading icon: metadata settings exceeded the maximum number of states possible ({max_possible_states})." ) ) ) ;
370446 } ;
371447
372- let mut images = vec ! [ ] ;
448+ let mut images = Vec :: with_capacity ( ( frames * dirs as u32 ) as usize ) ;
449+
450+ if let Some ( rgba_bytes) = & rgba_bytes {
451+ const RGBA_PIXEL_STRIDE : usize = 4 ;
452+ let row_stride = img_width as usize * RGBA_PIXEL_STRIDE ;
453+ let expected_buffer_len = row_stride * ( img_height as usize ) ;
454+ if rgba_bytes. len ( ) != expected_buffer_len {
455+ panic ! ( "{} != {}" , rgba_bytes. len( ) , expected_buffer_len) ;
456+ }
373457
374- if let Some ( full_image) = base_image. as_ref ( ) {
375- for image_idx in index..( index + ( frames * dirs as u32 ) ) {
458+ for image_idx in index..next_index {
376459 let x = ( image_idx % width_in_states) * width;
377- //This operation rounds towards zero, truncating any fractional part of the exact result, essentially a floor() function.
378460 let y = ( image_idx / width_in_states) * height;
379- images. push ( full_image. crop_imm ( x, y, width, height) ) ;
461+
462+ let mut cropped =
463+ Vec :: with_capacity ( ( width * height * RGBA_PIXEL_STRIDE as u32 ) as usize ) ;
464+ for row in y..( y + height) {
465+ let start = ( row as usize * row_stride) + ( x as usize * RGBA_PIXEL_STRIDE ) ;
466+ let end = start + ( width as usize * RGBA_PIXEL_STRIDE ) ;
467+ cropped. extend_from_slice ( & rgba_bytes[ start..end] ) ;
468+ }
469+
470+ let tile = image:: ImageBuffer :: < image:: Rgba < u8 > , _ > :: from_raw ( width, height, cropped)
471+ . ok_or_else ( || DmiError :: Generic ( "Failed to create image tile" . to_string ( ) ) ) ?;
472+
473+ images. push ( tile) ;
380474 }
381475 }
382476
@@ -593,7 +687,7 @@ pub struct IconState {
593687 pub name : String ,
594688 pub dirs : u8 ,
595689 pub frames : u32 ,
596- pub images : Vec < image:: DynamicImage > ,
690+ pub images : Vec < image:: RgbaImage > ,
597691 pub delay : Option < Vec < f32 > > ,
598692 pub loop_flag : Looping ,
599693 pub rewind : bool ,
@@ -605,7 +699,7 @@ pub struct IconState {
605699impl IconState {
606700 /// Gets a specific DynamicImage from `images`, given a dir and frame.
607701 /// If the dir or frame is invalid, returns a DmiError.
608- pub fn get_image ( & self , dir : & Dirs , frame : u32 ) -> Result < & DynamicImage , DmiError > {
702+ pub fn get_image ( & self , dir : & Dirs , frame : u32 ) -> Result < & RgbaImage , DmiError > {
609703 if self . frames < frame {
610704 return Err ( DmiError :: IconState ( format ! (
611705 "Specified frame \" {frame}\" is larger than the number of frames ({}) for icon_state \" {}\" " ,
0 commit comments