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