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?


  • Moderators

    Hi @Dmitiy-Tyugin
    There are some options to improve its performance namely setting RenderTarget-enum as RenderTarget-enum and setting PerformanceHint too.



  • Hi, @p3c0
    thanks for hints, but no luck. Tried to set in painted item constructor separately and both

    setRenderTarget(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?


  • Moderators

    @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 performance

    QRgb *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;
    

  • Moderators

    @Dmitiy-Tyugin Thanks for sharing. Perhaps you also try posting this question on Qt Interest Mailing List.


Log in to reply
 

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