From 142ec14c81233187cc12565fe1daaa03cbf0a4ae Mon Sep 17 00:00:00 2001 From: Mirco Miranda Date: Fri, 20 Mar 2026 14:14:44 +0100 Subject: [PATCH] TIM: PlayStation graphics read only support --- README.md | 2 + autotests/CMakeLists.txt | 1 + autotests/ossfuzz/build_fuzzers.sh | 1 + autotests/ossfuzz/kimgio_fuzzer.cc | 3 +- autotests/read/tim/testcard_idx4.png | Bin 0 -> 3566 bytes autotests/read/tim/testcard_idx4.tim | Bin 0 -> 8256 bytes autotests/read/tim/testcard_idx8.png | Bin 0 -> 3514 bytes autotests/read/tim/testcard_idx8.tim | Bin 0 -> 16928 bytes autotests/read/tim/testcard_rgb16.png | Bin 0 -> 3141 bytes autotests/read/tim/testcard_rgb16.tim | Bin 0 -> 32788 bytes autotests/read/tim/testcard_rgb24.png | Bin 0 -> 4481 bytes autotests/read/tim/testcard_rgb24.tim | Bin 0 -> 49172 bytes src/imageformats/CMakeLists.txt | 4 + src/imageformats/tim.cpp | 398 ++++++++++++++++++++++++++ src/imageformats/tim.json | 4 + src/imageformats/tim_p.h | 42 +++ 16 files changed, 454 insertions(+), 1 deletion(-) create mode 100644 autotests/read/tim/testcard_idx4.png create mode 100644 autotests/read/tim/testcard_idx4.tim create mode 100644 autotests/read/tim/testcard_idx8.png create mode 100644 autotests/read/tim/testcard_idx8.tim create mode 100644 autotests/read/tim/testcard_rgb16.png create mode 100644 autotests/read/tim/testcard_rgb16.tim create mode 100644 autotests/read/tim/testcard_rgb24.png create mode 100644 autotests/read/tim/testcard_rgb24.tim create mode 100644 src/imageformats/tim.cpp create mode 100644 src/imageformats/tim.json create mode 100644 src/imageformats/tim_p.h diff --git a/README.md b/README.md index fd7a25c..38fa3c6 100644 --- a/README.md +++ b/README.md @@ -21,6 +21,7 @@ The following image formats have read-only support: - Krita (kra) - OpenRaster (ora) - Pixar raster (pxr) +- PlayStation graphics (tim) - Portable FloatMap/HalfMap (pfm, phm) - Photoshop documents (psd, psb, pdd, psdt) - Radiance HDR (hdr) @@ -250,6 +251,7 @@ limit depends on the format encoding). - RAW: 65,535 x 65,535 pixels - RGB: 65,535 x 65,535 pixels - SCT: 300,000 x 300,000 pixels +- TIM: 65,535 x 65,535 pixels - TGA: 65,535 x 65,535 pixels - XCF: 300,000 x 300,000 pixels diff --git a/autotests/CMakeLists.txt b/autotests/CMakeLists.txt index 145aea1..3fe173e 100644 --- a/autotests/CMakeLists.txt +++ b/autotests/CMakeLists.txt @@ -86,6 +86,7 @@ kimageformats_read_tests( ras rgb sct + tim tga ) diff --git a/autotests/ossfuzz/build_fuzzers.sh b/autotests/ossfuzz/build_fuzzers.sh index 37f9919..d9082a0 100755 --- a/autotests/ossfuzz/build_fuzzers.sh +++ b/autotests/ossfuzz/build_fuzzers.sh @@ -180,6 +180,7 @@ HANDLER_TYPES="ANIHandler ani RAWHandler raw RGBHandler rgb ScitexHandler sct + TIMHandler tim TGAHandler tga XCFHandler xcf" diff --git a/autotests/ossfuzz/kimgio_fuzzer.cc b/autotests/ossfuzz/kimgio_fuzzer.cc index 82d0f94..e3408fa 100644 --- a/autotests/ossfuzz/kimgio_fuzzer.cc +++ b/autotests/ossfuzz/kimgio_fuzzer.cc @@ -23,7 +23,7 @@ Usage: python infra/helper.py build_image kimageformats python infra/helper.py build_fuzzers --sanitizer undefined|address|memory kimageformats - python infra/helper.py run_fuzzer kimageformats kimgio_[ani|avif|dds|exr|hdr|heif|iff|jp2|jxl|jxr|kra|ora|pcx|pfm|pic|psd|pxr|qoi|ras|raw|rgb|sct|tga|xcf]_fuzzer + python infra/helper.py run_fuzzer kimageformats kimgio_[ani|avif|dds|exr|hdr|heif|iff|jp2|jxl|jxr|kra|ora|pcx|pfm|pic|psd|pxr|qoi|ras|raw|rgb|sct|tim|tga|xcf]_fuzzer */ #include @@ -52,6 +52,7 @@ #include "raw_p.h" #include "rgb_p.h" #include "sct_p.h" +#include "tim_p.h" #include "tga_p.h" #include "xcf_p.h" diff --git a/autotests/read/tim/testcard_idx4.png b/autotests/read/tim/testcard_idx4.png new file mode 100644 index 0000000000000000000000000000000000000000..2e5c5a87df2d9570da727781e2d9cd9faa84cc5e GIT binary patch literal 3566 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 QbG+sLHK~7G?|=LG-zFIVb^rhX literal 0 HcmV?d00001 diff --git a/autotests/read/tim/testcard_idx8.png b/autotests/read/tim/testcard_idx8.png new file mode 100644 index 0000000000000000000000000000000000000000..06efdae41e933e5fb651a86903ed86d518445ff2 GIT binary patch literal 3514 zcmd_r`CC%i9|!PrK?F&WQoos*VVOaiiDgBZf~h4gDP|@uBDthf>L|H{9 zBBeyz$g!fP5h*Lt(Q;|QTncGJCJVL9Wm^0qP4j&JhwuA&ozFSv^TU14eeUy|TM!bw z$_#6R1pt`&ul8Z+XLw>@kor2LE*#TOM)9;D8UR-kyM-Aa0D#UVtXvtA##rS|VDCuS z7BfE~34l!D<)f_umeIELUb=PO+cJqnZ#J&o-yU0}b#QuJzU*C+NEw?RmmbkEZC~*w z?`q>sdvT#gW0Ss^KPgj3;*E0P%nz?aLyZatv*Pz!Jv`Mv&@yswVgJX|86_EnYs!=_ zqb-C{+QNCbsglBnt?NcSBkj@-d&qBC?3+pNZ+f=$&=RfNj$O4w)mhK4zsl`X;5d;l zYb^YN2J=V1{vNkAGx*D&7rHAm{h!TD;JsW?7SWZ>zFCy&7{7Gh)O~do=fY)m#{%9x znH}Xp)TM1{s8Z2XpODH2UV|HMKYXNm6n)FJ7gb8XxW=7ZKynL&_}KaNTh(qN8{+-G zT?@i`R@J}xnPXl|I9ld++VLx6!`rtP)|GRsZ30hxym>NhduAS{YIPjf$oR_l{?*cE9_d!=#9> zxxGs9?z5L$+phwE1R;TIef6L6M=%%w4D<^;23X?(zypl+4;VneUw_*rW-G25BT~JucarO9R$=^#8#C z1STM`|3OLwghZgPq%=U{0uqfzBNKQAd@^AIg&?31gcC@5VgM90l9)!47)WU(IgKo* zlNCQS)5+f{8Ds;Bit!H(gQ8(lbUzF;Dc_B=xl(SLlt~-cW7Ecg0&saWE{_fr3YvgN z7w{NDz8+I7NaM-V_;R*bz?F)5fXx3FhRin*U?@TZF@_?UK+}_AXmSiqz@kpy$7f6sF!%!I z_cIEFY>6;UEa55?V0f6$GlXx5z!0G!0-jvJm-FQkiBw^u;jir(vCLc=)^FiQVwOJN?R#@$uo|aYHtL zRt@T}FTi00hkft-|JL1~wCDAgfl12h$TR?ObrS$f;-~`B3NG z7JX}O!rJwmu;e&SMs#Yd;Sve-_wyx$q$U>-;n;+$aQML_$(baz+}dJ$rP!{viSW zKD4!&!uO}LQY^x48@lrX+Rkv6`qXc+Monw@$*<7M1EA5CDR}f`JQ|HARqQ7=dA-#V z9cS@4pnPr3nkPXv%zi=JI`m*sMR?c#jEn3Uq4^ggd*sPIs=svnoPq9`sPt>QZm+rB zKe9Wke>>wW!pYt^>b_^atPCiO)ICSGTZ(}9|szC3cNvjY_~;w z|J<%?G6d?+CCj3oo=Czm{^L#s^Ji;rt?HhB`M3QiT&9Xv*fX=778`?>*jFXvX_W_hS&iUsN5|(DAnV4a zNseuoeMTHXo#|Nx<`m7hX6-1o{V6tXM>OD$j2emFb#B1a<|>JpuV8(81Jn<4j&4>@ z0+lVca4X7^`mW(}-R9Zpldha9Sh4b5G&=%sfu3awYXI584a{l9?1JE9x$_EgDqwf& zZ;pk@fZl=xPyN0@#H`=JJ_I5JCqs@V#)g3KC>fObQ+NtkQ1jk2rF&Jb0%ln4?x!F@ zPmg`=$Q?~?>6izr$Rx+CYAQh8ebMtSY;e=1J;s^e!Z*N&c@K~n!k6+HwKH=aXPM$X zx0le35Wko+p+%@KkyPlSa|5z2r|lKAcmdumI-spIZ*6W>*Z#vO zI5-QOjsq8J8;~}D69=?~CO}vKcCc_nb3%zLh`AGH2U5?WVJDj@kZAeGK~Rdew!hN2 zr*LO+Xh_@9-U#qUl1q>KVtS!EAayr53fWLVjU8zkK~v*B z#Th>8yqFqx1?swz)>7w@GR4)4e#rw)h1L+~si(t#AUfH|*L*Y9wmrlvB9Js|I0tfF zoErnS;OWL%KalOh0(I);yFD(D!MfW=OngzjdWN97DX1B9Z}aKw^q)Z zE-FU;X^9k_LQ?Oeu0mJqKS5;kC++P|Jic`l%|CT<=JTp=g*(hI?;6Pc+%Fp`kv-j* zI_Nt!xSI8HtoJncGg(xQwk?X^HZKw~I|+M4lsoOtplA z)V{gP23|wefl%TO+SW%#y;PLnK_fD-#AHD*?liO-pL9c;XYsRzE2?G@gshrwE;%pR zc#HFwS-^i5FkQz_Ug~WrUdU~KFztAdjMEeuUqX()d-HL9N$Q9E7gqQ3%w{aws)ix^ zz9em&$AGK=;WY1f59UkM?aYUF$3AK{yZ1JAUUP{D=MFEof=p+w^W?Nqjnp(q8|+2d zJAw}cMYG|(A&{}F8FT;*wwys&y;%uC0H$qiVj`s3vj`DbO5T1kICsCd zC1^1PtmiE_R!K$e6H@#~VD;t=(YVP&%&u{{fX;WfhSm|_IUDlr8ujx+Q1|vAIA#hj&c(PQh#|Ug=&Hl(rFQts z60zm>*mAYWl1zkP)R~;p^|oK(Q5)~=a3_8b!VlEgFR1b{zi|h>GkQQ0pGisM`d}5%1#>XvK zRurdIR=?oAW#+40lc~1mwM)efzb)^us1sI}eY#`EqSz6zK4s&H*iqwJORdtly!HWf z)(eFm;4gN#24#NWi-2d34omoZ8x>qwa(!~)e5yd@`*A9v_;p?k4vT7 zI;JO}DmWC{0Iz2u_suRE+RPFQVV$3g?k`2Mf!m~iJ;jdaz4CRKF#e+Y{Bk#uRa~O+MaN( z?-`PqFf@Pa0x%=R>VX#+h&*|1>`d8u$5ibk-Yo>2ee}$)^nHE9GFj!3sEzHh%U;OM zPW4SWUA*imJqr!XDz@!YeD-(eAK$zO1;?J9QMD<8demXeYe!aH h$!S{Zi4Ebv<6y`;(6-SobYSAo>+c)vQ|Zmx^IxRLlm-9* literal 0 HcmV?d00001 diff --git a/autotests/read/tim/testcard_idx8.tim b/autotests/read/tim/testcard_idx8.tim new file mode 100644 index 0000000000000000000000000000000000000000..52270632619002c8a9ad54ec45cc31d3c2618940 GIT binary patch literal 16928 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 literal 0 HcmV?d00001 diff --git a/src/imageformats/CMakeLists.txt b/src/imageformats/CMakeLists.txt index e02347f..12cc318 100644 --- a/src/imageformats/CMakeLists.txt +++ b/src/imageformats/CMakeLists.txt @@ -137,6 +137,10 @@ kimageformats_add_plugin(kimg_sct SOURCES sct.cpp) ################################## +kimageformats_add_plugin(kimg_tim SOURCES tim.cpp) + +################################## + kimageformats_add_plugin(kimg_tga SOURCES tga.cpp microexif.cpp scanlineconverter.cpp) ################################## diff --git a/src/imageformats/tim.cpp b/src/imageformats/tim.cpp new file mode 100644 index 0000000..312886b --- /dev/null +++ b/src/imageformats/tim.cpp @@ -0,0 +1,398 @@ +/* + This file is part of the KDE project + SPDX-FileCopyrightText: 2026 Mirco Miranda + + SPDX-License-Identifier: LGPL-2.0-or-later +*/ + +#include "tim_p.h" +#include "util_p.h" + +#include +#include +#include + +Q_DECLARE_LOGGING_CATEGORY(LOG_TIMPLUGIN) +Q_LOGGING_CATEGORY(LOG_TIMPLUGIN, "kf.imageformats.plugins.tim", QtWarningMsg) + +#define TYPE_4BPP 0 // never seen +#define TYPE_IDX_4BPP 8 +#define TYPE_8BPP 1 // never seen +#define TYPE_IDX_8BPP 9 +#define TYPE_16BPP 2 +#define TYPE_24BPP 3 + +#define HEADER_SIZE 20 + +class TIMHeader +{ +private: + QByteArray m_rawHeader; + + quint16 ui16(quint8 c1, quint8 c2) const { + return (quint16(c2) << 8) | quint16(c1); + } + + quint32 ui32(quint8 c1, quint8 c2, quint8 c3, quint8 c4) const { + return (quint32(c4) << 24) | (quint32(c3) << 16) | (quint32(c2) << 8) | quint32(c1); + } + +public: + TIMHeader() + { + + } + + quint32 type() const + { + if (m_rawHeader.size() < HEADER_SIZE) { + return 0; + } + return ui32(m_rawHeader.at(4), m_rawHeader.at(5), m_rawHeader.at(6), m_rawHeader.at(7)) & 0xF; + } + + quint32 offset() const + { + if (m_rawHeader.size() < HEADER_SIZE) { + return 0; + } + auto o = quint32(HEADER_SIZE); + auto t = type(); + if (t == TYPE_IDX_4BPP || t == TYPE_IDX_8BPP) { // indexed + o += ui32(m_rawHeader.at(8), m_rawHeader.at(9), m_rawHeader.at(10), m_rawHeader.at(11)); + } + return o; + } + + bool isValid(quint32 size = 0) const + { + if (m_rawHeader.size() < HEADER_SIZE) { + return false; + } + if (size == 0) { + size = offset(); + } + if (m_rawHeader.size() < size) { + return false; + } + return (m_rawHeader.startsWith(QByteArray::fromRawData("\x10\x00\x00\x00", 4))); + } + + bool isSupported() const + { + return format() != QImage::Format_Invalid; + } + + qint32 width() const + { + auto strideLen = strideSize(); + auto t = type(); + if (t == TYPE_4BPP || t == TYPE_IDX_4BPP) { + return strideLen * 2; + } + if (t == TYPE_8BPP || t == TYPE_IDX_8BPP) { + return strideLen; + } + if (t == TYPE_24BPP) { + return strideLen / 3; + } + return strideLen / 2; + } + + qint32 height() const + { + auto o = offset(); + if (!isValid(o)) { + return 0; + } + return qint32(ui16(m_rawHeader.at(o - 2), m_rawHeader.at(o - 1))); + } + + QSize size() const + { + return QSize(width(), height()); + } + + QImage::Format format() const + { + auto t = type(); + if (t == TYPE_IDX_4BPP || t == TYPE_IDX_8BPP || t == TYPE_4BPP) { + return QImage::Format_Indexed8; + } + if (t == TYPE_IDX_8BPP) { + return QImage::Format_Grayscale8; + } + if (t == TYPE_16BPP) { + return QImage::Format_RGB555; + } + if (t == TYPE_24BPP) { + return QImage::Format_RGB888; + } + return QImage::Format_Invalid; + } + + quint32 strideSize() const + { + auto o = offset(); + if (!isValid(o)) { + return 0; + } + return ui16(m_rawHeader.at(o - 4), m_rawHeader.at(o - 3)) * 2; + } + + qint32 paletteColors() const + { + if (this->format() != QImage::Format_Indexed8) { + return 0; + } + return qint32(ui16(m_rawHeader.at(16), m_rawHeader.at(17))); + } + + qint32 paletteCount() const + { + if (this->format() != QImage::Format_Indexed8) { + return 0; + } + return qint32(ui16(m_rawHeader.at(18), m_rawHeader.at(19))); + } + + QList palette() const + { + if (format() != QImage::Format_Indexed8) { + return {}; + } + + // 4bpp without CLUT is treated as indexed + if (type() == TYPE_4BPP) { + QList pal; + for (auto i = 0; i < 16; ++i) { + auto v = i * 17; + pal << qRgb(v, v, v); + } + return pal; + } + + // read the first paette only + auto len = paletteColors(); + if (!isValid(HEADER_SIZE + len * 2)) { + return {}; + } + QList clut; + for (auto i = 0; i < len; ++i) { + auto v = ui16(m_rawHeader.at(HEADER_SIZE + i * 2), m_rawHeader.at(HEADER_SIZE + i * 2 + 1)); + // in some specs, the bit 15 is the alpha but with the image sample used, transparencies appear + // where there shouldn't be any (so, disabled for now) + clut << qRgba((v & 0x1F) * 255 / 31, ((v >> 5) & 0x1F) * 255 / 31, ((v >> 10) & 0x1F) * 255 / 31, 255); + } + return clut; + } + + bool read(QIODevice *d) + { + m_rawHeader = d->read(HEADER_SIZE); + if (m_rawHeader.size() != HEADER_SIZE) { + return false; + } + auto o = offset() - HEADER_SIZE; + if (o > kMaxQVectorSize - HEADER_SIZE) { + return false; + } + m_rawHeader.append(d->read(o)); + return isValid(); + } + + bool peek(QIODevice *d) + { + m_rawHeader = d->peek(HEADER_SIZE); + if (m_rawHeader.size() != HEADER_SIZE) { + return false; + } + auto o = offset(); + if (o > kMaxQVectorSize - HEADER_SIZE) { + return false; + } + if (o > m_rawHeader.size()) { + m_rawHeader = d->peek(o); + } + return isValid(); + } + + bool jumpToImageData(QIODevice *d) const + { + if (d->isSequential()) { + if (auto sz = std::max(offset() - quint32(m_rawHeader.size()), quint32())) { + return d->read(sz).size() == sz; + } + return true; + } + return d->seek(offset()); + } +}; + +class TIMHandlerPrivate +{ +public: + TIMHandlerPrivate() {} + ~TIMHandlerPrivate() {} + + TIMHeader m_header; +}; + +TIMHandler::TIMHandler() + : QImageIOHandler() + , d(new TIMHandlerPrivate) +{ +} + +bool TIMHandler::canRead() const +{ + if (canRead(device())) { + setFormat("tim"); + return true; + } + return false; +} + +bool TIMHandler::canRead(QIODevice *device) +{ + if (!device) { + qCWarning(LOG_TIMPLUGIN) << "TIMHandler::canRead() called with no device"; + return false; + } + + TIMHeader h; + if (!h.peek(device)) { + return false; + } + + return h.isSupported(); +} + +bool TIMHandler::read(QImage *image) +{ + auto&& header = d->m_header; + + if (!header.read(device())) { + qCWarning(LOG_TIMPLUGIN) << "TIMHandler::read() invalid header"; + return false; + } + + auto img = imageAlloc(header.size(), header.format()); + if (img.isNull()) { + qCWarning(LOG_TIMPLUGIN) << "TIMHandler::read() error while allocating the image"; + return false; + } + + if (img.format() == QImage::Format_Indexed8) { + auto pal = header.palette(); + if (pal.isEmpty()) { + qCWarning(LOG_TIMPLUGIN) << "TIMHandler::read() error while reading the palette"; + return false; + } + img.setColorTable(pal); + } + + auto d = device(); + if (!header.jumpToImageData(d)) { + qCWarning(LOG_TIMPLUGIN) << "TIMHandler::read() error while seeking image data"; + return false; + } + + auto size = std::min(img.bytesPerLine(), qsizetype(header.strideSize())); + QByteArray tmpBuff; + auto conv_4bpp = (header.type() == TYPE_4BPP || header.type() == TYPE_IDX_4BPP); + if (conv_4bpp && size * 2 <= img.bytesPerLine()) { + tmpBuff.resize(size); + } + for (auto y = 0, h = img.height(); y < h; ++y) { + auto line = reinterpret_cast(img.scanLine(y)); + auto tbuf = tmpBuff.isEmpty() ? line : tmpBuff.data(); + if (d->read(tbuf, size) != size) { + qCWarning(LOG_TIMPLUGIN) << "TIMHandler::read() error while reading image scanline"; + return false; + } + if (conv_4bpp) { + for (auto x = 0, w = qint32(tmpBuff.size()); x < w; ++x) { + auto &&v = tmpBuff.at(x); + line[x * 2 + 1] = (v >> 4) & 0xF; + line[x * 2] = v & 0xF; + } + } + } + + if (img.format() == QImage::Format_RGB555) { + img.rgbSwap(); + } + + *image = img; + return true; +} + +bool TIMHandler::supportsOption(ImageOption option) const +{ + if (option == QImageIOHandler::Size) { + return true; + } + if (option == QImageIOHandler::ImageFormat) { + return true; + } + return false; +} + +QVariant TIMHandler::option(ImageOption option) const +{ + QVariant v; + + if (option == QImageIOHandler::Size) { + auto&& h = d->m_header; + if (h.isValid()) { + v = QVariant::fromValue(h.size()); + } else if (auto d = device()) { + if (h.peek(d)) { + v = QVariant::fromValue(h.size()); + } + } + } + + if (option == QImageIOHandler::ImageFormat) { + auto&& h = d->m_header; + if (h.isValid()) { + v = QVariant::fromValue(h.format()); + } else if (auto d = device()) { + if (h.peek(d)) { + v = QVariant::fromValue(h.format()); + } + } + } + + return v; +} + +QImageIOPlugin::Capabilities TIMPlugin::capabilities(QIODevice *device, const QByteArray &format) const +{ + if (format == "tim") { + return Capabilities(CanRead); + } + if (!format.isEmpty()) { + return {}; + } + if (!device->isOpen()) { + return {}; + } + + Capabilities cap; + if (device->isReadable() && TIMHandler::canRead(device)) { + cap |= CanRead; + } + return cap; +} + +QImageIOHandler *TIMPlugin::create(QIODevice *device, const QByteArray &format) const +{ + QImageIOHandler *handler = new TIMHandler; + handler->setDevice(device); + handler->setFormat(format); + return handler; +} + +#include "moc_tim_p.cpp" diff --git a/src/imageformats/tim.json b/src/imageformats/tim.json new file mode 100644 index 0000000..04371a9 --- /dev/null +++ b/src/imageformats/tim.json @@ -0,0 +1,4 @@ +{ + "Keys": [ "tim" ], + "MimeTypes": [ "image/x-tim" ] +} diff --git a/src/imageformats/tim_p.h b/src/imageformats/tim_p.h new file mode 100644 index 0000000..2494f86 --- /dev/null +++ b/src/imageformats/tim_p.h @@ -0,0 +1,42 @@ +/* + This file is part of the KDE project + SPDX-FileCopyrightText: 2026 Mirco Miranda + + SPDX-License-Identifier: LGPL-2.0-or-later +*/ + +#ifndef KIMG_TIM_P_H +#define KIMG_TIM_P_H + +#include +#include + +class TIMHandlerPrivate; +class TIMHandler : public QImageIOHandler +{ +public: + TIMHandler(); + + bool canRead() const override; + bool read(QImage *image) override; + + bool supportsOption(QImageIOHandler::ImageOption option) const override; + QVariant option(QImageIOHandler::ImageOption option) const override; + + static bool canRead(QIODevice *device); + +private: + const QScopedPointer d; +}; + +class TIMPlugin : public QImageIOPlugin +{ + Q_OBJECT + Q_PLUGIN_METADATA(IID "org.qt-project.Qt.QImageIOHandlerFactoryInterface" FILE "tim.json") + +public: + Capabilities capabilities(QIODevice *device, const QByteArray &format) const override; + QImageIOHandler *create(QIODevice *device, const QByteArray &format = QByteArray()) const override; +}; + +#endif // KIMG_TIM_P_H