diff --git a/Userland/Libraries/LibGfx/CMakeLists.txt b/Userland/Libraries/LibGfx/CMakeLists.txt index 2a6d9750db5..59c9a7fd0f8 100644 --- a/Userland/Libraries/LibGfx/CMakeLists.txt +++ b/Userland/Libraries/LibGfx/CMakeLists.txt @@ -22,6 +22,7 @@ set(SOURCES PBMLoader.cpp PGMLoader.cpp PNGLoader.cpp + PNGWriter.cpp PPMLoader.cpp Point.cpp Rect.cpp diff --git a/Userland/Libraries/LibGfx/PNGWriter.cpp b/Userland/Libraries/LibGfx/PNGWriter.cpp new file mode 100644 index 00000000000..99fe188e4f9 --- /dev/null +++ b/Userland/Libraries/LibGfx/PNGWriter.cpp @@ -0,0 +1,228 @@ +/* + * Copyright (c) 2021, Pierre Hoffmeister + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include "PNGWriter.h" +#include +#include + +namespace Gfx { + +class PNGChunk { +public: + PNGChunk(const String&); + const Vector& data() const { return m_data; }; + const String& type() const { return m_type; }; + void add_u8(u8); + void add_u16_big(u16); + void add_u32_big(u32); + void add_u16_little(u16); + void add_u32_little(u32); + +private: + Vector m_data; + String m_type; +}; + +class NonCompressibleBlock { +public: + void finalize(PNGChunk&); + void add_byte_to_block(u8 data, PNGChunk&); + + u32 adler_s1() { return m_adler_s1; } + u32 adler_s2() { return m_adler_s2; } + +private: + void add_block_to_chunk(PNGChunk&, bool); + void update_adler(u8); + bool full() { return m_non_compressible_data.size() == 65535; } + Vector m_non_compressible_data; + u32 m_adler_s1 { 1 }; + u32 m_adler_s2 { 0 }; +}; + +PNGChunk::PNGChunk(const String& type) + : m_type(move(type)) +{ +} + +void PNGChunk::add_u8(u8 data) +{ + m_data.append(data); +} + +void PNGChunk::add_u16_little(u16 data) +{ + m_data.append(data & 0xff); + m_data.append((data >> 8) & 0xff); +} + +void PNGChunk::add_u32_little(u32 data) +{ + m_data.append(data & 0xff); + m_data.append((data >> 8) & 0xff); + m_data.append((data >> 16) & 0xff); + m_data.append((data >> 24) & 0xff); +} + +void PNGChunk::add_u32_big(u32 data) +{ + m_data.append((data >> 24) & 0xff); + m_data.append((data >> 16) & 0xff); + m_data.append((data >> 8) & 0xff); + m_data.append(data & 0xff); +} + +void PNGChunk::add_u16_big(u16 data) +{ + m_data.append((data >> 8) & 0xff); + m_data.append(data & 0xff); +} + +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) +{ + if (last) { + png_chunk.add_u8(1); + } else { + png_chunk.add_u8(0); + } + + auto len = m_non_compressible_data.size(); + auto nlen = ~len; + + png_chunk.add_u16_little(len); + png_chunk.add_u16_little(nlen); + + 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; +} + +void PNGWriter::add_chunk(const PNGChunk& png_chunk) +{ + Vector combined; + for (auto character : png_chunk.type()) { + combined.append(character); + } + combined.append(png_chunk.data()); + + auto crc = BigEndian(Crypto::Checksum::CRC32({ (const u8*)combined.data(), combined.size() }).digest()); + auto data_len = BigEndian(png_chunk.data().size()); + + ByteBuffer buf; + buf.append(&data_len, sizeof(u32)); + buf.append(combined.data(), combined.size()); + 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" }; + png_chunk.add_u32_big(width); + png_chunk.add_u32_big(height); + 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); +} + +void PNGWriter::add_IDAT_chunk(const RefPtr bitmap) +{ + PNGChunk png_chunk { "IDAT" }; + + u16 CMF_FLG = 0x81d; + png_chunk.add_u16_big(CMF_FLG); + + NonCompressibleBlock non_compressible_block; + + for (int y = 0; y < bitmap->height(); ++y) { + non_compressible_block.add_byte_to_block(0, png_chunk); + + for (int x = 0; x < bitmap->width(); ++x) { + auto pixel = bitmap->get_pixel(x, y); + 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); + + png_chunk.add_u16_big(non_compressible_block.adler_s2()); + png_chunk.add_u16_big(non_compressible_block.adler_s1()); + + add_chunk(png_chunk); +} + +ByteBuffer PNGWriter::write(const RefPtr bitmap) +{ + add_png_header(); + add_IHDR_chunk(bitmap->width(), bitmap->height(), 8, 6, 0, 0, 0); + add_IDAT_chunk(bitmap); + add_IEND_chunk(); + + ByteBuffer buf; + buf.append(m_data.data(), m_data.size()); + return buf; +} + +} diff --git a/Userland/Libraries/LibGfx/PNGWriter.h b/Userland/Libraries/LibGfx/PNGWriter.h new file mode 100644 index 00000000000..d07ed9d273a --- /dev/null +++ b/Userland/Libraries/LibGfx/PNGWriter.h @@ -0,0 +1,50 @@ +/* + * Copyright (c) 2021, Pierre Hoffmeister + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#pragma once + +#include +#include +#include + +namespace Gfx { + +class PNGChunk; + +class PNGWriter { +public: + ByteBuffer write(const RefPtr); + +private: + Vector m_data; + void add_chunk(const PNGChunk&); + void add_png_header(); + void add_IHDR_chunk(u32 width, u32 height, u8 bit_depth, u8 color_type, u8 compression_method, u8 filter_method, u8 interlace_method); + void add_IDAT_chunk(const RefPtr); + void add_IEND_chunk(); +}; + +}