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