Qml OpacityMask reduces image quality - image

I have a Rectangle that contains an Image.
I'm trying to create a rounded image with a black border.
I've tried using an OpacityMask, but when I do, it reduces the image quality.
Also there is a very small amount of white space in some places between the border and the image.
I've provided the code below. The commented out lines are things I've tried to no avail. I've also tried making the Rectangle and Image siblings contained within an Item with a separate sibling OpacityMask but get the same result.
Does anyone know a better way of doing this.
Also does anyone know a better way of creating the border - at the moment I'm stopping the image covering the border by using
anchors.margins: imageRect.border.width
Rectangle {
id: imageRect
property int spacer: 10
property int spacedWidth: imageDel.width - spacer
anchors.horizontalCenter: parent.horizontalCenter
width: spacedWidth
height: spacedWidth / imageListModel.getImageWToHRatio()
border.color: "black"
border.width: 2
radius: imageRadius
//clip: true
Image {
id: image
source: "image://lmImageProvider/" + rowIndex + "_" + index
//sourceSize: Qt.size(parent.width, parent.height)
anchors.fill: parent
anchors.margins: imageRect.border.width
cache: false
//visible: false
//fillMode: Image.PreserveAspectCrop
layer.enabled: true
layer.smooth: true
//layer.textureSize: "600x900"
layer.effect: OpacityMask {
maskSource: imageRect
// cached: true
}
//mipmap: true
property bool reload: reloadImageRole
onReloadChanged: {
source = ""
source = "image://lmImageProvider/" + rowIndex + "_" + index
}
MouseArea {
anchors.fill: parent
onClicked: {
imageListModel.toggleSelected(rowIndex + "_" + index)
}
}
}
}

Still not sure why the above code doesn't work.
Also not sure why my original attempt at "making the Rectangle and Image siblings contained within an Item with a separate sibling OpacityMask" (which was based on another answer in Stack Overflow), didn't work.
But upon much further investigation, I found another answer which was a slight variation on the "making the Rectangle and Image siblings contained within an Item with a separate sibling OpacityMask" which did work. Updated code is below:
Rectangle {
id: imageRect
property int spacer: 10
property int spacedWidth: imageDel.width - spacer
anchors.horizontalCenter: parent.horizontalCenter
width: spacedWidth
height: spacedWidth / imageListModel.getImageWToHRatio()
border.color: "black"
border.width: indImageBorderWidth
radius: indImageRadius
Image {
id: image
source: "image://lmImageProvider/" + rowIndex + "_" + index
anchors.fill: parent
anchors.margins: imageRect.border.width - 2
cache: false
visible: false
property bool reload: reloadImageRole
onReloadChanged: {
source = ""
source = "image://lmImageProvider/" + rowIndex + "_" + index
}
}
Rectangle{
id: maskRect
anchors.fill: image
radius: indImageRadius - 3
visible: false
z: image.z + 1
}
OpacityMask {
anchors.fill: image
source: image
maskSource: maskRect
}
MouseArea {
anchors.fill: parent
onClicked: {
//imageListModel.toggleSelected(rowIndex + "_" + index)
console.log("Image Clicked: " + rowIndex + "_" + index)
}
}
}

Related

Image atlas in QML

I have an image like this one:
http://www.imagemagick.org/Usage/basics/result.gif
(it contains two rows of individual data: three square images in the upper row, and below another square and two 'empty' squares)
I want to use it inside a repeater, so that I get four image buttons, each with one of the subimages.
Is there a "texture-atlas" modules in QML?
I only found http://doc.qt.io/qt-5/qml-qtquick-sprite.html and I hope there is something that is better for my use-case.
As I said in comment I would do that using QQuickImageProvider since that's the fastest and the least memory intensive way. But QML is very flexible and you can do with it whatever you want. As for your question you can do as following:
Tile.qml
import QtQuick 2.9
Item {
id: container
property alias source: img.source
property int cellWidth: 1
property int cellHeight: 1
property int slideIndex: 0
clip: true
Image {
id: img
property int cols: img.paintedWidth / container.cellWidth
property int rows: img.paintedHeight / container.cellHeight
x: -container.cellWidth * Math.floor(container.slideIndex % cols)
y: -container.cellHeight * Math.floor(container.slideIndex / cols)
}
}
and usage:
import QtQuick 2.9
import QtQuick.Window 2.2
Window {
id: window
title: "Test"
visible: true
width: 600
height: 400
Row {
spacing: 10
anchors.centerIn: parent
Repeater {
model: 4
Tile {
width: 32
height: 32
cellWidth: 32
cellHeight: 32
slideIndex: index
source: "http://www.imagemagick.org/Usage/basics/result.gif"
}
}
}
}
The idea is to wrap the entire image into a container and clip it appropriately. Here I use index-based access to a slide, you can do that as you want.

