/src/serenity/Userland/Libraries/LibGfx/ImageFormats/PNGLoader.cpp
Line | Count | Source (jump to first uncovered line) |
1 | | /* |
2 | | * Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org> |
3 | | * Copyright (c) 2022, the SerenityOS developers. |
4 | | * |
5 | | * SPDX-License-Identifier: BSD-2-Clause |
6 | | */ |
7 | | |
8 | | #include <AK/Debug.h> |
9 | | #include <AK/Endian.h> |
10 | | #include <AK/MemoryStream.h> |
11 | | #include <AK/Vector.h> |
12 | | #include <LibCompress/Zlib.h> |
13 | | #include <LibGfx/ImageFormats/PNGLoader.h> |
14 | | #include <LibGfx/ImageFormats/TIFFLoader.h> |
15 | | #include <LibGfx/ImageFormats/TIFFMetadata.h> |
16 | | #include <LibGfx/Painter.h> |
17 | | |
18 | | namespace Gfx { |
19 | | |
20 | | struct PNG_IHDR { |
21 | | NetworkOrdered<u32> width; |
22 | | NetworkOrdered<u32> height; |
23 | | u8 bit_depth { 0 }; |
24 | | PNG::ColorType color_type { 0 }; |
25 | | u8 compression_method { 0 }; |
26 | | u8 filter_method { 0 }; |
27 | | u8 interlace_method { 0 }; |
28 | | }; |
29 | | |
30 | | static_assert(AssertSize<PNG_IHDR, 13>()); |
31 | | |
32 | | struct acTL_Chunk { |
33 | | NetworkOrdered<u32> num_frames; |
34 | | NetworkOrdered<u32> num_plays; |
35 | | }; |
36 | | static_assert(AssertSize<acTL_Chunk, 8>()); |
37 | | |
38 | | struct fcTL_Chunk { |
39 | | enum class DisposeOp : u8 { |
40 | | APNG_DISPOSE_OP_NONE = 0, |
41 | | APNG_DISPOSE_OP_BACKGROUND, |
42 | | APNG_DISPOSE_OP_PREVIOUS |
43 | | }; |
44 | | enum class BlendOp : u8 { |
45 | | APNG_BLEND_OP_SOURCE = 0, |
46 | | APNG_BLEND_OP_OVER |
47 | | }; |
48 | | NetworkOrdered<u32> sequence_number; |
49 | | NetworkOrdered<u32> width; |
50 | | NetworkOrdered<u32> height; |
51 | | NetworkOrdered<u32> x_offset; |
52 | | NetworkOrdered<u32> y_offset; |
53 | | NetworkOrdered<u16> delay_num; |
54 | | NetworkOrdered<u16> delay_den; |
55 | | DisposeOp dispose_op { DisposeOp::APNG_DISPOSE_OP_NONE }; |
56 | | BlendOp blend_op { BlendOp::APNG_BLEND_OP_SOURCE }; |
57 | | }; |
58 | | static_assert(AssertSize<fcTL_Chunk, 26>()); |
59 | | |
60 | | struct ChromaticitiesAndWhitepoint { |
61 | | NetworkOrdered<u32> white_point_x; |
62 | | NetworkOrdered<u32> white_point_y; |
63 | | NetworkOrdered<u32> red_x; |
64 | | NetworkOrdered<u32> red_y; |
65 | | NetworkOrdered<u32> green_x; |
66 | | NetworkOrdered<u32> green_y; |
67 | | NetworkOrdered<u32> blue_x; |
68 | | NetworkOrdered<u32> blue_y; |
69 | | }; |
70 | | static_assert(AssertSize<ChromaticitiesAndWhitepoint, 32>()); |
71 | | |
72 | | struct CodingIndependentCodePoints { |
73 | | u8 color_primaries; |
74 | | u8 transfer_function; |
75 | | u8 matrix_coefficients; |
76 | | u8 video_full_range_flag; |
77 | | }; |
78 | | static_assert(AssertSize<CodingIndependentCodePoints, 4>()); |
79 | | |
80 | | struct EmbeddedICCProfile { |
81 | | StringView profile_name; |
82 | | ReadonlyBytes compressed_data; |
83 | | }; |
84 | | |
85 | | struct Scanline { |
86 | | PNG::FilterType filter; |
87 | | ReadonlyBytes data {}; |
88 | | }; |
89 | | |
90 | | struct [[gnu::packed]] PaletteEntry { |
91 | | u8 r; |
92 | | u8 g; |
93 | | u8 b; |
94 | | // u8 a; |
95 | | }; |
96 | | |
97 | | template<typename T> |
98 | | struct [[gnu::packed]] Tuple { |
99 | | T gray; |
100 | | T a; |
101 | | }; |
102 | | |
103 | | template<typename T> |
104 | | struct [[gnu::packed]] Triplet { |
105 | | T r; |
106 | | T g; |
107 | | T b; |
108 | | |
109 | 527k | bool operator==(Triplet const& other) const = default; Gfx::Triplet<unsigned char>::operator==(Gfx::Triplet<unsigned char> const&) const Line | Count | Source | 109 | 111k | bool operator==(Triplet const& other) const = default; |
Gfx::Triplet<unsigned short>::operator==(Gfx::Triplet<unsigned short> const&) const Line | Count | Source | 109 | 415k | bool operator==(Triplet const& other) const = default; |
|
110 | | }; |
111 | | |
112 | | template<typename T> |
113 | | struct [[gnu::packed]] Quartet { |
114 | | T r; |
115 | | T g; |
116 | | T b; |
117 | | T a; |
118 | | }; |
119 | | |
120 | | enum PngInterlaceMethod { |
121 | | Null = 0, |
122 | | Adam7 = 1 |
123 | | }; |
124 | | |
125 | | enum RenderingIntent { |
126 | | Perceptual = 0, |
127 | | RelativeColorimetric = 1, |
128 | | Saturation = 2, |
129 | | AbsoluteColorimetric = 3, |
130 | | }; |
131 | | |
132 | | struct AnimationFrame { |
133 | | fcTL_Chunk const& fcTL; |
134 | | RefPtr<Bitmap> bitmap; |
135 | | ByteBuffer compressed_data; |
136 | | |
137 | | AnimationFrame(fcTL_Chunk const& fcTL) |
138 | 395 | : fcTL(fcTL) |
139 | 395 | { |
140 | 395 | } |
141 | | |
142 | | u32 duration_ms() const |
143 | 18 | { |
144 | 18 | u32 num = fcTL.delay_num; |
145 | 18 | if (num == 0) |
146 | 1 | return 1; |
147 | 17 | u32 denom = fcTL.delay_den != 0 ? static_cast<u32>(fcTL.delay_den) : 100u; |
148 | 17 | return (num * 1000) / denom; |
149 | 18 | } |
150 | | |
151 | | IntRect rect() const |
152 | 0 | { |
153 | 0 | return { fcTL.x_offset, fcTL.y_offset, fcTL.width, fcTL.height }; |
154 | 0 | } |
155 | | }; |
156 | | |
157 | | struct PNGLoadingContext { |
158 | | enum State { |
159 | | NotDecoded = 0, |
160 | | Error, |
161 | | IHDRDecoded, |
162 | | ImageDataChunkDecoded, |
163 | | ChunksDecoded, |
164 | | BitmapDecoded, |
165 | | }; |
166 | | State state { State::NotDecoded }; |
167 | | u8 const* data { nullptr }; |
168 | | u8 const* data_current_ptr { nullptr }; |
169 | | size_t data_size { 0 }; |
170 | | i32 width { -1 }; |
171 | | i32 height { -1 }; |
172 | | u8 bit_depth { 0 }; |
173 | | PNG::ColorType color_type { 0 }; |
174 | | u8 compression_method { 0 }; |
175 | | u8 filter_method { 0 }; |
176 | | u8 interlace_method { 0 }; |
177 | | u8 channels { 0 }; |
178 | | u32 animation_next_expected_seq { 0 }; |
179 | | u32 animation_next_frame_to_render { 0 }; |
180 | | u32 animation_frame_count { 0 }; |
181 | | u32 animation_loop_count { 0 }; |
182 | | Optional<u32> last_completed_animation_frame_index; |
183 | | bool is_first_idat_part_of_animation { false }; |
184 | | bool has_seen_iend { false }; |
185 | | bool has_seen_idat_chunk { false }; |
186 | | bool has_seen_actl_chunk_before_idat { false }; |
187 | 1.37k | bool has_alpha() const { return to_underlying(color_type) & 4 || palette_transparency_data.size() > 0; } |
188 | | Vector<Scanline> scanlines; |
189 | | ByteBuffer unfiltered_data; |
190 | | RefPtr<Gfx::Bitmap> bitmap; |
191 | | ByteBuffer compressed_data; |
192 | | Vector<PaletteEntry> palette_data; |
193 | | ByteBuffer palette_transparency_data; |
194 | | Vector<AnimationFrame> animation_frames; |
195 | | |
196 | | Optional<ChromaticitiesAndWhitepoint> chromaticities_and_whitepoint; |
197 | | Optional<CodingIndependentCodePoints> coding_independent_code_points; |
198 | | Optional<u32> gamma; |
199 | | Optional<EmbeddedICCProfile> embedded_icc_profile; |
200 | | Optional<ByteBuffer> decompressed_icc_profile; |
201 | | Optional<RenderingIntent> sRGB_rendering_intent; |
202 | | |
203 | | OwnPtr<ExifMetadata> exif_metadata; |
204 | | |
205 | | Checked<int> compute_row_size_for_width(int width) |
206 | 5.16M | { |
207 | 5.16M | Checked<int> row_size = width; |
208 | 5.16M | row_size *= channels; |
209 | 5.16M | row_size *= bit_depth; |
210 | 5.16M | row_size += 7; |
211 | 5.16M | row_size /= 8; |
212 | 5.16M | if (row_size.has_overflow()) { |
213 | 6 | dbgln("PNG too large, integer overflow while computing row size"); |
214 | 6 | state = State::Error; |
215 | 6 | } |
216 | 5.16M | return row_size; |
217 | 5.16M | } |
218 | | |
219 | | PNGLoadingContext create_subimage_context(int width, int height) |
220 | 4.36k | { |
221 | 4.36k | PNGLoadingContext subimage_context; |
222 | 4.36k | subimage_context.state = State::ChunksDecoded; |
223 | 4.36k | subimage_context.width = width; |
224 | 4.36k | subimage_context.height = height; |
225 | 4.36k | subimage_context.channels = channels; |
226 | 4.36k | subimage_context.color_type = color_type; |
227 | 4.36k | subimage_context.palette_data = palette_data; |
228 | 4.36k | subimage_context.palette_transparency_data = palette_transparency_data; |
229 | 4.36k | subimage_context.bit_depth = bit_depth; |
230 | 4.36k | subimage_context.filter_method = filter_method; |
231 | 4.36k | return subimage_context; |
232 | 4.36k | } |
233 | | }; |
234 | | |
235 | | class Streamer { |
236 | | public: |
237 | | Streamer(u8 const* data, size_t size) |
238 | 23.2k | : m_data_ptr(data) |
239 | 23.2k | , m_size_remaining(size) |
240 | 23.2k | { |
241 | 23.2k | } |
242 | | |
243 | | template<typename T> |
244 | | bool read(T& value) |
245 | 5.48M | { |
246 | 5.48M | if (m_size_remaining < sizeof(T)) |
247 | 640 | return false; |
248 | 5.48M | value = *((NetworkOrdered<T> const*)m_data_ptr); |
249 | 5.48M | m_data_ptr += sizeof(T); |
250 | 5.48M | m_size_remaining -= sizeof(T); |
251 | 5.48M | return true; |
252 | 5.48M | } bool Gfx::Streamer::read<unsigned int>(unsigned int&) Line | Count | Source | 245 | 318k | { | 246 | 318k | if (m_size_remaining < sizeof(T)) | 247 | 501 | return false; | 248 | 318k | value = *((NetworkOrdered<T> const*)m_data_ptr); | 249 | 318k | m_data_ptr += sizeof(T); | 250 | 318k | m_size_remaining -= sizeof(T); | 251 | 318k | return true; | 252 | 318k | } |
bool Gfx::Streamer::read<unsigned char>(unsigned char&) Line | Count | Source | 245 | 5.16M | { | 246 | 5.16M | if (m_size_remaining < sizeof(T)) | 247 | 139 | return false; | 248 | 5.16M | value = *((NetworkOrdered<T> const*)m_data_ptr); | 249 | 5.16M | m_data_ptr += sizeof(T); | 250 | 5.16M | m_size_remaining -= sizeof(T); | 251 | 5.16M | return true; | 252 | 5.16M | } |
|
253 | | |
254 | | bool read_bytes(u8* buffer, size_t count) |
255 | 159k | { |
256 | 159k | if (m_size_remaining < count) |
257 | 148 | return false; |
258 | 159k | memcpy(buffer, m_data_ptr, count); |
259 | 159k | m_data_ptr += count; |
260 | 159k | m_size_remaining -= count; |
261 | 159k | return true; |
262 | 159k | } |
263 | | |
264 | | bool wrap_bytes(ReadonlyBytes& buffer, size_t count) |
265 | 5.32M | { |
266 | 5.32M | if (m_size_remaining < count) |
267 | 1.46k | return false; |
268 | 5.32M | buffer = ReadonlyBytes { m_data_ptr, count }; |
269 | 5.32M | m_data_ptr += count; |
270 | 5.32M | m_size_remaining -= count; |
271 | 5.32M | return true; |
272 | 5.32M | } |
273 | | |
274 | 153k | u8 const* current_data_ptr() const { return m_data_ptr; } |
275 | 153k | bool at_end() const { return !m_size_remaining; } |
276 | | |
277 | | private: |
278 | | u8 const* m_data_ptr { nullptr }; |
279 | | size_t m_size_remaining { 0 }; |
280 | | }; |
281 | | |
282 | | static ErrorOr<void> process_chunk(Streamer&, PNGLoadingContext& context); |
283 | | |
284 | | union [[gnu::packed]] Pixel { |
285 | | ARGB32 rgba { 0 }; |
286 | | u8 v[4]; |
287 | | struct { |
288 | | u8 r; |
289 | | u8 g; |
290 | | u8 b; |
291 | | u8 a; |
292 | | }; |
293 | | }; |
294 | | static_assert(AssertSize<Pixel, 4>()); |
295 | | |
296 | | void PNGImageDecoderPlugin::unfilter_scanline(PNG::FilterType filter, Bytes scanline_data, ReadonlyBytes previous_scanlines_data, u8 bytes_per_complete_pixel) |
297 | 829k | { |
298 | | // https://www.w3.org/TR/png-3/#9Filter-types |
299 | | // "Filters are applied to bytes, not to pixels, regardless of the bit depth or colour type of the image." |
300 | 829k | switch (filter) { |
301 | 0 | case PNG::FilterType::None: |
302 | 0 | break; |
303 | 280k | case PNG::FilterType::Sub: |
304 | | // This loop starts at bytes_per_complete_pixel because all bytes before that are |
305 | | // guaranteed to have no valid byte at index (i - bytes_per_complete pixel). |
306 | | // All such invalid byte indexes should be treated as 0, and adding 0 to the current |
307 | | // byte would do nothing, so the first bytes_per_complete_pixel bytes can instead |
308 | | // just be skipped. |
309 | 5.09M | for (size_t i = bytes_per_complete_pixel; i < scanline_data.size(); ++i) { |
310 | 4.81M | u8 left = scanline_data[i - bytes_per_complete_pixel]; |
311 | 4.81M | scanline_data[i] += left; |
312 | 4.81M | } |
313 | 280k | break; |
314 | 321k | case PNG::FilterType::Up: |
315 | 12.8M | for (size_t i = 0; i < scanline_data.size(); ++i) { |
316 | 12.5M | u8 above = previous_scanlines_data[i]; |
317 | 12.5M | scanline_data[i] += above; |
318 | 12.5M | } |
319 | 321k | break; |
320 | 30.9k | case PNG::FilterType::Average: |
321 | 1.09M | for (size_t i = 0; i < scanline_data.size(); ++i) { |
322 | 1.06M | u32 left = (i < bytes_per_complete_pixel) ? 0 : scanline_data[i - bytes_per_complete_pixel]; |
323 | 1.06M | u32 above = previous_scanlines_data[i]; |
324 | 1.06M | u8 average = (left + above) / 2; |
325 | 1.06M | scanline_data[i] += average; |
326 | 1.06M | } |
327 | 30.9k | break; |
328 | 195k | case PNG::FilterType::Paeth: |
329 | 3.98M | for (size_t i = 0; i < scanline_data.size(); ++i) { |
330 | 3.79M | u8 left = (i < bytes_per_complete_pixel) ? 0 : scanline_data[i - bytes_per_complete_pixel]; |
331 | 3.79M | u8 above = previous_scanlines_data[i]; |
332 | 3.79M | u8 upper_left = (i < bytes_per_complete_pixel) ? 0 : previous_scanlines_data[i - bytes_per_complete_pixel]; |
333 | 3.79M | scanline_data[i] += PNG::paeth_predictor(left, above, upper_left); |
334 | 3.79M | } |
335 | 195k | break; |
336 | 829k | } |
337 | 829k | } |
338 | | |
339 | | template<typename T> |
340 | | ALWAYS_INLINE static void unpack_grayscale_without_alpha(PNGLoadingContext& context) |
341 | 459 | { |
342 | 385k | for (int y = 0; y < context.height; ++y) { |
343 | 384k | auto* gray_values = reinterpret_cast<T const*>(context.scanlines[y].data.data()); |
344 | 19.8M | for (int i = 0; i < context.width; ++i) { |
345 | 19.4M | auto& pixel = (Pixel&)context.bitmap->scanline(y)[i]; |
346 | 19.4M | pixel.r = gray_values[i]; |
347 | 19.4M | pixel.g = gray_values[i]; |
348 | 19.4M | pixel.b = gray_values[i]; |
349 | 19.4M | pixel.a = 0xff; |
350 | 19.4M | } |
351 | 384k | } |
352 | 459 | } PNGLoader.cpp:void Gfx::unpack_grayscale_without_alpha<unsigned char>(Gfx::PNGLoadingContext&) Line | Count | Source | 341 | 236 | { | 342 | 280k | for (int y = 0; y < context.height; ++y) { | 343 | 280k | auto* gray_values = reinterpret_cast<T const*>(context.scanlines[y].data.data()); | 344 | 19.4M | for (int i = 0; i < context.width; ++i) { | 345 | 19.1M | auto& pixel = (Pixel&)context.bitmap->scanline(y)[i]; | 346 | 19.1M | pixel.r = gray_values[i]; | 347 | 19.1M | pixel.g = gray_values[i]; | 348 | 19.1M | pixel.b = gray_values[i]; | 349 | 19.1M | pixel.a = 0xff; | 350 | 19.1M | } | 351 | 280k | } | 352 | 236 | } |
PNGLoader.cpp:void Gfx::unpack_grayscale_without_alpha<unsigned short>(Gfx::PNGLoadingContext&) Line | Count | Source | 341 | 223 | { | 342 | 104k | for (int y = 0; y < context.height; ++y) { | 343 | 104k | auto* gray_values = reinterpret_cast<T const*>(context.scanlines[y].data.data()); | 344 | 411k | for (int i = 0; i < context.width; ++i) { | 345 | 306k | auto& pixel = (Pixel&)context.bitmap->scanline(y)[i]; | 346 | 306k | pixel.r = gray_values[i]; | 347 | 306k | pixel.g = gray_values[i]; | 348 | 306k | pixel.b = gray_values[i]; | 349 | 306k | pixel.a = 0xff; | 350 | 306k | } | 351 | 104k | } | 352 | 223 | } |
|
353 | | |
354 | | template<typename T> |
355 | | ALWAYS_INLINE static void unpack_grayscale_with_alpha(PNGLoadingContext& context) |
356 | 495 | { |
357 | 618k | for (int y = 0; y < context.height; ++y) { |
358 | 618k | auto* tuples = reinterpret_cast<Tuple<T> const*>(context.scanlines[y].data.data()); |
359 | 4.56M | for (int i = 0; i < context.width; ++i) { |
360 | 3.95M | auto& pixel = (Pixel&)context.bitmap->scanline(y)[i]; |
361 | 3.95M | pixel.r = tuples[i].gray; |
362 | 3.95M | pixel.g = tuples[i].gray; |
363 | 3.95M | pixel.b = tuples[i].gray; |
364 | 3.95M | pixel.a = tuples[i].a; |
365 | 3.95M | } |
366 | 618k | } |
367 | 495 | } PNGLoader.cpp:void Gfx::unpack_grayscale_with_alpha<unsigned char>(Gfx::PNGLoadingContext&) Line | Count | Source | 356 | 367 | { | 357 | 521k | for (int y = 0; y < context.height; ++y) { | 358 | 520k | auto* tuples = reinterpret_cast<Tuple<T> const*>(context.scanlines[y].data.data()); | 359 | 4.25M | for (int i = 0; i < context.width; ++i) { | 360 | 3.73M | auto& pixel = (Pixel&)context.bitmap->scanline(y)[i]; | 361 | 3.73M | pixel.r = tuples[i].gray; | 362 | 3.73M | pixel.g = tuples[i].gray; | 363 | 3.73M | pixel.b = tuples[i].gray; | 364 | 3.73M | pixel.a = tuples[i].a; | 365 | 3.73M | } | 366 | 520k | } | 367 | 367 | } |
PNGLoader.cpp:void Gfx::unpack_grayscale_with_alpha<unsigned short>(Gfx::PNGLoadingContext&) Line | Count | Source | 356 | 128 | { | 357 | 97.4k | for (int y = 0; y < context.height; ++y) { | 358 | 97.3k | auto* tuples = reinterpret_cast<Tuple<T> const*>(context.scanlines[y].data.data()); | 359 | 310k | for (int i = 0; i < context.width; ++i) { | 360 | 213k | auto& pixel = (Pixel&)context.bitmap->scanline(y)[i]; | 361 | 213k | pixel.r = tuples[i].gray; | 362 | 213k | pixel.g = tuples[i].gray; | 363 | 213k | pixel.b = tuples[i].gray; | 364 | 213k | pixel.a = tuples[i].a; | 365 | 213k | } | 366 | 97.3k | } | 367 | 128 | } |
|
368 | | |
369 | | template<typename T> |
370 | | ALWAYS_INLINE static void unpack_triplets_without_alpha(PNGLoadingContext& context) |
371 | 302 | { |
372 | 112k | for (int y = 0; y < context.height; ++y) { |
373 | 111k | auto* triplets = reinterpret_cast<Triplet<T> const*>(context.scanlines[y].data.data()); |
374 | 1.22M | for (int i = 0; i < context.width; ++i) { |
375 | 1.10M | auto& pixel = (Pixel&)context.bitmap->scanline(y)[i]; |
376 | 1.10M | pixel.r = triplets[i].r; |
377 | 1.10M | pixel.g = triplets[i].g; |
378 | 1.10M | pixel.b = triplets[i].b; |
379 | 1.10M | pixel.a = 0xff; |
380 | 1.10M | } |
381 | 111k | } |
382 | 302 | } PNGLoader.cpp:void Gfx::unpack_triplets_without_alpha<unsigned char>(Gfx::PNGLoadingContext&) Line | Count | Source | 371 | 152 | { | 372 | 66.2k | for (int y = 0; y < context.height; ++y) { | 373 | 66.0k | auto* triplets = reinterpret_cast<Triplet<T> const*>(context.scanlines[y].data.data()); | 374 | 781k | for (int i = 0; i < context.width; ++i) { | 375 | 715k | auto& pixel = (Pixel&)context.bitmap->scanline(y)[i]; | 376 | 715k | pixel.r = triplets[i].r; | 377 | 715k | pixel.g = triplets[i].g; | 378 | 715k | pixel.b = triplets[i].b; | 379 | 715k | pixel.a = 0xff; | 380 | 715k | } | 381 | 66.0k | } | 382 | 152 | } |
PNGLoader.cpp:void Gfx::unpack_triplets_without_alpha<unsigned short>(Gfx::PNGLoadingContext&) Line | Count | Source | 371 | 150 | { | 372 | 45.8k | for (int y = 0; y < context.height; ++y) { | 373 | 45.7k | auto* triplets = reinterpret_cast<Triplet<T> const*>(context.scanlines[y].data.data()); | 374 | 438k | for (int i = 0; i < context.width; ++i) { | 375 | 393k | auto& pixel = (Pixel&)context.bitmap->scanline(y)[i]; | 376 | 393k | pixel.r = triplets[i].r; | 377 | 393k | pixel.g = triplets[i].g; | 378 | 393k | pixel.b = triplets[i].b; | 379 | 393k | pixel.a = 0xff; | 380 | 393k | } | 381 | 45.7k | } | 382 | 150 | } |
|
383 | | |
384 | | template<typename T> |
385 | | ALWAYS_INLINE static void unpack_triplets_with_transparency_value(PNGLoadingContext& context, Triplet<T> transparency_value) |
386 | 407 | { |
387 | 126k | for (int y = 0; y < context.height; ++y) { |
388 | 126k | auto* triplets = reinterpret_cast<Triplet<T> const*>(context.scanlines[y].data.data()); |
389 | 595k | for (int i = 0; i < context.width; ++i) { |
390 | 469k | auto& pixel = (Pixel&)context.bitmap->scanline(y)[i]; |
391 | 469k | pixel.r = triplets[i].r; |
392 | 469k | pixel.g = triplets[i].g; |
393 | 469k | pixel.b = triplets[i].b; |
394 | 469k | if (triplets[i] == transparency_value) |
395 | 10.5k | pixel.a = 0x00; |
396 | 459k | else |
397 | 459k | pixel.a = 0xff; |
398 | 469k | } |
399 | 126k | } |
400 | 407 | } PNGLoader.cpp:void Gfx::unpack_triplets_with_transparency_value<unsigned char>(Gfx::PNGLoadingContext&, Gfx::Triplet<unsigned char>) Line | Count | Source | 386 | 196 | { | 387 | 74.2k | for (int y = 0; y < context.height; ++y) { | 388 | 74.0k | auto* triplets = reinterpret_cast<Triplet<T> const*>(context.scanlines[y].data.data()); | 389 | 156k | for (int i = 0; i < context.width; ++i) { | 390 | 82.8k | auto& pixel = (Pixel&)context.bitmap->scanline(y)[i]; | 391 | 82.8k | pixel.r = triplets[i].r; | 392 | 82.8k | pixel.g = triplets[i].g; | 393 | 82.8k | pixel.b = triplets[i].b; | 394 | 82.8k | if (triplets[i] == transparency_value) | 395 | 2.35k | pixel.a = 0x00; | 396 | 80.4k | else | 397 | 80.4k | pixel.a = 0xff; | 398 | 82.8k | } | 399 | 74.0k | } | 400 | 196 | } |
PNGLoader.cpp:void Gfx::unpack_triplets_with_transparency_value<unsigned short>(Gfx::PNGLoadingContext&, Gfx::Triplet<unsigned short>) Line | Count | Source | 386 | 211 | { | 387 | 52.3k | for (int y = 0; y < context.height; ++y) { | 388 | 52.0k | auto* triplets = reinterpret_cast<Triplet<T> const*>(context.scanlines[y].data.data()); | 389 | 438k | for (int i = 0; i < context.width; ++i) { | 390 | 386k | auto& pixel = (Pixel&)context.bitmap->scanline(y)[i]; | 391 | 386k | pixel.r = triplets[i].r; | 392 | 386k | pixel.g = triplets[i].g; | 393 | 386k | pixel.b = triplets[i].b; | 394 | 386k | if (triplets[i] == transparency_value) | 395 | 8.15k | pixel.a = 0x00; | 396 | 378k | else | 397 | 378k | pixel.a = 0xff; | 398 | 386k | } | 399 | 52.0k | } | 400 | 211 | } |
|
401 | | |
402 | | NEVER_INLINE FLATTEN static ErrorOr<void> unfilter(PNGLoadingContext& context) |
403 | 3.03k | { |
404 | | // First unfilter the scanlines: |
405 | | |
406 | | // FIXME: Instead of creating a separate buffer for the scanlines that need to be |
407 | | // mutated, the mutation could be done in place (if the data was non-const). |
408 | 3.03k | size_t bytes_per_scanline = context.scanlines[0].data.size(); |
409 | 3.03k | size_t bytes_needed_for_all_unfiltered_scanlines = 0; |
410 | 1.87M | for (int y = 0; y < context.height; ++y) { |
411 | 1.87M | if (context.scanlines[y].filter != PNG::FilterType::None) { |
412 | 829k | bytes_needed_for_all_unfiltered_scanlines += bytes_per_scanline; |
413 | 829k | } |
414 | 1.87M | } |
415 | 3.03k | context.unfiltered_data = TRY(ByteBuffer::create_uninitialized(bytes_needed_for_all_unfiltered_scanlines)); |
416 | | |
417 | | // From section 6.3 of http://www.libpng.org/pub/png/spec/1.2/PNG-Filters.html |
418 | | // "bpp is defined as the number of bytes per complete pixel, rounding up to one. |
419 | | // For example, for color type 2 with a bit depth of 16, bpp is equal to 6 |
420 | | // (three samples, two bytes per sample); for color type 0 with a bit depth of 2, |
421 | | // bpp is equal to 1 (rounding up); for color type 4 with a bit depth of 16, bpp |
422 | | // is equal to 4 (two-byte grayscale sample, plus two-byte alpha sample)." |
423 | 0 | u8 bytes_per_complete_pixel = ceil_div(context.bit_depth, (u8)8) * context.channels; |
424 | | |
425 | 3.03k | u8 dummy_scanline_bytes[bytes_per_scanline]; |
426 | 3.03k | memset(dummy_scanline_bytes, 0, sizeof(dummy_scanline_bytes)); |
427 | 3.03k | auto previous_scanlines_data = ReadonlyBytes { dummy_scanline_bytes, sizeof(dummy_scanline_bytes) }; |
428 | | |
429 | 1.87M | for (int y = 0, data_start = 0; y < context.height; ++y) { |
430 | 1.87M | if (context.scanlines[y].filter != PNG::FilterType::None) { |
431 | 829k | auto scanline_data_slice = context.unfiltered_data.bytes().slice(data_start, bytes_per_scanline); |
432 | | |
433 | | // Copy the current values over and set the scanline's data to the to-be-mutated slice |
434 | 829k | context.scanlines[y].data.copy_to(scanline_data_slice); |
435 | 829k | context.scanlines[y].data = scanline_data_slice; |
436 | | |
437 | 829k | PNGImageDecoderPlugin::unfilter_scanline(context.scanlines[y].filter, scanline_data_slice, previous_scanlines_data, bytes_per_complete_pixel); |
438 | | |
439 | 829k | data_start += bytes_per_scanline; |
440 | 829k | } |
441 | 1.87M | previous_scanlines_data = context.scanlines[y].data; |
442 | 1.87M | } |
443 | | |
444 | | // Now unpack the scanlines to RGBA: |
445 | 3.03k | switch (context.color_type) { |
446 | 856 | case PNG::ColorType::Greyscale: |
447 | 856 | if (context.bit_depth == 8) { |
448 | 236 | unpack_grayscale_without_alpha<u8>(context); |
449 | 620 | } else if (context.bit_depth == 16) { |
450 | 223 | unpack_grayscale_without_alpha<u16>(context); |
451 | 397 | } else if (context.bit_depth == 1 || context.bit_depth == 2 || context.bit_depth == 4) { |
452 | 397 | auto bit_depth_squared = context.bit_depth * context.bit_depth; |
453 | 397 | auto pixels_per_byte = 8 / context.bit_depth; |
454 | 397 | auto mask = (1 << context.bit_depth) - 1; |
455 | 203k | for (int y = 0; y < context.height; ++y) { |
456 | 203k | auto* gray_values = context.scanlines[y].data.data(); |
457 | 4.53M | for (int x = 0; x < context.width; ++x) { |
458 | 4.33M | auto bit_offset = (8 - context.bit_depth) - (context.bit_depth * (x % pixels_per_byte)); |
459 | 4.33M | auto value = (gray_values[x / pixels_per_byte] >> bit_offset) & mask; |
460 | 4.33M | auto& pixel = (Pixel&)context.bitmap->scanline(y)[x]; |
461 | 4.33M | pixel.r = value * (0xff / bit_depth_squared); |
462 | 4.33M | pixel.g = value * (0xff / bit_depth_squared); |
463 | 4.33M | pixel.b = value * (0xff / bit_depth_squared); |
464 | 4.33M | pixel.a = 0xff; |
465 | 4.33M | } |
466 | 203k | } |
467 | 397 | } else { |
468 | 0 | VERIFY_NOT_REACHED(); |
469 | 0 | } |
470 | 856 | break; |
471 | 856 | case PNG::ColorType::GreyscaleWithAlpha: |
472 | 495 | if (context.bit_depth == 8) { |
473 | 367 | unpack_grayscale_with_alpha<u8>(context); |
474 | 367 | } else if (context.bit_depth == 16) { |
475 | 128 | unpack_grayscale_with_alpha<u16>(context); |
476 | 128 | } else { |
477 | 0 | VERIFY_NOT_REACHED(); |
478 | 0 | } |
479 | 495 | break; |
480 | 709 | case PNG::ColorType::Truecolor: |
481 | 709 | if (context.palette_transparency_data.size() == 6) { |
482 | 407 | if (context.bit_depth == 8) { |
483 | 196 | unpack_triplets_with_transparency_value<u8>(context, Triplet<u8> { context.palette_transparency_data[0], context.palette_transparency_data[2], context.palette_transparency_data[4] }); |
484 | 211 | } else if (context.bit_depth == 16) { |
485 | 211 | u16 tr = context.palette_transparency_data[0] | context.palette_transparency_data[1] << 8; |
486 | 211 | u16 tg = context.palette_transparency_data[2] | context.palette_transparency_data[3] << 8; |
487 | 211 | u16 tb = context.palette_transparency_data[4] | context.palette_transparency_data[5] << 8; |
488 | 211 | unpack_triplets_with_transparency_value<u16>(context, Triplet<u16> { tr, tg, tb }); |
489 | 211 | } else { |
490 | 0 | VERIFY_NOT_REACHED(); |
491 | 0 | } |
492 | 407 | } else { |
493 | 302 | if (context.bit_depth == 8) |
494 | 152 | unpack_triplets_without_alpha<u8>(context); |
495 | 150 | else if (context.bit_depth == 16) |
496 | 150 | unpack_triplets_without_alpha<u16>(context); |
497 | 0 | else |
498 | 0 | VERIFY_NOT_REACHED(); |
499 | 302 | } |
500 | 709 | break; |
501 | 709 | case PNG::ColorType::TruecolorWithAlpha: |
502 | 214 | if (context.bit_depth == 8) { |
503 | 94.9k | for (int y = 0; y < context.height; ++y) { |
504 | 94.8k | memcpy(context.bitmap->scanline(y), context.scanlines[y].data.data(), context.scanlines[y].data.size()); |
505 | 94.8k | } |
506 | 118 | } else if (context.bit_depth == 16) { |
507 | 66.2k | for (int y = 0; y < context.height; ++y) { |
508 | 66.1k | auto* quartets = reinterpret_cast<Quartet<u16> const*>(context.scanlines[y].data.data()); |
509 | 433k | for (int i = 0; i < context.width; ++i) { |
510 | 367k | auto& pixel = (Pixel&)context.bitmap->scanline(y)[i]; |
511 | 367k | pixel.r = quartets[i].r & 0xFF; |
512 | 367k | pixel.g = quartets[i].g & 0xFF; |
513 | 367k | pixel.b = quartets[i].b & 0xFF; |
514 | 367k | pixel.a = quartets[i].a & 0xFF; |
515 | 367k | } |
516 | 66.1k | } |
517 | 118 | } else { |
518 | 0 | VERIFY_NOT_REACHED(); |
519 | 0 | } |
520 | 214 | break; |
521 | 760 | case PNG::ColorType::IndexedColor: |
522 | 760 | if (context.bit_depth == 8) { |
523 | 178k | for (int y = 0; y < context.height; ++y) { |
524 | 178k | auto* palette_index = context.scanlines[y].data.data(); |
525 | 567k | for (int i = 0; i < context.width; ++i) { |
526 | 388k | auto& pixel = (Pixel&)context.bitmap->scanline(y)[i]; |
527 | 388k | if (palette_index[i] >= context.palette_data.size()) |
528 | 21 | return Error::from_string_literal("PNGImageDecoderPlugin: Palette index out of range"); |
529 | 388k | auto& color = context.palette_data.at((int)palette_index[i]); |
530 | 388k | auto transparency = context.palette_transparency_data.size() >= palette_index[i] + 1u |
531 | 388k | ? context.palette_transparency_data[palette_index[i]] |
532 | 388k | : 0xff; |
533 | 388k | pixel.r = color.r; |
534 | 388k | pixel.g = color.g; |
535 | 388k | pixel.b = color.b; |
536 | 388k | pixel.a = transparency; |
537 | 388k | } |
538 | 178k | } |
539 | 431 | } else if (context.bit_depth == 1 || context.bit_depth == 2 || context.bit_depth == 4) { |
540 | 431 | auto pixels_per_byte = 8 / context.bit_depth; |
541 | 431 | auto mask = (1 << context.bit_depth) - 1; |
542 | 88.0k | for (int y = 0; y < context.height; ++y) { |
543 | 87.6k | auto* palette_indices = context.scanlines[y].data.data(); |
544 | 401k | for (int i = 0; i < context.width; ++i) { |
545 | 314k | auto bit_offset = (8 - context.bit_depth) - (context.bit_depth * (i % pixels_per_byte)); |
546 | 314k | auto palette_index = (palette_indices[i / pixels_per_byte] >> bit_offset) & mask; |
547 | 314k | auto& pixel = (Pixel&)context.bitmap->scanline(y)[i]; |
548 | 314k | if ((size_t)palette_index >= context.palette_data.size()) |
549 | 32 | return Error::from_string_literal("PNGImageDecoderPlugin: Palette index out of range"); |
550 | 314k | auto& color = context.palette_data.at(palette_index); |
551 | 314k | auto transparency = context.palette_transparency_data.size() >= palette_index + 1u |
552 | 314k | ? context.palette_transparency_data[palette_index] |
553 | 314k | : 0xff; |
554 | 314k | pixel.r = color.r; |
555 | 314k | pixel.g = color.g; |
556 | 314k | pixel.b = color.b; |
557 | 314k | pixel.a = transparency; |
558 | 314k | } |
559 | 87.6k | } |
560 | 431 | } else { |
561 | 0 | VERIFY_NOT_REACHED(); |
562 | 0 | } |
563 | 707 | break; |
564 | 707 | default: |
565 | 0 | VERIFY_NOT_REACHED(); |
566 | 0 | break; |
567 | 3.03k | } |
568 | | |
569 | | // Swap r and b values: |
570 | 1.87M | for (int y = 0; y < context.height; ++y) { |
571 | 1.87M | auto* pixels = (Pixel*)context.bitmap->scanline(y); |
572 | 32.3M | for (int i = 0; i < context.bitmap->width(); ++i) { |
573 | 30.5M | auto& x = pixels[i]; |
574 | 30.5M | swap(x.r, x.b); |
575 | 30.5M | } |
576 | 1.87M | } |
577 | | |
578 | 2.98k | return {}; |
579 | 3.03k | } |
580 | | |
581 | | static bool decode_png_header(PNGLoadingContext& context) |
582 | 15.5k | { |
583 | 15.5k | if (!context.data || context.data_size < sizeof(PNG::header)) { |
584 | 89 | dbgln_if(PNG_DEBUG, "Missing PNG header"); |
585 | 89 | context.state = PNGLoadingContext::State::Error; |
586 | 89 | return false; |
587 | 89 | } |
588 | | |
589 | 15.4k | if (memcmp(context.data, PNG::header.span().data(), sizeof(PNG::header)) != 0) { |
590 | 1.78k | dbgln_if(PNG_DEBUG, "Invalid PNG header"); |
591 | 1.78k | context.state = PNGLoadingContext::State::Error; |
592 | 1.78k | return false; |
593 | 1.78k | } |
594 | | |
595 | 13.6k | context.data_current_ptr = context.data + sizeof(PNG::header); |
596 | 13.6k | return true; |
597 | 15.4k | } |
598 | | |
599 | | static ErrorOr<void> decode_png_ihdr(PNGLoadingContext& context) |
600 | 9.54k | { |
601 | 9.54k | size_t data_remaining = context.data_size - (context.data_current_ptr - context.data); |
602 | | |
603 | 9.54k | Streamer streamer(context.data_current_ptr, data_remaining); |
604 | | |
605 | | // https://www.w3.org/TR/png/#11IHDR |
606 | | // The IHDR chunk shall be the first chunk in the PNG datastream. |
607 | 9.54k | TRY(process_chunk(streamer, context)); |
608 | | |
609 | 0 | context.data_current_ptr = streamer.current_data_ptr(); |
610 | | |
611 | 9.10k | VERIFY(context.state == PNGLoadingContext::State::IHDRDecoded); |
612 | 9.10k | return {}; |
613 | 9.10k | } |
614 | | |
615 | | static bool decode_png_image_data_chunk(PNGLoadingContext& context) |
616 | 9.10k | { |
617 | 9.10k | VERIFY(context.state >= PNGLoadingContext::IHDRDecoded); |
618 | | |
619 | 9.10k | if (context.state >= PNGLoadingContext::ImageDataChunkDecoded) |
620 | 0 | return true; |
621 | | |
622 | 9.10k | size_t data_remaining = context.data_size - (context.data_current_ptr - context.data); |
623 | | |
624 | 9.10k | Streamer streamer(context.data_current_ptr, data_remaining); |
625 | 63.0k | while (!streamer.at_end() && !context.has_seen_iend) { |
626 | 62.4k | if (auto result = process_chunk(streamer, context); result.is_error()) { |
627 | 5.50k | context.state = PNGLoadingContext::State::Error; |
628 | | // FIXME: Return this to caller instead of logging it. |
629 | 5.50k | dbgln("PNGLoader: Error processing chunk: {}", result.error()); |
630 | 5.50k | return false; |
631 | 5.50k | } |
632 | | |
633 | 56.9k | context.data_current_ptr = streamer.current_data_ptr(); |
634 | | |
635 | 56.9k | if (context.state >= PNGLoadingContext::State::ImageDataChunkDecoded) |
636 | 3.03k | return true; |
637 | 56.9k | } |
638 | | |
639 | 570 | return false; |
640 | 9.10k | } |
641 | | |
642 | | static bool decode_png_animation_data_chunks(PNGLoadingContext& context, u32 requested_animation_frame_index) |
643 | 0 | { |
644 | 0 | if (context.state >= PNGLoadingContext::ImageDataChunkDecoded) { |
645 | 0 | if (context.last_completed_animation_frame_index.has_value()) { |
646 | 0 | if (requested_animation_frame_index <= context.last_completed_animation_frame_index.value()) |
647 | 0 | return true; |
648 | 0 | } |
649 | 0 | } else if (!decode_png_image_data_chunk(context)) { |
650 | 0 | return false; |
651 | 0 | } |
652 | | |
653 | 0 | size_t data_remaining = context.data_size - (context.data_current_ptr - context.data); |
654 | |
|
655 | 0 | Streamer streamer(context.data_current_ptr, data_remaining); |
656 | 0 | while (!streamer.at_end() && !context.has_seen_iend) { |
657 | 0 | if (auto result = process_chunk(streamer, context); result.is_error()) { |
658 | | // FIXME: Return this to caller instead of logging it. |
659 | 0 | dbgln("PNGLoader: Error processing chunk: {}", result.error()); |
660 | 0 | context.state = PNGLoadingContext::State::Error; |
661 | 0 | return false; |
662 | 0 | } |
663 | | |
664 | 0 | context.data_current_ptr = streamer.current_data_ptr(); |
665 | |
|
666 | 0 | if (context.last_completed_animation_frame_index.has_value()) { |
667 | 0 | if (requested_animation_frame_index <= context.last_completed_animation_frame_index.value()) |
668 | 0 | break; |
669 | 0 | } |
670 | 0 | } |
671 | | |
672 | 0 | if (!context.last_completed_animation_frame_index.has_value()) |
673 | 0 | return false; |
674 | 0 | return requested_animation_frame_index <= context.last_completed_animation_frame_index.value(); |
675 | 0 | } |
676 | | |
677 | | static bool decode_png_chunks(PNGLoadingContext& context) |
678 | 3.03k | { |
679 | 3.03k | VERIFY(context.state >= PNGLoadingContext::IHDRDecoded); |
680 | | |
681 | 3.03k | if (context.state >= PNGLoadingContext::State::ChunksDecoded) |
682 | 0 | return true; |
683 | | |
684 | 3.03k | size_t data_remaining = context.data_size - (context.data_current_ptr - context.data); |
685 | | |
686 | 3.03k | context.compressed_data.ensure_capacity(context.data_size); |
687 | | |
688 | 3.03k | Streamer streamer(context.data_current_ptr, data_remaining); |
689 | 90.1k | while (!streamer.at_end() && !context.has_seen_iend) { |
690 | 87.9k | if (auto result = process_chunk(streamer, context); result.is_error()) { |
691 | | // Ignore failed chunk and just consider chunk decoding being done. |
692 | | // decode_png_bitmap() will check whether we got all required ones anyway. |
693 | 863 | break; |
694 | 863 | } |
695 | | |
696 | 87.1k | context.data_current_ptr = streamer.current_data_ptr(); |
697 | 87.1k | } |
698 | | |
699 | 3.03k | context.state = PNGLoadingContext::State::ChunksDecoded; |
700 | 3.03k | return true; |
701 | 3.03k | } |
702 | | |
703 | | static ErrorOr<void> decode_png_bitmap_simple(PNGLoadingContext& context, ByteBuffer& decompression_buffer) |
704 | 377 | { |
705 | 377 | Streamer streamer(decompression_buffer.data(), decompression_buffer.size()); |
706 | | |
707 | 3.91M | for (int y = 0; y < context.height; ++y) { |
708 | 3.91M | u8 filter_byte; |
709 | 3.91M | if (!streamer.read(filter_byte)) { |
710 | 23 | context.state = PNGLoadingContext::State::Error; |
711 | 23 | return Error::from_string_literal("PNGImageDecoderPlugin: Decoding failed"); |
712 | 23 | } |
713 | | |
714 | 3.91M | if (filter_byte > 4) { |
715 | 46 | context.state = PNGLoadingContext::State::Error; |
716 | 46 | return Error::from_string_literal("PNGImageDecoderPlugin: Invalid PNG filter"); |
717 | 46 | } |
718 | | |
719 | 3.91M | context.scanlines.append({ MUST(PNG::filter_type(filter_byte)) }); |
720 | 0 | auto& scanline_buffer = context.scanlines.last().data; |
721 | 3.91M | auto row_size = context.compute_row_size_for_width(context.width); |
722 | 3.91M | if (row_size.has_overflow()) |
723 | 6 | return Error::from_string_literal("PNGImageDecoderPlugin: Row size overflow"); |
724 | | |
725 | 3.91M | if (!streamer.wrap_bytes(scanline_buffer, row_size.value())) { |
726 | 78 | context.state = PNGLoadingContext::State::Error; |
727 | 78 | return Error::from_string_literal("PNGImageDecoderPlugin: Decoding failed"); |
728 | 78 | } |
729 | 3.91M | } |
730 | | |
731 | 224 | context.bitmap = TRY(Bitmap::create(context.has_alpha() ? BitmapFormat::BGRA8888 : BitmapFormat::BGRx8888, { context.width, context.height })); |
732 | 0 | return unfilter(context); |
733 | 224 | } |
734 | | |
735 | | static int adam7_height(PNGLoadingContext& context, int pass) |
736 | 4.36k | { |
737 | 4.36k | switch (pass) { |
738 | 1.09k | case 1: |
739 | 1.09k | return (context.height + 7) / 8; |
740 | 975 | case 2: |
741 | 975 | return (context.height + 7) / 8; |
742 | 700 | case 3: |
743 | 700 | return (context.height + 3) / 8; |
744 | 542 | case 4: |
745 | 542 | return (context.height + 3) / 4; |
746 | 430 | case 5: |
747 | 430 | return (context.height + 1) / 4; |
748 | 346 | case 6: |
749 | 346 | return (context.height + 1) / 2; |
750 | 271 | case 7: |
751 | 271 | return context.height / 2; |
752 | 0 | default: |
753 | 0 | VERIFY_NOT_REACHED(); |
754 | 4.36k | } |
755 | 4.36k | } |
756 | | |
757 | | static int adam7_width(PNGLoadingContext& context, int pass) |
758 | 4.36k | { |
759 | 4.36k | switch (pass) { |
760 | 1.09k | case 1: |
761 | 1.09k | return (context.width + 7) / 8; |
762 | 975 | case 2: |
763 | 975 | return (context.width + 3) / 8; |
764 | 700 | case 3: |
765 | 700 | return (context.width + 3) / 4; |
766 | 542 | case 4: |
767 | 542 | return (context.width + 1) / 4; |
768 | 430 | case 5: |
769 | 430 | return (context.width + 1) / 2; |
770 | 346 | case 6: |
771 | 346 | return context.width / 2; |
772 | 271 | case 7: |
773 | 271 | return context.width; |
774 | 0 | default: |
775 | 0 | VERIFY_NOT_REACHED(); |
776 | 4.36k | } |
777 | 4.36k | } |
778 | | |
779 | | // Index 0 unused (non-interlaced case) |
780 | | static int adam7_starty[8] = { 0, 0, 0, 4, 0, 2, 0, 1 }; |
781 | | static int adam7_startx[8] = { 0, 0, 4, 0, 2, 0, 1, 0 }; |
782 | | static int adam7_stepy[8] = { 1, 8, 8, 8, 4, 4, 2, 2 }; |
783 | | static int adam7_stepx[8] = { 1, 8, 8, 4, 4, 2, 2, 1 }; |
784 | | |
785 | | static ErrorOr<void> decode_adam7_pass(PNGLoadingContext& context, Streamer& streamer, int pass) |
786 | 4.36k | { |
787 | 4.36k | auto subimage_context = context.create_subimage_context(adam7_width(context, pass), adam7_height(context, pass)); |
788 | | |
789 | | // For small images, some passes might be empty |
790 | 4.36k | if (!subimage_context.width || !subimage_context.height) |
791 | 705 | return {}; |
792 | | |
793 | 1.25M | for (int y = 0; y < subimage_context.height; ++y) { |
794 | 1.25M | u8 filter_byte; |
795 | 1.25M | if (!streamer.read(filter_byte)) { |
796 | 116 | context.state = PNGLoadingContext::State::Error; |
797 | 116 | return Error::from_string_literal("PNGImageDecoderPlugin: Decoding failed"); |
798 | 116 | } |
799 | | |
800 | 1.25M | if (filter_byte > 4) { |
801 | 188 | context.state = PNGLoadingContext::State::Error; |
802 | 188 | return Error::from_string_literal("PNGImageDecoderPlugin: Invalid PNG filter"); |
803 | 188 | } |
804 | | |
805 | 1.25M | subimage_context.scanlines.append({ MUST(PNG::filter_type(filter_byte)) }); |
806 | 0 | auto& scanline_buffer = subimage_context.scanlines.last().data; |
807 | | |
808 | 1.25M | auto row_size = context.compute_row_size_for_width(subimage_context.width); |
809 | 1.25M | if (row_size.has_overflow()) |
810 | 0 | return Error::from_string_literal("PNGImageDecoderPlugin: Row size overflow"); |
811 | 1.25M | if (!streamer.wrap_bytes(scanline_buffer, row_size.value())) { |
812 | 540 | context.state = PNGLoadingContext::State::Error; |
813 | 540 | return Error::from_string_literal("PNGImageDecoderPlugin: Decoding failed"); |
814 | 540 | } |
815 | 1.25M | } |
816 | | |
817 | 2.81k | subimage_context.bitmap = TRY(Bitmap::create(context.bitmap->format(), { subimage_context.width, subimage_context.height })); |
818 | 2.81k | TRY(unfilter(subimage_context)); |
819 | | |
820 | | // Copy the subimage data into the main image according to the pass pattern |
821 | 1.17M | for (int y = 0, dy = adam7_starty[pass]; y < subimage_context.height && dy < context.height; ++y, dy += adam7_stepy[pass]) { |
822 | 27.8M | for (int x = 0, dx = adam7_startx[pass]; x < subimage_context.width && dx < context.width; ++x, dx += adam7_stepx[pass]) { |
823 | 26.6M | context.bitmap->set_pixel(dx, dy, subimage_context.bitmap->get_pixel(x, y)); |
824 | 26.6M | } |
825 | 1.17M | } |
826 | 2.76k | return {}; |
827 | 2.81k | } |
828 | | |
829 | | static ErrorOr<void> decode_png_adam7(PNGLoadingContext& context, ByteBuffer& decompression_buffer) |
830 | 1.15k | { |
831 | 1.15k | Streamer streamer(decompression_buffer.data(), decompression_buffer.size()); |
832 | 1.15k | context.bitmap = TRY(Bitmap::create(context.has_alpha() ? BitmapFormat::BGRA8888 : BitmapFormat::BGRx8888, { context.width, context.height })); |
833 | 4.56k | for (int pass = 1; pass <= 7; ++pass) |
834 | 4.36k | TRY(decode_adam7_pass(context, streamer, pass)); |
835 | 205 | return {}; |
836 | 1.09k | } |
837 | | |
838 | | static ErrorOr<void> decode_png_bitmap(PNGLoadingContext& context) |
839 | 3.03k | { |
840 | 3.03k | if (context.state < PNGLoadingContext::State::ChunksDecoded) { |
841 | 3.03k | if (!decode_png_chunks(context)) |
842 | 0 | return Error::from_string_literal("PNGImageDecoderPlugin: Decoding failed"); |
843 | 3.03k | } |
844 | | |
845 | 3.03k | if (context.state >= PNGLoadingContext::State::BitmapDecoded) |
846 | 0 | return {}; |
847 | | |
848 | 3.03k | if (context.color_type == PNG::ColorType::IndexedColor && context.palette_data.is_empty()) |
849 | 9 | return Error::from_string_literal("PNGImageDecoderPlugin: Didn't see a PLTE chunk for a palletized image, or it was empty."); |
850 | | |
851 | 3.02k | auto compressed_data_stream = make<FixedMemoryStream>(context.compressed_data.span()); |
852 | 3.02k | auto decompressor_or_error = Compress::ZlibDecompressor::create(move(compressed_data_stream)); |
853 | 3.02k | if (decompressor_or_error.is_error()) { |
854 | 139 | context.state = PNGLoadingContext::State::Error; |
855 | 139 | return decompressor_or_error.release_error(); |
856 | 139 | } |
857 | 2.88k | auto decompressor = decompressor_or_error.release_value(); |
858 | 2.88k | auto result_or_error = decompressor->read_until_eof(); |
859 | 2.88k | if (result_or_error.is_error()) { |
860 | 1.35k | context.state = PNGLoadingContext::State::Error; |
861 | 1.35k | return result_or_error.release_error(); |
862 | 1.35k | } |
863 | 1.53k | auto decompression_buffer = result_or_error.release_value(); |
864 | 1.53k | context.compressed_data.clear(); |
865 | | |
866 | 1.53k | context.scanlines.ensure_capacity(context.height); |
867 | 1.53k | switch (context.interlace_method) { |
868 | 377 | case PngInterlaceMethod::Null: |
869 | 377 | TRY(decode_png_bitmap_simple(context, decompression_buffer)); |
870 | 0 | break; |
871 | 1.15k | case PngInterlaceMethod::Adam7: |
872 | 1.15k | TRY(decode_png_adam7(context, decompression_buffer)); |
873 | 0 | break; |
874 | 0 | default: |
875 | 0 | context.state = PNGLoadingContext::State::Error; |
876 | 0 | return Error::from_string_literal("PNGImageDecoderPlugin: Invalid interlace method"); |
877 | 1.53k | } |
878 | | |
879 | 422 | context.state = PNGLoadingContext::State::BitmapDecoded; |
880 | 422 | return {}; |
881 | 1.53k | } |
882 | | |
883 | | static ErrorOr<RefPtr<Bitmap>> decode_png_animation_frame_bitmap(PNGLoadingContext& context, AnimationFrame& animation_frame) |
884 | 0 | { |
885 | 0 | if (context.color_type == PNG::ColorType::IndexedColor && context.palette_data.is_empty()) |
886 | 0 | return Error::from_string_literal("PNGImageDecoderPlugin: Didn't see a PLTE chunk for a palletized image, or it was empty."); |
887 | | |
888 | 0 | VERIFY(!animation_frame.bitmap); |
889 | | |
890 | 0 | auto frame_rect = animation_frame.rect(); |
891 | 0 | auto frame_context = context.create_subimage_context(frame_rect.width(), frame_rect.height()); |
892 | |
|
893 | 0 | auto compressed_data_stream = make<FixedMemoryStream>(animation_frame.compressed_data.span()); |
894 | 0 | auto decompressor = TRY(Compress::ZlibDecompressor::create(move(compressed_data_stream))); |
895 | 0 | auto decompression_buffer = TRY(decompressor->read_until_eof()); |
896 | 0 | frame_context.compressed_data.clear(); |
897 | |
|
898 | 0 | frame_context.scanlines.ensure_capacity(frame_context.height); |
899 | 0 | switch (context.interlace_method) { |
900 | 0 | case PngInterlaceMethod::Null: |
901 | 0 | TRY(decode_png_bitmap_simple(frame_context, decompression_buffer)); |
902 | 0 | break; |
903 | 0 | case PngInterlaceMethod::Adam7: |
904 | 0 | TRY(decode_png_adam7(frame_context, decompression_buffer)); |
905 | 0 | break; |
906 | 0 | default: |
907 | 0 | return Error::from_string_literal("PNGImageDecoderPlugin: Invalid interlace method"); |
908 | 0 | } |
909 | | |
910 | 0 | context.state = PNGLoadingContext::State::BitmapDecoded; |
911 | 0 | return move(frame_context.bitmap); |
912 | 0 | } |
913 | | |
914 | | static bool is_valid_compression_method(u8 compression_method) |
915 | 9.27k | { |
916 | 9.27k | return compression_method == 0; |
917 | 9.27k | } |
918 | | |
919 | | static bool is_valid_filter_method(u8 filter_method) |
920 | 9.25k | { |
921 | 9.25k | return filter_method == 0; |
922 | 9.25k | } |
923 | | |
924 | | static ErrorOr<void> process_IHDR(ReadonlyBytes data, PNGLoadingContext& context) |
925 | 9.42k | { |
926 | 9.42k | if (data.size() < (int)sizeof(PNG_IHDR)) |
927 | 11 | return Error::from_string_literal("IHDR chunk has an abnormal size"); |
928 | | |
929 | 9.41k | auto const& ihdr = *(const PNG_IHDR*)data.data(); |
930 | | |
931 | 9.41k | if (ihdr.width == 0 || ihdr.width > NumericLimits<i32>::max()) { |
932 | 67 | dbgln("PNG has invalid width {}", ihdr.width); |
933 | 67 | return Error::from_string_literal("Invalid width"); |
934 | 67 | } |
935 | | |
936 | 9.35k | if (ihdr.height == 0 || ihdr.height > NumericLimits<i32>::max()) { |
937 | 71 | dbgln("PNG has invalid height {}", ihdr.height); |
938 | 71 | return Error::from_string_literal("Invalid height"); |
939 | 71 | } |
940 | | |
941 | 9.27k | if (!is_valid_compression_method(ihdr.compression_method)) { |
942 | 23 | dbgln("PNG has invalid compression method {}", ihdr.compression_method); |
943 | 23 | return Error::from_string_literal("Unsupported compression method"); |
944 | 23 | } |
945 | | |
946 | 9.25k | if (!is_valid_filter_method(ihdr.filter_method)) { |
947 | 15 | dbgln("PNG has invalid filter method {}", ihdr.filter_method); |
948 | 15 | return Error::from_string_literal("Unsupported filter method"); |
949 | 15 | } |
950 | | |
951 | 9.24k | context.width = ihdr.width; |
952 | 9.24k | context.height = ihdr.height; |
953 | 9.24k | context.bit_depth = ihdr.bit_depth; |
954 | 9.24k | context.color_type = ihdr.color_type; |
955 | 9.24k | context.compression_method = ihdr.compression_method; |
956 | 9.24k | context.filter_method = ihdr.filter_method; |
957 | 9.24k | context.interlace_method = ihdr.interlace_method; |
958 | | |
959 | 9.24k | dbgln_if(PNG_DEBUG, "PNG: {}x{} ({} bpp)", context.width, context.height, context.bit_depth); |
960 | 9.24k | dbgln_if(PNG_DEBUG, " Color type: {}", to_underlying(context.color_type)); |
961 | 9.24k | dbgln_if(PNG_DEBUG, "Compress Method: {}", context.compression_method); |
962 | 9.24k | dbgln_if(PNG_DEBUG, " Filter Method: {}", context.filter_method); |
963 | 9.24k | dbgln_if(PNG_DEBUG, " Interlace type: {}", context.interlace_method); |
964 | | |
965 | 9.24k | if (context.interlace_method != PngInterlaceMethod::Null && context.interlace_method != PngInterlaceMethod::Adam7) { |
966 | 17 | dbgln_if(PNG_DEBUG, "PNGLoader::process_IHDR: unknown interlace method: {}", context.interlace_method); |
967 | 17 | return Error::from_string_literal("Unsupported interlacing method"); |
968 | 17 | } |
969 | | |
970 | 9.22k | switch (context.color_type) { |
971 | 4.18k | case PNG::ColorType::Greyscale: |
972 | 4.18k | if (context.bit_depth != 1 && context.bit_depth != 2 && context.bit_depth != 4 && context.bit_depth != 8 && context.bit_depth != 16) |
973 | 27 | return Error::from_string_literal("Unsupported bit depth for a greyscale image"); |
974 | 4.16k | context.channels = 1; |
975 | 4.16k | break; |
976 | 792 | case PNG::ColorType::GreyscaleWithAlpha: |
977 | 792 | if (context.bit_depth != 8 && context.bit_depth != 16) |
978 | 21 | return Error::from_string_literal("Unsupported bit depth for a greyscale image with alpha"); |
979 | 771 | context.channels = 2; |
980 | 771 | break; |
981 | 3.22k | case PNG::ColorType::Truecolor: |
982 | 3.22k | if (context.bit_depth != 8 && context.bit_depth != 16) |
983 | 21 | return Error::from_string_literal("Unsupported bit depth for a true color image"); |
984 | 3.20k | context.channels = 3; |
985 | 3.20k | break; |
986 | 572 | case PNG::ColorType::IndexedColor: |
987 | 572 | if (context.bit_depth != 1 && context.bit_depth != 2 && context.bit_depth != 4 && context.bit_depth != 8) |
988 | 25 | return Error::from_string_literal("Unsupported bit depth for a indexed color image"); |
989 | 547 | context.channels = 1; |
990 | 547 | break; |
991 | 447 | case PNG::ColorType::TruecolorWithAlpha: |
992 | 447 | if (context.bit_depth != 8 && context.bit_depth != 16) |
993 | 21 | return Error::from_string_literal("Unsupported bit depth for a true color image with alpha"); |
994 | 426 | context.channels = 4; |
995 | 426 | break; |
996 | 2 | default: |
997 | 2 | return Error::from_string_literal("Unsupported color type"); |
998 | 9.22k | } |
999 | | |
1000 | 9.10k | context.state = PNGLoadingContext::IHDRDecoded; |
1001 | | |
1002 | 9.10k | return {}; |
1003 | 9.22k | } |
1004 | | |
1005 | | static ErrorOr<void> process_IDAT(ReadonlyBytes data, PNGLoadingContext& context) |
1006 | 8.05k | { |
1007 | 8.05k | context.compressed_data.append(data); |
1008 | 8.05k | if (context.state < PNGLoadingContext::State::ImageDataChunkDecoded) |
1009 | 3.03k | context.state = PNGLoadingContext::State::ImageDataChunkDecoded; |
1010 | 8.05k | return {}; |
1011 | 8.05k | } |
1012 | | |
1013 | | static ErrorOr<void> process_PLTE(ReadonlyBytes data, PNGLoadingContext& context) |
1014 | 57.0k | { |
1015 | 57.0k | TRY(context.palette_data.try_append((PaletteEntry const*)data.data(), data.size() / 3)); |
1016 | 0 | return {}; |
1017 | 57.0k | } |
1018 | | |
1019 | | static ErrorOr<void> process_tRNS(ReadonlyBytes data, PNGLoadingContext& context) |
1020 | 6.23k | { |
1021 | 6.23k | switch (context.color_type) { |
1022 | 1.00k | case PNG::ColorType::Greyscale: |
1023 | 3.86k | case PNG::ColorType::Truecolor: |
1024 | 5.68k | case PNG::ColorType::IndexedColor: |
1025 | 5.68k | TRY(context.palette_transparency_data.try_append(data)); |
1026 | 0 | break; |
1027 | 541 | default: |
1028 | 541 | break; |
1029 | 6.23k | } |
1030 | 6.23k | return {}; |
1031 | 6.23k | } |
1032 | | |
1033 | | static ErrorOr<void> process_cHRM(ReadonlyBytes data, PNGLoadingContext& context) |
1034 | 231 | { |
1035 | | // https://www.w3.org/TR/png/#11cHRM |
1036 | 231 | if (data.size() != 32) |
1037 | 18 | return Error::from_string_literal("cHRM chunk has an abnormal size"); |
1038 | 213 | context.chromaticities_and_whitepoint = *bit_cast<ChromaticitiesAndWhitepoint* const>(data.data()); |
1039 | 213 | return {}; |
1040 | 231 | } |
1041 | | |
1042 | | static ErrorOr<void> process_cICP(ReadonlyBytes data, PNGLoadingContext& context) |
1043 | 234 | { |
1044 | | // https://www.w3.org/TR/png/#cICP-chunk |
1045 | 234 | if (data.size() != 4) |
1046 | 16 | return Error::from_string_literal("cICP chunk has an abnormal size"); |
1047 | 218 | context.coding_independent_code_points = *bit_cast<CodingIndependentCodePoints* const>(data.data()); |
1048 | 218 | return {}; |
1049 | 234 | } |
1050 | | |
1051 | | static ErrorOr<void> process_iCCP(ReadonlyBytes data, PNGLoadingContext& context) |
1052 | 450 | { |
1053 | | // https://www.w3.org/TR/png/#11iCCP |
1054 | 450 | size_t profile_name_length_max = min(80u, data.size()); |
1055 | 450 | size_t profile_name_length = strnlen((char const*)data.data(), profile_name_length_max); |
1056 | 450 | if (profile_name_length == 0 || profile_name_length == profile_name_length_max) |
1057 | 6 | return Error::from_string_literal("iCCP chunk does not contain a profile name"); |
1058 | | |
1059 | 444 | if (data.size() < profile_name_length + 2) |
1060 | 6 | return Error::from_string_literal("iCCP chunk is too small"); |
1061 | | |
1062 | 438 | u8 compression_method = data[profile_name_length + 1]; |
1063 | 438 | if (compression_method != 0) |
1064 | 23 | return Error::from_string_literal("Unsupported compression method in the iCCP chunk"); |
1065 | | |
1066 | 415 | context.embedded_icc_profile = EmbeddedICCProfile { { data.data(), profile_name_length }, data.slice(profile_name_length + 2) }; |
1067 | | |
1068 | 415 | return {}; |
1069 | 438 | } |
1070 | | |
1071 | | static ErrorOr<void> process_gAMA(ReadonlyBytes data, PNGLoadingContext& context) |
1072 | 379 | { |
1073 | | // https://www.w3.org/TR/png/#11gAMA |
1074 | 379 | if (data.size() != 4) |
1075 | 13 | return Error::from_string_literal("gAMA chunk has an abnormal size"); |
1076 | | |
1077 | 366 | u32 gamma = *bit_cast<NetworkOrdered<u32> const*>(data.data()); |
1078 | 366 | if (gamma & 0x8000'0000) |
1079 | 63 | return Error::from_string_literal("Gamma value is too high"); |
1080 | 303 | context.gamma = gamma; |
1081 | | |
1082 | 303 | return {}; |
1083 | 366 | } |
1084 | | |
1085 | | static ErrorOr<void> process_sRGB(ReadonlyBytes data, PNGLoadingContext& context) |
1086 | 1.32k | { |
1087 | | // https://www.w3.org/TR/png/#srgb-standard-colour-space |
1088 | 1.32k | if (data.size() != 1) { |
1089 | | // Invalid per spec, but (rarely) happens in the wild. Log and ignore. |
1090 | 1.00k | warnln("warning: PNG sRGB chunk has an abnormal size; ignoring"); |
1091 | 1.00k | return {}; |
1092 | 1.00k | } |
1093 | | |
1094 | 315 | u8 rendering_intent = data[0]; |
1095 | 315 | if (rendering_intent > 3) |
1096 | 11 | return Error::from_string_literal("Unsupported rendering intent"); |
1097 | | |
1098 | 304 | context.sRGB_rendering_intent = (RenderingIntent)rendering_intent; |
1099 | | |
1100 | 304 | return {}; |
1101 | 315 | } |
1102 | | |
1103 | | static ErrorOr<void> process_acTL(ReadonlyBytes data, PNGLoadingContext& context) |
1104 | 1.35k | { |
1105 | | // https://www.w3.org/TR/png/#acTL-chunk |
1106 | 1.35k | if (context.has_seen_idat_chunk) |
1107 | 0 | return {}; // Ignore if we encounter it after the first idat |
1108 | 1.35k | if (data.size() != sizeof(acTL_Chunk)) |
1109 | 18 | return Error::from_string_literal("acTL chunk has an abnormal size"); |
1110 | | |
1111 | 1.33k | auto const& acTL = *bit_cast<acTL_Chunk* const>(data.data()); |
1112 | 1.33k | context.animation_frame_count = acTL.num_frames; |
1113 | 1.33k | context.animation_loop_count = acTL.num_plays; |
1114 | 1.33k | context.has_seen_actl_chunk_before_idat = true; |
1115 | 1.33k | TRY(context.animation_frames.try_ensure_capacity(context.animation_frame_count)); |
1116 | 0 | return {}; |
1117 | 1.33k | } |
1118 | | |
1119 | | static ErrorOr<void> process_fcTL(ReadonlyBytes data, PNGLoadingContext& context) |
1120 | 1.19k | { |
1121 | | // https://www.w3.org/TR/png/#fcTL-chunk |
1122 | 1.19k | if (!context.has_seen_actl_chunk_before_idat) |
1123 | 301 | return {}; // Ignore if it's not a valid animated png |
1124 | | |
1125 | 891 | if (data.size() != sizeof(fcTL_Chunk)) |
1126 | 10 | return Error::from_string_literal("fcTL chunk has an abnormal size"); |
1127 | | |
1128 | 881 | auto const& fcTL = *bit_cast<fcTL_Chunk* const>(data.data()); |
1129 | 881 | if (fcTL.sequence_number != context.animation_next_expected_seq) { |
1130 | 92 | dbgln_if(PNG_DEBUG, "Expected fcTL sequence number: {}, got: {}", context.animation_next_expected_seq, fcTL.sequence_number); |
1131 | 92 | return Error::from_string_literal("Unexpected sequence number"); |
1132 | 92 | } |
1133 | | |
1134 | 789 | context.animation_next_expected_seq++; |
1135 | | |
1136 | 789 | if (fcTL.width == 0 || fcTL.height == 0) |
1137 | 5 | return Error::from_string_literal("width and height must be greater than zero in fcTL chunk"); |
1138 | | |
1139 | 784 | Checked<int> left { static_cast<int>(fcTL.x_offset) }; |
1140 | 784 | Checked<int> top { static_cast<int>(fcTL.y_offset) }; |
1141 | 784 | Checked<int> width { static_cast<int>(fcTL.width) }; |
1142 | 784 | Checked<int> height { static_cast<int>(fcTL.height) }; |
1143 | 784 | auto right = left + width; |
1144 | 784 | auto bottom = top + height; |
1145 | 784 | if (left < 0 || width <= 0 || right.has_overflow() || right > context.width) |
1146 | 193 | return Error::from_string_literal("Invalid x_offset value in fcTL chunk"); |
1147 | 591 | if (top < 0 || height <= 0 || bottom.has_overflow() || bottom > context.height) |
1148 | 196 | return Error::from_string_literal("Invalid y_offset value in fcTL chunk"); |
1149 | | |
1150 | 395 | bool is_first_animation_frame = context.animation_frames.is_empty(); |
1151 | 395 | if (!is_first_animation_frame) |
1152 | 256 | context.last_completed_animation_frame_index = context.animation_frames.size() - 1; |
1153 | | |
1154 | 395 | context.animation_frames.append({ fcTL }); |
1155 | | |
1156 | 395 | if (!context.has_seen_idat_chunk && is_first_animation_frame) |
1157 | 139 | context.is_first_idat_part_of_animation = true; |
1158 | 395 | return {}; |
1159 | 591 | } |
1160 | | |
1161 | | static ErrorOr<void> process_fdAT(ReadonlyBytes data, PNGLoadingContext& context) |
1162 | 383 | { |
1163 | | // https://www.w3.org/TR/png/#fdAT-chunk |
1164 | | |
1165 | 383 | if (data.size() <= 4) |
1166 | 5 | return Error::from_string_literal("fdAT chunk has an abnormal size"); |
1167 | | |
1168 | 378 | u32 sequence_number = *bit_cast<NetworkOrdered<u32> const*>(data.data()); |
1169 | 378 | if (sequence_number != context.animation_next_expected_seq) { |
1170 | 92 | dbgln_if(PNG_DEBUG, "Expected fdAT sequence number: {}, got: {}", context.animation_next_expected_seq, sequence_number); |
1171 | 92 | return Error::from_string_literal("Unexpected sequence number"); |
1172 | 92 | } |
1173 | 286 | context.animation_next_expected_seq++; |
1174 | | |
1175 | 286 | if (context.animation_frames.is_empty()) |
1176 | 2 | return Error::from_string_literal("No frame available"); |
1177 | 284 | auto& current_animation_frame = context.animation_frames[context.animation_frames.size() - 1]; |
1178 | 284 | auto compressed_data = data.slice(4); |
1179 | 284 | current_animation_frame.compressed_data.append(compressed_data.data(), compressed_data.size()); |
1180 | 284 | return {}; |
1181 | 286 | } |
1182 | | |
1183 | | static ErrorOr<void> process_eXIf(ReadonlyBytes bytes, PNGLoadingContext& context) |
1184 | 8.31k | { |
1185 | 8.31k | context.exif_metadata = TRY(TIFFImageDecoderPlugin::read_exif_metadata(bytes)); |
1186 | 0 | return {}; |
1187 | 8.31k | } |
1188 | | |
1189 | | static void process_IEND(ReadonlyBytes, PNGLoadingContext& context) |
1190 | 11 | { |
1191 | | // https://www.w3.org/TR/png/#11IEND |
1192 | 11 | if (context.has_seen_actl_chunk_before_idat) |
1193 | 2 | context.last_completed_animation_frame_index = context.animation_frames.size(); |
1194 | | |
1195 | 11 | context.has_seen_iend = true; |
1196 | 11 | } |
1197 | | |
1198 | | static ErrorOr<void> process_chunk(Streamer& streamer, PNGLoadingContext& context) |
1199 | 159k | { |
1200 | 159k | u32 chunk_size; |
1201 | 159k | if (!streamer.read(chunk_size)) { |
1202 | 462 | dbgln_if(PNG_DEBUG, "Bail at chunk_size"); |
1203 | 462 | return Error::from_string_literal("Error while reading from Streamer"); |
1204 | 462 | } |
1205 | | |
1206 | 159k | Array<u8, 4> chunk_type_buffer; |
1207 | 159k | StringView const chunk_type { chunk_type_buffer.span() }; |
1208 | 159k | if (!streamer.read_bytes(chunk_type_buffer.data(), chunk_type_buffer.size())) { |
1209 | 148 | dbgln_if(PNG_DEBUG, "Bail at chunk_type"); |
1210 | 148 | return Error::from_string_literal("Error while reading from Streamer"); |
1211 | 148 | } |
1212 | 159k | ReadonlyBytes chunk_data; |
1213 | 159k | if (!streamer.wrap_bytes(chunk_data, chunk_size)) { |
1214 | 850 | dbgln_if(PNG_DEBUG, "Bail at chunk_data"); |
1215 | 850 | return Error::from_string_literal("Error while reading from Streamer"); |
1216 | 850 | } |
1217 | 158k | u32 chunk_crc; |
1218 | 158k | if (!streamer.read(chunk_crc)) { |
1219 | 39 | dbgln_if(PNG_DEBUG, "Bail at chunk_crc"); |
1220 | 39 | return Error::from_string_literal("Error while reading from Streamer"); |
1221 | 39 | } |
1222 | 158k | dbgln_if(PNG_DEBUG, "Chunk type: '{}', size: {}, crc: {:x}", chunk_type, chunk_size, chunk_crc); |
1223 | | |
1224 | 158k | if (chunk_type == "IHDR"sv) { |
1225 | 9.43k | if (context.state >= PNGLoadingContext::IHDRDecoded) |
1226 | 11 | return Error::from_string_literal("Multiple IHDR chunks"); |
1227 | | |
1228 | 9.42k | return process_IHDR(chunk_data, context); |
1229 | 9.43k | } |
1230 | | |
1231 | 149k | if (context.state < PNGLoadingContext::IHDRDecoded) |
1232 | 31 | return Error::from_string_literal("IHDR is not the first chunk of the file"); |
1233 | | |
1234 | 149k | if (chunk_type == "IDAT"sv) |
1235 | 8.05k | return process_IDAT(chunk_data, context); |
1236 | 140k | if (chunk_type == "PLTE"sv) |
1237 | 57.0k | return process_PLTE(chunk_data, context); |
1238 | 83.8k | if (chunk_type == "cHRM"sv) |
1239 | 231 | return process_cHRM(chunk_data, context); |
1240 | 83.6k | if (chunk_type == "cICP"sv) |
1241 | 234 | return process_cICP(chunk_data, context); |
1242 | 83.4k | if (chunk_type == "iCCP"sv) |
1243 | 450 | return process_iCCP(chunk_data, context); |
1244 | 82.9k | if (chunk_type == "gAMA"sv) |
1245 | 379 | return process_gAMA(chunk_data, context); |
1246 | 82.6k | if (chunk_type == "sRGB"sv) |
1247 | 1.32k | return process_sRGB(chunk_data, context); |
1248 | 81.2k | if (chunk_type == "tRNS"sv) |
1249 | 6.23k | return process_tRNS(chunk_data, context); |
1250 | 75.0k | if (chunk_type == "acTL"sv) |
1251 | 1.35k | return process_acTL(chunk_data, context); |
1252 | 73.6k | if (chunk_type == "fcTL"sv) |
1253 | 1.19k | return process_fcTL(chunk_data, context); |
1254 | 72.5k | if (chunk_type == "fdAT"sv) |
1255 | 383 | return process_fdAT(chunk_data, context); |
1256 | 72.1k | if (chunk_type == "eXIf"sv) |
1257 | 8.31k | return process_eXIf(chunk_data, context); |
1258 | 63.8k | if (chunk_type == "IEND"sv) |
1259 | 11 | process_IEND(chunk_data, context); |
1260 | 63.8k | return {}; |
1261 | 72.1k | } |
1262 | | |
1263 | | PNGImageDecoderPlugin::PNGImageDecoderPlugin(u8 const* data, size_t size) |
1264 | 9.61k | { |
1265 | 9.61k | m_context = make<PNGLoadingContext>(); |
1266 | 9.61k | m_context->data = m_context->data_current_ptr = data; |
1267 | 9.61k | m_context->data_size = size; |
1268 | 9.61k | } |
1269 | | |
1270 | 9.61k | PNGImageDecoderPlugin::~PNGImageDecoderPlugin() = default; |
1271 | | |
1272 | | bool PNGImageDecoderPlugin::ensure_image_data_chunk_was_decoded() |
1273 | 9.10k | { |
1274 | 9.10k | if (m_context->state == PNGLoadingContext::State::Error) |
1275 | 0 | return false; |
1276 | | |
1277 | 9.10k | if (m_context->state < PNGLoadingContext::State::ImageDataChunkDecoded) { |
1278 | 9.10k | if (!decode_png_image_data_chunk(*m_context)) |
1279 | 6.07k | return false; |
1280 | 9.10k | } |
1281 | 3.03k | return true; |
1282 | 9.10k | } |
1283 | | |
1284 | | bool PNGImageDecoderPlugin::ensure_animation_frame_was_decoded(u32 animation_frame_index) |
1285 | 0 | { |
1286 | 0 | if (m_context->state == PNGLoadingContext::State::Error) |
1287 | 0 | return false; |
1288 | | |
1289 | 0 | if (m_context->state < PNGLoadingContext::State::ImageDataChunkDecoded) { |
1290 | 0 | if (!decode_png_image_data_chunk(*m_context)) |
1291 | 0 | return false; |
1292 | 0 | } |
1293 | | |
1294 | 0 | if (m_context->last_completed_animation_frame_index.has_value()) { |
1295 | 0 | if (m_context->last_completed_animation_frame_index.value() >= animation_frame_index) |
1296 | 0 | return true; |
1297 | 0 | } |
1298 | | |
1299 | 0 | return decode_png_animation_data_chunks(*m_context, animation_frame_index); |
1300 | 0 | } |
1301 | | |
1302 | | IntSize PNGImageDecoderPlugin::size() |
1303 | 0 | { |
1304 | 0 | return { m_context->width, m_context->height }; |
1305 | 0 | } |
1306 | | |
1307 | | bool PNGImageDecoderPlugin::sniff(ReadonlyBytes data) |
1308 | 5.89k | { |
1309 | 5.89k | PNGLoadingContext context; |
1310 | 5.89k | context.data = context.data_current_ptr = data.data(); |
1311 | 5.89k | context.data_size = data.size(); |
1312 | 5.89k | return decode_png_header(context); |
1313 | 5.89k | } |
1314 | | |
1315 | | ErrorOr<NonnullOwnPtr<ImageDecoderPlugin>> PNGImageDecoderPlugin::create(ReadonlyBytes data) |
1316 | 9.61k | { |
1317 | 9.61k | auto plugin = TRY(adopt_nonnull_own_or_enomem(new (nothrow) PNGImageDecoderPlugin(data.data(), data.size()))); |
1318 | 9.61k | if (!decode_png_header(*plugin->m_context)) |
1319 | 74 | return Error::from_string_literal("Invalid header for a PNG file"); |
1320 | 9.54k | TRY(decode_png_ihdr(*plugin->m_context)); |
1321 | 0 | return plugin; |
1322 | 9.54k | } |
1323 | | |
1324 | | bool PNGImageDecoderPlugin::is_animated() |
1325 | 0 | { |
1326 | 0 | if (!ensure_image_data_chunk_was_decoded()) |
1327 | 0 | return false; |
1328 | 0 | return m_context->has_seen_actl_chunk_before_idat; |
1329 | 0 | } |
1330 | | |
1331 | | size_t PNGImageDecoderPlugin::loop_count() |
1332 | 0 | { |
1333 | 0 | if (!ensure_image_data_chunk_was_decoded()) |
1334 | 0 | return 0; |
1335 | 0 | return m_context->animation_loop_count; |
1336 | 0 | } |
1337 | | |
1338 | | size_t PNGImageDecoderPlugin::frame_count() |
1339 | 0 | { |
1340 | 0 | if (!ensure_image_data_chunk_was_decoded()) |
1341 | 0 | return 0; |
1342 | | |
1343 | 0 | if (!m_context->has_seen_actl_chunk_before_idat) |
1344 | 0 | return 1; |
1345 | | |
1346 | 0 | auto total_frames = m_context->animation_frame_count; |
1347 | 0 | if (!m_context->is_first_idat_part_of_animation) |
1348 | 0 | total_frames++; |
1349 | 0 | return total_frames; |
1350 | 0 | } |
1351 | | |
1352 | | size_t PNGImageDecoderPlugin::first_animated_frame_index() |
1353 | 0 | { |
1354 | 0 | if (!ensure_image_data_chunk_was_decoded()) |
1355 | 0 | return 0; |
1356 | 0 | if (!m_context->has_seen_actl_chunk_before_idat) |
1357 | 0 | return 0; |
1358 | 0 | return m_context->is_first_idat_part_of_animation ? 0 : 1; |
1359 | 0 | } |
1360 | | |
1361 | | static ErrorOr<RefPtr<Bitmap>> render_animation_frame(AnimationFrame const& prev_animation_frame, AnimationFrame& animation_frame, Bitmap const& decoded_frame_bitmap) |
1362 | 0 | { |
1363 | 0 | auto rendered_bitmap = TRY(prev_animation_frame.bitmap->clone()); |
1364 | 0 | Painter painter(rendered_bitmap); |
1365 | |
|
1366 | 0 | static constexpr Color transparent_black = { 0, 0, 0, 0 }; |
1367 | |
|
1368 | 0 | auto frame_rect = animation_frame.rect(); |
1369 | 0 | switch (prev_animation_frame.fcTL.dispose_op) { |
1370 | 0 | case fcTL_Chunk::DisposeOp::APNG_DISPOSE_OP_NONE: |
1371 | 0 | break; |
1372 | 0 | case fcTL_Chunk::DisposeOp::APNG_DISPOSE_OP_BACKGROUND: |
1373 | 0 | painter.clear_rect(rendered_bitmap->rect(), transparent_black); |
1374 | 0 | break; |
1375 | 0 | case fcTL_Chunk::DisposeOp::APNG_DISPOSE_OP_PREVIOUS: { |
1376 | 0 | painter.blit(frame_rect.location(), *prev_animation_frame.bitmap, frame_rect, 1.0f, false); |
1377 | 0 | break; |
1378 | 0 | } |
1379 | 0 | } |
1380 | 0 | switch (animation_frame.fcTL.blend_op) { |
1381 | 0 | case fcTL_Chunk::BlendOp::APNG_BLEND_OP_SOURCE: |
1382 | 0 | painter.blit(frame_rect.location(), decoded_frame_bitmap, decoded_frame_bitmap.rect(), 1.0f, false); |
1383 | 0 | break; |
1384 | 0 | case fcTL_Chunk::BlendOp::APNG_BLEND_OP_OVER: |
1385 | 0 | painter.blit(frame_rect.location(), decoded_frame_bitmap, decoded_frame_bitmap.rect(), 1.0f, true); |
1386 | 0 | break; |
1387 | 0 | } |
1388 | 0 | return rendered_bitmap; |
1389 | 0 | } |
1390 | | |
1391 | | ErrorOr<ImageFrameDescriptor> PNGImageDecoderPlugin::frame(size_t index, Optional<IntSize>) |
1392 | 9.10k | { |
1393 | 9.10k | if (m_context->state == PNGLoadingContext::State::Error) |
1394 | 0 | return Error::from_string_literal("PNGImageDecoderPlugin: Decoding failed"); |
1395 | | |
1396 | 9.10k | if (!ensure_image_data_chunk_was_decoded()) |
1397 | 6.07k | return Error::from_string_literal("PNGImageDecoderPlugin: Decoding image data chunk"); |
1398 | | |
1399 | 3.03k | auto set_descriptor_duration = [](ImageFrameDescriptor& descriptor, AnimationFrame const& animation_frame) { |
1400 | 18 | descriptor.duration = static_cast<int>(animation_frame.duration_ms()); |
1401 | 18 | if (descriptor.duration < 0) |
1402 | 0 | descriptor.duration = NumericLimits<int>::min(); |
1403 | 18 | }; |
1404 | 3.03k | auto load_default_image = [&]() -> ErrorOr<void> { |
1405 | 3.03k | if (m_context->state < PNGLoadingContext::State::BitmapDecoded) { |
1406 | | // NOTE: This forces the chunk decoding to happen. |
1407 | 3.03k | TRY(decode_png_bitmap(*m_context)); |
1408 | 422 | } |
1409 | | |
1410 | 422 | VERIFY(m_context->bitmap); |
1411 | 422 | return {}; |
1412 | 422 | }; |
1413 | | |
1414 | 3.03k | if (index == 0) { |
1415 | 3.03k | TRY(load_default_image()); |
1416 | | |
1417 | 0 | ImageFrameDescriptor descriptor { m_context->bitmap }; |
1418 | 422 | if (m_context->has_seen_actl_chunk_before_idat && m_context->is_first_idat_part_of_animation) |
1419 | 18 | set_descriptor_duration(descriptor, m_context->animation_frames[0]); |
1420 | 422 | return descriptor; |
1421 | 3.03k | } |
1422 | | |
1423 | 0 | if (!m_context->has_seen_actl_chunk_before_idat) |
1424 | 0 | return Error::from_string_literal("PNGImageDecoderPlugin: Invalid frame index"); |
1425 | | |
1426 | 0 | if (!ensure_animation_frame_was_decoded(index)) |
1427 | 0 | return Error::from_string_literal("PNGImageDecoderPlugin: Decoding image data chunk"); |
1428 | | |
1429 | 0 | if (index >= m_context->animation_frames.size()) |
1430 | 0 | return Error::from_string_literal("PNGImageDecoderPlugin: Invalid animation frame index"); |
1431 | | |
1432 | | // We need to assemble each frame up until the one requested, |
1433 | | // so decode all bitmaps that haven't been decoded yet. |
1434 | 0 | for (size_t i = m_context->animation_next_frame_to_render; i <= index; i++) { |
1435 | 0 | if (i == 0) { |
1436 | | // If the default image hasn't been loaded, load it now |
1437 | 0 | TRY(load_default_image()); // May modify animation_frames! |
1438 | | |
1439 | 0 | auto& animation_frame = m_context->animation_frames[i]; |
1440 | 0 | animation_frame.bitmap = m_context->bitmap; |
1441 | 0 | } else { |
1442 | 0 | auto& animation_frame = m_context->animation_frames[i]; |
1443 | 0 | VERIFY(!animation_frame.bitmap); |
1444 | | |
1445 | 0 | auto decoded_bitmap = TRY(decode_png_animation_frame_bitmap(*m_context, animation_frame)); |
1446 | | |
1447 | 0 | auto prev_animation_frame = m_context->animation_frames[i - 1]; |
1448 | 0 | animation_frame.bitmap = TRY(render_animation_frame(prev_animation_frame, animation_frame, *decoded_bitmap)); |
1449 | 0 | } |
1450 | 0 | m_context->animation_next_frame_to_render = i + 1; |
1451 | 0 | } |
1452 | | |
1453 | 0 | auto const& animation_frame = m_context->animation_frames[index]; |
1454 | 0 | VERIFY(animation_frame.bitmap); |
1455 | | |
1456 | 0 | ImageFrameDescriptor descriptor { animation_frame.bitmap }; |
1457 | 0 | set_descriptor_duration(descriptor, animation_frame); |
1458 | 0 | return descriptor; |
1459 | 0 | } |
1460 | | |
1461 | | Optional<Metadata const&> PNGImageDecoderPlugin::metadata() |
1462 | 0 | { |
1463 | 0 | if (m_context->exif_metadata) |
1464 | 0 | return *m_context->exif_metadata; |
1465 | 0 | return OptionalNone {}; |
1466 | 0 | } |
1467 | | |
1468 | | ErrorOr<Optional<ReadonlyBytes>> PNGImageDecoderPlugin::icc_data() |
1469 | 0 | { |
1470 | 0 | if (!decode_png_chunks(*m_context)) |
1471 | 0 | return Error::from_string_literal("PNGImageDecoderPlugin: Decoding chunks failed"); |
1472 | | |
1473 | 0 | if (m_context->embedded_icc_profile.has_value()) { |
1474 | 0 | if (!m_context->decompressed_icc_profile.has_value()) { |
1475 | 0 | auto compressed_data_stream = make<FixedMemoryStream>(m_context->embedded_icc_profile->compressed_data); |
1476 | 0 | auto decompressor_or_error = Compress::ZlibDecompressor::create(move(compressed_data_stream)); |
1477 | 0 | if (decompressor_or_error.is_error()) { |
1478 | 0 | m_context->embedded_icc_profile.clear(); |
1479 | 0 | return decompressor_or_error.release_error(); |
1480 | 0 | } |
1481 | 0 | auto decompressor = decompressor_or_error.release_value(); |
1482 | 0 | auto result_or_error = decompressor->read_until_eof(); |
1483 | 0 | if (result_or_error.is_error()) { |
1484 | 0 | m_context->embedded_icc_profile.clear(); |
1485 | 0 | return result_or_error.release_error(); |
1486 | 0 | } |
1487 | 0 | m_context->decompressed_icc_profile = result_or_error.release_value(); |
1488 | 0 | } |
1489 | | |
1490 | 0 | return m_context->decompressed_icc_profile.value(); |
1491 | 0 | } |
1492 | | |
1493 | | // FIXME: Eventually, look at coding_independent_code_points, chromaticities_and_whitepoint, gamma, sRGB_rendering_intent too. |
1494 | | // The order is: |
1495 | | // 1. Use coding_independent_code_points if it exists, ignore the rest. |
1496 | | // 2. Use embedded_icc_profile if it exists, ignore the rest. |
1497 | | // 3. Use sRGB_rendering_intent if it exists, ignore the rest. |
1498 | | // 4. Use gamma to adjust gamma and chromaticities_and_whitepoint to adjust color. |
1499 | | // (Order between 2 and 3 isn't fully clear, but "It is recommended that the sRGB and iCCP chunks do not appear simultaneously in a PNG datastream." |
1500 | | |
1501 | 0 | return OptionalNone {}; |
1502 | 0 | } |
1503 | | |
1504 | | } |