1
0
mirror of https://github.com/thp/pyotherside.git synced 2025-01-17 23:22:53 +08:00

Add support for rendering SVG data to the image provider

The image provider can already render SVG images supplied
as format_data due to using QImage as backend.
This unfortunately only works correctly as long as the SVG image is
not scaled, as the image provider ignores the requested_size parameter
for format_data, causing the SVG image to be first rendered at its default size
and then scaled at the bitmap level, resulting in a very blurry image.

As there does not appear to be any easy fix for this when working with
the format_data type add a new data type called format_svg_data that
properly renders the SVG image at requested size with QtSvgRenderer.

Also the documentation has been updated to include format_svg_data
and an example has been added.
This commit is contained in:
Martin Kolman 2015-10-18 22:32:36 +02:00
parent 6d7f526c23
commit 57a6699727
8 changed files with 275 additions and 11 deletions

View File

@ -380,6 +380,12 @@ The following constants have been added in PyOtherSide 1.3:
**pyotherside.version**
Version of PyOtherSide as string.
.. versionadded:: 1.5.0
The following constants have been added in PyOtherSide 1.5:
**pyotherside.format_svg_data**
SVG image XML data
Data Type Mapping
@ -465,9 +471,11 @@ The image provider must return a tuple ``(data, size, format)``:
pixel data in pixels.
**format**
The pixel format of ``data`` (see `constants`_), or
The pixel format of ``data`` (see `constants`_),
``pyotherside.format_data`` if ``data`` contains an
encoded (PNG/JPEG) image instead of raw pixel data.
encoded (PNG/JPEG) image instead of raw pixel data
or ``pyotherside.format_svg_data`` if ``data`` contains
SVG image XML data.
In order to register the image provider with PyOtherSide for use
as provider for ``image://python/`` URLs, the image provider function

View File

@ -0,0 +1,32 @@
#
# PyOtherSide: Asynchronous Python 3 Bindings for Qt 5
# Copyright (c) 2011, 2013, Thomas Perl <m@thp.io>
#
# Permission to use, copy, modify, and/or distribute this software for any
# purpose with or without fee is hereby granted, provided that the above
# copyright notice and this permission notice appear in all copies.
#
# THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
# REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND
# FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
# INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
# LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
# OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
# PERFORMANCE OF THIS SOFTWARE.
#
import pyotherside
import os
def load_data(image_id, requested_size):
# If you return data in the format "pyotherside.format_svg_data" requested_size
# is is used as the target size when rendering the SVG image
# We use the image id to get name of the SVG file to render
with open(image_id, 'rb') as f:
svg_image_data = f.read()
return bytearray(svg_image_data), requested_size, pyotherside.format_svg_data
pyotherside.set_image_provider(load_data)

View File

@ -0,0 +1,41 @@
/**
* PyOtherSide: Asynchronous Python 3 Bindings for Qt 5
* Copyright (c) 2011, 2013, Thomas Perl <m@thp.io>
*
* Permission to use, copy, modify, and/or distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
* copyright notice and this permission notice appear in all copies.
*
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
* REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND
* FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
* INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
* LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
* OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
* PERFORMANCE OF THIS SOFTWARE.
**/
import QtQuick 2.0
import io.thp.pyotherside 1.0
Image {
id: image
width: 300
height: 300
sourceSize.width: 300
sourceSize.height: 300
Python {
Component.onCompleted: {
// Add the directory of this .qml file to the search path
addImportPath(Qt.resolvedUrl('.'));
importModule('imageprovider_svg_data', function () {
image.source = 'image://python/python_logo.svg';
});
}
onError: console.log('Python error: ' + traceback)
}
}

113
examples/python_logo.svg Normal file
View File

