9

Is there any possible way to make grabToImage() buffer different from what's displayed in GUI? I'm using ChartView without legend in GUI and want to export it to PNG with legend. So I try:

chartView.legend.visible = true
chartView.update()
chartView.grabToImage(function(result) {
    console.log("grabbed")

    var path = filename.toString().replace(/^(file:\/{2})/,"");
    console.log(path + result.saveToFile(path));
    chartView.legend.visible = false
    update();
});

But both those updates happen only after the control comes out of this function, so I don't get legend drawn in PNG. Also I would like the appearance of legend to be unnoticable to user. Are there any ways to do that in QML?

eyllanesc
  • 235,170
  • 19
  • 170
  • 241
ProdoElmit
  • 1,067
  • 9
  • 22
  • You can try to create a second invisible ChartView and use a Timer to let Qt some time to process everything before you save it to PNG. – m7913d May 25 '17 at 19:01
  • @m7913d , that's a not too straight-forward way, but I'll try =) – ProdoElmit May 26 '17 at 17:31

1 Answers1

7

I am unsure, whether I got you quite right.

My suggestion is, to not grab the Item you display, but a copy of it, that you then modify as you like.
Copy here does not mean, that you have the same object twice, but that you render it twice by using a ShaderEffectSource. Before you grab this ShaderEffectSource to image, you can add anything you like.

In my example I display a simple Rectangle with a nice gradient. What I save is the same Rectangle that is extended by the Text 'I am legend'. The user won't see this text apearing in the view at any time.

Rectangle {
    id: commonView
    width: 200
    height: 200
    gradient: Gradient {
        GradientStop { position: 0; color: 'steelblue' }
        GradientStop { position: 1; color: 'orange' }
    }
    MouseArea {
        anchors.fill: parent

        // don't grab the rectangle itself.
        onClicked: legendView.grabToImage(function(result) {
            console.log(result.saveToFile("something.png"));
        });
    }
}

ShaderEffectSource {
    id: legendView
    visible: false // Does not need to be shown.
    sourceItem: commonView
    width: 200
    height: 200
    Text {
        anchors {
            right: parent.right
            bottom: parent.bottom
        }
        text: 'I am legend'
    }
}

You might optimize the performance by only having the ShaderEffectSource active or even created when needed.

You might use the ShaderEffectSource.live-property to disable updating of it. Then use scheduleUpdate() to trigger the update.

This might look like this:

Rectangle {
    id: commonView
    width: 200
    height: 200
    gradient: Gradient {
        GradientStop { position: 0; color: 'steelblue' }
        GradientStop { id: gs1; position: 1; color: 'orange' }
    }
    MouseArea {
        anchors.fill: parent
        onClicked: {
            gs1.position -= 0.1
            legendView.save()
        }
    }
}

ShaderEffectSource {
    id: legendView
    y: 200
    visible: false // Do not render it (will only be rendered when called grapToImage()
    sourceItem: commonView
    width: 200
    height: 200
    live: false // Will only be updated, when explicitly called for
    function save() {
        console.log('Schedule Save')
        scheduleUpdate() // explicitly update. grabToImage() will force rendering afterwards.
        legendView.grabToImage(function(result) {
                        console.log(result.saveToFile("something" +gs1.position.toFixed(1) + ".png"));
                    })
    }

    // Put additional stuff on it.
    Text {
        anchors {
            right: parent.right
            bottom: parent.bottom
        }
        text: 'I am legend!'
    }
}
  • That's almost exactly what was suggested in comments, but in my case that's not a good solution: due to performance reasons I can't simultaneously change both charts series (which are added dynamically), thus I should then create a copy of that chart when user wants to take a snapshot. Anyway it would require to double the chart - double the memory used, and that might become a huge bottleneck. – ProdoElmit Jun 22 '17 at 09:30
  • You don't double the data. The `ShaderEffectSource` copies only the visual representation. You only have one `ChartView`. Maybe I need to clarify my answer to point that out. – derM - not here for BOT dreams Jun 22 '17 at 09:32
  • Ok, I start to understand, that's an interesting thing. But there is another problem: I would like to paint legend using `ChartView`'s methods. But I don't know how to disable updating the `commonView` so that legend doesn't appear on it. Sure, if that's not possible at all, I can switch to painting legend myself the same way you do it with `Text`. – ProdoElmit Jun 22 '17 at 10:03
  • This seems to be problematic. I don't have `QtCharts` installed, but it seems the `Legend` is no `Item` and therefore might not be suitable as source for a `ShaderEffectSource`. Otherwise you might turn it `visible: false` and replace my `Text` by another `ShaderEffectSource` with source: `chartView.legend` which then should be visible again on the saved image. You might try it (maybe it is, though undocumented, and Item) - I don't know if it will work. – derM - not here for BOT dreams Jun 22 '17 at 10:12
  • Wow. I would definitely try that. Legend is just boolean, but maybe there is some undocumented legend item inside – ProdoElmit Jun 22 '17 at 10:18