Qt5 QML ListView contents zoom - image

I am trying to implement an image viewer (sort of):
model/view approach
using both c++ and qml
the images are just a ListView filled up with Image (delegate) items
Here is a piece of code:
property double zoomFactor: 1.5;
property double imgScale: 1;
CustomToolBar
{
id: toolBar
onZoomInSignal:
{
imgScale = imgScale*zoomFactor;
}
onZoomOutSignal:
{
imgScale = imgScale/zoomFactor;
}
onZoomFitSignal:
{
imgScale = 1;
}
}
Rectangle
{
id: bgRect
Layout.fillWidth: true
Layout.fillHeight: true
color: "Grey"
anchors.margins: 10
ScrollView
{
id: scrollView
anchors.fill: parent
ListView
{
id: listView
anchors.fill: parent
clip: true
spacing: 10
model: listItemModel
anchors.top: parent.top
anchors.bottom: parent.bottom
anchors.left: parent.left
anchors.right: parent.right
anchors.margins: 10
boundsBehavior: Flickable.StopAtBounds
delegate: Image
{
id: item_id
anchors.horizontalCenter: parent.horizontalCenter
source: item_img_path + "/" + Math.random()
scale: imgScale
}
}
}
}
The images are loaded correctly, I need to be able to zoom them.
In order to zoom I just modify the scale property of the Image delegate.
Images not scaled (scale = 1) CORRECT:
Images unzoomed (scale = 1/1.5) WRONG! images spacing (?) is being incremented:
Images zoomed (scale = 1.5) WRONG! images overlap:
As you can see, zoom minus increments the spacing (I'm quite not sure it is really the spacing) between the images, and zoom plus overlaps the images.
Furthermore, it would be nice to have the horizontal scrollbar for the ScrollView when zooming in, but I cannot achieve this.
Can anyone help me?
Thanks
EDIT:
Following the solution proposed by grillvott the images are zoomed in/out correctly, but the whole ListView is getting smaller/bigger with them:
The result should be instead something like this (gimp mode ON):
Any idea?

I don't think that scale will take any boundaries into consideration. You could encapsulate the image and use fillMode to make sure the image scales accordingly:
delegate: Item {
anchors.horizontalCenter: parent.horizontalCenter
width: img.sourceSize.width * imgScale
height: img.sourceSize.height * imgScale
Image {
id: img
anchors.fill: parent
source: item_img_path + "/" + Math.random()
fillMode: Image.Stretch
}
}

Related

QML slow performance animating a lot of rectangles

I'm creating a "dotted paper" component for my QML app. Essentially this components consists of a dotted background that the user can zoom upon. To create this I used the following code:
DottedPaper.qml
import QtQuick 2.15
Rectangle{
id: page
property int spacing: 50
// this property changes the spacing between the dots
property real contentScale: 1.0
Repeater{
id: rowRepeater
model: 200
Repeater{
id: row
property int rowIndex: index
model: 200
Rectangle{
id: circle
color: "red"
width: 4; height: 4
x: index * spacing * page.contentScale + width/2;
y: rowIndex * spacing * page.contentScale+ height/2
radius: width/2
}
}
}
}
Then in my main file I have the following:
import QtQuick 2.15
import QtQuick.Window 2.15
import QtQuick.Controls 2.15
Window {
width: 640
height: 480
visible: true
title: qsTr("Hello World")
Flickable{
clip: true // doesn't improve anything
anchors.fill: parent
contentWidth: paper.width
contentHeight: paper.height
DottedPaper{
id: paper
width: 10000; height: 10000;
clip: true
}
}
// the slider changes the content scale of the dotted paper
Slider{
from: 1
to: 3
onValueChanged: paper.contentScale = value
}
}
The problem with this code is that it's slow. Upon checking the application does not render the content at 60fps, but I get around 45 - 50 fps on my desktop. I suspect the issues is that QML is reevaluating the contentScale property binding for each circle in the background even if the circle is not visible on the screen. What is the best way to prevent this?
It works a lot better if you change your Repeaters to be ListViews. That's because a ListView is smart enough to only create enough delegates as needed for the visible space. (At first I thought a GridView would work, but that doesn't give you enough control over rows/columns.)
Rectangle{
id: page
property int cellWidth: 50 * contentScale
property int dotWidth: 4
// this property changes the spacing between the dots
property real contentScale: 1.0
ListView {
id: grid
model: 200
anchors.fill: parent
anchors.leftMargin: page.dotWidth / 2
anchors.topMargin: page.dotWidth / 2
spacing: page.cellWidth - page.dotWidth
delegate: ListView {
model: 200
width: window.width
height: page.dotWidth
orientation: ListView.Horizontal
spacing: page.cellWidth - page.dotWidth
property int rowIndex: index
delegate: Rectangle{
id: circle
color: "red"
width: page.dotWidth; height: page.dotWidth
radius: width/2
}
}
}
}

Change height of rows based on item depth in QML Treeview

