diff --git a/autotests/CMakeLists.txt b/autotests/CMakeLists.txt index 86c540c..8a2209d 100644 --- a/autotests/CMakeLists.txt +++ b/autotests/CMakeLists.txt @@ -56,7 +56,6 @@ endmacro() # result against the data read from the corresponding png file kimageformats_read_tests( pcx - pic psd ras rgb @@ -101,3 +100,16 @@ endif() if (OpenEXR_FOUND) # FIXME: OpenEXR tests 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) diff --git a/autotests/long-runs.pic b/autotests/long-runs.pic new file mode 100644 index 0000000..620a3bb Binary files /dev/null and b/autotests/long-runs.pic differ diff --git a/autotests/pic/4x4-alpha-uncompressed.pic b/autotests/pic/4x4-alpha-uncompressed.pic new file mode 100644 index 0000000..e385b32 Binary files /dev/null and b/autotests/pic/4x4-alpha-uncompressed.pic differ diff --git a/autotests/pic/4x4-alpha.pic b/autotests/pic/4x4-alpha.pic new file mode 100644 index 0000000..fc3e43d Binary files /dev/null and b/autotests/pic/4x4-alpha.pic differ diff --git a/autotests/pic/4x4-alpha.png b/autotests/pic/4x4-alpha.png new file mode 100644 index 0000000..0ba8a1f Binary files /dev/null and b/autotests/pic/4x4-alpha.png differ diff --git a/autotests/pic/4x4-simple-color-uncompressed.pic b/autotests/pic/4x4-simple-color-uncompressed.pic new file mode 100644 index 0000000..6e59958 Binary files /dev/null and b/autotests/pic/4x4-simple-color-uncompressed.pic differ diff --git a/autotests/pic/4x4-simple-color.pic b/autotests/pic/4x4-simple-color.pic new file mode 100644 index 0000000..88b636f Binary files /dev/null and b/autotests/pic/4x4-simple-color.pic differ diff --git a/autotests/pic/4x4-simple-color.png b/autotests/pic/4x4-simple-color.png new file mode 100644 index 0000000..f8d5d43 Binary files /dev/null and b/autotests/pic/4x4-simple-color.png differ diff --git a/autotests/pic/long-comment.pic b/autotests/pic/long-comment.pic new file mode 100644 index 0000000..831521c Binary files /dev/null and b/autotests/pic/long-comment.pic differ diff --git a/autotests/pic/long-runs.pic b/autotests/pic/long-runs.pic new file mode 100644 index 0000000..99623e0 Binary files /dev/null and b/autotests/pic/long-runs.pic differ diff --git a/autotests/pic/long-runs.png b/autotests/pic/long-runs.png new file mode 100644 index 0000000..c718f7f Binary files /dev/null and b/autotests/pic/long-runs.png differ diff --git a/autotests/pic/short-comment.pic b/autotests/pic/short-comment.pic new file mode 100644 index 0000000..d6e8a3e Binary files /dev/null and b/autotests/pic/short-comment.pic differ diff --git a/autotests/pictest.cpp b/autotests/pictest.cpp new file mode 100644 index 0000000..fc8113c --- /dev/null +++ b/autotests/pictest.cpp @@ -0,0 +1,277 @@ +/* + * Copyright 2014 Alex Merry + * + * 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 + * . + */ + +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +Q_DECLARE_METATYPE(QImage::Format) + +class PicTests : public QObject +{ + Q_OBJECT + +private: + void common_data() + { + QTest::addColumn("picfile"); + QTest::addColumn("pngfile"); + QTest::addColumn("comment"); + // whether the pic file has/should have an alpha channel + QTest::addColumn("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("pngformat"); + QTest::addColumn("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(inputImage.bits()), + inputImage.byteCount()); + QFile pngDumpFile(fileNameBase + QStringLiteral("-actual.data")); + QVERIFY2(pngDumpFile.open(QIODevice::WriteOnly), qPrintable(pngDumpFile.errorString())); + pngDumpFile.write(reinterpret_cast(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" diff --git a/autotests/read/pic/bw.pic b/autotests/read/pic/bw.pic deleted file mode 100644 index 552c2e8..0000000 Binary files a/autotests/read/pic/bw.pic and /dev/null differ diff --git a/autotests/read/pic/bw.png b/autotests/read/pic/bw.png deleted file mode 100644 index e2d506b..0000000 Binary files a/autotests/read/pic/bw.png and /dev/null differ diff --git a/autotests/read/pic/bwa.pic b/autotests/read/pic/bwa.pic deleted file mode 100644 index affc603..0000000 Binary files a/autotests/read/pic/bwa.pic and /dev/null differ diff --git a/autotests/read/pic/bwa.png b/autotests/read/pic/bwa.png deleted file mode 100644 index a754150..0000000 Binary files a/autotests/read/pic/bwa.png and /dev/null differ diff --git a/autotests/read/pic/rgb.pic b/autotests/read/pic/rgb.pic deleted file mode 100644 index f8a1b9c..0000000 Binary files a/autotests/read/pic/rgb.pic and /dev/null differ diff --git a/autotests/read/pic/rgb.png b/autotests/read/pic/rgb.png deleted file mode 100644 index 267d678..0000000 Binary files a/autotests/read/pic/rgb.png and /dev/null differ diff --git a/autotests/read/pic/rgba.pic b/autotests/read/pic/rgba.pic deleted file mode 100644 index 05fed0f..0000000 Binary files a/autotests/read/pic/rgba.pic and /dev/null differ diff --git a/autotests/read/pic/rgba.png b/autotests/read/pic/rgba.png deleted file mode 100644 index cb0cae0..0000000 Binary files a/autotests/read/pic/rgba.png and /dev/null differ diff --git a/autotests/write/bw.pic b/autotests/write/bw.pic deleted file mode 100644 index 552c2e8..0000000 Binary files a/autotests/write/bw.pic and /dev/null differ diff --git a/autotests/write/bwa.pic b/autotests/write/bwa.pic deleted file mode 100644 index affc603..0000000 Binary files a/autotests/write/bwa.pic and /dev/null differ diff --git a/autotests/write/rgb.pic b/autotests/write/rgb.pic deleted file mode 100644 index f8a1b9c..0000000 Binary files a/autotests/write/rgb.pic and /dev/null differ diff --git a/autotests/write/rgba.pic b/autotests/write/rgba.pic deleted file mode 100644 index 05fed0f..0000000 Binary files a/autotests/write/rgba.pic and /dev/null differ diff --git a/src/imageformats/CMakeLists.txt b/src/imageformats/CMakeLists.txt index 59472ae..af169c1 100644 --- a/src/imageformats/CMakeLists.txt +++ b/src/imageformats/CMakeLists.txt @@ -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) install(TARGETS kimg_pic DESTINATION ${QT_PLUGIN_INSTALL_DIR}/imageformats/) diff --git a/src/imageformats/pic.cpp b/src/imageformats/pic.cpp index 9d8a7ed..36803be 100644 --- a/src/imageformats/pic.cpp +++ b/src/imageformats/pic.cpp @@ -1,6 +1,8 @@ /** - * PIC_RW - Qt PIC Support - * Copyright (C) 2007 Ruben Lopez + * Softimage PIC support for QImage + * Copyright 1998 Halfdan Ingvarsson + * Copyright 2007 Ruben Lopez + * Copyright 2014 Alex Merry * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public @@ -18,12 +20,340 @@ * ---------------------------------------------------------------------------- */ -#include "pic.h" -#include "pic_rw.h" +/* 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. + */ -#include +#include "pic.h" + +#include +#include #include -#include +#include +#include +#include +#include + +/** + * 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(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 &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 &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 +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 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 +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 { @@ -36,43 +366,211 @@ bool SoftimagePICHandler::canRead() const 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(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; } -bool SoftimagePICHandler::write(const QImage &image) +bool SoftimagePICHandler::write(const QImage &_image) { - pic_write(device(), &image); - return true; + bool alpha = _image.hasAlphaChannel(); + 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 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(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) { - PICHeader hdr; - if (picReadHeader(device, &hdr, true)) { - if (strncmp(hdr.id, "PICT", 4) == 0) { - return true; + char data[4]; + if (device->peek(data, 4) != 4) { + return false; + } + return qFromBigEndian(reinterpret_cast(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 { - if (option == Size) { - PICHeader hdr; - if (picReadHeader(device(), &hdr, true)) { - return QSize(hdr.width, hdr.height); - } else { - return QSize(-1, -1); - } + const_cast(this)->readHeader(); + switch (option) { + case Size: + if (const_cast(this)->readHeader()) { + return QSize(m_header.width, m_header.height); + } else { + return QVariant(); + } + case CompressionRatio: + return m_compression; + case Description: + if (const_cast(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(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 { - return (option == Size); + return (option == CompressionRatio || + option == Description || + option == ImageFormat || + option == Size); } QImageIOPlugin::Capabilities SoftimagePICPlugin::capabilities(QIODevice *device, const QByteArray &format) const diff --git a/src/imageformats/pic.h b/src/imageformats/pic.h index add0a35..b092078 100644 --- a/src/imageformats/pic.h +++ b/src/imageformats/pic.h @@ -23,17 +23,165 @@ #include +/** + * 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 { public: - virtual bool canRead() const; - virtual bool read(QImage *image); - virtual bool write(const QImage &); + bool canRead() const Q_DECL_OVERRIDE; + bool read(QImage *image) Q_DECL_OVERRIDE; + bool write(const QImage &) Q_DECL_OVERRIDE; - virtual QVariant option(ImageOption option) const; - virtual bool supportsOption(ImageOption option) const; + QVariant option(ImageOption option) const Q_DECL_OVERRIDE; + void setOption(ImageOption option, const QVariant &value) Q_DECL_OVERRIDE; + bool supportsOption(ImageOption option) const Q_DECL_OVERRIDE; 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 m_channels; + // mostly used for writing: + bool m_compression; + QByteArray m_description; }; class SoftimagePICPlugin : public QImageIOPlugin diff --git a/src/imageformats/pic_read.cpp b/src/imageformats/pic_read.cpp deleted file mode 100644 index 484c634..0000000 --- a/src/imageformats/pic_read.cpp +++ /dev/null @@ -1,293 +0,0 @@ -/** - * PIC_RW - Qt PIC Support - * Copyright (C) 2007 Ruben Lopez - * - * 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 -#include -#include -#include - -/** - * 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; -} diff --git a/src/imageformats/pic_rw.h b/src/imageformats/pic_rw.h deleted file mode 100644 index 2cc9589..0000000 --- a/src/imageformats/pic_rw.h +++ /dev/null @@ -1,108 +0,0 @@ -/** - * PIC_RW - Qt PIC Support - * Copyright (C) 2007 Ruben Lopez - * - * 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 -#include -#include - -/** - * 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__ diff --git a/src/imageformats/pic_write.cpp b/src/imageformats/pic_write.cpp deleted file mode 100644 index 0632eeb..0000000 --- a/src/imageformats/pic_write.cpp +++ /dev/null @@ -1,228 +0,0 @@ -/** - * PIC_RW - Qt PIC Support - * Copyright (C) 2007 Ruben Lopez - * - * 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 -#include -#include - -/** - * 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(PIC_MAGIC_NUMBER); - h.version = 3.71f; - strcpy(h.comment, msg.c_str()); - strncpy(h.id, "PICT", 4); - h.width = qToBigEndian(width); - h.height = qToBigEndian(height); - h.ratio = 1.0f; - h.fields = qToBigEndian(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(); - } - } -}