Merge pull request #1477 from ispyisail/perf/parallel-picture-decode
Linux build and tests / Build and test (Qt 6, Debug) (push) Failing after 2m6s

Speed up opening folios with photos: decode pictures in parallel
This commit is contained in:
ispyisail
2026-10-14 19:10:59 +13:00
committed by GitHub
5 changed files with 240 additions and 24 deletions
+32 -9
View File
@@ -53,6 +53,8 @@
#include "foliogrid.h"
#include <QGraphicsView>
#include <QTextStream>
#include <QThreadPool>
#include <QtConcurrentMap>
#include <algorithm>
#include <climits>
#include <cassert>
@@ -2006,16 +2008,37 @@ bool Diagram::fromXml(QDomElement &document,
}
// Load image
QList<DiagramImageItem *> added_images;
for (auto image_xml : QET::findInDomElement(root,
QStringLiteral("images"),
QStringLiteral("image"))) {
DiagramImageItem *dii = new DiagramImageItem ();
// Decoding is most of the time of a folio with photos (a quarter of
// a second each), so the pictures are decoded first, in parallel,
// and the items built afterwards in this thread.
const QList<QDomElement> images_xml = QET::findInDomElement(
root, QStringLiteral("images"), QStringLiteral("image"));
QList<DiagramImageItem::Picture> pictures;
pictures.reserve(images_xml.size());
for (const QDomElement &image_xml : images_xml) {
const QString picture_file = QetContainer::pictureFile(image_xml);
if (!picture_file.isEmpty() && project())
dii -> fromXml(image_xml, project() -> containerPicture(picture_file));
else
dii -> fromXml(image_xml);
pictures << DiagramImageItem::pictureOf(
image_xml,
!picture_file.isEmpty() && project()
? project() -> containerPicture(picture_file)
: QByteArray());
}
if (pictures.size() > 1) {
QThreadPool pool;
pool.setMaxThreadCount(std::min<int>(QThread::idealThreadCount(),
pictures.size()));
QtConcurrent::blockingMap(&pool, pictures,
&DiagramImageItem::decodePicture);
} else {
for (DiagramImageItem::Picture &picture : pictures)
DiagramImageItem::decodePicture(picture);
}
QList<DiagramImageItem *> added_images;
for (qsizetype i = 0 ; i < images_xml.size() ; ++i) {
const QDomElement &image_xml = images_xml.at(i);
DiagramImageItem *dii = new DiagramImageItem ();
dii -> fromXml(image_xml, std::move(pictures[i]));
settle_uuid(dii, image_xml, QStringLiteral("image"), added_images.size());
ItemGroups::setGroup(dii, ItemGroups::read(image_xml));
addItem(dii);
+59 -15
View File
@@ -1819,6 +1819,60 @@ bool DiagramImageItem::fromXml(const QDomElement &e)
pictures as files (QetContainer::join()).
*/
bool DiagramImageItem::fromXml(const QDomElement &e, const QByteArray &picture)
{
Picture p = pictureOf(e, picture);
decodePicture(p);
return fromXml(e, std::move(p));
}
/**
@brief DiagramImageItem::pictureOf
@return the picture of @p e, @p picture unless it is empty, else the
base64 text of @p e, still encoded : decodePicture() does the slow part.
Reads the DOM, so GUI thread only.
*/
DiagramImageItem::Picture DiagramImageItem::pictureOf(const QDomElement &e,
const QByteArray &picture)
{
Picture p;
p.bytes = picture;
if (p.bytes.isEmpty())
{
// From the first text node only, not e.text(): that also collects
// the text of every child element, so on a cropped or colour-keyed
// picture it carries <image_base>'s bytes too.
const QDomNode image_node = e.firstChild();
if (image_node.isText())
p.base64 = image_node.toText().data();
else
p.found = false;
}
return p;
}
/**
@brief DiagramImageItem::decodePicture
Decodes the base64 text and the picture file of @p picture. Touches
nothing else, so it can run in any thread : a large photo takes about
a quarter of a second, and Diagram::fromXml() decodes a folio's
pictures in parallel.
*/
void DiagramImageItem::decodePicture(Picture &picture)
{
if (picture.bytes.isEmpty() && !picture.base64.isEmpty())
{
picture.bytes = QByteArray::fromBase64(picture.base64.toLatin1());
picture.base64.clear();
}
picture.image = QImage::fromData(picture.bytes);
}
/**
@brief DiagramImageItem::fromXml
Load this image from xml element e, its picture already decoded by
decodePicture().
*/
bool DiagramImageItem::fromXml(const QDomElement &e, Picture picture)
{
if (e.tagName() != "image") {
return (false);
@@ -1828,23 +1882,13 @@ bool DiagramImageItem::fromXml(const QDomElement &e, const QByteArray &picture)
const QUuid uuid(e.attribute(QStringLiteral("uuid")));
if (!uuid.isNull() && uuid != m_uuid) setUuid(uuid);
// From the first text node only, not e.text(): that also collects the
// text of every child element, so on a cropped or colour-keyed picture
// it carries <image_base>'s bytes too. Decoded once: on a project with
// large pictures each decoding costs seconds.
QByteArray array = picture;
if (array.isEmpty())
{
QDomNode image_node = e.firstChild();
if (!image_node.isText()) {
return (false);
}
array = QByteArray::fromBase64(image_node.toText().data().toLatin1());
if (!picture.found) {
return (false);
}
const QByteArray array = picture.bytes;
//Set QPixmap from the array
QPixmap pixmap;
pixmap.loadFromData(array);
//Set QPixmap from the decoded picture
QPixmap pixmap = QPixmap::fromImage(std::move(picture.image));
setPixmap(pixmap);
if (isPng(array) && !pixmap_.isNull())
{
@@ -24,6 +24,7 @@
#include <QColor>
#include <QFont>
#include <QImage>
#include <QList>
#include <QUuid>
#include <QStyleOptionGraphicsItem>
@@ -119,8 +120,20 @@ class DiagramImageItem : public QetGraphicsItem {
*/
int type() const override { return Type; }
/// A picture as read from its <image> element, before and after
/// decodePicture(), which may run outside the GUI thread.
struct Picture {
QString base64; ///< text of <image>, decoded when bytes is empty
QByteArray bytes; ///< the picture file (PNG, JPEG...)
QImage image; ///< bytes, decoded
bool found = true; ///< false : neither bytes nor text
};
static Picture pictureOf(const QDomElement &, const QByteArray &picture);
static void decodePicture(Picture &picture);
virtual bool fromXml(const QDomElement &);
bool fromXml(const QDomElement &, const QByteArray &picture);
bool fromXml(const QDomElement &, Picture picture);
virtual QDomElement toXml(QDomDocument &) const;
QUuid uuid() const {return m_uuid;}
void setUuid(const QUuid &uuid) {m_uuid = uuid; emit uuidChanged();}