summaryrefslogtreecommitdiffstats
path: root/third_party/rust/image/src/ico
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-28 14:29:10 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-28 14:29:10 +0000
commit2aa4a82499d4becd2284cdb482213d541b8804dd (patch)
treeb80bf8bf13c3766139fbacc530efd0dd9d54394c /third_party/rust/image/src/ico
parentInitial commit. (diff)
downloadfirefox-2aa4a82499d4becd2284cdb482213d541b8804dd.tar.xz
firefox-2aa4a82499d4becd2284cdb482213d541b8804dd.zip
Adding upstream version 86.0.1.upstream/86.0.1upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'third_party/rust/image/src/ico')
-rw-r--r--third_party/rust/image/src/ico/decoder.rs284
-rw-r--r--third_party/rust/image/src/ico/encoder.rs113
-rw-r--r--third_party/rust/image/src/ico/mod.rs13
3 files changed, 410 insertions, 0 deletions
diff --git a/third_party/rust/image/src/ico/decoder.rs b/third_party/rust/image/src/ico/decoder.rs
new file mode 100644
index 0000000000..0dfc44f112
--- /dev/null
+++ b/third_party/rust/image/src/ico/decoder.rs
@@ -0,0 +1,284 @@
+use byteorder::{LittleEndian, ReadBytesExt};
+use std::convert::TryFrom;
+use std::io::{self, Cursor, Read, Seek, SeekFrom};
+use std::marker::PhantomData;
+use std::mem;
+
+use crate::color::ColorType;
+use crate::error::{ImageError, ImageResult};
+use crate::image::{self, ImageDecoder};
+
+use self::InnerDecoder::*;
+use crate::bmp::BmpDecoder;
+use crate::png::PngDecoder;
+
+// http://www.w3.org/TR/PNG-Structure.html
+// The first eight bytes of a PNG file always contain the following (decimal) values:
+const PNG_SIGNATURE: [u8; 8] = [137, 80, 78, 71, 13, 10, 26, 10];
+
+/// An ico decoder
+pub struct IcoDecoder<R: Read> {
+ selected_entry: DirEntry,
+ inner_decoder: InnerDecoder<R>,
+}
+
+enum InnerDecoder<R: Read> {
+ BMP(BmpDecoder<R>),
+ PNG(PngDecoder<R>),
+}
+
+#[derive(Clone, Copy, Default)]
+struct DirEntry {
+ width: u8,
+ height: u8,
+ color_count: u8,
+ reserved: u8,
+
+ num_color_planes: u16,
+ bits_per_pixel: u16,
+
+ image_length: u32,
+ image_offset: u32,
+}
+
+impl<R: Read + Seek> IcoDecoder<R> {
+ /// Create a new decoder that decodes from the stream ```r```
+ pub fn new(mut r: R) -> ImageResult<IcoDecoder<R>> {
+ let entries = read_entries(&mut r)?;
+ let entry = best_entry(entries)?;
+ let decoder = entry.decoder(r)?;
+
+ Ok(IcoDecoder {
+ selected_entry: entry,
+ inner_decoder: decoder,
+ })
+ }
+}
+
+fn read_entries<R: Read>(r: &mut R) -> ImageResult<Vec<DirEntry>> {
+ let _reserved = r.read_u16::<LittleEndian>()?;
+ let _type = r.read_u16::<LittleEndian>()?;
+ let count = r.read_u16::<LittleEndian>()?;
+ (0..count).map(|_| read_entry(r)).collect()
+}
+
+fn read_entry<R: Read>(r: &mut R) -> ImageResult<DirEntry> {
+ let mut entry = DirEntry::default();
+
+ entry.width = r.read_u8()?;
+ entry.height = r.read_u8()?;
+ entry.color_count = r.read_u8()?;
+ // Reserved value (not used)
+ entry.reserved = r.read_u8()?;
+
+ // This may be either the number of color planes (0 or 1), or the horizontal coordinate
+ // of the hotspot for CUR files.
+ entry.num_color_planes = r.read_u16::<LittleEndian>()?;
+ if entry.num_color_planes > 256 {
+ return Err(ImageError::FormatError(
+ "ICO image entry has a too large color planes/hotspot value".to_string(),
+ ));
+ }
+
+ // This may be either the bit depth (may be 0 meaning unspecified),
+ // or the vertical coordinate of the hotspot for CUR files.
+ entry.bits_per_pixel = r.read_u16::<LittleEndian>()?;
+ if entry.bits_per_pixel > 256 {
+ return Err(ImageError::FormatError(
+ "ICO image entry has a too large bits per pixel/hotspot value".to_string(),
+ ));
+ }
+
+ entry.image_length = r.read_u32::<LittleEndian>()?;
+ entry.image_offset = r.read_u32::<LittleEndian>()?;
+
+ Ok(entry)
+}
+
+/// Find the entry with the highest (color depth, size).
+fn best_entry(mut entries: Vec<DirEntry>) -> ImageResult<DirEntry> {
+ let mut best = entries.pop().ok_or(ImageError::ImageEnd)?;
+ let mut best_score = (
+ best.bits_per_pixel,
+ u32::from(best.real_width()) * u32::from(best.real_height()),
+ );
+
+ for entry in entries {
+ let score = (
+ entry.bits_per_pixel,
+ u32::from(entry.real_width()) * u32::from(entry.real_height()),
+ );
+ if score > best_score {
+ best = entry;
+ best_score = score;
+ }
+ }
+ Ok(best)
+}
+
+impl DirEntry {
+ fn real_width(&self) -> u16 {
+ match self.width {
+ 0 => 256,
+ w => u16::from(w),
+ }
+ }
+
+ fn real_height(&self) -> u16 {
+ match self.height {
+ 0 => 256,
+ h => u16::from(h),
+ }
+ }
+
+ fn matches_dimensions(&self, width: u32, height: u32) -> bool {
+ u32::from(self.real_width()) == width && u32::from(self.real_height()) == height
+ }
+
+ fn seek_to_start<R: Read + Seek>(&self, r: &mut R) -> ImageResult<()> {
+ r.seek(SeekFrom::Start(u64::from(self.image_offset)))?;
+ Ok(())
+ }
+
+ fn is_png<R: Read + Seek>(&self, r: &mut R) -> ImageResult<bool> {
+ self.seek_to_start(r)?;
+
+ // Read the first 8 bytes to sniff the image.
+ let mut signature = [0u8; 8];
+ r.read_exact(&mut signature)?;
+
+ Ok(signature == PNG_SIGNATURE)
+ }
+
+ fn decoder<R: Read + Seek>(&self, mut r: R) -> ImageResult<InnerDecoder<R>> {
+ let is_png = self.is_png(&mut r)?;
+ self.seek_to_start(&mut r)?;
+
+ if is_png {
+ Ok(PNG(PngDecoder::new(r)?))
+ } else {
+ Ok(BMP(BmpDecoder::new_with_ico_format(r)?))
+ }
+ }
+}
+
+/// Wrapper struct around a `Cursor<Vec<u8>>`
+pub struct IcoReader<R>(Cursor<Vec<u8>>, PhantomData<R>);
+impl<R> Read for IcoReader<R> {
+ fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
+ self.0.read(buf)
+ }
+ fn read_to_end(&mut self, buf: &mut Vec<u8>) -> io::Result<usize> {
+ if self.0.position() == 0 && buf.is_empty() {
+ mem::swap(buf, self.0.get_mut());
+ Ok(buf.len())
+ } else {
+ self.0.read_to_end(buf)
+ }
+ }
+}
+
+impl<'a, R: 'a + Read + Seek> ImageDecoder<'a> for IcoDecoder<R> {
+ type Reader = IcoReader<R>;
+
+ fn dimensions(&self) -> (u32, u32) {
+ match self.inner_decoder {
+ BMP(ref decoder) => decoder.dimensions(),
+ PNG(ref decoder) => decoder.dimensions(),
+ }
+ }
+
+ fn color_type(&self) -> ColorType {
+ match self.inner_decoder {
+ BMP(ref decoder) => decoder.color_type(),
+ PNG(ref decoder) => decoder.color_type(),
+ }
+ }
+
+ fn into_reader(self) -> ImageResult<Self::Reader> {
+ Ok(IcoReader(Cursor::new(image::decoder_to_vec(self)?), PhantomData))
+ }
+
+ fn read_image(self, buf: &mut [u8]) -> ImageResult<()> {
+ assert_eq!(u64::try_from(buf.len()), Ok(self.total_bytes()));
+ match self.inner_decoder {
+ PNG(decoder) => {
+ if self.selected_entry.image_length < PNG_SIGNATURE.len() as u32 {
+ return Err(ImageError::FormatError(
+ "Entry specified a length that is shorter than PNG header!".to_string(),
+ ));
+ }
+
+ // Check if the image dimensions match the ones in the image data.
+ let (width, height) = decoder.dimensions();
+ if !self.selected_entry.matches_dimensions(width, height) {
+ return Err(ImageError::FormatError(
+ "Entry and PNG dimensions do not match!".to_string(),
+ ));
+ }
+
+ // Embedded PNG images can only be of the 32BPP RGBA format.
+ // https://blogs.msdn.microsoft.com/oldnewthing/20101022-00/?p=12473/
+ let color_type = decoder.color_type();
+ if let ColorType::Rgba8 = color_type {
+ } else {
+ return Err(ImageError::FormatError(
+ "The PNG is not in RGBA format!".to_string(),
+ ));
+ }
+
+ decoder.read_image(buf)
+ }
+ BMP(mut decoder) => {
+ let (width, height) = decoder.dimensions();
+ if !self.selected_entry.matches_dimensions(width, height) {
+ return Err(ImageError::FormatError(
+ "Entry({:?}) and BMP({:?}) dimensions do not match!".to_string(),
+ ));
+ }
+
+ // The ICO decoder needs an alpha channel to apply the AND mask.
+ if decoder.color_type() != ColorType::Rgba8 {
+ return Err(ImageError::UnsupportedColor(decoder.color_type().into()));
+ }
+
+ decoder.read_image_data(buf)?;
+
+ // If there's an AND mask following the image, read and apply it.
+ let r = decoder.reader();
+ let mask_start = r.seek(SeekFrom::Current(0))?;
+ let mask_end =
+ u64::from(self.selected_entry.image_offset + self.selected_entry.image_length);
+ let mask_length = mask_end - mask_start;
+
+ if mask_length > 0 {
+ // A mask row contains 1 bit per pixel, padded to 4 bytes.
+ let mask_row_bytes = ((width + 31) / 32) * 4;
+ let expected_length = u64::from(mask_row_bytes) * u64::from(height);
+ if mask_length < expected_length {
+ return Err(ImageError::ImageEnd);
+ }
+
+ for y in 0..height {
+ let mut x = 0;
+ for _ in 0..mask_row_bytes {
+ // Apply the bits of each byte until we reach the end of the row.
+ let mask_byte = r.read_u8()?;
+ for bit in (0..8).rev() {
+ if x >= width {
+ break;
+ }
+ if mask_byte & (1 << bit) != 0 {
+ // Set alpha channel to transparent.
+ buf[((height - y - 1) * width + x) as usize * 4 + 3] = 0;
+ }
+ x += 1;
+ }
+ }
+ }
+ }
+ Ok(())
+ }
+ }
+ }
+}
diff --git a/third_party/rust/image/src/ico/encoder.rs b/third_party/rust/image/src/ico/encoder.rs
new file mode 100644
index 0000000000..57a14a1ce8
--- /dev/null
+++ b/third_party/rust/image/src/ico/encoder.rs
@@ -0,0 +1,113 @@
+use byteorder::{LittleEndian, WriteBytesExt};
+use std::io::{self, Write};
+
+use crate::color::ColorType;
+use crate::error::ImageResult;
+use crate::image::ImageEncoder;
+
+use crate::png::PNGEncoder;
+
+// Enum value indicating an ICO image (as opposed to a CUR image):
+const ICO_IMAGE_TYPE: u16 = 1;
+// The length of an ICO file ICONDIR structure, in bytes:
+const ICO_ICONDIR_SIZE: u32 = 6;
+// The length of an ICO file DIRENTRY structure, in bytes:
+const ICO_DIRENTRY_SIZE: u32 = 16;
+
+/// ICO encoder
+pub struct ICOEncoder<W: Write> {
+ w: W,
+}
+
+impl<W: Write> ICOEncoder<W> {
+ /// Create a new encoder that writes its output to ```w```.
+ pub fn new(w: W) -> ICOEncoder<W> {
+ ICOEncoder { w }
+ }
+
+ /// Encodes the image ```image``` that has dimensions ```width``` and
+ /// ```height``` and ```ColorType``` ```c```. The dimensions of the image
+ /// must be between 1 and 256 (inclusive) or an error will be returned.
+ pub fn encode(
+ mut self,
+ data: &[u8],
+ width: u32,
+ height: u32,
+ color: ColorType,
+ ) -> ImageResult<()> {
+ let mut image_data: Vec<u8> = Vec::new();
+ PNGEncoder::new(&mut image_data).encode(data, width, height, color)?;
+
+ write_icondir(&mut self.w, 1)?;
+ write_direntry(
+ &mut self.w,
+ width,
+ height,
+ color,
+ ICO_ICONDIR_SIZE + ICO_DIRENTRY_SIZE,
+ image_data.len() as u32,
+ )?;
+ self.w.write_all(&image_data)?;
+ Ok(())
+ }
+}
+
+impl<W: Write> ImageEncoder for ICOEncoder<W> {
+ fn write_image(
+ self,
+ buf: &[u8],
+ width: u32,
+ height: u32,
+ color_type: ColorType,
+ ) -> ImageResult<()> {
+ self.encode(buf, width, height, color_type)
+ }
+}
+
+fn write_icondir<W: Write>(w: &mut W, num_images: u16) -> io::Result<()> {
+ // Reserved field (must be zero):
+ w.write_u16::<LittleEndian>(0)?;
+ // Image type (ICO or CUR):
+ w.write_u16::<LittleEndian>(ICO_IMAGE_TYPE)?;
+ // Number of images in the file:
+ w.write_u16::<LittleEndian>(num_images)?;
+ Ok(())
+}
+
+fn write_direntry<W: Write>(
+ w: &mut W,
+ width: u32,
+ height: u32,
+ color: ColorType,
+ data_start: u32,
+ data_size: u32,
+) -> io::Result<()> {
+ // Image dimensions:
+ write_width_or_height(w, width)?;
+ write_width_or_height(w, height)?;
+ // Number of colors in palette (or zero for no palette):
+ w.write_u8(0)?;
+ // Reserved field (must be zero):
+ w.write_u8(0)?;
+ // Color planes:
+ w.write_u16::<LittleEndian>(0)?;
+ // Bits per pixel:
+ w.write_u16::<LittleEndian>(color.bits_per_pixel())?;
+ // Image data size, in bytes:
+ w.write_u32::<LittleEndian>(data_size)?;
+ // Image data offset, in bytes:
+ w.write_u32::<LittleEndian>(data_start)?;
+ Ok(())
+}
+
+/// Encode a width/height value as a single byte, where 0 means 256.
+fn write_width_or_height<W: Write>(w: &mut W, value: u32) -> io::Result<()> {
+ if value < 1 || value > 256 {
+ return Err(io::Error::new(
+ io::ErrorKind::InvalidData,
+ "Invalid ICO dimensions (width and \
+ height must be between 1 and 256)",
+ ));
+ }
+ w.write_u8(if value < 256 { value as u8 } else { 0 })
+}
diff --git a/third_party/rust/image/src/ico/mod.rs b/third_party/rust/image/src/ico/mod.rs
new file mode 100644
index 0000000000..fd65df1f6e
--- /dev/null
+++ b/third_party/rust/image/src/ico/mod.rs
@@ -0,0 +1,13 @@
+//! Decoding and Encoding of ICO files
+//!
+//! A decoder and encoder for ICO (Windows Icon) image container files.
+//!
+//! # Related Links
+//! * <https://msdn.microsoft.com/en-us/library/ms997538.aspx>
+//! * <https://en.wikipedia.org/wiki/ICO_%28file_format%29>
+
+pub use self::decoder::IcoDecoder;
+pub use self::encoder::ICOEncoder;
+
+mod decoder;
+mod encoder;