1 /************************************************************************
2  *									*
3  *  This file is part of Kooka, a scanning/OCR application using	*
4  *  Qt <http://www.qt.io> and KDE Frameworks <http://www.kde.org>.	*
5  *									*
6  *  Copyright (C) 1999-2016 Klaas Freitag <freitag@suse.de>		*
7  *                          Jonathan Marten <jjm@keelhaul.me.uk>	*
8  *									*
9  *  Kooka is free software; you can redistribute it and/or modify it	*
10  *  under the terms of the GNU Library General Public License as	*
11  *  published by the Free Software Foundation and appearing in the	*
12  *  file COPYING included in the packaging of this file;  either	*
13  *  version 2 of the License, or (at your option) any later version.	*
14  *									*
15  *  As a special exception, permission is given to link this program	*
16  *  with any version of the KADMOS OCR/ICR engine (a product of		*
17  *  reRecognition GmbH, Kreuzlingen), and distribute the resulting	*
18  *  executable without including the source code for KADMOS in the	*
19  *  source distribution.						*
20  *									*
21  *  This program is distributed in the hope that it will be useful,	*
22  *  but WITHOUT ANY WARRANTY; without even the implied warranty of	*
23  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the	*
24  *  GNU General Public License for more details.			*
25  *									*
26  *  You should have received a copy of the GNU General Public		*
27  *  License along with this program;  see the file COPYING.  If		*
28  *  not, see <http://www.gnu.org/licenses/>.				*
29  *									*
30  ************************************************************************/
31 
32 #include "kookaimage.h"
33 
34 #include <qdebug.h>
35 #include <qfile.h>
36 
37 #include <klocalizedstring.h>
38 
39 #include "imageformat.h"
40 
41 #ifdef HAVE_TIFF
42 extern "C"
43 {
44 #include <tiffio.h>
45 #include <tiff.h>
46 }
47 #endif
48 
49 
KookaImage()50 KookaImage::KookaImage()
51     : QImage()
52 {
53     init();
54 }
55 
56 
KookaImage(const QImage & img)57 KookaImage::KookaImage(const QImage &img)
58     : QImage(img)
59 {
60     init();
61     //qDebug() << "image" << img.size();
62 }
63 
64 
65 
init()66 void KookaImage::init()
67 {
68     m_subImages = 0;
69     m_fileItem = nullptr;
70     m_fileBound = false;
71 }
72 
73 
operator =(const KookaImage & img)74 KookaImage &KookaImage::operator=(const KookaImage &img)
75 {
76     if (this != &img) {             // ignore self-assignment
77         QImage::operator=(img);
78 
79         m_subImages = img.m_subImages;
80         m_url       = img.m_url;
81         m_fileItem  = img.m_fileItem;
82         m_fileBound = img.m_fileBound;
83     }
84 
85     return (*this);
86 }
87 
fileItem() const88 const KFileItem *KookaImage::fileItem() const
89 {
90     return (m_fileItem);
91 }
92 
setFileItem(const KFileItem * fi)93 void KookaImage::setFileItem(const KFileItem *fi)
94 {
95     m_fileItem = fi;
96 }
97 
setUrl(const QUrl & url)98 void KookaImage::setUrl(const QUrl &url)
99 {
100     m_url = url;
101 }
102 
url() const103 QUrl KookaImage::url() const
104 {
105     return m_url;
106 }
107 
isFileBound() const108 bool KookaImage::isFileBound() const
109 {
110     return m_fileBound;
111 }
112 
loadFromUrl(const QUrl & url)113 QString KookaImage::loadFromUrl(const QUrl &url)
114 {
115     bool ret = true;					// return status
116     bool isTiff = false;				// do we have a TIFF file?
117     bool haveTiff = false;				// can it be read via TIFF lib?
118 
119     if (!url.isLocalFile()) return (i18n("Loading non-local images is not yet implemented"));
120 
121     if (url.hasFragment())				// is this a subimage?
122     {
123         int subno = url.fragment().toInt();		// get subimage number
124         if (subno>0)					// valid number from fragment
125         {						// get local file without fragment
126             const QString fileName = url.adjusted(QUrl::RemoveFragment).toLocalFile();
127             qDebug() << "subimage" << subno << "from" << fileName;
128             loadTiffDir(fileName, subno);		// load TIFF subimage
129             return (QString());
130         }
131 
132     }
133 
134     const QString filename = url.toLocalFile();		// local path of image
135     ImageFormat format = ImageFormat::formatForUrl(url);
136 
137     // If the file is TIFF and QImageReader supports TIFF files, 'format' as
138     // returned by ImageFormat::formatForUrl() will be "TIF".  If TIFF files
139     // are not supported but the MIME type of the file says that it is TIFF,
140     // then 'format' will be "TIFFLIB".  So either of these format names means
141     // that a TIFF file is being loaded.
142     isTiff = format.isTiff();
143     //qDebug() << "Image format to load:" << format << "from file" << filename;
144 
145 #ifdef HAVE_TIFF
146     if (isTiff)						// if it is TIFF, check
147     {							// for multiple images
148         qDebug() << "Checking for multi-page TIFF";
149         TIFF *tif = TIFFOpen(QFile::encodeName(filename).constData(), "r");
150         if (tif!=nullptr)
151         {
152             do
153             {
154                 ++m_subImages;
155             } while (TIFFReadDirectory(tif));
156             qDebug() << "found" << m_subImages << "TIFF directories";
157             if (m_subImages>1) haveTiff = true;
158             // This format will have been specially detected
159             // in ImageFormat::formatForMime(), if the file is TIFF
160             // but QImageReader does not have TIFF support.
161             // The TIFF file can still be read by loadTiffDir() below.
162             else if (m_subImages==1 && format==ImageFormat("TIFFLIB")) haveTiff = true;
163             TIFFClose(tif);
164         }
165     }
166 #endif
167     if (!haveTiff)					// not TIFF, or not multi-page
168     {
169         ret = QImage::load(filename);			// Qt can only read one image
170         if (!ret) return (i18n("Image loading failed"));
171         m_subImages = 0;
172     }
173 #ifdef HAVE_TIFF
174     else						// a TIFF file, multi-page or not
175     {
176         loadTiffDir(filename, 0);			// read by TIFFlib directly
177     }
178 #endif
179 
180     m_url = url;					// record image source
181     m_fileBound = true;					// note loaded from file
182     return (QString());				// loaded OK
183 }
184 
185 
loadTiffDir(const QString & filename,int subno)186 QString  KookaImage::loadTiffDir(const QString &filename, int subno)
187 {
188 #ifdef HAVE_TIFF
189     // if it is TIFF, check with TIFFlib if it is multiple images
190     qDebug() << "Trying to load TIFF, subimage" << subno;
191     TIFF *tif = TIFFOpen(QFile::encodeName(filename).constData(), "r");
192     if (tif==nullptr) return (i18n("Failed to open TIFF file"));
193 
194     if (subno>0)						// want a subimage
195     {
196         if (!TIFFSetDirectory(tif, subno-1))
197         {
198             TIFFClose(tif);
199             return (i18n("Failed to read TIFF directory"));
200         }
201     }
202 
203     int imgWidth, imgHeight;
204     TIFFGetField(tif, TIFFTAG_IMAGEWIDTH,  &imgWidth);
205     TIFFGetField(tif, TIFFTAG_IMAGELENGTH, &imgHeight);
206 
207     // TODO: load bw-image correctly only 2 bit
208     //
209     // The Qt3 version of this did:
210     //
211     //   create( imgWidth, imgHeight, 32 );
212     //
213     // and then read the TIFF image into the image data as returned by bits().
214     //
215     // This is not possible on Qt4, where the size/depth of a QImage is set in
216     // its constructor and cannot be subsequently changed.  Instead we read
217     // the TIFF file into a temporary image and then use QImage::operator=
218     // to shallow copy that temporary image into ours.  After that temporary
219     // image has been destroyed, we have the only copy of the TIFF read image.
220     //
221     QImage tmpImg(imgWidth, imgHeight, QImage::Format_RGB32);
222     uint32 *data = (uint32 *)(tmpImg.bits());
223     if (TIFFReadRGBAImage(tif, imgWidth, imgHeight, data, 0)) {
224         // Successfully read, now convert
225         tmpImg = tmpImg.rgbSwapped();           // swap red and blue
226         tmpImg = tmpImg.mirrored();         // reverse (it's upside down)
227     } else {
228         TIFFClose(tif);
229         return (i18n("Failed to read TIFF image"));
230     }
231 
232     // Fetch the x- and y-resolutions to adjust images
233     float xReso, yReso;
234     bool resosFound = TIFFGetField(tif, TIFFTAG_XRESOLUTION, &xReso) &&
235                       TIFFGetField(tif, TIFFTAG_YRESOLUTION, &yReso);
236     //qDebug() << "TIFF image: X-Res" << xReso << "Y-Res" << yReso;
237 
238     TIFFClose(tif);                 // finished with TIFF file
239 
240     // Check now if resolution in x- and y-directions differ.
241     // If so, stretch the image accordingly.
242     if (resosFound && xReso != yReso) {
243         if (xReso > yReso) {
244             float yScalefactor = xReso / yReso;
245             //qDebug() << "Different resolution X/Y, rescaling Y with factor" << yScalefactor;
246             /* rescale the image */
247             tmpImg = tmpImg.scaled(imgWidth, int(imgHeight * yScalefactor),
248                                    Qt::IgnoreAspectRatio, Qt::SmoothTransformation);
249         } else {
250             float xScalefactor = yReso / xReso;
251             //qDebug() << "Different resolution X/Y, rescaling X with factor" << xScalefactor;
252             tmpImg = tmpImg.scaled(int(imgWidth * xScalefactor), imgHeight,
253                                    Qt::IgnoreAspectRatio, Qt::SmoothTransformation);
254         }
255     }
256 
257     QImage::operator=(tmpImg);				// copy as our image
258 #else
259     return (i18n("TIFF not supported"));
260 #endif							// HAVE_TIFF
261     return (QString());				// TIFF read succeeded
262 }
263 
264 
subImagesCount() const265 int KookaImage::subImagesCount() const
266 {
267     return (m_subImages);
268 }
269 
270 
isSubImage() const271 bool KookaImage::isSubImage() const
272 {
273     return (m_url.isValid() && m_url.fragment().toInt()>0);
274 }
275 
276 
277 #ifdef KDE3
278 
279 // TODO: only used in kookaprint, move to there
280 /*
281  * tiling
282  */
cutToTiles(const QSize maxSize,int & rows,int & cols,TileMode)283 int KookaImage::cutToTiles(const QSize maxSize, int &rows, int &cols, TileMode)
284 {
285     QSize imgSize = size();
286 
287     int w = imgSize.width();
288     if (w > maxSize.width()) {
289         // image is wider than paper
290         w = maxSize.width();
291     }
292     int h = imgSize.height();
293     if (h > maxSize.height()) {
294         // image is wider than paper
295         h = maxSize.height();
296     }
297 
298     int absX = 0;  // absolute x position from where to start print
299     int absY = 0;  // on the image, left top corner of the part to print
300     rows = 0;
301 
302     while (h) { // Loop over height, cut in vertical direction
303         rows++;
304         cols = 0;
305         while (w) { // Loop over width, cut in horizontal direction
306             cols++;
307             m_tileVector.append(QRect(absX, absY, w, h));
308 
309             absX += w + 1;
310             w = imgSize.width() - absX;
311 
312             // if w < 0, this was the last loop, set w to zero to stop loop
313             if (w < 0) {
314                 w = 0;
315             }
316 
317             // if > 0 here, a new page is required
318             if (w > 0) {
319                 if (w > maxSize.width()) {
320                     w = maxSize.width();
321                 }
322             }
323         }
324         // Reset the X-values to start on the left border again
325         absX = 0;
326         // start with full width again
327         w = imgSize.width();
328         if (w > maxSize.width()) {
329             w = maxSize.width();
330         }
331 
332         absY += h + 1;
333         h = imgSize.height() - absY;
334 
335         if (h < 0) {
336             h = 0;    // be sure to meet the break condition
337         }
338         if (h > maxSize.height()) {
339             h = maxSize.height();    // limit to page height
340         }
341     }
342     m_tileCols = cols;
343 
344     return m_tileVector.count();
345 }
346 
getTileRect(int rowPos,int colPos) const347 QRect KookaImage::getTileRect(int rowPos, int colPos) const
348 {
349     int indx = rowPos * m_tileCols + colPos;
350     //qDebug() << "Tile index" << indx;
351     return (m_tileVector[indx]);
352 }
353 
354 #endif
355 
356 
357 // TODO: implement dpiX() and setDpiX()
358 // (more useful than dots per metre!)
359