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

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) 1999-2016 Klaas Freitag <freitag@suse.de>     *
0007  *                          Jonathan Marten <jjm@keelhaul.me.uk>    *
0008  *                                  *
0009  *  Kooka is free software; you can redistribute it and/or modify it    *
0010  *  under the terms of the GNU Library General Public License as    *
0011  *  published by the Free Software Foundation and appearing in the  *
0012  *  file COPYING included in the packaging of this file;  either    *
0013  *  version 2 of the License, or (at your option) any later version.    *
0014  *                                  *
0015  *  As a special exception, permission is given to link this program    *
0016  *  with any version of the KADMOS OCR/ICR engine (a product of     *
0017  *  reRecognition GmbH, Kreuzlingen), and distribute the resulting  *
0018  *  executable without including the source code for KADMOS in the  *
0019  *  source distribution.                        *
0020  *                                  *
0021  *  This program is distributed in the hope that it will be useful, *
0022  *  but WITHOUT ANY WARRANTY; without even the implied warranty of  *
0023  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the   *
0024  *  GNU General Public License for more details.            *
0025  *                                  *
0026  *  You should have received a copy of the GNU General Public       *
0027  *  License along with this program;  see the file COPYING.  If     *
0028  *  not, see <http://www.gnu.org/licenses/>.                *
0029  *                                  *
0030  ************************************************************************/
0031 
0032 #include "scanimage.h"
0033 
0034 #include <stdint.h>
0035 
0036 #include <qfile.h>
0037 
0038 #include <klocalizedstring.h>
0039 
0040 #include "imageformat.h"
0041 #include "libkookascan_logging.h"
0042 
0043 #ifdef HAVE_TIFF
0044 extern "C"
0045 {
0046 #include <tiffio.h>
0047 #include <tiff.h>
0048 }
0049 #endif
0050 
0051 
0052 ScanImage::ScanImage(int width, int height, QImage::Format format)
0053     : QImage(width, height, format)
0054 {
0055     init();
0056 }
0057 
0058 
0059 ScanImage::ScanImage(const QImage &img)
0060     : QImage(img)
0061 {
0062     init();
0063 }
0064 
0065 
0066 ScanImage::ScanImage(const QUrl &url)
0067     : QImage()
0068 {
0069     init();
0070 
0071     bool isTiff = false;                // do we have a TIFF file?
0072     bool haveTiff = false;              // can it be read via TIFF lib?
0073 
0074     if (!url.isLocalFile())
0075     {
0076         m_errorString = i18n("Loading non-local images is not yet implemented");
0077         return;
0078     }
0079 
0080     if (url.hasFragment())              // is this a subimage?
0081     {
0082         int subno = url.fragment().toInt();     // get subimage number
0083         if (subno>0)                    // valid number from fragment
0084         {                       // get local file without fragment
0085             const QString fileName = url.adjusted(QUrl::RemoveFragment).toLocalFile();
0086             qCDebug(LIBKOOKASCAN_LOG) << "subimage" << subno << "from" << fileName;
0087             m_errorString = loadTiffDir(fileName, subno);
0088             return;                 // load TIFF subimage
0089         }
0090     }
0091 
0092     const QString filename = url.toLocalFile();     // local path of image
0093     ImageFormat format = ImageFormat::formatForUrl(url);
0094 
0095     // If the file is TIFF and QImageReader supports TIFF files, 'format' as
0096     // returned by ImageFormat::formatForUrl() will be "TIF".  If TIFF files
0097     // are not supported but the MIME type of the file says that it is TIFF,
0098     // then 'format' will be "TIFFLIB".  So either of these format names means
0099     // that a TIFF file is being loaded.
0100     isTiff = format.isTiff();
0101     qCDebug(LIBKOOKASCAN_LOG) << "Loading image format" << format << "from" << filename;
0102 
0103 #ifdef HAVE_TIFF
0104     if (isTiff)                     // if it is TIFF, check
0105     {                           // for multiple images
0106         qCDebug(LIBKOOKASCAN_LOG) << "Checking for multi-page TIFF";
0107         TIFF *tif = TIFFOpen(QFile::encodeName(filename).constData(), "r");
0108         if (tif!=nullptr)
0109         {
0110             do
0111             {
0112                 ++m_subImages;
0113             } while (TIFFReadDirectory(tif));
0114             qCDebug(LIBKOOKASCAN_LOG) << "found" << m_subImages << "TIFF directories";
0115             if (m_subImages>1) haveTiff = true;
0116             // This format will have been specially detected
0117             // in ImageFormat::formatForMime(), if the file is TIFF
0118             // but QImageReader does not have TIFF support.
0119             // The TIFF file can still be read by loadTiffDir() below.
0120             else if (m_subImages==1 && format==ImageFormat("TIFFLIB")) haveTiff = true;
0121             TIFFClose(tif);
0122         }
0123     }
0124 #endif
0125     if (!haveTiff)                  // not TIFF, or not multi-page
0126     {
0127         QImage::load(filename);             // Qt can only read one image
0128         if (isNull()) m_errorString = i18n("Image loading failed");
0129         m_subImages = 0;
0130     }
0131 #ifdef HAVE_TIFF
0132     else                        // a TIFF file, multi-page or not
0133     {
0134         m_errorString = loadTiffDir(filename, 0);   // read by TIFFlib directly
0135     }
0136 #endif
0137 
0138     m_url = url;                    // record image source as from file
0139 }
0140 
0141 
0142 void ScanImage::init()
0143 {
0144     m_subImages = 0;                    // no subimages present
0145     m_errorString.clear();              // no error detected yet
0146     mImageType = ScanImage::None;           // image type not yet set
0147 }
0148 
0149 
0150 bool ScanImage::isFileBound() const
0151 {
0152     return (m_url.isValid());
0153 }
0154 
0155 
0156 QString ScanImage::loadTiffDir(const QString &filename, int subno)
0157 {
0158 #ifdef HAVE_TIFF
0159     // if it is TIFF, check with TIFFlib if it is multiple images
0160     qCDebug(LIBKOOKASCAN_LOG) << "Trying to load TIFF, subimage" << subno;
0161     TIFF *tif = TIFFOpen(QFile::encodeName(filename).constData(), "r");
0162     if (tif==nullptr) return (i18n("Failed to open TIFF file"));
0163 
0164     if (subno>0)                        // want a subimage
0165     {
0166         if (!TIFFSetDirectory(tif, subno-1))
0167         {
0168             TIFFClose(tif);
0169             return (i18n("Failed to read TIFF directory"));
0170         }
0171     }
0172 
0173     int imgWidth, imgHeight;
0174     TIFFGetField(tif, TIFFTAG_IMAGEWIDTH,  &imgWidth);
0175     TIFFGetField(tif, TIFFTAG_IMAGELENGTH, &imgHeight);
0176 
0177     // TODO: load bw-image correctly only 2 bit
0178     //
0179     // The Qt3 version of this did:
0180     //
0181     //   create( imgWidth, imgHeight, 32 );
0182     //
0183     // and then read the TIFF image into the image data as returned by bits().
0184     //
0185     // This is not possible on Qt4, where the size/depth of a QImage is set in
0186     // its constructor and cannot be subsequently changed.  Instead we read
0187     // the TIFF file into a temporary image and then use QImage::operator=
0188     // to shallow copy that temporary image into ours.  After that temporary
0189     // image has been destroyed, we have the only copy of the TIFF read image.
0190     //
0191     QImage tmpImg(imgWidth, imgHeight, QImage::Format_RGB32);
0192     uint32_t *data = (uint32_t *)(tmpImg.bits());
0193     if (TIFFReadRGBAImage(tif, imgWidth, imgHeight, data, 0)) {
0194                             // Successfully read, now convert
0195         tmpImg = tmpImg.rgbSwapped();           // swap red and blue
0196         tmpImg = tmpImg.mirrored();         // reverse (it's upside down)
0197     } else {
0198         TIFFClose(tif);
0199         return (i18n("Failed to read TIFF image"));
0200     }
0201 
0202     // Fetch the x- and y-resolutions to adjust images
0203     float xReso, yReso;
0204     bool resosFound = TIFFGetField(tif, TIFFTAG_XRESOLUTION, &xReso) &&
0205                       TIFFGetField(tif, TIFFTAG_YRESOLUTION, &yReso);
0206     //qCDebug(LIBKOOKASCAN_LOG) << "TIFF image: X-Res" << xReso << "Y-Res" << yReso;
0207 
0208     TIFFClose(tif);                 // finished with TIFF file
0209 
0210     // Check now if resolution in x- and y-directions differ.
0211     // If so, stretch the image accordingly.
0212     if (resosFound && xReso != yReso) {
0213         if (xReso > yReso) {
0214             float yScalefactor = xReso / yReso;
0215             //qCDebug(LIBKOOKASCAN_LOG) << "Different resolution X/Y, rescaling Y with factor" << yScalefactor;
0216             /* rescale the image */
0217             tmpImg = tmpImg.scaled(imgWidth, int(imgHeight * yScalefactor),
0218                                    Qt::IgnoreAspectRatio, Qt::SmoothTransformation);
0219         } else {
0220             float xScalefactor = yReso / xReso;
0221             //qCDebug(LIBKOOKASCAN_LOG) << "Different resolution X/Y, rescaling X with factor" << xScalefactor;
0222             tmpImg = tmpImg.scaled(int(imgWidth * xScalefactor), imgHeight,
0223                                    Qt::IgnoreAspectRatio, Qt::SmoothTransformation);
0224         }
0225     }
0226 
0227     QImage::operator=(tmpImg);              // copy as our image
0228 #else
0229     return (i18n("TIFF not supported"));
0230 #endif                          // HAVE_TIFF
0231     return (QString());                 // TIFF read succeeded
0232 }
0233 
0234 
0235 bool ScanImage::isSubImage() const
0236 {
0237     return (m_url.isValid() && m_url.fragment().toInt()>0);
0238 }
0239 
0240 
0241 void ScanImage::setScannerName(const QByteArray &scanner)
0242 {
0243     setText("ScannerName", QString::fromLocal8Bit(scanner));
0244 }
0245 
0246 
0247 QByteArray ScanImage::getScannerName() const
0248 {
0249     return (text("ScannerName").toLocal8Bit());
0250 }
0251 
0252 
0253 void ScanImage::setXResolution(int res)
0254 {
0255     setDotsPerMeterX(DPI_TO_DPM(res));
0256 }
0257 
0258 
0259 void ScanImage::setYResolution(int res)
0260 {
0261     setDotsPerMeterY(DPI_TO_DPM(res));
0262 }
0263 
0264 
0265 int ScanImage::getXResolution() const
0266 {
0267     return (DPM_TO_DPI(dotsPerMeterX()));
0268 }
0269 
0270 
0271 int ScanImage::getYResolution() const
0272 {
0273     return (DPM_TO_DPI(dotsPerMeterY()));
0274 }
0275 
0276 
0277 // This was originally ImageMetaInfo::findImageType() and then
0278 // ImgSaver::findImageType().  It is used to deduce the image
0279 // type, if it has not been possible to derive it from the
0280 // SANE parameters by KScanDevice.
0281 //
0282 // The logic is very similar to KScanDevice::getImageFormat(),
0283 // except that it looks at the actual result image instead of the
0284 // initial SANE parameters.  This means that the image type could
0285 // possibly be resolved as LowColour here.
0286 ScanImage::ImageType ScanImage::imageType() const
0287 {
0288     if (mImageType!=ScanImage::None) return (mImageType);   // image type as set
0289     if (isNull()) return (ScanImage::None);         // null image
0290 
0291     if (depth()==1 || colorCount()==2) return (ScanImage::BlackWhite);
0292                                 // black/white bitmap
0293     if (depth()>8) return (ScanImage::HighColour);      // high colour
0294     if (allGray()) return (ScanImage::Greyscale);       // grey scale
0295     return (ScanImage::LowColour);              // low (indexed) colour
0296 }