/src/serenity/Userland/Libraries/LibGfx/ImageFormats/ICOLoader.cpp
Line | Count | Source |
1 | | /* |
2 | | * Copyright (c) 2020, Paul Roukema <roukemap@gmail.com> |
3 | | * |
4 | | * SPDX-License-Identifier: BSD-2-Clause |
5 | | */ |
6 | | |
7 | | #include <AK/ByteBuffer.h> |
8 | | #include <AK/Debug.h> |
9 | | #include <AK/Enumerate.h> |
10 | | #include <AK/MemoryStream.h> |
11 | | #include <AK/Types.h> |
12 | | #include <LibGfx/ImageFormats/BMPLoader.h> |
13 | | #include <LibGfx/ImageFormats/ICOLoader.h> |
14 | | #include <LibGfx/ImageFormats/PNGLoader.h> |
15 | | #include <string.h> |
16 | | |
17 | | namespace Gfx { |
18 | | |
19 | | // FIXME: This is in little-endian order. Maybe need a NetworkOrdered<T> equivalent eventually. |
20 | | struct ICONDIR { |
21 | | u16 must_be_0 = 0; |
22 | | u16 must_be_1 = 0; |
23 | | u16 image_count = 0; |
24 | | }; |
25 | | static_assert(AssertSize<ICONDIR, 6>()); |
26 | | |
27 | | struct ICONDIRENTRY { |
28 | | u8 width; |
29 | | u8 height; |
30 | | u8 color_count; |
31 | | u8 reserved_0; |
32 | | u16 planes; |
33 | | u16 bits_per_pixel; |
34 | | u32 size; |
35 | | u32 offset; |
36 | | }; |
37 | | static_assert(AssertSize<ICONDIRENTRY, 16>()); |
38 | | |
39 | | }; |
40 | | |
41 | | template<> |
42 | | class AK::Traits<Gfx::ICONDIR> : public DefaultTraits<Gfx::ICONDIR> { |
43 | | public: |
44 | 0 | static constexpr bool is_trivially_serializable() { return true; } |
45 | | }; |
46 | | |
47 | | template<> |
48 | | class AK::Traits<Gfx::ICONDIRENTRY> : public DefaultTraits<Gfx::ICONDIRENTRY> { |
49 | | public: |
50 | 0 | static constexpr bool is_trivially_serializable() { return true; } |
51 | | }; |
52 | | |
53 | | namespace Gfx { |
54 | | |
55 | | struct ICOImageDescriptor { |
56 | | u16 width; |
57 | | u16 height; |
58 | | u16 bits_per_pixel; |
59 | | size_t offset; |
60 | | size_t size; |
61 | | RefPtr<Gfx::Bitmap> bitmap; |
62 | | }; |
63 | | |
64 | | struct ICOLoadingContext { |
65 | | enum State { |
66 | | NotDecoded = 0, |
67 | | Error, |
68 | | DirectoryDecoded, |
69 | | BitmapDecoded |
70 | | }; |
71 | | State state { NotDecoded }; |
72 | | u8 const* data { nullptr }; |
73 | | size_t data_size { 0 }; |
74 | | Vector<ICOImageDescriptor> images; |
75 | | size_t largest_index; |
76 | | }; |
77 | | |
78 | | static ErrorOr<size_t> decode_ico_header(Stream& stream) |
79 | 0 | { |
80 | 0 | auto header = TRY(stream.read_value<ICONDIR>()); |
81 | 0 | if (header.must_be_0 != 0 || header.must_be_1 != 1) |
82 | 0 | return Error::from_string_literal("Invalid ICO header"); |
83 | 0 | return { header.image_count }; |
84 | 0 | } |
85 | | |
86 | | static ErrorOr<ICOImageDescriptor> decode_ico_direntry(Stream& stream) |
87 | 0 | { |
88 | 0 | auto entry = TRY(stream.read_value<ICONDIRENTRY>()); |
89 | 0 | ICOImageDescriptor desc = { entry.width, entry.height, entry.bits_per_pixel, entry.offset, entry.size, nullptr }; |
90 | 0 | if (desc.width == 0) |
91 | 0 | desc.width = 256; |
92 | 0 | if (desc.height == 0) |
93 | 0 | desc.height = 256; |
94 | |
|
95 | 0 | return { desc }; |
96 | 0 | } |
97 | | |
98 | | static size_t find_largest_image(ICOLoadingContext const& context) |
99 | 0 | { |
100 | 0 | size_t max_area = 0; |
101 | 0 | size_t largest_index = 0; |
102 | 0 | u16 max_bits_per_pixel = 0; |
103 | |
|
104 | 0 | for (auto const& [index, desc] : enumerate(context.images)) { |
105 | 0 | auto area = static_cast<size_t>(desc.width) * static_cast<size_t>(desc.height); |
106 | |
|
107 | 0 | if (area > max_area || (area == max_area && desc.bits_per_pixel > max_bits_per_pixel)) { |
108 | 0 | max_area = area; |
109 | 0 | largest_index = index; |
110 | 0 | max_bits_per_pixel = desc.bits_per_pixel; |
111 | 0 | } |
112 | 0 | } |
113 | |
|
114 | 0 | return largest_index; |
115 | 0 | } |
116 | | |
117 | | static ErrorOr<void> load_ico_directory(ICOLoadingContext& context) |
118 | 0 | { |
119 | 0 | FixedMemoryStream stream { { context.data, context.data_size } }; |
120 | |
|
121 | 0 | auto image_count = TRY(decode_ico_header(stream)); |
122 | 0 | if (image_count == 0) |
123 | 0 | return Error::from_string_literal("ICO file has no images"); |
124 | | |
125 | 0 | for (size_t i = 0; i < image_count; ++i) { |
126 | 0 | auto desc = TRY(decode_ico_direntry(stream)); |
127 | 0 | if (desc.offset + desc.size < desc.offset // detect integer overflow |
128 | 0 | || (desc.offset + desc.size) > context.data_size) { |
129 | 0 | dbgln_if(ICO_DEBUG, "load_ico_directory: offset: {} size: {} doesn't fit in ICO size: {}", desc.offset, desc.size, context.data_size); |
130 | 0 | return Error::from_string_literal("ICO size too large"); |
131 | 0 | } |
132 | 0 | dbgln_if(ICO_DEBUG, "load_ico_directory: index {} width: {} height: {} offset: {} size: {}", i, desc.width, desc.height, desc.offset, desc.size); |
133 | 0 | TRY(context.images.try_append(desc)); |
134 | 0 | } |
135 | 0 | context.largest_index = find_largest_image(context); |
136 | 0 | context.state = ICOLoadingContext::State::DirectoryDecoded; |
137 | 0 | return {}; |
138 | 0 | } |
139 | | |
140 | | ErrorOr<void> ICOImageDecoderPlugin::load_ico_bitmap(ICOLoadingContext& context) |
141 | 0 | { |
142 | 0 | VERIFY(context.state >= ICOLoadingContext::State::DirectoryDecoded); |
143 | | |
144 | 0 | size_t const real_index = context.largest_index; |
145 | 0 | if (real_index >= context.images.size()) |
146 | 0 | return Error::from_string_literal("Index out of bounds"); |
147 | | |
148 | 0 | ICOImageDescriptor& desc = context.images[real_index]; |
149 | 0 | if (PNGImageDecoderPlugin::sniff({ context.data + desc.offset, desc.size })) { |
150 | 0 | auto png_decoder = TRY(PNGImageDecoderPlugin::create({ context.data + desc.offset, desc.size })); |
151 | 0 | auto decoded_png_frame = TRY(png_decoder->frame(0)); |
152 | 0 | if (!decoded_png_frame.image) { |
153 | 0 | dbgln_if(ICO_DEBUG, "load_ico_bitmap: failed to load PNG encoded image index: {}", real_index); |
154 | 0 | return Error::from_string_literal("Encoded image not null"); |
155 | 0 | } |
156 | 0 | desc.bitmap = decoded_png_frame.image; |
157 | 0 | return {}; |
158 | 0 | } else { |
159 | 0 | auto bmp_decoder = TRY(BMPImageDecoderPlugin::create_as_included_in_ico({}, { context.data + desc.offset, desc.size })); |
160 | | // NOTE: We don't initialize a BMP decoder in the usual way, but rather |
161 | | // we just create an object and try to sniff for a frame when it's included |
162 | | // inside an ICO image. |
163 | 0 | if (bmp_decoder->sniff_dib()) { |
164 | 0 | auto decoded_bmp_frame = TRY(bmp_decoder->frame(0)); |
165 | 0 | if (!decoded_bmp_frame.image) { |
166 | 0 | dbgln_if(ICO_DEBUG, "load_ico_bitmap: failed to load BMP encoded image index: {}", real_index); |
167 | 0 | return Error::from_string_literal("Encoded image not null"); |
168 | 0 | } |
169 | 0 | desc.bitmap = decoded_bmp_frame.image; |
170 | 0 | } else { |
171 | 0 | dbgln_if(ICO_DEBUG, "load_ico_bitmap: encoded image not supported at index: {}", real_index); |
172 | 0 | return Error::from_string_literal("Encoded image not supported"); |
173 | 0 | } |
174 | 0 | return {}; |
175 | 0 | } |
176 | 0 | } |
177 | | |
178 | | bool ICOImageDecoderPlugin::sniff(ReadonlyBytes data) |
179 | 0 | { |
180 | 0 | FixedMemoryStream stream { data }; |
181 | 0 | return !decode_ico_header(stream).is_error(); |
182 | 0 | } |
183 | | |
184 | | ErrorOr<NonnullOwnPtr<ImageDecoderPlugin>> ICOImageDecoderPlugin::create(ReadonlyBytes data) |
185 | 0 | { |
186 | 0 | auto plugin = TRY(adopt_nonnull_own_or_enomem(new (nothrow) ICOImageDecoderPlugin(data.data(), data.size()))); |
187 | 0 | TRY(load_ico_directory(*plugin->m_context)); |
188 | 0 | return plugin; |
189 | 0 | } |
190 | | |
191 | | ICOImageDecoderPlugin::ICOImageDecoderPlugin(u8 const* data, size_t size) |
192 | 0 | { |
193 | 0 | m_context = make<ICOLoadingContext>(); |
194 | 0 | m_context->data = data; |
195 | 0 | m_context->data_size = size; |
196 | 0 | } |
197 | | |
198 | 0 | ICOImageDecoderPlugin::~ICOImageDecoderPlugin() = default; |
199 | | |
200 | | IntSize ICOImageDecoderPlugin::size() |
201 | 0 | { |
202 | 0 | return { m_context->images[m_context->largest_index].width, m_context->images[m_context->largest_index].height }; |
203 | 0 | } |
204 | | |
205 | | ErrorOr<ImageFrameDescriptor> ICOImageDecoderPlugin::frame(size_t index, Optional<IntSize>) |
206 | 0 | { |
207 | 0 | if (index > 0) |
208 | 0 | return Error::from_string_literal("ICOImageDecoderPlugin: Invalid frame index"); |
209 | | |
210 | 0 | if (m_context->state == ICOLoadingContext::State::Error) |
211 | 0 | return Error::from_string_literal("ICOImageDecoderPlugin: Decoding failed"); |
212 | | |
213 | 0 | if (m_context->state < ICOLoadingContext::State::BitmapDecoded) { |
214 | | // NOTE: This forces the chunk decoding to happen. |
215 | 0 | auto maybe_error = load_ico_bitmap(*m_context); |
216 | 0 | if (maybe_error.is_error()) { |
217 | 0 | m_context->state = ICOLoadingContext::State::Error; |
218 | 0 | return Error::from_string_literal("ICOImageDecoderPlugin: Decoding failed"); |
219 | 0 | } |
220 | 0 | m_context->state = ICOLoadingContext::State::BitmapDecoded; |
221 | 0 | } |
222 | | |
223 | 0 | VERIFY(m_context->images[m_context->largest_index].bitmap); |
224 | 0 | return ImageFrameDescriptor { m_context->images[m_context->largest_index].bitmap, 0 }; |
225 | 0 | } |
226 | | |
227 | | } |