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 }