| 
									
										
										
										
											2021-01-22 11:46:09 +01:00
										 |  |  | /*
 | 
					
						
							|  |  |  |  * Copyright (c) 2021, Pierre Hoffmeister | 
					
						
							| 
									
										
										
										
											2021-04-19 23:43:04 +02:00
										 |  |  |  * Copyright (c) 2021, Andreas Kling <kling@serenityos.org> | 
					
						
							| 
									
										
										
										
											2021-01-22 11:46:09 +01:00
										 |  |  |  * | 
					
						
							| 
									
										
										
										
											2021-04-22 01:24:48 -07:00
										 |  |  |  * SPDX-License-Identifier: BSD-2-Clause | 
					
						
							| 
									
										
										
										
											2021-01-22 11:46:09 +01:00
										 |  |  |  */ | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-07-11 18:39:17 +02:00
										 |  |  | #include <AK/Concepts.h>
 | 
					
						
							| 
									
										
										
										
											2021-01-22 11:46:09 +01:00
										 |  |  | #include <AK/String.h>
 | 
					
						
							|  |  |  | #include <LibCrypto/Checksum/CRC32.h>
 | 
					
						
							| 
									
										
										
										
											2021-04-19 23:43:04 +02:00
										 |  |  | #include <LibGfx/Bitmap.h>
 | 
					
						
							|  |  |  | #include <LibGfx/PNGWriter.h>
 | 
					
						
							| 
									
										
										
										
											2021-01-22 11:46:09 +01:00
										 |  |  | 
 | 
					
						
							|  |  |  | namespace Gfx { | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | class PNGChunk { | 
					
						
							|  |  |  | public: | 
					
						
							| 
									
										
										
										
											2021-04-19 23:43:04 +02:00
										 |  |  |     explicit PNGChunk(String); | 
					
						
							| 
									
										
										
										
											2021-07-11 18:39:17 +02:00
										 |  |  |     auto const& data() const { return m_data; }; | 
					
						
							| 
									
										
										
										
											2021-04-19 23:43:04 +02:00
										 |  |  |     String const& type() const { return m_type; }; | 
					
						
							| 
									
										
										
										
											2021-07-11 18:50:45 +02:00
										 |  |  |     void reserve(size_t bytes) { m_data.ensure_capacity(bytes); } | 
					
						
							| 
									
										
										
										
											2021-07-11 18:39:17 +02:00
										 |  |  | 
 | 
					
						
							|  |  |  |     template<typename T> | 
					
						
							|  |  |  |     void add_as_big_endian(T); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     template<typename T> | 
					
						
							|  |  |  |     void add_as_little_endian(T); | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-01-22 11:46:09 +01:00
										 |  |  |     void add_u8(u8); | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-07-11 19:00:32 +02:00
										 |  |  |     void store_type(); | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-01-22 11:46:09 +01:00
										 |  |  | private: | 
					
						
							| 
									
										
										
										
											2021-07-11 18:39:17 +02:00
										 |  |  |     template<typename T> | 
					
						
							|  |  |  |     requires(IsUnsigned<T>) void add(T); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     ByteBuffer m_data; | 
					
						
							| 
									
										
										
										
											2021-01-22 11:46:09 +01:00
										 |  |  |     String m_type; | 
					
						
							|  |  |  | }; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | class NonCompressibleBlock { | 
					
						
							|  |  |  | public: | 
					
						
							|  |  |  |     void finalize(PNGChunk&); | 
					
						
							|  |  |  |     void add_byte_to_block(u8 data, PNGChunk&); | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-04-19 23:43:04 +02:00
										 |  |  |     u32 adler_s1() const { return m_adler_s1; } | 
					
						
							|  |  |  |     u32 adler_s2() const { return m_adler_s2; } | 
					
						
							| 
									
										
										
										
											2021-01-22 11:46:09 +01:00
										 |  |  | 
 | 
					
						
							|  |  |  | private: | 
					
						
							|  |  |  |     void add_block_to_chunk(PNGChunk&, bool); | 
					
						
							|  |  |  |     void update_adler(u8); | 
					
						
							|  |  |  |     bool full() { return m_non_compressible_data.size() == 65535; } | 
					
						
							|  |  |  |     Vector<u8> m_non_compressible_data; | 
					
						
							| 
									
										
										
										
											2021-07-11 18:39:17 +02:00
										 |  |  |     u16 m_adler_s1 { 1 }; | 
					
						
							|  |  |  |     u16 m_adler_s2 { 0 }; | 
					
						
							| 
									
										
										
										
											2021-01-22 11:46:09 +01:00
										 |  |  | }; | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-04-19 23:43:04 +02:00
										 |  |  | PNGChunk::PNGChunk(String type) | 
					
						
							| 
									
										
										
										
											2021-01-22 11:46:09 +01:00
										 |  |  |     : m_type(move(type)) | 
					
						
							|  |  |  | { | 
					
						
							| 
									
										
										
										
											2021-07-11 19:00:32 +02:00
										 |  |  |     store_type(); | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | void PNGChunk::store_type() | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  |     for (auto character : type()) { | 
					
						
							|  |  |  |         m_data.append(&character, sizeof(character)); | 
					
						
							|  |  |  |     } | 
					
						
							| 
									
										
										
										
											2021-01-22 11:46:09 +01:00
										 |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-07-11 18:39:17 +02:00
										 |  |  | template<typename T> | 
					
						
							|  |  |  | requires(IsUnsigned<T>) void PNGChunk::add(T data) | 
					
						
							| 
									
										
										
										
											2021-01-22 11:46:09 +01:00
										 |  |  | { | 
					
						
							| 
									
										
										
										
											2021-07-11 18:39:17 +02:00
										 |  |  |     m_data.append(&data, sizeof(T)); | 
					
						
							| 
									
										
										
										
											2021-01-22 11:46:09 +01:00
										 |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-07-11 18:39:17 +02:00
										 |  |  | template<typename T> | 
					
						
							|  |  |  | void PNGChunk::add_as_little_endian(T data) | 
					
						
							| 
									
										
										
										
											2021-01-22 11:46:09 +01:00
										 |  |  | { | 
					
						
							| 
									
										
										
										
											2021-07-11 18:39:17 +02:00
										 |  |  |     auto data_out = AK::convert_between_host_and_little_endian(data); | 
					
						
							|  |  |  |     add(data_out); | 
					
						
							| 
									
										
										
										
											2021-01-22 11:46:09 +01:00
										 |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-07-11 18:39:17 +02:00
										 |  |  | template<typename T> | 
					
						
							|  |  |  | void PNGChunk::add_as_big_endian(T data) | 
					
						
							| 
									
										
										
										
											2021-01-22 11:46:09 +01:00
										 |  |  | { | 
					
						
							| 
									
										
										
										
											2021-07-11 18:39:17 +02:00
										 |  |  |     auto data_out = AK::convert_between_host_and_big_endian(data); | 
					
						
							|  |  |  |     add(data_out); | 
					
						
							| 
									
										
										
										
											2021-01-22 11:46:09 +01:00
										 |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-07-11 18:39:17 +02:00
										 |  |  | void PNGChunk::add_u8(u8 data) | 
					
						
							| 
									
										
										
										
											2021-01-22 11:46:09 +01:00
										 |  |  | { | 
					
						
							| 
									
										
										
										
											2021-07-11 18:39:17 +02:00
										 |  |  |     add(data); | 
					
						
							| 
									
										
										
										
											2021-01-22 11:46:09 +01:00
										 |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | void NonCompressibleBlock::add_byte_to_block(u8 data, PNGChunk& chunk) | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  |     m_non_compressible_data.append(data); | 
					
						
							|  |  |  |     update_adler(data); | 
					
						
							|  |  |  |     if (full()) { | 
					
						
							|  |  |  |         add_block_to_chunk(chunk, false); | 
					
						
							|  |  |  |         m_non_compressible_data.clear(); | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | void NonCompressibleBlock::add_block_to_chunk(PNGChunk& png_chunk, bool last) | 
					
						
							|  |  |  | { | 
					
						
							| 
									
										
										
										
											2021-07-11 18:39:17 +02:00
										 |  |  |     png_chunk.add_u8(last); | 
					
						
							| 
									
										
										
										
											2021-01-22 11:46:09 +01:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-07-11 18:39:17 +02:00
										 |  |  |     u16 len = m_non_compressible_data.size(); | 
					
						
							|  |  |  |     u16 nlen = ~len; | 
					
						
							| 
									
										
										
										
											2021-01-22 11:46:09 +01:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-07-11 18:39:17 +02:00
										 |  |  |     png_chunk.add_as_little_endian(len); | 
					
						
							|  |  |  |     png_chunk.add_as_little_endian(nlen); | 
					
						
							| 
									
										
										
										
											2021-01-22 11:46:09 +01:00
										 |  |  | 
 | 
					
						
							|  |  |  |     for (auto non_compressed_byte : m_non_compressible_data) { | 
					
						
							|  |  |  |         png_chunk.add_u8(non_compressed_byte); | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | void NonCompressibleBlock::finalize(PNGChunk& chunk) | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  |     add_block_to_chunk(chunk, true); | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | void NonCompressibleBlock::update_adler(u8 data) | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  |     m_adler_s1 = (m_adler_s1 + data) % 65521; | 
					
						
							|  |  |  |     m_adler_s2 = (m_adler_s2 + m_adler_s1) % 65521; | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-04-19 23:43:04 +02:00
										 |  |  | void PNGWriter::add_chunk(PNGChunk const& png_chunk) | 
					
						
							| 
									
										
										
										
											2021-01-22 11:46:09 +01:00
										 |  |  | { | 
					
						
							| 
									
										
										
										
											2021-07-11 19:00:32 +02:00
										 |  |  |     auto crc = BigEndian(Crypto::Checksum::CRC32({ (const u8*)png_chunk.data().data(), png_chunk.data().size() }).digest()); | 
					
						
							|  |  |  |     auto data_len = BigEndian(png_chunk.data().size() - png_chunk.type().length()); | 
					
						
							| 
									
										
										
										
											2021-01-22 11:46:09 +01:00
										 |  |  | 
 | 
					
						
							|  |  |  |     ByteBuffer buf; | 
					
						
							|  |  |  |     buf.append(&data_len, sizeof(u32)); | 
					
						
							| 
									
										
										
										
											2021-07-11 19:00:32 +02:00
										 |  |  |     buf.append(png_chunk.data().data(), png_chunk.data().size()); | 
					
						
							| 
									
										
										
										
											2021-01-22 11:46:09 +01:00
										 |  |  |     buf.append(&crc, sizeof(u32)); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     m_data.append(buf.data(), buf.size()); | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | void PNGWriter::add_png_header() | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  |     const u8 png_header[8] = { 0x89, 'P', 'N', 'G', 13, 10, 26, 10 }; | 
					
						
							|  |  |  |     m_data.append(png_header, sizeof(png_header)); | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | void PNGWriter::add_IHDR_chunk(u32 width, u32 height, u8 bit_depth, u8 color_type, u8 compression_method, u8 filter_method, u8 interlace_method) | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  |     PNGChunk png_chunk { "IHDR" }; | 
					
						
							| 
									
										
										
										
											2021-07-11 18:39:17 +02:00
										 |  |  |     png_chunk.add_as_big_endian(width); | 
					
						
							|  |  |  |     png_chunk.add_as_big_endian(height); | 
					
						
							| 
									
										
										
										
											2021-01-22 11:46:09 +01:00
										 |  |  |     png_chunk.add_u8(bit_depth); | 
					
						
							|  |  |  |     png_chunk.add_u8(color_type); | 
					
						
							|  |  |  |     png_chunk.add_u8(compression_method); | 
					
						
							|  |  |  |     png_chunk.add_u8(filter_method); | 
					
						
							|  |  |  |     png_chunk.add_u8(interlace_method); | 
					
						
							|  |  |  |     add_chunk(png_chunk); | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | void PNGWriter::add_IEND_chunk() | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  |     PNGChunk png_chunk { "IEND" }; | 
					
						
							|  |  |  |     add_chunk(png_chunk); | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-04-19 23:43:04 +02:00
										 |  |  | void PNGWriter::add_IDAT_chunk(Gfx::Bitmap const& bitmap) | 
					
						
							| 
									
										
										
										
											2021-01-22 11:46:09 +01:00
										 |  |  | { | 
					
						
							|  |  |  |     PNGChunk png_chunk { "IDAT" }; | 
					
						
							| 
									
										
										
										
											2021-07-11 18:50:45 +02:00
										 |  |  |     png_chunk.reserve(bitmap.size_in_bytes()); | 
					
						
							| 
									
										
										
										
											2021-01-22 11:46:09 +01:00
										 |  |  | 
 | 
					
						
							|  |  |  |     u16 CMF_FLG = 0x81d; | 
					
						
							| 
									
										
										
										
											2021-07-11 18:39:17 +02:00
										 |  |  |     png_chunk.add_as_big_endian(CMF_FLG); | 
					
						
							| 
									
										
										
										
											2021-01-22 11:46:09 +01:00
										 |  |  | 
 | 
					
						
							|  |  |  |     NonCompressibleBlock non_compressible_block; | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-04-19 23:43:04 +02:00
										 |  |  |     for (int y = 0; y < bitmap.height(); ++y) { | 
					
						
							| 
									
										
										
										
											2021-01-22 11:46:09 +01:00
										 |  |  |         non_compressible_block.add_byte_to_block(0, png_chunk); | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-04-19 23:43:04 +02:00
										 |  |  |         for (int x = 0; x < bitmap.width(); ++x) { | 
					
						
							|  |  |  |             auto pixel = bitmap.get_pixel(x, y); | 
					
						
							| 
									
										
										
										
											2021-01-22 11:46:09 +01:00
										 |  |  |             non_compressible_block.add_byte_to_block(pixel.red(), png_chunk); | 
					
						
							|  |  |  |             non_compressible_block.add_byte_to_block(pixel.green(), png_chunk); | 
					
						
							|  |  |  |             non_compressible_block.add_byte_to_block(pixel.blue(), png_chunk); | 
					
						
							|  |  |  |             non_compressible_block.add_byte_to_block(pixel.alpha(), png_chunk); | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  |     non_compressible_block.finalize(png_chunk); | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-07-11 18:39:17 +02:00
										 |  |  |     png_chunk.add_as_big_endian(non_compressible_block.adler_s2()); | 
					
						
							|  |  |  |     png_chunk.add_as_big_endian(non_compressible_block.adler_s1()); | 
					
						
							| 
									
										
										
										
											2021-01-22 11:46:09 +01:00
										 |  |  | 
 | 
					
						
							|  |  |  |     add_chunk(png_chunk); | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-04-19 23:43:04 +02:00
										 |  |  | ByteBuffer PNGWriter::encode(Gfx::Bitmap const& bitmap) | 
					
						
							| 
									
										
										
										
											2021-01-22 11:46:09 +01:00
										 |  |  | { | 
					
						
							| 
									
										
										
										
											2021-04-19 23:43:04 +02:00
										 |  |  |     PNGWriter writer; | 
					
						
							|  |  |  |     writer.add_png_header(); | 
					
						
							|  |  |  |     writer.add_IHDR_chunk(bitmap.width(), bitmap.height(), 8, 6, 0, 0, 0); | 
					
						
							|  |  |  |     writer.add_IDAT_chunk(bitmap); | 
					
						
							|  |  |  |     writer.add_IEND_chunk(); | 
					
						
							|  |  |  |     return ByteBuffer::copy(writer.m_data); | 
					
						
							| 
									
										
										
										
											2021-01-22 11:46:09 +01:00
										 |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | } |