From 7b5b747b19c16ffe00192a273e18e4a061e49ea4 Mon Sep 17 00:00:00 2001 From: Stanislav Dmitrenko <7953703+avently@users.noreply.github.com> Date: Thu, 30 May 2024 23:07:58 +0700 Subject: [PATCH] desktop: performant drawing of wallpaper (#4249) * desktop: low quality drawing of wallpaper * fast drawing * clean up * unused --- .../simplex/common/views/chat/ChatView.kt | 3 +- .../common/views/helpers/ChatWallpaper.kt | 120 +++++++++++------- .../common/views/usersettings/Appearance.kt | 6 +- .../kotlin/chat/simplex/common/DesktopApp.kt | 1 + .../kotlin/chat/simplex/desktop/Main.kt | 3 +- 5 files changed, 84 insertions(+), 49 deletions(-) diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/ChatView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/ChatView.kt index 03d4e30a6c..71d82b5691 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/ChatView.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/ChatView.kt @@ -12,6 +12,7 @@ import androidx.compose.runtime.saveable.mapSaver import androidx.compose.runtime.saveable.rememberSaveable import androidx.compose.ui.* import androidx.compose.ui.draw.drawBehind +import androidx.compose.ui.draw.drawWithCache import androidx.compose.ui.graphics.* import androidx.compose.ui.platform.* import dev.icerock.moko.resources.compose.painterResource @@ -620,7 +621,7 @@ fun ChatLayout( .fillMaxSize() .background(MaterialTheme.colors.background) .then(if (wallpaperImage != null) - Modifier.drawBehind { chatViewBackground(wallpaperImage, wallpaperType, backgroundColor, tintColor) } + Modifier.drawWithCache { chatViewBackground(wallpaperImage, wallpaperType, backgroundColor, tintColor) } else Modifier) .padding(contentPadding) diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/helpers/ChatWallpaper.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/helpers/ChatWallpaper.kt index 89796baf4e..8921685cd6 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/helpers/ChatWallpaper.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/helpers/ChatWallpaper.kt @@ -1,12 +1,13 @@ package chat.simplex.common.views.helpers +import androidx.compose.ui.draw.CacheDrawScope +import androidx.compose.ui.draw.DrawResult +import androidx.compose.ui.geometry.Offset import androidx.compose.ui.geometry.Size import androidx.compose.ui.graphics.* -import androidx.compose.ui.graphics.drawscope.DrawScope -import androidx.compose.ui.graphics.drawscope.clipRect +import androidx.compose.ui.graphics.drawscope.* import androidx.compose.ui.layout.ContentScale -import androidx.compose.ui.unit.IntOffset -import androidx.compose.ui.unit.IntSize +import androidx.compose.ui.unit.* import chat.simplex.common.model.ChatController.appPrefs import chat.simplex.common.platform.* import chat.simplex.common.ui.theme.* @@ -352,9 +353,17 @@ sealed class WallpaperType { } } -fun DrawScope.chatViewBackground(image: ImageBitmap, imageType: WallpaperType, background: Color, tint: Color) = clipRect { - val quality = FilterQuality.High - fun repeat(imageScale: Float) { +private fun drawToBitmap(image: ImageBitmap, imageScale: Float, tint: Color, size: Size, density: Float, layoutDirection: LayoutDirection): ImageBitmap { + val quality = if (appPlatform.isAndroid) FilterQuality.High else FilterQuality.Low + val drawScope = CanvasDrawScope() + val bitmap = ImageBitmap(size.width.toInt(), size.height.toInt()) + val canvas = Canvas(bitmap) + drawScope.draw( + density = Density(density), + layoutDirection = layoutDirection, + canvas = canvas, + size = size, + ) { val scale = imageScale * density for (h in 0..(size.height / image.height / scale).roundToInt()) { for (w in 0..(size.width / image.width / scale).roundToInt()) { @@ -368,50 +377,71 @@ fun DrawScope.chatViewBackground(image: ImageBitmap, imageType: WallpaperType, b } } } + return bitmap +} - drawRect(background) - when (imageType) { - is WallpaperType.Preset -> repeat((imageType.scale ?: 1f) * imageType.predefinedImageScale) - is WallpaperType.Image -> when (val scaleType = imageType.scaleType ?: WallpaperScaleType.FILL) { - WallpaperScaleType.REPEAT -> repeat(imageType.scale ?: 1f) - WallpaperScaleType.FILL, WallpaperScaleType.FIT -> { - val scale = scaleType.contentScale.computeScaleFactor(Size(image.width.toFloat(), image.height.toFloat()), Size(size.width, size.height)) - val scaledWidth = (image.width * scale.scaleX).roundToInt() - val scaledHeight = (image.height * scale.scaleY).roundToInt() - // Large image will cause freeze - if (image.width > 4320 || image.height > 4320) return@clipRect +fun CacheDrawScope.chatViewBackground(image: ImageBitmap, imageType: WallpaperType, background: Color, tint: Color): DrawResult { + val imageScale = if (imageType is WallpaperType.Preset) { + (imageType.scale ?: 1f) * imageType.predefinedImageScale + } else if (imageType is WallpaperType.Image && imageType.scaleType == WallpaperScaleType.REPEAT) { + imageType.scale ?: 1f + } else { + 1f + } + val image = if (imageType is WallpaperType.Preset || (imageType is WallpaperType.Image && imageType.scaleType == WallpaperScaleType.REPEAT)) { + drawToBitmap(image, imageScale, tint, size, density, layoutDirection) + } else { + image + } - drawImage(image, dstOffset = IntOffset(x = ((size.width - scaledWidth) / 2).roundToInt(), y = ((size.height - scaledHeight) / 2).roundToInt()), dstSize = IntSize(scaledWidth, scaledHeight), filterQuality = quality) - if (scaleType == WallpaperScaleType.FIT) { - if (scaledWidth < size.width) { - // has black lines at left and right sides - var x = (size.width - scaledWidth) / 2 - while (x > 0) { - drawImage(image, dstOffset = IntOffset(x = (x - scaledWidth).roundToInt(), y = ((size.height - scaledHeight) / 2).roundToInt()), dstSize = IntSize(scaledWidth, scaledHeight), filterQuality = quality) - x -= scaledWidth - } - x = size.width - (size.width - scaledWidth) / 2 - while (x < size.width) { - drawImage(image, dstOffset = IntOffset(x = x.roundToInt(), y = ((size.height - scaledHeight) / 2).roundToInt()), dstSize = IntSize(scaledWidth, scaledHeight), filterQuality = quality) - x += scaledWidth - } - } else { - // has black lines at top and bottom sides - var y = (size.height - scaledHeight) / 2 - while (y > 0) { - drawImage(image, dstOffset = IntOffset(x = ((size.width - scaledWidth) / 2).roundToInt(), y = (y - scaledHeight).roundToInt()), dstSize = IntSize(scaledWidth, scaledHeight), filterQuality = quality) - y -= scaledHeight - } - y = size.height - (size.height - scaledHeight) / 2 - while (y < size.height) { - drawImage(image, dstOffset = IntOffset(x = ((size.width - scaledWidth) / 2).roundToInt(), y = y.roundToInt()), dstSize = IntSize(scaledWidth, scaledHeight), filterQuality = quality) - y += scaledHeight + return onDrawBehind { + val quality = if (appPlatform.isAndroid) FilterQuality.High else FilterQuality.Low + drawRect(background) + when (imageType) { + is WallpaperType.Preset -> drawImage(image) + is WallpaperType.Image -> when (val scaleType = imageType.scaleType ?: WallpaperScaleType.FILL) { + WallpaperScaleType.REPEAT -> drawImage(image) + WallpaperScaleType.FILL, WallpaperScaleType.FIT -> { + clipRect { + val scale = scaleType.contentScale.computeScaleFactor(Size(image.width.toFloat(), image.height.toFloat()), Size(size.width, size.height)) + val scaledWidth = (image.width * scale.scaleX).roundToInt() + val scaledHeight = (image.height * scale.scaleY).roundToInt() + // Large image will cause freeze + if (image.width > 4320 || image.height > 4320) return@clipRect + + drawImage(image, dstOffset = IntOffset(x = ((size.width - scaledWidth) / 2).roundToInt(), y = ((size.height - scaledHeight) / 2).roundToInt()), dstSize = IntSize(scaledWidth, scaledHeight), filterQuality = quality) + if (scaleType == WallpaperScaleType.FIT) { + if (scaledWidth < size.width) { + // has black lines at left and right sides + var x = (size.width - scaledWidth) / 2 + while (x > 0) { + drawImage(image, dstOffset = IntOffset(x = (x - scaledWidth).roundToInt(), y = ((size.height - scaledHeight) / 2).roundToInt()), dstSize = IntSize(scaledWidth, scaledHeight), filterQuality = quality) + x -= scaledWidth + } + x = size.width - (size.width - scaledWidth) / 2 + while (x < size.width) { + drawImage(image, dstOffset = IntOffset(x = x.roundToInt(), y = ((size.height - scaledHeight) / 2).roundToInt()), dstSize = IntSize(scaledWidth, scaledHeight), filterQuality = quality) + x += scaledWidth + } + } else { + // has black lines at top and bottom sides + var y = (size.height - scaledHeight) / 2 + while (y > 0) { + drawImage(image, dstOffset = IntOffset(x = ((size.width - scaledWidth) / 2).roundToInt(), y = (y - scaledHeight).roundToInt()), dstSize = IntSize(scaledWidth, scaledHeight), filterQuality = quality) + y -= scaledHeight + } + y = size.height - (size.height - scaledHeight) / 2 + while (y < size.height) { + drawImage(image, dstOffset = IntOffset(x = ((size.width - scaledWidth) / 2).roundToInt(), y = y.roundToInt()), dstSize = IntSize(scaledWidth, scaledHeight), filterQuality = quality) + y += scaledHeight + } + } } } + drawRect(tint) } - drawRect(tint) } + is WallpaperType.Empty -> {} } - is WallpaperType.Empty -> {} } } diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/Appearance.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/Appearance.kt index 8b5fbd523f..84fe333ba8 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/Appearance.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/Appearance.kt @@ -95,11 +95,13 @@ object AppearanceScope { val backgroundColor = backgroundColor ?: wallpaperType?.defaultBackgroundColor(theme, MaterialTheme.colors.background) val tintColor = tintColor ?: wallpaperType?.defaultTintColor(theme) Column(Modifier - .drawBehind { + .drawWithCache { if (wallpaperImage != null && wallpaperType != null && backgroundColor != null && tintColor != null) { chatViewBackground(wallpaperImage, wallpaperType, backgroundColor, tintColor) } else { - drawRect(themeBackgroundColor) + onDrawBehind { + drawRect(themeBackgroundColor) + } } } .padding(DEFAULT_PADDING_HALF) diff --git a/apps/multiplatform/common/src/desktopMain/kotlin/chat/simplex/common/DesktopApp.kt b/apps/multiplatform/common/src/desktopMain/kotlin/chat/simplex/common/DesktopApp.kt index 36149c8248..4128982f78 100644 --- a/apps/multiplatform/common/src/desktopMain/kotlin/chat/simplex/common/DesktopApp.kt +++ b/apps/multiplatform/common/src/desktopMain/kotlin/chat/simplex/common/DesktopApp.kt @@ -115,6 +115,7 @@ private fun ApplicationScope.AppWindow(closedByError: MutableState) { false } }, title = "SimpleX") { +// val hardwareAccelerationDisabled = remember { listOf(GraphicsApi.SOFTWARE_FAST, GraphicsApi.SOFTWARE_COMPAT, GraphicsApi.UNKNOWN).contains(window.renderApi) } simplexWindowState.window = window AppScreen() if (simplexWindowState.openDialog.isAwaiting) { diff --git a/apps/multiplatform/desktop/src/jvmMain/kotlin/chat/simplex/desktop/Main.kt b/apps/multiplatform/desktop/src/jvmMain/kotlin/chat/simplex/desktop/Main.kt index 9925a6346b..f69cf817e5 100644 --- a/apps/multiplatform/desktop/src/jvmMain/kotlin/chat/simplex/desktop/Main.kt +++ b/apps/multiplatform/desktop/src/jvmMain/kotlin/chat/simplex/desktop/Main.kt @@ -3,7 +3,6 @@ package chat.simplex.desktop import androidx.compose.animation.core.Animatable import androidx.compose.animation.core.AnimationVector1D import androidx.compose.foundation.* -import androidx.compose.foundation.interaction.* import androidx.compose.foundation.lazy.LazyListState import androidx.compose.runtime.* import androidx.compose.ui.ExperimentalComposeUiApi @@ -17,6 +16,8 @@ import kotlinx.coroutines.* import java.io.File fun main() { + // Disable hardware acceleration + //System.setProperty("skiko.renderApi", "SOFTWARE") initHaskell() runMigrations() initApp()