Coverage Report

Created: 2025-07-18 06:49

/src/image/src/codecs/ico/encoder.rs
Line
Count
Source (jump to first uncovered line)
1
use byteorder_lite::{LittleEndian, WriteBytesExt};
2
use std::borrow::Cow;
3
use std::io::{self, Write};
4
5
use crate::codecs::png::PngEncoder;
6
use crate::error::{ImageError, ImageResult, ParameterError, ParameterErrorKind};
7
use crate::{ExtendedColorType, ImageEncoder};
8
9
// Enum value indicating an ICO image (as opposed to a CUR image):
10
const ICO_IMAGE_TYPE: u16 = 1;
11
// The length of an ICO file ICONDIR structure, in bytes:
12
const ICO_ICONDIR_SIZE: u32 = 6;
13
// The length of an ICO file DIRENTRY structure, in bytes:
14
const ICO_DIRENTRY_SIZE: u32 = 16;
15
16
/// ICO encoder
17
pub struct IcoEncoder<W: Write> {
18
    w: W,
19
}
20
21
/// An ICO image entry
22
pub struct IcoFrame<'a> {
23
    // Pre-encoded PNG or BMP
24
    encoded_image: Cow<'a, [u8]>,
25
    // Stored as `0 => 256, n => n`
26
    width: u8,
27
    // Stored as `0 => 256, n => n`
28
    height: u8,
29
    color_type: ExtendedColorType,
