diff --git a/Libraries/LibGfx/Bitmap.cpp b/Libraries/LibGfx/Bitmap.cpp index e4b79d49e52..aba7941e380 100644 --- a/Libraries/LibGfx/Bitmap.cpp +++ b/Libraries/LibGfx/Bitmap.cpp @@ -33,6 +33,7 @@ #include #include #include +#include #include #include #include diff --git a/Libraries/LibGfx/Bitmap.h b/Libraries/LibGfx/Bitmap.h index ddd480363c5..a2dd8b0b2ae 100644 --- a/Libraries/LibGfx/Bitmap.h +++ b/Libraries/LibGfx/Bitmap.h @@ -37,7 +37,8 @@ __ENUMERATE_IMAGE_FORMAT(pbm, ".pbm") \ __ENUMERATE_IMAGE_FORMAT(png, ".png") \ __ENUMERATE_IMAGE_FORMAT(gif, ".gif") \ - __ENUMERATE_IMAGE_FORMAT(bmp, ".bmp") + __ENUMERATE_IMAGE_FORMAT(bmp, ".bmp") \ + __ENUMERATE_IMAGE_FORMAT(ico, ".ico") namespace Gfx { diff --git a/Libraries/LibGfx/CMakeLists.txt b/Libraries/LibGfx/CMakeLists.txt index 610fdfd5d72..0e56c01a47c 100644 --- a/Libraries/LibGfx/CMakeLists.txt +++ b/Libraries/LibGfx/CMakeLists.txt @@ -9,6 +9,7 @@ set(SOURCES FloatRect.cpp Font.cpp GIFLoader.cpp + ICOLoader.cpp ImageDecoder.cpp Painter.cpp Palette.cpp diff --git a/Libraries/LibGfx/ICOLoader.cpp b/Libraries/LibGfx/ICOLoader.cpp new file mode 100644 index 00000000000..90d0d67d0bf --- /dev/null +++ b/Libraries/LibGfx/ICOLoader.cpp @@ -0,0 +1,287 @@ +/* + * Copyright (c) 2020, Paul Roukema + * 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 +#include +#include +#include +#include +#include +#include +#include +#include +#include + +//#define ICO_DEBUG + +namespace Gfx { + +// FIXME: This is in little-endian order. Maybe need a NetworkOrdered equivalent eventually. +struct ICONDIR { + u16 must_be_0; + u16 must_be_1; + u16 image_count; +}; +static_assert(sizeof(ICONDIR) == 6); + +struct ICONDIRENTRY { + u8 width; + u8 height; + u8 color_count; + u8 reserved_0; + u16 planes; + u16 bits_per_pixel; + u32 size; + u32 offset; +}; +static_assert(sizeof(ICONDIRENTRY) == 16); + +struct ImageDescriptor { + u16 width; + u16 height; + size_t offset; + size_t size; + RefPtr bitmap; +}; + +struct ICOLoadingContext { + enum State { + NotDecoded = 0, + Error, + DirectoryDecoded, + BitmapDecoded + }; + State state { NotDecoded }; + const u8* data { nullptr }; + size_t data_size { 0 }; + Vector images; +}; + +RefPtr load_ico(const StringView& path) +{ + MappedFile mapped_file(path); + if (!mapped_file.is_valid()) + return nullptr; + ICOImageDecoderPlugin decoder((const u8*)mapped_file.data(), mapped_file.size()); + auto bitmap = decoder.bitmap(); + if (bitmap) + bitmap->set_mmap_name(String::format("Gfx::Bitmap [%dx%d] - Decoded ICO: %s", bitmap->width(), bitmap->height(), LexicalPath::canonicalized_path(path).characters())); + return bitmap; +} + +RefPtr load_ico_from_memory(const u8* data, size_t length) +{ + ICOImageDecoderPlugin decoder(data, length); + auto bitmap = decoder.bitmap(); + if (bitmap) + bitmap->set_mmap_name(String::format("Gfx::Bitmap [%dx%d] - Decoded ICO: ", bitmap->width(), bitmap->height())); + return bitmap; +} + +static Optional decode_ico_header(BufferStream& stream) +{ + ICONDIR header; + stream.read_raw((uint8_t*)&header, sizeof(header)); + if (stream.handle_read_failure()) + return {}; + + if (header.must_be_0 != 0 || header.must_be_1 != 1) + return {}; + return { header.image_count }; +} + +static Optional decode_ico_direntry(BufferStream& stream) +{ + ICONDIRENTRY entry; + stream.read_raw((uint8_t*)&entry, sizeof(entry)); + if (stream.handle_read_failure()) + return {}; + + ImageDescriptor desc = { entry.width, entry.height, entry.offset, entry.size, nullptr }; + if (desc.width == 0) + desc.width = 256; + if (desc.height == 0) + desc.height = 256; + + return { desc }; +} + +static bool load_ico_directory(ICOLoadingContext& context) +{ + auto buffer = ByteBuffer::wrap(context.data, context.data_size); + auto stream = BufferStream(buffer); + auto image_count = decode_ico_header(stream); + if (!image_count.has_value() || image_count.value() == 0) { + return false; + } + + for (size_t i = 0; i < image_count.value(); ++i) { + auto maybe_desc = decode_ico_direntry(stream); + if (!maybe_desc.has_value()) { +#ifdef ICO_DEBUG + printf("load_ico_directory: error loading entry: %lu\n", i); +#endif + return false; + } + + auto& desc = maybe_desc.value(); + if (desc.offset + desc.size < desc.offset // detect integer overflow + || (desc.offset + desc.size) > context.data_size) { +#ifdef ICO_DEBUG + printf("load_ico_directory: offset: %lu size: %lu doesn't fit in ICO size: %lu\n", + desc.offset, desc.size, context.data_size); +#endif + return false; + } +#ifdef ICO_DEBUG + printf("load_ico_directory: index %lu width: %u height: %u offset: %lu size: %lu\n", + i, desc.width, desc.height, desc.offset, desc.size); +#endif + context.images.append(desc); + } + return true; +} + +static bool load_ico_bitmap(ICOLoadingContext& context, size_t index) +{ + if (context.state < ICOLoadingContext::State::DirectoryDecoded) { + if (!load_ico_directory(context)) { + context.state = ICOLoadingContext::State::Error; + return false; + } + context.state = ICOLoadingContext::State::DirectoryDecoded; + } + if (index >= context.images.size()) { + return false; + } + + ImageDescriptor& desc = context.images[index]; + + PNGImageDecoderPlugin png_decoder(context.data + desc.offset, desc.size); + if (png_decoder.sniff()) { + desc.bitmap = png_decoder.bitmap(); + if (!desc.bitmap) { +#ifdef ICO_DEBUG + printf("load_ico_bitmap: failed to load PNG encoded image index: %lu\n", index); +#endif + return false; + } + return true; + } else { + dbg() << "load_ico_bitmap: image index: " << index << " must be BMP encoded, not implemented\n"; + return false; + } +} + +ICOImageDecoderPlugin::ICOImageDecoderPlugin(const u8* data, size_t size) +{ + m_context = make(); + m_context->data = data; + m_context->data_size = size; +} + +ICOImageDecoderPlugin::~ICOImageDecoderPlugin() { } + +IntSize ICOImageDecoderPlugin::size() +{ + if (m_context->state == ICOLoadingContext::State::Error) { + return {}; + } + + if (m_context->state < ICOLoadingContext::State::DirectoryDecoded) { + if (!load_ico_directory(*m_context)) { + m_context->state = ICOLoadingContext::State::Error; + return {}; + } + m_context->state = ICOLoadingContext::State::DirectoryDecoded; + } + + return { m_context->images[0].width, m_context->images[0].height }; +} + +RefPtr ICOImageDecoderPlugin::bitmap() +{ + if (m_context->state == ICOLoadingContext::State::Error) + return nullptr; + + if (m_context->state < ICOLoadingContext::State::BitmapDecoded) { + // NOTE: This forces the chunk decoding to happen. + bool success = load_ico_bitmap(*m_context, 0); + if (!success) { + m_context->state = ICOLoadingContext::State::Error; + return nullptr; + } + m_context->state = ICOLoadingContext::State::BitmapDecoded; + } + + ASSERT(m_context->images[0].bitmap); + return m_context->images[0].bitmap; +} + +void ICOImageDecoderPlugin::set_volatile() +{ + if (m_context->images[0].bitmap) + m_context->images[0].bitmap->set_volatile(); +} + +bool ICOImageDecoderPlugin::set_nonvolatile() +{ + if (!m_context->images[0].bitmap) + return false; + return m_context->images[0].bitmap->set_nonvolatile(); +} + +bool ICOImageDecoderPlugin::sniff() +{ + auto buffer = ByteBuffer::wrap(m_context->data, m_context->data_size); + BufferStream stream(buffer); + return decode_ico_header(stream).has_value(); +} + +bool ICOImageDecoderPlugin::is_animated() +{ + return false; +} + +size_t ICOImageDecoderPlugin::loop_count() +{ + return 0; +} + +size_t ICOImageDecoderPlugin::frame_count() +{ + return 1; +} + +ImageFrameDescriptor ICOImageDecoderPlugin::frame(size_t i) +{ + if (i > 0) { + return { bitmap(), 0 }; + } + return {}; +} + +} diff --git a/Libraries/LibGfx/ICOLoader.h b/Libraries/LibGfx/ICOLoader.h new file mode 100644 index 00000000000..12c40b3398c --- /dev/null +++ b/Libraries/LibGfx/ICOLoader.h @@ -0,0 +1,58 @@ +/* + * Copyright (c) 2020, Paul Roukema + * 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 + +namespace Gfx { + +RefPtr load_ico(const StringView& path); +RefPtr load_ico_from_memory(const u8*, size_t); + +struct ICOLoadingContext; + +class ICOImageDecoderPlugin final : public ImageDecoderPlugin { +public: + virtual ~ICOImageDecoderPlugin() override; + ICOImageDecoderPlugin(const u8*, size_t); + + virtual IntSize size() override; + virtual RefPtr bitmap() override; + virtual void set_volatile() override; + [[nodiscard]] virtual bool set_nonvolatile() override; + virtual bool sniff() override; + virtual bool is_animated() override; + virtual size_t loop_count() override; + virtual size_t frame_count() override; + virtual ImageFrameDescriptor frame(size_t i) override; + +private: + OwnPtr m_context; +}; + +} diff --git a/Libraries/LibGfx/ImageDecoder.cpp b/Libraries/LibGfx/ImageDecoder.cpp index d56e2fbfe13..c2d62e79c81 100644 --- a/Libraries/LibGfx/ImageDecoder.cpp +++ b/Libraries/LibGfx/ImageDecoder.cpp @@ -27,6 +27,7 @@ #include #include #include +#include #include #include @@ -50,6 +51,10 @@ ImageDecoder::ImageDecoder(const u8* data, size_t size) if (m_plugin->sniff()) return; + m_plugin = make(data, size); + if (m_plugin->sniff()) + return; + m_plugin = nullptr; }