Skip to content

Instantly share code, notes, and snippets.

@SmartToolFactory
Created September 7, 2022 09:55
Show Gist options
  • Save SmartToolFactory/8d17cdbe74d83e5fde28a3295f02cf62 to your computer and use it in GitHub Desktop.
Save SmartToolFactory/8d17cdbe74d83e5fde28a3295f02cf62 to your computer and use it in GitHub Desktop.
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)
}
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment