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:
parent
6d7f526c23
commit
57a6699727
@ -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
|
||||
|
32
examples/imageprovider_svg_data.py
Normal file
32
examples/imageprovider_svg_data.py
Normal 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)
|
41
examples/imageprovider_svg_data.qml
Normal file
41
examples/imageprovider_svg_data.qml
Normal 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
113
examples/python_logo.svg
Normal 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 |
@ -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.
|
||||
|
@ -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);
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
Loading…
x
Reference in New Issue
Block a user