File indexing completed on 2024-05-12 16:46:08

0001 /***************************************************************************
0002     Copyright (C) 2003-2009 Robby Stephenson <robby@periapsis.org>
0003  ***************************************************************************/
0004 
0005 /***************************************************************************
0006  *                                                                         *
0007  *   This program is free software; you can redistribute it and/or         *
0008  *   modify it under the terms of the GNU General Public License as        *
0009  *   published by the Free Software Foundation; either version 2 of        *
0010  *   the License or (at your option) version 3 or any later version        *
0011  *   accepted by the membership of KDE e.V. (or its successor approved     *
0012  *   by the membership of KDE e.V.), which shall act as a proxy            *
0013  *   defined in Section 14 of version 3 of the license.                    *
0014  *                                                                         *
0015  *   This program is distributed in the hope that it will be useful,       *
0016  *   but WITHOUT ANY WARRANTY; without even the implied warranty of        *
0017  *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the         *
0018  *   GNU General Public License for more details.                          *
0019  *                                                                         *
0020  *   You should have received a copy of the GNU General Public License     *
0021  *   along with this program.  If not, see <http://www.gnu.org/licenses/>. *
0022  *                                                                         *
0023  ***************************************************************************/
0024 
0025 #include "image.h"
0026 #include "../utils/string_utils.h"
0027 #include "../tellico_debug.h"
0028 
0029 #include <QBuffer>
0030 #include <QRegularExpression>
0031 #include <QImageReader>
0032 #include <QImageWriter>
0033 #include <QCryptographicHash>
0034 
0035 using Tellico::Data::Image;
0036 
0037 const Image Image::null;
0038 QList<QByteArray> Image::s_outputFormats;
0039 
0040 Image::Image() : QImage(), m_linkOnly(false) {
0041 }
0042 
0043 Image::Image(const Image& other) : QImage(other)
0044   , m_id(other.m_id)
0045   , m_format(other.m_format)
0046   , m_linkOnly(other.m_linkOnly) {
0047 }
0048 
0049 Image& Image::operator=(const Image& other) {
0050   QImage::operator=(other);
0051   if(this != &other) {
0052     m_id = other.m_id;
0053     m_format = other.m_format;
0054     m_linkOnly = other.m_linkOnly;
0055   }
0056   return *this;
0057 }
0058 
0059 // I'm using the MD5 hash as the id. I consider it rather unlikely that two images in one
0060 // collection could ever have the same hash, and this lets me do a fast comparison of two images
0061 // simply by comparing their ids.
0062 Image::Image(const QString& filename_, const QString& id_) : QImage(), m_id(idClean(id_)), m_linkOnly(false) {
0063   QImageReader reader;
0064 #if (QT_VERSION >= QT_VERSION_CHECK(5, 5, 0))
0065   reader.setAutoTransform(true);
0066 #endif
0067   reader.setFileName(filename_);
0068   m_format = reader.format();
0069   if(!reader.read(this)) {
0070     // Tellico had an earlier bug where images were written in PNG format with a GIF extension
0071     // and for some reason, qt doesn't recognize the file then, so fall back and try to load as PNG
0072     reader.setFormat("PNG");
0073     if(reader.read(this)) {
0074       myWarning() << filename_ << "loaded as PNG image";
0075       m_format = "PNG";
0076     }
0077   }
0078   if(m_id.isEmpty()) {
0079     calculateID();
0080   }
0081 }
0082 
0083 Image::Image(const QImage& img_, const QString& format_) : QImage(img_), m_format(format_.toLatin1()), m_linkOnly(false) {
0084   calculateID();
0085 }
0086 
0087 Image::Image(const QByteArray& data_, const QString& format_, const QString& id_)
0088     : QImage(QImage::fromData(data_)), m_id(idClean(id_)), m_format(format_.toLatin1()), m_linkOnly(false) {
0089   if(isNull()) {
0090     m_id.clear();
0091   }
0092 }
0093 
0094 Image::~Image() {
0095 }
0096 
0097 QByteArray Image::byteArray() const {
0098   return byteArray(*this, outputFormat(m_format));
0099 }
0100 
0101 // TODO: once the min qt version is raised to 5.10, this can be removed
0102 qsizetype Image::byteSize() const {
0103 #if (QT_VERSION < QT_VERSION_CHECK(5, 10, 0))
0104   return byteCount();
0105 #else
0106   return sizeInBytes();
0107 #endif
0108 }
0109 
0110 bool Image::isNull() const {
0111   // 1x1 images are considered null for Tellico. Amazon returns some like that.
0112   return QImage::isNull() || (width() < 2 && height() < 2);
0113 }
0114 
0115 QPixmap Image::convertToPixmap() const {
0116   return QPixmap::fromImage(*this);
0117 }
0118 
0119 QPixmap Image::convertToPixmap(int w_, int h_) const {
0120   if(w_ < width() || h_ < height()) {
0121     return QPixmap::fromImage(*this).scaled(w_, h_, Qt::KeepAspectRatio);
0122   } else {
0123     return QPixmap::fromImage(*this);
0124   }
0125 }
0126 
0127 QByteArray Image::outputFormat(const QByteArray& inputFormat) {
0128   if(s_outputFormats.isEmpty()) {
0129     QList<QByteArray> list = QImageWriter::supportedImageFormats();
0130     foreach(const QByteArray& format, list) {
0131       s_outputFormats.append(format.toUpper());
0132     }
0133   }
0134   if(s_outputFormats.contains(inputFormat.toUpper())) {
0135     return inputFormat;
0136   }
0137   myWarning() << "writing" << inputFormat << "as PNG";
0138   return "PNG";
0139 }
0140 
0141 QByteArray Image::byteArray(const QImage& img_, const QByteArray& outputFormat_) {
0142   QByteArray ba;
0143   QBuffer buf(&ba);
0144   buf.open(QIODevice::WriteOnly);
0145   QImageWriter writer(&buf, outputFormat_);
0146   if(!writer.write(img_)) {
0147     myDebug() << writer.errorString();
0148   }
0149   buf.close();
0150   return ba;
0151 }
0152 
0153 QString Image::idClean(const QString& id_) {
0154   static const QRegularExpression rx(QLatin1Char('[') + QRegularExpression::escape(QLatin1String("/@<>#\"&%?={}|^~[]'`\\:+")) + QLatin1Char(']'));
0155   QString clean = id_;
0156   return Tellico::shareString(clean.remove(rx));
0157 }
0158 
0159 void Image::setID(const QString& id_) {
0160   // don't clean the id if we're linking only
0161   m_id = m_linkOnly ? id_ : idClean(id_);
0162 }
0163 
0164 void Image::calculateID() {
0165   // the id will eventually be used as a filename
0166   if(!isNull()) {
0167     m_id = calculateID(byteArray(), QLatin1String(m_format));
0168   }
0169 }
0170 
0171 QString Image::calculateID(const QByteArray& data_, const QString& format_) {
0172   QCryptographicHash md5(QCryptographicHash::Md5);
0173   md5.addData(data_);
0174   QString id = QLatin1String(md5.result().toHex()) + QLatin1Char('.') + format_.toLower();
0175   return idClean(id);
0176 }