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