1 /*
2     PosteRazor - Make your own poster!
3     Copyright (C) 2005-2018 by Alessandro Portale
4     http://posterazor.sourceforge.net/
5 
6     This file is part of PosteRazor
7 
8     PosteRazor is free software; you can redistribute it and/or modify
9     it under the terms of the GNU General Public License as published by
10     the Free Software Foundation, either version 3 of the License, or
11     (at your option) any later version.
12 
13     PosteRazor is distributed in the hope that it will be useful,
14     but WITHOUT ANY WARRANTY; without even the implied warranty of
15     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
16     GNU General Public License for more details.
17 
18     You should have received a copy of the GNU General Public License
19     along with PosteRazor; if not, write to the Free Software
20     Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
21 */
22 
23 #include "FreeImage.h"
24 #include "imageloaderfreeimage.h"
25 
26 #include <QColor>
27 #include <QStringList>
28 #include <qendian.h>
29 
30 #include <cmath>
31 
32 static QString FreeImageErrorMessage;
33 
FreeImageErrorHandler(FREE_IMAGE_FORMAT fif,const char * message)34 void FreeImageErrorHandler(FREE_IMAGE_FORMAT fif, const char *message)
35 {
36     Q_UNUSED(fif)
37     FreeImageErrorMessage = QLatin1String(message);
38 }
39 
40 class FreeImageInitializer
41 {
42 public:
FreeImageInitializer()43     FreeImageInitializer()
44     {
45         FreeImage_Initialise();
46         FreeImage_SetOutputMessage(FreeImageErrorHandler);
47     }
48 
~FreeImageInitializer()49     ~FreeImageInitializer()
50     {
51         FreeImage_DeInitialise();
52     }
53 };
54 
ImageLoaderFreeImage(QObject * parent)55 ImageLoaderFreeImage::ImageLoaderFreeImage(QObject *parent)
56     : QObject(parent)
57 {
58     const static FreeImageInitializer initializer;
59 }
60 
~ImageLoaderFreeImage()61 ImageLoaderFreeImage::~ImageLoaderFreeImage()
62 {
63     disposeImage();
64 }
65 
disposeImage()66 void ImageLoaderFreeImage::disposeImage()
67 {
68     if (m_bitmap) {
69         FreeImage_Unload(m_bitmap);
70         m_bitmap = nullptr;
71     }
72 }
73 
loadInputImage(const QString & imageFileName,QString & errorMessage)74 bool ImageLoaderFreeImage::loadInputImage(const QString &imageFileName, QString &errorMessage)
75 {
76     bool result = false;
77 
78     FreeImageErrorMessage.clear();
79 
80     const FREE_IMAGE_FORMAT fileType = FreeImage_GetFileType(imageFileName.toAscii(), 0);
81     FIBITMAP* newImage = FreeImage_Load(fileType, imageFileName.toAscii(), TIFF_CMYK|JPEG_CMYK);
82 
83     // Filter out images which FreeImage can load but not convert to Rgb24
84     // And images which we simply don't handle
85     if (newImage) {
86         const FREE_IMAGE_TYPE type = FreeImage_GetImageType(newImage);
87         if (type != FIT_BITMAP   // 1pbb Monochrome, 1-8bpp Palette, 8bpp Greyscale,
88                                  // 24bpp Rgb, 32bpp Argb, 32bpp Cmyk
89             && type != FIT_RGB16 // 16bpp Greyscale, 48bpp Rgb
90             ) {
91             FreeImage_Unload(newImage);
92             newImage = nullptr;
93         }
94     }
95 
96     if (newImage) {
97         result = true;
98         disposeImage();
99 
100         m_bitmap = newImage;
101 
102         m_widthPixels = FreeImage_GetWidth(m_bitmap);
103         m_heightPixels = FreeImage_GetHeight(m_bitmap);
104 
105         m_horizontalDotsPerMeter = FreeImage_GetDotsPerMeterX(m_bitmap);
106         m_verticalDotsPerMeter = FreeImage_GetDotsPerMeterY(m_bitmap);
107 
108         if (m_horizontalDotsPerMeter == 0)
109             m_horizontalDotsPerMeter = 2835; // 72 dpi
110         if (m_verticalDotsPerMeter == 0)
111             m_verticalDotsPerMeter = 2835;
112 
113         m_imageFileName = imageFileName;
114 
115         if (colorDataType() == Types::ColorTypeRGB && bitsPerPixel() == 32) {
116             // Sometimes, there are strange .PSD images like this (FreeImage bug?)
117             RGBQUAD white = { 255, 255, 255, 0 };
118             FIBITMAP *Image24Bit = FreeImage_Composite(m_bitmap, FALSE, &white);
119             FreeImage_Unload(m_bitmap);
120             m_bitmap = Image24Bit;
121         }
122     }
123 
124     errorMessage = FreeImageErrorMessage;
125 
126     return result;
127 }
128 
isImageLoaded() const129 bool ImageLoaderFreeImage::isImageLoaded() const
130 {
131     return (m_bitmap != nullptr);
132 }
133 
isJpeg() const134 bool ImageLoaderFreeImage::isJpeg() const
135 {
136     return FreeImage_GetFileType(m_imageFileName.toAscii(), 0) == FIF_JPEG;
137 }
138 
fileName() const139 QString ImageLoaderFreeImage::fileName() const
140 {
141     return m_imageFileName;
142 }
143 
sizePixels() const144 QSize ImageLoaderFreeImage::sizePixels() const
145 {
146     return QSize(m_widthPixels, m_heightPixels);
147 }
148 
horizontalDotsPerUnitOfLength(Types::UnitsOfLength unit) const149 qreal ImageLoaderFreeImage::horizontalDotsPerUnitOfLength(Types::UnitsOfLength unit) const
150 {
151     return m_horizontalDotsPerMeter / Types::convertBetweenUnitsOfLength(1, Types::UnitOfLengthMeter, unit);
152 }
153 
verticalDotsPerUnitOfLength(Types::UnitsOfLength unit) const154 qreal ImageLoaderFreeImage::verticalDotsPerUnitOfLength(Types::UnitsOfLength unit) const
155 {
156     return m_verticalDotsPerMeter / Types::convertBetweenUnitsOfLength(1, Types::UnitOfLengthMeter, unit);
157 }
158 
size(Types::UnitsOfLength unit) const159 QSizeF ImageLoaderFreeImage::size(Types::UnitsOfLength unit) const
160 {
161     const QSize sizePixels = this->sizePixels();
162     return QSizeF(sizePixels.width() / horizontalDotsPerUnitOfLength(unit), sizePixels.height() / verticalDotsPerUnitOfLength(unit));
163 }
164 
imageAsRGB(const QSize & size) const165 const QImage ImageLoaderFreeImage::imageAsRGB(const QSize &size) const
166 {
167     const QSize resultSize = size.isValid() ? size : sizePixels();
168     const bool isRGB24 = colorDataType() == Types::ColorTypeRGB && bitsPerPixel() == 24;
169     const bool isARGB32 = colorDataType() == Types::ColorTypeRGBA && bitsPerPixel() == 32;
170     QImage result(resultSize, isARGB32 ? QImage::Format_ARGB32 : QImage::Format_RGB32);
171 
172     const int width = resultSize.width();
173     const int height = resultSize.height();
174     const QSize sizePixels = this->sizePixels();
175 
176     FIBITMAP* originalImage = m_bitmap;
177     FIBITMAP* temp24BPPImage = nullptr;
178     FIBITMAP* scaledImage = nullptr;
179 
180     if (!(isRGB24 || isARGB32)) {
181         if (colorDataType() == Types::ColorTypeCMYK) {
182             const bool isCmykJpeg = isJpeg(); // Value range inverted
183             temp24BPPImage = FreeImage_Allocate(sizePixels.width(), sizePixels.height(), 24);
184             const unsigned int columnsCount = sizePixels.width();
185             const unsigned int scanlinesCount = sizePixels.height();
186             for (unsigned int scanline = 0; scanline < scanlinesCount; scanline++) {
187                 const BYTE* const cmykBits = FreeImage_GetScanLine(m_bitmap, scanline);
188                 tagRGBTRIPLE* const rgbBits = (tagRGBTRIPLE *)FreeImage_GetScanLine(temp24BPPImage, scanline);
189 
190                 for (unsigned int column = 0; column < columnsCount; column++) {
191                     const unsigned int cmykColumn = column * 4;
192 
193                     const QColor rgbColor = isCmykJpeg ?
194                         QColor::fromCmyk(255 - cmykBits[cmykColumn], 255 - cmykBits[cmykColumn + 1], 255 - cmykBits[cmykColumn + 2], 255 - cmykBits[cmykColumn + 3])
195                         : QColor::fromCmyk(cmykBits[cmykColumn], cmykBits[cmykColumn + 1], cmykBits[cmykColumn + 2], cmykBits[cmykColumn + 3]);
196 
197                     rgbBits[column].rgbtRed = (BYTE)rgbColor.red();
198                     rgbBits[column].rgbtGreen = (BYTE)rgbColor.green();
199                     rgbBits[column].rgbtBlue = (BYTE)rgbColor.blue();
200                 }
201             }
202         } else {
203             temp24BPPImage = FreeImage_ConvertTo24Bits(originalImage);
204         }
205         originalImage = temp24BPPImage;
206     }
207 
208     if (resultSize != sizePixels) {
209         scaledImage = FreeImage_Rescale(originalImage, width, height, FILTER_BOX);
210         originalImage = scaledImage;
211     }
212 
213     for (int scanline = 0; scanline < height; scanline++) {
214         QRgb *targetData = (QRgb*)result.scanLine(scanline);
215         if (isARGB32) {
216             const tagRGBQUAD *sourceRgba = (tagRGBQUAD*)FreeImage_GetScanLine(originalImage, height - scanline - 1);
217             for (int column = 0; column < width; column++) {
218                 *targetData++ = qRgba(sourceRgba->rgbRed, sourceRgba->rgbGreen, sourceRgba->rgbBlue, sourceRgba->rgbReserved);
219                 sourceRgba++;
220             }
221         } else {
222             const tagRGBTRIPLE *sourceRgb = (tagRGBTRIPLE*)FreeImage_GetScanLine(originalImage, height - scanline - 1);
223             for (int column = 0; column < width; column++) {
224                 *targetData++ = qRgb(sourceRgb->rgbtRed, sourceRgb->rgbtGreen, sourceRgb->rgbtBlue);
225                 sourceRgb++;
226             }
227         }
228     }
229 
230     if (temp24BPPImage)
231         FreeImage_Unload(temp24BPPImage);
232 
233     if (scaledImage)
234         FreeImage_Unload(scaledImage);
235 
236     return result;
237 }
238 
bitsPerPixel() const239 int ImageLoaderFreeImage::bitsPerPixel() const
240 {
241     return FreeImage_GetBPP(m_bitmap);
242 }
243 
colorDataType() const244 Types::ColorTypes ImageLoaderFreeImage::colorDataType() const
245 {
246     Types::ColorTypes colorDatatype = Types::ColorTypeRGB;
247     const FREE_IMAGE_COLOR_TYPE imageColorType = FreeImage_GetColorType(m_bitmap);
248 
249     if (imageColorType == FIC_MINISBLACK || imageColorType == FIC_MINISWHITE) {
250         colorDatatype = bitsPerPixel() == 1 ? Types::ColorTypeMonochrome : Types::ColorTypeGreyscale;
251     } else {
252         colorDatatype =
253             imageColorType == FIC_PALETTE ? Types::ColorTypePalette
254             : imageColorType == FIC_RGB ? Types::ColorTypeRGB
255             : imageColorType == FIC_RGBALPHA ? Types::ColorTypeRGBA
256             : /* imageColorType == FIC_CMYK ? */ Types::ColorTypeCMYK;
257     }
258 
259     return colorDatatype;
260 }
261 
bits() const262 const QByteArray ImageLoaderFreeImage::bits() const
263 {
264     const unsigned int bitsPerLine = m_widthPixels * bitsPerPixel();
265     const unsigned int bytesPerLine = (unsigned int)ceil(bitsPerLine/8.0);
266     const unsigned int imageBytesCount = bytesPerLine * m_heightPixels;
267 
268     QByteArray result(imageBytesCount, 0);
269     char *destination = result.data();
270     FreeImage_ConvertToRawBits((BYTE*)destination, m_bitmap, bytesPerLine, bitsPerPixel(), FI_RGBA_RED_MASK, FI_RGBA_GREEN_MASK, FI_RGBA_BLUE_MASK, hasFreeImageVersionCorrectTopDownInConvertBits());
271 
272     const unsigned int numberOfPixels = m_widthPixels * m_heightPixels;
273 #if FREEIMAGE_COLORORDER == FREEIMAGE_COLORORDER_BGR
274     if (colorDataType() == Types::ColorTypeRGB && bitsPerPixel() == 24) {
275         for (unsigned int pixelIndex = 0; pixelIndex < numberOfPixels; pixelIndex++) {
276             char *pixelPtr = destination + pixelIndex*3;
277             const char temp = pixelPtr[0];
278             pixelPtr[0] = pixelPtr[2];
279             pixelPtr[2] = temp;
280             pixelPtr+=3;
281         }
282     } else if (colorDataType() == Types::ColorTypeRGBA && bitsPerPixel() == 32) {
283         auto argbDestination = (unsigned int*)destination;
284         for (unsigned int pixelIndex = 0; pixelIndex < numberOfPixels; pixelIndex++) {
285             *argbDestination = qToBigEndian(*argbDestination);
286             argbDestination++;
287         }
288     } else
289 #endif // FREEIMAGE_COLORORDER == FREEIMAGE_COLORORDER_BGR
290     if (colorDataType() == Types::ColorTypeRGB && bitsPerPixel() == 48) {
291         // Apparently, the 48 bit data has to be reordered on Windows and ppc/i386 OSX
292         // TODO: So maybe this swap belongs into the PDFwriter. Investigate.
293         auto rgb48Destination = (unsigned short*)destination;
294         const unsigned long numberOfSwaps = numberOfPixels * 3; // Words are swapped
295         for (unsigned int pixelIndex = 0; pixelIndex < numberOfSwaps; pixelIndex++) {
296             *rgb48Destination = qToBigEndian(*rgb48Destination);
297             rgb48Destination++;
298         }
299     }
300 
301     return result;
302 }
303 
colorTable() const304 const QVector<QRgb> ImageLoaderFreeImage::colorTable() const
305 {
306     QVector<QRgb> result;
307 
308     const RGBQUAD* const palette = FreeImage_GetPalette(m_bitmap);
309     if (palette) {
310         const int count = FreeImage_GetColorsUsed(m_bitmap);
311         result.resize(count);
312         for (int i = 0; i < count; i++)
313             result.replace(i, qRgb(palette[i].rgbRed, palette[i].rgbGreen, palette[i].rgbBlue));
314     }
315 
316     return result;
317 }
318 
imageFormats() const319 const QVector<QPair<QStringList, QString> > &ImageLoaderFreeImage::imageFormats() const
320 {
321     static QVector<QPair<QStringList, QString> > formats;
322     if (formats.empty()) {
323         const int pluginsCount = FreeImage_GetFIFCount();
324         for (int pluginIndex = 0; pluginIndex < pluginsCount; pluginIndex++) {
325             const FREE_IMAGE_FORMAT fif = (FREE_IMAGE_FORMAT)pluginIndex;
326             if (FreeImage_FIFSupportsReading(fif)) {
327                 const QString pluginExtensions(QLatin1String(FreeImage_GetFIFExtensionList(fif)));
328                 const QString pluginDescription(QLatin1String(FreeImage_GetFIFDescription(fif)));
329                 formats.append(QPair<QStringList, QString> (pluginExtensions.split(QLatin1Char(',')), pluginDescription));
330             }
331         }
332     }
333     return formats;
334 }
335 
hasFreeImageVersionCorrectTopDownInConvertBits()336 bool ImageLoaderFreeImage::hasFreeImageVersionCorrectTopDownInConvertBits()
337 {
338     return FREEIMAGE_MAJOR_VERSION >= 3 && FREEIMAGE_MINOR_VERSION >= 10;
339 }
libraryName() const340 QString ImageLoaderFreeImage::libraryName() const
341 {
342     return QLatin1String("FreeImage");
343 }
344 
libraryAboutText() const345 QString ImageLoaderFreeImage::libraryAboutText() const
346 {
347     return QLatin1String(FreeImage_GetCopyrightMessage());
348 }
349