MacOS dock-like component in QML

Using QtQuick, I have a row of 5 images in a repeater. I'd like to implement an animation on hover that's similar to the MacOS dock animation. Here's a picture for reference:
To further break it down, here's what I'm trying to accomplish. These images, on hover, should act as follows:
Hovered images expand
Neighboring images expand, but slightly less
Images don't overlap on expansion
Here is the code I have so far
Row {
spacing: 2
anchors.bottom: parent.bottom
anchors.bottomMargin: 30
anchors.horizontalCenter: parent.horizontalCenter
Repeater {
id: iconRepeater
model: iconColors()
Image {
source: "icons/" + modelData + ".png"
scale: mouseArea.containsMouse ? 1.5 : 1.0
MouseArea {
id: mouseArea
anchors.fill: parent
hoverEnabled: true
onClicked: endTimer()
}
Behavior on scale {
PropertyAnimation {
duration: 75
}
}
}
}
}
This expands the image you hover over, but I cannot seem to also effect the neighbors. Any advice is appreciated!
I'd suggest a little more robust solution, where you have control over the zoom factor and the spread and decay of influence:
Column {
Slider {
id: foff
from: 1
to: 5
stepSize: 1
value: 2
snapMode: Slider.SnapAlways
}
Slider {
id: sf
from: 0.5
to: 2.5
stepSize: 0.5
value: 0.5
snapMode: Slider.SnapAlways
}
Slider {
id: dmp
from: 1
to: 5
stepSize: 1
value: 1
snapMode: Slider.SnapAlways
}
}
Row {
id: row
anchors.bottom: parent.bottom
anchors.bottomMargin: 30
anchors.horizontalCenter: parent.horizontalCenter
property int falloff: foff.value // how many adjacent elements are affected
property int current: -1
property real scaleFactor: sf.value // that's how much extra it scales
property real damp: dmp.value // decay of influence
Repeater {
id: iconRepeater
model: 10
Rectangle {
width: 50 * pseudoScale
height: width
anchors.bottom: parent.bottom
color: "red"
border.color: "black"
property real pseudoScale: {
if (row.current == -1) return 1
else {
var diff = Math.abs(index - row.current)
diff = Math.max(0, row.falloff - diff)
var damp = row.falloff - Math.max(1, diff)
var sc = row.scaleFactor
if (damp) sc /= damp * row.damp
diff = diff / row.falloff * sc + 1
return diff
}
}
MouseArea {
id: mouseArea
anchors.fill: parent
hoverEnabled: true
onContainsMouseChanged: row.current = containsMouse ? index : -1
}
Behavior on pseudoScale {
PropertyAnimation {
duration: 150
}
}
}
}
}
It could be something along the lines of this:
Row {
anchors {
bottom: parent.bottom
left: parent.left
right: parent.right
}
Repeater {
id: rep
model: ['red', 'yellow', 'pink', 'green', 'teal', 'orchid', 'blue', 'orange']
property int currentIndex: -10
delegate: Rectangle {
anchors.bottom: parent.bottom
// Calculate the width depending on the currently hovered element
width: (rep.currentIndex === index ? 100 : ((rep.currentIndex - index) === 1 || (rep.currentIndex - index) === -1 ? 80 : 50))
height: width
radius: width / 2
color: modelData
MouseArea {
anchors.fill: parent
hoverEnabled: true
// onEntered/Exited did not react. This will work.
onContainsMouseChanged: {
if (containsMouse) rep.currentIndex = index
else rep.currentIndex = -10 // -10 is safe
}
}
// Makes the movement smooth
Behavior on width {
NumberAnimation {}
}
}
}
}
I tried to put in the necessary explainations as comment in the code.
The only thing that would need some tweeking is, that the dots will be initially shifted when the first resizing happens. Putting it on a flickable and some manual labour for the right position handeling could deal with that. Basically you need to shift the flickable by half the width change (in my case ~ 55 or so) to the left, when the mouse enters, and to the right when it leaves again.
You could also do so with a ListView, most likely, but due to the ever changing estimated size of the background, it might be more challanging to get the positioning right.

Particles with different images. [qml]

