1
Fork 0
mirror of https://github.com/RGBCube/serenity synced 2025-05-20 14:55:08 +00:00
serenity/Userland/Libraries/LibGfx/ICOLoader.cpp
Simon Danner b10fe7c136 LibGfx: Prefer largest image with best depth for ICO display
Some favicons contain multiple icons of the same size, but with
increasing depth. Use the largest one to make things look nicer.
2023-01-06 17:31:05 +01:00

265 lines
7.9 KiB
C++

/*
* Copyright (c) 2020, Paul Roukema <roukemap@gmail.com>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#include <AK/ByteBuffer.h>
#include <AK/Debug.h>
#include <AK/MemoryStream.h>
#include <AK/NonnullOwnPtrVector.h>
#include <AK/Types.h>
#include <LibGfx/BMPLoader.h>
#include <LibGfx/ICOLoader.h>
#include <LibGfx/PNGLoader.h>
#include <string.h>
namespace Gfx {
// FIXME: This is in little-endian order. Maybe need a NetworkOrdered<T> equivalent eventually.
struct ICONDIR {
u16 must_be_0 = 0;
u16 must_be_1 = 0;
u16 image_count = 0;
};
static_assert(AssertSize<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(AssertSize<ICONDIRENTRY, 16>());
struct ICOImageDescriptor {
u16 width;
u16 height;
u16 bits_per_pixel;
size_t offset;
size_t size;
RefPtr<Gfx::Bitmap> bitmap;
};
struct ICOLoadingContext {
enum State {
NotDecoded = 0,
Error,
DirectoryDecoded,
BitmapDecoded
};
State state { NotDecoded };
u8 const* data { nullptr };
size_t data_size { 0 };
Vector<ICOImageDescriptor> images;
size_t largest_index;
};
static Optional<size_t> decode_ico_header(InputMemoryStream& stream)
{
ICONDIR header;
stream >> Bytes { &header, sizeof(header) };
if (stream.handle_any_error())
return {};
if (header.must_be_0 != 0 || header.must_be_1 != 1)
return {};
return { header.image_count };
}
static Optional<ICOImageDescriptor> decode_ico_direntry(InputMemoryStream& stream)
{
ICONDIRENTRY entry;
stream >> Bytes { &entry, sizeof(entry) };
if (stream.handle_any_error())
return {};
ICOImageDescriptor desc = { entry.width, entry.height, entry.bits_per_pixel, entry.offset, entry.size, nullptr };
if (desc.width == 0)
desc.width = 256;
if (desc.height == 0)
desc.height = 256;
return { desc };
}
static size_t find_largest_image(ICOLoadingContext const& context)
{
size_t max_area = 0;
size_t index = 0;
size_t largest_index = 0;
u16 max_bits_per_pixel = 0;
for (auto const& desc : context.images) {
if (static_cast<size_t>(desc.width) * static_cast<size_t>(desc.height) >= max_area) {
if (desc.bits_per_pixel > max_bits_per_pixel) {
max_area = desc.width * desc.height;
largest_index = index;
max_bits_per_pixel = desc.bits_per_pixel;
}
}
++index;
}
return largest_index;
}
static bool load_ico_directory(ICOLoadingContext& context)
{
InputMemoryStream stream { { context.data, context.data_size } };
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()) {
dbgln_if(ICO_DEBUG, "load_ico_directory: error loading entry: {}", i);
return false;
}
auto& desc = maybe_desc.value();
if (desc.offset + desc.size < desc.offset // detect integer overflow
|| (desc.offset + desc.size) > context.data_size) {
dbgln_if(ICO_DEBUG, "load_ico_directory: offset: {} size: {} doesn't fit in ICO size: {}", desc.offset, desc.size, context.data_size);
return false;
}
dbgln_if(ICO_DEBUG, "load_ico_directory: index {} width: {} height: {} offset: {} size: {}", i, desc.width, desc.height, desc.offset, desc.size);
context.images.append(desc);
}
context.largest_index = find_largest_image(context);
context.state = ICOLoadingContext::State::DirectoryDecoded;
return true;
}
static bool load_ico_bitmap(ICOLoadingContext& context, Optional<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;
}
size_t real_index = context.largest_index;
if (index.has_value())
real_index = index.value();
if (real_index >= context.images.size()) {
return false;
}
ICOImageDescriptor& desc = context.images[real_index];
PNGImageDecoderPlugin png_decoder(context.data + desc.offset, desc.size);
if (png_decoder.sniff()) {
auto decoded_png_frame = png_decoder.frame(0);
if (decoded_png_frame.is_error() || !decoded_png_frame.value().image) {
dbgln_if(ICO_DEBUG, "load_ico_bitmap: failed to load PNG encoded image index: {}", real_index);
return false;
}
desc.bitmap = decoded_png_frame.value().image;
return true;
} else {
BMPImageDecoderPlugin bmp_decoder(context.data + desc.offset, desc.size, true);
if (bmp_decoder.sniff_dib()) {
auto decoded_bmp_frame = bmp_decoder.frame(0);
if (decoded_bmp_frame.is_error() || !decoded_bmp_frame.value().image) {
dbgln_if(ICO_DEBUG, "load_ico_bitmap: failed to load BMP encoded image index: {}", real_index);
return false;
}
desc.bitmap = decoded_bmp_frame.value().image;
} else {
dbgln_if(ICO_DEBUG, "load_ico_bitmap: encoded image not supported at index: {}", real_index);
return false;
}
return true;
}
}
ICOImageDecoderPlugin::ICOImageDecoderPlugin(u8 const* data, size_t size)
{
m_context = make<ICOLoadingContext>();
m_context->data = data;
m_context->data_size = size;
}
ICOImageDecoderPlugin::~ICOImageDecoderPlugin() = default;
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[m_context->largest_index].width, m_context->images[m_context->largest_index].height };
}
void ICOImageDecoderPlugin::set_volatile()
{
if (m_context->images[0].bitmap)
m_context->images[0].bitmap->set_volatile();
}
bool ICOImageDecoderPlugin::set_nonvolatile(bool& was_purged)
{
if (!m_context->images[0].bitmap)
return false;
return m_context->images[0].bitmap->set_nonvolatile(was_purged);
}
bool ICOImageDecoderPlugin::sniff()
{
InputMemoryStream stream { { m_context->data, m_context->data_size } };
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;
}
ErrorOr<ImageFrameDescriptor> ICOImageDecoderPlugin::frame(size_t index)
{
if (index > 0)
return Error::from_string_literal("ICOImageDecoderPlugin: Invalid frame index");
if (m_context->state == ICOLoadingContext::State::Error)
return Error::from_string_literal("ICOImageDecoderPlugin: Decoding failed");
if (m_context->state < ICOLoadingContext::State::BitmapDecoded) {
// NOTE: This forces the chunk decoding to happen.
bool success = load_ico_bitmap(*m_context, {});
if (!success) {
m_context->state = ICOLoadingContext::State::Error;
return Error::from_string_literal("ICOImageDecoderPlugin: Decoding failed");
}
m_context->state = ICOLoadingContext::State::BitmapDecoded;
}
VERIFY(m_context->images[m_context->largest_index].bitmap);
return ImageFrameDescriptor { m_context->images[m_context->largest_index].bitmap, 0 };
}
}