Pārlūkot izejas kodu

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.
MacDue 2 gadi atpakaļ
vecāks
revīzija
1837e94ba4
5 mainītis faili ar 170 papildinājumiem un 31 dzēšanām
  1. 1 0
      Ladybird/CMakeLists.txt
  2. 79 0
      Ladybird/TVGIconEngine.cpp
  3. 71 0
      Ladybird/TVGIconEngine.h
  4. 18 30
      Ladybird/Tab.cpp
  5. 1 1
      Ladybird/Tab.h

+ 1 - 0
Ladybird/CMakeLists.txt

@@ -91,6 +91,7 @@ set(SOURCES
     Settings.cpp
     SettingsDialog.cpp
     Tab.cpp
+    TVGIconEngine.cpp
     Utilities.cpp
     WebContentView.cpp
     ladybird.qrc

+ 79 - 0
Ladybird/TVGIconEngine.cpp

@@ -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 - 0
Ladybird/TVGIconEngine.h

@@ -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() };
+};

+ 18 - 30
Ladybird/Tab.cpp

@@ -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)

+ 1 - 1
Ladybird/Tab.h

@@ -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&);