Files
qelectrotech-source-mirror/tests/qttest/tst_svgsmallpictures.cpp
T
ispyisail da6eb83035 Add an SVG export option to store pictures smaller
An SVG keeps every picture at full resolution, whatever size it is
drawn at: a folio with 22 phone photos shown at a tenth of their size
gave a 270 MB SVG and took 65 s to write, most of it compressing the
photos again.

The export dialog gets "SVG: smaller pictures" (off by default, kept
with the other export settings), and --export-svg gets
--svg-small-pictures. With it, a picture is stored at four times the
size it is drawn at when that is smaller than the picture; smaller
pictures stay whole. Four times is about 375 dpi on a printed folio.

The exporter sets Diagram::smallPictures() for its own drawing only,
so screen, print, PDF and PNG are unchanged. It is not part of
applyProperties(), which every format uses.

On that project: --export-svg 84.9 s -> 34.5 s (opening is 20 s of
both), 270 MB -> 57 MB, peak memory 2.45 -> 1.86 GB. Rendered at twice
the folio size, the two SVGs differ by more than 32/255 on 0.23 % of
pixels, along photo detail.

tst_svgsmallpictures: an 800x600 picture drawn at 0.1 is stored at
320x240 with the option, through --export-svg and through the
dialog's QPicture route, and at 800x600 without it; a 40x30 picture
stays whole. It fails when the factor is changed.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-14 13:12:38 +13:00

144 lines
4.8 KiB
C++

/*
Copyright 2006-2026 The QElectroTech Team
This file is part of QElectroTech.
QElectroTech is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 2 of the License, or
(at your option) any later version.
QElectroTech is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with QElectroTech. If not, see <http://www.gnu.org/licenses/>.
*/
#include "qetapptest.h"
#include "qetgraphicsitem/diagramimageitem.h"
#include <QBuffer>
#include <QDomDocument>
#include <QPicture>
#include <QProcess>
#include <QRegularExpression>
#include <QSvgGenerator>
#include <QTemporaryDir>
/**
SVG export with "smaller pictures" (ExportProperties::svg_small_pictures,
--svg-small-pictures) stores each picture at four times the size it is
drawn at when that is smaller than the picture, and at full resolution
otherwise. fixtures/svg_small_pictures.qet has an 800x600 picture drawn
at a tenth of its size and a 40x30 one at full size.
*/
class tst_svgsmallpictures : public QObject
{
Q_OBJECT
QString m_fixture;
/// Pixel size of each picture embedded in @p svg, smallest first
static QList<QSize> embeddedSizes(const QByteArray &svg)
{
static const QRegularExpression data(
QStringLiteral("data:image/png;base64,([A-Za-z0-9+/=\\s]+)"));
QList<QSize> sizes;
auto it = data.globalMatch(QString::fromLatin1(svg));
while (it.hasNext())
sizes << QImage::fromData(QByteArray::fromBase64(
it.next().captured(1).toLatin1())).size();
std::sort(sizes.begin(), sizes.end(), [](const QSize &a, const QSize &b) {
return a.width() < b.width();
});
return sizes;
}
QByteArray exportWithCli(const QStringList &extra)
{
QTemporaryDir dir;
QProcessEnvironment env = QProcessEnvironment::systemEnvironment();
env.insert(QStringLiteral("QT_QPA_PLATFORM"), QStringLiteral("offscreen"));
QProcess proc;
proc.setProcessEnvironment(env);
proc.start(QStringLiteral(QET_TEST_BINARY_PATH),
QStringList{QStringLiteral("--export-svg"), m_fixture, dir.path()} + extra);
if (!proc.waitForFinished(60000) || proc.exitCode() != 0)
return {};
const QStringList files = QDir(dir.path()).entryList({QStringLiteral("*.svg")});
QFile svg(QDir(dir.path()).filePath(files.value(0)));
return svg.open(QIODevice::ReadOnly) ? svg.readAll() : QByteArray();
}
/// As ExportDialog::generateSvg(): the folio drawn into a QPicture,
/// played into the SVG generator
static QByteArray exportLikeTheDialog(Diagram *diagram, bool small)
{
const QSize size = diagram->imageSize();
QPicture picture;
diagram->setSmallPictures(small);
diagram->toPaintDevice(picture, size.width(), size.height());
diagram->setSmallPictures(false);
QByteArray svg;
QBuffer buffer(&svg);
buffer.open(QIODevice::WriteOnly);
QSvgGenerator generator;
generator.setSize(size);
generator.setOutputDevice(&buffer);
QPainter painter(&generator);
picture.play(&painter);
painter.end();
return svg;
}
private slots:
void initTestCase()
{
m_fixture = QFINDTESTDATA("fixtures/svg_small_pictures.qet");
QVERIFY2(!m_fixture.isEmpty(), "fixture project not found");
QVERIFY(QetAppTest::editor());
}
void cliFullResolutionByDefault()
{
const QByteArray svg = exportWithCli({});
QVERIFY2(!svg.isEmpty(), "--export-svg failed");
QCOMPARE(embeddedSizes(svg), (QList<QSize>{QSize(40, 30), QSize(800, 600)}));
}
void cliSmallPictures()
{
const QByteArray svg = exportWithCli({QStringLiteral("--svg-small-pictures")});
QVERIFY2(!svg.isEmpty(), "--export-svg --svg-small-pictures failed");
// 800 x 600 drawn at 0.1, times 4; the 40 x 30 one is kept whole
QCOMPARE(embeddedSizes(svg), (QList<QSize>{QSize(40, 30), QSize(320, 240)}));
}
void dialogSmallPicturesThenFullAgain()
{
QFile file(m_fixture);
QVERIFY(file.open(QIODevice::ReadOnly));
QDomDocument document;
QVERIFY(document.setContent(&file));
const QDomElement image_xml = document.elementsByTagName(
QStringLiteral("image")).at(0).toElement();
Diagram *diagram = QetAppTest::diagram();
auto *picture = new DiagramImageItem();
QVERIFY(picture->fromXml(image_xml));
diagram->addItem(picture);
QCOMPARE(embeddedSizes(exportLikeTheDialog(diagram, true)),
QList<QSize>{QSize(320, 240)});
// The option lasts for one export only
QVERIFY(!diagram->smallPictures());
QCOMPARE(embeddedSizes(exportLikeTheDialog(diagram, false)),
QList<QSize>{QSize(800, 600)});
delete picture;
}
};
QET_APP_TEST_MAIN(tst_svgsmallpictures)
#include "tst_svgsmallpictures.moc"