30
}
31
32
impl<'a> IcoFrame<'a> {
33
    /// Construct a new `IcoFrame` using a pre-encoded PNG or BMP
34
    ///
35
    /// The `width` and `height` must be between 1 and 256 (inclusive).
36
0
    pub fn with_encoded(
37
0
        encoded_image: impl Into<Cow<'a, [u8]>>,
38
0
        width: u32,
39
0
        height: u32,
40
0
        color_type: ExtendedColorType,
41
0
    ) -> ImageResult<Self> {
42
0
        let encoded_image = encoded_image.into();
43
0
44
0
        if !(1..=256).contains(&width) {
45
0
            return Err(ImageError::Parameter(ParameterError::from_kind(
46
0
                ParameterErrorKind::Generic(format!(
47
0
                    "the image width must be `1..=256`, instead width {width} was provided",
48
0
                )),
49
0
            )));
50
0
        }
51
0
52
0
        if !(1..=256).contains(&height) {
53
0
            return Err(ImageError::Parameter(ParameterError::from_kind(
54
0
                ParameterErrorKind::Generic(format!(
55
0
                    "the image height must be `1..=256`, instead height {height} was provided",
56
0
                )),
57
0
            )));
58
0
        }
59
0
60
0
        Ok(Self {
61
0
            encoded_image,
62
0
            width: width as u8,
63
0
            height: height as u8,
64
0
            color_type,
65
0
        })
66
0
    }
67
68
    /// Construct a new `IcoFrame` by encoding `buf` as a PNG
69
    ///
70
    /// The `width` and `height` must be between 1 and 256 (inclusive)
71
0
    pub fn as_png(
72
0
        buf: &[u8],
73
0
        width: u32,
74
0
        height: u32,
75
0
        color_type: ExtendedColorType,
76
0
    ) -> ImageResult<Self> {
77
0
        let mut image_data: Vec<u8> = Vec::new();
78
0
        PngEncoder::new(&mut image_data).write_image(buf, width, height, color_type)?;
79
80
0
        let frame = Self::with_encoded(image_data, width, height, color_type)?;
81
0
        Ok(frame)
82
0
    }
83
}
84
85
impl<W: Write> IcoEncoder<W> {
86
    /// Create a new encoder that writes its output to ```w```.
87
0
    pub fn new(w: W) -> IcoEncoder<W> {
88
0
        IcoEncoder { w }
89
0
    }
Unexecuted instantiation: <image::codecs::ico::encoder::IcoEncoder<_>>::new
Unexecuted instantiation: <image::codecs::ico::encoder::IcoEncoder<&mut std::io::cursor::Cursor<alloc::vec::Vec<u8>>>>::new
90
91
    /// Takes some [`IcoFrame`]s and encodes them into an ICO.
92
    ///
93
    /// `images` is a list of images, usually ordered by dimension, which
94
    /// must be between 1 and 65535 (inclusive) in length.
95
0
    pub fn encode_images(mut self, images: &[IcoFrame<'_>]) -> ImageResult<()> {
96
0
        if !(1..=usize::from(u16::MAX)).contains(&images.len()) {
97
0
            return Err(ImageError::Parameter(ParameterError::from_kind(
98
0
                ParameterErrorKind::Generic(format!(
99
0
                    "the number of images must be `1..=u16::MAX`, instead {} images were provided",
100
0
                    images.len(),
101
0
                )),
102
0
            )));
103
0
        }
104
0
        let num_images = images.len() as u16;
105
0
106
0
        let mut offset = ICO_ICONDIR_SIZE + (ICO_DIRENTRY_SIZE * (images.len() as u32));
107
0
        write_icondir(&mut self.w, num_images)?;
108
0
        for image in images {
109
0
            write_direntry(
110
0
                &mut self.w,
111
0
                image.width,
112
0
                image.height,
113
0
                image.color_type,
114
0
                offset,
115
0
                image.encoded_image.len() as u32,
116
0
            )?;
117
118
0
            offset += image.encoded_image.len() as u32;
119
        }
120
0
        for image in images {
121
0
            self.w.write_all(&image.encoded_image)?;
122
        }
123
0
        Ok(())
124
0
    }
Unexecuted instantiation: <image::codecs::ico::encoder::IcoEncoder<_>>::encode_images
Unexecuted instantiation: <image::codecs::ico::encoder::IcoEncoder<&mut std::io::cursor::Cursor<alloc::vec::Vec<u8>>>>::encode_images
125
}
126
127
impl<W: Write> ImageEncoder for IcoEncoder<W> {
128
    /// Write an ICO image with the specified width, height, and color type.
129
    ///
130
    /// For color types with 16-bit per channel or larger, the contents of `buf` should be in
131
    /// native endian.
132
    ///
133
    /// WARNING: In image 0.23.14 and earlier this method erroneously expected buf to be in big endian.
134
    #[track_caller]
135
0
    fn write_image(
136
0
        self,
137
0
        buf: &[u8],
138
0
        width: u32,
139
0
        height: u32,
140
0
        color_type: ExtendedColorType,
141
0
    ) -> ImageResult<()> {
142
0
        let expected_buffer_len = color_type.buffer_size(width, height);
143
0
        assert_eq!(
144
0
            expected_buffer_len,
145
0
            buf.len() as u64,
146
0
            "Invalid buffer length: expected {expected_buffer_len} got {} for {width}x{height} image",
147
0
            buf.len(),
148
        );
149
150
0
        let image = IcoFrame::as_png(buf, width, height, color_type)?;
151
0
        self.encode_images(&[image])
152
0
    }
Unexecuted instantiation: <image::codecs::ico::encoder::IcoEncoder<_> as image::io::encoder::ImageEncoder>::write_image
Unexecuted instantiation: <image::codecs::ico::encoder::IcoEncoder<&mut std::io::cursor::Cursor<alloc::vec::Vec<u8>>> as image::io::encoder::ImageEncoder>::write_image
153
}
154
155
0
fn write_icondir<W: Write>(w: &mut W, num_images: u16) -> io::Result<()> {
156
0
    // Reserved field (must be zero):
157
0
    w.write_u16::<LittleEndian>(0)?;
158
    // Image type (ICO or CUR):
159
0
    w.write_u16::<LittleEndian>(ICO_IMAGE_TYPE)?;
160
    // Number of images in the file:
161
0
    w.write_u16::<LittleEndian>(num_images)?;
162
0
    Ok(())
163
0
}
Unexecuted instantiation: image::codecs::ico::encoder::write_icondir::<_>
Unexecuted instantiation: image::codecs::ico::encoder::write_icondir::<&mut std::io::cursor::Cursor<alloc::vec::Vec<u8>>>
164
165
0
fn write_direntry<W: Write>(
166
0
    w: &mut W,
167
0
    width: u8,
168
0
    height: u8,
169
0
    color: ExtendedColorType,
170
0
    data_start: u32,
171
0
    data_size: u32,
172
0
) -> io::Result<()> {
173
0
    // Image dimensions:
174
0
    w.write_u8(width)?;
175
0
    w.write_u8(height)?;
176
    // Number of colors in palette (or zero for no palette):
177
0
    w.write_u8(0)?;
178
    // Reserved field (must be zero):
179
0
    w.write_u8(0)?;
180
    // Color planes:
181
0
    w.write_u16::<LittleEndian>(0)?;
182
    // Bits per pixel:
183
0
    w.write_u16::<LittleEndian>(color.bits_per_pixel())?;
184
    // Image data size, in bytes:
185
0
    w.write_u32::<LittleEndian>(data_size)?;
186
    // Image data offset, in bytes:
187
0
    w.write_u32::<LittleEndian>(data_start)?;
188
0
    Ok(())
189
0
}
Unexecuted instantiation: image::codecs::ico::encoder::write_direntry::<_>
Unexecuted instantiation: image::codecs::ico::encoder::write_direntry::<&mut std::io::cursor::Cursor<alloc::vec::Vec<u8>>>