mirror of
https://github.com/LadybirdBrowser/ladybird.git
synced 2024-11-21 23:20:20 +00:00
Ladybird: Use custom QIconEngine to render scalable/vector icons
Rather than render the icons to a 16x16 bitmap, keep them as vector graphics and render them on request. This keeps the icons crisp on high DPI displays.
This commit is contained in:
parent
3e6ca1085c
commit
1837e94ba4
Notes:
sideshowbarker
2024-07-17 05:09:48 +09:00
Author: https://github.com/MacDue Commit: https://github.com/SerenityOS/serenity/commit/1837e94ba4 Pull-request: https://github.com/SerenityOS/serenity/pull/20254
5 changed files with 170 additions and 31 deletions
|
@ -91,6 +91,7 @@ set(SOURCES
|
|||
Settings.cpp
|
||||
SettingsDialog.cpp
|
||||
Tab.cpp
|
||||
TVGIconEngine.cpp
|
||||
Utilities.cpp
|
||||
WebContentView.cpp
|
||||
ladybird.qrc
|
||||
|
|
79
Ladybird/TVGIconEngine.cpp
Normal file
79
Ladybird/TVGIconEngine.cpp
Normal file
|
@ -0,0 +1,79 @@
|
|||
/*
|
||||
* Copyright (c) 2023, MacDue <macdue@dueutil.tech>
|
||||
*
|
||||
* SPDX-License-Identifier: BSD-2-Clause
|
||||
*/
|
||||
|
||||
#include <AK/MemoryStream.h>
|
||||
#include <AK/String.h>
|
||||
#include <LibGfx/Painter.h>
|
||||
#include <QFile>
|
||||
#include <QImage>
|
||||
#include <QPainter>
|
||||
#include <QPixmapCache>
|
||||
|
||||
#include "TVGIconEngine.h"
|
||||
#include "Utilities.h"
|
||||
|
||||
void TVGIconEngine::paint(QPainter* qpainter, QRect const& rect, QIcon::Mode mode, QIcon::State state)
|
||||
{
|
||||
qpainter->drawPixmap(rect, pixmap(rect.size(), mode, state));
|
||||
}
|
||||
|
||||
QIconEngine* TVGIconEngine::clone() const
|
||||
{
|
||||
return new TVGIconEngine(*this);
|
||||
}
|
||||
|
||||
QPixmap TVGIconEngine::pixmap(QSize const& size, QIcon::Mode mode, QIcon::State state)
|
||||
{
|
||||
QPixmap pixmap;
|
||||
auto key = pixmap_cache_key(size, mode, state);
|
||||
if (QPixmapCache::find(key, &pixmap))
|
||||
return pixmap;
|
||||
auto bitmap = MUST(Gfx::Bitmap::create(Gfx::BitmapFormat::BGRA8888, { size.width(), size.height() }));
|
||||
Gfx::Painter painter { *bitmap };
|
||||
auto icon_rect = m_image_data->rect().to_type<float>();
|
||||
auto scale = min(size.width() / icon_rect.width(), size.height() / icon_rect.height()) * m_scale;
|
||||
auto centered = Gfx::FloatRect { {}, icon_rect.size().scaled_by(scale) }
|
||||
.centered_within(Gfx::FloatRect { {}, { size.width(), size.height() } });
|
||||
auto transform = Gfx::AffineTransform {}
|
||||
.translate(centered.location())
|
||||
.multiply(Gfx::AffineTransform {}.scale(scale, scale));
|
||||
m_image_data->draw_transformed(painter, transform);
|
||||
for (auto const& filter : m_filters) {
|
||||
if (filter->mode() == mode) {
|
||||
painter.blit_filtered({}, *bitmap, bitmap->rect(), filter->function(), false);
|
||||
break;
|
||||
}
|
||||
}
|
||||
QImage qimage { bitmap->scanline_u8(0), bitmap->width(), bitmap->height(), QImage::Format::Format_ARGB32 };
|
||||
pixmap = QPixmap::fromImage(qimage);
|
||||
if (!pixmap.isNull())
|
||||
QPixmapCache::insert(key, pixmap);
|
||||
return pixmap;
|
||||
}
|
||||
|
||||
QString TVGIconEngine::pixmap_cache_key(QSize const& size, QIcon::Mode mode, QIcon::State state)
|
||||
{
|
||||
return qstring_from_ak_string(
|
||||
MUST(String::formatted("$sernity_tvgicon_{}_{}x{}_{}_{}", m_cache_id, size.width(), size.height(), to_underlying(mode), to_underlying(state))));
|
||||
}
|
||||
|
||||
void TVGIconEngine::add_filter(QIcon::Mode mode, Function<Color(Color)> filter)
|
||||
{
|
||||
m_filters.empend(adopt_ref(*new Filter(mode, move(filter))));
|
||||
invalidate_cache();
|
||||
}
|
||||
|
||||
TVGIconEngine* TVGIconEngine::from_file(QString const& path)
|
||||
{
|
||||
QFile icon_resource(path);
|
||||
if (!icon_resource.open(QIODeviceBase::ReadOnly))
|
||||
return nullptr;
|
||||
auto icon_data = icon_resource.readAll();
|
||||
FixedMemoryStream icon_bytes { ReadonlyBytes { icon_data.data(), static_cast<size_t>(icon_data.size()) } };
|
||||
if (auto tvg = Gfx::TinyVGDecodedImageData::decode(icon_bytes); !tvg.is_error())
|
||||
return new TVGIconEngine(tvg.release_value());
|
||||
return nullptr;
|
||||
}
|
71
Ladybird/TVGIconEngine.h
Normal file
71
Ladybird/TVGIconEngine.h
Normal file
|
@ -0,0 +1,71 @@
|
|||
/*
|
||||
* Copyright (c) 2023, MacDue <macdue@dueutil.tech>
|
||||
*
|
||||
* SPDX-License-Identifier: BSD-2-Clause
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <AK/Function.h>
|
||||
#include <AK/RefCounted.h>
|
||||
#include <AK/Vector.h>
|
||||
#include <LibGfx/ImageFormats/TinyVGLoader.h>
|
||||
#include <QIconEngine>
|
||||
|
||||
class TVGIconEngine : public QIconEngine {
|
||||
public:
|
||||
TVGIconEngine(Gfx::TinyVGDecodedImageData const& image_data)
|
||||
: m_image_data(image_data)
|
||||
{
|
||||
}
|
||||
|
||||
static TVGIconEngine* from_file(QString const& path);
|
||||
|
||||
void paint(QPainter* painter, QRect const& rect, QIcon::Mode mode,
|
||||
QIcon::State state) override;
|
||||
QIconEngine* clone() const override;
|
||||
QPixmap pixmap(QSize const& size, QIcon::Mode mode,
|
||||
QIcon::State state) override;
|
||||
|
||||
void add_filter(QIcon::Mode mode, Function<Color(Color)> filter);
|
||||
|
||||
void set_scale(float scale)
|
||||
{
|
||||
m_scale = scale;
|
||||
invalidate_cache();
|
||||
}
|
||||
|
||||
private:
|
||||
static unsigned next_cache_id()
|
||||
{
|
||||
static unsigned cache_id = 0;
|
||||
return cache_id++;
|
||||
}
|
||||
|
||||
void invalidate_cache()
|
||||
{
|
||||
m_cache_id = next_cache_id();
|
||||
}
|
||||
|
||||
class Filter : public RefCounted<Filter> {
|
||||
public:
|
||||
Filter(QIcon::Mode mode, Function<Color(Color)> function)
|
||||
: m_mode(mode)
|
||||
, m_function(move(function))
|
||||
{
|
||||
}
|
||||
QIcon::Mode mode() const { return m_mode; }
|
||||
Function<Color(Color)> const& function() const { return m_function; }
|
||||
|
||||
private:
|
||||
QIcon::Mode m_mode;
|
||||
Function<Color(Color)> m_function;
|
||||
};
|
||||
|
||||
QString pixmap_cache_key(QSize const& size, QIcon::Mode mode, QIcon::State state);
|
||||
|
||||
float m_scale { 1 };
|
||||
Vector<NonnullRefPtr<Filter>> m_filters;
|
||||
NonnullRefPtr<Gfx::TinyVGDecodedImageData> m_image_data;
|
||||
unsigned m_cache_id { next_cache_id() };
|
||||
};
|
|
@ -9,6 +9,7 @@
|
|||
#include "ConsoleWidget.h"
|
||||
#include "InspectorWidget.h"
|
||||
#include "Settings.h"
|
||||
#include "TVGIconEngine.h"
|
||||
#include "Utilities.h"
|
||||
#include <Browser/History.h>
|
||||
#include <LibGfx/ImageFormats/BMPWriter.h>
|
||||
|
@ -31,33 +32,20 @@
|
|||
extern DeprecatedString s_serenity_resource_root;
|
||||
extern Browser::Settings* s_settings;
|
||||
|
||||
static QIcon render_tvg_icon_with_theme_colors(QString name, QPalette const& palette)
|
||||
static QIcon create_tvg_icon_with_theme_colors(QString name, QPalette const& palette)
|
||||
{
|
||||
auto path = QString(":/Icons/%1.tvg").arg(name);
|
||||
Gfx::IntSize icon_size(16, 16);
|
||||
|
||||
QFile icon_resource(path);
|
||||
VERIFY(icon_resource.open(QIODeviceBase::ReadOnly));
|
||||
auto icon_data = icon_resource.readAll();
|
||||
ReadonlyBytes icon_bytes { icon_data.data(), static_cast<size_t>(icon_data.size()) };
|
||||
auto icon_raster = MUST(Gfx::Bitmap::load_from_bytes(icon_bytes, icon_size));
|
||||
|
||||
QIcon icon;
|
||||
auto render = [&](QColor color) -> QPixmap {
|
||||
auto image = MUST(Gfx::Bitmap::create(Gfx::BitmapFormat::BGRA8888, icon_size));
|
||||
Gfx::Painter painter { image };
|
||||
auto icon_color = Color::from_argb(color.rgba64().toArgb32());
|
||||
painter.blit_filtered({ 0, 0 }, *icon_raster, icon_raster->rect(), [&](auto color) {
|
||||
return icon_color.with_alpha((icon_color.alpha() * color.alpha()) / 255);
|
||||
});
|
||||
QImage qimage { image->scanline_u8(0), image->width(), image->height(), QImage::Format::Format_ARGB32 };
|
||||
return QPixmap::fromImage(qimage);
|
||||
auto icon_engine = TVGIconEngine::from_file(path);
|
||||
VERIFY(icon_engine);
|
||||
auto icon_filter = [](QColor color) {
|
||||
return [color = Color::from_argb(color.rgba64().toArgb32())](Gfx::Color icon_color) {
|
||||
return color.with_alpha((icon_color.alpha() * color.alpha()) / 255);
|
||||
};
|
||||
};
|
||||
|
||||
icon.addPixmap(render(palette.color(QPalette::ColorGroup::Normal, QPalette::ColorRole::ButtonText)), QIcon::Mode::Normal);
|
||||
icon.addPixmap(render(palette.color(QPalette::ColorGroup::Disabled, QPalette::ColorRole::ButtonText)), QIcon::Mode::Disabled);
|
||||
|
||||
return icon;
|
||||
icon_engine->add_filter(QIcon::Mode::Normal, icon_filter(palette.color(QPalette::ColorGroup::Normal, QPalette::ColorRole::ButtonText)));
|
||||
icon_engine->add_filter(QIcon::Mode::Disabled, icon_filter(palette.color(QPalette::ColorGroup::Disabled, QPalette::ColorRole::ButtonText)));
|
||||
icon_engine->set_scale(0.66f);
|
||||
return QIcon(icon_engine);
|
||||
}
|
||||
|
||||
Tab::Tab(BrowserWindow* window, StringView webdriver_content_ipc_path, WebView::EnableCallgrindProfiling enable_callgrind_profiling, WebView::UseJavaScriptBytecode use_javascript_bytecode)
|
||||
|
@ -85,7 +73,7 @@ Tab::Tab(BrowserWindow* window, StringView webdriver_content_ipc_path, WebView::
|
|||
m_layout->addWidget(m_toolbar);
|
||||
m_layout->addWidget(m_view);
|
||||
|
||||
rerender_toolbar_icons();
|
||||
recreate_toolbar_icons();
|
||||
|
||||
m_toolbar->addAction(&m_window->go_back_action());
|
||||
m_toolbar->addAction(&m_window->go_forward_action());
|
||||
|
@ -614,18 +602,18 @@ void Tab::update_hover_label()
|
|||
bool Tab::event(QEvent* event)
|
||||
{
|
||||
if (event->type() == QEvent::PaletteChange) {
|
||||
rerender_toolbar_icons();
|
||||
recreate_toolbar_icons();
|
||||
return QWidget::event(event);
|
||||
}
|
||||
|
||||
return QWidget::event(event);
|
||||
}
|
||||
|
||||
void Tab::rerender_toolbar_icons()
|
||||
void Tab::recreate_toolbar_icons()
|
||||
{
|
||||
m_window->go_back_action().setIcon(render_tvg_icon_with_theme_colors("back", palette()));
|
||||
m_window->go_forward_action().setIcon(render_tvg_icon_with_theme_colors("forward", palette()));
|
||||
m_window->reload_action().setIcon(render_tvg_icon_with_theme_colors("reload", palette()));
|
||||
m_window->go_back_action().setIcon(create_tvg_icon_with_theme_colors("back", palette()));
|
||||
m_window->go_forward_action().setIcon(create_tvg_icon_with_theme_colors("forward", palette()));
|
||||
m_window->reload_action().setIcon(create_tvg_icon_with_theme_colors("reload", palette()));
|
||||
}
|
||||
|
||||
void Tab::show_inspector_window(InspectorTarget inspector_target)
|
||||
|
|
|
@ -68,7 +68,7 @@ private:
|
|||
virtual void resizeEvent(QResizeEvent*) override;
|
||||
virtual bool event(QEvent*) override;
|
||||
|
||||
void rerender_toolbar_icons();
|
||||
void recreate_toolbar_icons();
|
||||
void update_hover_label();
|
||||
|
||||
void open_link(URL const&);
|
||||
|
|
Loading…
Reference in a new issue