mirror of https://github.com/cutefishos/qt-plugins
feat(imageformat): add librsvg SVG image plugin
parent
688d4d44d9
commit
bf650f6047
@ -0,0 +1,36 @@
|
||||
cmake_minimum_required(VERSION 3.16)
|
||||
|
||||
project(qsvg-rsvg)
|
||||
|
||||
set(TARGET qsvg-rsvg)
|
||||
set(CMAKE_CXX_STANDARD 17)
|
||||
set(CMAKE_AUTOMOC ON)
|
||||
set(CMAKE_INCLUDE_CURRENT_DIR ON)
|
||||
|
||||
find_package(Qt6 REQUIRED COMPONENTS Core Gui)
|
||||
find_package(PkgConfig REQUIRED)
|
||||
pkg_check_modules(LIBRSVG REQUIRED IMPORTED_TARGET librsvg-2.0)
|
||||
|
||||
set(SRCS
|
||||
rsvgimageiohandler.cpp
|
||||
rsvgimageiohandler.h
|
||||
rsvgimageplugin.cpp
|
||||
rsvgimageplugin.h
|
||||
)
|
||||
|
||||
add_library(${TARGET} MODULE ${SRCS})
|
||||
target_compile_definitions(${TARGET} PRIVATE QT_NO_KEYWORDS)
|
||||
set_target_properties(${TARGET} PROPERTIES
|
||||
LIBRARY_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/imageformats"
|
||||
)
|
||||
target_link_libraries(${TARGET}
|
||||
Qt6::Core
|
||||
Qt6::Gui
|
||||
PkgConfig::LIBRSVG
|
||||
)
|
||||
|
||||
find_program(QT_PATHS_EXECUTABLE NAMES qtpaths6 REQUIRED)
|
||||
execute_process(COMMAND "${QT_PATHS_EXECUTABLE}" -query QT_INSTALL_PLUGINS
|
||||
OUTPUT_VARIABLE CMAKE_INSTALL_QTPLUGINDIR OUTPUT_STRIP_TRAILING_WHITESPACE)
|
||||
|
||||
install(TARGETS ${TARGET} DESTINATION ${CMAKE_INSTALL_QTPLUGINDIR}/imageformats/)
|
||||
@ -0,0 +1,4 @@
|
||||
{
|
||||
"Keys": [ "svg" ],
|
||||
"MimeTypes": [ "image/svg+xml" ]
|
||||
}
|
||||
@ -0,0 +1,266 @@
|
||||
#include "rsvgimageiohandler.h"
|
||||
|
||||
#include <QImage>
|
||||
#include <QIODevice>
|
||||
#include <QVariant>
|
||||
|
||||
#include <librsvg/rsvg.h>
|
||||
|
||||
#include <algorithm>
|
||||
#include <cmath>
|
||||
#include <limits>
|
||||
#include <memory>
|
||||
|
||||
namespace {
|
||||
|
||||
bool isSvg(const QByteArray &data)
|
||||
{
|
||||
return data.left(64 * 1024).toLower().contains("<svg");
|
||||
}
|
||||
|
||||
struct CairoContextDeleter
|
||||
{
|
||||
void operator()(cairo_t *context) const noexcept
|
||||
{
|
||||
if (context) {
|
||||
cairo_destroy(context);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
struct CairoSurfaceDeleter
|
||||
{
|
||||
void operator()(cairo_surface_t *surface) const noexcept
|
||||
{
|
||||
if (surface) {
|
||||
cairo_surface_destroy(surface);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
struct GErrorDeleter
|
||||
{
|
||||
void operator()(GError *error) const noexcept
|
||||
{
|
||||
if (error) {
|
||||
g_error_free(error);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
struct RsvgHandleDeleter
|
||||
{
|
||||
void operator()(RsvgHandle *handle) const noexcept
|
||||
{
|
||||
if (handle) {
|
||||
g_object_unref(handle);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
using CairoContextPtr = std::unique_ptr<cairo_t, CairoContextDeleter>;
|
||||
using CairoSurfacePtr = std::unique_ptr<cairo_surface_t, CairoSurfaceDeleter>;
|
||||
using GErrorPtr = std::unique_ptr<GError, GErrorDeleter>;
|
||||
using RsvgHandlePtr = std::unique_ptr<RsvgHandle, RsvgHandleDeleter>;
|
||||
|
||||
RsvgHandlePtr createHandle(const QByteArray &data)
|
||||
{
|
||||
GError *rawError = nullptr;
|
||||
RsvgHandlePtr handle(rsvg_handle_new_from_data(
|
||||
reinterpret_cast<const guint8 *>(data.constData()),
|
||||
static_cast<gsize>(data.size()),
|
||||
&rawError));
|
||||
GErrorPtr error(rawError);
|
||||
return handle;
|
||||
}
|
||||
|
||||
int toPixelDimension(gdouble dimension)
|
||||
{
|
||||
if (!std::isfinite(dimension) || dimension <= 0.0
|
||||
|| dimension > static_cast<gdouble>(std::numeric_limits<int>::max())) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
const gdouble rounded = std::ceil(dimension);
|
||||
if (rounded > static_cast<gdouble>(std::numeric_limits<int>::max())) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
return std::max(1, static_cast<int>(rounded));
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
bool RsvgImageIOHandler::canRead() const
|
||||
{
|
||||
if (!device() || !device()->isReadable()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return format().compare("svg", Qt::CaseInsensitive) == 0
|
||||
|| isSvg(device()->peek(64 * 1024));
|
||||
}
|
||||
|
||||
bool RsvgImageIOHandler::read(QImage *image)
|
||||
{
|
||||
if (!image || !loadIntrinsicSize()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const QSize targetSize = m_scaledSize.isValid() ? m_scaledSize : m_intrinsicSize;
|
||||
return render(targetSize, image);
|
||||
}
|
||||
|
||||
QVariant RsvgImageIOHandler::option(ImageOption option) const
|
||||
{
|
||||
switch (option) {
|
||||
case Size:
|
||||
return loadIntrinsicSize() ? QVariant::fromValue(m_intrinsicSize) : QVariant();
|
||||
case ScaledSize:
|
||||
return m_scaledSize;
|
||||
default:
|
||||
return QImageIOHandler::option(option);
|
||||
}
|
||||
}
|
||||
|
||||
void RsvgImageIOHandler::setOption(ImageOption option, const QVariant &value)
|
||||
{
|
||||
if (option == ScaledSize) {
|
||||
m_scaledSize = value.canConvert<QSize>() ? value.toSize() : QSize();
|
||||
return;
|
||||
}
|
||||
|
||||
QImageIOHandler::setOption(option, value);
|
||||
}
|
||||
|
||||
bool RsvgImageIOHandler::supportsOption(ImageOption option) const
|
||||
{
|
||||
return option == Size || option == ScaledSize || QImageIOHandler::supportsOption(option);
|
||||
}
|
||||
|
||||
bool RsvgImageIOHandler::loadData() const
|
||||
{
|
||||
if (m_dataLoaded) {
|
||||
return !m_data.isEmpty();
|
||||
}
|
||||
|
||||
m_dataLoaded = true;
|
||||
if (!device()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const qint64 position = device()->pos();
|
||||
m_data = device()->readAll();
|
||||
if (!device()->isSequential() && position >= 0) {
|
||||
device()->seek(position);
|
||||
}
|
||||
|
||||
return !m_data.isEmpty();
|
||||
}
|
||||
|
||||
bool RsvgImageIOHandler::loadIntrinsicSize() const
|
||||
{
|
||||
if (m_metadataLoaded) {
|
||||
return m_valid;
|
||||
}
|
||||
|
||||
m_metadataLoaded = true;
|
||||
if (!loadData()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const RsvgHandlePtr handle = createHandle(m_data);
|
||||
if (!handle) {
|
||||
return false;
|
||||
}
|
||||
|
||||
gdouble width = 0.0;
|
||||
gdouble height = 0.0;
|
||||
if (!rsvg_handle_get_intrinsic_size_in_pixels(handle.get(), &width, &height)) {
|
||||
gboolean hasWidth = false;
|
||||
gboolean hasHeight = false;
|
||||
gboolean hasViewbox = false;
|
||||
RsvgLength intrinsicWidth {};
|
||||
RsvgLength intrinsicHeight {};
|
||||
RsvgRectangle viewbox {};
|
||||
rsvg_handle_get_intrinsic_dimensions(handle.get(),
|
||||
&hasWidth,
|
||||
&intrinsicWidth,
|
||||
&hasHeight,
|
||||
&intrinsicHeight,
|
||||
&hasViewbox,
|
||||
&viewbox);
|
||||
if (!hasViewbox || viewbox.width <= 0.0 || viewbox.height <= 0.0) {
|
||||
return false;
|
||||
}
|
||||
width = viewbox.width;
|
||||
height = viewbox.height;
|
||||
}
|
||||
|
||||
const int pixelWidth = toPixelDimension(width);
|
||||
const int pixelHeight = toPixelDimension(height);
|
||||
if (pixelWidth <= 0 || pixelHeight <= 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
m_intrinsicSize = QSize(pixelWidth, pixelHeight);
|
||||
m_valid = true;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool RsvgImageIOHandler::render(const QSize &size, QImage *image) const
|
||||
{
|
||||
if (!image || !size.isValid() || !loadData()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const RsvgHandlePtr handle = createHandle(m_data);
|
||||
if (!handle) {
|
||||
return false;
|
||||
}
|
||||
|
||||
QImage target(size, QImage::Format_ARGB32_Premultiplied);
|
||||
if (target.isNull()) {
|
||||
return false;
|
||||
}
|
||||
target.fill(Qt::transparent);
|
||||
|
||||
const int cairoStride = cairo_format_stride_for_width(CAIRO_FORMAT_ARGB32, size.width());
|
||||
if (cairoStride != target.bytesPerLine()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
CairoSurfacePtr surface(cairo_image_surface_create_for_data(
|
||||
target.bits(),
|
||||
CAIRO_FORMAT_ARGB32,
|
||||
size.width(),
|
||||
size.height(),
|
||||
target.bytesPerLine()));
|
||||
if (!surface || cairo_surface_status(surface.get()) != CAIRO_STATUS_SUCCESS) {
|
||||
return false;
|
||||
}
|
||||
|
||||
CairoContextPtr context(cairo_create(surface.get()));
|
||||
if (!context || cairo_status(context.get()) != CAIRO_STATUS_SUCCESS) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const RsvgRectangle viewport {
|
||||
0.0,
|
||||
0.0,
|
||||
static_cast<gdouble>(size.width()),
|
||||
static_cast<gdouble>(size.height())
|
||||
};
|
||||
|
||||
GError *rawError = nullptr;
|
||||
const gboolean rendered = rsvg_handle_render_document(handle.get(), context.get(), &viewport, &rawError);
|
||||
GErrorPtr error(rawError);
|
||||
if (!rendered || cairo_status(context.get()) != CAIRO_STATUS_SUCCESS) {
|
||||
return false;
|
||||
}
|
||||
|
||||
context.reset();
|
||||
cairo_surface_flush(surface.get());
|
||||
*image = target.copy();
|
||||
return !image->isNull();
|
||||
}
|
||||
@ -0,0 +1,31 @@
|
||||
#ifndef RSVGIMAGEIOHANDLER_H
|
||||
#define RSVGIMAGEIOHANDLER_H
|
||||
|
||||
#include <QByteArray>
|
||||
#include <QImageIOHandler>
|
||||
#include <QSize>
|
||||
|
||||
class RsvgImageIOHandler final : public QImageIOHandler
|
||||
{
|
||||
public:
|
||||
bool canRead() const override;
|
||||
bool read(QImage *image) override;
|
||||
|
||||
QVariant option(ImageOption option) const override;
|
||||
void setOption(ImageOption option, const QVariant &value) override;
|
||||
bool supportsOption(ImageOption option) const override;
|
||||
|
||||
private:
|
||||
bool loadData() const;
|
||||
bool loadIntrinsicSize() const;
|
||||
bool render(const QSize &size, QImage *image) const;
|
||||
|
||||
mutable QByteArray m_data;
|
||||
mutable QSize m_intrinsicSize;
|
||||
mutable bool m_dataLoaded = false;
|
||||
mutable bool m_metadataLoaded = false;
|
||||
mutable bool m_valid = false;
|
||||
QSize m_scaledSize;
|
||||
};
|
||||
|
||||
#endif // RSVGIMAGEIOHANDLER_H
|
||||
@ -0,0 +1,39 @@
|
||||
#include "rsvgimageplugin.h"
|
||||
|
||||
#include "rsvgimageiohandler.h"
|
||||
|
||||
#include <QIODevice>
|
||||
|
||||
namespace {
|
||||
|
||||
bool isSvg(const QByteArray &data)
|
||||
{
|
||||
return data.left(64 * 1024).toLower().contains("<svg");
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
QImageIOPlugin::Capabilities RsvgImagePlugin::capabilities(QIODevice *device, const QByteArray &format) const
|
||||
{
|
||||
if (!device || !device->isReadable()) {
|
||||
return {};
|
||||
}
|
||||
|
||||
if (!format.isEmpty() && format.compare("svg", Qt::CaseInsensitive) != 0) {
|
||||
return {};
|
||||
}
|
||||
|
||||
if (format.compare("svg", Qt::CaseInsensitive) == 0) {
|
||||
return CanRead;
|
||||
}
|
||||
|
||||
return isSvg(device->peek(64 * 1024)) ? Capabilities(CanRead) : Capabilities();
|
||||
}
|
||||
|
||||
QImageIOHandler *RsvgImagePlugin::create(QIODevice *device, const QByteArray &format) const
|
||||
{
|
||||
auto *handler = new RsvgImageIOHandler;
|
||||
handler->setDevice(device);
|
||||
handler->setFormat(format.isEmpty() ? QByteArrayLiteral("svg") : format);
|
||||
return handler;
|
||||
}
|
||||
@ -0,0 +1,16 @@
|
||||
#ifndef RSVGIMAGEPLUGIN_H
|
||||
#define RSVGIMAGEPLUGIN_H
|
||||
|
||||
#include <QImageIOPlugin>
|
||||
|
||||
class RsvgImagePlugin final : public QImageIOPlugin
|
||||
{
|
||||
Q_OBJECT
|
||||
Q_PLUGIN_METADATA(IID "org.qt-project.Qt.QImageIOHandlerFactoryInterface" FILE "rsvgimage.json")
|
||||
|
||||
public:
|
||||
Capabilities capabilities(QIODevice *device, const QByteArray &format) const override;
|
||||
QImageIOHandler *create(QIODevice *device, const QByteArray &format = QByteArray()) const override;
|
||||
};
|
||||
|
||||
#endif // RSVGIMAGEPLUGIN_H
|
||||
Loading…
Reference in New Issue