Commit 9b48c5a9 authored by Mikel Johnson's avatar Mikel Johnson
Browse files

Use custom element to show SVGs instead of images

Introduces VectorImage which allows for partial rendering of the image allowing us to show SVGs at high quality without sacrificing performance.
We use QQuickPaintedItem as it performs better than Image and allows us to potentially replace the QSvg in the future with a more compliant implementation.
parent f4424407
......@@ -4,6 +4,7 @@
project(Koko)
cmake_minimum_required(VERSION 3.16)
set(CMAKE_CXX_STANDARD 17)
set(QT5_MIN_VERSION 5.15)
set(KF5_MIN_VERSION 5.79)
......@@ -28,7 +29,7 @@ ecm_setup_version(${PROJECT_VERSION}
VARIABLE_PREFIX KOKO
VERSION_HEADER ${CMAKE_CURRENT_BINARY_DIR}/version.h
)
find_package(Qt5 ${QT5_MIN_VERSION} REQUIRED NO_MODULE COMPONENTS Qml Quick Widgets Test Sql Positioning)
find_package(Qt5 ${QT5_MIN_VERSION} REQUIRED NO_MODULE COMPONENTS Qml Quick Widgets Test Sql Positioning Svg)
if(ANDROID)
find_package(Qt5 ${QT5_MIN_VERSION} REQUIRED NO_MODULE COMPONENTS AndroidExtras)
......
......@@ -17,6 +17,7 @@ target_link_libraries(kokocommon
Qt5::Core
Qt5::Positioning
Qt5::Sql
Qt5::Svg
KF5::CoreAddons
KF5::DBusAddons
KF5::FileMetaData
......@@ -43,6 +44,7 @@ set(koko_SRCS
processor.cpp
committimer.cpp
imageprocessorrunnable.cpp
vectorimage.cpp
kdtree.c
resources.qrc
)
......
......@@ -32,6 +32,7 @@
#include "openfilemodel.h"
#include "processor.h"
#include "version.h"
#include "vectorimage.h"
#ifdef Q_OS_ANDROID
#include <QtAndroid>
......@@ -150,6 +151,7 @@ int main(int argc, char **argv)
engine.rootContext()->setContextObject(new KLocalizedContext(&engine));
qmlRegisterSingletonInstance("org.kde.koko.private", 0, 1, "OpenFileModel", &openFileModel);
qmlRegisterType<VectorImage>("org.kde.koko.image", 0, 1, "VectorImage");
engine.rootContext()->setContextProperty("kokoProcessor", &processor);
engine.rootContext()->setContextProperty("kokoConfig", &config);
......
......@@ -12,6 +12,7 @@ import QtQml 2.15
import QtMultimedia 5.15
import org.kde.kirigami 2.15 as Kirigami
import org.kde.koko 0.1
import org.kde.koko.image 0.1
MouseArea {
id: root
......@@ -28,16 +29,23 @@ MouseArea {
* to check for a specific type of media viewer or not.
*/
property VideoPlayer videoPlayer: null
property VectorImage vectorImage: null
property AnimatedImage animatedImage: null
property Image image: null
readonly property Item media: videoPlayer || animatedImage || image
readonly property Item media: videoPlayer || vectorImage || animatedImage || image
/**
* Properties for info about the media and media viewer.
*/
readonly property int status: media ? media.status : 0 // 0 is equal to MediaPlayer.UnknownStatus and Image.Null
readonly property bool loaded: videoPlayer ? status === MediaPlayer.Loaded : status === Image.Ready
readonly property bool loading: videoPlayer ? status === MediaPlayer.Loading : status === Image.Loading
readonly property bool loaded: videoPlayer ? status === MediaPlayer.Loaded
: vectorImage ? media.status === VectorImage.Ready
: media.status === Image.Ready
readonly property bool loading: videoPlayer ? status === MediaPlayer.Loading
: vectorImage ? media.status === VectorImage.Loading
: media.status === Image.Loading
property real mediaSourceWidth: videoPlayer ? videoPlayer.implicitWidth : media.sourceSize.width
property real mediaSourceHeight: videoPlayer ? videoPlayer.implicitHeight : media.sourceSize.height
readonly property real mediaAspectRatio: root.mediaSourceWidth / root.mediaSourceHeight
......@@ -59,7 +67,7 @@ MouseArea {
}
readonly property real viewAspectRatio: root.width / root.height
// Should be the same for both width and height
readonly property real zoomFactor: (videoPlayer ? contentItem.width : media.paintedWidth) / mediaSourceWidth
readonly property real zoomFactor: (videoPlayer || vectorImage ? contentItem.width : media.paintedWidth) / mediaSourceWidth
// Fit to root unless arguments are smaller than the size of root.
// Returning size instead of using separate width and height functions
......@@ -374,6 +382,15 @@ MouseArea {
}
}
Component {
id: vectorImageComponent
VectorImage {
anchors.fill: root
source: currentImageSource
sourceClipRect: Qt.rect(-root.contentX, -root.contentY, root.contentWidth, root.contentHeight)
}
}
Component {
id: animatedImageComponent
// sadly sourceSize is read only in AnimatedImage, so we keep it separate
......@@ -413,14 +430,22 @@ MouseArea {
currentImageMimeType.startsWith("video/") && videoPlayer === null
) {
videoPlayer = videoPlayerComponent.createObject(root)
vectorImage = null
animatedImage = null
image = null
} else if ((currentImageSource.endsWith(".svg") || currentImageSource.endsWith(".svgz")) && vectorImage === null) {
videoPlayer = null
vectorImage = vectorImageComponent.createObject(root)
animatedImage = null
image = null
} else if (currentImageSource.endsWith(".gif") && animatedImage === null) {
videoPlayer = null
vectorImage = null
animatedImage = animatedImageComponent.createObject(contentItem)
image = null
} else if (image === null) {
videoPlayer = null
vectorImage = null
animatedImage = null
image = imageComponent.createObject(contentItem)
}
......
/*
* SPDX-FileCopyrightText: (C) 2021 Mikel Johnson <mikel5764@gmail.com>
*
* SPDX-License-Identifier: LGPL-2.0-or-later
*/
#include "vectorimage.h"
#include <QQuickWindow>
VectorImage::VectorImage(QQuickItem *parent) : QQuickPaintedItem(parent), m_devicePixelRatio(0), m_status(Null)
{
}
void VectorImage::setSourceClipRect(const QRectF &sourceClipRect)
{
if (m_sourceClipRect == sourceClipRect) {
return;
}
m_sourceClipRect = sourceClipRect;
Q_EMIT sourceClipRectChanged();
update();
}
void VectorImage::setSourceSize(const QSize &sourceSize)
{
if (m_sourceSize == sourceSize) {
return;
}
m_sourceSize = sourceSize;
Q_EMIT sourceSizeChanged();
}
void VectorImage::setStatus(Status status)
{
if (m_status == status) {
return;
}
m_status = status;
Q_EMIT statusChanged(m_status);
}
void VectorImage::setSource(const QUrl &source)
{
if (m_source == source) {
return;
}
m_source = source;
Q_EMIT sourceChanged();
if (m_source.isEmpty()) {
setStatus(Null);
m_viewBoxF = QRectF();
setSourceSize(QSize());
return;
}
setStatus(Loading);
m_renderer = std::make_unique<QSvgRenderer>(m_source.toLocalFile());
if (!m_renderer->isValid()) {
setStatus(Error);
m_viewBoxF = QRectF();
setSourceSize(QSize());
return;
}
m_renderer->setAspectRatioMode(Qt::KeepAspectRatio);
setSourceSize(m_renderer->defaultSize());
m_viewBoxF = m_renderer->viewBoxF();
if (m_devicePixelRatio == 0) {
m_devicePixelRatio = window()->effectiveDevicePixelRatio();
}
setStatus(Ready);
update();
}
void VectorImage::itemChange(ItemChange change, const ItemChangeData &value)
{
if (change == ItemDevicePixelRatioHasChanged && m_devicePixelRatio != value.realValue) {
m_devicePixelRatio = value.realValue;
update();
}
QQuickItem::itemChange(change, value);
}
void VectorImage::paint(QPainter *painter)
{
if (m_status != Ready) {
return;
}
auto scale_x = sourceSize().width() / width() / m_devicePixelRatio;
auto zoom_x = sourceClipRect().width() / m_viewBoxF.width() * scale_x;
auto scale_y = sourceSize().height() / height() / m_devicePixelRatio;
auto zoom_y = sourceClipRect().height() / m_viewBoxF.height() * scale_y;
QRectF clip(sourceClipRect().x() / zoom_x * scale_x + m_viewBoxF.x(), sourceClipRect().y() / zoom_y * scale_y + m_viewBoxF.y(), sourceSize().width() / zoom_x, sourceSize().height() / zoom_y);
m_renderer->setViewBox(clip);
m_renderer->render(painter);
return;
}
/*
* SPDX-FileCopyrightText: (C) 2021 Mikel Johnson <mikel5764@gmail.com>
*
* SPDX-License-Identifier: LGPL-2.0-or-later
*/
#ifndef VECTOR_IMAGE_H
#define VECTOR_IMAGE_H
#include <QQuickItem>
#include <QPainter>
#include <QQuickPaintedItem>
#include <QSvgRenderer>
#include <memory>
class VectorImage : public QQuickPaintedItem
{
Q_OBJECT
Q_PROPERTY(Status status READ status NOTIFY statusChanged) // read only
Q_PROPERTY(QRectF sourceClipRect READ sourceClipRect WRITE setSourceClipRect NOTIFY sourceClipRectChanged)
Q_PROPERTY(QUrl source READ source WRITE setSource NOTIFY sourceChanged)
Q_PROPERTY(QSize sourceSize READ sourceSize NOTIFY sourceSizeChanged) // read only
public:
VectorImage(QQuickItem *parent = nullptr);
enum Status { Null, Ready, Loading, Error };
Q_ENUM(Status)
void paint(QPainter *painter) override;
void itemChange(ItemChange change, const ItemChangeData &value) override;
QRectF sourceClipRect() const
{
return m_sourceClipRect;
}
QUrl source() const
{
return m_source;
}
QSize sourceSize() const
{
return m_sourceSize;
}
Status status() const
{
return m_status;
}
void setSourceClipRect(const QRectF &sourceClipRect);
void setSource(const QUrl &source);
void setSourceSize(const QSize &sourceSize);
void setStatus(Status status);
Q_SIGNALS:
void sourceClipRectChanged();
void sourceChanged();
void sourceSizeChanged();
void statusChanged(VectorImage::Status);
private:
QUrl m_source;
std::unique_ptr<QSvgRenderer> m_renderer;
QRectF m_sourceClipRect;
QRectF m_viewBoxF;
QSize m_sourceSize;
qreal m_devicePixelRatio;
Status m_status;
};
#endif // VECTOR_IMAGE_H
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment