Skip to content
Snippets Groups Projects
Commit 32a44c98 authored by Andreas Hatziiliou's avatar Andreas Hatziiliou Committed by François-Simon Fauteux-Chapleau
Browse files

chatview: image scaling algorithm

Revise the image scaling algorithm to deal with images
whose aspect ratio was too large or small. Deals with
oversized images as well as images which are too small.

GitLab: #1437
Change-Id: I454e64972ccde1415d80182a2aa89db9656fec1b
parent d1a8ec3a
Branches
Tags
No related merge requests found
...@@ -22,7 +22,6 @@ import QtQuick ...@@ -22,7 +22,6 @@ import QtQuick
import QtQuick.Controls import QtQuick.Controls
import QtQuick.Layouts import QtQuick.Layouts
import Qt5Compat.GraphicalEffects import Qt5Compat.GraphicalEffects
import net.jami.Models 1.1 import net.jami.Models 1.1
import net.jami.Constants 1.1 import net.jami.Constants 1.1
import net.jami.Adapters 1.1 import net.jami.Adapters 1.1
...@@ -44,12 +43,12 @@ Loader { ...@@ -44,12 +43,12 @@ Loader {
property int transferStatus: TransferStatus property int transferStatus: TransferStatus
onTidChanged: { onTidChanged: {
if (tid === "") { if (tid === "") {
sourceComponent = deletedMsgComp sourceComponent = deletedMsgComp;
} }
} }
onTransferStatusChanged: { onTransferStatusChanged: {
if (tid === "") { if (tid === "") {
sourceComponent = deletedMsgComp sourceComponent = deletedMsgComp;
return; return;
} else if (transferStatus === Interaction.TransferStatus.TRANSFER_FINISHED) { } else if (transferStatus === Interaction.TransferStatus.TRANSFER_FINISHED) {
mediaInfo = MessagesAdapter.getMediaInfo(root.body); mediaInfo = MessagesAdapter.getMediaInfo(root.body);
...@@ -64,7 +63,11 @@ Loader { ...@@ -64,7 +63,11 @@ Loader {
width: ListView.view ? ListView.view.width : 0 width: ListView.view ? ListView.view.width : 0
opacity: 0 opacity: 0
Behavior on opacity { NumberAnimation { duration: 100 } } Behavior on opacity {
NumberAnimation {
duration: 100
}
}
onLoaded: opacity = 1 onLoaded: opacity = 1
Component { Component {
...@@ -93,9 +96,9 @@ Loader { ...@@ -93,9 +96,9 @@ Loader {
bottomPadding: 6 bottomPadding: 6
topPadding: 6 topPadding: 6
leftPadding: 10 leftPadding: 10
text: UtilsAdapter.getBestNameForUri(CurrentAccount.id, Author) + " " + JamiStrings.deletedMedia ; text: UtilsAdapter.getBestNameForUri(CurrentAccount.id, Author) + " " + JamiStrings.deletedMedia
horizontalAlignment: Text.AlignLeft horizontalAlignment: Text.AlignLeft
width: Math.min((2 / 3) * parent.width, implicitWidth + 18, innerContent.width - senderMargin + 18) width: Math.min((2 / 3) * parent.width, implicitWidth + 18, innerContent.width - senderMargin + 18)
font.pointSize: JamiTheme.smallFontSize font.pointSize: JamiTheme.smallFontSize
font.hintingPreference: Font.PreferNoHinting font.hintingPreference: Font.PreferNoHinting
...@@ -107,8 +110,8 @@ Loader { ...@@ -107,8 +110,8 @@ Loader {
opacity: 0.5 opacity: 0.5
function getBaseColor() { function getBaseColor() {
bubble.isDeleted = true bubble.isDeleted = true;
return UtilsAdapter.luma(bubble.color) ? "white" : "dark" return UtilsAdapter.luma(bubble.color) ? "white" : "dark";
} }
} }
] ]
...@@ -124,9 +127,7 @@ Loader { ...@@ -124,9 +127,7 @@ Loader {
transferId: Id transferId: Id
property var transferStats: MessagesAdapter.getTransferStats(transferId, root.transferStatus) property var transferStats: MessagesAdapter.getTransferStats(transferId, root.transferStatus)
property bool canOpen: root.transferStatus === Interaction.TransferStatus.TRANSFER_FINISHED || isOutgoing property bool canOpen: root.transferStatus === Interaction.TransferStatus.TRANSFER_FINISHED || isOutgoing
property real maxMsgWidth: root.width - senderMargin - property real maxMsgWidth: root.width - senderMargin - 2 * hPadding - avatarBlockWidth - buttonsLoader.width - 24 - 6 - 24
2 * hPadding - avatarBlockWidth
- buttonsLoader.width - 24 - 6 - 24
isOutgoing: Author === CurrentAccount.uri isOutgoing: Author === CurrentAccount.uri
showTime: root.showTime showTime: root.showTime
...@@ -150,14 +151,12 @@ Loader { ...@@ -150,14 +151,12 @@ Loader {
enabled: canOpen enabled: canOpen
onHoveredChanged: { onHoveredChanged: {
if (enabled && hovered) { if (enabled && hovered) {
dataTransferItem.hoveredLink = UtilsAdapter.urlFromLocalPath(location) dataTransferItem.hoveredLink = UtilsAdapter.urlFromLocalPath(location);
} else { } else {
dataTransferItem.hoveredLink = "" dataTransferItem.hoveredLink = "";
} }
} }
cursorShape: enabled ? cursorShape: enabled ? Qt.PointingHandCursor : Qt.ArrowCursor
Qt.PointingHandCursor :
Qt.ArrowCursor
} }
Loader { Loader {
id: buttonsLoader id: buttonsLoader
...@@ -171,21 +170,21 @@ Loader { ...@@ -171,21 +170,21 @@ Loader {
switch (root.transferStatus) { switch (root.transferStatus) {
case Interaction.TransferStatus.TRANSFER_CREATED: case Interaction.TransferStatus.TRANSFER_CREATED:
case Interaction.TransferStatus.TRANSFER_FINISHED: case Interaction.TransferStatus.TRANSFER_FINISHED:
iconSource = JamiResources.link_black_24dp_svg iconSource = JamiResources.link_black_24dp_svg;
return terminatedComp return terminatedComp;
case Interaction.TransferStatus.TRANSFER_CANCELED: case Interaction.TransferStatus.TRANSFER_CANCELED:
case Interaction.TransferStatus.TRANSFER_ERROR: case Interaction.TransferStatus.TRANSFER_ERROR:
case Interaction.TransferStatus.TRANSFER_UNJOINABLE_PEER: case Interaction.TransferStatus.TRANSFER_UNJOINABLE_PEER:
case Interaction.TransferStatus.TRANSFER_TIMEOUT_EXPIRED: case Interaction.TransferStatus.TRANSFER_TIMEOUT_EXPIRED:
case Interaction.TransferStatus.TRANSFER_AWAITING_HOST: case Interaction.TransferStatus.TRANSFER_AWAITING_HOST:
iconSource = JamiResources.download_black_24dp_svg iconSource = JamiResources.download_black_24dp_svg;
return optionsComp return optionsComp;
case Interaction.TransferStatus.TRANSFER_ONGOING: case Interaction.TransferStatus.TRANSFER_ONGOING:
iconSource = JamiResources.close_black_24dp_svg iconSource = JamiResources.close_black_24dp_svg;
return optionsComp return optionsComp;
default: default:
iconSource = JamiResources.error_outline_black_24dp_svg iconSource = JamiResources.error_outline_black_24dp_svg;
return terminatedComp return terminatedComp;
} }
} }
Component { Component {
...@@ -216,9 +215,9 @@ Loader { ...@@ -216,9 +215,9 @@ Loader {
imageColor: JamiTheme.chatviewButtonColor imageColor: JamiTheme.chatviewButtonColor
onClicked: { onClicked: {
if (root.transferStatus === Interaction.TransferStatus.TRANSFER_ONGOING) { if (root.transferStatus === Interaction.TransferStatus.TRANSFER_ONGOING) {
return MessagesAdapter.cancelFile(transferId) return MessagesAdapter.cancelFile(transferId);
} else { } else {
return MessagesAdapter.acceptFile(transferId) return MessagesAdapter.acceptFile(transferId);
} }
} }
} }
...@@ -230,27 +229,21 @@ Loader { ...@@ -230,27 +229,21 @@ Loader {
TextEdit { TextEdit {
width: Math.min(implicitWidth, maxMsgWidth) width: Math.min(implicitWidth, maxMsgWidth)
topPadding: 10 topPadding: 10
text: CurrentConversation.isSwarm ? text: CurrentConversation.isSwarm ? transferName : location
transferName :
location
wrapMode: Label.WrapAtWordBoundaryOrAnywhere wrapMode: Label.WrapAtWordBoundaryOrAnywhere
font.pointSize: 11 font.pointSize: 11
renderType: Text.NativeRendering renderType: Text.NativeRendering
readOnly: true readOnly: true
color: UtilsAdapter.luma(bubble.color) color: UtilsAdapter.luma(bubble.color) ? JamiTheme.chatviewTextColorLight : JamiTheme.chatviewTextColorDark
? JamiTheme.chatviewTextColorLight
: JamiTheme.chatviewTextColorDark
MouseArea { MouseArea {
anchors.fill: parent anchors.fill: parent
cursorShape: canOpen ? cursorShape: canOpen ? Qt.PointingHandCursor : Qt.ArrowCursor
Qt.PointingHandCursor :
Qt.ArrowCursor
onClicked: function (mouse) { onClicked: function (mouse) {
if (canOpen) { if (canOpen) {
dataTransferItem.hoveredLink = UtilsAdapter.urlFromLocalPath(location) dataTransferItem.hoveredLink = UtilsAdapter.urlFromLocalPath(location);
Qt.openUrlExternally(new URL(dataTransferItem.hoveredLink)) Qt.openUrlExternally(new URL(dataTransferItem.hoveredLink));
} else { } else {
dataTransferItem.hoveredLink = "" dataTransferItem.hoveredLink = "";
} }
} }
} }
...@@ -261,23 +254,20 @@ Loader { ...@@ -261,23 +254,20 @@ Loader {
width: Math.min(implicitWidth, maxMsgWidth) width: Math.min(implicitWidth, maxMsgWidth)
bottomPadding: 10 bottomPadding: 10
text: { text: {
var res = "" var res = "";
if (transferStats.totalSize !== undefined) { if (transferStats.totalSize !== undefined) {
if (transferStats.progress !== 0 && if (transferStats.progress !== 0 && transferStats.progress !== transferStats.totalSize) {
transferStats.progress !== transferStats.totalSize) { res += UtilsAdapter.humanFileSize(transferStats.progress) + " / ";
res += UtilsAdapter.humanFileSize(transferStats.progress) + " / "
} }
var totalSize = transferStats.totalSize !== 0 ? transferStats.totalSize : TotalSize var totalSize = transferStats.totalSize !== 0 ? transferStats.totalSize : TotalSize;
res += UtilsAdapter.humanFileSize(totalSize) res += UtilsAdapter.humanFileSize(totalSize);
} }
return res return res;
} }
wrapMode: Label.WrapAtWordBoundaryOrAnywhere wrapMode: Label.WrapAtWordBoundaryOrAnywhere
font.pointSize: 10 font.pointSize: 10
renderType: Text.NativeRendering renderType: Text.NativeRendering
color: UtilsAdapter.luma(bubble.color) color: UtilsAdapter.luma(bubble.color) ? JamiTheme.chatviewTextColorLight : JamiTheme.chatviewTextColorDark
? JamiTheme.chatviewTextColorLight
: JamiTheme.chatviewTextColorDark
} }
} }
}, },
...@@ -316,23 +306,23 @@ Loader { ...@@ -316,23 +306,23 @@ Loader {
Component.onCompleted: { Component.onCompleted: {
if (transferStats.totalSize !== undefined) { if (transferStats.totalSize !== undefined) {
var totalSize = transferStats.totalSize !== 0 ? transferStats.totalSize : TotalSize var totalSize = transferStats.totalSize !== 0 ? transferStats.totalSize : TotalSize;
var txt = UtilsAdapter.humanFileSize(totalSize) var txt = UtilsAdapter.humanFileSize(totalSize);
} }
bubble.timestampItem.timeLabel.text += " - " + txt bubble.timestampItem.timeLabel.text += " - " + txt;
bubble.color = "transparent" bubble.color = "transparent";
if (mediaInfo.isImage) if (mediaInfo.isImage)
bubble.z = 1 bubble.z = 1;
else else
timeUnderBubble = true timeUnderBubble = true;
} }
onContentWidthChanged: { onContentWidthChanged: {
if (bubble.timestampItem.timeLabel.width > contentWidth) if (bubble.timestampItem.timeLabel.width > contentWidth)
timeUnderBubble = true timeUnderBubble = true;
else { else {
bubble.timestampItem.timeColor = JamiTheme.whiteColor bubble.timestampItem.timeColor = JamiTheme.whiteColor;
bubble.timestampItem.timeLabel.opacity = 1 bubble.timestampItem.timeLabel.opacity = 1;
} }
} }
...@@ -346,10 +336,10 @@ Loader { ...@@ -346,10 +336,10 @@ Loader {
height: sourceComponent.height height: sourceComponent.height
sourceComponent: { sourceComponent: {
if (mediaInfo.isImage) if (mediaInfo.isImage)
return imageComp return imageComp;
if (mediaInfo.isAnimatedImage) if (mediaInfo.isAnimatedImage)
return animatedImageComp return animatedImageComp;
return avComp return avComp;
} }
Component { Component {
...@@ -357,10 +347,11 @@ Loader { ...@@ -357,10 +347,11 @@ Loader {
Loader { Loader {
Component.onCompleted: { Component.onCompleted: {
var qml = WITH_WEBENGINE ? var qml = WITH_WEBENGINE ? "qrc:/webengine/MediaPreviewBase.qml" : "qrc:/nowebengine/MediaPreviewBase.qml";
"qrc:/webengine/MediaPreviewBase.qml" : setSource(qml, {
"qrc:/nowebengine/MediaPreviewBase.qml" isVideo: mediaInfo.isVideo,
setSource( qml, { isVideo: mediaInfo.isVideo, html: mediaInfo.html } ) html: mediaInfo.html
});
} }
} }
} }
...@@ -381,9 +372,7 @@ Loader { ...@@ -381,9 +372,7 @@ Loader {
asynchronous: true asynchronous: true
source: UtilsAdapter.urlFromLocalPath(Body) source: UtilsAdapter.urlFromLocalPath(Body)
property real aspectRatio: implicitWidth / implicitHeight property real aspectRatio: implicitWidth / implicitHeight
property real adjustedWidth: Math.min(maxSize, property real adjustedWidth: Math.min(maxSize, Math.max(minSize, innerContent.width - senderMargin))
Math.max(minSize,
innerContent.width - senderMargin))
width: adjustedWidth width: adjustedWidth
height: Math.ceil(adjustedWidth / aspectRatio) height: Math.ceil(adjustedWidth / aspectRatio)
Rectangle { Rectangle {
...@@ -403,7 +392,7 @@ Loader { ...@@ -403,7 +392,7 @@ Loader {
} }
onWidthChanged: { onWidthChanged: {
localMediaMsgItem.contentWidth = width localMediaMsgItem.contentWidth = width;
} }
Component.onCompleted: localMediaMsgItem.bubble.imgSource = source Component.onCompleted: localMediaMsgItem.bubble.imgSource = source
...@@ -429,71 +418,54 @@ Loader { ...@@ -429,71 +418,54 @@ Loader {
Component { Component {
id: imageComp id: imageComp
Image { Rectangle {
id: img border.color: img.useBox ? (JamiTheme.darkTheme ? "white" : JamiTheme.blackColor) : JamiTheme.transparentColor
color: JamiTheme.transparentColor
anchors.right: isOutgoing ? parent.right : undefined anchors.right: isOutgoing ? parent.right : undefined
cache: true border.width: 1
fillMode: Image.PreserveAspectFit radius: msgRadius
mipmap: true
antialiasing: true
autoTransform: true
asynchronous: true
Component.onCompleted: { implicitWidth: img.width + (img.useBox ? 20 : 0)
source = UtilsAdapter.urlFromLocalPath(Body); implicitHeight: img.height + (img.useBox ? 20 : 0)
localMediaMsgItem.bubble.imgSource = source; onWidthChanged: {
localMediaMsgItem.contentWidth = width;
} }
// The sourceSize represents the maximum source dimensions. Image {
// This should not be a dynamic binding, as property changes id: img
// (resizing the chat view) here will trigger a reload of the image.
sourceSize: Qt.size(256, 256)
// Now we setup bindings for the destination image component size.
// This based on the width available (width of the chat view), and
// a restriction on the height.
readonly property real aspectRatio: paintedWidth / paintedHeight
readonly property real idealWidth: innerContent.width - senderMargin
onStatusChanged: {
if (img.status == Image.Ready && aspectRatio) {
height = Qt.binding(() => JamiQmlUtils.clamp(idealWidth / aspectRatio, 64, 256))
width = Qt.binding(() => height * aspectRatio)
}
}
onWidthChanged: { anchors.centerIn: parent
localMediaMsgItem.contentWidth = width cache: true
} fillMode: Image.PreserveAspectFit
mipmap: true
antialiasing: true
autoTransform: true
asynchronous: true
Rectangle { Component.onCompleted: {
color: JamiTheme.previewImageBackgroundColor source = UtilsAdapter.urlFromLocalPath(Body);
z: -1 localMediaMsgItem.bubble.imgSource = source;
anchors.fill: parent
}
layer.enabled: true
layer.effect: OpacityMask {
maskSource: MessageBubble {
out: isOutgoing
type: seq
width: img.width
height: img.height
radius: msgRadius
} }
}
LinearGradient { // Scale down the image if it's too wide or too tall.
id: gradient property real maxWidth: localMediaMsgItem.width - 170
anchors.fill: parent property bool xOverflow: sourceSize.width > maxWidth
start: Qt.point(0, height / 3) property bool yOverflow: sourceSize.height > JamiTheme.maxImageHeight
gradient: Gradient { property real scaleFactor: (xOverflow || yOverflow) ? Math.min(maxWidth / sourceSize.width, JamiTheme.maxImageHeight / sourceSize.height) : 1
GradientStop { width: sourceSize.width * scaleFactor
position: 0.0 height: sourceSize.height * scaleFactor
color: JamiTheme.transparentColor
} // Add a bounding box around the image if it's small (along at least one
GradientStop { // dimension) to ensure that it's easy for users to see it and click on it.
position: 1.0 property bool useBox: (paintedWidth < 40) || (paintedHeight < 40)
color: JamiTheme.darkGreyColorOpacityFade layer.enabled: !useBox
layer.effect: OpacityMask {
maskSource: MessageBubble {
out: isOutgoing
type: seq
width: img.width
height: img.height
radius: msgRadius
} }
} }
} }
......
...@@ -255,6 +255,7 @@ Item { ...@@ -255,6 +255,7 @@ Item {
property color messageWebViewFooterButtonImageColor: darkTheme ? "#838383" : "#656565" property color messageWebViewFooterButtonImageColor: darkTheme ? "#838383" : "#656565"
property color chatviewSecondaryInformationColor: "#A7A7A7" property color chatviewSecondaryInformationColor: "#A7A7A7"
property color draftIconColor: "#707070" property color draftIconColor: "#707070"
property real maxImageHeight: 375
// ChatView Footer // ChatView Footer
property color chatViewFooterListColor: darkTheme ? blackColor : "#E5E5E5" property color chatViewFooterListColor: darkTheme ? blackColor : "#E5E5E5"
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment