1 /***************************************************************************
2 Copyright (C) 2003-2009 Robby Stephenson <robby@periapsis.org>
3 ***************************************************************************/
4
5 /***************************************************************************
6 * *
7 * This program is free software; you can redistribute it and/or *
8 * modify it under the terms of the GNU General Public License as *
9 * published by the Free Software Foundation; either version 2 of *
10 * the License or (at your option) version 3 or any later version *
11 * accepted by the membership of KDE e.V. (or its successor approved *
12 * by the membership of KDE e.V.), which shall act as a proxy *
13 * defined in Section 14 of version 3 of the license. *
14 * *
15 * This program is distributed in the hope that it will be useful, *
16 * but WITHOUT ANY WARRANTY; without even the implied warranty of *
17 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
18 * GNU General Public License for more details. *
19 * *
20 * You should have received a copy of the GNU General Public License *
21 * along with this program. If not, see <http://www.gnu.org/licenses/>. *
22 * *
23 ***************************************************************************/
24
25 #include "image.h"
26 #include "../utils/string_utils.h"
27 #include "../tellico_debug.h"
28
29 #include <QBuffer>
30 #include <QRegularExpression>
31 #include <QImageReader>
32 #include <QImageWriter>
33 #include <QCryptographicHash>
34
35 using Tellico::Data::Image;
36
37 const Image Image::null;
38 QList<QByteArray> Image::s_outputFormats;
39
Image()40 Image::Image() : QImage(), m_linkOnly(false) {
41 }
42
Image(const Image & other)43 Image::Image(const Image& other) : QImage(other)
44 , m_id(other.m_id)
45 , m_format(other.m_format)
46 , m_linkOnly(other.m_linkOnly) {
47 }
48
operator =(const Image & other)49 Image& Image::operator=(const Image& other) {
50 QImage::operator=(other);
51 if(this != &other) {
52 m_id = other.m_id;
53 m_format = other.m_format;
54 m_linkOnly = other.m_linkOnly;
55 }
56 return *this;
57 }
58
59 // I'm using the MD5 hash as the id. I consider it rather unlikely that two images in one
60 // collection could ever have the same hash, and this lets me do a fast comparison of two images
61 // simply by comparing their ids.
Image(const QString & filename_,const QString & id_)62 Image::Image(const QString& filename_, const QString& id_) : QImage(), m_id(idClean(id_)), m_linkOnly(false) {
63 QImageReader reader;
64 #if (QT_VERSION >= QT_VERSION_CHECK(5, 5, 0))
65 reader.setAutoTransform(true);
66 #endif
67 reader.setFileName(filename_);
68 m_format = reader.format();
69 if(!reader.read(this)) {
70 // Tellico had an earlier bug where images were written in PNG format with a GIF extension
71 // and for some reason, qt doesn't recognize the file then, so fall back and try to load as PNG
72 reader.setFormat("PNG");
73 if(reader.read(this)) {
74 myWarning() << filename_ << "loaded as PNG image";
75 m_format = "PNG";
76 }
77 }
78 if(m_id.isEmpty()) {
79 calculateID();
80 }
81 }
82
Image(const QImage & img_,const QString & format_)83 Image::Image(const QImage& img_, const QString& format_) : QImage(img_), m_format(format_.toLatin1()), m_linkOnly(false) {
84 calculateID();
85 }
86
Image(const QByteArray & data_,const QString & format_,const QString & id_)87 Image::Image(const QByteArray& data_, const QString& format_, const QString& id_)
88 : QImage(QImage::fromData(data_)), m_id(idClean(id_)), m_format(format_.toLatin1()), m_linkOnly(false) {
89 if(isNull()) {
90 m_id.clear();
91 }
92 }
93
~Image()94 Image::~Image() {
95 }
96
byteArray() const97 QByteArray Image::byteArray() const {
98 return byteArray(*this, outputFormat(m_format));
99 }
100
101 // TODO: once the min qt version is raised to 5.10, this can be removed
byteSize() const102 qsizetype Image::byteSize() const {
103 #if (QT_VERSION < QT_VERSION_CHECK(5, 10, 0))
104 return byteCount();
105 #else
106 return sizeInBytes();
107 #endif
108 }
109
isNull() const110 bool Image::isNull() const {
111 // 1x1 images are considered null for Tellico. Amazon returns some like that.
112 return QImage::isNull() || (width() < 2 && height() < 2);
113 }
114
convertToPixmap() const115 QPixmap Image::convertToPixmap() const {
116 return QPixmap::fromImage(*this);
117 }
118
convertToPixmap(int w_,int h_) const119 QPixmap Image::convertToPixmap(int w_, int h_) const {
120 if(w_ < width() || h_ < height()) {
121 return QPixmap::fromImage(*this).scaled(w_, h_, Qt::KeepAspectRatio);
122 } else {
123 return QPixmap::fromImage(*this);
124 }
125 }
126
outputFormat(const QByteArray & inputFormat)127 QByteArray Image::outputFormat(const QByteArray& inputFormat) {
128 if(s_outputFormats.isEmpty()) {
129 QList<QByteArray> list = QImageWriter::supportedImageFormats();
130 foreach(const QByteArray& format, list) {
131 s_outputFormats.append(format.toUpper());
132 }
133 }
134 if(s_outputFormats.contains(inputFormat.toUpper())) {
135 return inputFormat;
136 }
137 myWarning() << "writing" << inputFormat << "as PNG";
138 return "PNG";
139 }
140
byteArray(const QImage & img_,const QByteArray & outputFormat_)141 QByteArray Image::byteArray(const QImage& img_, const QByteArray& outputFormat_) {
142 QByteArray ba;
143 QBuffer buf(&ba);
144 buf.open(QIODevice::WriteOnly);
145 QImageWriter writer(&buf, outputFormat_);
146 if(!writer.write(img_)) {
147 myDebug() << writer.errorString();
148 }
149 buf.close();
150 return ba;
151 }
152
idClean(const QString & id_)153 QString Image::idClean(const QString& id_) {
154 static const QRegularExpression rx(QLatin1Char('[') + QRegularExpression::escape(QLatin1String("/@<>#\"&%?={}|^~[]'`\\:+")) + QLatin1Char(']'));
155 QString clean = id_;
156 return Tellico::shareString(clean.remove(rx));
157 }
158
setID(const QString & id_)159 void Image::setID(const QString& id_) {
160 // don't clean the id if we're linking only
161 m_id = m_linkOnly ? id_ : idClean(id_);
162 }
163
calculateID()164 void Image::calculateID() {
165 // the id will eventually be used as a filename
166 if(!isNull()) {
167 m_id = calculateID(byteArray(), QLatin1String(m_format));
168 }
169 }
170
calculateID(const QByteArray & data_,const QString & format_)171 QString Image::calculateID(const QByteArray& data_, const QString& format_) {
172 QCryptographicHash md5(QCryptographicHash::Md5);
173 md5.addData(data_);
174 QString id = QLatin1String(md5.result().toHex()) + QLatin1Char('.') + format_.toLower();
175 return idClean(id);
176 }
177