From 51eca9b6a8220e25ff3c469d757185651c9242e5 Mon Sep 17 00:00:00 2001 From: Alex Merry Date: Mon, 3 Mar 2014 12:52:40 +0000 Subject: [PATCH] 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 --- autotests/CMakeLists.txt | 14 +- autotests/long-runs.pic | Bin 0 -> 2508 bytes autotests/pic/4x4-alpha-uncompressed.pic | Bin 0 -> 176 bytes autotests/pic/4x4-alpha.pic | Bin 0 -> 164 bytes autotests/pic/4x4-alpha.png | Bin 0 -> 98 bytes .../pic/4x4-simple-color-uncompressed.pic | Bin 0 -> 156 bytes autotests/pic/4x4-simple-color.pic | Bin 0 -> 145 bytes autotests/pic/4x4-simple-color.png | Bin 0 -> 90 bytes autotests/pic/long-comment.pic | Bin 0 -> 145 bytes autotests/pic/long-runs.pic | Bin 0 -> 734 bytes autotests/pic/long-runs.png | Bin 0 -> 117 bytes autotests/pic/short-comment.pic | Bin 0 -> 145 bytes autotests/pictest.cpp | 277 +++++++++ autotests/read/pic/bw.pic | Bin 1203 -> 0 bytes autotests/read/pic/bw.png | Bin 743 -> 0 bytes autotests/read/pic/bwa.pic | Bin 649 -> 0 bytes autotests/read/pic/bwa.png | Bin 574 -> 0 bytes autotests/read/pic/rgb.pic | Bin 1274 -> 0 bytes autotests/read/pic/rgb.png | Bin 1053 -> 0 bytes autotests/read/pic/rgba.pic | Bin 1631 -> 0 bytes autotests/read/pic/rgba.png | Bin 1234 -> 0 bytes autotests/write/bw.pic | Bin 1203 -> 0 bytes autotests/write/bwa.pic | Bin 649 -> 0 bytes autotests/write/rgb.pic | Bin 1274 -> 0 bytes autotests/write/rgba.pic | Bin 1631 -> 0 bytes src/imageformats/CMakeLists.txt | 2 +- src/imageformats/pic.cpp | 546 +++++++++++++++++- src/imageformats/pic.h | 158 ++++- src/imageformats/pic_read.cpp | 293 ---------- src/imageformats/pic_rw.h | 108 ---- src/imageformats/pic_write.cpp | 228 -------- 31 files changed, 966 insertions(+), 660 deletions(-) create mode 100644 autotests/long-runs.pic create mode 100644 autotests/pic/4x4-alpha-uncompressed.pic create mode 100644 autotests/pic/4x4-alpha.pic create mode 100644 autotests/pic/4x4-alpha.png create mode 100644 autotests/pic/4x4-simple-color-uncompressed.pic create mode 100644 autotests/pic/4x4-simple-color.pic create mode 100644 autotests/pic/4x4-simple-color.png create mode 100644 autotests/pic/long-comment.pic create mode 100644 autotests/pic/long-runs.pic create mode 100644 autotests/pic/long-runs.png create mode 100644 autotests/pic/short-comment.pic create mode 100644 autotests/pictest.cpp delete mode 100644 autotests/read/pic/bw.pic delete mode 100644 autotests/read/pic/bw.png delete mode 100644 autotests/read/pic/bwa.pic delete mode 100644 autotests/read/pic/bwa.png delete mode 100644 autotests/read/pic/rgb.pic delete mode 100644 autotests/read/pic/rgb.png delete mode 100644 autotests/read/pic/rgba.pic delete mode 100644 autotests/read/pic/rgba.png delete mode 100644 autotests/write/bw.pic delete mode 100644 autotests/write/bwa.pic delete mode 100644 autotests/write/rgb.pic delete mode 100644 autotests/write/rgba.pic delete mode 100644 src/imageformats/pic_read.cpp delete mode 100644 src/imageformats/pic_rw.h delete mode 100644 src/imageformats/pic_write.cpp 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 0000000000000000000000000000000000000000..620a3bb8eb322d06e0468eb641c56bd72b6c6545 GIT binary patch literal 2508 zcmWG;_-5jeTd;(I${@hgIfUT^1B-nF&|qdD=3sgt96oy#jfMc#L*UJdluu{As6I3x literal 0 HcmV?d00001 diff --git a/autotests/pic/4x4-alpha.pic b/autotests/pic/4x4-alpha.pic new file mode 100644 index 0000000000000000000000000000000000000000..fc3e43d639bec846f6225dfddd8c3fb96620d091 GIT binary patch literal 164 zcmWG;_-5jeTd;(I${@hgIfQ|QfyKT7XfQJa10x610|pKzfu{c;i~ch(|33+0{by+U Y|NnpUe`X*FoLW w*fg&GE}OL-k6e<1K7EgoyRm0=!Sr7K>uiF3&ir9FftnaRUHx3vIVCg!0NF_%8~^|S literal 0 HcmV?d00001 diff --git a/autotests/pic/4x4-simple-color-uncompressed.pic b/autotests/pic/4x4-simple-color-uncompressed.pic new file mode 100644 index 0000000000000000000000000000000000000000..6e599589b9d54a63f46e1a8561b66dc5fb6f0a70 GIT binary patch literal 156 zcmWG;_-5jeTd;(I${@hgIfQ|QfyKT7XfQJnb1*#k&%gjd5b8fN080G_Nx}h;4FH<> BCV>C| literal 0 HcmV?d00001 diff --git a/autotests/pic/4x4-simple-color.pic b/autotests/pic/4x4-simple-color.pic new file mode 100644 index 0000000000000000000000000000000000000000..88b636fdfad60e0c540f898946b1557fa626fcf0 GIT binary patch literal 145 zcmWG;_-5jeTd;(I${@hgIfQ|QfyKT7XfQJnb1*$<`VX=MMEqxH`v3nwGY|uL5CS9s Gk^unkkQy2Q literal 0 HcmV?d00001 diff --git a/autotests/pic/4x4-simple-color.png b/autotests/pic/4x4-simple-color.png new file mode 100644 index 0000000000000000000000000000000000000000..f8d5d4393f6c60722dba3da619f5734ba59c6336 GIT binary patch literal 90 zcmeAS@N?(olHy`uVBq!ia0vp^EFjFm1SHiab7}%9MNb#U5RU7*CpR)O81OV3T>M{O nHr=4>S(Z`pr!=mo2jA7OL~%0fFAMPnDrWF>^>bP0l+XkKW-%Az literal 0 HcmV?d00001 diff --git a/autotests/pic/long-comment.pic b/autotests/pic/long-comment.pic new file mode 100644 index 0000000000000000000000000000000000000000..831521c05abbc5c845bb04979db57bd2e88b83fb GIT binary patch literal 145 zcmXv_yA8rH5IvC;jPZt`2Z@Rfi7a4|OB^ZoMfj3B$}9}V6tw0HqIiG3+xkA7!t`u7 zDee*iN5$gCnW5e(df|k}(0j!^LB(1I#}?9k?f=pZJhpH)c$X06dO0^#Q+-^Cs50z# UuPvKWp-fxOxiVP#Q7Ovu0rkW--~a#s literal 0 HcmV?d00001 diff --git a/autotests/pic/long-runs.pic b/autotests/pic/long-runs.pic new file mode 100644 index 0000000000000000000000000000000000000000..99623e03c42df5a2c0902454d91bdbce911e5731 GIT binary patch literal 734 zcmWG;_-5jeTd;(I${@hgIfUT^1B-nF&|qdD=3sizz;Hr1eD=RLD^fso14F|Hz01de Yta{res)4dsM#+H+0r!EjnAY|J0HvPz8UO$Q literal 0 HcmV?d00001 diff --git a/autotests/pic/long-runs.png b/autotests/pic/long-runs.png new file mode 100644 index 0000000000000000000000000000000000000000..c718f7f7ca9a8a6072b302ad288966fa732fbc60 GIT binary patch literal 117 zcmeAS@N?(olHy`uVBq!ia0vp^CxDoRg9%8w2s~#4QZ}A0jv*e$lSTOEpEcleW8BJk z`s{(pix(OMIMnX_S@2crG5g-riv?{O93np-`S4}NTcN8CH?{-vBg*8HhQU9yI+2 Y83`i(Gc^7G|DPF%fjkHS5&+2n0BSEG3IG5A literal 0 HcmV?d00001 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 552c2e8eb15e30018ae365a5994e8d8d9a9d442e..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1203 zcmb`GJ4l;R7>4ubPc#=%l17Z^5O7fkFW4bEn2HG)Dp*N$&@2rRTBJ~tKtYG(7^6rp zx;Z$wba3!8II4pvh=RH*F2zd}K_w_PsL?*DjRbWlw0}69^L<~=d*1WC?{S5HzWo02 z!>dMr*V|y1&+_Sg@SWwg-J$r$?~Sw3t+4P@gl!5LFpcuZ_}$%IdPG=DH#avI7Z)NP zkLylPPY(|dW3d>Ev$M0~UWTwPrW@cI~P zFn4x##>U152L~gONH7=_E|)8txu>Vc>-BbYbhNaz*laeZ)7jqME{L^lZEelX&8@Ai zO-)Ut-EOzXx z)WpO@LqkJWRu-hXySry+XXoeVmzI_`H#gVU*SEK~x3;#HmzNh77FJhRY4q*wZ3;U> zyeJpZpP!#65(zY;%Q-nY@T9YQdwVqR`uaMVOu|F3S0=j85u_CRno^A2LqkJ!5gy2Z zvY*o>P$wrROG-+BL?i@%C;_#&xX7*P@}m+5ZftDS*4EN<0C7y_F0Mzr(Bjc9h;dI^ z3m~SzSxI@k3{4XXaU48DS7zuh(N&n)*P4Mhy=S7Z(>}x8B}fo=LhS zp~AqxfWSRce}6wwW(DPcSD630@;q8fE1ZKZpHNUke0qAiyu2LZKA$g>I9jc&tk~^# lY!(OvFjAVH8Y2*5Ti(R|b4!)JF=R@38OA8{g~a%u@e6rSmqq{p diff --git a/autotests/read/pic/bw.png b/autotests/read/pic/bw.png deleted file mode 100644 index e2d506bb3da276c09b8f9388dc8ad6b96f987f5e..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 743 zcmV?P)E1)jxbCtrE}4UMN{WWBtG@56F7V!ybw)(i9BT()zyEYy z*ECJnb>X{hTkpM;5&(=b&N=6twKj?(N+|#kLcI6RIRJ3Z5kf*95z{n9#4i9{*Ue_L ztE;P`D6F;f`Fu8;5kkh}G3Wg0>8WX&s;b`J-Ufp~nx>2~LWnV@u4_cB>ly&!IF?dw zZEc;MolU1xMEnG3n&$HI^8EaKKA$`1wAPEof)Fwo3@D|G#lm}Ut?jxF02pIY^wC~p z45idsyUa@|hr{8)!NC`R-y<9qLJ&e2W1RCW%LpNlkB?-YwM_g-rqjE0Co zBz*z^0Ove#Ym8ZO&VP@CIF32zKbeE3X~MYXocGUxbFM5)tu+7$A^NYt81wM(@c#a; zl*;q`?>W&!1$A9pYuCj=pkXQ5pSe{mFTVOV^*y77_t{#z7Na5J-rnA5G^*?R`T5xx z)Bk&r=lRagju2ur8m;)?!0O%f-oL)SUSD6|-`}5{oP@QfPk?~YwyoA$DYc@};Ky&* ZzW_2gTD;a-w0-~p002ovPDHLkV1n%MZ8iV^ diff --git a/autotests/read/pic/bwa.pic b/autotests/read/pic/bwa.pic deleted file mode 100644 index affc603bebdd64a1d63b5477f6c71e827405733b..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 649 zcmWG;_-3-CAlJdUC^fMpHASI3vm`^o+r^cELJ;8T9KxUggbWS#49pA+j2ui47&w>& z<^#>04}_@XWCkWiX2uS%crybtBZmNg8y}N^xMZUQi;}*gglMAxlb9%5BUnKb0}G>Q zP)k#5l|XgplC{g1Z8)%D>D<<~1@j!(K*lmKG%>KU+V6S$=0(eMmKX1T{`%hhjq&T3 z#@EbWzxN7YDB*hX>DPyw7f$Xzb>Uj$W9Cb1o#4j6EaGoD`Qt{hyS1LROHh8Ur!qUl zB|xV#aS95w@^PuG{C2^Ei-n1WmA#RNk&Uql?2cXrR`DfYFSxRH!?>bzzg!DqhjRs5 zUOz72fphs9UcRX2gLB0ee*e-V2P7@RA!^Uv=Ml5np0+CTr!E5W&f6Tbdj zgOC;MegC`!VUfhzUl*NO;EE+y{annn0vs%ftm~e5~H+Q diff --git a/autotests/read/pic/bwa.png b/autotests/read/pic/bwa.png deleted file mode 100644 index a75415012846b5eead134ac502aa740c0cf1cfa7..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 574 zcmV-E0>S->P)mofHc2PM z0S82JTDTEZbSp0XGyWxa{s(bsKoEDj(2WBK28=;*igwaTRl7L_Md*&EIw=uyp{O42 zJMYxF_uP6M#6OPdeZcJO2DdZjw^__#m}^{2C3mWpH;+YpM;`??^WRMJzF{zmKnEvq z8#hs6eFyS~aTdJ*4nyE5zT#)ruO$CWFz@3D2HkC-U0lIk+z8;-7!@j|=JUB2kyk0$ zuc`% z)=Dsoi?QD-p6&47*cSq7b6^1{+it?5_Qa=gD@0m#!AkDaINoFqOwxp9Y}gOZa&^vY z(*@P*x5yc=Xp=yJPdVX=6Ivy(j-?vLQWG0?_TW%DS0hw7i2p5q113|2rrJF}9RL6T M07*qoM6N<$f@8P)Z~y=R diff --git a/autotests/read/pic/rgb.pic b/autotests/read/pic/rgb.pic deleted file mode 100644 index f8a1b9cf58589d407006a6b64eefe37b6786d6a8..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1274 zcmbu8ZA=_R7{@ua#&3Qwrtz)egC-_M6SRp$)2KmXT8)ylDr#b-(zMiGsRW7?&a%8+ zDqJCgsBk9*4=Yznq*&-#Dg_NMS2#EinwD~Kqy?^byL)^4KC`QEyp(n~5xeiM za68608I>d+LI`URs}M;@2AGC8A|r-1Kpa6H%c22fLE;jOBCaD^1W}#=gwiwsJb;SE z^FUGoBN>c{p#HZcMPwf#vK)g0KI9EV`^4Tk^ywVR?Nv_|a|O0TB{my z)J|7@d7-4w|JeI6>-ObPYn-(CAL1W+d^d;fUjfaMT%x{}wlGxj4)A2v={Q zAsP=Y>C=O7y~&tq&<9S>Oi?+ZvM0IEUyo0_IcYILt4vs7ytm=GMK(w;TxY^-jBiQ| z^a^|kqJL~jZKYvg2(SW>WRSgp2txyF02=~MZe=W{_EmAkhuAi2B0LEVNZiB-V_;xe zM;g*~WFSPW)nt^EO-k02*h0&CL16vSoBr(a)jwCEZls1&D2n7#{=AhfuyL+s5Jeaz zhf_!d+|bDBbES?eS1Jp z#Ymr~AP||+%MG@lt48|k)ku_hv4QSkRlaWarz5ikCucmYA~;A>bxA9TVKP|ut>jG3 zZTbBY_^Yizz0SOEH_Q9L7bw|rgsT}DO6bNxP-3`BDlkod7dm%S_3?%6j!(GFB$ z16f1^IvGFpY+}&jjQdHoNf7Lo{1iE^7Lv)0H3>At`B)p(H=ki4nN%iGHPL&+Lw(#@ yW+9nWCXp1qyMK*(G3%CvWKx+#*Kue`r(EBw7LrMoBuP8c$Tua5shq6aN&E%h5u9!S diff --git a/autotests/read/pic/rgb.png b/autotests/read/pic/rgb.png deleted file mode 100644 index 267d67841ad5f80e0f36cbf514c33d6e9cac146b..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1053 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 diff --git a/autotests/write/bw.pic b/autotests/write/bw.pic deleted file mode 100644 index 552c2e8eb15e30018ae365a5994e8d8d9a9d442e..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1203 zcmb`GJ4l;R7>4ubPc#=%l17Z^5O7fkFW4bEn2HG)Dp*N$&@2rRTBJ~tKtYG(7^6rp zx;Z$wba3!8II4pvh=RH*F2zd}K_w_PsL?*DjRbWlw0}69^L<~=d*1WC?{S5HzWo02 z!>dMr*V|y1&+_Sg@SWwg-J$r$?~Sw3t+4P@gl!5LFpcuZ_}$%IdPG=DH#avI7Z)NP zkLylPPY(|dW3d>Ev$M0~UWTwPrW@cI~P zFn4x##>U152L~gONH7=_E|)8txu>Vc>-BbYbhNaz*laeZ)7jqME{L^lZEelX&8@Ai zO-)Ut-EOzXx z)WpO@LqkJWRu-hXySry+XXoeVmzI_`H#gVU*SEK~x3;#HmzNh77FJhRY4q*wZ3;U> zyeJpZpP!#65(zY;%Q-nY@T9YQdwVqR`uaMVOu|F3S0=j85u_CRno^A2LqkJ!5gy2Z zvY*o>P$wrROG-+BL?i@%C;_#&xX7*P@}m+5ZftDS*4EN<0C7y_F0Mzr(Bjc9h;dI^ z3m~SzSxI@k3{4XXaU48DS7zuh(N&n)*P4Mhy=S7Z(>}x8B}fo=LhS zp~AqxfWSRce}6wwW(DPcSD630@;q8fE1ZKZpHNUke0qAiyu2LZKA$g>I9jc&tk~^# lY!(OvFjAVH8Y2*5Ti(R|b4!)JF=R@38OA8{g~a%u@e6rSmqq{p diff --git a/autotests/write/bwa.pic b/autotests/write/bwa.pic deleted file mode 100644 index affc603bebdd64a1d63b5477f6c71e827405733b..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 649 zcmWG;_-3-CAlJdUC^fMpHASI3vm`^o+r^cELJ;8T9KxUggbWS#49pA+j2ui47&w>& z<^#>04}_@XWCkWiX2uS%crybtBZmNg8y}N^xMZUQi;}*gglMAxlb9%5BUnKb0}G>Q zP)k#5l|XgplC{g1Z8)%D>D<<~1@j!(K*lmKG%>KU+V6S$=0(eMmKX1T{`%hhjq&T3 z#@EbWzxN7YDB*hX>DPyw7f$Xzb>Uj$W9Cb1o#4j6EaGoD`Qt{hyS1LROHh8Ur!qUl zB|xV#aS95w@^PuG{C2^Ei-n1WmA#RNk&Uql?2cXrR`DfYFSxRH!?>bzzg!DqhjRs5 zUOz72fphs9UcRX2gLB0ee*e-V2P7@RA!^Uv=Ml5np0+CTr!E5W&f6Tbdj zgOC;MegC`!VUfhzUl*NO;EE+y{annn0vs%ftm~e5~H+Q diff --git a/autotests/write/rgb.pic b/autotests/write/rgb.pic deleted file mode 100644 index f8a1b9cf58589d407006a6b64eefe37b6786d6a8..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1274 zcmbu8ZA=_R7{@ua#&3Qwrtz)egC-_M6SRp$)2KmXT8)ylDr#b-(zMiGsRW7?&a%8+ zDqJCgsBk9*4=Yznq*&-#Dg_NMS2#EinwD~Kqy?^byL)^4KC`QEyp(n~5xeiM za68608I>d+LI`URs}M;@2AGC8A|r-1Kpa6H%c22fLE;jOBCaD^1W}#=gwiwsJb;SE z^FUGoBN>c{p#HZcMPwf#vK)g0KI9EV`^4Tk^ywVR?Nv_|a|O0TB{my z)J|7@d7-4w|JeI6>-ObPYn-(CAL1W+d^d;fUjfaMT%x{}wlGxj4)A2v={Q zAsP=Y>C=O7y~&tq&<9S>Oi?+ZvM0IEUyo0_IcYILt4vs7ytm=GMK(w;TxY^-jBiQ| z^a^|kqJL~jZKYvg2(SW>WRSgp2txyF02=~MZe=W{_EmAkhuAi2B0LEVNZiB-V_;xe zM;g*~WFSPW)nt^EO-k02*h0&CL16vSoBr(a)jwCEZls1&D2n7#{=AhfuyL+s5Jeaz zhf_!d+|bDBbES?eS1Jp z#Ymr~AP||+%MG@lt48|k)ku_hv4QSkRlaWarz5ikCucmYA~;A>bxA9TVKP|ut>jG3 zZTbBY_^Yizz0SOEH_Q9L7bw|rgsT}DO6bNxP-3`BDlkod7dm%S_3?%6j!(GFB$ z16f1^IvGFpY+}&jjQdHoNf7Lo{1iE^7Lv)0H3>At`B)p(H=ki4nN%iGHPL&+Lw(#@ yW+9nWCXp1qyMK*(G3%CvWKx+#*Kue`r(EBw7LrMoBuP8c$Tua5shq6aN&E%h5u9!S diff --git a/autotests/write/rgba.pic b/autotests/write/rgba.pic deleted file mode 100644 index 05fed0fccecb597d236d30fd020e681a96ea69a6..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1631 zcmbu=R_R<22mf8pHY6#}UvKp2 zYj9Qlns854Jkai4gM<2x6;Z*>E&vx3%`iZLEU0ub1ENzA7^2b<25qjLvlGk4!3Gus zlpu~cxQT--9Pr0zJDQfnd2YS2fj`_4E`NN|UyUXRi;+J#IWp(MRJy*@zdv0myHV%X ztypS zs>9UThD!S`jpx(ClxLD}6)$N&jWF`D%&%EP%jS-f!D0Ygu5Hxz8N`Fu5Bs`xw;nwi z95%guJ@)*~XdYu`@&FDBagh3wmVL+znQu-`!VKxL1=(0}M&Xcq)T!{0in553(jU?G z%m-4(GYy;0)D-EFyy_vFJuj@#n#h%(>zB?kzE5Tz95=lh77tQ}$4q0$`0Qs-hDM$b z^xf4egJ|Yxhj)iI;Gh`?NpC2UOAGgS^LvP=tqqeu%8qG@6K6=ox}bzMrA=oKbW!b| zb7lWu6&|W0o}j+5p`cdY=eGUztf)&opD%lm>0l>IUs+uhCdw3HRkcm`-sHO#B+bDDo&qp9=K zWU)i*T1xUe>+USrgoEX6oXNz=SBC-%OO^wRDP~ZaET?tai3^Jn2B0HUij@Tg5`%+; zfdg=`VdPUk4R_^nG`v1K@$3c(v3wA~v@QA<6}FOT#k;yO?1UJp&xnv7;pNwmin5|( z;+!L?0A3MuKSB5~V&6;tk%5inouQb&dOq7SIP7mM8IDD+FN^^*$r)0yP=?)JHfwrKJet#z=G2o6;=H9DaDmFB1852b5*-BPuh37(!$a5-~{S zsyXL+wgs`5=pkYzswtI8sLUzl$u_NXA3=87jzGjrR3B6-y5_R&C8XQwC`8Oe^&thT zzerLnO_MGcmip{H<0&A@DK>LsaPZ30q5krSHFo3!>%f8bwsr3 + * 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(); - } - } -}