blob: fd1c488f6d4ff04ac225964d1f5d0e1e219d5ef5 [file] [log] [blame]
/****************************************************************************
**
** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies).
** All rights reserved.
** Contact: Nokia Corporation (qt-info@nokia.com)
**
** This file is part of the plugins of the Qt Toolkit.
**
** $QT_BEGIN_LICENSE:LGPL$
** GNU Lesser General Public License Usage
** This file may be used under the terms of the GNU Lesser General Public
** License version 2.1 as published by the Free Software Foundation and
** appearing in the file LICENSE.LGPL included in the packaging of this
** file. Please review the following information to ensure the GNU Lesser
** General Public License version 2.1 requirements will be met:
** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
**
** In addition, as a special exception, Nokia gives you certain additional
** rights. These rights are described in the Nokia Qt LGPL Exception
** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
**
** GNU General Public License Usage
** Alternatively, this file may be used under the terms of the GNU General
** Public License version 3.0 as published by the Free Software Foundation
** and appearing in the file LICENSE.GPL included in the packaging of this
** file. Please review the following information to ensure the GNU General
** Public License version 3.0 requirements will be met:
** http://www.gnu.org/copyleft/gpl.html.
**
** Other Usage
** Alternatively, this file may be used in accordance with the terms and
** conditions contained in a signed written agreement between you and Nokia.
**
**
**
**
**
** $QT_END_LICENSE$
**
****************************************************************************/
#include "qtiffhandler_p.h"
#include <qvariant.h>
#include <qdebug.h>
#include <qimage.h>
#include <qglobal.h>
extern "C" {
#include "tiffio.h"
}
QT_BEGIN_NAMESPACE
tsize_t qtiffReadProc(thandle_t fd, tdata_t buf, tsize_t size)
{
QIODevice* device = static_cast<QTiffHandler*>(fd)->device();
return device->isReadable() ? device->read(static_cast<char *>(buf), size) : -1;
}
tsize_t qtiffWriteProc(thandle_t fd, tdata_t buf, tsize_t size)
{
return static_cast<QTiffHandler*>(fd)->device()->write(static_cast<char *>(buf), size);
}
toff_t qtiffSeekProc(thandle_t fd, toff_t off, int whence)
{
QIODevice *device = static_cast<QTiffHandler*>(fd)->device();
switch (whence) {
case SEEK_SET:
device->seek(off);
break;
case SEEK_CUR:
device->seek(device->pos() + off);
break;
case SEEK_END:
device->seek(device->size() + off);
break;
}
return device->pos();
}
int qtiffCloseProc(thandle_t /*fd*/)
{
return 0;
}
toff_t qtiffSizeProc(thandle_t fd)
{
return static_cast<QTiffHandler*>(fd)->device()->size();
}
int qtiffMapProc(thandle_t /*fd*/, tdata_t* /*pbase*/, toff_t* /*psize*/)
{
return 0;
}
void qtiffUnmapProc(thandle_t /*fd*/, tdata_t /*base*/, toff_t /*size*/)
{
}
// for 32 bits images
inline void rotate_right_mirror_horizontal(QImage *const image)// rotate right->mirrored horizontal
{
const int height = image->height();
const int width = image->width();
QImage generated(/* width = */ height, /* height = */ width, image->format());
const uint32 *originalPixel = reinterpret_cast<const uint32*>(image->bits());
uint32 *const generatedPixels = reinterpret_cast<uint32*>(generated.bits());
for (int row=0; row < height; ++row) {
for (int col=0; col < width; ++col) {
int idx = col * height + row;
generatedPixels[idx] = *originalPixel;
++originalPixel;
}
}
*image = generated;
}
inline void rotate_right_mirror_vertical(QImage *const image) // rotate right->mirrored vertical
{
const int height = image->height();
const int width = image->width();
QImage generated(/* width = */ height, /* height = */ width, image->format());
const int lastCol = width - 1;
const int lastRow = height - 1;
const uint32 *pixel = reinterpret_cast<const uint32*>(image->bits());
uint32 *const generatedBits = reinterpret_cast<uint32*>(generated.bits());
for (int row=0; row < height; ++row) {
for (int col=0; col < width; ++col) {
int idx = (lastCol - col) * height + (lastRow - row);
generatedBits[idx] = *pixel;
++pixel;
}
}
*image = generated;
}
QTiffHandler::QTiffHandler() : QImageIOHandler()
{
compression = NoCompression;
}
bool QTiffHandler::canRead() const
{
if (canRead(device())) {
setFormat("tiff");
return true;
}
return false;
}
bool QTiffHandler::canRead(QIODevice *device)
{
if (!device) {
qWarning("QTiffHandler::canRead() called with no device");
return false;
}
// current implementation uses TIFFClientOpen which needs to be
// able to seek, so sequential devices are not supported
QByteArray header = device->peek(4);
return header == QByteArray::fromRawData("\x49\x49\x2A\x00", 4)
|| header == QByteArray::fromRawData("\x4D\x4D\x00\x2A", 4);
}
bool QTiffHandler::read(QImage *image)
{
if (!canRead())
return false;
TIFF *const tiff = TIFFClientOpen("foo",
"r",
this,
qtiffReadProc,
qtiffWriteProc,
qtiffSeekProc,
qtiffCloseProc,
qtiffSizeProc,
qtiffMapProc,
qtiffUnmapProc);
if (!tiff) {
return false;
}
uint32 width;
uint32 height;
uint16 photometric;
if (!TIFFGetField(tiff, TIFFTAG_IMAGEWIDTH, &width)
|| !TIFFGetField(tiff, TIFFTAG_IMAGELENGTH, &height)
|| !TIFFGetField(tiff, TIFFTAG_PHOTOMETRIC, &photometric)) {
TIFFClose(tiff);
return false;
}
uint16 bitsPerSample, samplesPerPixel, bitsPerPixel;
if (!TIFFGetFieldDefaulted(tiff, TIFFTAG_BITSPERSAMPLE, &bitsPerSample)) {
TIFFClose(tiff);
return false;
}
if (!TIFFGetFieldDefaulted(tiff, TIFFTAG_SAMPLESPERPIXEL, &samplesPerPixel)) {
TIFFClose(tiff);
return false;
}
bitsPerPixel = bitsPerSample * samplesPerPixel;
bool grayscale = photometric == PHOTOMETRIC_MINISBLACK || photometric == PHOTOMETRIC_MINISWHITE;
if (grayscale && bitsPerPixel == 1) {
if (image->size() != QSize(width, height) || image->format() != QImage::Format_Mono)
*image = QImage(width, height, QImage::Format_Mono);
QVector<QRgb> colortable(2);
if (photometric == PHOTOMETRIC_MINISBLACK) {
colortable[0] = 0xff000000;
colortable[1] = 0xffffffff;
} else {
colortable[0] = 0xffffffff;
colortable[1] = 0xff000000;
}
image->setColorTable(colortable);
if (!image->isNull()) {
for (uint32 y=0; y<height; ++y) {
if (TIFFReadScanline(tiff, image->scanLine(y), y, 0) < 0) {
TIFFClose(tiff);
return false;
}
}
}
} else {
if ((grayscale || photometric == PHOTOMETRIC_PALETTE) && bitsPerPixel == 8) {
if (image->size() != QSize(width, height) || image->format() != QImage::Format_Indexed8)
*image = QImage(width, height, QImage::Format_Indexed8);
if (!image->isNull()) {
const uint16 tableSize = 256;
QVector<QRgb> qtColorTable(tableSize);
if (grayscale) {
for (int i = 0; i<tableSize; ++i) {
const int c = (photometric == PHOTOMETRIC_MINISBLACK) ? i : (255 - i);
qtColorTable[i] = qRgb(c, c, c);
}
} else {
// create the color table
uint16 *redTable = 0;
uint16 *greenTable = 0;
uint16 *blueTable = 0;
if (!TIFFGetField(tiff, TIFFTAG_COLORMAP, &redTable, &greenTable, &blueTable)) {
TIFFClose(tiff);
return false;
}
if (!redTable || !greenTable || !blueTable) {
TIFFClose(tiff);
return false;
}
for (int i = 0; i<tableSize ;++i) {
const int red = redTable[i] / 257;
const int green = greenTable[i] / 257;
const int blue = blueTable[i] / 257;
qtColorTable[i] = qRgb(red, green, blue);
}
}
image->setColorTable(qtColorTable);
for (uint32 y=0; y<height; ++y) {
if (TIFFReadScanline(tiff, image->scanLine(y), y, 0) < 0) {
TIFFClose(tiff);
return false;
}
}
// free redTable, greenTable and greenTable done by libtiff
}
} else {
if (image->size() != QSize(width, height) || image->format() != QImage::Format_ARGB32)
*image = QImage(width, height, QImage::Format_ARGB32);
if (!image->isNull()) {
const int stopOnError = 1;
if (TIFFReadRGBAImageOriented(tiff, width, height, reinterpret_cast<uint32 *>(image->bits()), ORIENTATION_TOPLEFT, stopOnError)) {
for (uint32 y=0; y<height; ++y)
convert32BitOrder(image->scanLine(y), width);
} else {
TIFFClose(tiff);
return false;
}
}
}
}
if (image->isNull()) {
TIFFClose(tiff);
return false;
}
float resX = 0;
float resY = 0;
uint16 resUnit = RESUNIT_NONE;
if (TIFFGetField(tiff, TIFFTAG_RESOLUTIONUNIT, &resUnit)
&& TIFFGetField(tiff, TIFFTAG_XRESOLUTION, &resX)
&& TIFFGetField(tiff, TIFFTAG_YRESOLUTION, &resY)) {
switch(resUnit) {
case RESUNIT_CENTIMETER:
image->setDotsPerMeterX(qRound(resX * 100));
image->setDotsPerMeterY(qRound(resY * 100));
break;
case RESUNIT_INCH:
image->setDotsPerMeterX(qRound(resX * (100 / 2.54)));
image->setDotsPerMeterY(qRound(resY * (100 / 2.54)));
break;
default:
// do nothing as defaults have already
// been set within the QImage class
break;
}
}
// rotate the image if the orientation is defined in the file
uint16 orientationTag;
if (TIFFGetField(tiff, TIFFTAG_ORIENTATION, &orientationTag)) {
if (image->format() == QImage::Format_ARGB32) {
// TIFFReadRGBAImageOriented() flip the image but does not rotate them
switch (orientationTag) {
case 5:
rotate_right_mirror_horizontal(image);
break;
case 6:
rotate_right_mirror_vertical(image);
break;
case 7:
rotate_right_mirror_horizontal(image);
break;
case 8:
rotate_right_mirror_vertical(image);
break;
}
} else {
switch (orientationTag) {
case 1: // default orientation
break;
case 2: // mirror horizontal
*image = image->mirrored(true, false);
break;
case 3: // mirror both
*image = image->mirrored(true, true);
break;
case 4: // mirror vertical
*image = image->mirrored(false, true);
break;
case 5: // rotate right mirror horizontal
{
QMatrix transformation;
transformation.rotate(90);
*image = image->transformed(transformation);
*image = image->mirrored(true, false);
break;
}
case 6: // rotate right
{
QMatrix transformation;
transformation.rotate(90);
*image = image->transformed(transformation);
break;
}
case 7: // rotate right, mirror vertical
{
QMatrix transformation;
transformation.rotate(90);
*image = image->transformed(transformation);
*image = image->mirrored(false, true);
break;
}
case 8: // rotate left
{
QMatrix transformation;
transformation.rotate(270);
*image = image->transformed(transformation);
break;
}
}
}
}
TIFFClose(tiff);
return true;
}
static bool checkGrayscale(const QVector<QRgb> &colorTable)
{
if (colorTable.size() != 256)
return false;
const bool increasing = (colorTable.at(0) == 0xff000000);
for (int i = 0; i < 256; ++i) {
if ((increasing && colorTable.at(i) != qRgb(i, i, i))
|| (!increasing && colorTable.at(i) != qRgb(255 - i, 255 - i, 255 - i)))
return false;
}
return true;
}
bool QTiffHandler::write(const QImage &image)
{
if (!device()->isWritable())
return false;
TIFF *const tiff = TIFFClientOpen("foo",
"w",
this,
qtiffReadProc,
qtiffWriteProc,
qtiffSeekProc,
qtiffCloseProc,
qtiffSizeProc,
qtiffMapProc,
qtiffUnmapProc);
if (!tiff)
return false;
const int width = image.width();
const int height = image.height();
if (!TIFFSetField(tiff, TIFFTAG_IMAGEWIDTH, width)
|| !TIFFSetField(tiff, TIFFTAG_IMAGELENGTH, height)
|| !TIFFSetField(tiff, TIFFTAG_PLANARCONFIG, PLANARCONFIG_CONTIG)) {
TIFFClose(tiff);
return false;
}
// set the resolution
bool resolutionSet = false;
const int dotPerMeterX = image.dotsPerMeterX();
const int dotPerMeterY = image.dotsPerMeterY();
if ((dotPerMeterX % 100) == 0
&& (dotPerMeterY % 100) == 0) {
resolutionSet = TIFFSetField(tiff, TIFFTAG_RESOLUTIONUNIT, RESUNIT_CENTIMETER)
&& TIFFSetField(tiff, TIFFTAG_XRESOLUTION, dotPerMeterX/100.0)
&& TIFFSetField(tiff, TIFFTAG_YRESOLUTION, dotPerMeterY/100.0);
} else {
resolutionSet = TIFFSetField(tiff, TIFFTAG_RESOLUTIONUNIT, RESUNIT_INCH)
&& TIFFSetField(tiff, TIFFTAG_XRESOLUTION, static_cast<float>(image.logicalDpiX()))
&& TIFFSetField(tiff, TIFFTAG_YRESOLUTION, static_cast<float>(image.logicalDpiY()));
}
if (!resolutionSet) {
TIFFClose(tiff);
return false;
}
// configure image depth
const QImage::Format format = image.format();
if (format == QImage::Format_Mono || format == QImage::Format_MonoLSB) {
uint16 photometric = PHOTOMETRIC_MINISBLACK;
if (image.colorTable().at(0) == 0xffffffff)
photometric = PHOTOMETRIC_MINISWHITE;
if (!TIFFSetField(tiff, TIFFTAG_PHOTOMETRIC, photometric)
|| !TIFFSetField(tiff, TIFFTAG_COMPRESSION, compression == NoCompression ? COMPRESSION_NONE : COMPRESSION_CCITTRLE)
|| !TIFFSetField(tiff, TIFFTAG_BITSPERSAMPLE, 1)) {
TIFFClose(tiff);
return false;
}
// try to do the conversion in chunks no greater than 16 MB
int chunks = (width * height / (1024 * 1024 * 16)) + 1;
int chunkHeight = qMax(height / chunks, 1);
int y = 0;
while (y < height) {
QImage chunk = image.copy(0, y, width, qMin(chunkHeight, height - y)).convertToFormat(QImage::Format_Mono);
int chunkStart = y;
int chunkEnd = y + chunk.height();
while (y < chunkEnd) {
if (TIFFWriteScanline(tiff, reinterpret_cast<uint32 *>(chunk.scanLine(y - chunkStart)), y) != 1) {
TIFFClose(tiff);
return false;
}
++y;
}
}
TIFFClose(tiff);
} else if (format == QImage::Format_Indexed8) {
const QVector<QRgb> colorTable = image.colorTable();
bool isGrayscale = checkGrayscale(colorTable);
if (isGrayscale) {
uint16 photometric = PHOTOMETRIC_MINISBLACK;
if (image.colorTable().at(0) == 0xffffffff)
photometric = PHOTOMETRIC_MINISWHITE;
if (!TIFFSetField(tiff, TIFFTAG_PHOTOMETRIC, photometric)
|| !TIFFSetField(tiff, TIFFTAG_COMPRESSION, compression == NoCompression ? COMPRESSION_NONE : COMPRESSION_PACKBITS)
|| !TIFFSetField(tiff, TIFFTAG_BITSPERSAMPLE, 8)) {
TIFFClose(tiff);
return false;
}
} else {
if (!TIFFSetField(tiff, TIFFTAG_PHOTOMETRIC, PHOTOMETRIC_PALETTE)
|| !TIFFSetField(tiff, TIFFTAG_COMPRESSION, compression == NoCompression ? COMPRESSION_NONE : COMPRESSION_PACKBITS)
|| !TIFFSetField(tiff, TIFFTAG_BITSPERSAMPLE, 8)) {
TIFFClose(tiff);
return false;
}
//// write the color table
// allocate the color tables
uint16 *redTable = static_cast<uint16 *>(qMalloc(256 * sizeof(uint16)));
uint16 *greenTable = static_cast<uint16 *>(qMalloc(256 * sizeof(uint16)));
uint16 *blueTable = static_cast<uint16 *>(qMalloc(256 * sizeof(uint16)));
if (!redTable || !greenTable || !blueTable) {
qFree(redTable);
qFree(greenTable);
qFree(blueTable);
TIFFClose(tiff);
return false;
}
// set the color table
const int tableSize = colorTable.size();
Q_ASSERT(tableSize <= 256);
for (int i = 0; i<tableSize; ++i) {
const QRgb color = colorTable.at(i);
redTable[i] = qRed(color) * 257;
greenTable[i] = qGreen(color) * 257;
blueTable[i] = qBlue(color) * 257;
}
const bool setColorTableSuccess = TIFFSetField(tiff, TIFFTAG_COLORMAP, redTable, greenTable, blueTable);
qFree(redTable);
qFree(greenTable);
qFree(blueTable);
if (!setColorTableSuccess) {
TIFFClose(tiff);
return false;
}
}
//// write the data
// try to do the conversion in chunks no greater than 16 MB
int chunks = (width * height/ (1024 * 1024 * 16)) + 1;
int chunkHeight = qMax(height / chunks, 1);
int y = 0;
while (y < height) {
QImage chunk = image.copy(0, y, width, qMin(chunkHeight, height - y));
int chunkStart = y;
int chunkEnd = y + chunk.height();
while (y < chunkEnd) {
if (TIFFWriteScanline(tiff, reinterpret_cast<uint32 *>(chunk.scanLine(y - chunkStart)), y) != 1) {
TIFFClose(tiff);
return false;
}
++y;
}
}
TIFFClose(tiff);
} else {
if (!TIFFSetField(tiff, TIFFTAG_PHOTOMETRIC, PHOTOMETRIC_RGB)
|| !TIFFSetField(tiff, TIFFTAG_COMPRESSION, compression == NoCompression ? COMPRESSION_NONE : COMPRESSION_LZW)
|| !TIFFSetField(tiff, TIFFTAG_SAMPLESPERPIXEL, 4)
|| !TIFFSetField(tiff, TIFFTAG_BITSPERSAMPLE, 8)) {
TIFFClose(tiff);
return false;
}
// try to do the ARGB32 conversion in chunks no greater than 16 MB
int chunks = (width * height * 4 / (1024 * 1024 * 16)) + 1;
int chunkHeight = qMax(height / chunks, 1);
int y = 0;
while (y < height) {
QImage chunk = image.copy(0, y, width, qMin(chunkHeight, height - y)).convertToFormat(QImage::Format_ARGB32);
int chunkStart = y;
int chunkEnd = y + chunk.height();
while (y < chunkEnd) {
if (QSysInfo::ByteOrder == QSysInfo::LittleEndian)
convert32BitOrder(chunk.scanLine(y - chunkStart), width);
else
convert32BitOrderBigEndian(chunk.scanLine(y - chunkStart), width);
if (TIFFWriteScanline(tiff, reinterpret_cast<uint32 *>(chunk.scanLine(y - chunkStart)), y) != 1) {
TIFFClose(tiff);
return false;
}
++y;
}
}
TIFFClose(tiff);
}
return true;
}
QByteArray QTiffHandler::name() const
{
return "tiff";
}
QVariant QTiffHandler::option(ImageOption option) const
{
if (option == Size && canRead()) {
QSize imageSize;
qint64 pos = device()->pos();
TIFF *tiff = TIFFClientOpen("foo",
"r",
const_cast<QTiffHandler*>(this),
qtiffReadProc,
qtiffWriteProc,
qtiffSeekProc,
qtiffCloseProc,
qtiffSizeProc,
qtiffMapProc,
qtiffUnmapProc);
if (tiff) {
uint32 width = 0;
uint32 height = 0;
TIFFGetField(tiff, TIFFTAG_IMAGEWIDTH, &width);
TIFFGetField(tiff, TIFFTAG_IMAGELENGTH, &height);
imageSize = QSize(width, height);
TIFFClose(tiff);
}
device()->seek(pos);
if (imageSize.isValid())
return imageSize;
} else if (option == CompressionRatio) {
return compression;
} else if (option == ImageFormat) {
return QImage::Format_ARGB32;
}
return QVariant();
}
void QTiffHandler::setOption(ImageOption option, const QVariant &value)
{
if (option == CompressionRatio && value.type() == QVariant::Int)
compression = value.toInt();
}
bool QTiffHandler::supportsOption(ImageOption option) const
{
return option == CompressionRatio
|| option == Size
|| option == ImageFormat;
}
void QTiffHandler::convert32BitOrder(void *buffer, int width)
{
uint32 *target = reinterpret_cast<uint32 *>(buffer);
for (int32 x=0; x<width; ++x) {
uint32 p = target[x];
// convert between ARGB and ABGR
target[x] = (p & 0xff000000)
| ((p & 0x00ff0000) >> 16)
| (p & 0x0000ff00)
| ((p & 0x000000ff) << 16);
}
}
void QTiffHandler::convert32BitOrderBigEndian(void *buffer, int width)
{
uint32 *target = reinterpret_cast<uint32 *>(buffer);
for (int32 x=0; x<width; ++x) {
uint32 p = target[x];
target[x] = (p & 0xff000000) >> 24
| (p & 0x00ff0000) << 8
| (p & 0x0000ff00) << 8
| (p & 0x000000ff) << 8;
}
}
QT_END_NAMESPACE