@@ -278,6 +278,96 @@ impl Frame<'static> {
278278 }
279279 }
280280
281+ /// Creates a frame from pixels in LumaAlpha format (grayscale pixels with transparency).
282+ ///
283+ /// This is a lossy method. The `gif` format does not support arbitrary alpha but only a 1-bit
284+ /// transparency mask per pixel. Any non-zero alpha value will be interpreted as a fully opaque
285+ /// pixel. The least used color will be used to indicate alpha and replace with the closest other color
286+ /// in the image. Different frames have independent palettes.
287+ ///
288+ /// # Panics:
289+ /// * If the length of pixels does not equal `width * height * 2`.
290+ pub fn from_grayscale_with_alpha ( width : u16 , height : u16 , pixels : & [ u8 ] ) -> Self {
291+ assert_eq ! ( width as usize * height as usize * 2 , pixels. len( ) , "Too much or too little pixel data for the given width and height to create a GIF Frame" ) ;
292+
293+ // Input is in LumaA format.
294+ // Count the occurrences of all the colors, then pick the least common color as alpha.
295+ let mut num_transparent_pixels: u32 = 0 ;
296+ let mut color_frequencies: [ u32 ; 256 ] = [ 0 ; 256 ] ;
297+ for pixel in pixels. chunks_exact ( 2 ) {
298+ let color = pixel[ 0 ] ;
299+ let alpha = pixel[ 1 ] ;
300+ // do not count colors in fully transparent pixels
301+ if alpha == 0 {
302+ num_transparent_pixels += 1 ;
303+ } else {
304+ color_frequencies[ color as usize ] += 1 ;
305+ }
306+ }
307+
308+ let grayscale_palette: Vec < u8 > = ( 0 ..=255 ) . flat_map ( |i| [ i, i, i] ) . collect ( ) ;
309+
310+ // If there were no fully transparent pixels, do not allocate a color to transparency in the GIF
311+ // and return immediately with the generic grayscale palette
312+ if num_transparent_pixels == 0 {
313+ let stripped_alpha: Vec < u8 > = pixels. chunks_exact ( 2 ) . map ( |pixel| pixel[ 0 ] ) . collect ( ) ;
314+ return Frame {
315+ width,
316+ height,
317+ buffer : Cow :: Owned ( stripped_alpha) ,
318+ palette : Some ( grayscale_palette) ,
319+ transparent : None ,
320+ ..Frame :: default ( )
321+ } ;
322+ }
323+
324+ // Choose the color that will be our alpha color
325+ let least_used_color = color_frequencies
326+ . iter ( )
327+ . enumerate ( )
328+ . min_by_key ( |( _, & value) | value)
329+ . map ( |( index, _) | index as u8 )
330+ . expect ( "input slice is empty" ) ;
331+
332+ // pick the less used color out of the neighbours as the replacement color
333+ let replacement_color = if least_used_color == 255 {
334+ 254
335+ } else if least_used_color == 0 {
336+ 1
337+ } else if color_frequencies[ ( least_used_color - 1 ) as usize ]
338+ < color_frequencies[ ( least_used_color + 1 ) as usize ]
339+ {
340+ least_used_color - 1
341+ } else {
342+ least_used_color + 1
343+ } ;
344+
345+ // Strip alpha and replace fully transparent pixels with the chosen color
346+ let paletted: Vec < u8 > = pixels
347+ . chunks_exact ( 2 )
348+ . map ( |pixel| {
349+ let color = pixel[ 0 ] ;
350+ let alpha = pixel[ 1 ] ;
351+ if alpha == 0 {
352+ least_used_color
353+ } else if color == least_used_color {
354+ replacement_color
355+ } else {
356+ color
357+ }
358+ } )
359+ . collect ( ) ;
360+
361+ Frame {
362+ width,
363+ height,
364+ buffer : Cow :: Owned ( paletted) ,
365+ palette : Some ( grayscale_palette) ,
366+ transparent : Some ( least_used_color) ,
367+ ..Frame :: default ( )
368+ }
369+ }
370+
281371 /// Creates a frame from a palette and indexed pixels.
282372 ///
283373 /// # Panics:
0 commit comments