I am using particle systems. My particles come out from the center but I want different images to come out.
The file path of these images are stored in an array. I made a function to go through that arrangement and return what is in each position.
What returns is the path of each image to be sent to the source of the ImageParticle.
The problem is that if you scroll through the entire array but only return the path of the last image and logically I get a single image instead of the twenty that are stored in the array.
Someone could help me?
Help and suggestions are well accepted.
Here is my code:
import QtQuick 2.0
import QtQuick.Particles 2.0
Rectangle {
id: bg
width: 1920 //360
height: 1080 //360
color: "black"
ParticleSystem {
id: particleSys
}
Emitter{
id: particles
anchors.centerIn: parent
height: 1; width: 1
system: particleSys
emitRate: 30
lifeSpan: 4000
lifeSpanVariation: 500
maximumEmitted: 1000
size: 5
endSize: 200
velocity: TargetDirection{
targetX: 100; targetY: 100
targetVariation: 360
magnitude: 250
}
}
property var picturesList: [
"images/Image1.png", "images/Image2.png", "images/Image3.png", "images/Image4.png", "images/Image5.png", "images/Image6.png", "images/Image7.png", "images/Image8.png", "images/Image9.png", "images/Image10.png",
"images/Image11.png", "images/Image12.png", "images/Image13.png", "images/Image14.png", "images/Image15.png", "images/Image16.png", "images/Image17.png", "images/Image18.png", "images/Image19.png", "images/Image20.png"
]
function getImage(arr){
var flag = "";
for(var i = 0; i < arr.length ; i++){
flag = arr[i];
console.log("Image: " + arr[i] + " flag: " + flag ) //To check if array is traversing.
}
return flag;
}
ImageParticle{
property var link: getImage(picturesList)
source: link
system: particleSys
}
// To check which image is returned.
MouseArea {
anchors.fill: parent
onClicked: {
console.log(getImage(picturesList))
}
}
}
I am the person who asked the previous question and here I bring the answer.
I know very well that there can be several ways that solve the problem, if you know another way I invite you to share it.
Suggestions about the response are welcome.
Here I share the code:
import QtQuick 2.0
import QtQuick.Particles 2.0
Rectangle {
id: bg
width: 1920
height: 1080
color: "black"
ParticleSystem {
id: particleSys
}
Emitter{
id: particles
anchors.centerIn: parent
height: 1; width: 1
system: particleSys
emitRate: 30
lifeSpan: 4000
lifeSpanVariation: 500
maximumEmitted: 1000
size: 5
endSize: 200
velocity: TargetDirection{ //4
targetX: 100; targetY: 100
targetVariation: 360
magnitude: 250
}
}
property var picturesList: [
"images/Image1.png", "images/Image2.png", "images/Image3.png", "images/Image4.png", "images/Image5.png", "images/Image6.png", "images/Image7.png", "images/Image8.png", "images/Image9.png", "images/Image10.png",
"images/Image11.png", "images/Image12.png", "images/Image13.png", "images/Image14.png", "images/Image15.png", "images/Image16.png", "images/Image17.png", "images/Image18.png", "images/Image19.png", "images/Image20.png"
]
ItemParticle {
id: particle
system: particleSys
delegate: itemDelegate
}
Component {
id: itemDelegate
Rectangle {
id: container
width: 26*Math.ceil(Math.random()*3); height: width
color: 'white'
Image {
anchors.fill: parent
anchors.margins: 3
source: 'images/Image'+Math.ceil(Math.random()*20)+'.png'
}
}
}
}
As you can see, all the images in the folder will be output.
I simply change the "ImageParticle" to "ItemParticle" and I add a "Component" as a delegate where the desired image will be.
So we would not use the array (omit it in the previous code) since now only the path of the image is concatenated and a random number (to select the image).
I hope you serve them, and if you have a better implementation, please let them know.

QML changing size of image

