Rewrite the PIC image format handler

It now uses QDataStream to deal with endianness. It also supports
several QImageIOHandler options.

This comes with a more comprehensive test suite than the old code. Note
that the old test suite was incorrect as the old code wrote the floats
in the header out incorrectly (although no-one noticed because no
software seems to care about those values).

All the test PIC files in the test suite appear correct according to the
specification (by inspection with Okteta). Unfortunately, there is a
lack of other freely-available software that reads and writes PIC files
(the main application that uses them is proprietary), and so this is the
best I can do.

REVIEW: 117944
This commit is contained in:
Alex Merry 2014-03-03 12:52:40 +00:00
parent 74906dd3bc
commit 51eca9b6a8
31 changed files with 966 additions and 660 deletions

View File

@ -56,7 +56,6 @@ endmacro()
# result against the data read from the corresponding png file # result against the data read from the corresponding png file
kimageformats_read_tests( kimageformats_read_tests(
pcx pcx
pic
psd psd
ras ras
rgb rgb
@ -101,3 +100,16 @@ endif()
if (OpenEXR_FOUND) if (OpenEXR_FOUND)
# FIXME: OpenEXR tests # FIXME: OpenEXR tests
endif() endif()
find_package(Qt5Test ${REQUIRED_QT_VERSION} CONFIG QUIET)
if(NOT Qt5Test_FOUND)
message(STATUS "Qt5Test not found, some autotests will not be built.")
return()
endif()
add_executable(pictest pictest.cpp)
target_link_libraries(pictest Qt5::Gui Qt5::Test)
ecm_mark_as_test(pictest)
add_test(NAME kimageformats-pic COMMAND pictest)

BIN
autotests/long-runs.pic Normal file

Binary file not shown.

Binary file not shown.

BIN
autotests/pic/4x4-alpha.pic Normal file

Binary file not shown.

BIN
autotests/pic/4x4-alpha.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 98 B

Binary file not shown.

Binary file not shown.

Binary file not shown.

After

Width:  |  Height:  |  Size: 90 B

Binary file not shown.

BIN
autotests/pic/long-runs.pic Normal file

Binary file not shown.

BIN
autotests/pic/long-runs.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 117 B

Binary file not shown.

277
autotests/pictest.cpp Normal file
View File

@ -0,0 +1,277 @@
/*
* Copyright 2014 Alex Merry <alex.merry@kdemail.net>
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) version 3, or any
* later version accepted by the membership of KDE e.V. (or its
* successor approved by the membership of KDE e.V.), which shall
* act as a proxy defined in Section 6 of version 3 of the license.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library. If not, see
* <http://www.gnu.org/licenses/>.
*/
#include <stdio.h>
#include <QBuffer>
#include <QFile>
#include <QFileInfo>
#include <QImage>
#include <QImageReader>
#include <QImageWriter>
#include <QTest>
#include <QUuid>
Q_DECLARE_METATYPE(QImage::Format)
class PicTests : public QObject
{
Q_OBJECT
private:
void common_data()
{
QTest::addColumn<QString>("picfile");
QTest::addColumn<QString>("pngfile");
QTest::addColumn<QString>("comment");
// whether the pic file has/should have an alpha channel
QTest::addColumn<bool>("alpha");
// the format to convert the png file to before writing
// or comparing to the read image; this can be used to
// induce loss of data (eg: make the image monochrome)
QTest::addColumn<QImage::Format>("pngformat");
QTest::addColumn<bool>("compress");
QTest::newRow("4x4 no alpha RLE")
<< QFINDTESTDATA("pic/4x4-simple-color.pic")
<< QFINDTESTDATA("pic/4x4-simple-color.png")
<< QString()
<< false
<< QImage::Format_RGB32
<< true;
QTest::newRow("4x4 no alpha raw")
<< QFINDTESTDATA("pic/4x4-simple-color-uncompressed.pic")
<< QFINDTESTDATA("pic/4x4-simple-color.png")
<< QString()
<< false
<< QImage::Format_RGB32
<< false;
QTest::newRow("Short comment")
<< QFINDTESTDATA("pic/short-comment.pic")
<< QFINDTESTDATA("pic/4x4-simple-color.png")
<< QStringLiteral("Test comment value")
<< false
<< QImage::Format_RGB32
<< true;
QTest::newRow("Long comment")
<< QFINDTESTDATA("pic/long-comment.pic")
<< QFINDTESTDATA("pic/4x4-simple-color.png")
<< QStringLiteral("Test comment value that goes right up to the end of the comment field and has no")
<< false
<< QImage::Format_RGB32
<< true;
QTest::newRow("Long run-lengths")
<< QFINDTESTDATA("pic/long-runs.pic")
<< QFINDTESTDATA("pic/long-runs.png")
<< QString()
<< false
<< QImage::Format_RGB32
<< true;
QTest::newRow("4x4 with alpha RLE")
<< QFINDTESTDATA("pic/4x4-alpha.pic")
<< QFINDTESTDATA("pic/4x4-alpha.png")
<< QString()
<< true
<< QImage::Format_ARGB32
<< true;
QTest::newRow("4x4 with alpha raw")
<< QFINDTESTDATA("pic/4x4-alpha-uncompressed.pic")
<< QFINDTESTDATA("pic/4x4-alpha.png")
<< QString()
<< true
<< QImage::Format_ARGB32
<< false;
}
private Q_SLOTS:
void initTestCase()
{
QCoreApplication::addLibraryPath(QStringLiteral(PLUGIN_DIR));
}
void testWrite_data()
{
common_data();
// NB: 4x4-simple-color only uses solid red, blue, green and white,
// so there is no actual data loss in converting to RGB16.
// This just tests that the pic plugin can deal with different
// input formats.
QTest::newRow("altered format")
<< QFINDTESTDATA("pic/4x4-simple-color.pic")
<< QFINDTESTDATA("pic/4x4-simple-color.png")
<< QString()
<< false
<< QImage::Format_RGB16
<< true;
}
void testRead_data()
{
common_data();
// TODO: test reading files with unusual channel setups
// (eg: one channel for each component)
}
void testWrite()
{
QFETCH(QString, picfile);
QFETCH(QString, pngfile);
QFETCH(QString, comment);
QFETCH(QImage::Format, pngformat);
QFETCH(bool, compress);
QImageReader pngReader(pngfile, "png");
QImage pngImage;
QVERIFY2(pngReader.read(&pngImage), qPrintable(pngReader.errorString()));
pngImage = pngImage.convertToFormat(pngformat);
QFile expFile(picfile);
QVERIFY2(expFile.open(QIODevice::ReadOnly), qPrintable(expFile.errorString()));
QByteArray expData = expFile.readAll();
QByteArray picData;
QBuffer buffer(&picData);
QImageWriter imgWriter(&buffer, "pic");
imgWriter.setText(QStringLiteral("Description"), comment);
imgWriter.setCompression(compress);
imgWriter.write(pngImage);
if (expData != picData) {
QString fileNameBase = QUuid::createUuid().toString()
.remove(QLatin1Char('{'))
.remove(QLatin1Char('}'));
QFile dumpFile(fileNameBase + QStringLiteral(".pic"));
QVERIFY2(dumpFile.open(QIODevice::WriteOnly), qPrintable(dumpFile.errorString()));
dumpFile.write(picData);
QString msg = QStringLiteral("Written data (")
+ dumpFile.fileName()
+ QStringLiteral(") differed from expected data (")
+ picfile
+ QLatin1Char(')');
QFAIL(qPrintable(msg));
}
}
void testRead()
{
QFETCH(QString, picfile);
QFETCH(QString, pngfile);
QFETCH(bool, alpha);
QFETCH(QImage::Format, pngformat);
QImageReader inputReader(picfile, "pic");
QImageReader expReader(pngfile, "png");
QImage inputImage;
QImage expImage;
QVERIFY2(expReader.read(&expImage), qPrintable(expReader.errorString()));
QVERIFY2(inputReader.read(&inputImage), qPrintable(inputReader.errorString()));
QCOMPARE(inputImage.width(), expImage.width());
QCOMPARE(inputImage.height(), expImage.height());
QCOMPARE(inputImage.hasAlphaChannel(), alpha);
QCOMPARE(inputImage.format(), alpha ? QImage::Format_ARGB32
: QImage::Format_RGB32);
expImage = expImage.convertToFormat(pngformat);
expImage = expImage.convertToFormat(alpha ? QImage::Format_ARGB32
: QImage::Format_RGB32);
if (inputImage != expImage) {
QString fileNameBase = QUuid::createUuid().toString()
.remove(QLatin1Char('{'))
.remove(QLatin1Char('}'));
QFile picDumpFile(fileNameBase + QStringLiteral("-expected.data"));
QVERIFY2(picDumpFile.open(QIODevice::WriteOnly), qPrintable(picDumpFile.errorString()));
picDumpFile.write(reinterpret_cast<const char *>(inputImage.bits()),
inputImage.byteCount());
QFile pngDumpFile(fileNameBase + QStringLiteral("-actual.data"));
QVERIFY2(pngDumpFile.open(QIODevice::WriteOnly), qPrintable(pngDumpFile.errorString()));
pngDumpFile.write(reinterpret_cast<const char *>(expImage.bits()),
expImage.byteCount());
QString msg = QStringLiteral("Read image (")
+ picDumpFile.fileName()
+ QStringLiteral(") differed from expected image (")
+ pngDumpFile.fileName()
+ QLatin1Char(')');
QFAIL(qPrintable(msg));
}
}
void testPreReadComment_data()
{
testRead_data();
}
void testPreReadComment()
{
QFETCH(QString, picfile);
QFETCH(QString, comment);
QImageReader inputReader(picfile, "pic");
QCOMPARE(inputReader.text(QStringLiteral("Description")), comment);
}
void testPreReadSize_data()
{
testRead_data();
}
void testPreReadSize()
{
QFETCH(QString, picfile);
QFETCH(QString, pngfile);
QImageReader inputReader(picfile, "pic");
QImageReader expReader(pngfile, "png");
QCOMPARE(inputReader.size(), expReader.size());
}
void testPreReadImageFormat_data()
{
testRead_data();
}
void testPreReadImageFormat()
{
QFETCH(QString, picfile);
QFETCH(bool, alpha);
QImageReader inputReader(picfile, "pic");
QCOMPARE(inputReader.imageFormat(),
alpha ? QImage::Format_ARGB32 : QImage::Format_RGB32);
}
};
QTEST_MAIN(PicTests)
#include "pictest.moc"

Binary file not shown.

Binary file not shown.

Before

Width:  |  Height:  |  Size: 743 B

Binary file not shown.

Binary file not shown.

Before

Width:  |  Height:  |  Size: 574 B

Binary file not shown.

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.0 KiB

Binary file not shown.

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@ -57,7 +57,7 @@ install(FILES pcx.desktop DESTINATION ${SERVICES_INSTALL_DIR}/qimageioplugins/)
################################## ##################################
add_library(kimg_pic MODULE pic_read.cpp pic_write.cpp pic.cpp) add_library(kimg_pic MODULE pic.cpp)
target_link_libraries(kimg_pic Qt5::Gui) target_link_libraries(kimg_pic Qt5::Gui)
install(TARGETS kimg_pic DESTINATION ${QT_PLUGIN_INSTALL_DIR}/imageformats/) install(TARGETS kimg_pic DESTINATION ${QT_PLUGIN_INSTALL_DIR}/imageformats/)

View File

@ -1,6 +1,8 @@
/** /**
* PIC_RW - Qt PIC Support * Softimage PIC support for QImage
* Copyright (C) 2007 Ruben Lopez <r.lopez@bren.es> * Copyright 1998 Halfdan Ingvarsson
* Copyright 2007 Ruben Lopez <r.lopez@bren.es>
* Copyright 2014 Alex Merry <alex.merry@kde.org>
* *
* This library is free software; you can redistribute it and/or * This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public * modify it under the terms of the GNU Lesser General Public
@ -18,12 +20,340 @@
* ---------------------------------------------------------------------------- * ----------------------------------------------------------------------------
*/ */
#include "pic.h" /* This code is based on the GIMP-PIC plugin by Halfdan Ingvarsson,
#include "pic_rw.h" * and relicensed from GPL to LGPL to accommodate the KDE licensing policy
* with his permission.
*/
#include <QVariant> #include "pic.h"
#include <QDataStream>
#include <QDebug>
#include <QImage> #include <QImage>
#include <iostream> #include <QVariant>
#include <qendian.h>
#include <algorithm>
#include <functional>
/**
* Reads a PIC file header from a data stream.
*
* @param s The data stream to read from.
* @param channels Where the read header will be stored.
* @returns @p s
*
* @relates PicHeader
*/
static QDataStream &operator>> (QDataStream &s, PicHeader &header)
{
s.setFloatingPointPrecision(QDataStream::SinglePrecision);
s >> header.magic;
s >> header.version;
// the comment should be truncated to the first null byte
char comment[81] = {};
s.readRawData(comment, 80);
header.comment = QByteArray(comment);
header.id.resize(4);
s.readRawData(header.id.data(), 4);
s >> header.width;
s >> header.height;
s >> header.ratio;
qint16 fields;
s >> fields;
header.fields = static_cast<PicFields>(fields);
qint16 pad;
s >> pad;
return s;
}
/**
* Writes a PIC file header to a data stream.
*
* @param s The data stream to write to.
* @param channels The header to write.
* @returns @p s
*
* @relates PicHeader
*/
static QDataStream &operator<< (QDataStream &s, const PicHeader &header)
{
s.setFloatingPointPrecision(QDataStream::SinglePrecision);
s << header.magic;
s << header.version;
char comment[80] = {};
strncpy(comment, header.comment.constData(), sizeof(comment));
s.writeRawData(comment, sizeof(comment));
char id[4] = {};
strncpy(id, header.id.constData(), sizeof(id));
s.writeRawData(id, sizeof(id));
s << header.width;
s << header.height;
s << header.ratio;
s << quint16(header.fields);
s << quint16(0);
return s;
}
/**
* Reads a series of channel descriptions from a data stream.
*
* If the stream contains more than 8 channel descriptions, the status of @p s
* will be set to QDataStream::ReadCorruptData (note that more than 4 channels
* - one for each component - does not really make sense anyway).
*
* @param s The data stream to read from.
* @param channels The location to place the read channel descriptions; any
* existing entries will be cleared.
* @returns @p s
*
* @relates PicChannel
*/
static QDataStream &operator>> (QDataStream &s, QList<PicChannel> &channels)
{
const unsigned maxChannels = 8;
unsigned count = 0;
quint8 chained = 1;
channels.clear();
while (chained && count < maxChannels && s.status() == QDataStream::Ok) {
PicChannel channel;
s >> chained;
s >> channel.size;
quint8 encoding;
s >> encoding;
channel.encoding = PicChannelEncoding(encoding);
s >> channel.code;
channels << channel;
++count;
}
if (chained) {
// too many channels!
s.setStatus(QDataStream::ReadCorruptData);
}
return s;
}
/**
* Writes a series of channel descriptions to a data stream.
*
* Note that the corresponding read operation will not read more than 8 channel
* descriptions, although there should be no reason to have more than 4 channels
* anyway.
*
* @param s The data stream to write to.
* @param channels The channel descriptions to write.
* @returns @p s
*
* @relates PicChannel
*/
static QDataStream &operator<< (QDataStream &s, const QList<PicChannel> &channels)
{
Q_ASSERT(channels.size() > 0);
for (int i = 0; i < channels.size() - 1; ++i) {
s << quint8(1); // chained
s << channels[i].size;
s << quint8(channels[i].encoding);
s << channels[i].code;
}
s << quint8(0); // chained
s << channels.last().size;
s << quint8(channels.last().encoding);
s << channels.last().code;
return s;
}
/**
* Decodes data written in mixed run-length encoding format.
*
* This is intended to be used with lambda functions.
*
* Note that this functions expects that, at the current location in @p stream,
* exactly @p length items have been encoded as a unit (and so it will not be
* partway through a run when it has decoded @p length items). If this is not
* the case, it will return @c false.
*
* @param stream The stream to read the data from.
* @param data The location to write the data.
* @param length The number of items to read.
* @param readItem A function that takes a QDataStream reference and reads a
* single item.
* @param updateItem A function that takes an item from @p data and an item
* read by @p readItem and produces the item that should be
* written to @p data.
*
* @returns @c true if @p length items in mixed RLE were successfully read
* into @p data, @c false otherwise.
*/
template<typename Item, typename Func1, typename Func2>
static bool decodeMixedRLEData(QDataStream &stream,
Item *data,
quint16 length,
Func1 readItem,
Func2 updateItem)
{
unsigned offset = 0; // in data
while (offset < length) {
unsigned remaining = length - offset;
quint8 count1;
stream >> count1;
if (count1 >= 128u) {
unsigned length;
if (count1 == 128u) {
// If the value is exactly 128, it means that it is more than
// 127 repetitions
quint16 count2;
stream >> count2;
length = count2;
} else {
// If last bit is 1, then it is 2 to 127 repetitions
length = count1 - 127u;
}
if (length > remaining) {
qDebug() << "Row overrun:" << length << ">" << remaining;
return false;
}
Item item = readItem(stream);
for (unsigned i = offset; i < offset + length; ++i) {
data[i] = updateItem(data[i], item);
}
offset += length;
} else {
// No repetitions
unsigned length = count1 + 1u;
if (length > remaining) {
qDebug() << "Row overrun:" << length << ">" << remaining;
return false;
}
for (unsigned i = offset; i < offset + length; ++i) {
Item item = readItem(stream);
data[i] = updateItem(data[i], item);
}
offset += length;
}
}
if (stream.status() != QDataStream::Ok) {
qDebug() << "DataStream status was" << stream.status();;;;
}
return stream.status() == QDataStream::Ok;
}
static bool readRow(QDataStream &stream, QRgb *row, quint16 width, QList<PicChannel> channels)
{
Q_FOREACH(const PicChannel &channel, channels) {
auto readPixel = [&] (QDataStream &str) -> QRgb {
quint8 red = 0;
if (channel.code & RED) {
str >> red;
}
quint8 green = 0;
if (channel.code & GREEN) {
str >> green;
}
quint8 blue = 0;
if (channel.code & BLUE) {
str >> blue;
}
quint8 alpha = 0;
if (channel.code & ALPHA) {
str >> alpha;
}
return qRgba(red, green, blue, alpha);
};
auto updatePixel = [&] (QRgb oldPixel, QRgb newPixel) -> QRgb {
return qRgba(
qRed((channel.code & RED) ? newPixel : oldPixel),
qGreen((channel.code & GREEN) ? newPixel : oldPixel),
qBlue((channel.code & BLUE) ? newPixel : oldPixel),
qAlpha((channel.code & ALPHA) ? newPixel : oldPixel));
};
if (channel.encoding == MixedRLE) {
if (!decodeMixedRLEData(stream, row, width, readPixel, updatePixel)) {
qDebug() << "decodeMixedRLEData failed";
return false;
}
} else if (channel.encoding == Uncompressed) {
for (quint16 i = 0; i < width; ++i) {
QRgb pixel = readPixel(stream);
row[i] = updatePixel(row[i], pixel);
}
} else {
// unknown encoding
qDebug() << "Unknown encoding";
return false;
}
}
if (stream.status() != QDataStream::Ok) {
qDebug() << "DataStream status was" << stream.status();
}
return stream.status() == QDataStream::Ok;
}
/**
* Encodes data in mixed run-length encoding format.
*
* This is intended to be used with lambda functions.
*
* @param stream The stream to write the data to.
* @param data The data to be written.
* @param length The number of items to write.
* @param itemsEqual A function that takes two items and returns whether
* @p writeItem would write them identically.
* @param writeItem A function that takes a QDataStream reference and an item
* and writes the item to the data stream.
*/
template<typename Item, typename Func1, typename Func2>
static void encodeMixedRLEData(QDataStream &stream, const Item *data, unsigned length, Func1 itemsEqual, Func2 writeItem)
{
unsigned offset = 0;
while (offset < length) {
const Item *chunkStart = data + offset;
unsigned maxChunk = qMin(length - offset, 65535u);
const Item *chunkEnd = chunkStart + 1;
quint16 chunkLength = 1;
while (chunkLength < maxChunk && itemsEqual(*chunkStart, *chunkEnd)) {
++chunkEnd;
++chunkLength;
}
if (chunkLength > 127) {
// Sequence of > 127 identical pixels
stream << quint8(128);
stream << quint16(chunkLength);
writeItem(stream, *chunkStart);
} else if (chunkLength > 1) {
// Sequence of < 128 identical pixels
stream << quint8(chunkLength + 127);
writeItem(stream, *chunkStart);
} else {
// find a string of up to 128 values, each different from the one
// that follows it
if (maxChunk > 128) {
maxChunk = 128;
}
chunkLength = 1;
chunkEnd = chunkStart + 1;
while (chunkLength < maxChunk &&
(chunkLength + 1 == maxChunk ||
!itemsEqual(*chunkEnd, *(chunkEnd+1))))
{
++chunkEnd;
++chunkLength;
}
stream << quint8(chunkLength - 1);
for (unsigned i = 0; i < chunkLength; ++i) {
writeItem(stream, *(chunkStart + i));
}
}
offset += chunkLength;
}
}
bool SoftimagePICHandler::canRead() const bool SoftimagePICHandler::canRead() const
{ {
@ -36,43 +366,211 @@ bool SoftimagePICHandler::canRead() const
bool SoftimagePICHandler::read(QImage *image) bool SoftimagePICHandler::read(QImage *image)
{ {
pic_read(device(), image); if (!readChannels()) {
return false;
}
QImage::Format fmt = QImage::Format_RGB32;
Q_FOREACH(const PicChannel &channel, m_channels) {
if (channel.size != 8) {
// we cannot read images that do not come in bytes
qDebug() << "Channel size was" << channel.size;
m_state = Error;
return false;
}
if (channel.code & ALPHA) {
fmt = QImage::Format_ARGB32;
}
}
QImage img(m_header.width, m_header.height, fmt);
img.fill(qRgb(0,0,0));
for (int y = 0; y < m_header.height; y++) {
QRgb *row = reinterpret_cast<QRgb*>(img.scanLine(y));
if (!readRow(m_dataStream, row, m_header.width, m_channels)) {
qDebug() << "readRow failed";
m_state = Error;
return false;
}
}
*image = img;
m_state = Ready;
return true; return true;
} }
bool SoftimagePICHandler::write(const QImage &image) bool SoftimagePICHandler::write(const QImage &_image)
{ {
pic_write(device(), &image); bool alpha = _image.hasAlphaChannel();
return true; const QImage image = _image.convertToFormat(
alpha ? QImage::Format_ARGB32
: QImage::Format_RGB32);
if (image.width() < 0 || image.height() < 0) {
qDebug() << "Image size invalid:" << image.width() << image.height();
return false;
}
if (image.width() > 65535 || image.height() > 65535) {
qDebug() << "Image too big:" << image.width() << image.height();
// there are only two bytes for each dimension
return false;
}
QDataStream stream(device());
stream << PicHeader(image.width(), image.height(), m_description);
PicChannelEncoding encoding = m_compression ? MixedRLE : Uncompressed;
QList<PicChannel> channels;
channels << PicChannel(encoding, RED | GREEN | BLUE);
if (alpha) {
channels << PicChannel(encoding, ALPHA);
}
stream << channels;
for (int r = 0; r < image.height(); r++) {
const QRgb *row = reinterpret_cast<const QRgb*>(image.scanLine(r));
/* Write the RGB part of the scanline */
auto rgbEqual = [] (QRgb p1, QRgb p2) -> bool {
return qRed(p1) == qRed(p2) &&
qGreen(p1) == qGreen(p2) &&
qBlue(p1) == qBlue(p2);
};
auto writeRgb = [] (QDataStream &str, QRgb pixel) -> void {
str << quint8(qRed(pixel))
<< quint8(qGreen(pixel))
<< quint8(qBlue(pixel));
};
if (m_compression) {
encodeMixedRLEData(stream, row, image.width(), rgbEqual, writeRgb);
} else {
for (int i = 0; i < image.width(); ++i) {
writeRgb(stream, row[i]);
}
}
/* Write the alpha channel */
if (alpha) {
auto alphaEqual = [] (QRgb p1, QRgb p2) -> bool {
return qAlpha(p1) == qAlpha(p2);
};
auto writeAlpha = [] (QDataStream &str, QRgb pixel) -> void {
str << quint8(qAlpha(pixel));
};
if (m_compression) {
encodeMixedRLEData(stream, row, image.width(), alphaEqual, writeAlpha);
} else {
for (int i = 0; i < image.width(); ++i) {
writeAlpha(stream, row[i]);
}
}
}
}
return stream.status() == QDataStream::Ok;
} }
bool SoftimagePICHandler::canRead(QIODevice *device) bool SoftimagePICHandler::canRead(QIODevice *device)
{ {
PICHeader hdr; char data[4];
if (picReadHeader(device, &hdr, true)) { if (device->peek(data, 4) != 4) {
if (strncmp(hdr.id, "PICT", 4) == 0) { return false;
return true; }
return qFromBigEndian<qint32>(reinterpret_cast<uchar*>(data)) == PIC_MAGIC_NUMBER;
}
bool SoftimagePICHandler::readHeader()
{
if (m_state == Ready) {
m_state = Error;
m_dataStream.setDevice(device());
m_dataStream >> m_header;
if (m_header.isValid() && m_dataStream.status() == QDataStream::Ok) {
m_state = ReadHeader;
} }
} }
return false; return m_state != Error;
}
bool SoftimagePICHandler::readChannels()
{
readHeader();
if (m_state == ReadHeader) {
m_state = Error;
m_dataStream >> m_channels;
if (m_dataStream.status() == QDataStream::Ok) {
m_state = ReadChannels;
}
}
return m_state != Error;
}
void SoftimagePICHandler::setOption(ImageOption option, const QVariant &value)
{
switch (option) {
case CompressionRatio:
m_compression = value.toBool();
break;
case Description: {
m_description.clear();
QStringList entries = value.toString().split(QStringLiteral("\n\n"));
Q_FOREACH(const QString entry, entries) {
if (entry.startsWith(QStringLiteral("Description: "))) {
m_description = entry.mid(13).simplified().toUtf8();
}
}
break;
}
default:
break;
}
} }
QVariant SoftimagePICHandler::option(ImageOption option) const QVariant SoftimagePICHandler::option(ImageOption option) const
{ {
if (option == Size) { const_cast<SoftimagePICHandler*>(this)->readHeader();
PICHeader hdr; switch (option) {
if (picReadHeader(device(), &hdr, true)) { case Size:
return QSize(hdr.width, hdr.height); if (const_cast<SoftimagePICHandler*>(this)->readHeader()) {
} else { return QSize(m_header.width, m_header.height);
return QSize(-1, -1); } else {
} return QVariant();
}
case CompressionRatio:
return m_compression;
case Description:
if (const_cast<SoftimagePICHandler*>(this)->readHeader()) {
QString descStr = QString::fromUtf8(m_header.comment);
if (!descStr.isEmpty()) {
return QString(QStringLiteral("Description: ") +
descStr +
QStringLiteral("\n\n"));
}
}
return QString();
case ImageFormat:
if (const_cast<SoftimagePICHandler*>(this)->readChannels()) {
Q_FOREACH (const PicChannel &channel, m_channels) {
if (channel.code & ALPHA) {
return QImage::Format_ARGB32;
}
}
return QImage::Format_RGB32;
}
return QVariant();
default:
return QVariant();
} }
return QVariant();
} }
bool SoftimagePICHandler::supportsOption(ImageOption option) const bool SoftimagePICHandler::supportsOption(ImageOption option) const
{ {
return (option == Size); return (option == CompressionRatio ||
option == Description ||
option == ImageFormat ||
option == Size);
} }
QImageIOPlugin::Capabilities SoftimagePICPlugin::capabilities(QIODevice *device, const QByteArray &format) const QImageIOPlugin::Capabilities SoftimagePICPlugin::capabilities(QIODevice *device, const QByteArray &format) const

View File

@ -23,17 +23,165 @@
#include <QImageIOPlugin> #include <QImageIOPlugin>
/**
* The magic number at the start of a SoftImage PIC file.
*/
static const qint32 PIC_MAGIC_NUMBER = 0x5380f634;
/**
* How fields are distributed over the image.
*
* This information is not used by this image format code.
*/
enum PicFields {
NoPicture = 0, /**< No picture */
OddScanlines = 1, /**< Odd scanlines */
EvenScanlines = 2, /**< Even scanlines */
BothScanlines = 3 /**< Every scanline */
};
/**
* How the data for a channel is encoded.
*/
enum PicChannelEncoding {
Uncompressed = 0, /**< Image is uncompressed */
MixedRLE = 2 /**< Run length compression */
};
/**
* What components are encoded in a channel.
*/
enum PicChannelCode {
RED = 0x80, /**< Red channel */
GREEN = 0x40, /**< Green channel */
BLUE = 0x20, /**< Blue channel */
ALPHA = 0x10 /**< Alpha channel */
};
/**
* The header for a SoftImage PIC file.
*/
struct PicHeader {
/**
* Construct a valid header for a SoftImage PIC file.
*
* Note that the comment will be truncated to 80 bytes when written.
*
* @param _width The width of the image in pixels
* @param _height The height of the image in pixels
* @param _comment A comment to add to the image
*/
PicHeader(quint16 _width, quint16 _height, const QByteArray &_comment = QByteArray())
: magic(PIC_MAGIC_NUMBER)
, version(3.71f)
, comment(_comment)
, id("PICT")
, width(_width)
, height(_height)
, ratio(1.0f)
, fields(BothScanlines)
{}
/** Construct an invalid header. */
PicHeader() {}
quint32 magic; /**< Should be PIC_MAGIC_NUMBER */
float version; /**< Version of something (header? file format?) (ignored) */
QByteArray comment; /**< A free comment field (truncated to 80 bytes when
written) */
QByteArray id; /**< The file format ID (should be "PICT") */
quint16 width; /**< The width of the image in pixels */
quint16 height; /**< The height of the image in pixels */
float ratio; /**< The aspect ratio: width/height of each individual pixel
(ignored) */
PicFields fields; /**< The interlace type (ignored) */
/**
* Returns true if the @p magic and @p id fields are set correctly.
*/
bool isValid() const {
return magic == PIC_MAGIC_NUMBER
&& id == "PICT";
}
/**
* The length of the encoded data, in bytes.
*/
static const qint64 encodedLength = 104;
};
/**
* Describes a channel in a SoftImage PIC file.
*/
struct PicChannel {
quint8 size; /**< Bits per component per pixel. */
PicChannelEncoding encoding; /**< How the channel's data is encoded. */
quint8 code; /**< Flag field to describe which components are encoded in
this channel. */
/**
* Constructs a channel description for a SoftImage PIC file.
*
* @param _encoding How the channel's data is or will be encoded.
* @param _code What components are or will be encoded by this
* channel.
* @param _size The number of bits used to encoded a single component
* for a single pixel in this channel (should be 8).
*/
PicChannel(PicChannelEncoding _encoding, quint8 _code, quint8 _size = 8)
: size(_size)
, encoding(_encoding)
, code(_code)
{}
/**
* Constructs a default channel description for a SoftImage PIC file.
*
* This will have size set to 8, encoding set to Uncompressed and the code
* set to 0 (so that the channel does not encode any information).
*
* The result of this should not be written to a file without setting the
* encoding and channel fields correctly.
*/
PicChannel()
: size(8)
{}
};
class SoftimagePICHandler : public QImageIOHandler class SoftimagePICHandler : public QImageIOHandler
{ {
public: public:
virtual bool canRead() const; bool canRead() const Q_DECL_OVERRIDE;
virtual bool read(QImage *image); bool read(QImage *image) Q_DECL_OVERRIDE;
virtual bool write(const QImage &); bool write(const QImage &) Q_DECL_OVERRIDE;
virtual QVariant option(ImageOption option) const; QVariant option(ImageOption option) const Q_DECL_OVERRIDE;
virtual bool supportsOption(ImageOption option) const; void setOption(ImageOption option, const QVariant &value) Q_DECL_OVERRIDE;
bool supportsOption(ImageOption option) const Q_DECL_OVERRIDE;
static bool canRead(QIODevice *device); static bool canRead(QIODevice *device);
enum State {
Error,
Ready,
ReadHeader,
ReadChannels
};
SoftimagePICHandler()
: m_state(Ready)
, m_compression(true)
{}
bool readHeader();
bool readChannels();
private:
State m_state;
QDataStream m_dataStream;
PicHeader m_header;
QList<PicChannel> m_channels;
// mostly used for writing:
bool m_compression;
QByteArray m_description;
}; };
class SoftimagePICPlugin : public QImageIOPlugin class SoftimagePICPlugin : public QImageIOPlugin

View File

@ -1,293 +0,0 @@
/**
* PIC_RW - Qt PIC Support
* Copyright (C) 2007 Ruben Lopez <r.lopez@bren.es>
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
* ----------------------------------------------------------------------------
*/
/* This code is based on the GIMP-PIC plugin by Halfdan Ingvarsson,
* and relicensed from GPL to LGPL to accommodate the KDE licensing policy
* with his permission.
* These is the original copyright:
* Copyright (C) 1998 Halfdan Ingvarsson
*/
#include "pic_rw.h"
#include <qendian.h>
#include <iostream>
#include <qimage.h>
#include <algorithm>
/**
* Reads the PIC header and checks that it is OK
* @param dev The QT device to read from
* @param hdr A pointer to the PIC header
* @param peek Keep bytes in the device
* @return true on success
*/
bool picReadHeader(QIODevice *dev, PICHeader *hdr, bool peek)
{
int result = 0;
if (peek) {
result = dev->peek((char *) hdr, HEADER_SIZE);
} else {
result = dev->read((char *) hdr, HEADER_SIZE);
}
hdr->magic = qFromBigEndian(hdr->magic);
hdr->width = qFromBigEndian(hdr->width);
hdr->height = qFromBigEndian(hdr->height);
hdr->fields = qFromBigEndian(hdr->fields);
if (hdr->magic != PIC_MAGIC_NUMBER || strncmp(hdr->id, "PICT", 4)) {
return false;
}
return result == HEADER_SIZE;
}
#define CHANNEL_BYTE(ch, mask) (( ch & mask) ? 1 : 0)
/**
* Gets the channels definition and returns the number of bytes per pixel
* @param channels The channels bitfield
* @return The number of bytes per pixel
*/
static int channels2bpp(char channels)
{
return CHANNEL_BYTE(channels, RED)
+ CHANNEL_BYTE(channels, GREEN)
+ CHANNEL_BYTE(channels, BLUE)
+ CHANNEL_BYTE(channels, ALPHA);
}
/**
* Reads the channels info
* @param dev The QT device to read from
* @param channels A pointer to 8 channels
* @return true on success
*/
static bool readChannels(QIODevice *dev, PICChannel *channels, int &bpp)
{
int c = 0;
memset(channels, 0, sizeof(PICChannel) * 8);
do {
int result = dev->read((char *) & channels[c], CHANNEL_SIZE);
if (result != CHANNEL_SIZE) {
return false;
} else {
bpp += channels2bpp(channels[c].channel);
c++;
}
} while (channels[c - 1].chained);
return true;
}
/**
* Makes a component map based on the channels info
* @param channels The channel information
* @param cmap The component map to be built
*/
inline static void makeComponentMap(unsigned channel, unsigned char *cmap)
{
std::fill(cmap, cmap + 8, 0);
unsigned compos[] = {ALPHA, BLUE, GREEN, RED};
unsigned rgba[] = {3, 2, 1, 0};
unsigned pos = 0;
for (unsigned compo = 0; compo < 4; compo++) {
if (CHANNEL_BYTE(channel, compos[compo])) {
cmap[pos++] = rgba[compo];
}
}
}
/**
* Converts a PIC pixel to 32bits RGBA
* @param src_pixel The source PIC pixel as readed from file
* @param target_pixel The target buffer where to write the pixel info
* @param cmap The component map that maps each component in PIC format to RGBA format
* @param components The number of components in the source pixel
*/
inline static void pic2RGBA(unsigned char *src_pixel, unsigned char *target_pixel, unsigned char *cmap, unsigned components)
{
for (unsigned i = 0; i < components; i++) {
target_pixel[cmap[i]] = src_pixel[i];
}
}
/**
* Counts the number of channels in the PICChannel header
* @param channels The header
* @return The number of used channels
*/
inline static unsigned getNumChannels(PICChannel *channels)
{
unsigned result = 0;
for (unsigned i = 0; i < 8; i++) {
if (channels[i].channel != 0) {
result++;
} else {
return result;
}
}
return result;
}
/**
* Decodes a Run-length encoded chunk
* @param dev The device to read from
* @param row The row pointer to write to
* @param max The maximum length to write
* @param channels The channels header
* @return The number of generated pixels
*/
static int decodeRLE(QIODevice *dev, void *row, unsigned max, unsigned bpp, unsigned channels)
{
unsigned char buf[512];
unsigned *ptr = (unsigned *) row;
unsigned char component_map[8];
unsigned len = 0;
makeComponentMap(channels, component_map);
if (dev->read((char *) buf, 1) != 1) {
return -1;
}
/* If last bit is 1, then it is 2 to 127 repetitions */
if (buf[0] > 128) {
len = buf[0] - 127;
if (len > max) {
return -1;
}
unsigned count = dev->read((char *) buf, bpp);
if (count != bpp) {
return -1;
}
for (unsigned i = 0; i < len; i++) {
pic2RGBA(buf, (unsigned char *)(ptr + i), component_map, bpp);
}
} /* If the value is exactly 10000000, it means that it is more than 127 repetitions */
else if (buf[0] == 128) {
unsigned count = dev->read((char *) buf, bpp + 2);
if (count != bpp + 2) {
return -1;
}
len = (buf[0] << 8) | buf[1];
if (len > max) {
return -1;
}
for (unsigned i = 0; i < len; i++) {
pic2RGBA(buf + 2, (unsigned char *)(ptr + i), component_map, bpp);
}
} /** No repetitions */
else {
len = buf[0] + 1;
if (len > max) {
return -1;
}
unsigned count = dev->read((char *) buf, len * bpp);
if (count != len * bpp) {
return -1;
}
for (unsigned i = 0; i < len; i++) {
pic2RGBA(buf + (i * bpp), (unsigned char *)(ptr + i), component_map, bpp);
}
}
return len;
}
/**
* Reads a row from the file
* @param dev The device to read from
* @param row The row pointer to write to
* @param width The image width
* @param bpp The bytes per pixel
* @param channels The channels header info
*/
static bool readRow(QIODevice *dev, unsigned *row, unsigned width, PICChannel *channels)
{
for (int c = 0; channels[c].channel != 0; c++) {
unsigned remain = width;
unsigned bpp = channels2bpp(channels[c].channel);
if (channels[c].type == (int) RLE) {
unsigned *rowpos = row;
while (remain > 0) {
int readed = decodeRLE(dev, rowpos, remain, bpp, channels[c].channel);
if (readed < 0) {
return false;
}
remain -= readed;
rowpos += readed;
}
} else {
unsigned char component_map[8];
unsigned count = dev->read((char *) row, width * bpp);
if (count != width * bpp) {
return false;
}
makeComponentMap(channels[c].channel, component_map);
for (unsigned i = 0; i < width; i++) {
pic2RGBA(((unsigned char *) row) + (i * bpp), (unsigned char *)(row + i), component_map, bpp);
}
}
}
return true;
}
#define FAIL() { \
std::cout << "ERROR Reading PIC!" << std::endl; \
return; \
}
bool hasAlpha(PICChannel *channels)
{
int channel = 0;
do {
if (CHANNEL_BYTE(channels[channel].channel, ALPHA)) {
return true;
}
channel++;
} while (channels[channel - 1].chained);
return false;
}
/**
* KDE image reading function. Must have this exact name in order to work
*/
void pic_read(QIODevice *dev, QImage *result)
{
PICHeader header;
PICChannel channels[8];
int bpp = 0;
if (!picReadHeader(dev, &header) || !readChannels(dev, channels, bpp)) {
FAIL();
}
QImage img(header.width, header.height, QImage::Format_ARGB32);
for (int r = 0; r < header.height; r++) {
unsigned *row = (unsigned *) img.scanLine(r);
std::fill(row, row + header.width, 0);
if (!readRow(dev, row, header.width, channels)) {
FAIL();
}
}
// img->setAlphaBuffer(hasAlpha(channels));
*result = img;
}

View File

@ -1,108 +0,0 @@
/**
* PIC_RW - Qt PIC Support
* Copyright (C) 2007 Ruben Lopez <r.lopez@bren.es>
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
* ----------------------------------------------------------------------------
*/
/* This code is based on the GIMP-PIC plugin by Halfdan Ingvarsson,
* and relicensed from GPL to LGPL to accommodate the KDE licensing policy
* with his permission.
* These is the original copyright:
* Copyright (C) 1998 Halfdan Ingvarsson
*/
#ifndef __PIC_RW_H__
#define __PIC_RW_H__
#define PIC_MAGIC_NUMBER 0x5380f634
#include <QtCore/QFile>
#include <QImageIOPlugin>
#include <QColor>
/**
* How fields are distributed over the image
*/
typedef enum {
NONE = 0, /* No picture */
ODD = 1, /* Odd scanlines */
EVEN = 2, /* Even scanlines */
BOTH = 3 /* Every scanline */
} PICFields;
/**
* Type of a channel
*/
typedef enum {
UNCOMPRESSED = 0, /* Image is uncompressed */
RLE = 2 /* Run length compression */
} PICChannelType;
/**
* Channel codes
*/
typedef enum {
RED = 0x80, /* Red channel */
GREEN = 0x40, /* Green channel */
BLUE = 0x20, /* Blue channel */
ALPHA = 0x10 /* Alpha channel */
} PICChannelCode;
/**
* PIC format header
*/
typedef struct {
qint32 magic; /* PIC_MAGIC_NUMBER */
float version; /* Version of format */
char comment[80]; /* Prototype description */
char id[4]; /* "PICT" */
qint16 width; /* Image width, in pixels */
qint16 height; /* Image height, in pixels */
float ratio; /* Pixel aspect ratio */
qint16 fields; /* Picture field type */
qint16 pad; /* Unused */
} PICHeader;
/**
* PIC channel header
*/
typedef struct {
char chained; /* 1 if another packet follows, else 0 */
char size; /* Bits per pixel by channel */
char type; /* RLE or uncompressed */
char channel; /* Channel code (which planes are affected by this channel) */
} PICChannel;
#define HEADER_SIZE sizeof(PICHeader)
#define CHANNEL_SIZE sizeof(PICChannel)
/**
* Reads the PIC header and checks that it is OK
* @param dev The QT device to read from
* @param hdr A pointer to the PIC header
* @param peek Keep bytes in the device
* @return true on success
*/
bool picReadHeader(QIODevice *dev, PICHeader *hdr, bool peek = false);
/// Pic read handler for Qt / KDE
void pic_read(QIODevice *dev, QImage *img);
/// Pic write handler for Qt / KDE
void pic_write(QIODevice *dev, const QImage *img);
#endif//__PIC_RW_H__

View File

@ -1,228 +0,0 @@
/**
* PIC_RW - Qt PIC Support
* Copyright (C) 2007 Ruben Lopez <r.lopez@bren.es>
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
* ----------------------------------------------------------------------------
*/
/* This code is based on the GIMP-PIC plugin by Halfdan Ingvarsson,
* and relicensed from GPL to LGPL to accommodate the KDE licensing policy
* with his permission.
* These is the original copyright:
* Copyright (C) 1998 Halfdan Ingvarsson
*/
#include "pic_rw.h"
#include <iostream>
#include <qimage.h>
#include <qendian.h>
/**
* Writes the PIC header info.
* @param dev IO Device
* @param msg Header message
* @param width Image width
* @param height Image height
* @param alpha Image has alpha?
* @return True on success
*/
static bool writeHeader(QIODevice *dev, std::string msg, unsigned width, unsigned height, bool alpha)
{
PICHeader h;
PICChannel c;
unsigned count = 0;
memset(&h, 0, sizeof(PICHeader));
h.magic = qToBigEndian<qint32>(PIC_MAGIC_NUMBER);
h.version = 3.71f;
strcpy(h.comment, msg.c_str());
strncpy(h.id, "PICT", 4);
h.width = qToBigEndian<qint16>(width);
h.height = qToBigEndian<qint16>(height);
h.ratio = 1.0f;
h.fields = qToBigEndian<qint16>(BOTH);
count = dev->write((const char *) & h, sizeof(PICHeader));
if (count != sizeof(PICHeader)) {
return false;
}
memset(&c, 0, sizeof(PICChannel));
c.size = 8;
c.type = RLE;
c.channel = RED | GREEN | BLUE;
if (alpha) {
c.chained = 1;
}
count = dev->write((const char *) & c, sizeof(PICChannel));
if (count != sizeof(PICChannel)) {
return false;
}
if (alpha) {
c.channel = ALPHA;
c.chained = 0;
count = dev->write((const char *) & c, sizeof(PICChannel));
if (count != sizeof(PICChannel)) {
return false;
}
}
return true;
}
inline unsigned convertABGRtoRGBA(unsigned pixel)
{
unsigned r = pixel & 0xFF;
unsigned g = (pixel >> 8) & 0xFF;
unsigned b = (pixel >> 16) & 0xFF;
unsigned a = (pixel >> 24) & 0xFF;
return a | (b << 8) | (g << 16) | (r << 24);
}
/**
* Encodes a portion of the image in RLE coding
* @param image The image that we want to encode
* @param output The output buffer
* @param channels The number of channels to write
* @param offset Offset in bytes to copy
* @param max The maximum number of pixels to write
* @param oConsumed The number of pixels consumed from image
* @param oProduced The number of bytes produced in out
* @return True on success
*/
static bool encodeRLE(const unsigned *image, unsigned char *output, bool rgb, unsigned max, unsigned &oConsumed, unsigned &oProduced)
{
const unsigned *in = image;
unsigned char *out = output;
unsigned count = 0;
unsigned channels = 3;
unsigned offset = 1;
unsigned mask = 0x00FFFFFF;
if (!rgb) {
channels = 1;
offset = 0;
mask = 0xFF000000;
}
for (; (*in & mask) == (*image & mask) && count < 65536 && count < max; in++, count++) {
}
if (count > 127) {
/* Sequence of > 127 identical pixels */
*out++ = 128;
*out++ = count >> 8;
*out++ = count & 0xFF;
unsigned pixel = convertABGRtoRGBA(*image);
memcpy(out, ((char *) & pixel) + offset, channels);
out += channels;
oConsumed = count;
oProduced = out - output;
} else if (count > 1) {
/* Sequece of < 128 identical pixels */
*out++ = (count + 127);
unsigned pixel = convertABGRtoRGBA(*image);
memcpy(out, ((char *) & pixel) + offset, channels);
out += channels;
oConsumed = count;
oProduced = out - output;
} else {
in = image + 1;
unsigned previous = *image;
count = 0;
while ((*in & mask) != (previous & mask) && count < 128 && count < max) {
previous = *in;
in++;
count++;
}
// This only happens when it is the end of the row, and it is ok
if (count == 0) {
count = 1;
}
*out++ = (count - 1);
in = image;
for (unsigned c = 0; c < count; ++c) {
unsigned pixel = convertABGRtoRGBA(*in);
memcpy(out, ((char *) & pixel) + offset, channels);
out += channels;
in++;
}
oConsumed = count;
oProduced = out - output;
}
return true;
}
/**
* Writes a row to the file
* @return True on success
*/
static bool writeRow(QIODevice *dev, unsigned *row, unsigned width, bool alpha)
{
unsigned char *buf = new unsigned char[width * 4];
unsigned posIn = 0;
unsigned posOut = 0;
memset(buf, 0, width * 4);
unsigned consumed = 0;
unsigned produced = 0;
/* Write the RGB part of the scanline */
while (posIn < width) {
if (!encodeRLE(row + posIn, buf + posOut, true, width - posIn, consumed, produced)) {
delete[] buf;
return false;
}
posIn += consumed;
posOut += produced;
}
/* Write the alpha channel */
if (alpha) {
posIn = 0;
while (posIn < width) {
if (!encodeRLE(row + posIn, buf + posOut, false, width - posIn, consumed, produced)) {
delete[] buf;
return false;
}
posIn += consumed;
posOut += produced;
}
}
dev->write((const char *) buf, posOut);
delete[] buf;
return true;
}
#define FAIL() { \
std::cout << "ERROR Writing PIC!" << std::endl; \
return; \
}
/// Pic write handler for Qt / KDE
void pic_write(QIODevice *dev, const QImage *img)
{
bool alpha = img->hasAlphaChannel();
if (!writeHeader(dev, "Created with KDE", img->width(), img->height(), alpha)) {
FAIL();
}
for (int r = 0; r < img->height(); r++) {
unsigned *row = (unsigned *) img->scanLine(r);
if (!writeRow(dev, row, img->width(), alpha)) {
FAIL();
}
}
}