Custom image plugin testing difficulties



  • I just made a custom image plugin to read Playstation texture format files (*.tim) into Qt programs that use QImage. I made sure to try and follow the details when implementing and it compiles successfully without any warnings. However, I made a simple tim2png program that's literally just this, with some qDebug() output I used omitted:

    #include <QImage>
    
    int main()
    {
    	QImage img;
    	img.load("0025.tim", "tim");
    	img.save("0025.png");
    	return 0;
    }
    

    However, when running the test program, the program exits immediately. A bit of qDebug() statements show that the image is not loaded nor saved successfully and I wouldn't know what exception class to usew to diagnose this. I've tried puting my plugin library in the same directory and then in an "imageformats" subdirectory with no luck. As for the plugin itself, this is a bit lengthy, but please bear with me:

    // timplugin.h
    #ifndef _TIMPLUGIN_H
    #define _TIMPLUGIN_H
    
    #include <QByteArray>
    #include <QImageIOPlugin>
    
    class QStringList;
    class QImageIOHandler;
    class QIODevice;
    
    class TimPlugin final: public QImageIOPlugin
    {
    	Q_OBJECT
    	Q_PLUGIN_METADATA(IID "TimPlugin" FILE "tim.json")
    public:
    	QStringList keys() const;
    	Capabilities capabilities(QIODevice *dev, const QByteArray &fmt) const;
    	QImageIOHandler* create(QIODevice *dev, const QByteArray &fmt = QByteArray()) const;
    };
    
    #endif // _TIMPLUGIN_H
    
    // timplugin.cpp
    
    #include <QIODevice>
    #include <QStringList>
    
    #include "timhandler.hpp"
    #include "timplugin.hpp"
    
    QStringList TimPlugin::keys() const
    {
    	return QStringList() << "tim";
    }
    
    QImageIOPlugin::Capabilities TimPlugin::capabilities(QIODevice *dev, const QByteArray &fmt) const
    {
    	if (fmt == "tim") return Capabilities(CanRead);
    	if (!(fmt.isEmpty() && dev->isOpen())) return 0;
    	
    	Capabilities cap;
    	if (dev->isReadable()) cap |= CanRead;
    	
    	return cap;
    }
    
    QImageIOHandler* TimPlugin::create(QIODevice *dev, const QByteArray &fmt) const
    {
    	QImageIOHandler *hnd = new TimHandler;
    	hnd->setDevice(dev);
    	hnd->setFormat(fmt);
    	return hnd;
    }
    
    // timhandler.h
    #ifndef _TIMHANDLER_H
    #define _TIMHANDLER_H
    
    #include <QImageIOHandler>
    
    class TimHandler final: public QImageIOHandler
    {
    public:
    	bool canRead() const;
    	bool read(QImage *img);
    	QVariant option(ImageOption op) const override;
    	bool supportsOption(ImageOption op) const override;
    };
    
    #endif // _TIMHANDLER_H
    
    // timhandler.cpp
    #include <QDataStream>
    #include <QImage>
    #include <QIODevice>
    #include <QtGlobal>
    #include <QtMath>
    #include <QVariant>
    #include <QVector>
    
    #include "timhandler.hpp"
    
    #define BIT16 0.121568627451
    
    bool TimHandler::canRead() const
    {
    	return device()->peek(4) == "\x10\x0\x0\x0";
    }
    
    static inline quint32 rgba5551ToArgb32(quint16 c)
    {
    	quint32 r = ((c & 0xf800) >> 11) / BIT16;
    	quint32 g = ((c & 0x7c0) >> 6) / BIT16;
    	quint32 b = ((c & 0x3e) >> 1) / BIT16;
    	quint32 a = c & 1 ? 0 : 0xff;
    	
    	return ((a << 24) | (r << 16) | (g << 8) | b);
    }
    
    bool TimHandler::read(QImage *img)
    {
    	QDataStream ds(device());
    	ds.setByteOrder(QDataStream::LittleEndian);
    	quint32 magic, flags;
    	
    	ds >> magic >> flags;
    	if (ds.status() != QDataStream::Ok || magic != 0x10)
    		return false;
    	bool hasClut = flags & 0x8 ? true : false;
    	quint8 pxlMode = flags & 0x7;
    	
    	quint8 bpp;
    	switch (pxlMode)
    	{
    		case 0: bpp = 4; break;
    		case 1: bpp = 8; break;
    		case 2: bpp = 16; break;
    		case 3: bpp = 24; break;
    		default: bpp = 4;
    	}
    	
    	QVector<QRgb> clut;
    	if (hasClut)
    	{
    		quint32 colors, npals;
    		ds.skipRawData(12);
    		ds >> colors >> npals;
    		
    		for (quint16 i = 0; i < (colors*npals); i++)
    		{
    			quint16 c;
    			ds >> c;
    			clut.append(rgba5551ToArgb32(c));
    		}
    	}
    	
    	ds.skipRawData(8);
    	quint16 w, h;
    	ds >> w >> h;
    	w = (w*16)/bpp;
    	
    	if (img) delete img;
    	img = new QImage(w, h, QImage::Format_ARGB32);
    	if (hasClut) img->setColorTable(clut);
    	
    	for (quint16 y = 0; y < h; y++)
    	{
    		for (quint16 x = 0; x < w; x++)
    		{
    			switch (bpp)
    			{
    				case 4:
    				{
    					quint8 p;
    					ds >> p;
    					img->setPixel(x, y, p & 0xf0);
    					img->setPixel(x, y, p & 0xf);
    				}
    					break;
    				case 8:
    				{
    					quint8 p;
    					ds >> p;
    					img->setPixel(x, y, p);
    				}
    					break;
    				case 16:
    				{
    					quint16 p;
    					ds >> p;
    					img->setPixel(x, y, rgba5551ToArgb32(p));
    				}
    					break;
    			}
    		}
    	}
    	
    	return true;
    }
    
    bool TimHandler::supportsOption(ImageOption op) const
    {
    	switch (op)
    	{
    		case Size:
    			return true;
    		default:
    			return false;
    	}
    }
    
    QVariant TimHandler::option(ImageOption op) const
    {
    	switch (op)
    	{
    		case Size:
    		{
    			quint32 size;
    			quint16 w, h;
    			QByteArray ba = device()->peek(8);
    			QDataStream ds(&ba, QIODevice::ReadOnly);
    			ds >> size;
    			size -= 4; // 12 - TIM header + image size/palette x/y
    			ds.skipRawData(size);
    			ds >> w >> h;
    			return QSize(w, h);
    		}
    		default:
    			return QVariant();
    	}
    }
    

  • Lifetime Qt Champion

    Hi,

    You don't instantiate a QApplication so there's not way for QImage to know where to load your plugin from



  • Oops, instantiate... okay, my bad. Thanks

    EDIT: Still doesn't work, and QT_DEBUG_PLUGINS shows normal results:

    Found metadata in lib /home/admin/Documents/Qt-extra/imageformats/tim/timtest/imageformats/libtim.so, metadata=
    {
    "IID": "TimPlugin",
    "MetaData": {
    "Keys": [
    "tim"
    ]
    },
    "className": "TimPlugin",
    "debug": true,
    "version": 328705
    }


  • Lifetime Qt Champion

    The most simple is to copy your plugin in Qt's plugin folder or you can tell your application to search in the folder containing your plugin with QCoreApplication::addLibraryPath



  • Well as I showed above, with the QT_DEBUG_PLUGINS variable set, the program is picking it up. However, it still refuses to load it. Is the IID or keys array invalid? I figured if it was choking on the plugin code itself, it would either error, segfault, or spit out a corrupt PNG.


  • Lifetime Qt Champion

    Without seeing your code, it's pretty much Crystal Ball Debugging (™) I can't say



  • Well, I had posted my code in the original post. However, my handler class was tweaked a bit after spotting some errors, so here's the updated class code:

    EDIT: I did a few tests on QImageReader that should fail with unsupported format error, but I keep getting "unknown error." I attempted to load my ".pro" qmake file to see which error it would get but still unknown error. However, I compiled qtraw from Github, dropped it into an imageformats subdirectory and it ran fine.

    #include <QDataStream>
    #include <QImage>
    #include <QIODevice>
    #include <QtGlobal>
    #include <QtMath>
    #include <QVariant>
    #include <QVector>
    #include <QImageIOHandler>
    
    class TimHandler final: public QImageIOHandler
    {
    public:
    	bool canRead() const;
    	bool read(QImage *img);
    	QVariant option(ImageOption op) const override;
    	bool supportsOption(ImageOption op) const override;
    };
    
    #define BIT16 0.121568627451
    
    bool TimHandler::canRead() const
    {
    	return device()->peek(4) == "\x10\x0\x0\x0";
    }
    
    static inline quint32 rgba5551ToArgb32(quint16 c)
    {
    	quint32 r = ((c & 0xf800) >> 11) / BIT16;
    	quint32 g = ((c & 0x7c0) >> 6) / BIT16;
    	quint32 b = ((c & 0x3e) >> 1) / BIT16;
    	quint32 a = c & 1 ? 0 : 0xff;
    	
    	return ((a << 24) | (r << 16) | (g << 8) | b);
    }
    
    bool TimHandler::read(QImage *img)
    {
    	QDataStream ds(device());
    	ds.setByteOrder(QDataStream::LittleEndian);
    	quint32 magic, flags;
    	
    	ds >> magic >> flags;
    	if (ds.status() != QDataStream::Ok || magic != 0x10)
    		return false;
    	bool hasClut = flags & 0x8 ? true : false;
    	quint8 pxlMode = flags & 0x7;
    	
    	quint8 bpp;
    	switch (pxlMode)
    	{
    		case 0: bpp = 4; break;
    		case 1: bpp = 8; break;
    		case 2: bpp = 16; break;
    		case 3: bpp = 24; break;
    		default: bpp = 4;
    	}
    	
    	QVector<QRgb> clut;
    	if (hasClut)
    	{
    		quint32 colors, npals;
    		ds.skipRawData(12);
    		ds >> colors >> npals;
    		
    		for (quint16 i = 0; i < (colors*npals); i++)
    		{
    			quint16 c;
    			ds >> c;
    			clut.append(rgba5551ToArgb32(c));
    		}
    	}
    	
    	ds.skipRawData(8);
    	quint16 w, h;
    	ds >> w >> h;
    	w = (w*16)/bpp;
    	
    	if (img) delete img;
    	img = new QImage(w, h, QImage::Format_ARGB32);
    	if (hasClut) img->setColorTable(clut);
    	
    	for (quint16 y = 0; y < h; y++)
    	{
    		for (quint16 x = 0; x < w; x++)
    		{
    			switch (bpp)
    			{
    				case 4:
    				{
    					quint8 p;
    					ds >> p;
    					img->setPixel(x++, y, p & 0xf0);
    					img->setPixel(x, y, p & 0xf);
    				}
    					break;
    				case 8:
    				{
    					quint8 p;
    					ds >> p;
    					img->setPixel(x, y, p);
    				}
    					break;
    				case 16:
    				{
    					quint16 p;
    					ds >> p;
    					img->setPixel(x, y, rgba5551ToArgb32(p));
    				}
    					break;
    			}
    		}
    	}
    	
    	return true;
    }
    
    bool TimHandler::supportsOption(ImageOption op) const
    {
    	switch (op)
    	{
    		case Size:
    			return true;
    		default:
    			return false;
    	}
    }
    
    QVariant TimHandler::option(ImageOption op) const
    {
    	switch (op)
    	{
    		case Size:
    		{
    			quint32 size;
    			quint16 w, h;
    			QDataStream ds(device());
    			ds.skipRawData(8);
    			ds >> size;
    			size -= 4; // 12 - image size - palette x - palette y
    			ds.skipRawData(size);
    			ds >> w >> h;
    			return QSize(w, h);
    		}
    		default:
    			return QVariant();
    	}
    }
    

  • Lifetime Qt Champion

    Sorry about the code part, I had a problem when I read it first and only saw the end of it.
    What about a more simple json ?

    {
        "Keys": [ "tim" ],
        "MimeTypes": [ "image/x-tim" ]
    }
    
    


  • Well, while I could try that, I don't know if that's the mime type for TIM files. A Google search doesn't help either. My examination of qtraw shows that it too lacks a mime type entry. Thus, I thought it was unnecessary.

    EDIT: tried it anyway, still nothing


  • Lifetime Qt Champion

    Just thought of something… Your plugin code is correct but:

    img.load("0025.tim", "tim");

    "0025.tim" is a relative path, is that image in the same folder as your executable ? You should rather give the full path to it.



  • Same directory, yep. Though of course in a real world scenario this would be different.


  • Lifetime Qt Champion

    I forgot a correction I made:

    Q_PLUGIN_METADATA(IID "org.qt-project.Qt.QImageIOHandlerFactoryInterface" FILE "tim.json")



  • Yeah, I changed it to that in my progress trying to fix it, but no go. I booted up QtCreator and created a new project from Qt plugin template but I see nothing unusual in comparison with my code, aside from the constructor with QObject* parameter, which I skipped in my code because I saw no point for an empty constructor/destructor.



  • So after a little tinkering, I got qmake to generate main.moc which I thought was needed, but I'm still having issues with the plugin not being discovered. Additionally, the RGBA5551 conversion function was incorrect, which I fixed, but that shouldn't be the make-or-break factor.


  • Lifetime Qt Champion

    Not being discovered or not loaded ? Did you put your plugin in a imageformats subfolder ?



  • Yep. Even set target.path in my .pro file thinking that was the issue. Qt seems very nitpicky.

    EDIT: Just debugged again, found this gem of an error:

    QLibraryPrivate::loadPlugin failed on "/home/admin/Documents/Qt-extra/imageformats/qtim/timtest/imageformats/libqtim.so" : "Cannot load library /home/admin/Documents/Qt-extra/imageformats/qtim/timtest/imageformats/libqtim.so: (/home/admin/Documents/Qt-extra/imageformats/qtim/timtest/imageformats/libqtim.so: undefined symbol: _ZN11QTimHandler9setOptionEN15QImageIOHandler11ImageOptionERK8QVariant)"
    

  • Lifetime Qt Champion

    Which version of Qt are you using ?
    What happens if you try to load it by hand using QLibrary ?


Log in to reply
 

Looks like your connection to Qt Forum was lost, please wait while we try to reconnect.