Browse Source

ImageViewer: Visual improvements (#2851)

pull/2884/head
Sebastian Aigner 2 years ago committed by GitHub
parent
commit
2f9e886ea6
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 3
      experimental/examples/imageviewer/shared/src/androidMain/kotlin/example/imageviewer/platform.android.kt
  2. 10
      experimental/examples/imageviewer/shared/src/androidMain/kotlin/example/imageviewer/utils/GraphicsMath.android.kt
  3. 15
      experimental/examples/imageviewer/shared/src/commonMain/kotlin/example/imageviewer/ImageViewer.common.kt
  4. 13
      experimental/examples/imageviewer/shared/src/commonMain/kotlin/example/imageviewer/model/Page.kt
  5. 0
      experimental/examples/imageviewer/shared/src/commonMain/kotlin/example/imageviewer/platform.common.kt
  6. 2
      experimental/examples/imageviewer/shared/src/commonMain/kotlin/example/imageviewer/style/Palette.kt
  7. 16
      experimental/examples/imageviewer/shared/src/commonMain/kotlin/example/imageviewer/view/GalleryScreen.kt
  8. 75
      experimental/examples/imageviewer/shared/src/commonMain/kotlin/example/imageviewer/view/MemoryScreen.kt
  9. 48
      experimental/examples/imageviewer/shared/src/commonMain/kotlin/example/imageviewer/view/PreviewImage.common.kt
  10. BIN
      experimental/examples/imageviewer/shared/src/commonMain/resources/magic.png
  11. 5
      experimental/examples/imageviewer/shared/src/desktopMain/kotlin/example/imageviewer/plarfom.desktop.kt
  12. 7
      experimental/examples/imageviewer/shared/src/desktopMain/kotlin/example/imageviewer/platform.desktop.kt
  13. 6
      experimental/examples/imageviewer/shared/src/desktopMain/kotlin/example/imageviewer/utils/GraphicsMath.desktop.kt
  14. 2
      experimental/examples/imageviewer/shared/src/desktopMain/kotlin/example/imageviewer/view/ImageViewer.desktop.kt
  15. 15
      experimental/examples/imageviewer/shared/src/iosMain/kotlin/example/imageviewer/utils/GraphicsMath.ios.kt

3
experimental/examples/imageviewer/shared/src/androidMain/kotlin/example/imageviewer/plarfom.android.kt → experimental/examples/imageviewer/shared/src/androidMain/kotlin/example/imageviewer/platform.android.kt

@ -1,6 +1,7 @@
package example.imageviewer
import androidx.compose.foundation.layout.displayCutoutPadding
import androidx.compose.foundation.layout.statusBarsPadding
import androidx.compose.ui.Modifier
import androidx.compose.foundation.layout.*
actual fun Modifier.notchPadding(): Modifier = displayCutoutPadding().statusBarsPadding()

10
experimental/examples/imageviewer/shared/src/androidMain/kotlin/example/imageviewer/utils/GraphicsMath.android.kt

@ -1,7 +1,11 @@
package example.imageviewer.utils
import android.content.Context
import android.graphics.*
import android.graphics.Bitmap
import android.graphics.Canvas
import android.graphics.ColorMatrix
import android.graphics.ColorMatrixColorFilter
import android.graphics.Paint
import android.renderscript.Allocation
import android.renderscript.Element
import android.renderscript.RenderScript
@ -49,7 +53,7 @@ fun applyPixelFilter(bitmap: Bitmap): Bitmap {
var result: Bitmap = bitmap.copy(Bitmap.Config.ARGB_8888, true)
val w: Int = bitmap.width
val h: Int = bitmap.height
result = scaleBitmapAspectRatio(result, w / 20, h / 20)
result = scaleBitmapAspectRatio(result, w / 4, h / 4)
result = scaleBitmapAspectRatio(result, w, h)
return result
@ -67,7 +71,7 @@ fun applyBlurFilter(bitmap: Bitmap, context: Context): Bitmap {
val theIntrinsic: ScriptIntrinsicBlur =
ScriptIntrinsicBlur.create(renderScript, Element.U8_4(renderScript))
theIntrinsic.setRadius(25f)
theIntrinsic.setRadius(3f)
theIntrinsic.setInput(tmpIn)
theIntrinsic.forEach(tmpOut)

15
experimental/examples/imageviewer/shared/src/commonMain/kotlin/example/imageviewer/ImageViewer.common.kt

@ -2,23 +2,23 @@ package example.imageviewer
import androidx.compose.animation.AnimatedContent
import androidx.compose.animation.ExperimentalAnimationApi
import androidx.compose.animation.core.tween
import androidx.compose.animation.fadeIn
import androidx.compose.animation.fadeOut
import androidx.compose.animation.slideInHorizontally
import androidx.compose.animation.slideOutHorizontally
import androidx.compose.animation.with
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.material3.Surface
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue
import androidx.compose.runtime.remember
import androidx.compose.ui.Modifier
import example.imageviewer.model.CameraPage
import example.imageviewer.model.FullScreenPage
import example.imageviewer.model.GalleryPage
import example.imageviewer.model.PhotoGallery
import example.imageviewer.model.MemoryPage
import example.imageviewer.model.Page
import example.imageviewer.model.PhotoGallery
import example.imageviewer.model.bigUrl
import example.imageviewer.view.CameraScreen
import example.imageviewer.view.FullscreenImage
@ -48,8 +48,14 @@ internal fun ImageViewerCommon(
val previousIdx = initialState.index
val currentIdx = targetState.index
val multiplier = if (previousIdx < currentIdx) 1 else -1
if (initialState.value is GalleryPage && targetState.value is MemoryPage) {
fadeIn() with fadeOut(tween(durationMillis = 500, 500))
} else if (initialState.value is MemoryPage && targetState.value is GalleryPage) {
fadeIn() with fadeOut(tween(delayMillis = 150))
} else {
slideInHorizontally { w -> multiplier * w } with
slideOutHorizontally { w -> multiplier * -1 * w }
}
}) { (index, page) ->
when (page) {
is GalleryPage -> {
@ -82,6 +88,7 @@ internal fun ImageViewerCommon(
MemoryScreen(
memoryPage = page,
photoGallery = photoGallery,
getImage = { dependencies.imageRepository.loadContent(it.bigUrl) },
localization = dependencies.localization,
onSelectRelatedMemory = { galleryId ->
navigationStack.push(MemoryPage(galleryId))

13
experimental/examples/imageviewer/shared/src/commonMain/kotlin/example/imageviewer/model/Page.kt

@ -25,14 +25,23 @@ class GalleryPage(
var galleryStyle by mutableStateOf(GalleryStyle.SQUARES)
fun toggleGalleryStyle() {
galleryStyle = if(galleryStyle == GalleryStyle.SQUARES) GalleryStyle.LIST else GalleryStyle.SQUARES
galleryStyle =
if (galleryStyle == GalleryStyle.SQUARES) GalleryStyle.LIST else GalleryStyle.SQUARES
}
var currentPictureIndex by mutableStateOf(0)
val picture get(): Picture? = photoGallery.galleryStateFlow.value.getOrNull(currentPictureIndex)?.picture
val pictureId get(): GalleryId? = photoGallery.galleryStateFlow.value.getOrNull(currentPictureIndex)?.id
val galleryEntry: GalleryEntryWithMetadata?
get() = photoGallery.galleryStateFlow.value.getOrNull(
currentPictureIndex
)
val pictureId
get(): GalleryId? = photoGallery.galleryStateFlow.value.getOrNull(
currentPictureIndex
)?.id
fun nextImage() {
currentPictureIndex =

0
experimental/examples/imageviewer/shared/src/commonMain/kotlin/example/imageviewer/plarfom.common.kt → experimental/examples/imageviewer/shared/src/commonMain/kotlin/example/imageviewer/platform.common.kt

2
experimental/examples/imageviewer/shared/src/commonMain/kotlin/example/imageviewer/style/Palette.kt

@ -27,7 +27,7 @@ object ImageviewerColors {
val fullScreenImageBackground = Color(0xFF19191C)
val uiLightBlack = Color(25, 25, 28, 128)
val uiLightBlack = Color(25, 25, 28, 180)
val textOnImage = Color.White
val noteBlockBackground = Color(0xFFF3F3F4)

16
experimental/examples/imageviewer/shared/src/commonMain/kotlin/example/imageviewer/view/GalleryScreen.kt

@ -10,10 +10,7 @@ import androidx.compose.foundation.lazy.grid.GridCells
import androidx.compose.foundation.lazy.grid.LazyVerticalGrid
import androidx.compose.foundation.lazy.grid.itemsIndexed
import androidx.compose.foundation.shape.CircleShape
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.TopAppBar
import androidx.compose.material3.TopAppBarDefaults
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.collectAsState
@ -31,7 +28,6 @@ import example.imageviewer.model.GalleryId
import example.imageviewer.model.GalleryPage
import example.imageviewer.model.PhotoGallery
import example.imageviewer.model.bigUrl
import example.imageviewer.notchPadding
import example.imageviewer.style.ImageviewerColors
import org.jetbrains.compose.resources.ExperimentalResourceApi
import org.jetbrains.compose.resources.painterResource
@ -42,6 +38,7 @@ enum class GalleryStyle {
LIST
}
@OptIn(ExperimentalResourceApi::class)
@Composable
internal fun GalleryScreen(
galleryPage: GalleryPage,
@ -64,7 +61,7 @@ internal fun GalleryScreen(
Box {
PreviewImage(
getImage = { dependencies.imageRepository.loadContent(it.bigUrl) },
picture = galleryPage.picture, onClick = {
picture = galleryPage.galleryEntry, onClick = {
galleryPage.pictureId?.let(onClickPreviewPicture)
}
)
@ -77,7 +74,7 @@ internal fun GalleryScreen(
},
)
}
Box(Modifier.fillMaxSize(), contentAlignment = Alignment.BottomCenter) {
Box(Modifier.fillMaxSize(), contentAlignment = Alignment.TopCenter) {
when (galleryPage.galleryStyle) {
GalleryStyle.SQUARES -> SquaresGalleryView(
pictures,
@ -107,7 +104,7 @@ private fun SquaresGalleryView(
onSelect: (GalleryId) -> Unit,
) {
Column {
Spacer(Modifier.height(1.dp))
Spacer(Modifier.height(4.dp))
LazyVerticalGrid(
columns = GridCells.Adaptive(minSize = 130.dp),
verticalArrangement = Arrangement.spacedBy(1.dp),
@ -128,8 +125,8 @@ private fun SquaresGalleryView(
@OptIn(ExperimentalResourceApi::class)
@Composable
private fun MakeNewMemoryMiniature(onClick: () -> Unit) {
Column {
private fun BoxScope.MakeNewMemoryMiniature(onClick: () -> Unit) {
Column(modifier = Modifier.align(Alignment.BottomCenter)) {
Box(
Modifier
.clip(CircleShape)
@ -206,6 +203,7 @@ private fun ListGalleryView(
ScrollableColumn(
modifier = Modifier.fillMaxSize()
) {
Spacer(modifier = Modifier.height(10.dp))
for ((idx, picWithThumb) in pictures.withIndex()) {
val (galleryId, picture, miniature) = picWithThumb
Miniature(

75
experimental/examples/imageviewer/shared/src/commonMain/kotlin/example/imageviewer/view/MemoryScreen.kt

@ -29,6 +29,7 @@ import example.imageviewer.model.GalleryEntryWithMetadata
import example.imageviewer.model.GalleryId
import example.imageviewer.model.MemoryPage
import example.imageviewer.model.PhotoGallery
import example.imageviewer.model.Picture
import example.imageviewer.style.ImageviewerColors
import org.jetbrains.compose.resources.ExperimentalResourceApi
import org.jetbrains.compose.resources.painterResource
@ -38,6 +39,7 @@ import org.jetbrains.compose.resources.painterResource
internal fun MemoryScreen(
memoryPage: MemoryPage,
photoGallery: PhotoGallery,
getImage: suspend (Picture) -> ImageBitmap,
localization: Localization,
onSelectRelatedMemory: (GalleryId) -> Unit,
onBack: () -> Unit,
@ -45,6 +47,10 @@ internal fun MemoryScreen(
) {
val pictures by photoGallery.galleryStateFlow.collectAsState()
val picture = pictures.first { it.id == memoryPage.galleryId }
var headerImage by remember(picture) { mutableStateOf(picture.thumbnail) }
LaunchedEffect(picture) {
headerImage = getImage(picture.picture)
}
Box {
val scrollState = memoryPage.scrollState
Column(
@ -62,19 +68,10 @@ internal fun MemoryScreen(
},
contentAlignment = Alignment.Center
) {
MemoryHeader(picture.thumbnail, onClick = { onHeaderClick(memoryPage.galleryId) })
MemoryHeader(headerImage, onClick = { onHeaderClick(memoryPage.galleryId) })
}
Box(modifier = Modifier.background(MaterialTheme.colorScheme.background)) {
Column {
Headliner("Place")
val locationShape = RoundedCornerShape(10.dp)
LocationVisualizer(
Modifier.padding(horizontal = 12.dp)
.clip(locationShape)
.border(1.dp, Color.Gray, locationShape)
.fillMaxWidth()
.height(200.dp)
)
Headliner("Note")
Collapsible(
"""
@ -87,6 +84,15 @@ internal fun MemoryScreen(
)
Headliner("Related memories")
RelatedMemoriesVisualizer(pictures, onSelectRelatedMemory)
Headliner("Place")
val locationShape = RoundedCornerShape(10.dp)
LocationVisualizer(
Modifier.padding(horizontal = 12.dp)
.clip(locationShape)
.border(1.dp, Color.Gray, locationShape)
.fillMaxWidth()
.height(200.dp)
)
Spacer(Modifier.height(50.dp))
Row(
modifier = Modifier.fillMaxWidth(),
@ -130,13 +136,7 @@ internal fun MemoryScreen(
@Composable
private fun MemoryHeader(bitmap: ImageBitmap, onClick: () -> Unit) {
val interactionSource = remember { MutableInteractionSource() }
val shadowTextStyle = LocalTextStyle.current.copy(
shadow = Shadow(
color = Color.Black,
offset = Offset(4f, 4f),
blurRadius = 4f
)
)
Box(modifier = Modifier.clickable(interactionSource, null, onClick = { onClick() })) {
Image(
bitmap,
@ -144,30 +144,55 @@ private fun MemoryHeader(bitmap: ImageBitmap, onClick: () -> Unit) {
contentScale = ContentScale.Crop,
modifier = Modifier.fillMaxSize()
)
MagicButtonOverlay(onClick)
MemoryTextOverlay()
}
}
@OptIn(ExperimentalResourceApi::class)
@Composable
internal fun BoxScope.MagicButtonOverlay(onClick: () -> Unit) {
Column(
modifier = Modifier.align(Alignment.BottomEnd).padding(end = 12.dp, bottom = 16.dp)
) {
CircularButton(painterResource("magic.png"), onClick)
}
}
@Composable
internal fun BoxScope.MemoryTextOverlay() {
val shadowTextStyle = LocalTextStyle.current.copy(
shadow = Shadow(
color = Color.Black.copy(0.75f),
offset = Offset(0f, 0f),
blurRadius = 4f
)
)
Column(
modifier = Modifier.align(Alignment.BottomStart).padding(start = 12.dp, bottom = 16.dp)
) {
Text(
"Your Memory",
"28. Feb",
textAlign = TextAlign.Left,
color = Color.White,
fontSize = 20.sp,
lineHeight = 22.sp,
modifier = Modifier.fillMaxWidth(),
fontWeight = FontWeight.SemiBold,
style = shadowTextStyle
)
Spacer(Modifier.height(5.dp))
Spacer(Modifier.height(1.dp))
Text(
"19th of April 2023",
"London",
textAlign = TextAlign.Left,
color = Color.White,
fontSize = 14.sp,
lineHeight = 16.sp,
fontWeight = FontWeight.Normal,
style = shadowTextStyle
)
}
}
}
@Composable
internal fun Collapsible(s: String) {
@ -176,7 +201,7 @@ internal fun Collapsible(s: String) {
val text = if (isCollapsed) s.lines().first() + "... (see more)" else s
Text(
text,
fontSize = 12.sp,
fontSize = 16.sp,
modifier = Modifier
.padding(10.dp, 0.dp)
.clip(RoundedCornerShape(10.dp))
@ -190,7 +215,8 @@ internal fun Collapsible(s: String) {
)
.clickable(interactionSource = interctionSource, indication = null) {
isCollapsed = !isCollapsed
},
}
.fillMaxWidth(),
)
}
@ -212,7 +238,6 @@ internal fun RelatedMemoriesVisualizer(
) {
Box(
modifier = Modifier.padding(10.dp, 0.dp).clip(RoundedCornerShape(10.dp)).fillMaxWidth()
.height(200.dp)
) {
LazyRow(
modifier = Modifier.fillMaxSize(),

48
experimental/examples/imageviewer/shared/src/commonMain/kotlin/example/imageviewer/view/PreviewImage.common.kt

@ -1,15 +1,15 @@
package example.imageviewer.view
import androidx.compose.animation.AnimatedContent
import androidx.compose.animation.AnimatedContentScope
import androidx.compose.animation.ExperimentalAnimationApi
import androidx.compose.animation.core.Spring
import androidx.compose.animation.core.spring
import androidx.compose.animation.slideInHorizontally
import androidx.compose.animation.slideOutHorizontally
import androidx.compose.animation.core.FastOutSlowInEasing
import androidx.compose.animation.core.tween
import androidx.compose.animation.with
import androidx.compose.foundation.Image
import androidx.compose.foundation.background
import androidx.compose.foundation.clickable
import androidx.compose.foundation.interaction.MutableInteractionSource
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxSize
@ -27,44 +27,46 @@ import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.ImageBitmap
import androidx.compose.ui.layout.ContentScale
import androidx.compose.ui.unit.dp
import example.imageviewer.model.GalleryEntryWithMetadata
import example.imageviewer.model.Picture
@OptIn(ExperimentalAnimationApi::class)
@Composable
internal fun PreviewImage(
picture: Picture?,
picture: GalleryEntryWithMetadata?,
onClick: () -> Unit,
getImage: suspend (Picture) -> ImageBitmap
) {
Box(Modifier.fillMaxWidth().height(393.dp).background(Color.Black), contentAlignment = Alignment.Center) {
val interactionSource = remember { MutableInteractionSource() }
Box(
modifier = Modifier.fillMaxSize()
.clickable { onClick() },
Modifier.fillMaxWidth().height(393.dp).background(Color.Black),
contentAlignment = Alignment.Center
) {
Box(
modifier = Modifier
.fillMaxSize()
.clickable(interactionSource, indication = null, onClick = onClick),
) {
AnimatedContent(
targetState = picture,
transitionSpec = {
slideInHorizontally(
initialOffsetX = { it }, animationSpec = spring(
dampingRatio = Spring.DampingRatioLowBouncy,
stiffness = Spring.StiffnessLow
)
) with slideOutHorizontally(
targetOffsetX = { -it }, animationSpec = spring(
dampingRatio = Spring.DampingRatioLowBouncy,
stiffness = Spring.StiffnessLow
slideIntoContainer(
towards = AnimatedContentScope.SlideDirection.Left,
animationSpec = tween(durationMillis = 500, easing = FastOutSlowInEasing)
) with slideOutOfContainer(
towards = AnimatedContentScope.SlideDirection.Left,
animationSpec = tween(durationMillis = 500, easing = FastOutSlowInEasing)
)
)
// slideInVertically(initialOffsetY = { it }) with slideOutVertically(targetOffsetY = { -it })
}
) { currentPicture ->
var image by remember(currentPicture) { mutableStateOf<ImageBitmap?>(null) }
var image by remember(currentPicture) { mutableStateOf(currentPicture?.thumbnail) }
LaunchedEffect(currentPicture) {
if (currentPicture != null) {
image = getImage(currentPicture)
image = getImage(currentPicture.picture)
}
}
if (image != null) {
Box(Modifier.fillMaxSize()) {
Image(
bitmap = image!!,
contentDescription = null,
@ -72,14 +74,14 @@ internal fun PreviewImage(
.fillMaxSize(),
contentScale = ContentScale.Crop
)
MemoryTextOverlay()
}
} else {
Spacer(
modifier = Modifier.fillMaxSize()
)
}
}
}
}
}

BIN
experimental/examples/imageviewer/shared/src/commonMain/resources/magic.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.6 KiB

5
experimental/examples/imageviewer/shared/src/desktopMain/kotlin/example/imageviewer/plarfom.desktop.kt

@ -1,5 +0,0 @@
package example.imageviewer
import androidx.compose.ui.Modifier
actual fun Modifier.notchPadding(): Modifier = this

7
experimental/examples/imageviewer/shared/src/desktopMain/kotlin/example/imageviewer/platform.desktop.kt

@ -0,0 +1,7 @@
package example.imageviewer
import androidx.compose.foundation.layout.padding
import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.dp
actual fun Modifier.notchPadding(): Modifier = Modifier.padding(top = 12.dp)

6
experimental/examples/imageviewer/shared/src/desktopMain/kotlin/example/imageviewer/utils/GraphicsMath.kt → experimental/examples/imageviewer/shared/src/desktopMain/kotlin/example/imageviewer/utils/GraphicsMath.desktop.kt

@ -51,7 +51,7 @@ fun applyPixelFilter(bitmap: BufferedImage): BufferedImage {
val w: Int = bitmap.width
val h: Int = bitmap.height
var result = scaleBitmapAspectRatio(bitmap, w / 20, h / 20)
var result = scaleBitmapAspectRatio(bitmap, w / 4, h / 4)
result = scaleBitmapAspectRatio(result, w, h)
return result
@ -65,8 +65,8 @@ fun applyBlurFilter(bitmap: BufferedImage): BufferedImage {
graphics.drawImage(bitmap, 0, 0, null)
graphics.dispose()
val radius = 11
val size = 11
val radius = 3
val size = 3
val weight: Float = 1.0f / (size * size)
val matrix = FloatArray(size * size)

2
experimental/examples/imageviewer/shared/src/desktopMain/kotlin/example/imageviewer/view/ImageViewer.desktop.kt

@ -58,7 +58,7 @@ fun ApplicationScope.ImageViewerDesktop() {
title = "Image Viewer",
state = WindowState(
position = WindowPosition.Aligned(Alignment.Center),
size = getPreferredWindowSize(800, 1000)
size = getPreferredWindowSize(720, 857)
),
icon = painterResource("ic_imageviewer_round.png"),
// https://github.com/JetBrains/compose-jb/issues/2741

15
experimental/examples/imageviewer/shared/src/iosMain/kotlin/example/imageviewer/utils/GraphicsMath.ios.kt

@ -1,6 +1,15 @@
package example.imageviewer.utils
import org.jetbrains.skia.*
import org.jetbrains.skia.Bitmap
import org.jetbrains.skia.Canvas
import org.jetbrains.skia.ColorAlphaType
import org.jetbrains.skia.ColorInfo
import org.jetbrains.skia.ColorType
import org.jetbrains.skia.FilterTileMode
import org.jetbrains.skia.Image
import org.jetbrains.skia.ImageFilter
import org.jetbrains.skia.ImageInfo
import org.jetbrains.skia.Paint
fun scaleBitmapAspectRatio(
bitmap: Bitmap,
@ -50,7 +59,7 @@ fun applyPixelFilter(bitmap: Bitmap): Bitmap {
val width = bitmap.width
val height = bitmap.height
var result = scaleBitmapAspectRatio(bitmap, width / 20, height / 20)
var result = scaleBitmapAspectRatio(bitmap, width / 4, height / 4)
result = scaleBitmapAspectRatio(result, width, height)
return result
@ -61,7 +70,7 @@ fun applyBlurFilter(bitmap: Bitmap): Bitmap {
allocN32Pixels(bitmap.width, bitmap.height)
}
val blur = Paint().apply {
imageFilter = ImageFilter.makeBlur(10f, 10f, FilterTileMode.CLAMP)
imageFilter = ImageFilter.makeBlur(3f, 3f, FilterTileMode.CLAMP)
}
val canvas = Canvas(result)

Loading…
Cancel
Save