Dynamic image in QML
-
I'm developing an app where user should erase image by eraser, it is similar to clear image by eraser in microsoft paint app. When the user drag a mouse with pressed button I need to clear appropriate point on an image.
It is pretty simple to do by subclassing from QQuickPainted item, redefine paint event and just draw original image, connect mouse move signal with coordinates and replace image pixel color to transparent at each point produced by user. But I'm stuck with a performance, since update() call takes too much time on mobile device.Another idea I have is to write fragment shader, but in this case I need a way to put to shader space a data array with cleared points produced by user. Have no idea how to do it.
Could you please help me to find a way to solve my task in QtQuick with good performance on mobile platform?
-
Hi @Dmitiy-Tyugin
There are some options to improve its performance namely setting RenderTarget-enum asRenderTarget-enum
and setting PerformanceHint too. -
Hi, @p3c0
thanks for hints, but no luck. Tried to set in painted item constructor separately and bothsetRenderTarget(QQuickPaintedItem::FramebufferObject); setPerformanceHint(QQuickPaintedItem::FastFBOResizing);
The difference: with flags paint event performs much faster, but until I do not change QImage object. When I change QImage object in mouse change slot called from qml, performance became the same: too slow.
void Surface::mouseChanged(int x, int y) { qDebug()<<"Surface::mouseChanged: "<<x<<y; m_img.setPixelColor(x,y,QColor(0,0,0,0)); update(); }
And paint event
void Surface::paint(QPainter *painter) { qDebug()<<"Surface::Paint"; painter->drawImage(0,0,m_img); }
Performance highly depends on image size, for small images painting works much faster.
Any other ideas?
-
@Dmitiy-Tyugin According to the setPixelColor documentation.
Warning: This function is expensive due to the call of the internal detach() function called within; if performance is a concern, we recommend the use of scanLine() or bits() to access pixel data directly.
Can you try these ?
-
Hi @p3c0,
thanks for suggestion. Tried, but no changes in performanceQRgb *rowData = (QRgb*)m_img.scanLine(j); rowData[i] = qRgba(0,0,0,0);
Looks like performance degradation happens on paint->drawImage.
Since small images work enough fast I decided to split my big image into a set of small images. Because user will not change the whole image simoultaneously in my scenario. It works with almost enough performance I need. So currently I'm stop digging for solutions.
By the way, it works only with render hint, so thanks a lot to @p3c0! I very appreciate your support.setRenderTarget(QQuickPaintedItem::FramebufferObject);
Here is an idea if somebody need it (it may have some bugs because this is quick dirty solution)
const int ImgSize = 100; int Surface::getImgId(int x, int y) { int col = x/ImgSize; int row = y/ImgSize; return col*1000 + row; } void Surface::paint(QPainter *painter) { for(const auto& it:m_imgs){ QImage* img = it.img; int x = it.x; int y = it.y; if(img!=nullptr) painter->drawImage(x,y,*img); } } void Surface::splitImages() { m_cols = m_img.width()/ImgSize; if(m_img.width()>ImgSize*m_cols) m_cols++; m_rows = m_img.height()/ImgSize; if(m_img.height()>ImgSize*m_rows) m_rows++; m_imgs.clear();//memory leak here, delete objects before clear for(int i=0;i<m_cols;i++) for(int j=0;j<m_rows;j++) { int startX = i*ImgSize; int startY = j*ImgSize; int endX = startX + ImgSize >= m_img.width() ? m_img.width()-1 : startX + ImgSize; int endY = startY + ImgSize >= m_img.height() ? m_img.height()-1 : startY + ImgSize; SubImage subImage; subImage.img = new QImage; *(subImage.img) = m_img.copy(startX,startY, endX - startX, endY-startY); subImage.x = startX; subImage.y = startY; m_imgs[getImgId(startX, startY)] = subImage; } } void Surface::mouseChanged(int x, int y) { const int BrushWidth = 50; const int BrushHeight = 50; int id = getImgId(x,y); SubImage subImg = m_imgs[id]; x = x-subImg.x; y = y-subImg.y; QImage* img = subImg.img; for(int i=x-BrushWidth/2;i<x+BrushWidth/2;i++) for(int j=y-BrushHeight/2;j<y+BrushHeight/2;j++) { if(i>=0 && i<img->width() && j>=0 && j<img->height()) { img->setPixelColor(i,j,QColor(0,0,0,0)); } } update(); }
And some code from header
struct SubImage{ int x,y; QImage* img; }; typedef QMap<int, SubImage> ImgMap; ImgMap m_imgs;
-
@Dmitiy-Tyugin Thanks for sharing. Perhaps you also try posting this question on Qt Interest Mailing List.