Serenity Operating System
1/*
2 * Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
3 * All rights reserved.
4 *
5 * Redistribution and use in source and binary forms, with or without
6 * modification, are permitted provided that the following conditions are met:
7 *
8 * 1. Redistributions of source code must retain the above copyright notice, this
9 * list of conditions and the following disclaimer.
10 *
11 * 2. Redistributions in binary form must reproduce the above copyright notice,
12 * this list of conditions and the following disclaimer in the documentation
13 * and/or other materials provided with the distribution.
14 *
15 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
16 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
17 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
18 * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
19 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
20 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
21 * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
22 * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
23 * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
24 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
25 */
26
27#include <AK/ByteBuffer.h>
28#include <AK/FileSystemPath.h>
29#include <AK/MappedFile.h>
30#include <AK/NetworkOrdered.h>
31#include <LibCore/puff.h>
32#include <LibGfx/PNGLoader.h>
33#include <fcntl.h>
34#include <serenity.h>
35#include <stdio.h>
36#include <string.h>
37#include <sys/mman.h>
38#include <sys/stat.h>
39#include <unistd.h>
40
41namespace Gfx {
42
43static const u8 png_header[8] = { 0x89, 'P', 'N', 'G', 13, 10, 26, 10 };
44
45struct PNG_IHDR {
46 NetworkOrdered<u32> width;
47 NetworkOrdered<u32> height;
48 u8 bit_depth { 0 };
49 u8 color_type { 0 };
50 u8 compression_method { 0 };
51 u8 filter_method { 0 };
52 u8 interlace_method { 0 };
53};
54
55static_assert(sizeof(PNG_IHDR) == 13);
56
57struct Scanline {
58 u8 filter { 0 };
59 ByteBuffer data {};
60};
61
62struct [[gnu::packed]] PaletteEntry
63{
64 u8 r;
65 u8 g;
66 u8 b;
67 //u8 a;
68};
69
70struct [[gnu::packed]] Triplet
71{
72 u8 r;
73 u8 g;
74 u8 b;
75};
76
77struct [[gnu::packed]] Triplet16
78{
79 u16 r;
80 u16 g;
81 u16 b;
82};
83
84struct [[gnu::packed]] Quad16
85{
86 u16 r;
87 u16 g;
88 u16 b;
89 u16 a;
90};
91
92struct PNGLoadingContext {
93 enum State {
94 NotDecoded = 0,
95 Error,
96 HeaderDecoded,
97 SizeDecoded,
98 ChunksDecoded,
99 BitmapDecoded,
100 };
101 State state { State::NotDecoded };
102 const u8* data { nullptr };
103 size_t data_size { 0 };
104 int width { -1 };
105 int height { -1 };
106 u8 bit_depth { 0 };
107 u8 color_type { 0 };
108 u8 compression_method { 0 };
109 u8 filter_method { 0 };
110 u8 interlace_method { 0 };
111 u8 bytes_per_pixel { 0 };
112 bool has_seen_zlib_header { false };
113 bool has_alpha() const { return color_type & 4 || palette_transparency_data.size() > 0; }
114 Vector<Scanline> scanlines;
115 RefPtr<Gfx::Bitmap> bitmap;
116 u8* decompression_buffer { nullptr };
117 int decompression_buffer_size { 0 };
118 Vector<u8> compressed_data;
119 Vector<PaletteEntry> palette_data;
120 Vector<u8> palette_transparency_data;
121};
122
123class Streamer {
124public:
125 Streamer(const u8* data, int size)
126 : m_original_data(data)
127 , m_original_size(size)
128 , m_data_ptr(data)
129 , m_size_remaining(size)
130 {
131 }
132
133 template<typename T>
134 bool read(T& value)
135 {
136 if (m_size_remaining < (int)sizeof(T))
137 return false;
138 value = *((const NetworkOrdered<T>*)m_data_ptr);
139 m_data_ptr += sizeof(T);
140 m_size_remaining -= sizeof(T);
141 return true;
142 }
143
144 bool read_bytes(u8* buffer, int count)
145 {
146 if (m_size_remaining < count)
147 return false;
148 memcpy(buffer, m_data_ptr, count);
149 m_data_ptr += count;
150 m_size_remaining -= count;
151 return true;
152 }
153
154 bool wrap_bytes(ByteBuffer& buffer, int count)
155 {
156 if (m_size_remaining < count)
157 return false;
158 buffer = ByteBuffer::wrap(m_data_ptr, count);
159 m_data_ptr += count;
160 m_size_remaining -= count;
161 return true;
162 }
163
164 bool at_end() const { return !m_size_remaining; }
165
166private:
167 const u8* m_original_data;
168 int m_original_size;
169 const u8* m_data_ptr;
170 int m_size_remaining;
171};
172
173static RefPtr<Gfx::Bitmap> load_png_impl(const u8*, int);
174static bool process_chunk(Streamer&, PNGLoadingContext& context, bool decode_size_only);
175
176RefPtr<Gfx::Bitmap> load_png(const StringView& path)
177{
178 MappedFile mapped_file(path);
179 if (!mapped_file.is_valid())
180 return nullptr;
181 auto bitmap = load_png_impl((const u8*)mapped_file.data(), mapped_file.size());
182 if (bitmap)
183 bitmap->set_mmap_name(String::format("Gfx::Bitmap [%dx%d] - Decoded PNG: %s", bitmap->width(), bitmap->height(), canonicalized_path(path).characters()));
184 return bitmap;
185}
186
187RefPtr<Gfx::Bitmap> load_png_from_memory(const u8* data, size_t length)
188{
189 auto bitmap = load_png_impl(data, length);
190 if (bitmap)
191 bitmap->set_mmap_name(String::format("Gfx::Bitmap [%dx%d] - Decoded PNG: <memory>", bitmap->width(), bitmap->height()));
192 return bitmap;
193}
194
195[[gnu::always_inline]] static inline u8 paeth_predictor(int a, int b, int c)
196{
197 int p = a + b - c;
198 int pa = abs(p - a);
199 int pb = abs(p - b);
200 int pc = abs(p - c);
201 if (pa <= pb && pa <= pc)
202 return a;
203 if (pb <= pc)
204 return b;
205 return c;
206}
207
208union [[gnu::packed]] Pixel
209{
210 RGBA32 rgba { 0 };
211 u8 v[4];
212 struct {
213 u8 r;
214 u8 g;
215 u8 b;
216 u8 a;
217 };
218};
219static_assert(sizeof(Pixel) == 4);
220
221template<bool has_alpha, u8 filter_type>
222[[gnu::always_inline]] static inline void unfilter_impl(Gfx::Bitmap& bitmap, int y, const void* dummy_scanline_data)
223{
224 auto* dummy_scanline = (const Pixel*)dummy_scanline_data;
225 if constexpr (filter_type == 0) {
226 auto* pixels = (Pixel*)bitmap.scanline(y);
227 for (int i = 0; i < bitmap.width(); ++i) {
228 auto& x = pixels[i];
229 swap(x.r, x.b);
230 }
231 }
232
233 if constexpr (filter_type == 1) {
234 auto* pixels = (Pixel*)bitmap.scanline(y);
235 swap(pixels[0].r, pixels[0].b);
236 for (int i = 1; i < bitmap.width(); ++i) {
237 auto& x = pixels[i];
238 swap(x.r, x.b);
239 auto& a = (const Pixel&)pixels[i - 1];
240 x.v[0] += a.v[0];
241 x.v[1] += a.v[1];
242 x.v[2] += a.v[2];
243 if constexpr (has_alpha)
244 x.v[3] += a.v[3];
245 }
246 return;
247 }
248 if constexpr (filter_type == 2) {
249 auto* pixels = (Pixel*)bitmap.scanline(y);
250 auto* pixels_y_minus_1 = y == 0 ? dummy_scanline : (Pixel*)bitmap.scanline(y - 1);
251 for (int i = 0; i < bitmap.width(); ++i) {
252 auto& x = pixels[i];
253 swap(x.r, x.b);
254 const Pixel& b = pixels_y_minus_1[i];
255 x.v[0] += b.v[0];
256 x.v[1] += b.v[1];
257 x.v[2] += b.v[2];
258 if constexpr (has_alpha)
259 x.v[3] += b.v[3];
260 }
261 return;
262 }
263 if constexpr (filter_type == 3) {
264 auto* pixels = (Pixel*)bitmap.scanline(y);
265 auto* pixels_y_minus_1 = y == 0 ? dummy_scanline : (Pixel*)bitmap.scanline(y - 1);
266 for (int i = 0; i < bitmap.width(); ++i) {
267 auto& x = pixels[i];
268 swap(x.r, x.b);
269 Pixel a;
270 if (i != 0)
271 a = pixels[i - 1];
272 const Pixel& b = pixels_y_minus_1[i];
273 x.v[0] = x.v[0] + ((a.v[0] + b.v[0]) / 2);
274 x.v[1] = x.v[1] + ((a.v[1] + b.v[1]) / 2);
275 x.v[2] = x.v[2] + ((a.v[2] + b.v[2]) / 2);
276 if constexpr (has_alpha)
277 x.v[3] = x.v[3] + ((a.v[3] + b.v[3]) / 2);
278 }
279 return;
280 }
281 if constexpr (filter_type == 4) {
282 auto* pixels = (Pixel*)bitmap.scanline(y);
283 auto* pixels_y_minus_1 = y == 0 ? dummy_scanline : (Pixel*)bitmap.scanline(y - 1);
284 for (int i = 0; i < bitmap.width(); ++i) {
285 auto& x = pixels[i];
286 swap(x.r, x.b);
287 Pixel a;
288 const Pixel& b = pixels_y_minus_1[i];
289 Pixel c;
290 if (i != 0) {
291 a = pixels[i - 1];
292 c = pixels_y_minus_1[i - 1];
293 }
294 x.v[0] += paeth_predictor(a.v[0], b.v[0], c.v[0]);
295 x.v[1] += paeth_predictor(a.v[1], b.v[1], c.v[1]);
296 x.v[2] += paeth_predictor(a.v[2], b.v[2], c.v[2]);
297 if constexpr (has_alpha)
298 x.v[3] += paeth_predictor(a.v[3], b.v[3], c.v[3]);
299 }
300 }
301}
302
303[[gnu::noinline]] static void unfilter(PNGLoadingContext& context)
304{
305 // First unpack the scanlines to RGBA:
306 switch (context.color_type) {
307 case 2:
308 if (context.bit_depth == 8) {
309 for (int y = 0; y < context.height; ++y) {
310 auto* triplets = (Triplet*)context.scanlines[y].data.data();
311 for (int i = 0; i < context.width; ++i) {
312 auto& pixel = (Pixel&)context.bitmap->scanline(y)[i];
313 pixel.r = triplets[i].r;
314 pixel.g = triplets[i].g;
315 pixel.b = triplets[i].b;
316 pixel.a = 0xff;
317 }
318 }
319 } else if (context.bit_depth == 16) {
320 for (int y = 0; y < context.height; ++y) {
321 auto* triplets = (Triplet16*)context.scanlines[y].data.data();
322 for (int i = 0; i < context.width; ++i) {
323 auto& pixel = (Pixel&)context.bitmap->scanline(y)[i];
324 pixel.r = triplets[i].r & 0xFF;
325 pixel.g = triplets[i].g & 0xFF;
326 pixel.b = triplets[i].b & 0xFF;
327 pixel.a = 0xff;
328 }
329 }
330 } else {
331 ASSERT_NOT_REACHED();
332 }
333 break;
334 case 6:
335 if (context.bit_depth == 8) {
336 for (int y = 0; y < context.height; ++y) {
337 memcpy(context.bitmap->scanline(y), context.scanlines[y].data.data(), context.scanlines[y].data.size());
338 }
339 } else if (context.bit_depth == 16) {
340 for (int y = 0; y < context.height; ++y) {
341 auto* triplets = (Quad16*)context.scanlines[y].data.data();
342 for (int i = 0; i < context.width; ++i) {
343 auto& pixel = (Pixel&)context.bitmap->scanline(y)[i];
344 pixel.r = triplets[i].r & 0xFF;
345 pixel.g = triplets[i].g & 0xFF;
346 pixel.b = triplets[i].b & 0xFF;
347 pixel.a = triplets[i].a & 0xFF;
348 }
349 }
350 } else {
351 ASSERT_NOT_REACHED();
352 }
353 break;
354 case 3:
355 for (int y = 0; y < context.height; ++y) {
356 auto* palette_index = (u8*)context.scanlines[y].data.data();
357 for (int i = 0; i < context.width; ++i) {
358 auto& pixel = (Pixel&)context.bitmap->scanline(y)[i];
359 auto& color = context.palette_data.at((int)palette_index[i]);
360 auto transparency = context.palette_transparency_data.size() >= palette_index[i] + 1u
361 ? context.palette_transparency_data.data()[palette_index[i]]
362 : 0xff;
363 pixel.r = color.r;
364 pixel.g = color.g;
365 pixel.b = color.b;
366 pixel.a = transparency;
367 }
368 }
369 break;
370 default:
371 ASSERT_NOT_REACHED();
372 break;
373 }
374
375 auto dummy_scanline = ByteBuffer::create_zeroed(context.width * sizeof(RGBA32));
376
377 for (int y = 0; y < context.height; ++y) {
378 auto filter = context.scanlines[y].filter;
379 if (filter == 0) {
380 if (context.has_alpha())
381 unfilter_impl<true, 0>(*context.bitmap, y, dummy_scanline.data());
382 else
383 unfilter_impl<false, 0>(*context.bitmap, y, dummy_scanline.data());
384 continue;
385 }
386 if (filter == 1) {
387 if (context.has_alpha())
388 unfilter_impl<true, 1>(*context.bitmap, y, dummy_scanline.data());
389 else
390 unfilter_impl<false, 1>(*context.bitmap, y, dummy_scanline.data());
391 continue;
392 }
393 if (filter == 2) {
394 if (context.has_alpha())
395 unfilter_impl<true, 2>(*context.bitmap, y, dummy_scanline.data());
396 else
397 unfilter_impl<false, 2>(*context.bitmap, y, dummy_scanline.data());
398 continue;
399 }
400 if (filter == 3) {
401 if (context.has_alpha())
402 unfilter_impl<true, 3>(*context.bitmap, y, dummy_scanline.data());
403 else
404 unfilter_impl<false, 3>(*context.bitmap, y, dummy_scanline.data());
405 continue;
406 }
407 if (filter == 4) {
408 if (context.has_alpha())
409 unfilter_impl<true, 4>(*context.bitmap, y, dummy_scanline.data());
410 else
411 unfilter_impl<false, 4>(*context.bitmap, y, dummy_scanline.data());
412 continue;
413 }
414 }
415}
416
417static bool decode_png_header(PNGLoadingContext& context)
418{
419 if (context.state >= PNGLoadingContext::HeaderDecoded)
420 return true;
421
422 if (memcmp(context.data, png_header, sizeof(png_header)) != 0) {
423 dbg() << "Invalid PNG header";
424 context.state = PNGLoadingContext::State::Error;
425 return false;
426 }
427
428 context.state = PNGLoadingContext::HeaderDecoded;
429 return true;
430}
431
432static bool decode_png_size(PNGLoadingContext& context)
433{
434 if (context.state >= PNGLoadingContext::SizeDecoded)
435 return true;
436
437 if (context.state < PNGLoadingContext::HeaderDecoded) {
438 if (!decode_png_header(context))
439 return false;
440 }
441
442 const u8* data_ptr = context.data + sizeof(png_header);
443 size_t data_remaining = context.data_size - sizeof(png_header);
444
445 Streamer streamer(data_ptr, data_remaining);
446 while (!streamer.at_end()) {
447 if (!process_chunk(streamer, context, true)) {
448 context.state = PNGLoadingContext::State::Error;
449 return false;
450 }
451 if (context.width && context.height) {
452 context.state = PNGLoadingContext::State::SizeDecoded;
453 return true;
454 }
455 }
456
457 return false;
458}
459
460static bool decode_png_chunks(PNGLoadingContext& context)
461{
462 if (context.state >= PNGLoadingContext::State::ChunksDecoded)
463 return true;
464
465 if (context.state < PNGLoadingContext::HeaderDecoded) {
466 if (!decode_png_header(context))
467 return false;
468 }
469
470 const u8* data_ptr = context.data + sizeof(png_header);
471 int data_remaining = context.data_size - sizeof(png_header);
472
473 context.compressed_data.ensure_capacity(context.data_size);
474
475 Streamer streamer(data_ptr, data_remaining);
476 while (!streamer.at_end()) {
477 if (!process_chunk(streamer, context, false)) {
478 context.state = PNGLoadingContext::State::Error;
479 return false;
480 }
481 }
482
483 context.state = PNGLoadingContext::State::ChunksDecoded;
484 return true;
485}
486
487static bool decode_png_bitmap(PNGLoadingContext& context)
488{
489 if (context.state < PNGLoadingContext::State::ChunksDecoded) {
490 if (!decode_png_chunks(context))
491 return false;
492 }
493
494 if (context.state >= PNGLoadingContext::State::BitmapDecoded)
495 return true;
496
497 unsigned long srclen = context.compressed_data.size() - 6;
498 unsigned long destlen = context.decompression_buffer_size;
499 int ret = puff(context.decompression_buffer, &destlen, context.compressed_data.data() + 2, &srclen);
500 if (ret < 0) {
501 context.state = PNGLoadingContext::State::Error;
502 return false;
503 }
504 context.compressed_data.clear();
505
506 context.scanlines.ensure_capacity(context.height);
507 Streamer streamer(context.decompression_buffer, context.decompression_buffer_size);
508 for (int y = 0; y < context.height; ++y) {
509 u8 filter;
510 if (!streamer.read(filter)) {
511 context.state = PNGLoadingContext::State::Error;
512 return false;
513 }
514
515 context.scanlines.append({ filter });
516 auto& scanline_buffer = context.scanlines.last().data;
517 if (!streamer.wrap_bytes(scanline_buffer, context.width * context.bytes_per_pixel)) {
518 context.state = PNGLoadingContext::State::Error;
519 return false;
520 }
521 }
522
523 context.bitmap = Bitmap::create_purgeable(context.has_alpha() ? BitmapFormat::RGBA32 : BitmapFormat::RGB32, { context.width, context.height });
524
525 unfilter(context);
526
527 munmap(context.decompression_buffer, context.decompression_buffer_size);
528 context.decompression_buffer = nullptr;
529 context.decompression_buffer_size = 0;
530
531 context.state = PNGLoadingContext::State::BitmapDecoded;
532 return true;
533}
534
535static RefPtr<Gfx::Bitmap> load_png_impl(const u8* data, int data_size)
536{
537 PNGLoadingContext context;
538 context.data = data;
539 context.data_size = data_size;
540
541 if (!decode_png_chunks(context))
542 return nullptr;
543
544 if (!decode_png_bitmap(context))
545 return nullptr;
546
547 return context.bitmap;
548}
549
550static bool process_IHDR(const ByteBuffer& data, PNGLoadingContext& context, bool decode_size_only = false)
551{
552 if (data.size() < (int)sizeof(PNG_IHDR))
553 return false;
554 auto& ihdr = *(const PNG_IHDR*)data.data();
555 context.width = ihdr.width;
556 context.height = ihdr.height;
557 context.bit_depth = ihdr.bit_depth;
558 context.color_type = ihdr.color_type;
559 context.compression_method = ihdr.compression_method;
560 context.filter_method = ihdr.filter_method;
561 context.interlace_method = ihdr.interlace_method;
562
563#ifdef PNG_DEBUG
564 printf("PNG: %dx%d (%d bpp)\n", context.width, context.height, context.bit_depth);
565 printf(" Color type: %d\n", context.color_type);
566 printf("Compress Method: %d\n", context.compression_method);
567 printf(" Filter Method: %d\n", context.filter_method);
568 printf(" Interlace type: %d\n", context.interlace_method);
569#endif
570
571 // FIXME: Implement Adam7 deinterlacing
572 if (context.interlace_method != 0) {
573 dbgprintf("PNGLoader::process_IHDR: Interlaced PNGs not currently supported.\n");
574 return false;
575 }
576
577 switch (context.color_type) {
578 case 0: // Each pixel is a grayscale sample.
579 case 4: // Each pixel is a grayscale sample, followed by an alpha sample.
580 // FIXME: Implement grayscale PNG support.
581 dbgprintf("PNGLoader::process_IHDR: Unsupported grayscale format.\n");
582 return false;
583 case 2:
584 context.bytes_per_pixel = 3 * (ihdr.bit_depth / 8);
585 break;
586 case 3: // Each pixel is a palette index; a PLTE chunk must appear.
587 // FIXME: Implement support for 1/2/4 bit palette based images.
588 if (ihdr.bit_depth != 8) {
589 dbgprintf("PNGLoader::process_IHDR: Unsupported index-based format (%d bpp).\n", context.bit_depth);
590 return false;
591 }
592 context.bytes_per_pixel = 1;
593 break;
594 case 6:
595 context.bytes_per_pixel = 4 * (ihdr.bit_depth / 8);
596 break;
597 default:
598 ASSERT_NOT_REACHED();
599 }
600
601 if (!decode_size_only) {
602 context.decompression_buffer_size = (context.width * context.height * context.bytes_per_pixel + context.height);
603 context.decompression_buffer = (u8*)mmap_with_name(nullptr, context.decompression_buffer_size, PROT_READ | PROT_WRITE, MAP_ANONYMOUS | MAP_PRIVATE, 0, 0, "PNG decompression buffer");
604 }
605 return true;
606}
607
608static bool process_IDAT(const ByteBuffer& data, PNGLoadingContext& context)
609{
610 context.compressed_data.append(data.data(), data.size());
611 return true;
612}
613
614static bool process_PLTE(const ByteBuffer& data, PNGLoadingContext& context)
615{
616 context.palette_data.append((const PaletteEntry*)data.data(), data.size() / 3);
617 return true;
618}
619
620static bool process_tRNS(const ByteBuffer& data, PNGLoadingContext& context)
621{
622 switch (context.color_type) {
623 case 3:
624 context.palette_transparency_data.append(data.data(), data.size());
625 break;
626 }
627 return true;
628}
629
630static bool process_chunk(Streamer& streamer, PNGLoadingContext& context, bool decode_size_only)
631{
632 u32 chunk_size;
633 if (!streamer.read(chunk_size)) {
634 printf("Bail at chunk_size\n");
635 return false;
636 }
637 u8 chunk_type[5];
638 chunk_type[4] = '\0';
639 if (!streamer.read_bytes(chunk_type, 4)) {
640 printf("Bail at chunk_type\n");
641 return false;
642 }
643 ByteBuffer chunk_data;
644 if (!streamer.wrap_bytes(chunk_data, chunk_size)) {
645 printf("Bail at chunk_data\n");
646 return false;
647 }
648 u32 chunk_crc;
649 if (!streamer.read(chunk_crc)) {
650 printf("Bail at chunk_crc\n");
651 return false;
652 }
653#ifdef PNG_DEBUG
654 printf("Chunk type: '%s', size: %u, crc: %x\n", chunk_type, chunk_size, chunk_crc);
655#endif
656
657 if (!strcmp((const char*)chunk_type, "IHDR"))
658 return process_IHDR(chunk_data, context, decode_size_only);
659 if (!strcmp((const char*)chunk_type, "IDAT"))
660 return process_IDAT(chunk_data, context);
661 if (!strcmp((const char*)chunk_type, "PLTE"))
662 return process_PLTE(chunk_data, context);
663 if (!strcmp((const char*)chunk_type, "tRNS"))
664 return process_tRNS(chunk_data, context);
665 return true;
666}
667
668PNGImageDecoderPlugin::PNGImageDecoderPlugin(const u8* data, size_t size)
669{
670 m_context = make<PNGLoadingContext>();
671 m_context->data = data;
672 m_context->data_size = size;
673}
674
675PNGImageDecoderPlugin::~PNGImageDecoderPlugin()
676{
677}
678
679Size PNGImageDecoderPlugin::size()
680{
681 if (m_context->state == PNGLoadingContext::State::Error)
682 return {};
683
684 if (m_context->state < PNGLoadingContext::State::SizeDecoded) {
685 bool success = decode_png_size(*m_context);
686 if (!success)
687 return {};
688 }
689
690 return { m_context->width, m_context->height };
691}
692
693RefPtr<Gfx::Bitmap> PNGImageDecoderPlugin::bitmap()
694{
695 if (m_context->state == PNGLoadingContext::State::Error)
696 return nullptr;
697
698 if (m_context->state < PNGLoadingContext::State::BitmapDecoded) {
699 // NOTE: This forces the chunk decoding to happen.
700 bool success = decode_png_bitmap(*m_context);
701 if (!success)
702 return nullptr;
703 }
704
705 ASSERT(m_context->bitmap);
706 return m_context->bitmap;
707}
708
709void PNGImageDecoderPlugin::set_volatile()
710{
711 if (m_context->bitmap)
712 m_context->bitmap->set_volatile();
713}
714
715bool PNGImageDecoderPlugin::set_nonvolatile()
716{
717 if (!m_context->bitmap)
718 return false;
719 return m_context->bitmap->set_nonvolatile();
720}
721
722}