/**************************************************************************** ** ** Copyright (C) 2010 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$ ** No Commercial Usage ** This file contains pre-release code and may not be distributed. ** You may use this file in accordance with the terms and conditions ** contained in the Technology Preview License Agreement accompanying ** this package. ** ** GNU Lesser General Public License Usage ** Alternatively, 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. ** ** If you have questions regarding the use of this file, please contact ** Nokia at qt-info@nokia.com. ** ** ** ** ** ** ** ** ** $QT_END_LICENSE$ ** ****************************************************************************/ #include "qtiffhandler.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; } // BitsPerSample defaults to 1 according to the TIFF spec. uint16 bitPerSample; if (!TIFFGetField(tiff, TIFFTAG_BITSPERSAMPLE, &bitPerSample)) bitPerSample = 1; bool grayscale = photometric == PHOTOMETRIC_MINISBLACK || photometric == PHOTOMETRIC_MINISWHITE; if (grayscale && bitPerSample == 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) && bitPerSample == 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 = static_cast<uint16 *>(qMalloc(tableSize * sizeof(uint16))); uint16 *greenTable = static_cast<uint16 *>(qMalloc(tableSize * sizeof(uint16))); uint16 *blueTable = static_cast<uint16 *>(qMalloc(tableSize * sizeof(uint16))); if (!redTable || !greenTable || !blueTable) { TIFFClose(tiff); return false; } if (!TIFFGetField(tiff, TIFFTAG_COLORMAP, &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) { 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); } 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