File indexing completed on 2024-05-12 05:09:49

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   reader.setAutoTransform(true);
0065   reader.setFileName(filename_);
0066   m_format = reader.format();
0067   if(!reader.read(this)) {
0068     // Tellico had an earlier bug where images were written in PNG format with a GIF extension
0069     // and for some reason, qt doesn't recognize the file then, so fall back and try to load as PNG
0070     reader.setFormat("PNG");
0071     if(reader.read(this)) {
0072       myWarning() << filename_ << "loaded as PNG image";
0073       m_format = "PNG";
0074     }
0075   }
0076   if(m_id.isEmpty()) {
0077     calculateID();
0078   }
0079 }
0080 
0081 Image::Image(const QImage& img_, const QString& format_) : QImage(img_), m_format(format_.toLatin1()), m_linkOnly(false) {
0082   calculateID();
0083 }
0084 
0085 Image::Image(const QByteArray& data_, const QString& format_, const QString& id_)
0086     : QImage(QImage::fromData(data_)), m_id(idClean(id_)), m_format(format_.toLatin1()), m_linkOnly(false) {
0087   if(isNull()) {
0088     m_id.clear();
0089   } else if(m_id.isEmpty()) {
0090     calculateID();
0091   }
0092 }
0093 
0094 Image::~Image() {
0095 }
0096 
0097 QByteArray Image::byteArray() const {
0098   return byteArray(*this, outputFormat(m_format));
0099 }
0100 
0101 bool Image::isNull() const {
0102   // 1x1 images are considered null for Tellico. Amazon returns some like that.
0103   return QImage::isNull() || (width() < 2 && height() < 2);
0104 }
0105 
0106 QPixmap Image::convertToPixmap() const {
0107   return QPixmap::fromImage(*this);
0108 }
0109 
0110 QPixmap Image::convertToPixmap(int w_, int h_) const {
0111   if(w_ < width() || h_ < height()) {
0112     return QPixmap::fromImage(*this).scaled(w_, h_, Qt::KeepAspectRatio);
0113   } else {
0114     return QPixmap::fromImage(*this);
0115   }
0116 }
0117 
0118 QByteArray Image::outputFormat(const QByteArray& inputFormat) {
0119   if(s_outputFormats.isEmpty()) {
0120     QList<QByteArray> list = QImageWriter::supportedImageFormats();
0121     foreach(const QByteArray& format, list) {
0122       s_outputFormats.append(format.toUpper());
0123     }
0124   }
0125   if(s_outputFormats.contains(inputFormat.toUpper())) {
0126     return inputFormat;
0127   }
0128   myWarning() << "writing" << inputFormat << "as PNG";
0129   return "PNG";
0130 }
0131 
0132 QByteArray Image::byteArray(const QImage& img_, const QByteArray& outputFormat_) {
0133   QByteArray ba;
0134   QBuffer buf(&ba);
0135   buf.open(QIODevice::WriteOnly);
0136   QImageWriter writer(&buf, outputFormat_);
0137   if(!writer.write(img_)) {
0138     myDebug() << writer.errorString();
0139   }
0140   buf.close();
0141   return ba;
0142 }
0143 
0144 QString Image::idClean(const QString& id_) {
0145   static const QRegularExpression rx(QLatin1Char('[') + QRegularExpression::escape(QLatin1String("/@<>#\"&%?={}|^~[]'`\\:+")) + QLatin1Char(']'));
0146   QString clean = id_;
0147   return Tellico::shareString(clean.remove(rx));
0148 }
0149 
0150 void Image::setID(const QString& id_) {
0151   // don't clean the id if we're linking only
0152   m_id = m_linkOnly ? id_ : idClean(id_);
0153 }
0154 
0155 void Image::calculateID() {
0156   // the id will eventually be used as a filename
0157   if(!isNull()) {
0158     m_id = calculateID(byteArray(), QLatin1String(m_format));
0159   }
0160 }
0161 
0162 QString Image::calculateID(const QByteArray& data_, const QString& format_) {
0163   QCryptographicHash md5(QCryptographicHash::Md5);
0164   md5.addData(data_);
0165   QString id = QLatin1String(md5.result().toHex()) + QLatin1Char('.') + format_.toLower();
0166   return idClean(id);
0167 }