File indexing completed on 2025-01-12 12:39:34
0001 /************************************************************************ 0002 * * 0003 * This file is part of Kooka, a scanning/OCR application using * 0004 * Qt <http://www.qt.io> and KDE Frameworks <http://www.kde.org>. * 0005 * * 0006 * Copyright (C) 2009-2016 Jonathan Marten <jjm@keelhaul.me.uk> * 0007 * * 0008 * Kooka is free software; you can redistribute it and/or modify it * 0009 * under the terms of the GNU Library General Public License as * 0010 * published by the Free Software Foundation and appearing in the * 0011 * file COPYING included in the packaging of this file; either * 0012 * version 2 of the License, or (at your option) any later version. * 0013 * * 0014 * As a special exception, permission is given to link this program * 0015 * with any version of the KADMOS OCR/ICR engine (a product of * 0016 * reRecognition GmbH, Kreuzlingen), and distribute the resulting * 0017 * executable without including the source code for KADMOS in the * 0018 * source distribution. * 0019 * * 0020 * This program is distributed in the hope that it will be useful, * 0021 * but WITHOUT ANY WARRANTY; without even the implied warranty of * 0022 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * 0023 * GNU General Public License for more details. * 0024 * * 0025 * You should have received a copy of the GNU General Public * 0026 * License along with this program; see the file COPYING. If * 0027 * not, see <http://www.gnu.org/licenses/>. * 0028 * * 0029 ************************************************************************/ 0030 0031 #include "imageformat.h" 0032 0033 #include <qmimetype.h> 0034 #include <qmimedatabase.h> 0035 #include <qimagereader.h> 0036 #include <qimagewriter.h> 0037 0038 #include "libkookascan_logging.h" 0039 0040 0041 // This class provides two facilities: 0042 // 0043 // (a) The opaque ImageFormat data type, which is used throughout to represent 0044 // a Qt image format name. It is a separate type from standard strings 0045 // so that it does not get confused with a MIME type (always passed 0046 // around as a QMimeType) or a file extension (always handled as a QString). 0047 // 0048 // (b) Its static functions provide some useful lookups not available from 0049 // the QImage* classes. 0050 // 0051 // In KDE Frameworks this class no longer uses KImageIO or any KDE service 0052 // files, although it does make an assumption about the internal operation 0053 // of QImage* - i.e. that a Qt format name is actually a file extension. 0054 0055 0056 ImageFormat::ImageFormat(const QByteArray &format) 0057 { 0058 mFormat = format.toUpper(); 0059 } 0060 0061 bool ImageFormat::isValid() const 0062 { 0063 return (!mFormat.isEmpty()); 0064 } 0065 0066 const char *ImageFormat::name() const 0067 { 0068 return (mFormat.constData()); 0069 } 0070 0071 ImageFormat::ImageFormat() 0072 { 0073 mFormat.clear(); 0074 } 0075 0076 bool ImageFormat::operator==(const ImageFormat &other) 0077 { 0078 return (mFormat == other.mFormat); 0079 } 0080 0081 KOOKASCAN_EXPORT QDebug operator<<(QDebug stream, const ImageFormat &format) 0082 { 0083 stream.nospace() << "ImageFormat[" << format.name() << "]"; 0084 return (stream.space()); 0085 } 0086 0087 0088 QMimeType ImageFormat::mime() const 0089 { 0090 // As noted previously and in FormatDialog::FormatDialog(), a Qt image 0091 // format name is really a file extension. Therefore we can do this 0092 // lookup using a file name with that extension. 0093 QMimeDatabase db; 0094 QMimeType mime = db.mimeTypeForFile(QString("a.")+mFormat, QMimeDatabase::MatchExtension); 0095 if (!mime.isValid() || mime.isDefault()) 0096 { 0097 qCDebug(LIBKOOKASCAN_LOG) << "no MIME type for image format" << *this; 0098 } 0099 else 0100 { 0101 //qCDebug(LIBKOOKASCAN_LOG) << "for" << *this << "returning" << mime.name(); 0102 } 0103 0104 return (mime); 0105 } 0106 0107 0108 QString ImageFormat::extension() const 0109 { 0110 QString suf = mFormat.toLower(); 0111 QMimeType mp = mime(); 0112 if (mp.isValid()) { 0113 QString ext = mp.preferredSuffix(); 0114 if (!ext.isEmpty()) suf = ext; 0115 } 0116 0117 if (suf.startsWith('.')) suf = suf.mid(1); 0118 //qCDebug(LIBKOOKASCAN_LOG) << "for" << *this << "returning" << suf; 0119 return (suf); 0120 } 0121 0122 0123 bool ImageFormat::canWrite() const 0124 { 0125 return (QImageWriter::supportedImageFormats().contains(mFormat.toLower())); 0126 } 0127 0128 0129 ImageFormat ImageFormat::formatForMime(const QMimeType &mime) 0130 { 0131 if (!mime.isValid()) return (ImageFormat()); 0132 0133 #if QT_VERSION >= QT_VERSION_CHECK(5, 12, 0) 0134 // This information can now be obtained directly. The first format 0135 // in the list always appears to be the image type corresponding to 0136 // the MIME type's preferred suffix. 0137 const QList<QByteArray> fmts = QImageReader::imageFormatsForMimeType(mime.name().toLocal8Bit()); 0138 if (!fmts.isEmpty()) return (ImageFormat(fmts.first())); 0139 #else 0140 // Before Qt 5.12 there was no official way to perform this operation; 0141 // that is, to find the Qt image format name corresponding to a MIME type. 0142 // So this does so by assuming that the format name is the first file 0143 // extension ("suffix") registered for that MIME type which is a 0144 // supported Qt image type. 0145 const QStringList sufs = mime.suffixes(); 0146 if (sufs.isEmpty()) return (ImageFormat()); 0147 0148 const QList<QByteArray> formats = QImageReader::supportedImageFormats(); 0149 for (const QString &suf : sufs) // find the first matching one 0150 { 0151 const QByteArray s = suf.toLocal8Bit(); 0152 if (formats.contains(s.toLower())) return (ImageFormat(s)); 0153 } 0154 #endif 0155 0156 #ifdef HAVE_TIFF 0157 // Qt 5: This can happen if only QtGui is installed, because TIFF support is 0158 // now provided in QtImageFormats. However, if we are built with TIFF library 0159 // support (for reading multi-page TIFF files) then TIFF reading is also available 0160 // via that library even if Qt does not support it. 0161 // 0162 // The special format name used here is checked by isTiff() below and used 0163 // in ScanImage::loadFromUrl() to force loading via the TIFF library. 0164 // 0165 // TIFF write support will still not be available unless QtImageFormats is 0166 // installed. 0167 if (mime.inherits("image/tiff")) return (ImageFormat("TIFFLIB")); 0168 #endif 0169 return (ImageFormat()); // nothing matched 0170 } 0171 0172 0173 bool ImageFormat::isTiff() const 0174 { 0175 if (mFormat=="TIF" || mFormat=="TIFF") return (true); 0176 #ifdef HAVE_TIFF 0177 if (mFormat=="TIFFLIB") return (true); 0178 #endif 0179 return (false); 0180 } 0181 0182 0183 ImageFormat ImageFormat::formatForUrl(const QUrl &url) 0184 { 0185 QMimeDatabase db; 0186 QMimeType mime = db.mimeTypeForUrl(url); 0187 return (formatForMime(mime)); 0188 } 0189 0190 0191 static const QList<QMimeType> *sMimeList = nullptr; 0192 0193 0194 void buildMimeTypeList() 0195 { 0196 QList<QMimeType> *list = new QList<QMimeType>; 0197 0198 // Each of the lists that we get from Qt (supported image formats and 0199 // supported MIME types) is sorted alphabetically. That means that 0200 // there is no correlation between the two lists. The formats list 0201 // may also contain duplicates, e.g. "jpeg" and "jpg" both correspond 0202 // to MIME type image/jpeg. 0203 // 0204 // However, it appears that the Qt image format is actually a file 0205 // name extension. That means that we can obtain the MIME type by 0206 // finding the type for a file with that extension. 0207 0208 const QList<QByteArray> supportedFormats = QImageWriter::supportedImageFormats(); 0209 qCDebug(LIBKOOKASCAN_LOG) << "have" << supportedFormats.count() << "image formats:" << supportedFormats; 0210 0211 // Although the format list from standard Qt 5 (unlike Qt 4) does not 0212 // appear to contain any duplicates or mixed case, it is always possible 0213 // that a plugin could introduce some. So the apparently pointless loop 0214 // here filters the list. 0215 QList<QByteArray> formatList; 0216 for (const QByteArray &format : supportedFormats) 0217 { 0218 const QByteArray f = format.toLower(); 0219 if (!formatList.contains(f)) formatList.append(f); 0220 } 0221 qCDebug(LIBKOOKASCAN_LOG) << "have" << formatList.count() << "image types" 0222 << "from" << supportedFormats.count() << "supported"; 0223 0224 // Even after filtering the list as above, there will be MIME type 0225 // duplicates (e.g. JPG and JPEG both map to image/jpeg and produce 0226 // the same results). So the list is filtered again to eliminate 0227 // duplicate MIME types. 0228 // 0229 // As a side effect, this completely eliminates any formats that do 0230 // not have a defined MIME type. None of those affected (currently 0231 // BW, RGBA, XV) seem to be of any use. 0232 0233 QMimeDatabase db; 0234 for (const QByteArray &format : qAsConst(formatList)) 0235 { 0236 QMimeType mime = db.mimeTypeForFile(QString("a.")+format, QMimeDatabase::MatchExtension); 0237 if (!mime.isValid() || mime.isDefault()) 0238 { 0239 qCWarning(LIBKOOKASCAN_LOG) << "No MIME type for format" << format; 0240 continue; 0241 } 0242 0243 // ImageFormat::formatForMime() should now work even in the presence 0244 // of MIME aliases, but double check that it works at this stage. 0245 ImageFormat fmt = ImageFormat::formatForMime(mime); 0246 if (!fmt.isValid()) 0247 { 0248 qCWarning(LIBKOOKASCAN_LOG) << "MIME type" << mime.name() << "does not map back to format" << format; 0249 continue; 0250 } 0251 0252 bool seen = false; 0253 for (const QMimeType &mt : qAsConst(*list)) 0254 { 0255 if (mime.inherits(mt.name())) 0256 { 0257 seen = true; 0258 break; 0259 } 0260 } 0261 0262 if (!seen) list->append(mime); 0263 } 0264 0265 if (list->isEmpty()) qCWarning(LIBKOOKASCAN_LOG) << "No MIME types available!"; 0266 else qCDebug(LIBKOOKASCAN_LOG) << "have" << list->count() << "unique MIME types"; 0267 sMimeList = list; 0268 } 0269 0270 0271 const QList<QMimeType> *ImageFormat::mimeTypes() 0272 { 0273 if (sMimeList==nullptr) buildMimeTypeList(); 0274 return (sMimeList); 0275 }