1
Fork 0
mirror of https://github.com/RGBCube/serenity synced 2025-05-16 19:55:06 +00:00
serenity/Userland/Libraries/LibGfx/ImageFormats/ICOLoader.cpp
MacDue e7cddda7e1 LibGfx: Allow passing an ideal size to image decoders
The ideal size is the size the user will display the image. Raster
formats should ignore this parameter, but vector formats can use
it to generate a bitmap of the ideal size.
2023-07-03 23:54:51 +02:00

285 lines
8.8 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/Types.h>
#include <LibGfx/ImageFormats/BMPLoader.h>
#include <LibGfx/ImageFormats/ICOLoader.h>
#include <LibGfx/ImageFormats/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>());
};
template<>
class AK::Traits<Gfx::ICONDIR> : public GenericTraits<Gfx::ICONDIR> {
public:
static constexpr bool is_trivially_serializable() { return true; }
};
template<>
class AK::Traits<Gfx::ICONDIRENTRY> : public GenericTraits<Gfx::ICONDIRENTRY> {
public:
static constexpr bool is_trivially_serializable() { return true; }
};
namespace Gfx {
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 ErrorOr<size_t> decode_ico_header(Stream& stream)
{
auto header = TRY(stream.read_value<ICONDIR>());
if (header.must_be_0 != 0 || header.must_be_1 != 1)
return Error::from_string_literal("Invalid ICO header");
return { header.image_count };
}
static ErrorOr<ICOImageDescriptor> decode_ico_direntry(Stream& stream)
{
auto entry = TRY(stream.read_value<ICONDIRENTRY>());
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 ErrorOr<void> load_ico_directory(ICOLoadingContext& context)
{
FixedMemoryStream stream { { context.data, context.data_size } };
auto image_count = TRY(decode_ico_header(stream));
if (image_count == 0)
return Error::from_string_literal("ICO file has no images");
for (size_t i = 0; i < image_count; ++i) {
auto desc = TRY(decode_ico_direntry(stream));
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 Error::from_string_literal("ICO size too large");
}
dbgln_if(ICO_DEBUG, "load_ico_directory: index {} width: {} height: {} offset: {} size: {}", i, desc.width, desc.height, desc.offset, desc.size);
TRY(context.images.try_append(desc));
}
context.largest_index = find_largest_image(context);
context.state = ICOLoadingContext::State::DirectoryDecoded;
return {};
}
ErrorOr<void> ICOImageDecoderPlugin::load_ico_bitmap(ICOLoadingContext& context, Optional<size_t> index)
{
if (context.state < ICOLoadingContext::State::DirectoryDecoded)
TRY(load_ico_directory(context));
size_t real_index = context.largest_index;
if (index.has_value())
real_index = index.value();
if (real_index >= context.images.size())
return Error::from_string_literal("Index out of bounds");
ICOImageDescriptor& desc = context.images[real_index];
if (PNGImageDecoderPlugin::sniff({ context.data + desc.offset, desc.size })) {
auto png_decoder = TRY(PNGImageDecoderPlugin::create({ context.data + desc.offset, desc.size }));
TRY(png_decoder->initialize());
auto decoded_png_frame = TRY(png_decoder->frame(0));
if (!decoded_png_frame.image) {
dbgln_if(ICO_DEBUG, "load_ico_bitmap: failed to load PNG encoded image index: {}", real_index);
return Error::from_string_literal("Encoded image not null");
}
desc.bitmap = decoded_png_frame.image;
return {};
} else {
auto bmp_decoder = TRY(BMPImageDecoderPlugin::create_as_included_in_ico({}, { context.data + desc.offset, desc.size }));
// NOTE: We don't initialize a BMP decoder in the usual way, but rather
// we just create an object and try to sniff for a frame when it's included
// inside an ICO image.
if (bmp_decoder->sniff_dib()) {
auto decoded_bmp_frame = TRY(bmp_decoder->frame(0));
if (!decoded_bmp_frame.image) {
dbgln_if(ICO_DEBUG, "load_ico_bitmap: failed to load BMP encoded image index: {}", real_index);
return Error::from_string_literal("Encoded image not null");
}
desc.bitmap = decoded_bmp_frame.image;
} else {
dbgln_if(ICO_DEBUG, "load_ico_bitmap: encoded image not supported at index: {}", real_index);
return Error::from_string_literal("Encoded image not supported");
}
return {};
}
}
bool ICOImageDecoderPlugin::sniff(ReadonlyBytes data)
{
FixedMemoryStream stream { data };
return !decode_ico_header(stream).is_error();
}
ErrorOr<NonnullOwnPtr<ImageDecoderPlugin>> ICOImageDecoderPlugin::create(ReadonlyBytes data)
{
return adopt_nonnull_own_or_enomem(new (nothrow) ICOImageDecoderPlugin(data.data(), data.size()));
}
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).is_error()) {
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);
}
ErrorOr<void> ICOImageDecoderPlugin::initialize()
{
FixedMemoryStream stream { { m_context->data, m_context->data_size } };
TRY(decode_ico_header(stream));
return {};
}
bool ICOImageDecoderPlugin::is_animated()
{
return false;
}
size_t ICOImageDecoderPlugin::loop_count()
{
return 0;
}
size_t ICOImageDecoderPlugin::frame_count()
{
return 1;
}
size_t ICOImageDecoderPlugin::first_animated_frame_index()
{
return 0;
}
ErrorOr<ImageFrameDescriptor> ICOImageDecoderPlugin::frame(size_t index, Optional<IntSize>)
{
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.
auto maybe_error = load_ico_bitmap(*m_context, {});
if (maybe_error.is_error()) {
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 };
}
ErrorOr<Optional<ReadonlyBytes>> ICOImageDecoderPlugin::icc_data()
{
return OptionalNone {};
}
}