About rendering a QML Item into pixmap



  • So I've got this case where I'm rendering a QML Item into a pixmap to be used as the source of an Image {} in the view to do some magic.. so my setup is that I've got an object to grab the item into QPixmap and another as QDeclarativeImageProvider to provide that bitmap into an Image. The following code grabs an item:

    @
    void Capture::save(QDeclarativeItem* item)
    {
    delete m_pixmap;
    m_pixmap = NULL;

    m_pixmap = new QPixmap(item->width(), item->height());
    QPainter painter(m_pixmap);
    QStyleOptionGraphicsItem option;
    item->paint(&painter, &option, NULL);
    

    }
    @

    ..but the problem is that only the top-level Item is rendered and none of its children. Say I have this QML:

    @
    Item {
    id: root

    Rectangle {
    id: fooRect
    width: parent.width
    height: parent.height

      gradient: Gradient {
          GradientStop { position: 0.0; color: "#8888FF" }
          GradientStop { position: 1.0; color: "#8888AA" }
      }
    
      Text {
          id: pageHeader
          text: qsTr("View 1")
          anchors.top: parent.top
          anchors.left: parent.left
          font.pixelSize: 40
          color: "white"
      }
    

    }

    function grab() {
    capture.save(fooRect);
    }
    }
    @

    After grab() the pixmap would contain the gradient from the fooRect but not the text from pageHeader.

    Now, I obviously can recurse into QGraphicsItem::childItems() breadth-first and render all children manually, but this will require dabbling with the hierarchial transforms. So, there must be an easier way. What is it? :D

    • Matti


  • In case someone's interested, here's how I ended up doing this. It was only tested in my own special case where it works, it might not work properly elsewhere. The draw order is important.. anyway:

    @
    void renderItem(QGraphicsItem* item, QPainter* painter,
    QStyleOptionGraphicsItem* option,
    const QTransform& baseTransform)
    {
    QTransform itemTransform = item->sceneTransform();
    itemTransform *= baseTransform;
    painter->setWorldTransform(itemTransform, false);
    item->paint(painter, option, NULL);

    // Recurse into children
    //TODO this should be breadth-first not depth-first!
    foreach ( QGraphicsItem* child, item->childItems() )
    {
        renderItem(child, painter, option, baseTransform);
    }
    

    }

    void Capture::save(QDeclarativeItem* item)
    {
    delete m_pixmap;
    m_pixmap = NULL;

    m_pixmap = new QPixmap(item->width(), item->height());
    QPainter painter(m_pixmap);
    QStyleOptionGraphicsItem option;
    
    // Get the inverse transform of the root item's scene transform; all the
    // children's transforms will be transformed by this in order to bring
    // their coordinate systems from the scene space to the root item space
    QTransform inverse = item->sceneTransform().inverted();
    
    // Recursively render the item and all its children
    renderItem(item, &painter, &option, inverse);
    

    }
    @

    So the beef is that we're grabbing the scene transforms (which indicated how the items are positioned / oriented in relation to the root window/"scene") of each item and setting that to the painter. We're also transforming those transforms by the inverse transform of the root item we're drawing to bring the coordinate systems from the "scene space" into the root item's coordinate space.

    • Matti

    ps. EDIT: could we please have the annotated code block to always (independent of the browser window width) allow for at least 80 characters per line without wrapping the lines? Would make the code whole lot more readable.


Log in to reply
 

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