mirror of
https://gitlab.linphone.org/BC/public/linphone-desktop.git
synced 2026-01-17 11:28:07 +00:00
303 lines
9.4 KiB
C++
303 lines
9.4 KiB
C++
/*
|
|
* ImageProvider.cpp
|
|
* Copyright (C) 2017-2018 Belledonne Communications, Grenoble, France
|
|
*
|
|
* This program 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.
|
|
*
|
|
* This program 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 this program; if not, write to the Free Software
|
|
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
|
*
|
|
* Created on: June 19, 2017
|
|
* Author: Ronan Abhamon
|
|
*/
|
|
|
|
#include <QElapsedTimer>
|
|
#include <QFileInfo>
|
|
#include <QPainter>
|
|
#include <QSvgRenderer>
|
|
|
|
#include "../App.hpp"
|
|
|
|
#include "ImageProvider.hpp"
|
|
|
|
using namespace std;
|
|
|
|
// =============================================================================
|
|
|
|
namespace {
|
|
// Max image size in bytes. (100Kb)
|
|
constexpr qint64 cMaxImageSize = 102400;
|
|
}
|
|
|
|
static void removeAttribute (QXmlStreamAttributes &readerAttributes, const QString &name) {
|
|
auto it = find_if(readerAttributes.cbegin(), readerAttributes.cend(), [&name](const QXmlStreamAttribute &attribute) {
|
|
return name == attribute.name() && !attribute.prefix().length();
|
|
});
|
|
if (it != readerAttributes.cend())
|
|
readerAttributes.remove(int(distance(readerAttributes.cbegin(), it)));
|
|
}
|
|
|
|
static QByteArray buildByteArrayAttribute (const QByteArray &name, const QByteArray &value) {
|
|
QByteArray attribute = name;
|
|
attribute.append("=\"");
|
|
attribute.append(value);
|
|
attribute.append("\" ");
|
|
return attribute;
|
|
}
|
|
|
|
static QByteArray parseFillAndStroke (QXmlStreamAttributes &readerAttributes, const Colors &colors) {
|
|
static QRegExp regex("^color-([^-]+)-(fill|stroke)$");
|
|
|
|
QByteArray attributes;
|
|
|
|
for (const auto &classValue : readerAttributes.value("class").toLatin1().split(' ')) {
|
|
regex.indexIn(classValue.trimmed());
|
|
if (Q_LIKELY(regex.pos() == -1))
|
|
continue;
|
|
|
|
const QStringList list = regex.capturedTexts();
|
|
|
|
const QVariant colorValue = colors.property(list[1].toStdString().c_str());
|
|
if (Q_UNLIKELY(!colorValue.isValid())) {
|
|
qWarning() << QStringLiteral("Color name `%1` does not exist.").arg(list[1]);
|
|
continue;
|
|
}
|
|
|
|
::removeAttribute(readerAttributes, list[2]);
|
|
attributes.append(::buildByteArrayAttribute(list[2].toLatin1(), colorValue.value<QColor>().name().toLatin1()));
|
|
}
|
|
|
|
return attributes;
|
|
}
|
|
|
|
static QByteArray parseStyle (QXmlStreamAttributes &readerAttributes, const Colors &colors) {
|
|
static QRegExp regex("^color-([^-]+)-style-(fill|stroke)$");
|
|
|
|
QByteArray attribute;
|
|
|
|
QSet<QString> overrode;
|
|
for (const auto &classValue : readerAttributes.value("class").toLatin1().split(' ')) {
|
|
regex.indexIn(classValue.trimmed());
|
|
if (Q_LIKELY(regex.pos() == -1))
|
|
continue;
|
|
|
|
const QStringList list = regex.capturedTexts();
|
|
|
|
overrode.insert(list[2]);
|
|
|
|
const QVariant colorValue = colors.property(list[1].toStdString().c_str());
|
|
if (Q_UNLIKELY(!colorValue.isValid())) {
|
|
qWarning() << QStringLiteral("Color name `%1` does not exist.").arg(list[1]);
|
|
continue;
|
|
}
|
|
|
|
attribute.append(list[2].toLatin1());
|
|
attribute.append(":");
|
|
attribute.append(colorValue.value<QColor>().name().toLatin1());
|
|
attribute.append(";");
|
|
}
|
|
|
|
const QByteArrayList styleValues = readerAttributes.value("style").toLatin1().split(';');
|
|
for (const auto &styleValue : styleValues) {
|
|
const QByteArrayList list = styleValue.split(':');
|
|
if (Q_UNLIKELY(list.length() > 0 && !overrode.contains(list[0]))) {
|
|
attribute.append(styleValue);
|
|
attribute.append(";");
|
|
}
|
|
}
|
|
|
|
::removeAttribute(readerAttributes, "style");
|
|
|
|
if (attribute.length() > 0) {
|
|
attribute.prepend("style=\"");
|
|
attribute.append("\" ");
|
|
}
|
|
|
|
return attribute;
|
|
}
|
|
|
|
static QByteArray parseAttributes (const QXmlStreamReader &reader, const Colors &colors) {
|
|
QXmlStreamAttributes readerAttributes = reader.attributes();
|
|
|
|
QByteArray attributes = ::parseFillAndStroke(readerAttributes, colors);
|
|
attributes.append(::parseStyle(readerAttributes, colors));
|
|
|
|
for (const auto &attribute : readerAttributes) {
|
|
const QByteArray prefix = attribute.prefix().toLatin1();
|
|
if (Q_UNLIKELY(prefix.length() > 0)) {
|
|
attributes.append(prefix);
|
|
attributes.append(":");
|
|
}
|
|
|
|
attributes.append(
|
|
::buildByteArrayAttribute(attribute.name().toLatin1(), attribute.value().toLatin1())
|
|
);
|
|
}
|
|
|
|
return attributes;
|
|
}
|
|
|
|
static QByteArray parseDeclarations (const QXmlStreamReader &reader) {
|
|
QByteArray declarations;
|
|
for (const auto &declaration : reader.namespaceDeclarations()) {
|
|
const QByteArray prefix = declaration.prefix().toLatin1();
|
|
if (Q_UNLIKELY(prefix.length() > 0)) {
|
|
declarations.append("xmlns:");
|
|
declarations.append(prefix);
|
|
} else
|
|
declarations.append("xmlns");
|
|
|
|
declarations.append("=\"");
|
|
declarations.append(declaration.namespaceUri().toLatin1());
|
|
declarations.append("\" ");
|
|
}
|
|
|
|
return declarations;
|
|
}
|
|
|
|
static QByteArray parseStartDocument (const QXmlStreamReader &reader) {
|
|
QByteArray startDocument = "<?xml version=\"";
|
|
startDocument.append(reader.documentVersion().toLatin1());
|
|
startDocument.append("\" encoding=\"");
|
|
startDocument.append(reader.documentEncoding().toLatin1());
|
|
startDocument.append("\"?>");
|
|
return startDocument;
|
|
}
|
|
|
|
static QByteArray parseStartElement (const QXmlStreamReader &reader, const Colors &colors) {
|
|
QByteArray startElement = "<";
|
|
startElement.append(reader.name().toLatin1());
|
|
startElement.append(" ");
|
|
startElement.append(::parseAttributes(reader, colors));
|
|
startElement.append(" ");
|
|
startElement.append(::parseDeclarations(reader));
|
|
startElement.append(">");
|
|
return startElement;
|
|
}
|
|
|
|
static QByteArray parseEndElement (const QXmlStreamReader &reader) {
|
|
QByteArray endElement = "</";
|
|
endElement.append(reader.name().toLatin1());
|
|
endElement.append(">");
|
|
return endElement;
|
|
}
|
|
|
|
// -----------------------------------------------------------------------------
|
|
|
|
static QByteArray computeContent (QFile &file) {
|
|
const Colors *colors = App::getInstance()->getColors();
|
|
|
|
QByteArray content;
|
|
QXmlStreamReader reader(&file);
|
|
while (!reader.atEnd())
|
|
switch (reader.readNext()) {
|
|
case QXmlStreamReader::Comment:
|
|
case QXmlStreamReader::DTD:
|
|
case QXmlStreamReader::EndDocument:
|
|
case QXmlStreamReader::Invalid:
|
|
case QXmlStreamReader::NoToken:
|
|
case QXmlStreamReader::ProcessingInstruction:
|
|
break;
|
|
|
|
case QXmlStreamReader::StartDocument:
|
|
content.append(::parseStartDocument(reader));
|
|
break;
|
|
|
|
case QXmlStreamReader::StartElement:
|
|
content.append(::parseStartElement(reader, *colors));
|
|
break;
|
|
|
|
case QXmlStreamReader::EndElement:
|
|
content.append(::parseEndElement(reader));
|
|
break;
|
|
|
|
case QXmlStreamReader::Characters:
|
|
content.append(reader.text().toLatin1());
|
|
break;
|
|
|
|
case QXmlStreamReader::EntityReference:
|
|
content.append(reader.name().toLatin1());
|
|
break;
|
|
}
|
|
|
|
return reader.hasError() ? QByteArray() : content;
|
|
}
|
|
|
|
// -----------------------------------------------------------------------------
|
|
|
|
const QString ImageProvider::PROVIDER_ID = "internal";
|
|
|
|
ImageProvider::ImageProvider () : QQuickImageProvider(
|
|
QQmlImageProviderBase::Image,
|
|
QQmlImageProviderBase::ForceAsynchronousImageLoading
|
|
) {}
|
|
|
|
// -----------------------------------------------------------------------------
|
|
|
|
QImage ImageProvider::requestImage (const QString &id, QSize *size, const QSize &requestedSize) {
|
|
const QString path = QStringLiteral(":/assets/images/%1").arg(id);
|
|
qInfo() << QStringLiteral("Image `%1` requested.").arg(path);
|
|
|
|
QElapsedTimer timer;
|
|
timer.start();
|
|
|
|
// 1. Read and update XML content.
|
|
QFile file(path);
|
|
if (Q_UNLIKELY(QFileInfo(file).size() > cMaxImageSize)) {
|
|
qWarning() << QStringLiteral("Unable to open large file: `%1`.").arg(path);
|
|
return QImage();
|
|
}
|
|
|
|
if (Q_UNLIKELY(!file.open(QIODevice::ReadOnly))) {
|
|
qWarning() << QStringLiteral("Unable to open file: `%1`.").arg(path);
|
|
return QImage();
|
|
}
|
|
|
|
const QByteArray content = ::computeContent(file);
|
|
if (Q_UNLIKELY(!content.length())) {
|
|
qWarning() << QStringLiteral("Unable to parse file: `%1`.").arg(path);
|
|
return QImage();
|
|
}
|
|
|
|
// 2. Build svg renderer.
|
|
QSvgRenderer renderer(content);
|
|
if (Q_UNLIKELY(!renderer.isValid())) {
|
|
qWarning() << QStringLiteral("Invalid svg file: `%1`.").arg(path);
|
|
return QImage();
|
|
}
|
|
|
|
// 3. Create en empty image.
|
|
const QRectF viewBox = renderer.viewBoxF();
|
|
const int width = requestedSize.width();
|
|
const int height = requestedSize.height();
|
|
QImage image(
|
|
width > 0 ? width : int(viewBox.width()),
|
|
height > 0 ? height : int(viewBox.height()),
|
|
QImage::Format_ARGB32
|
|
);
|
|
if (Q_UNLIKELY(image.isNull())) {
|
|
qWarning() << QStringLiteral("Unable to create image of size `(%1, %2)` from path: `%3`.")
|
|
.arg(viewBox.width()).arg(viewBox.height()).arg(path);
|
|
return QImage(); // Memory cannot be allocated.
|
|
}
|
|
image.fill(0x00000000);
|
|
|
|
*size = image.size();
|
|
|
|
// 4. Paint!
|
|
QPainter painter(&image);
|
|
renderer.render(&painter);
|
|
|
|
qInfo() << QStringLiteral("Image `%1` loaded in %2 milliseconds.").arg(path).arg(timer.elapsed());
|
|
|
|
return image;
|
|
}
|