I am trying to change the height of rows in my TreeView based on the item depth in the tree. The itemDelegate has access to the depth property (so I can change height of the item), but the row height can only be set from the rowDelegate, which can't see item depth.
As you can see from example I need to change row heights to match depth. How can the rowdelegate determine the item depth from the model?
Component {
id: delegateMenuItem
Column {
height: 100 // No effect
spacing: 20 // No effect
bottomPadding: 20 // No effect
Rectangle {
border {
color: "blue"
width: 2
}
color: "red"
height: itemText.height * 2
width: 300
Text {
id: itemText
text: qsTr(styleData.value)+"-"+height
color: "white"
font.pointSize: fontSize(styleData.depth)
}
}
}
}
Using TreeView from QQC1, I suggest that you try setting the implicitHeight for the itemDelegate and not setting any height for the rowDelegate. So try changing your snippet to:
itemDelegate: Rectangle {
id: delegateMenuItem
border { color: "blue"; width: 2 }
color: "red"
width: ...
implicitHeight: styleData.depth * ...
Text {
id: itemText
anchors.fill: parent
...
}
}
(In your current implementation delegateMenuItem does not have any implicitHeight. Meaning its height is probably 0. Also the ColumnLayout as you are using it is useless so I have removed it.)
I have not tried the above. However I have tried doing what you want to do with TreeView from Qt marketplace. It is designed to work with QQC2 and is based on TableView from QQC2 so you can use rowHeightProvider.

qml flickable canvas load content doesn't resize and cannot flick

I have a Flickable with a child Canvas and Image. I am loading an image into the source of the Image on a button click. That all works.
The issue is the flickable doesn't refresh its dimensions to that of the image so that the whole image is "flickable" and I can scroll around it (it's bigger than the viewable window). However it does refresh if I resize the window and I can then flick around the image.
I guess this is because the window is being redrawn and qml is re-reading the img.width and img.height values to redraw the Flickable.
(FYI if I load an image in when I start the app, Flickable works as expected, it's only when I load an image on a button click, once the app is running that the window needs redrawing (or resizing) to get the Flickable to work as expected
Flickable {
id: flickableCanvas
anchors.fill: parent
Item {
id: source
width: Math.max(parent.width,img.width)
height: Math.max(parent.height,img.height)
Image
{
id: img
}
Canvas {
id: myCanvas
}
}
}
Connections {
target: QmlBridge
onLoadImage: {
img.source = p
myCanvas.requestPaint()
flickableCanvas.contentWidth = img.width
flickableCanvas.contentHeight = img.height
}
}
It looks like you are trying to access the width/height of the image before it had been loaded.
You should move your Flickable contentWidth/contentHeight assignment under the Image component:
Image {
id: im
onStatusChanged: {
if (status === Image.Ready) {
flickableCanvas.contentWidth = img.width
flickableCanvas.contentHeight = img.height
}
}
}
// ...
Connections {
target: QmlBridge
onLoadImage: {
img.source = p
myCanvas.requestPaint()
// flickableCanvas.contentWidth = img.width
// flickableCanvas.contentHeight = img.height
}
}
But I think you don't even have to manually set these properties. You can do much simpler by just binding them to your img width/height:
Flickable {
id: flickableCanvas
anchors.fill: parent
contentWidth: img.width // no need for future assignment
contentHeight: img.height
// ...
}

How to set a red outline border while using BorderImage

I am using BorderImage to display images on a GridView.
Following is sort of what I am doing:
BorderImage {
source: some_image
asynchronous: true
cache: false
}
I want to set a red outline border to my image. How can I do that ?
I looked at the Border.left, Border.right properties but they just stretch the image.
You add a Rectangle to your (Border)Image
BorderImage {
source: some_image
Rectangle {
anchors.fill: parent
anchors.margins: -border.width
z: -1
border.width: 4
color: 'red'
}
}

QML/QtQuick2 Anchor Item to static location on a scaled image

What I am attempting to do is anchor an Item over a particular place over an Image. The Image needs to use Image.PreserveAspectFit for the fillMode. The problem I am having is that the height/width/paintedWidth/paintedHeight of the Image are for the entire image "canvas" (I'm not sure what it's actually called), not the drawn part of the image itself. So I can't anchor to the actual image.
See code below for an example I tried with anchors (red rectangle) and with a child rectangle and x,y coordinates (green rectangle).
import QtQuick 2.3
import QtQuick.Window 2.2
Window {
visible: true
height: 400
width: 400
Image {
id: image
anchors.fill: parent
fillMode: Image.PreserveAspectFit
source: "image.jpg"
Rectangle {
id: bottomRight
width: 40
height: 40
color: "green"
x: parent.width * 0.75
y: parent.height * 0.75
}
}
Rectangle {
id: topLeft
width: 40
height: 40
color: "red"
anchors.top: image.top
anchors.left: image.left
anchors.topMargin: image.height * 0.25
anchors.leftMargin: image.width * 0.25
}
}
When you change the size of the window the placement of the rectangles is not in the same place over the image. I'd post some screenshots but I don't have enough reputation yet!
I poked around through the widget tree using the debugger but I can't seem to find any properties that provide the information about the scaling that I could use to calculate placement of the rectangles.
Update
I used the following functions to calculate the margins since I will be using potentially lots of these overlays.
function horMargin(val)
{
return ((image.width - image.paintedWidth)/2 + image.paintedWidth * val)
}
function verMargin(val)
{
return ((image.height - image.paintedHeight)/2 + image.paintedHeight * val)
}
Did you try to use paintedWidth and paintedHeight? :)
This will give you a rectangle that fills the painted image:
import QtQuick 2.3
import QtQuick.Window 2.2
Window {
visible: true
height: 400
width: 400
Image {
id: image
anchors.fill: parent
fillMode: Image.PreserveAspectFit
source: "image.jpg"
}
Rectangle {
x: (image.width - image.paintedWidth) / 2
y: (image.height - image.paintedHeight) / 2
width: image.paintedWidth
height: image.paintedHeight
color: "transparent"
border.color: "darkorange"
border.width: 2
}
}
From there, you can work out how to apply the margins that you need.

Resources