/**************************************************************************** | |
** | |
** 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 |