PDA

View Full Version : Using QAbstractItemDelegate to display a TGA from a database



rakkar
27th August 2009, 21:42
I have the following code that should display any column that is a byte array as a 320x200 tga image. But the cells in the QTTableView are grey rather than the image. Also they don't size to 320x200, as the sizeHint() function is never called.

Any idea what I'm doing wrong?



#ifndef __IMAGE_DELEGATE_H_
#define __IMAGE_DELEGATE_H_

#include <QItemDelegate>

class SQLQueryItemDelegate : public QItemDelegate
{
Q_OBJECT
public:
SQLQueryItemDelegate(QWidget *parent = 0) : QItemDelegate(parent) {}

virtual void paint(QPainter *painter, const QStyleOptionViewItem &option,
const QModelIndex &index) const;

virtual QSize sizeHint(const QStyleOptionViewItem &option,
const QModelIndex &index) const;
};

#endif




#include "SQLQueryItemDelegate.h"
#include <QPainter>

void SQLQueryItemDelegate::paint(QPainter *painter, const QStyleOptionViewItem &option,
const QModelIndex &index) const
{
const QVariant &v = index.data();
if (v.type()==QVariant::ByteArray)
{
const QByteArray ba = v.toByteArray();
QImage siImage(320,200,QImage::Format_ARGB32);
siImage.fromData((const uchar*) ba.data(), ba.size());
QPixmap siPixmap;
siPixmap = QPixmap(320,200);
siPixmap.fromImage(siImage,Qt::AutoColor);
painter->drawPixmap(option.rect, siPixmap);

}
else
{
QItemDelegate::paint(painter,option,index);
}
}

QSize SQLQueryItemDelegate::sizeHint(const QStyleOptionViewItem &option,
const QModelIndex &index) const
{
const QVariant &v = index.data();
if (v.type()==QVariant::ByteArray)
{
return QSize(320,200);
}
else
{
return QItemDelegate::sizeHint(option,index);
}
}



Thanks, everyone is very helpful here.

rakkar
28th August 2009, 05:27
I figured it out. Maybe not all these steps are needed, but it works



QByteArray ba = v.toByteArray();
QBuffer buffer;
buffer.setBuffer(&ba);
buffer.open(QIODevice.ReadOnly);
QImageReader qir;
qir.setDevice(&buffer);
QImage image;
qir.read(&image);
QPixmap pixmap = QPixmap::fromImage(image);
painter->drawPixmap(option.rect, pixmap);

rakkar
28th August 2009, 06:05
I found that it can't load the images fast enough in some cases. I drag the scrollbar to view through the rows, but the scrollbar lags behind the mouse cursor. Is there a way to defer paint events for say 100 milliseconds, so if you're just skipping by that row it doesn't need to draw?

victor.fernandez
28th August 2009, 07:47
You may paint to cell blank when you don't have the data, then store the index in an array and use a timer with setSingleShot(true) and the interval you want. Connect the timeout() signal to a slot where you call view->update(index) for all the indexes that need to be repainted.



class SQLQueryItemDelegate : public QStyledItemDelegate
{
...
signals:
void paintingDeferred(const QModelIndex& index);
...
};

class SQLQueryDeferredPainter : public QObject
{
Q_OBJECT

public:
SQLQueryDeferredPainter(QAbstractItemView *view, SQLQueryItemDelegate *delegate, QObject *parent);

protected slots:
void paintLater(const QModelIndex& index);
void paintPendingIndexes();

protected:
QModelIndexList m_indexes;
QAbstractItemView *m_view;
QTimer m_timer;
};

SQLQueryDeferredPainter::SQLQueryDeferredPainter(Q AbstractItemView *view, SQLQueryItemDelegate *delegate, QObject *parent)
: QObject(parent)
{
m_view = view;

connect(delegate, SIGNAL(paintingDeferred(const QModelIndex&)),
this, SLOT(paintLater(const QModelIndex&)));

m_timer.setSingleShot(true);
m_timer.setInterval(100);
connect(&m_timer, SIGNAL(timeout()),
this, SLOT(paintPendingIndexes()));
}

void SQLQueryDeferredPainter::paintLater(const QModelIndex& index)
{
m_indexes.append(index);
m_timer.start();
}

void SQLQueryDeferredPainter::paintPendingIndexes()
{
foreach(const QModelIndex& index, m_indexes)
m_view->update(index);

m_indexes.clear();
}

wysota
28th August 2009, 08:17
Don't load the images on every refresh. Load them once and then store in a pixmap cache or even subclass your model and make it return the images directly as the decoration role for its indexes.

rakkar
28th August 2009, 17:41
I tried the deferred loading but it didn't look good while scrolling.

I'll load images in a thread. It will load images around the current view automatically. If you scroll, it will add images to load in a fixed-size buffer. That way, if you scroll fast, the buffer will overflow and you won't load images you won't see anyway.

rakkar
28th August 2009, 18:55
Actually, using a cache increased the performance enough I can use it. Here's the code



#ifndef __BYTE_ARRAY_AS_IMAGE_ITEM_DELEGATE_H_
#define __BYTE_ARRAY_AS_IMAGE_ITEM_DELEGATE_H_

