Created
September 7, 2022 09:55
-
-
Save SmartToolFactory/8d17cdbe74d83e5fde28a3295f02cf62 to your computer and use it in GitHub Desktop.
Revisions
-
SmartToolFactory created this gist
Sep 7, 2022 .There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal file line number Diff line number Diff line change @@ -0,0 +1,342 @@ package com.smarttoolfactory.composeimagecropper.demo import android.graphics.Bitmap import android.graphics.PorterDuff import android.graphics.PorterDuffXfermode import androidx.compose.foundation.* import androidx.compose.foundation.layout.* import androidx.compose.material.Text import androidx.compose.runtime.* import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.draw.alpha import androidx.compose.ui.geometry.Offset import androidx.compose.ui.graphics.* import androidx.compose.ui.layout.ContentScale import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.res.imageResource import androidx.compose.ui.unit.IntSize import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp import com.smarttoolfactory.composeimagecropper.R import com.smarttoolfactory.gesture.pointerMotionEvents import com.smarttoolfactory.imagecropper.createPolygonPath import kotlin.math.roundToInt @Composable fun CanvasDemo() { Column( modifier = Modifier .fillMaxSize() .verticalScroll(rememberScrollState()) ) { val imageBitmap1 = ImageBitmap.imageResource( LocalContext.current.resources, R.drawable.landscape1 ).asAndroidBitmap().copy(Bitmap.Config.ARGB_8888, true).asImageBitmap() val imageBitmap2 = ImageBitmap.imageResource( LocalContext.current.resources, R.drawable.landscape1 ).asAndroidBitmap().copy(Bitmap.Config.ARGB_8888, true).asImageBitmap() val aspectRatio1 = imageBitmap1.width / imageBitmap1.height.toFloat() val aspectRatio2 = imageBitmap2.width / imageBitmap2.height.toFloat() Text("Native Canvas Clipping") // NativeCanvasSample1( // imageBitmap = imageBitmap1, // modifier = Modifier // .fillMaxWidth() // .aspectRatio(aspectRatio1) // ) // Spacer(modifier = Modifier.height(50.dp)) Text("Native Canvas BlendMode Clear") NativeCanvasSample2( imageBitmap = imageBitmap2, modifier = Modifier .fillMaxWidth() .aspectRatio(aspectRatio2) ) // // val dstBitmap = ImageBitmap.imageResource(id = R.drawable.landscape1) // val aspectRatioDst = dstBitmap.width / dstBitmap.height.toFloat() // // Spacer(modifier = Modifier.height(50.dp)) // Text("Compose Canvas BlendMode Clear") // ComposeCanvasSample( // imageBitmap = dstBitmap, // modifier = Modifier // .fillMaxWidth() // .aspectRatio(aspectRatioDst) // ) // // Spacer(modifier = Modifier.height(50.dp)) // Text("Compose Canvas Path + BlendMode Clear") // ComposeCanvasSample2( // imageBitmap = dstBitmap, // modifier = Modifier // .fillMaxWidth() // .aspectRatio(aspectRatioDst) // ) } } // FIXME This one does not refresh correctly, here is the question i asked on Stackoverflow // https://stackoverflow.com/questions/72168588/jetpack-compose-androidx-compose-ui-graphics-canvas-not-refreshing-correctly-for @Composable fun NativeCanvasSample2(imageBitmap: ImageBitmap, modifier: Modifier) { BoxWithConstraints(modifier) { val imageWidth = constraints.maxWidth val imageHeight = constraints.maxHeight val bitmapWidth = imageBitmap.width val bitmapHeight = imageBitmap.height var offset by remember { mutableStateOf(Offset(bitmapWidth / 2f, bitmapHeight / 2f)) } val canvasModifier = Modifier.pointerMotionEvents( Unit, onDown = { val position = it.position val offsetX = position.x * bitmapWidth / imageWidth val offsetY = position.y * bitmapHeight / imageHeight offset = Offset(offsetX, offsetY) it.consume() }, onMove = { val position = it.position val offsetX = position.x * bitmapWidth / imageWidth val offsetY = position.y * bitmapHeight / imageHeight offset = Offset(offsetX, offsetY) it.consume() }, delayAfterDownInMillis = 20 ) val canvas: Canvas = remember { Canvas(imageBitmap) } val paint = remember { android.graphics.Paint().apply { color = android.graphics.Color.TRANSPARENT xfermode = PorterDuffXfermode(PorterDuff.Mode.MULTIPLY) } } val paintBg2 = remember { android.graphics.Paint().apply { color = 0x55000000 } } val paintClear = remember { android.graphics.Paint().apply { color = android.graphics.Color.RED xfermode = PorterDuffXfermode(PorterDuff.Mode.MULTIPLY) } } val paintBg = remember { Paint().apply { color = Color(0x55000000) } } val paintImage = remember { Paint().apply { } } canvas.apply { with(canvas.nativeCanvas) { val checkPoint = saveLayer(null, null) // this.drawColor(android.graphics.Color.TRANSPARENT, PorterDuff.Mode.MULTIPLY) // this.drawBitmap(imageBitmap.asAndroidBitmap(), 0f, 0f, paint) // this.drawRect(0f, 0f, canvasWidth, canvasHeight, paintBg2) this.drawCircle(200f, 100f, 100f, paintClear) restoreToCount(checkPoint) } } Image( modifier = canvasModifier.alpha(.99f), bitmap = imageBitmap, contentDescription = null, contentScale = ContentScale.FillBounds ) Text( "Offset: $offset", modifier = Modifier.align(Alignment.BottomEnd), color = Color.White, fontSize = 16.sp ) } } @Composable fun ComposeCanvasSample(modifier: Modifier, imageBitmap: ImageBitmap) { BoxWithConstraints(modifier) { val dstBitmap = imageBitmap val imageWidth = constraints.maxWidth val imageHeight = constraints.maxHeight val bitmapWidth = imageBitmap.width val bitmapHeight = imageBitmap.height var offset by remember { mutableStateOf(Offset(bitmapWidth / 2f, bitmapHeight / 2f)) } val canvasModifier = Modifier.pointerMotionEvents( Unit, onDown = { val position = it.position val offsetX = (position.x).coerceIn(0f, imageWidth.toFloat()) val offsetY = (position.y).coerceIn(0f, imageHeight.toFloat()) offset = Offset(offsetX, offsetY) it.consume() }, onMove = { val position = it.position val offsetX = (position.x).coerceIn(0f, imageWidth.toFloat()) val offsetY = (position.y).coerceIn(0f, imageHeight.toFloat()) offset = Offset(offsetX, offsetY) it.consume() }, delayAfterDownInMillis = 20 ) Canvas(modifier = canvasModifier.fillMaxSize()) { val canvasWidth = size.width.roundToInt() val canvasHeight = size.height.roundToInt() drawImage( image = dstBitmap, srcSize = IntSize(dstBitmap.width, dstBitmap.height), dstSize = IntSize(canvasWidth, canvasHeight) ) with(drawContext.canvas.nativeCanvas) { val checkPoint = saveLayer(null, null) // Destination drawRect(Color(0x55000000)) // Source drawCircle( center = offset, color = Color.Blue, radius = canvasHeight.coerceAtMost(canvasWidth) / 8f, blendMode = BlendMode.Clear ) restoreToCount(checkPoint) } } } } @Composable fun ComposeCanvasSample2(modifier: Modifier, imageBitmap: ImageBitmap) { val path = remember { Path() } BoxWithConstraints(modifier) { val dstBitmap = imageBitmap val imageWidth = constraints.maxWidth val imageHeight = constraints.maxHeight val bitmapWidth = imageBitmap.width val bitmapHeight = imageBitmap.height var offset by remember { mutableStateOf(Offset(bitmapWidth / 2f, bitmapHeight / 2f)) } val canvasModifier = Modifier.pointerMotionEvents( Unit, onDown = { val position = it.position val offsetX = (position.x).coerceIn(0f, imageWidth.toFloat()) val offsetY = (position.y).coerceIn(0f, imageHeight.toFloat()) offset = Offset(offsetX, offsetY) path.moveTo(offset.x, offset.y) it.consume() }, onMove = { val position = it.position val offsetX = (position.x).coerceIn(0f, imageWidth.toFloat()) val offsetY = (position.y).coerceIn(0f, imageHeight.toFloat()) offset = Offset(offsetX, offsetY) path.lineTo(offset.x, offset.y) it.consume() }, onUp = { val position = it.position val offsetX = (position.x).coerceIn(0f, imageWidth.toFloat()) val offsetY = (position.y).coerceIn(0f, imageHeight.toFloat()) offset = Offset(offsetX, offsetY) path.lineTo(offset.x, offset.y) path.close() }, delayAfterDownInMillis = 20 ) println("⛺️ canvasModifier: $canvasModifier, offset: $offset") Canvas(modifier = canvasModifier.fillMaxSize()) { val canvasWidth = size.width.roundToInt() val canvasHeight = size.height.roundToInt() drawImage( image = dstBitmap, srcSize = IntSize(dstBitmap.width, dstBitmap.height), dstSize = IntSize(canvasWidth, canvasHeight) ) with(drawContext.canvas.nativeCanvas) { val checkPoint = saveLayer(null, null) // Destination drawRect(Color(0x55000000)) // Source // drawCircle( // center = offset, // color = Color.Blue, // radius = canvasHeight.coerceAtMost(canvasWidth) / 8f, // blendMode = BlendMode.Clear // ) drawPath( color = Color.Green, path = path, // style = Stroke(width = 1.dp.toPx()), blendMode = BlendMode.Clear ) restoreToCount(checkPoint) } } } }