linphone-desktop/src/app/providers/ImageProvider.cpp

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;
}