@ -0,0 +1,113 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Created with Inkscape (http://www.inkscape.org/) -->
<svg
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://web.resource.org/cc/"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
xmlns:xlink="http://www.w3.org/1999/xlink"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
width="110.4211"
height="109.8461"
id="svg2169"
sodipodi:version="0.32"
inkscape:version="0.45.1"
version="1.0"
sodipodi:docbase="/home/bene/Desktop"
sodipodi:docname="dessin-1.svg"
inkscape:output_extension="org.inkscape.output.svg.inkscape">
<defs
id="defs2171">
<linearGradient
id="linearGradient11301"
inkscape:collect="always">
<stop
id="stop11303"
offset="0"
style="stop-color:#ffe052;stop-opacity:1" />
<stop
id="stop11305"
offset="1"
style="stop-color:#ffc331;stop-opacity:1" />
</linearGradient>
<linearGradient
gradientUnits="userSpaceOnUse"
y2="168.1012"
x2="147.77737"
y1="111.92053"
x1="89.136749"
id="linearGradient11307"
xlink:href="#linearGradient11301"
inkscape:collect="always" />
<linearGradient
id="linearGradient9515"
inkscape:collect="always">
<stop
id="stop9517"
offset="0"
style="stop-color:#387eb8;stop-opacity:1" />
<stop
id="stop9519"
offset="1"
style="stop-color:#366994;stop-opacity:1" />
</linearGradient>
<linearGradient
gradientUnits="userSpaceOnUse"
y2="131.85291"
x2="110.14919"
y1="77.070274"
x1="55.549179"
id="linearGradient9521"
xlink:href="#linearGradient9515"
inkscape:collect="always" />
</defs>
<sodipodi:namedview
id="base"
pagecolor="#ffffff"
bordercolor="#666666"
borderopacity="1.0"
inkscape:pageopacity="0.0"
inkscape:pageshadow="2"
inkscape:zoom="0.24748737"
inkscape:cx="-260.46312"
inkscape:cy="316.02744"
inkscape:document-units="px"
inkscape:current-layer="layer1"
width="131.10236px"
height="184.25197px"
inkscape:window-width="872"
inkscape:window-height="624"
inkscape:window-x="5"
inkscape:window-y="48" />
<metadata
id="metadata2174">
<rdf:RDF>
<cc:Work
rdf:about="">
<dc:format>image/svg+xml</dc:format>
<dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
</cc:Work>
</rdf:RDF>
</metadata>
<g
inkscape:label="Calque 1"
inkscape:groupmode="layer"
id="layer1"
transform="translate(-473.36088,-251.72485)">
<g
id="g1894"
transform="translate(428.42338,184.2561)">
<path
style="opacity:1;color:#000000;fill:url(#linearGradient9521);fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:1;stroke-linecap:butt;stroke-linejoin:miter;marker:none;marker-start:none;marker-mid:none;marker-end:none;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;visibility:visible;display:inline;overflow:visible"
d="M 99.75,67.46875 C 71.718268,67.468752 73.46875,79.625 73.46875,79.625 L 73.5,92.21875 L 100.25,92.21875 L 100.25,96 L 62.875,96 C 62.875,96 44.9375,93.965724 44.9375,122.25 C 44.937498,150.53427 60.59375,149.53125 60.59375,149.53125 L 69.9375,149.53125 L 69.9375,136.40625 C 69.9375,136.40625 69.433848,120.75 85.34375,120.75 C 101.25365,120.75 111.875,120.75 111.875,120.75 C 111.875,120.75 126.78125,120.99096 126.78125,106.34375 C 126.78125,91.696544 126.78125,82.125 126.78125,82.125 C 126.78125,82.124998 129.04443,67.46875 99.75,67.46875 z M 85,75.9375 C 87.661429,75.937498 89.8125,78.088571 89.8125,80.75 C 89.812502,83.411429 87.661429,85.5625 85,85.5625 C 82.338571,85.562502 80.1875,83.411429 80.1875,80.75 C 80.187498,78.088571 82.338571,75.9375 85,75.9375 z "
id="path8615" />
<path
id="path8620"
d="M 100.5461,177.31485 C 128.57784,177.31485 126.82735,165.1586 126.82735,165.1586 L 126.7961,152.56485 L 100.0461,152.56485 L 100.0461,148.7836 L 137.4211,148.7836 C 137.4211,148.7836 155.3586,150.81787 155.3586,122.53359 C 155.35861,94.249323 139.70235,95.252343 139.70235,95.252343 L 130.3586,95.252343 L 130.3586,108.37734 C 130.3586,108.37734 130.86226,124.03359 114.95235,124.03359 C 99.042448,124.03359 88.421098,124.03359 88.421098,124.03359 C 88.421098,124.03359 73.514848,123.79263 73.514848,138.43985 C 73.514848,153.08705 73.514848,162.6586 73.514848,162.6586 C 73.514848,162.6586 71.251668,177.31485 100.5461,177.31485 z M 115.2961,168.8461 C 112.63467,168.8461 110.4836,166.69503 110.4836,164.0336 C 110.4836,161.37217 112.63467,159.2211 115.2961,159.2211 C 117.95753,159.2211 120.1086,161.37217 120.1086,164.0336 C 120.10861,166.69503 117.95753,168.8461 115.2961,168.8461 z "
style="opacity:1;color:#000000;fill:url(#linearGradient11307);fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:1;stroke-linecap:butt;stroke-linejoin:miter;marker:none;marker-start:none;marker-mid:none;marker-end:none;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;visibility:visible;display:inline;overflow:visible" />
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 5.4 KiB

View File

@ -22,6 +22,8 @@
#include "ensure_gil_state.h"
#include <QDebug>
#include <QSvgRenderer>
#include <QPainter>
QPythonImageProvider::QPythonImageProvider()
@ -109,7 +111,9 @@ QPythonImageProvider::requestImage(const QString &id, QSize *size, const QSize &
}
switch (format) {
case -1: /* pyotherside.format_data */
case PYOTHERSIDE_IMAGE_FORMAT_ENCODED: /* pyotherside.format_data */
break;
case PYOTHERSIDE_IMAGE_FORMAT_SVG: /* pyotherside.format_svg_data */
break;
case QImage::Format_Mono:
case QImage::Format_MonoLSB:
@ -141,7 +145,7 @@ QPythonImageProvider::requestImage(const QString &id, QSize *size, const QSize &
// While we could re-pack the data to be aligned, we don't want to do that
// for performance reasons.
// If we're using 32-bit data (e.g. ARGB32), it will always be aligned.
if (format != -1 && bitsPerPixel != 32) {
if (format >= 0 && bitsPerPixel != 32) {
if ((bitsPerPixel * width) % 32 != 0) {
// If actualBytes > requiredBytes, we can check if there are enough
// bytes to consider the data 32-bit aligned (from Python) and avoid
@ -155,7 +159,7 @@ QPythonImageProvider::requestImage(const QString &id, QSize *size, const QSize &
}
}
if (format != -1 && requiredBytes > actualBytes) {
if (format >= 0 && requiredBytes > actualBytes) {
qDebug() << "Format" << (enum QImage::Format)format <<
"at size" << QSize(width, height) <<
"requires at least" << requiredBytes <<
@ -163,11 +167,72 @@ QPythonImageProvider::requestImage(const QString &id, QSize *size, const QSize &
goto cleanup;
}
if (format < 0) {
switch (format) {
case PYOTHERSIDE_IMAGE_FORMAT_ENCODED: {
// Pixel data is actually encoded image data that we need to decode
img.loadFromData((const unsigned char*)PyByteArray_AsString(pixels), PyByteArray_Size(pixels));
break;
}
case PYOTHERSIDE_IMAGE_FORMAT_SVG: {
// Convert the Python byte array to a QByteArray
QByteArray svgDataArray(PyByteArray_AsString(pixels), PyByteArray_Size(pixels));
if (format == -1) {
// Pixel data is actually encoded image data that we need to decode
img.loadFromData((const unsigned char*)PyByteArray_AsString(pixels),
PyByteArray_Size(pixels));
// Load the SVG data to the SVG renderer
QSvgRenderer renderer(svgDataArray);
// Handle width, height or both not being set
if (width < 0 && height < 0) {
// Both Width and Height have not been set - use the defaults from the SVG data
// (each SVG image has a default size)
// NOTE: we get a -1,-1 requestedSize only if sourceSize is not set at all,
// if either width or height is set then the other one is 0, not -1
width = renderer.defaultSize().width();
height = renderer.defaultSize().height();
} else { // At least width or height is valid
if (width <= 0) {
// Width is not set, use default width scaled according to height to keep
// aspect ratio
if (renderer.defaultSize().height() != 0) { // Protect from division by zero
width = (float)renderer.defaultSize().width()*((float)height/(float)renderer.defaultSize().height());
}
}
if (height <= 0) {
// Height is not set, use default height scaled according to width to keep
// aspect ratio
if (renderer.defaultSize().width() != 0) { // Protect from division by zero
height = (float)renderer.defaultSize().height()*((float)width/(float)renderer.defaultSize().width());
}
}
}
// The pixel data is actually SVG image data that we need to render at correct size
//
// Note: according to the QImage and QPainter documentation the optimal QImage
// format for drawing is Format_ARGB32_Premultiplied
img = QImage(width, height, QImage::Format_ARGB32_Premultiplied);
// According to the documentation an empty QImage needs to be "flushed" before
// being used with QPainter to prevent rendering artifacts from showing up
img.fill(Qt::transparent);
// Convert the Python byte array to a QByteArray
QByteArray svgDataArray(PyByteArray_AsString(pixels), PyByteArray_Size(pixels));
// Load the SVG data to the SVG renderer
QSvgRenderer renderer(svgDataArray);
// Paints the rendered SVG to the QImage instance
QPainter painter(&img);
renderer.render(&painter);
break;
}
default:
qWarning() << "Unknown format" << format <<
"has been specified and will not be handled.";
break;
}
} else {
// Need to keep a reference to the byte array object, as it contains
// the backing store data for the QImage.

View File

@ -454,7 +454,8 @@ PyOtherSide_init()
PyModule_AddIntConstant(pyotherside, "format_rgb444", QImage::Format_RGB444);
// Custom constant - pixels are to be interpreted as encoded image file data
PyModule_AddIntConstant(pyotherside, "format_data", -1);
PyModule_AddIntConstant(pyotherside, "format_data", PYOTHERSIDE_IMAGE_FORMAT_ENCODED);
PyModule_AddIntConstant(pyotherside, "format_svg_data", PYOTHERSIDE_IMAGE_FORMAT_SVG);
// Version of PyOtherSide (new in 1.3)
PyModule_AddStringConstant(pyotherside, "version", PYOTHERSIDE_VERSION);

View File

@ -28,6 +28,10 @@
#include <QVariant>
#include <QString>
enum PyOtherSideImageFormat {
PYOTHERSIDE_IMAGE_FORMAT_ENCODED = -1,
PYOTHERSIDE_IMAGE_FORMAT_SVG = -2,
};
class QPythonPriv : public QObject {
Q_OBJECT

View File

@ -8,7 +8,7 @@ PLUGIN_IMPORT_PATH = io/thp/pyotherside
TEMPLATE = lib
CONFIG += qt plugin
QT += qml quick
QT += qml quick svg
target.path = $$[QT_INSTALL_QML]/$$PLUGIN_IMPORT_PATH
INSTALLS += target