summaryrefslogtreecommitdiff
path: root/Userland/Libraries/LibGfx/Bitmap.cpp
diff options
context:
space:
mode:
authorNico Weber <thakis@chromium.org>2021-01-19 12:10:47 -0500
committerAndreas Kling <kling@serenityos.org>2021-01-20 10:28:27 +0100
commit5f9c42c404a5acfa66461a3c6568592f5326e9de (patch)
tree9d29f3caeea87721d8ec9a3d754e526bb5d253b9 /Userland/Libraries/LibGfx/Bitmap.cpp
parentc6726f331ea53bc7ee5679a84c6c10af65428a4b (diff)
downloadserenity-5f9c42c404a5acfa66461a3c6568592f5326e9de.zip
LibGfx: Give Bitmap a scale factor
Gfx::Bitmap can now store its scale factor. Normally it's 1, but in high dpi mode it can be 2. If a Bitmap with a scale factor of 2 is blitted to a Painter with scale factor of 2, the pixels can be copied over without any resampling. (When blitting a Bitmap with a scale factor of 1 to a Painter with scale factor of 2, the Bitmap is painted at twice its width and height at paint time. Blitting a Bitmap with a scale factor of 2 to a Painter with scale factor 1 is not supported.) A Bitmap with scale factor of 2 reports the same width() and height() as one with scale factor 1. That's important because many places in the codebase use a bitmap's width() and height() to layout Widgets, and all widget coordinates are in logical coordinates as well, per Documentation/HighDPI.md. Bitmap grows physical_width() / physical_height() to access the actual pixel size. Update a few callers that work with pixels to call this instead. Make Painter's constructor take its scale factor from the target bitmap that's passed in, and update its various blit() methods to handle blitting a 2x bitmap to a 2x painter. This allows removing some gnarly code in Compositor. (In return, put some new gnarly code in LibGfxScaleDemo to preserve behavior there.) No intended behavior change.
Diffstat (limited to 'Userland/Libraries/LibGfx/Bitmap.cpp')
-rw-r--r--Userland/Libraries/LibGfx/Bitmap.cpp119
1 files changed, 61 insertions, 58 deletions
diff --git a/Userland/Libraries/LibGfx/Bitmap.cpp b/Userland/Libraries/LibGfx/Bitmap.cpp
index 6296e4512c..291d30a9b3 100644
--- a/Userland/Libraries/LibGfx/Bitmap.cpp
+++ b/Userland/Libraries/LibGfx/Bitmap.cpp
@@ -56,7 +56,7 @@ struct BackingStore {
size_t size_in_bytes { 0 };
};
-size_t Bitmap::minimum_pitch(size_t width, BitmapFormat format)
+size_t Bitmap::minimum_pitch(size_t physical_width, BitmapFormat format)
{
size_t element_size;
switch (determine_storage_format(format)) {
@@ -71,73 +71,74 @@ size_t Bitmap::minimum_pitch(size_t width, BitmapFormat format)
ASSERT_NOT_REACHED();
}
- return width * element_size;
+ return physical_width * element_size;
}
-static bool size_would_overflow(BitmapFormat format, const IntSize& size)
+static bool size_would_overflow(BitmapFormat format, const IntSize& size, int scale_factor)
{
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() >= 32768 || size.height() >= 32768)
+ if (size.width() >= 32768 || size.height() >= 32768 || scale_factor < 1 || scale_factor > 4)
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());
+ size_t pitch = Bitmap::minimum_pitch(size.width() * scale_factor, format);
+ return Checked<size_t>::multiplication_would_overflow(pitch, size.height() * scale_factor);
}
-RefPtr<Bitmap> Bitmap::create(BitmapFormat format, const IntSize& size)
+RefPtr<Bitmap> Bitmap::create(BitmapFormat format, const IntSize& size, int scale_factor)
{
- auto backing_store = Bitmap::allocate_backing_store(format, size, Purgeable::No);
+ auto backing_store = Bitmap::allocate_backing_store(format, size, scale_factor, Purgeable::No);
if (!backing_store.has_value())
return nullptr;
- return adopt(*new Bitmap(format, size, Purgeable::No, backing_store.value()));
+ return adopt(*new Bitmap(format, size, scale_factor, Purgeable::No, backing_store.value()));
}
-RefPtr<Bitmap> Bitmap::create_purgeable(BitmapFormat format, const IntSize& size)
+RefPtr<Bitmap> Bitmap::create_purgeable(BitmapFormat format, const IntSize& size, int scale_factor)
{
- auto backing_store = Bitmap::allocate_backing_store(format, size, Purgeable::Yes);
+ auto backing_store = Bitmap::allocate_backing_store(format, size, scale_factor, Purgeable::Yes);
if (!backing_store.has_value())
return nullptr;
- return adopt(*new Bitmap(format, size, Purgeable::Yes, backing_store.value()));
+ return adopt(*new Bitmap(format, size, scale_factor, Purgeable::Yes, backing_store.value()));
}
#ifdef __serenity__
-RefPtr<Bitmap> Bitmap::create_shareable(BitmapFormat format, const IntSize& size)
+RefPtr<Bitmap> Bitmap::create_shareable(BitmapFormat format, const IntSize& size, int scale_factor)
{
- if (size_would_overflow(format, size))
+ if (size_would_overflow(format, size, scale_factor))
return nullptr;
- const auto pitch = minimum_pitch(size.width(), format);
- const auto data_size = size_in_bytes(pitch, size.height());
+ const auto pitch = minimum_pitch(size.width() * scale_factor, format);
+ const auto data_size = size_in_bytes(pitch, size.height() * scale_factor);
auto anon_fd = anon_create(round_up_to_power_of_two(data_size, PAGE_SIZE), O_CLOEXEC);
if (anon_fd < 0)
return nullptr;
- return Bitmap::create_with_anon_fd(format, anon_fd, size, {}, ShouldCloseAnonymousFile::No);
+ return Bitmap::create_with_anon_fd(format, anon_fd, size, scale_factor, {}, ShouldCloseAnonymousFile::No);
}
#endif
-Bitmap::Bitmap(BitmapFormat format, const IntSize& size, Purgeable purgeable, const BackingStore& backing_store)
+Bitmap::Bitmap(BitmapFormat format, const IntSize& size, int scale_factor, Purgeable purgeable, const BackingStore& backing_store)
: m_size(size)
+ , m_scale(scale_factor)
, m_data(backing_store.data)
, m_pitch(backing_store.pitch)
, m_format(format)
, m_purgeable(purgeable == Purgeable::Yes)
{
ASSERT(!m_size.is_empty());
- ASSERT(!size_would_overflow(format, size));
+ ASSERT(!size_would_overflow(format, size, scale_factor));
ASSERT(m_data);
ASSERT(backing_store.size_in_bytes == size_in_bytes());
allocate_palette_from_format(format, {});
m_needs_munmap = true;
}
-RefPtr<Bitmap> Bitmap::create_wrapper(BitmapFormat format, const IntSize& size, size_t pitch, void* data)
+RefPtr<Bitmap> Bitmap::create_wrapper(BitmapFormat format, const IntSize& size, int scale_factor, size_t pitch, void* data)
{
- if (size_would_overflow(format, size))
+ if (size_would_overflow(format, size, scale_factor))
return nullptr;
- return adopt(*new Bitmap(format, size, pitch, data));
+ return adopt(*new Bitmap(format, size, scale_factor, pitch, data));
}
RefPtr<Bitmap> Bitmap::load_from_file(const StringView& path)
@@ -151,30 +152,31 @@ RefPtr<Bitmap> Bitmap::load_from_file(const StringView& path)
return nullptr;
}
-Bitmap::Bitmap(BitmapFormat format, const IntSize& size, size_t pitch, void* data)
+Bitmap::Bitmap(BitmapFormat format, const IntSize& size, int scale_factor, size_t pitch, void* data)
: m_size(size)
+ , m_scale(scale_factor)
, m_data(data)
, m_pitch(pitch)
, m_format(format)
{
- ASSERT(pitch >= minimum_pitch(size.width(), format));
- ASSERT(!size_would_overflow(format, size));
+ ASSERT(pitch >= minimum_pitch(size.width() * scale_factor, format));
+ ASSERT(!size_would_overflow(format, size, scale_factor));
// FIXME: assert that `data` is actually long enough!
allocate_palette_from_format(format, {});
}
-static bool check_size(const IntSize& size, BitmapFormat format, unsigned actual_size)
+static bool check_size(const IntSize& size, int scale_factor, BitmapFormat format, unsigned actual_size)
{
-
// FIXME: Code duplication of size_in_bytes() and m_pitch
- unsigned expected_size_min = Bitmap::minimum_pitch(size.width(), format) * size.height();
+ unsigned expected_size_min = Bitmap::minimum_pitch(size.width() * scale_factor, format) * size.height() * scale_factor;
unsigned expected_size_max = round_up_to_power_of_two(expected_size_min, PAGE_SIZE);
if (expected_size_min > actual_size || actual_size > expected_size_max) {
// Getting here is most likely an error.
- dbgln("Constructing a shared bitmap for format {} and size {}, which demands {} bytes, which rounds up to at most {}.",
+ dbgln("Constructing a shared bitmap for format {} and size {} @ {}x, which demands {} bytes, which rounds up to at most {}.",
static_cast<int>(format),
size,
+ scale_factor,
expected_size_min,
expected_size_max);
@@ -184,7 +186,7 @@ static bool check_size(const IntSize& size, BitmapFormat format, unsigned actual
return true;
}
-RefPtr<Bitmap> Bitmap::create_with_anon_fd(BitmapFormat format, int anon_fd, const IntSize& size, const Vector<RGBA32>& palette, ShouldCloseAnonymousFile should_close_anon_fd)
+RefPtr<Bitmap> Bitmap::create_with_anon_fd(BitmapFormat format, int anon_fd, const IntSize& size, int scale_factor, const Vector<RGBA32>& palette, ShouldCloseAnonymousFile should_close_anon_fd)
{
void* data = nullptr;
{
@@ -197,11 +199,11 @@ RefPtr<Bitmap> Bitmap::create_with_anon_fd(BitmapFormat format, int anon_fd, con
}
};
- if (size_would_overflow(format, size))
+ if (size_would_overflow(format, size, scale_factor))
return nullptr;
- const auto pitch = minimum_pitch(size.width(), format);
- const auto data_size_in_bytes = size_in_bytes(pitch, size.height());
+ const auto pitch = minimum_pitch(size.width() * scale_factor, format);
+ const auto data_size_in_bytes = size_in_bytes(pitch, size.height() * scale_factor);
data = mmap(nullptr, round_up_to_power_of_two(data_size_in_bytes, PAGE_SIZE), PROT_READ | PROT_WRITE, MAP_FILE | MAP_SHARED, anon_fd, 0);
if (data == MAP_FAILED) {
@@ -210,13 +212,14 @@ RefPtr<Bitmap> Bitmap::create_with_anon_fd(BitmapFormat format, int anon_fd, con
}
}
- return adopt(*new Bitmap(format, anon_fd, size, data, palette));
+ return adopt(*new Bitmap(format, anon_fd, size, scale_factor, data, palette));
}
/// Read a bitmap as described by:
/// - actual size
/// - width
/// - height
+/// - scale_factor
/// - format
/// - palette count
/// - palette data (= palette count * RGBA32)
@@ -227,6 +230,7 @@ RefPtr<Bitmap> Bitmap::create_from_serialized_byte_buffer(ByteBuffer&& buffer)
unsigned actual_size;
unsigned width;
unsigned height;
+ unsigned scale_factor;
BitmapFormat format;
unsigned palette_size;
Vector<RGBA32> palette;
@@ -237,13 +241,13 @@ RefPtr<Bitmap> Bitmap::create_from_serialized_byte_buffer(ByteBuffer&& buffer)
return true;
};
- if (!read(actual_size) || !read(width) || !read(height) || !read(format) || !read(palette_size))
+ if (!read(actual_size) || !read(width) || !read(height) || !read(scale_factor) || !read(format) || !read(palette_size))
return nullptr;
if (format > BitmapFormat::RGBA32 || format < BitmapFormat::Indexed1)
return nullptr;
- if (!check_size({ width, height }, format, actual_size))
+ if (!check_size({ width, height }, scale_factor, format, actual_size))
return {};
palette.ensure_capacity(palette_size);
@@ -257,7 +261,7 @@ RefPtr<Bitmap> Bitmap::create_from_serialized_byte_buffer(ByteBuffer&& buffer)
auto data = stream.bytes().slice(stream.offset(), actual_size);
- auto bitmap = Bitmap::create(format, { width, height });
+ auto bitmap = Bitmap::create(format, { width, height }, scale_factor);
if (!bitmap)
return {};
@@ -282,7 +286,7 @@ ByteBuffer Bitmap::serialize_to_byte_buffer() const
auto palette = palette_to_vector();
- if (!write(size_in_bytes()) || !write((unsigned)size().width()) || !write((unsigned)size().height()) || !write(m_format) || !write((unsigned)palette.size()))
+ if (!write(size_in_bytes()) || !write((unsigned)size().width()) || !write((unsigned)size().height()) || !write((unsigned)scale()) || !write(m_format) || !write((unsigned)palette.size()))
return {};
for (auto& p : palette) {
@@ -298,17 +302,18 @@ ByteBuffer Bitmap::serialize_to_byte_buffer() const
return buffer;
}
-Bitmap::Bitmap(BitmapFormat format, int anon_fd, const IntSize& size, void* data, const Vector<RGBA32>& palette)
+Bitmap::Bitmap(BitmapFormat format, int anon_fd, const IntSize& size, int scale_factor, void* data, const Vector<RGBA32>& palette)
: m_size(size)
+ , m_scale(scale_factor)
, m_data(data)
- , m_pitch(minimum_pitch(size.width(), format))
+ , m_pitch(minimum_pitch(size.width() * scale_factor, format))
, m_format(format)
, m_needs_munmap(true)
, m_purgeable(true)
, m_anon_fd(anon_fd)
{
ASSERT(!is_indexed() || !palette.is_empty());
- ASSERT(!size_would_overflow(format, size));
+ ASSERT(!size_would_overflow(format, size, scale_factor));
if (is_indexed(m_format))
allocate_palette_from_format(m_format, palette);
@@ -318,9 +323,9 @@ RefPtr<Gfx::Bitmap> Bitmap::clone() const
{
RefPtr<Gfx::Bitmap> new_bitmap {};
if (m_purgeable) {
- new_bitmap = Bitmap::create_purgeable(format(), size());
+ new_bitmap = Bitmap::create_purgeable(format(), size(), scale());
} else {
- new_bitmap = Bitmap::create(format(), size());
+ new_bitmap = Bitmap::create(format(), size(), scale());
}
if (!new_bitmap) {
@@ -335,13 +340,12 @@ RefPtr<Gfx::Bitmap> Bitmap::clone() const
RefPtr<Gfx::Bitmap> Bitmap::rotated(Gfx::RotationDirection rotation_direction) const
{
- auto w = this->width();
- auto h = this->height();
-
- auto new_bitmap = Gfx::Bitmap::create(this->format(), { h, w });
+ auto new_bitmap = Gfx::Bitmap::create(this->format(), { height(), width() }, scale());
if (!new_bitmap)
return nullptr;
+ auto w = this->physical_width();
+ auto h = this->physical_height();
for (int i = 0; i < w; i++) {
for (int j = 0; j < h; j++) {
Color color;
@@ -359,13 +363,12 @@ RefPtr<Gfx::Bitmap> Bitmap::rotated(Gfx::RotationDirection rotation_direction) c
RefPtr<Gfx::Bitmap> Bitmap::flipped(Gfx::Orientation orientation) const
{
- auto w = this->width();
- auto h = this->height();
-
- auto new_bitmap = Gfx::Bitmap::create(this->format(), { w, h });
+ auto new_bitmap = Gfx::Bitmap::create(this->format(), { width(), height() }, scale());
if (!new_bitmap)
return nullptr;
+ auto w = this->physical_width();
+ auto h = this->physical_height();
for (int i = 0; i < w; i++) {
for (int j = 0; j < h; j++) {
Color color = this->get_pixel(i, j);
@@ -387,7 +390,7 @@ RefPtr<Bitmap> Bitmap::to_bitmap_backed_by_anon_fd() const
auto anon_fd = anon_create(round_up_to_power_of_two(size_in_bytes(), PAGE_SIZE), O_CLOEXEC);
if (anon_fd < 0)
return nullptr;
- auto bitmap = Bitmap::create_with_anon_fd(m_format, anon_fd, m_size, palette_to_vector(), ShouldCloseAnonymousFile::No);
+ auto bitmap = Bitmap::create_with_anon_fd(m_format, anon_fd, size(), scale(), palette_to_vector(), ShouldCloseAnonymousFile::No);
if (!bitmap)
return nullptr;
memcpy(bitmap->scanline(0), scanline(0), size_in_bytes());
@@ -420,9 +423,9 @@ void Bitmap::set_mmap_name([[maybe_unused]] const StringView& name)
void Bitmap::fill(Color color)
{
ASSERT(!is_indexed(m_format));
- for (int y = 0; y < height(); ++y) {
+ for (int y = 0; y < physical_height(); ++y) {
auto* scanline = this->scanline(y);
- fast_u32_fill(scanline, color.value(), width());
+ fast_u32_fill(scanline, color.value(), physical_width());
}
}
@@ -469,13 +472,13 @@ ShareableBitmap Bitmap::to_shareable_bitmap() const
}
#endif
-Optional<BackingStore> Bitmap::allocate_backing_store(BitmapFormat format, const IntSize& size, [[maybe_unused]] Purgeable purgeable)
+Optional<BackingStore> Bitmap::allocate_backing_store(BitmapFormat format, const IntSize& size, int scale_factor, [[maybe_unused]] Purgeable purgeable)
{
- if (size_would_overflow(format, size))
+ if (size_would_overflow(format, size, scale_factor))
return {};
- const auto pitch = minimum_pitch(size.width(), format);
- const auto data_size_in_bytes = size_in_bytes(pitch, size.height());
+ const auto pitch = minimum_pitch(size.width() * scale_factor, format);
+ const auto data_size_in_bytes = size_in_bytes(pitch, size.height() * scale_factor);
int map_flags = MAP_ANONYMOUS | MAP_PRIVATE;
if (purgeable == Purgeable::Yes)