Files
ladybird/Libraries/LibWeb/CSS/StyleValues/ImageStyleValue.cpp
Aliaksandr Kalenik 76c79ee522 LibGfx: Remove ImmutableBitmap
DecodedImageFrame now owns decoded bitmap pixels directly, so the
separate ImmutableBitmap wrapper no longer carries useful semantics.
Remove the class and pass decoded image frames or bitmaps at the
boundaries where pixels are actually required.

The Skia image cache now keys off DecodedImageFrame, matching the
display-list commands that paint decoded images. Video frames stay
owned by LibMedia, with the explicit YUV-to-bitmap conversion living
at HTMLVideoElement's decoded-frame entry point for canvas and WebGL
callers.
2026-05-05 14:39:17 -05:00

261 lines
8.1 KiB
C++

/*
* Copyright (c) 2018-2023, Andreas Kling <andreas@ladybird.org>
* Copyright (c) 2021, Tobias Christiansen <tobyase@serenityos.org>
* Copyright (c) 2021-2025, Sam Atkins <sam@ladybird.org>
* Copyright (c) 2022-2023, MacDue <macdue@dueutil.tech>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#include <LibGfx/DecodedImageFrame.h>
#include <LibWeb/CSS/CSSStyleSheet.h>
#include <LibWeb/CSS/ComputedValues.h>
#include <LibWeb/CSS/Fetch.h>
#include <LibWeb/CSS/StyleValues/ImageStyleValue.h>
#include <LibWeb/DOM/Document.h>
#include <LibWeb/DOMURL/DOMURL.h>
#include <LibWeb/HTML/DecodedImageData.h>
#include <LibWeb/HTML/PotentialCORSRequest.h>
#include <LibWeb/HTML/Scripting/Environments.h>
#include <LibWeb/HTML/SharedResourceRequest.h>
#include <LibWeb/Painting/DisplayListRecorder.h>
#include <LibWeb/Painting/DisplayListRecordingContext.h>
#include <LibWeb/Platform/Timer.h>
namespace Web::CSS {
ValueComparingNonnullRefPtr<ImageStyleValue const> ImageStyleValue::create(URL const& url)
{
return adopt_ref(*new (nothrow) ImageStyleValue(url));
}
ValueComparingNonnullRefPtr<ImageStyleValue const> ImageStyleValue::create(::URL::URL const& url)
{
return adopt_ref(*new (nothrow) ImageStyleValue(URL { url.to_string() }));
}
ImageStyleValue::ImageStyleValue(URL const& url)
: AbstractImageStyleValue(Type::Image)
, m_url(url)
{
}
ImageStyleValue::~ImageStyleValue() = default;
void ImageStyleValue::visit_edges(JS::Cell::Visitor& visitor) const
{
Base::visit_edges(visitor);
// FIXME: visit_edges in non-GC allocated classes is confusing pattern.
// Consider making StyleValue to be GC allocated instead.
visitor.visit(m_resource_request);
visitor.visit(m_style_sheet);
visitor.visit(m_timer);
}
void ImageStyleValue::load_any_resources(DOM::Document& document)
{
if (m_resource_request)
return;
m_document = &document;
RuleOrDeclaration rule_or_declaration {
.environment_settings_object = document.relevant_settings_object(),
.value = RuleOrDeclaration::Rule {
.parent_style_sheet = m_style_sheet,
}
};
m_resource_request = fetch_an_external_image_for_a_stylesheet(m_url, rule_or_declaration, m_style_sheet ? *m_style_sheet->owning_document() : document);
if (m_resource_request) {
m_resource_request->add_callbacks(
weak_callback(*this, [](auto& self) {
if (!self.m_document)
return;
for (auto* client : self.m_clients)
client->image_style_value_did_update(self);
auto image_data = self.m_resource_request->image_data();
if (image_data->is_animated() && image_data->frame_count() > 1) {
self.m_timer = Platform::Timer::create(self.m_document->heap());
self.m_timer->set_interval(image_data->frame_duration(0));
self.m_timer->on_timeout = GC::create_function(self.m_document->heap(), [ptr = &self] { ptr->animate(); });
self.m_timer->start();
}
}),
nullptr);
}
}
void ImageStyleValue::animate()
{
if (!m_resource_request)
return;
auto image_data = m_resource_request->image_data();
if (!image_data)
return;
m_current_frame_index = (m_current_frame_index + 1) % image_data->frame_count();
m_current_frame_index = image_data->notify_frame_advanced(m_current_frame_index);
auto current_frame_duration = image_data->frame_duration(m_current_frame_index);
if (current_frame_duration != m_timer->interval())
m_timer->restart(current_frame_duration);
if (m_current_frame_index == image_data->frame_count() - 1) {
++m_loops_completed;
if (m_loops_completed > 0 && m_loops_completed == image_data->loop_count())
m_timer->stop();
}
if (on_animate)
on_animate();
}
bool ImageStyleValue::is_paintable() const
{
return image_data();
}
RefPtr<Gfx::DecodedImageFrame> ImageStyleValue::frame(size_t frame_index, Gfx::IntSize size) const
{
if (auto image_data = this->image_data())
return image_data->frame(frame_index, size);
return nullptr;
}
void ImageStyleValue::serialize(StringBuilder& builder, SerializationMode) const
{
builder.append(m_url.to_string());
}
bool ImageStyleValue::equals(StyleValue const& other) const
{
if (type() != other.type())
return false;
return m_url == other.as_image().m_url;
}
Optional<CSSPixels> ImageStyleValue::natural_width() const
{
if (auto image_data = this->image_data())
return image_data->intrinsic_width();
return {};
}
Optional<CSSPixels> ImageStyleValue::natural_height() const
{
if (auto image_data = this->image_data())
return image_data->intrinsic_height();
return {};
}
Optional<CSSPixelFraction> ImageStyleValue::natural_aspect_ratio() const
{
if (auto image_data = this->image_data())
return image_data->intrinsic_aspect_ratio();
return {};
}
void ImageStyleValue::paint(DisplayListRecordingContext& context, DevicePixelRect const& dest_rect, CSS::ImageRendering image_rendering) const
{
auto image_data = this->image_data();
if (!image_data)
return;
auto dest_int_rect = dest_rect.to_type<int>();
auto rect = image_data->frame_rect(m_current_frame_index).value_or(dest_int_rect);
auto scaling_mode = to_gfx_scaling_mode(image_rendering, rect.size(), dest_int_rect.size());
image_data->paint(context, m_current_frame_index, dest_int_rect, dest_int_rect, scaling_mode);
}
RefPtr<Gfx::DecodedImageFrame> ImageStyleValue::current_frame(DevicePixelRect const& dest_rect) const
{
return frame(m_current_frame_index, dest_rect.size().to_type<int>());
}
GC::Ptr<HTML::DecodedImageData> ImageStyleValue::image_data() const
{
if (!m_resource_request)
return nullptr;
return m_resource_request->image_data();
}
Optional<Gfx::Color> ImageStyleValue::color_if_single_pixel_bitmap() const
{
if (auto decoded_frame = frame(m_current_frame_index)) {
auto const& bitmap = decoded_frame->bitmap();
if (bitmap.width() == 1 && bitmap.height() == 1)
return bitmap.get_pixel(0, 0);
}
return {};
}
void ImageStyleValue::set_style_sheet(GC::Ptr<CSSStyleSheet> style_sheet)
{
Base::set_style_sheet(style_sheet);
m_style_sheet = style_sheet;
if (m_style_sheet)
m_style_sheet->register_pending_image_value(*this);
}
ValueComparingNonnullRefPtr<StyleValue const> ImageStyleValue::absolutized(ComputationContext const&) const
{
if (m_url.url().is_empty())
return *this;
// FIXME: The spec has been updated to handle this better. The computation of the base URL here is roughly based on:
// https://drafts.csswg.org/css-values-4/#style-resource-base-url
// https://github.com/w3c/csswg-drafts/pull/12261
auto base_url = [&]() -> Optional<::URL::URL> {
if (m_style_sheet) {
return m_style_sheet->base_url()
.value_or_lazy_evaluated_optional([&]() { return m_style_sheet->location(); })
.value_or_lazy_evaluated_optional([&]() { return HTML::relevant_settings_object(*m_style_sheet).api_base_url(); });
}
if (m_document)
return m_document->base_url();
return {};
}();
if (base_url.has_value()) {
if (auto resolved_url = DOMURL::parse(m_url.url(), *base_url); resolved_url.has_value())
return ImageStyleValue::create(*resolved_url);
}
return *this;
}
void ImageStyleValue::register_client(Client& client) const
{
auto result = m_clients.set(&client);
VERIFY(result == AK::HashSetResult::InsertedNewEntry);
}
void ImageStyleValue::unregister_client(Client& client) const
{
auto did_remove = m_clients.remove(&client);
VERIFY(did_remove);
}
ImageStyleValue::Client::Client(ImageStyleValue const& image_style_value)
: m_image_style_value(image_style_value)
{
m_image_style_value.register_client(*this);
}
ImageStyleValue::Client::~Client()
{
}
void ImageStyleValue::Client::image_style_value_finalize()
{
m_image_style_value.unregister_client(*this);
}
}