mirror of
https://github.com/zebrajr/ladybird.git
synced 2025-12-06 12:20:00 +01:00
When decoding a BitmapSequence received over IPC, we were creating an AnonymousBuffer for each bitmap and then making a Gfx::Bitmap wrapper around it. This was unnecessarily using up one file descriptor per bitmap, and also wasting a lot of memory for small bitmaps since we always allocated at least one VM page. This patch changes the BitmapSequence decoder to use malloc memory instead, saving file descriptors and using less memory overall.
375 lines
14 KiB
C++
375 lines
14 KiB
C++
/*
|
|
* Copyright (c) 2018-2025, Andreas Kling <andreas@ladybird.org>
|
|
* Copyright (c) 2022, Timothy Slater <tslater2006@gmail.com>
|
|
* Copyright (c) 2024, Jelle Raaijmakers <jelle@ladybird.org>
|
|
*
|
|
* SPDX-License-Identifier: BSD-2-Clause
|
|
*/
|
|
|
|
#include <AK/Bitmap.h>
|
|
#include <AK/Checked.h>
|
|
#include <LibGfx/Bitmap.h>
|
|
#include <LibGfx/ShareableBitmap.h>
|
|
#include <LibGfx/SkiaUtils.h>
|
|
|
|
#include <core/SkBitmap.h>
|
|
#include <core/SkColorSpace.h>
|
|
#include <core/SkImage.h>
|
|
#include <errno.h>
|
|
|
|
#ifdef AK_OS_MACOS
|
|
# include <Accelerate/Accelerate.h>
|
|
#endif
|
|
|
|
namespace Gfx {
|
|
|
|
struct BackingStore {
|
|
void* data { nullptr };
|
|
size_t pitch { 0 };
|
|
size_t size_in_bytes { 0 };
|
|
};
|
|
|
|
size_t Bitmap::minimum_pitch(size_t width, BitmapFormat format)
|
|
{
|
|
size_t element_size;
|
|
switch (determine_storage_format(format)) {
|
|
case StorageFormat::BGRx8888:
|
|
case StorageFormat::BGRA8888:
|
|
case StorageFormat::RGBx8888:
|
|
case StorageFormat::RGBA8888:
|
|
element_size = 4;
|
|
break;
|
|
default:
|
|
VERIFY_NOT_REACHED();
|
|
}
|
|
|
|
return width * element_size;
|
|
}
|
|
|
|
static bool size_would_overflow(BitmapFormat format, IntSize size)
|
|
{
|
|
if (size.width() < 0 || size.height() < 0)
|
|
return true;
|
|
// This check is a bit arbitrary, but should protect us from most shenanigans:
|
|
if (size.width() >= INT16_MAX || size.height() >= INT16_MAX)
|
|
return true;
|
|
// In contrast, this check is absolutely necessary:
|
|
size_t pitch = Bitmap::minimum_pitch(size.width(), format);
|
|
return Checked<size_t>::multiplication_would_overflow(pitch, size.height());
|
|
}
|
|
|
|
ErrorOr<NonnullRefPtr<Bitmap>> Bitmap::create(BitmapFormat format, IntSize size)
|
|
{
|
|
// For backwards compatibility, premultiplied alpha is assumed
|
|
return create(format, AlphaType::Premultiplied, size);
|
|
}
|
|
|
|
ErrorOr<NonnullRefPtr<Bitmap>> Bitmap::create(BitmapFormat format, AlphaType alpha_type, IntSize size)
|
|
{
|
|
auto backing_store = TRY(Bitmap::allocate_backing_store(format, size));
|
|
return AK::adopt_nonnull_ref_or_enomem(new (nothrow) Bitmap(format, alpha_type, size, backing_store));
|
|
}
|
|
|
|
ErrorOr<NonnullRefPtr<Bitmap>> Bitmap::create_shareable(BitmapFormat format, AlphaType alpha_type, IntSize size)
|
|
{
|
|
if (size_would_overflow(format, size))
|
|
return Error::from_string_literal("Gfx::Bitmap::create_shareable size overflow");
|
|
|
|
auto const pitch = minimum_pitch(size.width(), format);
|
|
auto const data_size = size_in_bytes(pitch, size.height());
|
|
|
|
auto buffer = TRY(Core::AnonymousBuffer::create_with_size(round_up_to_power_of_two(data_size, PAGE_SIZE)));
|
|
auto bitmap = TRY(Bitmap::create_with_anonymous_buffer(format, alpha_type, buffer, size));
|
|
return bitmap;
|
|
}
|
|
|
|
Bitmap::Bitmap(BitmapFormat format, AlphaType alpha_type, IntSize size, BackingStore const& backing_store)
|
|
: m_size(size)
|
|
, m_data(backing_store.data)
|
|
, m_pitch(backing_store.pitch)
|
|
, m_format(format)
|
|
, m_alpha_type(alpha_type)
|
|
{
|
|
VERIFY(!m_size.is_empty());
|
|
VERIFY(!size_would_overflow(format, size));
|
|
VERIFY(m_data);
|
|
VERIFY(backing_store.size_in_bytes == size_in_bytes());
|
|
m_destruction_callback = [data = m_data, size_in_bytes = this->size_in_bytes()] {
|
|
kfree_sized(data, size_in_bytes);
|
|
};
|
|
}
|
|
|
|
ErrorOr<NonnullRefPtr<Bitmap>> Bitmap::create_wrapper(BitmapFormat format, AlphaType alpha_type, IntSize size, size_t pitch, void* data, Function<void()>&& destruction_callback)
|
|
{
|
|
if (size_would_overflow(format, size))
|
|
return Error::from_string_literal("Gfx::Bitmap::create_wrapper size overflow");
|
|
return adopt_ref(*new Bitmap(format, alpha_type, size, pitch, data, move(destruction_callback)));
|
|
}
|
|
|
|
Bitmap::Bitmap(BitmapFormat format, AlphaType alpha_type, IntSize size, size_t pitch, void* data, Function<void()>&& destruction_callback)
|
|
: m_size(size)
|
|
, m_data(data)
|
|
, m_pitch(pitch)
|
|
, m_format(format)
|
|
, m_alpha_type(alpha_type)
|
|
, m_destruction_callback(move(destruction_callback))
|
|
{
|
|
VERIFY(pitch >= minimum_pitch(size.width(), format));
|
|
VERIFY(!size_would_overflow(format, size));
|
|
// FIXME: assert that `data` is actually long enough!
|
|
}
|
|
|
|
ErrorOr<NonnullRefPtr<Bitmap>> Bitmap::create_with_anonymous_buffer(BitmapFormat format, AlphaType alpha_type, Core::AnonymousBuffer buffer, IntSize size)
|
|
{
|
|
if (size_would_overflow(format, size))
|
|
return Error::from_string_literal("Gfx::Bitmap::create_with_anonymous_buffer size overflow");
|
|
|
|
return adopt_nonnull_ref_or_enomem(new (nothrow) Bitmap(format, alpha_type, move(buffer), size));
|
|
}
|
|
|
|
ErrorOr<NonnullRefPtr<Bitmap>> Bitmap::create_with_raw_data(BitmapFormat format, AlphaType alpha_type, ReadonlyBytes raw_data, IntSize size)
|
|
{
|
|
if (size_would_overflow(format, size))
|
|
return Error::from_string_literal("Gfx::Bitmap::create_with_raw_data size overflow");
|
|
|
|
auto backing_store = TRY(Bitmap::allocate_backing_store(format, size, InitializeBackingStore::No));
|
|
raw_data.copy_to(Bytes { backing_store.data, backing_store.size_in_bytes });
|
|
return AK::adopt_nonnull_ref_or_enomem(new (nothrow) Bitmap(format, alpha_type, size, backing_store));
|
|
}
|
|
|
|
Bitmap::Bitmap(BitmapFormat format, AlphaType alpha_type, Core::AnonymousBuffer buffer, IntSize size)
|
|
: m_size(size)
|
|
, m_data(buffer.data<void>())
|
|
, m_pitch(minimum_pitch(size.width(), format))
|
|
, m_format(format)
|
|
, m_alpha_type(alpha_type)
|
|
, m_buffer(move(buffer))
|
|
{
|
|
VERIFY(!size_would_overflow(format, size));
|
|
}
|
|
|
|
ErrorOr<NonnullRefPtr<Gfx::Bitmap>> Bitmap::clone() const
|
|
{
|
|
auto new_bitmap = TRY(Bitmap::create(format(), alpha_type(), size()));
|
|
|
|
VERIFY(size_in_bytes() == new_bitmap->size_in_bytes());
|
|
memcpy(new_bitmap->scanline(0), scanline(0), size_in_bytes());
|
|
|
|
return new_bitmap;
|
|
}
|
|
|
|
void Bitmap::apply_mask(Gfx::Bitmap const& mask, MaskKind mask_kind)
|
|
{
|
|
VERIFY(size() == mask.size());
|
|
|
|
for (int y = 0; y < height(); y++) {
|
|
for (int x = 0; x < width(); x++) {
|
|
auto color = get_pixel(x, y);
|
|
auto mask_color = mask.get_pixel(x, y);
|
|
if (mask_kind == MaskKind::Luminance) {
|
|
color = color.with_alpha(color.alpha() * mask_color.alpha() * mask_color.luminosity() / (255 * 255));
|
|
} else {
|
|
VERIFY(mask_kind == MaskKind::Alpha);
|
|
color = color.with_alpha(color.alpha() * mask_color.alpha() / 255);
|
|
}
|
|
set_pixel(x, y, color);
|
|
}
|
|
}
|
|
}
|
|
|
|
ErrorOr<NonnullRefPtr<Gfx::Bitmap>> Bitmap::cropped(Gfx::IntRect crop, Gfx::Color outside_color) const
|
|
{
|
|
// OPTIMIZATION: Skip slow manual copying for NO-OP crops
|
|
if (crop == rect())
|
|
return clone();
|
|
|
|
auto new_bitmap = TRY(Gfx::Bitmap::create(format(), alpha_type(), { crop.width(), crop.height() }));
|
|
|
|
for (int y = 0; y < crop.height(); ++y) {
|
|
for (int x = 0; x < crop.width(); ++x) {
|
|
int global_x = x + crop.left();
|
|
int global_y = y + crop.top();
|
|
if (global_x >= width() || global_y >= height() || global_x < 0 || global_y < 0) {
|
|
new_bitmap->set_pixel(x, y, outside_color);
|
|
} else {
|
|
new_bitmap->set_pixel(x, y, get_pixel(global_x, global_y));
|
|
}
|
|
}
|
|
}
|
|
return new_bitmap;
|
|
}
|
|
|
|
ErrorOr<NonnullRefPtr<Bitmap>> Bitmap::scaled(int const width, int const height, ScalingMode const scaling_mode) const
|
|
{
|
|
auto const source_info = SkImageInfo::Make(this->width(), this->height(), to_skia_color_type(format()), to_skia_alpha_type(format(), alpha_type()), nullptr);
|
|
SkPixmap const source_sk_pixmap(source_info, begin(), pitch());
|
|
SkBitmap source_sk_bitmap;
|
|
source_sk_bitmap.installPixels(source_sk_pixmap);
|
|
source_sk_bitmap.setImmutable();
|
|
|
|
auto scaled_bitmap = TRY(Gfx::Bitmap::create(format(), alpha_type(), { width, height }));
|
|
auto const scaled_info = SkImageInfo::Make(scaled_bitmap->width(), scaled_bitmap->height(), to_skia_color_type(scaled_bitmap->format()), to_skia_alpha_type(scaled_bitmap->format(), scaled_bitmap->alpha_type()), nullptr);
|
|
SkPixmap const scaled_sk_pixmap(scaled_info, scaled_bitmap->begin(), scaled_bitmap->pitch());
|
|
|
|
sk_sp<SkImage> source_sk_image = source_sk_bitmap.asImage();
|
|
if (!source_sk_image->scalePixels(scaled_sk_pixmap, to_skia_sampling_options(scaling_mode)))
|
|
return Error::from_string_literal("Unable to scale pixels for bitmap");
|
|
return scaled_bitmap;
|
|
}
|
|
|
|
ErrorOr<NonnullRefPtr<Bitmap>> Bitmap::to_bitmap_backed_by_anonymous_buffer() const
|
|
{
|
|
if (m_buffer.is_valid()) {
|
|
// FIXME: The const_cast here is awkward.
|
|
return NonnullRefPtr { const_cast<Bitmap&>(*this) };
|
|
}
|
|
auto buffer = TRY(Core::AnonymousBuffer::create_with_size(round_up_to_power_of_two(size_in_bytes(), PAGE_SIZE)));
|
|
auto bitmap = TRY(Bitmap::create_with_anonymous_buffer(format(), alpha_type(), move(buffer), size()));
|
|
memcpy(bitmap->scanline(0), scanline(0), size_in_bytes());
|
|
return bitmap;
|
|
}
|
|
|
|
Bitmap::~Bitmap()
|
|
{
|
|
if (m_destruction_callback)
|
|
m_destruction_callback();
|
|
m_data = nullptr;
|
|
}
|
|
|
|
void Bitmap::strip_alpha_channel()
|
|
{
|
|
VERIFY(m_format == BitmapFormat::BGRA8888 || m_format == BitmapFormat::BGRx8888);
|
|
for (ARGB32& pixel : *this)
|
|
pixel = 0xff000000 | (pixel & 0xffffff);
|
|
m_format = BitmapFormat::BGRx8888;
|
|
}
|
|
|
|
Gfx::ShareableBitmap Bitmap::to_shareable_bitmap() const
|
|
{
|
|
auto bitmap_or_error = to_bitmap_backed_by_anonymous_buffer();
|
|
if (bitmap_or_error.is_error())
|
|
return {};
|
|
return Gfx::ShareableBitmap { bitmap_or_error.release_value_but_fixme_should_propagate_errors(), Gfx::ShareableBitmap::ConstructWithKnownGoodBitmap };
|
|
}
|
|
|
|
ErrorOr<BackingStore> Bitmap::allocate_backing_store(BitmapFormat format, IntSize size, InitializeBackingStore initialize_backing_store)
|
|
{
|
|
if (size.is_empty())
|
|
return Error::from_string_literal("Gfx::Bitmap backing store size is empty");
|
|
|
|
if (size_would_overflow(format, size))
|
|
return Error::from_string_literal("Gfx::Bitmap backing store size overflow");
|
|
|
|
auto const pitch = minimum_pitch(size.width(), format);
|
|
auto const data_size_in_bytes = size_in_bytes(pitch, size.height());
|
|
|
|
void* data;
|
|
if (initialize_backing_store == InitializeBackingStore::Yes)
|
|
data = kcalloc(1, data_size_in_bytes);
|
|
else
|
|
data = kmalloc(data_size_in_bytes);
|
|
if (data == nullptr)
|
|
return Error::from_errno(errno);
|
|
return BackingStore { data, pitch, data_size_in_bytes };
|
|
}
|
|
|
|
Bitmap::DiffResult Bitmap::diff(Bitmap const& other) const
|
|
{
|
|
auto own_width = width();
|
|
auto own_height = height();
|
|
VERIFY(own_width == other.width() && own_height == other.height());
|
|
|
|
DiffResult result;
|
|
for (auto y = 0; y < own_height; ++y) {
|
|
for (auto x = 0; x < own_width; ++x) {
|
|
auto own_pixel = get_pixel(x, y);
|
|
auto other_pixel = other.get_pixel(x, y);
|
|
if (own_pixel == other_pixel)
|
|
continue;
|
|
|
|
++result.pixel_error_count;
|
|
|
|
u8 red_error = abs(static_cast<int>(own_pixel.red()) - other_pixel.red());
|
|
u8 green_error = abs(static_cast<int>(own_pixel.green()) - other_pixel.green());
|
|
u8 blue_error = abs(static_cast<int>(own_pixel.blue()) - other_pixel.blue());
|
|
u8 alpha_error = abs(static_cast<int>(own_pixel.alpha()) - other_pixel.alpha());
|
|
|
|
result.total_red_error += red_error;
|
|
result.total_green_error += green_error;
|
|
result.total_blue_error += blue_error;
|
|
result.total_alpha_error += alpha_error;
|
|
|
|
result.maximum_red_error = max(result.maximum_red_error, red_error);
|
|
result.maximum_green_error = max(result.maximum_green_error, green_error);
|
|
result.maximum_blue_error = max(result.maximum_blue_error, blue_error);
|
|
result.maximum_alpha_error = max(result.maximum_alpha_error, alpha_error);
|
|
}
|
|
}
|
|
|
|
result.identical = result.pixel_error_count == 0;
|
|
result.total_error = result.total_red_error + result.total_green_error + result.total_blue_error + result.total_alpha_error;
|
|
|
|
u8 maximum_red_green_error = max(result.maximum_red_error, result.maximum_green_error);
|
|
u8 maximum_blue_alpha_error = max(result.maximum_blue_error, result.maximum_alpha_error);
|
|
result.maximum_error = max(maximum_red_green_error, maximum_blue_alpha_error);
|
|
|
|
return result;
|
|
}
|
|
|
|
void Bitmap::set_alpha_type_destructive(AlphaType alpha_type)
|
|
{
|
|
if (alpha_type == m_alpha_type)
|
|
return;
|
|
|
|
#ifdef AK_OS_MACOS
|
|
vImage_Buffer buf { .data = m_data, .height = vImagePixelCount(height()), .width = vImagePixelCount(width()), .rowBytes = pitch() };
|
|
vImage_Error err;
|
|
if (m_alpha_type == AlphaType::Unpremultiplied) {
|
|
switch (m_format) {
|
|
case BitmapFormat::BGRA8888:
|
|
case BitmapFormat::BGRx8888:
|
|
err = vImagePremultiplyData_BGRA8888(&buf, &buf, kvImageNoFlags);
|
|
break;
|
|
case BitmapFormat::RGBA8888:
|
|
case BitmapFormat::RGBx8888:
|
|
err = vImagePremultiplyData_RGBA8888(&buf, &buf, kvImageNoFlags);
|
|
break;
|
|
default:
|
|
VERIFY_NOT_REACHED();
|
|
}
|
|
} else {
|
|
switch (m_format) {
|
|
case BitmapFormat::BGRA8888:
|
|
case BitmapFormat::BGRx8888:
|
|
err = vImageUnpremultiplyData_BGRA8888(&buf, &buf, kvImageNoFlags);
|
|
break;
|
|
case BitmapFormat::RGBA8888:
|
|
case BitmapFormat::RGBx8888:
|
|
err = vImageUnpremultiplyData_RGBA8888(&buf, &buf, kvImageNoFlags);
|
|
break;
|
|
default:
|
|
VERIFY_NOT_REACHED();
|
|
}
|
|
}
|
|
VERIFY(err == kvImageNoError);
|
|
#else
|
|
// FIXME: Make this fast on other platforms too.
|
|
if (m_alpha_type == AlphaType::Unpremultiplied) {
|
|
for (auto y = 0; y < height(); ++y) {
|
|
for (auto x = 0; x < width(); ++x)
|
|
set_pixel(x, y, get_pixel(x, y).to_premultiplied());
|
|
}
|
|
} else if (m_alpha_type == AlphaType::Premultiplied) {
|
|
for (auto y = 0; y < height(); ++y) {
|
|
for (auto x = 0; x < width(); ++x)
|
|
set_pixel(x, y, get_pixel(x, y).to_unpremultiplied());
|
|
}
|
|
} else {
|
|
VERIFY_NOT_REACHED();
|
|
}
|
|
#endif
|
|
m_alpha_type = alpha_type;
|
|
}
|
|
|
|
}
|