I have an image object in QML (QtQuick 1.0), its height must be relative (in case of window scaling), but the problem is when I try to display this image in fullsize. Please look at the following part of my code:
Image {
id: selectionDialogImage
source: "qrc:/Images/myImage.png"
property int oldHeight: sourceSize.height
sourceSize.height: testsList.height/3
scale: 1
anchors.bottom: parent.bottom
anchors.left: parent.left
visible: false
property bool imageFullsize: false
MouseArea {
anchors.fill: parent
onClicked:
{
if(parent.imageFullsize)
{
parent.parent = selectionDialogQuestionText;
parent.sourceSize.width = undefined;
parent.sourceSize.height = testsList.height/3;
parent.anchors.horizontalCenter = undefined;
parent.anchors.verticalCenter = undefined;
parent.anchors.left = selectionDialogQuestionText.left;
parent.anchors.bottom = selectionDialogQuestionText.bottom;
parent.imageFullsize = false;
}
else
{
parent.parent = mainItem;
parent.sourceSize.height = parent.oldHeight;
//parent.sourceSize.height = undefined;
parent.sourceSize.width = mainItem.width;
parent.anchors.horizontalCenter = mainItem.horizontalCenter;
parent.imageFullsize = true;
}
}
}
}
selectionDialogQuestionText is a default parent of my image item. mainItem is the largest item, it has size of the whole window. So I want to have the image size set on the basis of width when I'm displaying it on a fullscreen, but in another state I want to scale the image setting its height.
When I set parent.sourceSize.width = mainItem.width;, image isn't scaled, but its height is as previous (very little) so its proportions are inappropriate.
Can I store old height of source image in any way? I need something like const, because property int oldHeight: sourceSize.heigh is relative. Or is there a way to restore default size of image and then set height/width?
UPDATE #1:
I tried to use method described by #Mitch:
property int oldHeight
Component.onCompleted: {
oldHeight = sourceSize.height
}
sourceSize.height: 250
but console.log("oldHeight="+parent.oldHeight) a few lines below shows oldHeight=250, not original height.
Can I store old height of source image in any way?
You can use Component.onCompleted:
Image {
// ...
Component.onCompleted: {
oldHeight = sourceSize.height
}
}

QML timer combined with transition

import QtQuick 2.0
Rectangle {
id: rec
property int img_in:2
property int img_out:3
Image {
id: imgout
source: "pics/"+img_out+".jpg"
opacity: 1
anchors.fill: parent
}
Image {
id: imgin
source: "pics/"+img_in+".jpg"
opacity: 0
anchors.fill: imgout
}
Timer {
interval: 5000
repeat: true
running: true
onTriggered: {
img_in = img_in+1
img_out = img_out+1
anim.running = true;
}
}
states: State {
name: "state1"
PropertyChanges { target: imgout; opacity: 0}
PropertyChanges { target: imgin; opacity: 1}
}
SequentialAnimation {
id: anim
NumberAnimation { target: imgin; properties: "opacity"; easing.type: Easing.InOutQuad; duration: 1500 }
NumberAnimation { target: imgout; properties: "opacity"; easing.type: Easing.InOutQuad; duration: 1500 }
}
}
I would like to know if the problem is in opacity or in pics number change. Pics do change (suddenly) but there is no animation. Inside onTriggered I have tried to use these options: states, SequentialAnimation plus transition. No help.
So I took your code and customized it a bit. It basically starts at pics/1.jpg fades it out while fading pics/2.jpg in, then continues to the next images. You should be able to use it and change it to suit your needs.
Importantly there is no need to do this with States rather use Behaviour on the Image elements opacity
Hope this answers your question! It does make for a nice slideshow!
import QtQuick 2.0
Rectangle {
id: rec
// [1] First image is to be display is pics/1.jpg,
// followed by 2.jpg, 3.jpg, etc...
property int currentIndex: 1
property int nextIndex: 2
// [2] When swapping the image sources we need
// to block the animation behaviour.
// By default turn it on.
property bool allowBehaviour: true
// [3] When the 'rec' is loaded
// set the current image to fade out
// and the next image to fade in.
Component.onCompleted: {
currentImage.opacity = 0;
nextImage.opacity = 1;
}
Image {
id: currentImage
source: "pics/" + currentIndex + ".jpg"
opacity: 1
anchors.fill: parent
// [4] Here we define that whenever we change the
// opacity we want it to animate. Notice the enable
// is tied to `allowBehaviour`
Behavior on opacity {
enabled: allowBehaviour
NumberAnimation { easing.type: Easing.InOutQuad; duration: 2500 }
}
}
Image {
id: nextImage
source: "pics/" + nextIndex + ".jpg"
opacity: 0
anchors.fill: currentImage
// [5] See [4] above.
Behavior on opacity {
enabled: allowBehaviour
NumberAnimation { easing.type: Easing.InOutQuad; duration: 2500 }
}
}
Timer {
interval: 2500
repeat: true
running: true
onTriggered: {
// [6] Block the Behaviour animation.
allowBehaviour = false;
// [7] Advance the indices.
currentIndex = nextIndex;
++nextIndex;
// [8] This is key, set the current
// image to visible and the next
// image to invisible. This happens
// instantly as the Behaviour is off.
currentImage.opacity = 1;
nextImage.opacity = 0;
// [9] Turn the behaviour so the
// opacity change at [10] will
// cause an animation.
allowBehaviour = true;
// [10] Like [3] set the current
// image to fade out and the
// next image to fade in.
currentImage.opacity = 0;
nextImage.opacity = 1;
}
}
}

Resources