From 94282dfe736bb095d053a2fd9bcd612554d32e00 Mon Sep 17 00:00:00 2001 From: Nathan Adams Date: Sun, 14 Aug 2022 00:27:02 +0200 Subject: [PATCH] core: Moved render utils into own module --- core/src/backend/render.rs | 400 +------------------------------ core/src/backend/render/utils.rs | 395 ++++++++++++++++++++++++++++++ 2 files changed, 401 insertions(+), 394 deletions(-) create mode 100644 core/src/backend/render/utils.rs diff --git a/core/src/backend/render.rs b/core/src/backend/render.rs index c9b33716a..931e7e501 100644 --- a/core/src/backend/render.rs +++ b/core/src/backend/render.rs @@ -1,15 +1,15 @@ mod bitmap; mod null; +mod utils; pub use null::{NullBitmapSource, NullRenderer}; +pub use utils::{determine_jpeg_tag_format, remove_invalid_jpeg_data}; use crate::matrix::Matrix; use crate::shape_utils::DistilledShape; pub use crate::{transform::Transform, Color}; pub use bitmap::{Bitmap, BitmapFormat, BitmapHandle, BitmapInfo, BitmapSource}; use downcast_rs::Downcast; -use std::borrow::Cow; -use std::io::Read; pub use swf; pub trait RenderBackend: Downcast { @@ -32,12 +32,12 @@ pub trait RenderBackend: Downcast { data: &[u8], jpeg_tables: Option<&[u8]>, ) -> Result { - let data = glue_tables_to_jpeg(data, jpeg_tables); + let data = utils::glue_tables_to_jpeg(data, jpeg_tables); self.register_bitmap_jpeg_2(&data) } fn register_bitmap_jpeg_2(&mut self, data: &[u8]) -> Result { - let bitmap = decode_define_bits_jpeg(data, None)?; + let bitmap = utils::decode_define_bits_jpeg(data, None)?; let width = bitmap.width() as u16; let height = bitmap.height() as u16; let handle = self.register_bitmap(bitmap)?; @@ -53,7 +53,7 @@ pub trait RenderBackend: Downcast { jpeg_data: &[u8], alpha_data: &[u8], ) -> Result { - let bitmap = decode_define_bits_jpeg(jpeg_data, Some(alpha_data))?; + let bitmap = utils::decode_define_bits_jpeg(jpeg_data, Some(alpha_data))?; let width = bitmap.width() as u16; let height = bitmap.height() as u16; let handle = self.register_bitmap(bitmap)?; @@ -68,7 +68,7 @@ pub trait RenderBackend: Downcast { &mut self, swf_tag: &swf::DefineBitsLossless, ) -> Result { - let bitmap = decode_define_bits_lossless(swf_tag)?; + let bitmap = utils::decode_define_bits_lossless(swf_tag)?; let width = bitmap.width() as u16; let height = bitmap.height() as u16; let handle = self.register_bitmap(bitmap)?; @@ -119,391 +119,3 @@ pub enum JpegTagFormat { Gif, Unknown, } - -/// Determines the format of the image data in `data` from a DefineBitsJPEG2/3 tag. -pub fn determine_jpeg_tag_format(data: &[u8]) -> JpegTagFormat { - match data { - [0xff, 0xd8, ..] => JpegTagFormat::Jpeg, - [0xff, 0xd9, 0xff, 0xd8, ..] => JpegTagFormat::Jpeg, // erroneous header in SWF - [0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a, ..] => JpegTagFormat::Png, - [0x47, 0x49, 0x46, 0x38, 0x39, 0x61, ..] => JpegTagFormat::Gif, - _ => JpegTagFormat::Unknown, - } -} - -/// Decodes bitmap data from a DefineBitsJPEG2/3 tag. -/// The data is returned with pre-multiplied alpha. -fn decode_define_bits_jpeg(data: &[u8], alpha_data: Option<&[u8]>) -> Result { - let format = determine_jpeg_tag_format(data); - if format != JpegTagFormat::Jpeg && alpha_data.is_some() { - // Only DefineBitsJPEG3 with true JPEG data should have separate alpha data. - log::warn!("DefineBitsJPEG contains non-JPEG data with alpha; probably incorrect") - } - match format { - JpegTagFormat::Jpeg => decode_jpeg(data, alpha_data), - JpegTagFormat::Png => decode_png(data), - JpegTagFormat::Gif => decode_gif(data), - JpegTagFormat::Unknown => Err("Unknown bitmap data format".into()), - } -} - -/// Glues the JPEG encoding tables from a JPEGTables SWF tag to the JPEG data -/// in a DefineBits tag, producing complete JPEG data suitable for a decoder. -fn glue_tables_to_jpeg<'a>(jpeg_data: &'a [u8], jpeg_tables: Option<&'a [u8]>) -> Cow<'a, [u8]> { - if let Some(jpeg_tables) = jpeg_tables { - if jpeg_tables.len() >= 2 { - let mut full_jpeg = Vec::with_capacity(jpeg_tables.len() + jpeg_data.len()); - full_jpeg.extend_from_slice(&jpeg_tables[..jpeg_tables.len() - 2]); - if jpeg_data.len() >= 2 { - full_jpeg.extend_from_slice(&jpeg_data[2..]); - } - - return full_jpeg.into(); - } - } - - // No JPEG tables or not enough data; return JPEG data as is - jpeg_data.into() -} - -/// Removes potential invalid JPEG data from SWF DefineBitsJPEG tags. -/// -/// SWF19 p.138: -/// "Before version 8 of the SWF file format, SWF files could contain an erroneous header of 0xFF, 0xD9, 0xFF, 0xD8 before the JPEG SOI marker." -/// These bytes need to be removed for the JPEG to decode properly. -pub fn remove_invalid_jpeg_data(mut data: &[u8]) -> Cow<[u8]> { - // TODO: Might be better to return an Box> instead of a Cow here, - // where the spliced iter is a data[..n].chain(data[n+4..])? - if data.starts_with(&[0xFF, 0xD9, 0xFF, 0xD8]) { - data = &data[4..]; - } - if let Some(pos) = data.windows(4).position(|w| w == [0xFF, 0xD9, 0xFF, 0xD8]) { - let mut out_data = Vec::with_capacity(data.len() - 4); - out_data.extend_from_slice(&data[..pos]); - out_data.extend_from_slice(&data[pos + 4..]); - out_data.into() - } else { - data.into() - } -} - -/// Decodes a JPEG with optional alpha data. -/// The decoded bitmap will have pre-multiplied alpha. -fn decode_jpeg( - jpeg_data: &[u8], - alpha_data: Option<&[u8]>, -) -> Result> { - let jpeg_data = remove_invalid_jpeg_data(jpeg_data); - - let mut decoder = jpeg_decoder::Decoder::new(&jpeg_data[..]); - decoder.read_info()?; - let metadata = decoder.info().ok_or("Unable to get image info")?; - let decoded_data = decoder.decode()?; - - let decoded_data = match metadata.pixel_format { - jpeg_decoder::PixelFormat::RGB24 => decoded_data, - jpeg_decoder::PixelFormat::CMYK32 => decoded_data - .chunks_exact(4) - .flat_map(|cmyk| { - let c = 255 - u16::from(cmyk[0]); - let m = 255 - u16::from(cmyk[1]); - let y = 255 - u16::from(cmyk[2]); - let k = 256 - u16::from(cmyk[3]); - - let r = c * k / 255; - let g = m * k / 255; - let b = y * k / 255; - [r as u8, g as u8, b as u8] - }) - .collect(), - jpeg_decoder::PixelFormat::L8 => { - let mut rgb = Vec::with_capacity(decoded_data.len() * 3); - for elem in decoded_data { - rgb.push(elem); - rgb.push(elem); - rgb.push(elem); - } - rgb - } - jpeg_decoder::PixelFormat::L16 => { - log::warn!("Unimplemented L16 JPEG pixel format"); - decoded_data - } - }; - - // Decompress the alpha data (DEFLATE compression). - if let Some(alpha_data) = alpha_data { - let alpha_data = decompress_zlib(alpha_data)?; - - if alpha_data.len() == decoded_data.len() / 3 { - let mut rgba = Vec::with_capacity((decoded_data.len() / 3) * 4); - let mut i = 0; - let mut a = 0; - while i < decoded_data.len() { - // The JPEG data should be premultiplied alpha, but it isn't in some incorrect SWFs (see #6893). - // This means 0% alpha pixels may have color and incorrectly show as visible. - // Flash Player clamps color to the alpha value to fix this case. - // Only applies to DefineBitsJPEG3; DefineBitsLossless does not seem to clamp. - let alpha = alpha_data[a]; - rgba.push(decoded_data[i].min(alpha)); - rgba.push(decoded_data[i + 1].min(alpha)); - rgba.push(decoded_data[i + 2].min(alpha)); - rgba.push(alpha); - i += 3; - a += 1; - } - return Ok(Bitmap::new( - metadata.width.into(), - metadata.height.into(), - BitmapFormat::Rgba, - rgba, - )); - } else { - // Size isn't correct; fallback to RGB? - log::error!("Size mismatch in DefineBitsJPEG3 alpha data"); - } - } - - // No alpha. - Ok(Bitmap::new( - metadata.width.into(), - metadata.height.into(), - BitmapFormat::Rgb, - decoded_data, - )) -} - -/// Decodes the bitmap data in DefineBitsLossless tag into RGBA. -/// DefineBitsLossless is Zlib encoded pixel data (similar to PNG), possibly -/// palletized. -fn decode_define_bits_lossless( - swf_tag: &swf::DefineBitsLossless, -) -> Result> { - // Decompress the image data (DEFLATE compression). - let mut decoded_data = decompress_zlib(swf_tag.data)?; - - // Swizzle/de-palettize the bitmap. - let out_data = match (swf_tag.version, swf_tag.format) { - (1, swf::BitmapFormat::Rgb15) => { - let padded_width = (swf_tag.width + 0b1) & !0b1; - let mut out_data: Vec = - Vec::with_capacity(swf_tag.width as usize * swf_tag.height as usize * 4); - let mut i = 0; - for _ in 0..swf_tag.height { - for _ in 0..swf_tag.width { - let compressed = u16::from_be_bytes([decoded_data[i], decoded_data[i + 1]]); - let rgb5_component = |shift: u16| { - let component = compressed >> shift & 0x1F; - ((component * 255 + 15) / 31) as u8 - }; - out_data.push(rgb5_component(10)); - out_data.push(rgb5_component(5)); - out_data.push(rgb5_component(0)); - out_data.push(0xff); - i += 2; - } - i += (padded_width - swf_tag.width) as usize * 2; - } - out_data - } - (1, swf::BitmapFormat::Rgb32) => { - let mut i = 0; - while i < decoded_data.len() { - decoded_data[i] = decoded_data[i + 1]; - decoded_data[i + 1] = decoded_data[i + 2]; - decoded_data[i + 2] = decoded_data[i + 3]; - decoded_data[i + 3] = 0xff; - i += 4; - } - decoded_data - } - (2, swf::BitmapFormat::Rgb32) => { - let mut i = 0; - while i < decoded_data.len() { - let alpha = decoded_data[i]; - decoded_data[i] = decoded_data[i + 1]; - decoded_data[i + 1] = decoded_data[i + 2]; - decoded_data[i + 2] = decoded_data[i + 3]; - decoded_data[i + 3] = alpha; - i += 4; - } - decoded_data - } - (1, swf::BitmapFormat::ColorMap8 { num_colors }) => { - let mut i = 0; - let padded_width = (swf_tag.width + 0b11) & !0b11; - - let mut palette = Vec::with_capacity(num_colors as usize + 1); - for _ in 0..=num_colors { - palette.push(Color { - r: decoded_data[i], - g: decoded_data[i + 1], - b: decoded_data[i + 2], - a: 255, - }); - i += 3; - } - let mut out_data: Vec = - Vec::with_capacity(swf_tag.width as usize * swf_tag.height as usize * 4); - for _ in 0..swf_tag.height { - for _ in 0..swf_tag.width { - let entry = decoded_data[i] as usize; - if entry < palette.len() { - let color = &palette[entry]; - out_data.push(color.r); - out_data.push(color.g); - out_data.push(color.b); - out_data.push(color.a); - } else { - out_data.push(0); - out_data.push(0); - out_data.push(0); - out_data.push(255); - } - i += 1; - } - i += (padded_width - swf_tag.width) as usize; - } - out_data - } - (2, swf::BitmapFormat::ColorMap8 { num_colors }) => { - let mut i = 0; - let padded_width = (swf_tag.width + 0b11) & !0b11; - - let mut palette = Vec::with_capacity(num_colors as usize + 1); - for _ in 0..=num_colors { - palette.push(Color { - r: decoded_data[i], - g: decoded_data[i + 1], - b: decoded_data[i + 2], - a: decoded_data[i + 3], - }); - i += 4; - } - let mut out_data: Vec = - Vec::with_capacity(swf_tag.width as usize * swf_tag.height as usize * 4); - for _ in 0..swf_tag.height { - for _ in 0..swf_tag.width { - let entry = decoded_data[i] as usize; - if entry < palette.len() { - let color = &palette[entry]; - out_data.push(color.r); - out_data.push(color.g); - out_data.push(color.b); - out_data.push(color.a); - } else { - out_data.push(0); - out_data.push(0); - out_data.push(0); - out_data.push(0); - } - i += 1; - } - i += (padded_width - swf_tag.width) as usize; - } - out_data - } - _ => { - return Err(format!( - "Unexpected DefineBitsLossless{} format: {:?} ", - swf_tag.version, swf_tag.format, - ) - .into()); - } - }; - - Ok(Bitmap::new( - swf_tag.width.into(), - swf_tag.height.into(), - BitmapFormat::Rgba, - out_data, - )) -} - -/// Decodes the bitmap data in DefineBitsLossless tag into RGBA. -/// DefineBitsLossless is Zlib encoded pixel data (similar to PNG), possibly -/// palletized. -fn decode_png(data: &[u8]) -> Result { - use png::{ColorType, Transformations}; - - let mut decoder = png::Decoder::new(data); - // Normalize output to 8-bit grayscale or RGB. - // Ideally we'd want to normalize to 8-bit RGB only, but seems like the `png` crate provides no such a feature. - decoder.set_transformations(Transformations::normalize_to_color8()); - let mut reader = decoder.read_info()?; - - let mut data = vec![0; reader.output_buffer_size()]; - let info = reader.next_frame(&mut data)?; - - let (format, data) = match info.color_type { - ColorType::Rgb => (BitmapFormat::Rgb, data), - ColorType::Rgba => { - // In contrast to DefineBitsLossless tags, PNGs embedded in a DefineBitsJPEG tag will not have - // premultiplied alpha and need to be converted before sending to the renderer. - premultiply_alpha_rgba(&mut data); - (BitmapFormat::Rgba, data) - } - ColorType::Grayscale => ( - BitmapFormat::Rgb, - data.into_iter().flat_map(|v| [v, v, v]).collect(), - ), - ColorType::GrayscaleAlpha => { - ( - BitmapFormat::Rgba, - data.chunks_exact(2) - .flat_map(|pixel| { - // Pre-multiply alpha. - let a = pixel[1]; - let v = (u16::from(pixel[0]) * u16::from(a) / 255) as u8; - [v, v, v, a] - }) - .collect(), - ) - } - ColorType::Indexed => { - // Shouldn't get here because of `normalize_to_color8` transformation above. - unreachable!("Unexpected PNG ColorType::Indexed"); - } - }; - - Ok(Bitmap::new(info.width, info.height, format, data)) -} - -/// Decodes the bitmap data in DefineBitsLossless tag into RGBA. -/// DefineBitsLossless is Zlib encoded pixel data (similar to PNG), possibly -/// palletized. -fn decode_gif(data: &[u8]) -> Result { - let mut decode_options = gif::DecodeOptions::new(); - decode_options.set_color_output(gif::ColorOutput::RGBA); - let mut reader = decode_options.read_info(data)?; - let frame = reader.read_next_frame()?.ok_or("No frames in GIF")?; - // GIFs embedded in a DefineBitsJPEG tag will not have premultiplied alpha and need to be converted before sending to the renderer. - let mut data = frame.buffer.to_vec(); - premultiply_alpha_rgba(&mut data); - - Ok(Bitmap::new( - frame.width.into(), - frame.height.into(), - BitmapFormat::Rgba, - data, - )) -} - -/// Converts standard RBGA to premultiplied alpha. -fn premultiply_alpha_rgba(rgba: &mut [u8]) { - rgba.chunks_exact_mut(4).for_each(|rgba| { - let a = f32::from(rgba[3]) / 255.0; - rgba[0] = (f32::from(rgba[0]) * a) as u8; - rgba[1] = (f32::from(rgba[1]) * a) as u8; - rgba[2] = (f32::from(rgba[2]) * a) as u8; - }) -} - -/// Decodes zlib-compressed data. -fn decompress_zlib(data: &[u8]) -> Result, std::io::Error> { - let mut out_data = Vec::new(); - let mut decoder = flate2::bufread::ZlibDecoder::new(data); - decoder.read_to_end(&mut out_data)?; - out_data.shrink_to_fit(); - Ok(out_data) -} diff --git a/core/src/backend/render/utils.rs b/core/src/backend/render/utils.rs new file mode 100644 index 000000000..f54fedc3b --- /dev/null +++ b/core/src/backend/render/utils.rs @@ -0,0 +1,395 @@ +use crate::backend::render::{Bitmap, BitmapFormat, Error, JpegTagFormat}; +use std::borrow::Cow; +use std::io::Read; +use swf::Color; + +/// Determines the format of the image data in `data` from a DefineBitsJPEG2/3 tag. +pub fn determine_jpeg_tag_format(data: &[u8]) -> JpegTagFormat { + match data { + [0xff, 0xd8, ..] => JpegTagFormat::Jpeg, + [0xff, 0xd9, 0xff, 0xd8, ..] => JpegTagFormat::Jpeg, // erroneous header in SWF + [0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a, ..] => JpegTagFormat::Png, + [0x47, 0x49, 0x46, 0x38, 0x39, 0x61, ..] => JpegTagFormat::Gif, + _ => JpegTagFormat::Unknown, + } +} + +/// Decodes bitmap data from a DefineBitsJPEG2/3 tag. +/// The data is returned with pre-multiplied alpha. +pub fn decode_define_bits_jpeg(data: &[u8], alpha_data: Option<&[u8]>) -> Result { + let format = determine_jpeg_tag_format(data); + if format != JpegTagFormat::Jpeg && alpha_data.is_some() { + // Only DefineBitsJPEG3 with true JPEG data should have separate alpha data. + log::warn!("DefineBitsJPEG contains non-JPEG data with alpha; probably incorrect") + } + match format { + JpegTagFormat::Jpeg => decode_jpeg(data, alpha_data), + JpegTagFormat::Png => decode_png(data), + JpegTagFormat::Gif => decode_gif(data), + JpegTagFormat::Unknown => Err("Unknown bitmap data format".into()), + } +} + +/// Glues the JPEG encoding tables from a JPEGTables SWF tag to the JPEG data +/// in a DefineBits tag, producing complete JPEG data suitable for a decoder. +pub fn glue_tables_to_jpeg<'a>( + jpeg_data: &'a [u8], + jpeg_tables: Option<&'a [u8]>, +) -> Cow<'a, [u8]> { + if let Some(jpeg_tables) = jpeg_tables { + if jpeg_tables.len() >= 2 { + let mut full_jpeg = Vec::with_capacity(jpeg_tables.len() + jpeg_data.len()); + full_jpeg.extend_from_slice(&jpeg_tables[..jpeg_tables.len() - 2]); + if jpeg_data.len() >= 2 { + full_jpeg.extend_from_slice(&jpeg_data[2..]); + } + + return full_jpeg.into(); + } + } + + // No JPEG tables or not enough data; return JPEG data as is + jpeg_data.into() +} + +/// Removes potential invalid JPEG data from SWF DefineBitsJPEG tags. +/// +/// SWF19 p.138: +/// "Before version 8 of the SWF file format, SWF files could contain an erroneous header of 0xFF, 0xD9, 0xFF, 0xD8 before the JPEG SOI marker." +/// These bytes need to be removed for the JPEG to decode properly. +pub fn remove_invalid_jpeg_data(mut data: &[u8]) -> Cow<[u8]> { + // TODO: Might be better to return an Box> instead of a Cow here, + // where the spliced iter is a data[..n].chain(data[n+4..])? + if data.starts_with(&[0xFF, 0xD9, 0xFF, 0xD8]) { + data = &data[4..]; + } + if let Some(pos) = data.windows(4).position(|w| w == [0xFF, 0xD9, 0xFF, 0xD8]) { + let mut out_data = Vec::with_capacity(data.len() - 4); + out_data.extend_from_slice(&data[..pos]); + out_data.extend_from_slice(&data[pos + 4..]); + out_data.into() + } else { + data.into() + } +} + +/// Decodes a JPEG with optional alpha data. +/// The decoded bitmap will have pre-multiplied alpha. +fn decode_jpeg( + jpeg_data: &[u8], + alpha_data: Option<&[u8]>, +) -> Result> { + let jpeg_data = remove_invalid_jpeg_data(jpeg_data); + + let mut decoder = jpeg_decoder::Decoder::new(&jpeg_data[..]); + decoder.read_info()?; + let metadata = decoder.info().ok_or("Unable to get image info")?; + let decoded_data = decoder.decode()?; + + let decoded_data = match metadata.pixel_format { + jpeg_decoder::PixelFormat::RGB24 => decoded_data, + jpeg_decoder::PixelFormat::CMYK32 => decoded_data + .chunks_exact(4) + .flat_map(|cmyk| { + let c = 255 - u16::from(cmyk[0]); + let m = 255 - u16::from(cmyk[1]); + let y = 255 - u16::from(cmyk[2]); + let k = 256 - u16::from(cmyk[3]); + + let r = c * k / 255; + let g = m * k / 255; + let b = y * k / 255; + [r as u8, g as u8, b as u8] + }) + .collect(), + jpeg_decoder::PixelFormat::L8 => { + let mut rgb = Vec::with_capacity(decoded_data.len() * 3); + for elem in decoded_data { + rgb.push(elem); + rgb.push(elem); + rgb.push(elem); + } + rgb + } + jpeg_decoder::PixelFormat::L16 => { + log::warn!("Unimplemented L16 JPEG pixel format"); + decoded_data + } + }; + + // Decompress the alpha data (DEFLATE compression). + if let Some(alpha_data) = alpha_data { + let alpha_data = decompress_zlib(alpha_data)?; + + if alpha_data.len() == decoded_data.len() / 3 { + let mut rgba = Vec::with_capacity((decoded_data.len() / 3) * 4); + let mut i = 0; + let mut a = 0; + while i < decoded_data.len() { + // The JPEG data should be premultiplied alpha, but it isn't in some incorrect SWFs (see #6893). + // This means 0% alpha pixels may have color and incorrectly show as visible. + // Flash Player clamps color to the alpha value to fix this case. + // Only applies to DefineBitsJPEG3; DefineBitsLossless does not seem to clamp. + let alpha = alpha_data[a]; + rgba.push(decoded_data[i].min(alpha)); + rgba.push(decoded_data[i + 1].min(alpha)); + rgba.push(decoded_data[i + 2].min(alpha)); + rgba.push(alpha); + i += 3; + a += 1; + } + return Ok(Bitmap::new( + metadata.width.into(), + metadata.height.into(), + BitmapFormat::Rgba, + rgba, + )); + } else { + // Size isn't correct; fallback to RGB? + log::error!("Size mismatch in DefineBitsJPEG3 alpha data"); + } + } + + // No alpha. + Ok(Bitmap::new( + metadata.width.into(), + metadata.height.into(), + BitmapFormat::Rgb, + decoded_data, + )) +} + +/// Decodes the bitmap data in DefineBitsLossless tag into RGBA. +/// DefineBitsLossless is Zlib encoded pixel data (similar to PNG), possibly +/// palletized. +pub fn decode_define_bits_lossless( + swf_tag: &swf::DefineBitsLossless, +) -> Result> { + // Decompress the image data (DEFLATE compression). + let mut decoded_data = decompress_zlib(swf_tag.data)?; + + // Swizzle/de-palettize the bitmap. + let out_data = match (swf_tag.version, swf_tag.format) { + (1, swf::BitmapFormat::Rgb15) => { + let padded_width = (swf_tag.width + 0b1) & !0b1; + let mut out_data: Vec = + Vec::with_capacity(swf_tag.width as usize * swf_tag.height as usize * 4); + let mut i = 0; + for _ in 0..swf_tag.height { + for _ in 0..swf_tag.width { + let compressed = u16::from_be_bytes([decoded_data[i], decoded_data[i + 1]]); + let rgb5_component = |shift: u16| { + let component = compressed >> shift & 0x1F; + ((component * 255 + 15) / 31) as u8 + }; + out_data.push(rgb5_component(10)); + out_data.push(rgb5_component(5)); + out_data.push(rgb5_component(0)); + out_data.push(0xff); + i += 2; + } + i += (padded_width - swf_tag.width) as usize * 2; + } + out_data + } + (1, swf::BitmapFormat::Rgb32) => { + let mut i = 0; + while i < decoded_data.len() { + decoded_data[i] = decoded_data[i + 1]; + decoded_data[i + 1] = decoded_data[i + 2]; + decoded_data[i + 2] = decoded_data[i + 3]; + decoded_data[i + 3] = 0xff; + i += 4; + } + decoded_data + } + (2, swf::BitmapFormat::Rgb32) => { + let mut i = 0; + while i < decoded_data.len() { + let alpha = decoded_data[i]; + decoded_data[i] = decoded_data[i + 1]; + decoded_data[i + 1] = decoded_data[i + 2]; + decoded_data[i + 2] = decoded_data[i + 3]; + decoded_data[i + 3] = alpha; + i += 4; + } + decoded_data + } + (1, swf::BitmapFormat::ColorMap8 { num_colors }) => { + let mut i = 0; + let padded_width = (swf_tag.width + 0b11) & !0b11; + + let mut palette = Vec::with_capacity(num_colors as usize + 1); + for _ in 0..=num_colors { + palette.push(Color { + r: decoded_data[i], + g: decoded_data[i + 1], + b: decoded_data[i + 2], + a: 255, + }); + i += 3; + } + let mut out_data: Vec = + Vec::with_capacity(swf_tag.width as usize * swf_tag.height as usize * 4); + for _ in 0..swf_tag.height { + for _ in 0..swf_tag.width { + let entry = decoded_data[i] as usize; + if entry < palette.len() { + let color = &palette[entry]; + out_data.push(color.r); + out_data.push(color.g); + out_data.push(color.b); + out_data.push(color.a); + } else { + out_data.push(0); + out_data.push(0); + out_data.push(0); + out_data.push(255); + } + i += 1; + } + i += (padded_width - swf_tag.width) as usize; + } + out_data + } + (2, swf::BitmapFormat::ColorMap8 { num_colors }) => { + let mut i = 0; + let padded_width = (swf_tag.width + 0b11) & !0b11; + + let mut palette = Vec::with_capacity(num_colors as usize + 1); + for _ in 0..=num_colors { + palette.push(Color { + r: decoded_data[i], + g: decoded_data[i + 1], + b: decoded_data[i + 2], + a: decoded_data[i + 3], + }); + i += 4; + } + let mut out_data: Vec = + Vec::with_capacity(swf_tag.width as usize * swf_tag.height as usize * 4); + for _ in 0..swf_tag.height { + for _ in 0..swf_tag.width { + let entry = decoded_data[i] as usize; + if entry < palette.len() { + let color = &palette[entry]; + out_data.push(color.r); + out_data.push(color.g); + out_data.push(color.b); + out_data.push(color.a); + } else { + out_data.push(0); + out_data.push(0); + out_data.push(0); + out_data.push(0); + } + i += 1; + } + i += (padded_width - swf_tag.width) as usize; + } + out_data + } + _ => { + return Err(format!( + "Unexpected DefineBitsLossless{} format: {:?} ", + swf_tag.version, swf_tag.format, + ) + .into()); + } + }; + + Ok(Bitmap::new( + swf_tag.width.into(), + swf_tag.height.into(), + BitmapFormat::Rgba, + out_data, + )) +} + +/// Decodes the bitmap data in DefineBitsLossless tag into RGBA. +/// DefineBitsLossless is Zlib encoded pixel data (similar to PNG), possibly +/// palletized. +fn decode_png(data: &[u8]) -> Result { + use png::{ColorType, Transformations}; + + let mut decoder = png::Decoder::new(data); + // Normalize output to 8-bit grayscale or RGB. + // Ideally we'd want to normalize to 8-bit RGB only, but seems like the `png` crate provides no such a feature. + decoder.set_transformations(Transformations::normalize_to_color8()); + let mut reader = decoder.read_info()?; + + let mut data = vec![0; reader.output_buffer_size()]; + let info = reader.next_frame(&mut data)?; + + let (format, data) = match info.color_type { + ColorType::Rgb => (BitmapFormat::Rgb, data), + ColorType::Rgba => { + // In contrast to DefineBitsLossless tags, PNGs embedded in a DefineBitsJPEG tag will not have + // premultiplied alpha and need to be converted before sending to the renderer. + premultiply_alpha_rgba(&mut data); + (BitmapFormat::Rgba, data) + } + ColorType::Grayscale => ( + BitmapFormat::Rgb, + data.into_iter().flat_map(|v| [v, v, v]).collect(), + ), + ColorType::GrayscaleAlpha => { + ( + BitmapFormat::Rgba, + data.chunks_exact(2) + .flat_map(|pixel| { + // Pre-multiply alpha. + let a = pixel[1]; + let v = (u16::from(pixel[0]) * u16::from(a) / 255) as u8; + [v, v, v, a] + }) + .collect(), + ) + } + ColorType::Indexed => { + // Shouldn't get here because of `normalize_to_color8` transformation above. + unreachable!("Unexpected PNG ColorType::Indexed"); + } + }; + + Ok(Bitmap::new(info.width, info.height, format, data)) +} + +/// Decodes the bitmap data in DefineBitsLossless tag into RGBA. +/// DefineBitsLossless is Zlib encoded pixel data (similar to PNG), possibly +/// palletized. +fn decode_gif(data: &[u8]) -> Result { + let mut decode_options = gif::DecodeOptions::new(); + decode_options.set_color_output(gif::ColorOutput::RGBA); + let mut reader = decode_options.read_info(data)?; + let frame = reader.read_next_frame()?.ok_or("No frames in GIF")?; + // GIFs embedded in a DefineBitsJPEG tag will not have premultiplied alpha and need to be converted before sending to the renderer. + let mut data = frame.buffer.to_vec(); + premultiply_alpha_rgba(&mut data); + + Ok(Bitmap::new( + frame.width.into(), + frame.height.into(), + BitmapFormat::Rgba, + data, + )) +} + +/// Converts standard RBGA to premultiplied alpha. +fn premultiply_alpha_rgba(rgba: &mut [u8]) { + rgba.chunks_exact_mut(4).for_each(|rgba| { + let a = f32::from(rgba[3]) / 255.0; + rgba[0] = (f32::from(rgba[0]) * a) as u8; + rgba[1] = (f32::from(rgba[1]) * a) as u8; + rgba[2] = (f32::from(rgba[2]) * a) as u8; + }) +} + +/// Decodes zlib-compressed data. +fn decompress_zlib(data: &[u8]) -> Result, std::io::Error> { + let mut out_data = Vec::new(); + let mut decoder = flate2::bufread::ZlibDecoder::new(data); + decoder.read_to_end(&mut out_data)?; + out_data.shrink_to_fit(); + Ok(out_data) +}