File indexing completed on 2024-04-21 15:12:10

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 }