#include <QItemDelegate>
#include <QTimer>

class ModelIndicesDeferredPainter;

// If an item is a byte array, wait 100 milliseconds, then load it as an image if possible.
class ByteArrayAsImageItemDelegate : public QItemDelegate
{
Q_OBJECT
public:
ByteArrayAsImageItemDelegate(QWidget *parent = 0) : QItemDelegate(parent) {}

virtual void paint(QPainter *painter, const QStyleOptionViewItem &option,
const QModelIndex &index) const;

// This is needed because you can't emit events from paint() since paint() is const
void SetDeferredPainter(ModelIndicesDeferredPainter *df) {deferredPainter=df;}

signals:
void paintingDeferred(const QModelIndex& index);

protected:
ModelIndicesDeferredPainter *deferredPainter;
};

class ModelIndicesDeferredPainter : public QObject
{
Q_OBJECT

public:
ModelIndicesDeferredPainter(QAbstractItemView *view, QObject *parent);
bool paintLater(const QModelIndex& index);

protected slots:
void paintPendingIndexes();

protected:
QModelIndexList indicesToPaint;
QAbstractItemView *storedItemView;

// Don't immediately load images. This way, if an image immediately goes out of viewable area, it is not drawn
QTimer timer;

// This is needed so we know not to just call paintLater() again from ByteArrayAsImageItemDelegate
bool isPaintingLater;
};


#endif




#include "ByteArrayAsImageItemDelegate.h"
#include <QPainter>
#include <QBuffer>
#include <QImageReader>
#include <QAbstractItemView>
#include <QPixmapCache>

// From RakNet
#include "SuperFastHash.h"

void ByteArrayAsImageItemDelegate::paint(QPainter *painter, const QStyleOptionViewItem &option,
const QModelIndex &index) const
{
const QVariant &v = index.data();
if (v.type()==QVariant::ByteArray)
{
QByteArray ba = v.toByteArray();
unsigned int hash = SuperFastHash(ba.data(),ba.length());
QString hashKey;
// Safer collision code commented out, in case I need it
//hashKey.sprintf("%i%i%i",index.row(),index.column(),hash);
// Faster, in case images are duplicated
hashKey.sprintf("%i",hash);
QPixmap pixmap;
bool foundPixmap = QPixmapCache::find(hashKey, pixmap);
bool paintLater;
if (foundPixmap==false)
{
paintLater=deferredPainter->paintLater(index);
if (paintLater==false)
{
// Load pixmap
QBuffer buffer;
buffer.setBuffer(&ba);
buffer.open(QIODevice::ReadOnly);
QImageReader qir;
qir.setDevice(&buffer);
QImage image;
if (qir.read(&image))
{
pixmap = QPixmap::fromImage(image);

// Save to cache
QPixmapCache::insert(hashKey,pixmap);
}
else
{
// Not an image?
QItemDelegate::paint(painter,option,index);
return;
}
}
}

if (foundPixmap==true || paintLater==false)
{
// Paint
QSize imageSize = pixmap.size();
QRect drawRect;
float scaleWidth, scaleHeight;
float drawWidth, drawHeight;
scaleWidth = (float) imageSize.width() / (float)option.rect.width();
scaleHeight = (float)imageSize.height() / (float)option.rect.height();
// Downscale by the larger of the two proportions
if (scaleHeight > scaleWidth)
{
drawWidth=(float)imageSize.width()/scaleHeight;
drawHeight=(float)imageSize.height()/scaleHeight;
}
else
{
drawWidth=(float)imageSize.width()/scaleWidth;
drawHeight=(float)imageSize.height()/scaleWidth;
}

float centerX = (float)option.rect.left() + (float)option.rect.width()/2.0;
float centerY = (float)option.rect.top() + (float)option.rect.height()/2.0;
drawRect.setLeft(centerX-drawWidth/2.0);
drawRect.setRight(centerX+drawWidth/2.0);
drawRect.setTop(centerY-drawHeight/2.0);
drawRect.setBottom(centerY+drawHeight/2.0);
painter->drawPixmap(drawRect, pixmap);
}

}
else
{
QItemDelegate::paint(painter,option,index);
}
}
ModelIndicesDeferredPainter::ModelIndicesDeferredP ainter(QAbstractItemView *view, QObject *parent)
: QObject(parent)
{
storedItemView = view;

timer.setSingleShot(true);
timer.setInterval(100);
connect(&timer, SIGNAL(timeout()),
this, SLOT(paintPendingIndexes()));
isPaintingLater=false;
}

bool ModelIndicesDeferredPainter::paintLater(const QModelIndex& index)
{
if (isPaintingLater)
return false;

if (indicesToPaint.contains(index)==false)
{
indicesToPaint.append(index);
timer.start();
}

return true;
}

void ModelIndicesDeferredPainter::paintPendingIndexes()
{
// This flag is needed so ByteArrayAsImageItemDelegate::paint does not just call paintLater again()
isPaintingLater=true;

foreach(const QModelIndex& index, indicesToPaint)
storedItemView->update(index);
storedItemView->repaint();

isPaintingLater=false;

indicesToPaint.clear();
}

wysota
30th August 2009, 22:58
Why are you deferring anything? Not that I see what you are deferring...