LibGfx: Remove Bitmap and Painter "scale" concept

We don't need intrinsic scale factors for Gfx::Bitmap in Ladybird,
as everything flows through the CSS / device pixel ratio mechanism.

This patch also removes various unused functions instead of adapting
them to the change.
This commit is contained in:
Andreas Kling 2024-06-05 08:17:28 +02:00
parent d0afc3e643
commit 6a96920dbc
Notes: sideshowbarker 2024-07-17 07:20:49 +09:00
14 changed files with 125 additions and 507 deletions

View file

@ -126,12 +126,3 @@ TEST_CASE(0008_bitmap_downscaling_height1)
}
}
}
TEST_CASE(0009_serialize_and_deserialize_roundtrip)
{
auto original_bitmap = MUST(Gfx::Bitmap::create(Gfx::BitmapFormat::BGRx8888, Gfx::IntSize { 10, 10 }));
original_bitmap->fill(Gfx::Color::Red);
auto bytes = MUST(original_bitmap->serialize_to_byte_buffer());
auto bitmap = MUST(Gfx::Bitmap::create_from_serialized_bytes(bytes));
EXPECT(bitmap->visually_equals(original_bitmap));
}

View file

@ -316,10 +316,6 @@ FLATTEN AntiAliasingPainter::Range AntiAliasingPainter::draw_ellipse_part(
8-way symmetry, or an ellipse in two calls using 4-way symmetry.
*/
center *= m_underlying_painter.scale();
radius_a *= m_underlying_painter.scale();
radius_b *= m_underlying_painter.scale();
// If this is a ellipse everything can be drawn in one pass with 8 way symmetry
bool const is_circle = radius_a == radius_b;

View file

@ -11,19 +11,13 @@
#include <AK/LexicalPath.h>
#include <AK/Memory.h>
#include <AK/MemoryStream.h>
#include <AK/Optional.h>
#include <AK/Queue.h>
#include <AK/ScopeGuard.h>
#include <AK/Try.h>
#include <LibCore/File.h>
#include <LibCore/MappedFile.h>
#include <LibCore/MimeData.h>
#include <LibCore/System.h>
#include <LibGfx/Bitmap.h>
#include <LibGfx/ImageFormats/ImageDecoder.h>
#include <LibGfx/ShareableBitmap.h>
#include <errno.h>
#include <stdio.h>
namespace Gfx {
@ -33,7 +27,7 @@ struct BackingStore {
size_t size_in_bytes { 0 };
};
size_t Bitmap::minimum_pitch(size_t physical_width, BitmapFormat format)
size_t Bitmap::minimum_pitch(size_t width, BitmapFormat format)
{
size_t element_size;
switch (determine_storage_format(format)) {
@ -46,92 +40,62 @@ size_t Bitmap::minimum_pitch(size_t physical_width, BitmapFormat format)
VERIFY_NOT_REACHED();
}
return physical_width * element_size;
return width * element_size;
}
static bool size_would_overflow(BitmapFormat format, IntSize size, int scale_factor)
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 || scale_factor < 1 || scale_factor > 4)
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() * scale_factor, format);
return Checked<size_t>::multiplication_would_overflow(pitch, size.height() * scale_factor);
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, int scale_factor)
ErrorOr<NonnullRefPtr<Bitmap>> Bitmap::create(BitmapFormat format, IntSize size)
{
auto backing_store = TRY(Bitmap::allocate_backing_store(format, size, scale_factor));
return AK::adopt_nonnull_ref_or_enomem(new (nothrow) Bitmap(format, size, scale_factor, backing_store));
auto backing_store = TRY(Bitmap::allocate_backing_store(format, size));
return AK::adopt_nonnull_ref_or_enomem(new (nothrow) Bitmap(format, size, backing_store));
}
ErrorOr<NonnullRefPtr<Bitmap>> Bitmap::create_shareable(BitmapFormat format, IntSize size, int scale_factor)
ErrorOr<NonnullRefPtr<Bitmap>> Bitmap::create_shareable(BitmapFormat format, IntSize size)
{
if (size_would_overflow(format, size, scale_factor))
if (size_would_overflow(format, size))
return Error::from_string_literal("Gfx::Bitmap::create_shareable size overflow");
auto const pitch = minimum_pitch(size.width() * scale_factor, format);
auto const data_size = size_in_bytes(pitch, size.height() * scale_factor);
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, buffer, size, scale_factor));
auto bitmap = TRY(Bitmap::create_with_anonymous_buffer(format, buffer, size));
return bitmap;
}
Bitmap::Bitmap(BitmapFormat format, IntSize size, int scale_factor, BackingStore const& backing_store)
Bitmap::Bitmap(BitmapFormat format, IntSize size, BackingStore const& backing_store)
: m_size(size)
, m_scale(scale_factor)
, m_data(backing_store.data)
, m_pitch(backing_store.pitch)
, m_format(format)
{
VERIFY(!m_size.is_empty());
VERIFY(!size_would_overflow(format, size, scale_factor));
VERIFY(!size_would_overflow(format, size));
VERIFY(m_data);
VERIFY(backing_store.size_in_bytes == size_in_bytes());
m_data_is_malloced = true;
}
ErrorOr<NonnullRefPtr<Bitmap>> Bitmap::create_wrapper(BitmapFormat format, IntSize size, int scale_factor, size_t pitch, void* data)
ErrorOr<NonnullRefPtr<Bitmap>> Bitmap::create_wrapper(BitmapFormat format, IntSize size, size_t pitch, void* data)
{
if (size_would_overflow(format, size, scale_factor))
if (size_would_overflow(format, size))
return Error::from_string_literal("Gfx::Bitmap::create_wrapper size overflow");
return adopt_ref(*new Bitmap(format, size, scale_factor, pitch, data));
return adopt_ref(*new Bitmap(format, size, pitch, data));
}
ErrorOr<NonnullRefPtr<Bitmap>> Bitmap::load_from_file(StringView path, int scale_factor, Optional<IntSize> ideal_size)
ErrorOr<NonnullRefPtr<Bitmap>> Bitmap::load_from_file(StringView path, Optional<IntSize> ideal_size)
{
if (scale_factor > 1 && path.starts_with("/res/"sv)) {
auto load_scaled_bitmap = [](StringView path, int scale_factor, Optional<IntSize> ideal_size) -> ErrorOr<NonnullRefPtr<Bitmap>> {
LexicalPath lexical_path { path };
StringBuilder highdpi_icon_path;
TRY(highdpi_icon_path.try_appendff("{}/{}-{}x.{}", lexical_path.dirname(), lexical_path.title(), scale_factor, lexical_path.extension()));
auto highdpi_icon_string = highdpi_icon_path.string_view();
auto file = TRY(Core::File::open(highdpi_icon_string, Core::File::OpenMode::Read));
auto bitmap = TRY(load_from_file(move(file), highdpi_icon_string, ideal_size));
if (bitmap->width() % scale_factor != 0 || bitmap->height() % scale_factor != 0)
return Error::from_string_literal("Bitmap::load_from_file: HighDPI image size should be divisible by scale factor");
bitmap->m_size.set_width(bitmap->width() / scale_factor);
bitmap->m_size.set_height(bitmap->height() / scale_factor);
bitmap->m_scale = scale_factor;
return bitmap;
};
auto scaled_bitmap_or_error = load_scaled_bitmap(path, scale_factor, ideal_size);
if (!scaled_bitmap_or_error.is_error())
return scaled_bitmap_or_error.release_value();
auto error = scaled_bitmap_or_error.release_error();
if (!(error.is_syscall() && error.code() == ENOENT)) {
dbgln("Couldn't load scaled bitmap: {}", error);
dbgln("Trying base scale instead.");
}
}
auto file = TRY(Core::File::open(path, Core::File::OpenMode::Read));
return load_from_file(move(file), path, ideal_size);
}
@ -154,118 +118,38 @@ ErrorOr<NonnullRefPtr<Bitmap>> Bitmap::load_from_bytes(ReadonlyBytes bytes, Opti
return Error::from_string_literal("Gfx::Bitmap unable to load from file");
}
Bitmap::Bitmap(BitmapFormat format, IntSize size, int scale_factor, size_t pitch, void* data)
Bitmap::Bitmap(BitmapFormat format, IntSize size, size_t pitch, void* data)
: m_size(size)
, m_scale(scale_factor)
, m_data(data)
, m_pitch(pitch)
, m_format(format)
{
VERIFY(pitch >= minimum_pitch(size.width() * scale_factor, format));
VERIFY(!size_would_overflow(format, size, scale_factor));
VERIFY(pitch >= minimum_pitch(size.width(), format));
VERIFY(!size_would_overflow(format, size));
// FIXME: assert that `data` is actually long enough!
}
static bool check_size(IntSize size, int scale_factor, BitmapFormat format, unsigned actual_size)
ErrorOr<NonnullRefPtr<Bitmap>> Bitmap::create_with_anonymous_buffer(BitmapFormat format, Core::AnonymousBuffer buffer, IntSize size)
{
// FIXME: Code duplication of size_in_bytes() and m_pitch
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 {} @ {}x, which demands {} bytes, which rounds up to at most {}.",
static_cast<int>(format),
size,
scale_factor,
expected_size_min,
expected_size_max);
dbgln("However, we were given {} bytes, which is outside this range?! Refusing cowardly.", actual_size);
return false;
}
return true;
}
ErrorOr<NonnullRefPtr<Bitmap>> Bitmap::create_with_anonymous_buffer(BitmapFormat format, Core::AnonymousBuffer buffer, IntSize size, int scale_factor)
{
if (size_would_overflow(format, size, scale_factor))
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, move(buffer), size, scale_factor));
return adopt_nonnull_ref_or_enomem(new (nothrow) Bitmap(format, move(buffer), size));
}
ErrorOr<NonnullRefPtr<Bitmap>> Bitmap::create_from_serialized_byte_buffer(ByteBuffer&& buffer)
{
return create_from_serialized_bytes(buffer.bytes());
}
/// Read a bitmap as described by:
/// - actual size
/// - width
/// - height
/// - scale_factor
/// - format
/// - image data (= actual size * u8)
ErrorOr<NonnullRefPtr<Bitmap>> Bitmap::create_from_serialized_bytes(ReadonlyBytes bytes)
{
FixedMemoryStream stream { bytes };
auto actual_size = TRY(stream.read_value<size_t>());
auto width = TRY(stream.read_value<unsigned>());
auto height = TRY(stream.read_value<unsigned>());
auto scale_factor = TRY(stream.read_value<unsigned>());
auto format = TRY(stream.read_value<BitmapFormat>());
if (format > BitmapFormat::LastValid || format < BitmapFormat::FirstValid)
return Error::from_string_literal("Gfx::Bitmap::create_from_serialized_byte_buffer: decode failed");
if (!check_size({ width, height }, scale_factor, format, actual_size))
return Error::from_string_literal("Gfx::Bitmap::create_from_serialized_byte_buffer: decode failed");
if (TRY(stream.size()) - TRY(stream.tell()) < actual_size)
return Error::from_string_literal("Gfx::Bitmap::create_from_serialized_byte_buffer: decode failed");
auto data = bytes.slice(TRY(stream.tell()), actual_size);
auto bitmap = TRY(Bitmap::create(format, { width, height }, scale_factor));
data.copy_to({ bitmap->scanline(0), bitmap->size_in_bytes() });
return bitmap;
}
ErrorOr<ByteBuffer> Bitmap::serialize_to_byte_buffer() const
{
auto buffer = TRY(ByteBuffer::create_uninitialized(sizeof(size_t) + 3 * sizeof(unsigned) + sizeof(BitmapFormat) + size_in_bytes()));
FixedMemoryStream stream { buffer.span() };
TRY(stream.write_value(size_in_bytes()));
TRY(stream.write_value<unsigned>(size().width()));
TRY(stream.write_value<unsigned>(size().height()));
TRY(stream.write_value<unsigned>(scale()));
TRY(stream.write_value(m_format));
auto size = size_in_bytes();
TRY(stream.write_until_depleted({ scanline(0), size }));
VERIFY(TRY(stream.tell()) == TRY(stream.size()));
return buffer;
}
Bitmap::Bitmap(BitmapFormat format, Core::AnonymousBuffer buffer, IntSize size, int scale_factor)
Bitmap::Bitmap(BitmapFormat format, Core::AnonymousBuffer buffer, IntSize size)
: m_size(size)
, m_scale(scale_factor)
, m_data(buffer.data<void>())
, m_pitch(minimum_pitch(size.width() * scale_factor, format))
, m_pitch(minimum_pitch(size.width(), format))
, m_format(format)
, m_buffer(move(buffer))
{
VERIFY(!size_would_overflow(format, size, scale_factor));
VERIFY(!size_would_overflow(format, size));
}
ErrorOr<NonnullRefPtr<Gfx::Bitmap>> Bitmap::clone() const
{
auto new_bitmap = TRY(Bitmap::create(format(), size(), scale()));
auto new_bitmap = TRY(Bitmap::create(format(), size()));
VERIFY(size_in_bytes() == new_bitmap->size_in_bytes());
memcpy(new_bitmap->scanline(0), scanline(0), size_in_bytes());
@ -273,59 +157,6 @@ ErrorOr<NonnullRefPtr<Gfx::Bitmap>> Bitmap::clone() const
return new_bitmap;
}
ErrorOr<NonnullRefPtr<Gfx::Bitmap>> Bitmap::rotated(Gfx::RotationDirection rotation_direction) const
{
if (rotation_direction == Gfx::RotationDirection::Flip) {
auto new_bitmap = TRY(Gfx::Bitmap::create(format(), { width(), height() }, scale()));
auto w = this->physical_width();
auto h = this->physical_height();
for (int i = 0; i < w; i++) {
for (int j = 0; j < h; j++)
new_bitmap->set_pixel(w - i - 1, h - j - 1, this->get_pixel(i, j));
}
return new_bitmap;
}
auto new_bitmap = TRY(Gfx::Bitmap::create(this->format(), { height(), width() }, scale()));
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;
if (rotation_direction == Gfx::RotationDirection::CounterClockwise)
color = this->get_pixel(w - i - 1, j);
else
color = this->get_pixel(i, h - j - 1);
new_bitmap->set_pixel(j, i, color);
}
}
return new_bitmap;
}
ErrorOr<NonnullRefPtr<Gfx::Bitmap>> Bitmap::flipped(Gfx::Orientation orientation) const
{
auto new_bitmap = TRY(Gfx::Bitmap::create(this->format(), { width(), height() }, scale()));
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);
if (orientation == Orientation::Vertical)
new_bitmap->set_pixel(i, h - j - 1, color);
else
new_bitmap->set_pixel(w - i - 1, j, color);
}
}
return new_bitmap;
}
void Bitmap::apply_mask(Gfx::Bitmap const& mask, MaskKind mask_kind)
{
VERIFY(size() == mask.size());
@ -351,10 +182,10 @@ ErrorOr<NonnullRefPtr<Gfx::Bitmap>> Bitmap::scaled(int sx, int sy) const
if (sx == 1 && sy == 1)
return clone();
auto new_bitmap = TRY(Gfx::Bitmap::create(format(), { width() * sx, height() * sy }, scale()));
auto new_bitmap = TRY(Gfx::Bitmap::create(format(), { width() * sx, height() * sy }));
auto old_width = physical_width();
auto old_height = physical_height();
auto old_width = width();
auto old_height = height();
for (int y = 0; y < old_height; y++) {
for (int x = 0; x < old_width; x++) {
@ -387,12 +218,12 @@ ErrorOr<NonnullRefPtr<Gfx::Bitmap>> Bitmap::scaled(float sx, float sy) const
// http://fourier.eng.hmc.edu/e161/lectures/resize/node3.html
ErrorOr<NonnullRefPtr<Gfx::Bitmap>> Bitmap::scaled_to_size(Gfx::IntSize size) const
{
auto new_bitmap = TRY(Gfx::Bitmap::create(format(), size, scale()));
auto new_bitmap = TRY(Gfx::Bitmap::create(format(), size));
auto old_width = physical_width();
auto old_height = physical_height();
auto new_width = new_bitmap->physical_width();
auto new_height = new_bitmap->physical_height();
auto old_width = width();
auto old_height = height();
auto new_width = new_bitmap->width();
auto new_height = new_bitmap->height();
if (old_width == 1 && old_height == 1) {
new_bitmap->fill(get_pixel(0, 0));
@ -457,7 +288,7 @@ ErrorOr<NonnullRefPtr<Gfx::Bitmap>> Bitmap::scaled_to_size(Gfx::IntSize size) co
}
// Bottom-right pixel
new_bitmap->set_pixel(new_width - 1, new_height - 1, get_pixel(physical_width() - 1, physical_height() - 1));
new_bitmap->set_pixel(new_width - 1, new_height - 1, get_pixel(width() - 1, height() - 1));
return new_bitmap;
} else if (old_height == 1) {
// Copy horizontal strip multiple times (excluding last pixel to out of bounds).
@ -477,7 +308,7 @@ ErrorOr<NonnullRefPtr<Gfx::Bitmap>> Bitmap::scaled_to_size(Gfx::IntSize size) co
}
for (int new_bottom_y = 0; new_bottom_y < new_height; new_bottom_y++) {
// Copy last pixel of horizontal strip
new_bitmap->set_pixel(new_width - 1, new_bottom_y, get_pixel(physical_width() - 1, old_bottom_y));
new_bitmap->set_pixel(new_width - 1, new_bottom_y, get_pixel(width() - 1, old_bottom_y));
}
return new_bitmap;
} else if (old_width == 1) {
@ -499,7 +330,7 @@ ErrorOr<NonnullRefPtr<Gfx::Bitmap>> Bitmap::scaled_to_size(Gfx::IntSize size) co
}
for (int new_right_x = 0; new_right_x < new_width; new_right_x++) {
// Copy last pixel of vertical strip
new_bitmap->set_pixel(new_right_x, new_height - 1, get_pixel(old_right_x, physical_height() - 1));
new_bitmap->set_pixel(new_right_x, new_height - 1, get_pixel(old_right_x, height() - 1));
}
}
return new_bitmap;
@ -507,14 +338,13 @@ ErrorOr<NonnullRefPtr<Gfx::Bitmap>> Bitmap::scaled_to_size(Gfx::IntSize size) co
ErrorOr<NonnullRefPtr<Gfx::Bitmap>> Bitmap::cropped(Gfx::IntRect crop, Optional<BitmapFormat> new_bitmap_format) const
{
auto new_bitmap = TRY(Gfx::Bitmap::create(new_bitmap_format.value_or(format()), { crop.width(), crop.height() }, scale()));
auto scaled_crop = crop * scale();
auto new_bitmap = TRY(Gfx::Bitmap::create(new_bitmap_format.value_or(format()), { crop.width(), crop.height() }));
for (int y = 0; y < scaled_crop.height(); ++y) {
for (int x = 0; x < scaled_crop.width(); ++x) {
int global_x = x + scaled_crop.left();
int global_y = y + scaled_crop.top();
if (global_x >= physical_width() || global_y >= physical_height() || global_x < 0 || global_y < 0) {
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, Gfx::Color::Black);
} else {
new_bitmap->set_pixel(x, y, get_pixel(global_x, global_y));
@ -531,21 +361,11 @@ ErrorOr<NonnullRefPtr<Bitmap>> Bitmap::to_bitmap_backed_by_anonymous_buffer() co
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(m_format, move(buffer), size(), scale()));
auto bitmap = TRY(Bitmap::create_with_anonymous_buffer(m_format, move(buffer), size()));
memcpy(bitmap->scanline(0), scanline(0), size_in_bytes());
return bitmap;
}
ErrorOr<NonnullRefPtr<Gfx::Bitmap>> Bitmap::inverted() const
{
auto inverted_bitmap = TRY(clone());
for (auto y = 0; y < height(); y++) {
for (auto x = 0; x < width(); x++)
inverted_bitmap->set_pixel(x, y, get_pixel(x, y).inverted());
}
return inverted_bitmap;
}
Bitmap::~Bitmap()
{
if (m_data_is_malloced) {
@ -564,9 +384,9 @@ void Bitmap::strip_alpha_channel()
void Bitmap::fill(Color color)
{
for (int y = 0; y < physical_height(); ++y) {
for (int y = 0; y < height(); ++y) {
auto* scanline = this->scanline(y);
fast_u32_fill(scanline, color.value(), physical_width());
fast_u32_fill(scanline, color.value(), width());
}
}
@ -578,16 +398,16 @@ Gfx::ShareableBitmap Bitmap::to_shareable_bitmap() const
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, int scale_factor)
ErrorOr<BackingStore> Bitmap::allocate_backing_store(BitmapFormat format, IntSize size)
{
if (size.is_empty())
return Error::from_string_literal("Gfx::Bitmap backing store size is empty");
if (size_would_overflow(format, size, scale_factor))
if (size_would_overflow(format, size))
return Error::from_string_literal("Gfx::Bitmap backing store size overflow");
auto const pitch = minimum_pitch(size.width() * scale_factor, format);
auto const data_size_in_bytes = size_in_bytes(pitch, size.height() * scale_factor);
auto const pitch = minimum_pitch(size.width(), format);
auto const data_size_in_bytes = size_in_bytes(pitch, size.height());
void* data = kcalloc(1, data_size_in_bytes);
if (data == nullptr)
@ -612,67 +432,4 @@ bool Bitmap::visually_equals(Bitmap const& other) const
return true;
}
Optional<Color> Bitmap::solid_color(u8 alpha_threshold) const
{
Optional<Color> color;
for (auto y = 0; y < height(); ++y) {
for (auto x = 0; x < width(); ++x) {
auto const& pixel = get_pixel(x, y);
if (has_alpha_channel() && pixel.alpha() <= alpha_threshold)
continue;
if (!color.has_value())
color = pixel;
else if (pixel != color)
return {};
}
}
return color;
}
void Bitmap::flood_visit_from_point(Gfx::IntPoint start_point, int threshold,
Function<void(Gfx::IntPoint location)> pixel_reached)
{
VERIFY(rect().contains(start_point));
auto target_color = get_pixel(start_point.x(), start_point.y());
float threshold_normalized_squared = (threshold / 100.0f) * (threshold / 100.0f);
Queue<Gfx::IntPoint> points_to_visit = Queue<Gfx::IntPoint>();
points_to_visit.enqueue(start_point);
pixel_reached(start_point);
auto flood_mask = AK::Bitmap::create(width() * height(), false).release_value_but_fixme_should_propagate_errors();
flood_mask.set(width() * start_point.y() + start_point.x(), true);
// This implements a non-recursive flood fill. This is a breadth-first search of paintable neighbors
// As we find neighbors that are reachable we call the location_reached callback, add them to the queue, and mark them in the mask
while (!points_to_visit.is_empty()) {
auto current_point = points_to_visit.dequeue();
auto candidate_points = Array {
current_point.moved_left(1),
current_point.moved_right(1),
current_point.moved_up(1),
current_point.moved_down(1)
};
for (auto candidate_point : candidate_points) {
auto flood_mask_index = width() * candidate_point.y() + candidate_point.x();
if (!rect().contains(candidate_point))
continue;
auto pixel_color = get_pixel<Gfx::StorageFormat::BGRA8888>(candidate_point.x(), candidate_point.y());
auto can_paint = pixel_color.distance_squared_to(target_color) <= threshold_normalized_squared;
if (flood_mask.get(flood_mask_index) == false && can_paint) {
points_to_visit.enqueue(candidate_point);
pixel_reached(candidate_point);
}
flood_mask.set(flood_mask_index, true);
}
}
}
}

View file

@ -96,42 +96,24 @@ enum class RotationDirection {
class Bitmap : public RefCounted<Bitmap> {
public:
[[nodiscard]] static ErrorOr<NonnullRefPtr<Bitmap>> create(BitmapFormat, IntSize, int intrinsic_scale = 1);
[[nodiscard]] static ErrorOr<NonnullRefPtr<Bitmap>> create_shareable(BitmapFormat, IntSize, int intrinsic_scale = 1);
[[nodiscard]] static ErrorOr<NonnullRefPtr<Bitmap>> create_wrapper(BitmapFormat, IntSize, int intrinsic_scale, size_t pitch, void*);
[[nodiscard]] static ErrorOr<NonnullRefPtr<Bitmap>> load_from_file(StringView path, int scale_factor = 1, Optional<IntSize> ideal_size = {});
[[nodiscard]] static ErrorOr<NonnullRefPtr<Bitmap>> create(BitmapFormat, IntSize);
[[nodiscard]] static ErrorOr<NonnullRefPtr<Bitmap>> create_shareable(BitmapFormat, IntSize);
[[nodiscard]] static ErrorOr<NonnullRefPtr<Bitmap>> create_wrapper(BitmapFormat, IntSize, size_t pitch, void*);
[[nodiscard]] static ErrorOr<NonnullRefPtr<Bitmap>> load_from_file(StringView path, Optional<IntSize> ideal_size = {});
[[nodiscard]] static ErrorOr<NonnullRefPtr<Bitmap>> load_from_file(NonnullOwnPtr<Core::File>, StringView path, Optional<IntSize> ideal_size = {});
[[nodiscard]] static ErrorOr<NonnullRefPtr<Bitmap>> load_from_bytes(ReadonlyBytes, Optional<IntSize> ideal_size = {}, Optional<ByteString> mine_type = {});
[[nodiscard]] static ErrorOr<NonnullRefPtr<Bitmap>> create_with_anonymous_buffer(BitmapFormat, Core::AnonymousBuffer, IntSize, int intrinsic_scale);
static ErrorOr<NonnullRefPtr<Bitmap>> create_from_serialized_bytes(ReadonlyBytes);
static ErrorOr<NonnullRefPtr<Bitmap>> create_from_serialized_byte_buffer(ByteBuffer&&);
static bool is_path_a_supported_image_format(StringView path)
{
#define __ENUMERATE_IMAGE_FORMAT(Name, Ext) \
if (path.ends_with(Ext##sv, CaseSensitivity::CaseInsensitive)) \
return true;
ENUMERATE_IMAGE_FORMATS
#undef __ENUMERATE_IMAGE_FORMAT
return false;
}
[[nodiscard]] static ErrorOr<NonnullRefPtr<Bitmap>> create_with_anonymous_buffer(BitmapFormat, Core::AnonymousBuffer, IntSize);
ErrorOr<NonnullRefPtr<Gfx::Bitmap>> clone() const;
ErrorOr<NonnullRefPtr<Gfx::Bitmap>> rotated(Gfx::RotationDirection) const;
ErrorOr<NonnullRefPtr<Gfx::Bitmap>> flipped(Gfx::Orientation) const;
ErrorOr<NonnullRefPtr<Gfx::Bitmap>> scaled(int sx, int sy) const;
ErrorOr<NonnullRefPtr<Gfx::Bitmap>> scaled(float sx, float sy) const;
ErrorOr<NonnullRefPtr<Gfx::Bitmap>> scaled_to_size(Gfx::IntSize) const;
ErrorOr<NonnullRefPtr<Gfx::Bitmap>> cropped(Gfx::IntRect, Optional<BitmapFormat> new_bitmap_format = {}) const;
ErrorOr<NonnullRefPtr<Gfx::Bitmap>> to_bitmap_backed_by_anonymous_buffer() const;
[[nodiscard]] ErrorOr<ByteBuffer> serialize_to_byte_buffer() const;
[[nodiscard]] ShareableBitmap to_shareable_bitmap() const;
ErrorOr<NonnullRefPtr<Gfx::Bitmap>> inverted() const;
enum class MaskKind {
Alpha,
Luminance
@ -156,12 +138,7 @@ public:
[[nodiscard]] IntSize size() const { return m_size; }
[[nodiscard]] int width() const { return m_size.width(); }
[[nodiscard]] int height() const { return m_size.height(); }
[[nodiscard]] int scale() const { return m_scale; }
[[nodiscard]] IntRect physical_rect() const { return rect() * scale(); }
[[nodiscard]] IntSize physical_size() const { return size() * scale(); }
[[nodiscard]] int physical_width() const { return physical_size().width(); }
[[nodiscard]] int physical_height() const { return physical_size().height(); }
[[nodiscard]] size_t pitch() const { return m_pitch; }
[[nodiscard]] static unsigned bpp_for_format(BitmapFormat format)
@ -177,7 +154,7 @@ public:
}
}
[[nodiscard]] static size_t minimum_pitch(size_t physical_width, BitmapFormat);
[[nodiscard]] static size_t minimum_pitch(size_t width, BitmapFormat);
[[nodiscard]] unsigned bpp() const
{
@ -187,27 +164,13 @@ public:
void fill(Color);
[[nodiscard]] bool has_alpha_channel() const { return m_format == BitmapFormat::BGRA8888 || m_format == BitmapFormat::RGBA8888; }
void add_alpha_channel()
{
switch (m_format) {
case BitmapFormat::BGRx8888:
m_format = BitmapFormat::BGRA8888;
break;
case BitmapFormat::RGBA8888:
case BitmapFormat::BGRA8888:
// Nothing to do.
break;
case BitmapFormat::Invalid:
VERIFY_NOT_REACHED();
}
}
[[nodiscard]] BitmapFormat format() const { return m_format; }
// Call only for BGRx8888 and BGRA8888 bitmaps.
void strip_alpha_channel();
[[nodiscard]] static constexpr size_t size_in_bytes(size_t pitch, int physical_height) { return pitch * physical_height; }
[[nodiscard]] size_t size_in_bytes() const { return size_in_bytes(m_pitch, physical_height()); }
[[nodiscard]] static constexpr size_t size_in_bytes(size_t pitch, int height) { return pitch * height; }
[[nodiscard]] size_t size_in_bytes() const { return size_in_bytes(m_pitch, height()); }
template<StorageFormat>
[[nodiscard]] Color get_pixel(int physical_x, int physical_y) const;
@ -230,19 +193,14 @@ public:
[[nodiscard]] bool visually_equals(Bitmap const&) const;
[[nodiscard]] Optional<Color> solid_color(u8 alpha_threshold = 0) const;
void flood_visit_from_point(Gfx::IntPoint start_point, int threshold, Function<void(Gfx::IntPoint location)> pixel_reached);
private:
Bitmap(BitmapFormat, IntSize, int, BackingStore const&);
Bitmap(BitmapFormat, IntSize, int, size_t pitch, void*);
Bitmap(BitmapFormat, Core::AnonymousBuffer, IntSize, int);
Bitmap(BitmapFormat, IntSize, BackingStore const&);
Bitmap(BitmapFormat, IntSize, size_t pitch, void*);
Bitmap(BitmapFormat, Core::AnonymousBuffer, IntSize);
static ErrorOr<BackingStore> allocate_backing_store(BitmapFormat format, IntSize size, int scale_factor);
static ErrorOr<BackingStore> allocate_backing_store(BitmapFormat format, IntSize size);
IntSize m_size;
int m_scale;
void* m_data { nullptr };
size_t m_pitch { 0 };
BitmapFormat m_format { BitmapFormat::Invalid };
@ -253,14 +211,14 @@ private:
ALWAYS_INLINE u8* Bitmap::scanline_u8(int y)
{
VERIFY(y >= 0);
VERIFY(y < physical_height());
VERIFY(y < height());
return reinterpret_cast<u8*>(m_data) + (y * m_pitch);
}
ALWAYS_INLINE u8 const* Bitmap::scanline_u8(int y) const
{
VERIFY(y >= 0);
VERIFY(y < physical_height());
VERIFY(y < height());
return reinterpret_cast<u8 const*>(m_data) + (y * m_pitch);
}
@ -303,7 +261,7 @@ template<>
ALWAYS_INLINE Color Bitmap::get_pixel<StorageFormat::BGRx8888>(int x, int y) const
{
VERIFY(x >= 0);
VERIFY(x < physical_width());
VERIFY(x < width());
return Color::from_rgb(scanline(y)[x]);
}
@ -311,7 +269,7 @@ template<>
ALWAYS_INLINE Color Bitmap::get_pixel<StorageFormat::BGRA8888>(int x, int y) const
{
VERIFY(x >= 0);
VERIFY(x < physical_width());
VERIFY(x < width());
return Color::from_argb(scanline(y)[x]);
}
@ -331,7 +289,7 @@ template<>
ALWAYS_INLINE void Bitmap::set_pixel<StorageFormat::BGRx8888>(int x, int y, Color color)
{
VERIFY(x >= 0);
VERIFY(x < physical_width());
VERIFY(x < width());
scanline(y)[x] = color.value();
}
@ -339,7 +297,7 @@ template<>
ALWAYS_INLINE void Bitmap::set_pixel<StorageFormat::BGRA8888>(int x, int y, Color color)
{
VERIFY(x >= 0);
VERIFY(x < physical_width());
VERIFY(x < width());
scanline(y)[x] = color.value(); // drop alpha
}
@ -347,7 +305,7 @@ template<>
ALWAYS_INLINE void Bitmap::set_pixel<StorageFormat::RGBA8888>(int x, int y, Color color)
{
VERIFY(x >= 0);
VERIFY(x < physical_width());
VERIFY(x < width());
// FIXME: There's a lot of inaccurately named functions in the Color class right now (RGBA vs BGRA),
// clear those up and then make this more convenient.
auto rgba = (color.alpha() << 24) | (color.blue() << 16) | (color.green() << 8) | color.red();

View file

@ -124,9 +124,6 @@ void EdgeFlagPathRasterizer<SamplesPerPixel>::fill(Painter& painter, Path const&
template<unsigned SamplesPerPixel>
void EdgeFlagPathRasterizer<SamplesPerPixel>::fill_internal(Painter& painter, Path const& path, auto color_or_function, Painter::WindingRule winding_rule, FloatPoint offset)
{
// FIXME: Figure out how painter scaling works here...
VERIFY(painter.scale() == 1);
auto bounding_box = enclosing_int_rect(path.bounding_box().translated(offset));
auto dest_rect = bounding_box.translated(painter.translation());
auto origin = bounding_box.top_left().to_type<float>() - offset;

View file

@ -129,7 +129,7 @@ public:
void paint_into_physical_rect(Painter& painter, IntRect rect, auto location_transform)
{
auto clipped_rect = rect.intersected(painter.clip_rect() * painter.scale());
auto clipped_rect = rect.intersected(painter.clip_rect());
auto start_offset = clipped_rect.location() - rect.location();
for (int y = 0; y < clipped_rect.height(); y++) {
for (int x = 0; x < clipped_rect.width(); x++) {
@ -279,7 +279,7 @@ static auto create_radial_gradient(IntRect const& physical_rect, ReadonlySpan<Co
void Painter::fill_rect_with_linear_gradient(IntRect const& rect, ReadonlySpan<ColorStop> color_stops, float angle, Optional<float> repeat_length)
{
auto a_rect = to_physical(rect);
if (a_rect.intersected(clip_rect() * scale()).is_empty())
if (a_rect.intersected(clip_rect()).is_empty())
return;
auto linear_gradient = create_linear_gradient(a_rect, color_stops, angle, repeat_length);
linear_gradient.paint(*this, a_rect);
@ -293,10 +293,10 @@ static FloatPoint pixel_center(IntPoint point)
void Painter::fill_rect_with_conic_gradient(IntRect const& rect, ReadonlySpan<ColorStop> color_stops, IntPoint center, float start_angle, Optional<float> repeat_length)
{
auto a_rect = to_physical(rect);
if (a_rect.intersected(clip_rect() * scale()).is_empty())
if (a_rect.intersected(clip_rect()).is_empty())
return;
// Translate position/center to the center of the pixel (avoids some funky painting)
auto center_point = pixel_center(center * scale());
auto center_point = pixel_center(center);
auto conic_gradient = create_conic_gradient(color_stops, center_point, start_angle, repeat_length);
conic_gradient.paint(*this, a_rect);
}
@ -304,10 +304,10 @@ void Painter::fill_rect_with_conic_gradient(IntRect const& rect, ReadonlySpan<Co
void Painter::fill_rect_with_radial_gradient(IntRect const& rect, ReadonlySpan<ColorStop> color_stops, IntPoint center, IntSize size, Optional<float> repeat_length, Optional<float> rotation_angle)
{
auto a_rect = to_physical(rect);
if (a_rect.intersected(clip_rect() * scale()).is_empty())
if (a_rect.intersected(clip_rect()).is_empty())
return;
auto radial_gradient = create_radial_gradient(a_rect, color_stops, center * scale(), size * scale(), repeat_length, rotation_angle);
auto radial_gradient = create_radial_gradient(a_rect, color_stops, center, size, repeat_length, rotation_angle);
radial_gradient.paint(*this, a_rect);
}

View file

@ -34,10 +34,6 @@ static Gfx::IntRect rect_where_pixels_are_different(Bitmap const& a, Bitmap cons
{
VERIFY(a.size() == b.size());
// FIXME: This works on physical pixels.
VERIFY(a.scale() == 1);
VERIFY(b.scale() == 1);
int number_of_equal_pixels_at_top = 0;
while (number_of_equal_pixels_at_top < a.height() && are_scanlines_equal(a, b, number_of_equal_pixels_at_top))
++number_of_equal_pixels_at_top;

View file

@ -48,9 +48,9 @@ static ErrorOr<ByteBuffer> write_pixel_data(Bitmap const& bitmap, int pixel_row_
auto buffer = TRY(ByteBuffer::create_uninitialized(image_size));
int current_row = 0;
for (int y = bitmap.physical_height() - 1; y >= 0; --y) {
for (int y = bitmap.height() - 1; y >= 0; --y) {
auto* row = buffer.data() + (pixel_row_data_size * current_row++);
for (int x = 0; x < bitmap.physical_width(); x++) {
for (int x = 0; x < bitmap.width(); x++) {
auto pixel = bitmap.get_pixel(x, y);
row[x * bytes_per_pixel + 0] = pixel.blue();
row[x * bytes_per_pixel + 1] = pixel.green();

View file

@ -366,7 +366,7 @@ static ErrorOr<NonnullRefPtr<Bitmap>> decode_webp_chunk_VP8L_image(ImageKind ima
// "The smallest distance codes [1..120] are special, and are reserved for a close neighborhood of the current pixel."
if (distance <= 120) {
auto offset = distance_map[distance - 1];
distance = offset.x + offset.y * bitmap->physical_width();
distance = offset.x + offset.y * bitmap->width();
if (distance < 1)
distance = 1;
} else {

View file

@ -1287,7 +1287,7 @@ ErrorOr<NonnullRefPtr<Bitmap>> decode_webp_chunk_VP8_contents(VP8Header const& v
auto width = static_cast<int>(vp8_header.width);
auto height = static_cast<int>(vp8_header.height);
if (bitmap->physical_size() == IntSize { width, height })
if (bitmap->size() == IntSize { width, height })
return bitmap;
return bitmap->cropped({ 0, 0, width, height });
}

View file

@ -54,13 +54,9 @@ ALWAYS_INLINE Color get_pixel(Gfx::Bitmap const& bitmap, int x, int y)
Painter::Painter(Gfx::Bitmap& bitmap)
: m_target(bitmap)
{
int scale = bitmap.scale();
VERIFY(bitmap.format() == Gfx::BitmapFormat::BGRx8888 || bitmap.format() == Gfx::BitmapFormat::BGRA8888);
VERIFY(bitmap.physical_width() % scale == 0);
VERIFY(bitmap.physical_height() % scale == 0);
m_state_stack.append(State());
state().clip_rect = { { 0, 0 }, bitmap.size() };
state().scale = scale;
m_clip_origin = state().clip_rect;
}
@ -71,7 +67,6 @@ void Painter::clear_rect(IntRect const& a_rect, Color color)
return;
VERIFY(m_target->rect().contains(rect));
rect *= scale();
ARGB32* dst = m_target->scanline(rect.top()) + rect.left();
size_t const dst_skip = m_target->pitch() / sizeof(ARGB32);
@ -111,7 +106,7 @@ void Painter::fill_rect(IntRect const& a_rect, Color color)
return;
VERIFY(m_target->rect().contains(rect));
fill_physical_rect(rect * scale(), color);
fill_physical_rect(rect, color);
}
void Painter::fill_rect(IntRect const& rect, PaintStyle const& paint_style)
@ -120,8 +115,6 @@ void Painter::fill_rect(IntRect const& rect, PaintStyle const& paint_style)
auto clipped_rect = a_rect.intersected(clip_rect());
if (clipped_rect.is_empty())
return;
a_rect *= scale();
clipped_rect *= scale();
auto start_offset = clipped_rect.location() - a_rect.location();
paint_style.paint(a_rect, [&](PaintStyle::SamplerFunction sample) {
for (int y = 0; y < clipped_rect.height(); ++y) {
@ -252,10 +245,6 @@ void Painter::fill_rounded_corner(IntRect const& a_rect, int radius, Color color
if (translated_a_rect.y() < rect.y())
clip_offset = rect.y() - translated_a_rect.y();
radius *= scale();
rect *= scale();
clip_offset *= scale();
ARGB32* dst = m_target->scanline(rect.top()) + rect.left();
size_t const dst_skip = m_target->pitch() / sizeof(ARGB32);
@ -345,8 +334,6 @@ static void on_each_ellipse_point(IntRect const& rect, Function<void(IntPoint)>&
void Painter::fill_ellipse(IntRect const& a_rect, Color color)
{
VERIFY(scale() == 1); // FIXME: Add scaling support.
auto rect = a_rect.translated(translation()).intersected(clip_rect());
if (rect.is_empty())
return;
@ -387,14 +374,12 @@ void Painter::draw_rect(IntRect const& a_rect, Color color, bool rough)
int min_y = clipped_rect.top();
int max_y = clipped_rect.bottom() - 1;
int scale = this->scale();
if (rect.top() >= clipped_rect.top() && rect.top() < clipped_rect.bottom()) {
int width = rough ? max(0, min(rect.width() - 2, clipped_rect.width())) : clipped_rect.width();
if (width > 0) {
int start_x = rough ? max(rect.x() + 1, clipped_rect.x()) : clipped_rect.x();
for (int i = 0; i < scale; ++i)
fill_physical_scanline(rect.top() * scale + i, start_x * scale, width * scale, color);
fill_physical_scanline(rect.top(), start_x, width, color);
}
++min_y;
}
@ -402,8 +387,7 @@ void Painter::draw_rect(IntRect const& a_rect, Color color, bool rough)
int width = rough ? max(0, min(rect.width() - 2, clipped_rect.width())) : clipped_rect.width();
if (width > 0) {
int start_x = rough ? max(rect.x() + 1, clipped_rect.x()) : clipped_rect.x();
for (int i = 0; i < scale; ++i)
fill_physical_scanline(max_y * scale + i, start_x * scale, width * scale, color);
fill_physical_scanline(max_y, start_x, width, color);
}
--max_y;
}
@ -413,22 +397,18 @@ void Painter::draw_rect(IntRect const& a_rect, Color color, bool rough)
if (draw_left_side && draw_right_side) {
// Specialized loop when drawing both sides.
for (int y = min_y * scale; y <= max_y * scale; ++y) {
for (int y = min_y; y <= max_y; ++y) {
auto* bits = m_target->scanline(y);
for (int i = 0; i < scale; ++i)
set_physical_pixel(bits[rect.left() * scale + i], color);
for (int i = 0; i < scale; ++i)
set_physical_pixel(bits[(rect.right() - 1) * scale + i], color);
set_physical_pixel(bits[rect.left()], color);
set_physical_pixel(bits[(rect.right() - 1)], color);
}
} else {
for (int y = min_y * scale; y <= max_y * scale; ++y) {
for (int y = min_y; y <= max_y; ++y) {
auto* bits = m_target->scanline(y);
if (draw_left_side)
for (int i = 0; i < scale; ++i)
set_physical_pixel(bits[rect.left() * scale + i], color);
set_physical_pixel(bits[rect.left()], color);
if (draw_right_side)
for (int i = 0; i < scale; ++i)
set_physical_pixel(bits[(rect.right() - 1) * scale + i], color);
set_physical_pixel(bits[(rect.right() - 1)], color);
}
}
}
@ -489,27 +469,18 @@ static void do_blit_with_opacity(BlitState& state)
}
}
void Painter::blit_with_opacity(IntPoint position, Gfx::Bitmap const& source, IntRect const& a_src_rect, float opacity, bool apply_alpha)
void Painter::blit_with_opacity(IntPoint position, Gfx::Bitmap const& source, IntRect const& src_rect, float opacity, bool apply_alpha)
{
VERIFY(scale() >= source.scale() && "painter doesn't support downsampling scale factors");
if (opacity >= 1.0f && !(source.has_alpha_channel() && apply_alpha))
return blit(position, source, a_src_rect);
return blit(position, source, src_rect);
IntRect safe_src_rect = IntRect::intersection(a_src_rect, source.rect());
if (scale() != source.scale())
return draw_scaled_bitmap({ position, safe_src_rect.size() }, source, safe_src_rect, opacity);
IntRect safe_src_rect = IntRect::intersection(src_rect, source.rect());
auto dst_rect = IntRect(position, safe_src_rect.size()).translated(translation());
auto clipped_rect = dst_rect.intersected(clip_rect());
if (clipped_rect.is_empty())
return;
int scale = this->scale();
auto src_rect = a_src_rect * scale;
clipped_rect *= scale;
dst_rect *= scale;
int const first_row = clipped_rect.top() - dst_rect.top();
int const last_row = clipped_rect.bottom() - dst_rect.top();
int const first_column = clipped_rect.left() - dst_rect.left();
@ -541,19 +512,12 @@ void Painter::blit_with_opacity(IntPoint position, Gfx::Bitmap const& source, In
void Painter::blit_filtered(IntPoint position, Gfx::Bitmap const& source, IntRect const& src_rect, Function<Color(Color)> const& filter, bool apply_alpha)
{
VERIFY((source.scale() == 1 || source.scale() == scale()) && "blit_filtered only supports integer upsampling");
IntRect safe_src_rect = src_rect.intersected(source.rect());
auto dst_rect = IntRect(position, safe_src_rect.size()).translated(translation());
auto clipped_rect = dst_rect.intersected(clip_rect());
if (clipped_rect.is_empty())
return;
int scale = this->scale();
clipped_rect *= scale;
dst_rect *= scale;
safe_src_rect *= source.scale();
int const first_row = clipped_rect.top() - dst_rect.top();
int const last_row = clipped_rect.bottom() - dst_rect.top();
int const first_column = clipped_rect.left() - dst_rect.left();
@ -563,53 +527,31 @@ void Painter::blit_filtered(IntPoint position, Gfx::Bitmap const& source, IntRec
auto dst_format = target()->format();
auto src_format = source.format();
int s = scale / source.scale();
if (s == 1) {
ARGB32 const* src = source.scanline(safe_src_rect.top() + first_row) + safe_src_rect.left() + first_column;
size_t const src_skip = source.pitch() / sizeof(ARGB32);
ARGB32 const* src = source.scanline(safe_src_rect.top() + first_row) + safe_src_rect.left() + first_column;
size_t const src_skip = source.pitch() / sizeof(ARGB32);
for (int row = first_row; row < last_row; ++row) {
for (int x = 0; x < (last_column - first_column); ++x) {
auto source_color = color_for_format(src_format, src[x]);
if (source_color.alpha() == 0)
continue;
auto filtered_color = filter(source_color);
if (!apply_alpha || filtered_color.alpha() == 0xff)
dst[x] = filtered_color.value();
else
dst[x] = color_for_format(dst_format, dst[x]).blend(filtered_color).value();
}
dst += dst_skip;
src += src_skip;
}
} else {
for (int row = first_row; row < last_row; ++row) {
ARGB32 const* src = source.scanline(safe_src_rect.top() + row / s) + safe_src_rect.left() + first_column / s;
for (int x = 0; x < (last_column - first_column); ++x) {
auto source_color = color_for_format(src_format, src[x / s]);
if (source_color.alpha() == 0)
continue;
auto filtered_color = filter(source_color);
if (!apply_alpha || filtered_color.alpha() == 0xff)
dst[x] = filtered_color.value();
else
dst[x] = color_for_format(dst_format, dst[x]).blend(filtered_color).value();
}
dst += dst_skip;
for (int row = first_row; row < last_row; ++row) {
for (int x = 0; x < (last_column - first_column); ++x) {
auto source_color = color_for_format(src_format, src[x]);
if (source_color.alpha() == 0)
continue;
auto filtered_color = filter(source_color);
if (!apply_alpha || filtered_color.alpha() == 0xff)
dst[x] = filtered_color.value();
else
dst[x] = color_for_format(dst_format, dst[x]).blend(filtered_color).value();
}
dst += dst_skip;
src += src_skip;
}
}
void Painter::blit(IntPoint position, Gfx::Bitmap const& source, IntRect const& a_src_rect, float opacity, bool apply_alpha)
void Painter::blit(IntPoint position, Gfx::Bitmap const& source, IntRect const& src_rect, float opacity, bool apply_alpha)
{
VERIFY(scale() >= source.scale() && "painter doesn't support downsampling scale factors");
if (opacity < 1.0f || (source.has_alpha_channel() && apply_alpha))
return blit_with_opacity(position, source, a_src_rect, opacity, apply_alpha);
return blit_with_opacity(position, source, src_rect, opacity, apply_alpha);
auto safe_src_rect = a_src_rect.intersected(source.rect());
if (scale() != source.scale())
return draw_scaled_bitmap({ position, safe_src_rect.size() }, source, safe_src_rect, opacity);
auto safe_src_rect = src_rect.intersected(source.rect());
// If we get here, the Painter might have a scale factor, but the source bitmap has the same scale factor.
// We need to transform from logical to physical coordinates, but we can just copy pixels without resampling.
@ -618,12 +560,6 @@ void Painter::blit(IntPoint position, Gfx::Bitmap const& source, IntRect const&
if (clipped_rect.is_empty())
return;
// All computations below are in physical coordinates.
int scale = this->scale();
auto src_rect = a_src_rect * scale;
clipped_rect *= scale;
dst_rect *= scale;
int const first_row = clipped_rect.top() - dst_rect.top();
int const last_row = clipped_rect.bottom() - dst_rect.top();
int const first_column = clipped_rect.left() - dst_rect.left();
@ -871,7 +807,7 @@ void Painter::draw_scaled_bitmap(IntRect const& a_dst_rect, Gfx::Bitmap const& s
void Painter::draw_scaled_bitmap(IntRect const& a_dst_rect, Gfx::Bitmap const& source, FloatRect const& a_src_rect, float opacity, ScalingMode scaling_mode)
{
IntRect int_src_rect = enclosing_int_rect(a_src_rect);
if (scale() == source.scale() && a_src_rect == int_src_rect && a_dst_rect.size() == int_src_rect.size())
if (a_src_rect == int_src_rect && a_dst_rect.size() == int_src_rect.size())
return blit(a_dst_rect.location(), source, int_src_rect, opacity);
if (scaling_mode == ScalingMode::None) {
@ -880,8 +816,8 @@ void Painter::draw_scaled_bitmap(IntRect const& a_dst_rect, Gfx::Bitmap const& s
}
auto dst_rect = to_physical(a_dst_rect);
auto src_rect = a_src_rect * source.scale();
auto clipped_rect = dst_rect.intersected(clip_rect() * scale());
auto src_rect = a_src_rect;
auto clipped_rect = dst_rect.intersected(clip_rect());
if (clipped_rect.is_empty())
return;
@ -1238,8 +1174,6 @@ void Painter::draw_text(FloatRect const& rect, StringView raw_text, Font const&
void Painter::draw_text(Function<void(FloatRect const&, Utf8CodePointIterator&)> draw_one_glyph, FloatRect const& rect, Utf8View const& text, Font const& font, TextAlignment alignment, TextElision elision, TextWrapping wrapping)
{
VERIFY(scale() == 1); // FIXME: Add scaling support.
do_draw_text(rect, text, font, alignment, elision, wrapping, [&](FloatRect const& r, Utf8CodePointIterator& it) {
draw_one_glyph(r, it);
});
@ -1247,8 +1181,6 @@ void Painter::draw_text(Function<void(FloatRect const&, Utf8CodePointIterator&)>
void Painter::draw_text(Function<void(FloatRect const&, Utf8CodePointIterator&)> draw_one_glyph, FloatRect const& rect, StringView raw_text, Font const& font, TextAlignment alignment, TextElision elision, TextWrapping wrapping)
{
VERIFY(scale() == 1); // FIXME: Add scaling support.
Utf8View text { raw_text };
do_draw_text(rect, text, font, alignment, elision, wrapping, [&](FloatRect const& r, Utf8CodePointIterator& it) {
draw_one_glyph(r, it);
@ -1276,7 +1208,7 @@ void Painter::set_pixel(IntPoint p, Color color, bool blend)
point.translate_by(state().translation);
// Use the scale only to avoid clipping pixels set in drawing functions that handle
// scaling and call set_pixel() -- do not scale the pixel.
if (!clip_rect().contains(point / scale()))
if (!clip_rect().contains(point))
return;
set_physical_pixel(point, color, blend);
}
@ -1296,14 +1228,13 @@ Optional<Color> Painter::get_pixel(IntPoint p)
{
auto point = p;
point.translate_by(state().translation);
if (!clip_rect().contains(point / scale()))
if (!clip_rect().contains(point))
return {};
return m_target->get_pixel(point);
}
ErrorOr<NonnullRefPtr<Bitmap>> Painter::get_region_bitmap(IntRect const& region, BitmapFormat format, Optional<IntRect&> actual_region)
{
VERIFY(scale() == 1);
auto bitmap_region = region.translated(state().translation).intersected(m_target->rect());
if (actual_region.has_value())
actual_region.value() = bitmap_region.translated(-state().translation);
@ -1338,7 +1269,7 @@ void Painter::draw_physical_pixel(IntPoint physical_position, Color color, int t
}
IntRect rect { physical_position, { thickness, thickness } };
rect.intersect(clip_rect() * scale());
rect.intersect(clip_rect());
fill_physical_rect(rect, color);
}
@ -1353,14 +1284,13 @@ void Painter::draw_line(IntPoint a_p1, IntPoint a_p2, Color color, int thickness
if (color.alpha() == 0)
return;
auto clip_rect = this->clip_rect() * scale();
auto clip_rect = this->clip_rect();
auto const p1 = thickness > 1 ? a_p1.translated(-(thickness / 2), -(thickness / 2)) : a_p1;
auto const p2 = thickness > 1 ? a_p2.translated(-(thickness / 2), -(thickness / 2)) : a_p2;
auto point1 = to_physical(p1);
auto point2 = to_physical(p2);
thickness *= scale();
auto alternate_color_is_transparent = alternate_color == Color::Transparent;
@ -1738,8 +1668,6 @@ void Painter::draw_signed_distance_field(IntRect const& dst_rect, Color color, G
auto clipped_rect = target_rect.intersected(clip_rect());
if (clipped_rect.is_empty())
return;
target_rect *= scale();
clipped_rect *= scale();
auto start_offset = clipped_rect.location() - target_rect.location();
auto x_ratio = static_cast<float>(sdf.width() - 1) / (dst_rect.width() - 1);
auto y_ratio = static_cast<float>(sdf.height() - 1) / (dst_rect.height() - 1);

View file

@ -138,16 +138,14 @@ public:
IntRect clip_rect() const { return state().clip_rect; }
int scale() const { return state().scale; }
protected:
friend GradientLine;
friend AntiAliasingPainter;
template<unsigned SamplesPerPixel>
friend class EdgeFlagPathRasterizer;
IntRect to_physical(IntRect const& r) const { return r.translated(translation()) * scale(); }
IntPoint to_physical(IntPoint p) const { return p.translated(translation()) * scale(); }
IntRect to_physical(IntRect const& r) const { return r.translated(translation()); }
IntPoint to_physical(IntPoint p) const { return p.translated(translation()); }
void set_physical_pixel(u32& pixel, Color);
void fill_physical_scanline(int y, int x, int width, Color color);
void blit_with_opacity(IntPoint, Gfx::Bitmap const&, IntRect const& src_rect, float opacity, bool apply_alpha = true);
@ -156,7 +154,6 @@ protected:
struct State {
IntPoint translation;
int scale = 1;
IntRect clip_rect;
};

View file

@ -32,7 +32,6 @@ ErrorOr<void> encode(Encoder& encoder, Gfx::ShareableBitmap const& shareable_bit
auto& bitmap = *shareable_bitmap.bitmap();
TRY(encoder.encode(TRY(IPC::File::clone_fd(bitmap.anonymous_buffer().fd()))));
TRY(encoder.encode(bitmap.size()));
TRY(encoder.encode(static_cast<u32>(bitmap.scale())));
TRY(encoder.encode(static_cast<u32>(bitmap.format())));
return {};
}
@ -45,15 +44,14 @@ ErrorOr<Gfx::ShareableBitmap> decode(Decoder& decoder)
auto anon_file = TRY(decoder.decode<IPC::File>());
auto size = TRY(decoder.decode<Gfx::IntSize>());
auto scale = TRY(decoder.decode<u32>());
auto raw_bitmap_format = TRY(decoder.decode<u32>());
if (!Gfx::is_valid_bitmap_format(raw_bitmap_format))
return Error::from_string_literal("IPC: Invalid Gfx::ShareableBitmap format");
auto bitmap_format = static_cast<Gfx::BitmapFormat>(raw_bitmap_format);
auto buffer = TRY(Core::AnonymousBuffer::create_from_anon_fd(anon_file.take_fd(), Gfx::Bitmap::size_in_bytes(Gfx::Bitmap::minimum_pitch(size.width() * scale, bitmap_format), size.height() * scale)));
auto bitmap = TRY(Gfx::Bitmap::create_with_anonymous_buffer(bitmap_format, move(buffer), size, scale));
auto buffer = TRY(Core::AnonymousBuffer::create_from_anon_fd(anon_file.take_fd(), Gfx::Bitmap::size_in_bytes(Gfx::Bitmap::minimum_pitch(size.width(), bitmap_format), size.height())));
auto bitmap = TRY(Gfx::Bitmap::create_with_anonymous_buffer(bitmap_format, move(buffer), size));
return Gfx::ShareableBitmap { move(bitmap), Gfx::ShareableBitmap::ConstructWithKnownGoodBitmap };
}

View file

@ -30,7 +30,7 @@ WebIDL::ExceptionOr<JS::NonnullGCPtr<ImageData>> ImageData::create(JS::Realm& re
// 2. Initialize this given sw, sh, and settings set to settings.
// 3. Initialize the image data of this to transparent black.
auto data = TRY(JS::Uint8ClampedArray::create(realm, sw * sh * 4));
auto bitmap = TRY_OR_THROW_OOM(vm, Gfx::Bitmap::create_wrapper(Gfx::BitmapFormat::RGBA8888, Gfx::IntSize(sw, sh), 1, sw * sizeof(u32), data->data().data()));
auto bitmap = TRY_OR_THROW_OOM(vm, Gfx::Bitmap::create_wrapper(Gfx::BitmapFormat::RGBA8888, Gfx::IntSize(sw, sh), sw * sizeof(u32), data->data().data()));
return realm.heap().allocate<ImageData>(realm, realm, bitmap, data);
}
@ -74,7 +74,7 @@ WebIDL::ExceptionOr<JS::NonnullGCPtr<ImageData>> ImageData::create(JS::Realm& re
return WebIDL::IndexSizeError::create(realm, "Source height must be equal to the calculated height of the data."_fly_string);
// 7. Initialize this given sw, sh, settings set to settings, and source set to data.
auto bitmap = TRY_OR_THROW_OOM(vm, Gfx::Bitmap::create_wrapper(Gfx::BitmapFormat::RGBA8888, Gfx::IntSize(sw, height), 1, sw * sizeof(u32), uint8_clamped_array_data.data().data()));
auto bitmap = TRY_OR_THROW_OOM(vm, Gfx::Bitmap::create_wrapper(Gfx::BitmapFormat::RGBA8888, Gfx::IntSize(sw, height), sw * sizeof(u32), uint8_clamped_array_data.data().data()));
return realm.heap().allocate<ImageData>(realm, realm, bitmap, uint8_clamped_array_data);
}