How i can animation or state change inside the Canvas in jetpack compose? - kotlin

suppose i have simple composable fun which has Canvas.
Canvas(modifier = Modifier
.padding(start = 60.dp, end = 60.dp)
.fillMaxSize(),
onDraw = {
val w = size.width
val h = size.height
val s1LineOffset = w / 4 - 10
val s2LineOffset = w * 3 / 8 - 10
drawImage(
image = ImageBitmap.imageResource(
res = resources,
id = R.drawable.bttn
),
topLeft = Offset(
x = b1StartOffset,
y = 0f
)
)
}
)
I want to define the state of animation using the canvas sizes for initial and target value but i cant do so because i can't use this inside draw scope thats why i have to use it above the Canvas block, hence cant access Canvas size what should i do
val anim by ballAnim.animateFloat(
initialValue = ,
targetValue =,
animationSpec =
)

It is more of a simple job. Just create and remember a MutableState<T> value, then update it inside Canvas. For example,
var canvasSize by remember { mutableStateOf(IntSize()) }
Canvas(modifier = Modifier
.padding(start = 60.dp, end = 60.dp)
.fillMaxSize(),
onDraw = {
canvasSize = size //Assign it here
val w = size.width
val h = size.height
val s1LineOffset = w / 4 - 10
val s2LineOffset = w * 3 / 8 - 10
drawImage(
image = ImageBitmap.imageResource(
res = resources,
id = R.drawable.bttn
),
topLeft = Offset(
x = b1StartOffset,
y = 0f
)
)
}
)
val anim by ballAnim.animateFloat(
initialValue = canvasSize.width,
targetValue = canvasSize.height, //whatever
animationSpec = spring()
)

One of the options is animating some normalized value and denormalizing it using size inside onDraw, e.g.:
val animNormalized by ballAnim.animateFloat(
initialValue = 0.5,
targetValue = 0.8,
animationSpec =
)
Canvas(modifier = Modifier
.padding(start = 60.dp, end = 60.dp)
.fillMaxSize(),
onDraw = {
val w = size.width
val h = size.height
val anim = animNormalized * w
...
}
)

Related

How to move the image as transparent after erasing background to other screen?

I'm developing a photo editing app, in that I have a feature to add the image to other image as transparent after editing in the app. So I can able to erase the background of the image. But I don't know how to move the background erased image to other screen , like how we do for the normal image. Now if I move the transparent part appeared as grey part. you can see that image. Help me to solve this. Thank you.
[
Canvas(
modifier = drawModifier
.onGloballyPositioned {
capturingViewBounds.value = it.boundsInWindow()
}
) {
val canvasWidth = size.width.roundToInt()
val canvasHeight = size.height.roundToInt()
val width = this.size.width
val height = this.size.height
val checkerWidth = 10.dp.toPx()
val checkerHeight = 10.dp.toPx()
val horizontalSteps = (width / checkerWidth).toInt()
val verticalSteps = (height / checkerHeight).toInt()
for (y in 0..verticalSteps) {
for (x in 0..horizontalSteps) {
val isGrayTile = ((x + y) % 2 == 1)
drawRect(
color = if (isGrayTile) Color.LightGray else Color.White,
topLeft = Offset(x * checkerWidth, y * checkerHeight),
size = Size(checkerWidth, checkerHeight)
)
}
}
val space = 20.dp.roundToPx()
with(drawContext.canvas.nativeCanvas) {
val checkPoint = saveLayer(null, null)
drawImage(
image = dstBitmap,
dstOffset = IntOffset(space / 2, space / 2),
dstSize = IntSize(canvasWidth - space, canvasHeight - space)
)
clipBounds
drawPath(
color = Color.Transparent,
path = erasePath,
style = Stroke(
width = 30f,
cap = StrokeCap.Round,
join = StrokeJoin.Round
),
blendMode = BlendMode.Clear
)
restoreToCount(checkPoint)
}
}

How can I measure the height and width of a text in Jetpack Compose Canvas?

I'm use Jetpack Compose Canvas to draw a division circle. The min value of the division circle is 20, and the max value of the division circle is 120.
So I write the Code A, and I get the result Image A as expected except the label.
From the Image A, I find the label 0, 20, 40 are on good position, and the label 60, 80, 100, 120 are not on good position.
1: It seems that I need to measure the height and width of a text,then adjust the position of the text, if so, how can I measure the height and width of a text?
2: Is there other way to place these text on apposite position without measurement the height and width of text?
Code A
#Composable
fun setCanvas(maxCountList: MaxCountList<Double>) {
Canvas(
modifier = Modifier
) {
val axisPaint = Paint()
val textPaint = TextPaint()
drawIntoCanvas {
val orig = MyPoint(size.width / 2, size.height / 2)
val temp = min(size.height, size.width)
val radius = temp / 2 - 10
it.drawCircle(Offset(x = 0f.toX(orig), y = 0f.toY(orig)), radius, axisPaint)
val lineOffset = 5.0f
val lineLength = 20.0f
val labelOffset = 10.0f
val point1 = radius - lineOffset
val point2 = radius - lineOffset - lineLength
val point3 = radius - lineOffset - lineLength - labelOffset
(0..6).forEach { i ->
val radians = Math.toRadians(225 - i * 45.0)
val x1 = point1 * cos(radians).toFloat()
val x2 = point2 * cos(radians).toFloat()
val x3 = point3 * cos(radians).toFloat()
val y1 = point1 * sin(radians).toFloat()
val y2 = point2 * sin(radians).toFloat()
val y3 = point3 * sin(radians).toFloat()
it.drawLine(
Offset(x = x1.toX(orig), y = y1.toY(orig)),
Offset(x = x2.toX(orig), y = y2.toY(orig)),
axisPaint
)
val label=(i * 20).toString()
it.nativeCanvas.drawText(label, x3.toX(orig), y3.toY(orig), textPaint)
}
}
}
}
//Convert X to new coordinate
fun Float.toX(originCoordinate: MyPoint) :Float {
return originCoordinate.x+this
}
//Convert Y to new coordinate
fun Float.toY(originCoordinate: MyPoint):Float {
return originCoordinate.y-this
}
class MyPoint (val x:Float, val y: Float)
Image A
you can use TextMeasurer.measure in jetpack compose 1.3.0-alpha2 onwards
you can check samples here
https://android-review.googlesource.com/c/platform/frameworks/support/+/2135315/4/compose/foundation/foundation/integration-tests/foundation-demos/src/main/java/androidx/compose/foundation/demos/text/DrawTextDemo.kt

Cutting one shape from an other shape in Jetpack Compose

I had a question about making this view in Compose and I have no idea about implementing it.
My current code looks like this:
Box(
modifier = Modifier
.fillMaxSize()
.height(300.dp)
) {
Canvas(modifier = Modifier.matchParentSize()) {
drawRoundRect(
color = Color.Yellow,
cornerRadius = CornerRadius(16.dp.toPx(), 16.dp.toPx())
)
drawRoundRect(
color = Color.White,
topLeft = Offset(
x = size.width / 5,
y = size.height - 60.dp.toPx()
),
size = Size((size.width / 5) * 3, 50.dp.toPx() * 2),
cornerRadius = CornerRadius(24.dp.toPx(), 24.dp.toPx()),
)
}
Box(
modifier = Modifier
.fillMaxSize()
.padding(16.dp)
) {
Text(
text = "Test",
modifier = Modifier.align(Alignment.BottomCenter)
)
}
}
Result is the following:
To clip some path from an other path, you can use clipPath.
And to outer corner radius, you need to add arcs to the path manually, like this:
Canvas(modifier = Modifier.matchParentSize()) {
val outerCornerRadius = 16.dp.toPx()
val clippedPath = Path().apply {
val innerCornerRadius = 24.dp.toPx()
val rectSize = Size(round((size.width / 5) * 3), round(50.dp.toPx() * 2))
val rect = Rect(
offset = Offset(
x = (size.width - rectSize.width) / 2,
y = size.height - rectSize.height
),
size = rectSize
)
addRoundRect(
RoundRect(
rect,
topLeft = CornerRadius(x = innerCornerRadius, y = innerCornerRadius),
topRight = CornerRadius(x = innerCornerRadius, y = innerCornerRadius),
)
)
val outerCornerDiameter = outerCornerRadius * 2
val cornerSize = Size(outerCornerDiameter,outerCornerDiameter)
val cornerOffset = Offset(outerCornerDiameter, outerCornerDiameter)
val cornerYOffset = Offset(0f, outerCornerDiameter)
moveTo(rect.bottomLeft - cornerYOffset)
addArc(
Rect(
offset = rect.bottomLeft - cornerOffset,
size = cornerSize
),
startAngleDegrees = 0f,
sweepAngleDegrees = 90f,
)
lineTo(rect.bottomLeft)
moveTo(rect.bottomRight - cornerYOffset)
addArc(
Rect(
offset = rect.bottomRight - cornerYOffset,
size = cornerSize
),
startAngleDegrees = 180f,
sweepAngleDegrees = -90f,
)
lineTo(rect.bottomRight)
}
clipPath(clippedPath, clipOp = ClipOp.Difference) {
drawRoundRect(
color = Color.Yellow,
cornerRadius = CornerRadius(outerCornerRadius, outerCornerRadius)
)
}
}
Result:
Also you can use a custom Shape for your Composables to give them a specific outline. Just extend the Shape interface and override the createOutline() method.
Example:
For the corners, the Path API offers a function arcTo(). Then, to draw the edges of the shape, use the lineTo() method.
class RoundedRectOutlinedCorner(
private val cornerRadius: Dp = 16.dp,
private val cutOutHeight: Dp = 60.dp,
private val cutOutWidth: Dp = 145.dp
) : Shape {
override fun createOutline(
size: Size, layoutDirection: LayoutDirection, density: Density
): Outline {
return Outline.Generic(Path().apply {
val cornerRadius = with(density) { cornerRadius.toPx() }
val cutOutHeight = with(density) { cutOutHeight.toPx() }
val cutOutWidth = with(density) { cutOutWidth.toPx() }
arcTo(
rect = Rect(offset = Offset(0f, 0f), Size(cornerRadius, cornerRadius)),
startAngleDegrees = 180f,
sweepAngleDegrees = 90f,
forceMoveTo = false
)
lineTo(size.width - cutOutWidth - cornerRadius, 0f)
arcTo(
rect = Rect(
offset = Offset(size.width - cutOutWidth - cornerRadius, 0f),
Size(cornerRadius, cornerRadius)
), startAngleDegrees = 270.0f, sweepAngleDegrees = 90f, forceMoveTo = false
)
lineTo(size.width - cutOutWidth, cutOutHeight - cornerRadius)
arcTo(
rect = Rect(
offset = Offset(size.width - cutOutWidth, cutOutHeight - cornerRadius),
Size(cornerRadius, cornerRadius)
), startAngleDegrees = 180.0f, sweepAngleDegrees = -90f, forceMoveTo = false
)
lineTo(size.width - cornerRadius, cutOutHeight)
arcTo(
rect = Rect(
offset = Offset(size.width - cornerRadius, cutOutHeight),
Size(cornerRadius, cornerRadius)
), startAngleDegrees = 270f, sweepAngleDegrees = 90f, forceMoveTo = false
)
lineTo(size.width, size.height - cornerRadius)
arcTo(
rect = Rect(
offset = Offset(size.width - cornerRadius, size.height - cornerRadius),
Size(cornerRadius, cornerRadius)
), startAngleDegrees = 0f, sweepAngleDegrees = 90f, forceMoveTo = false
)
lineTo(cornerRadius, size.height)
arcTo(
rect = Rect(
offset = Offset(0f, size.height - cornerRadius),
Size(cornerRadius, cornerRadius)
), startAngleDegrees = 90f, sweepAngleDegrees = 90f, forceMoveTo = false
)
close()
})
}
}
Usage:
Then, you can clip a shape with:
Modifier
.height(250.dp)
.clip(RoundedRectOutlinedCorner()),
Or with .graphicsLayer/.background etc.
Result:

Attempt to Crop bitmap using Renderscript failed with invalid slot index error

I'm trying to apply the solution presented in this Question's answer to crop a bitmap using RenderSript. My approach is a little bit different since I'm using bitmap directly as an input to crop.
Having very limited knowledge in RenderScript I developed the below code to crop my bitmap by modifying that answer.
crop.rs
#pragma version(1)
#pragma rs java_package_name(com.xxx.yyy)
#pragma rs_fp_relaxed
int32_t width;
int32_t height;
rs_allocation croppedImg;
uint xStart, yStart;
void __attribute__((kernel)) doCrop(uchar4 in,uint32_t x, uint32_t y) {
rsSetElementAt_uchar4(croppedImg,in, x-xStart, y-yStart);
}
ImageCropper
import android.content.Context
import android.graphics.Bitmap
import androidx.renderscript.*
import com.xxx.yyy.ScriptC_crop
import kotlin.math.abs
class ImageCropper(context: Context?) {
val rs = RenderScript.create(context)
var dx = 0 // (-width < dx < width);
var dy = 250 // (- height < dy < height);
var xStart = 50
var xEnd = 100
var yStart = 50
var yEnd = 100
fun crop(sourceBitmap: Bitmap): Bitmap{
val width = sourceBitmap.width
val height = sourceBitmap.height
if (dx<0) {
xStart = abs(dx)
xEnd= width
} else {
xStart = 0
xEnd = width - abs(dx)
}
if (dy<0) {
yStart= abs(dy)
yEnd=height
} else {
yStart = 0;
yEnd = height - abs(dy)
}
val cropperScript = ScriptC_crop(rs)
val inputType = Type.createXY(rs, Element.RGBA_8888(rs), width, height)
val inputAllocation = Allocation.createTyped(rs, inputType, Allocation.USAGE_SCRIPT)
inputAllocation.copyFrom(sourceBitmap)
val outputType = Type.createXY(rs, Element.RGBA_8888(rs), xEnd - xStart, yEnd - yStart)
val outputAllocation = Allocation.createTyped(rs, outputType, Allocation.USAGE_SCRIPT)
cropperScript._croppedImg = outputAllocation
cropperScript._width = width
cropperScript._height = height
cropperScript._xStart = xStart.toLong()
cropperScript._yStart = yStart.toLong()
val launchOptions: Script.LaunchOptions = Script.LaunchOptions()
launchOptions.setX(xStart, xEnd)
launchOptions.setY(yStart, yEnd)
cropperScript.forEach_doCrop(inputAllocation, launchOptions)
val resultBitmap = Bitmap.createBitmap(xEnd - xStart, yEnd - yStart, sourceBitmap.config)
outputAllocation.copyTo(resultBitmap)
rs.destroy()
return resultBitmap
}
}
When I try to execute the code I get a bellow error.
2021-04-11 20:55:41.639 18145-18202/com.chathuranga.shan.renderscriptexample E/RenderScript: Script::setVar unable to set allocation, invalid slot index
2021-04-11 20:55:41.645 18145-18202/com.chathuranga.shan.renderscriptexample A/libc: Fatal signal 11 (SIGSEGV), code 1 (SEGV_MAPERR), fault addr 0x519 in tid 18202 (erscriptexample), pid 18145 (erscriptexample)
2021-04-11 20:55:41.639 18145-18202/com.chathuranga.shan.renderscriptexample E/RenderScript: Script::setVar unable to set allocation, invalid slot index
This error occurs in the line where I set _xStart and _yStart values. What am I doing wrong? is it some sort of data type issue?

Assignment problem - What am I doing wrong?

I am working on image seam carving project, looking for some help. Can someone tell me what am I doing wrong here, Hyperskill is not accepting my solution. I am pretty sure I did not understand the project statement correctly. (I’ve been fighting it for a week)
Project: https://hyperskill.org/projects/100/stages/553/implement
First I am finding the minimum seam from all possible seams.
var minX = 0
// (minSeamX, 0) would be the coordinate of the minimum seam
var minSeamX = 0
var minSeam = Double.MAX_VALUE
//Starting from top left find the sum of pixel energies for all possible seams(#width number of possible seams)
for (column in 0 until width) {
var totalSeam = 0.0
var xHigh = column
var xLow = column
var min = Double.MAX_VALUE
for (y in 0 until height) {
for (x in xLow..xHigh) {
if (x < 0 || x > width - 1) continue
val energy = calculateEnergy(x, y, bufferedImage)
// println("Energy $x $y $energy")
if (energy < min) {
min = energy
minX = x
}
}
totalSeam += min
min = Double.MAX_VALUE
xLow = minX - 1
xHigh = minX + 1
}
if (totalSeam < minSeam) {
minSeamX = column
minSeam = totalSeam
}
println("total:$totalSeam")
}
after that I am applying the color to the minimum seam pixels
var xLow = minSeamX
var xHigh = minSeamX
var min = Double.MAX_VALUE
for (y in 0 until height) {
for (x in xLow..xHigh) {
val energy = calculateEnergy(x, y, bufferedImage)
if (energy < min) {
min = energy
minX = x
}
}
val createGraphics = applyColor(outImage, minX, y)
min = Double.MAX_VALUE
xLow = minX - 1
xHigh = minX + 1
}
Complete code
package seamcarving
import java.awt.Color
import java.awt.Graphics2D
import java.awt.image.BufferedImage
import java.io.File
import javax.imageio.ImageIO
import kotlin.math.pow
import kotlin.math.sqrt
fun main(args: Array<String>) {
val bufferedImage = ImageIO.read(File("/Users/name/Downloads/images/blue.png"))
val outImage = ImageIO.read(File("/Users/name/Downloads/images/blue.png"))
val height = bufferedImage.height
val width = bufferedImage.width
var minX = 0
// (minSeamX, 0) would be the coordinate of the minimum seam
var minSeamX = 0
var minSeam = Double.MAX_VALUE
//Starting from top left find the sum of pixel energies for all possible seams(#width number of possible seams)
for (column in 0 until width) {
var totalSeam = 0.0
var xHigh = column
var xLow = column
var min = Double.MAX_VALUE
for (y in 0 until height) {
for (x in xLow..xHigh) {
if (x < 0 || x > width - 1) continue
val energy = calculateEnergy(x, y, bufferedImage)
// println("Energy $x $y $energy")
if (energy < min) {
min = energy
minX = x
}
}
totalSeam += min
min = Double.MAX_VALUE
xLow = minX - 1
xHigh = minX + 1
}
if (totalSeam < minSeam) {
minSeamX = column
minSeam = totalSeam
}
println("total:$totalSeam")
}
var xLow = minSeamX
var xHigh = minSeamX
var min = Double.MAX_VALUE
for (y in 0 until height) {
for (x in xLow..xHigh) {
val energy = calculateEnergy(x, y, bufferedImage)
if (energy < min) {
min = energy
minX = x
}
}
val createGraphics = applyColor(outImage, minX, y)
min = Double.MAX_VALUE
xLow = minX - 1
xHigh = minX + 1
}
// for (x in 0 until width) {
// for (y in 0 until height) {
// val intensity = ((255.0 * array[x][y]) / max).toInt()
// val color = Color(intensity, intensity, intensity)
//// outputImage.setRGB(x, y, intensity)
// createGraphics.paint = color
// createGraphics.fillRect(x, y, 1, 1)
// }
// }
ImageIO.write(outImage, "png", File("out.png"))
// ImageIO.write(bufferedImage, "png", File("${args[3]}"))
}
private fun applyColor(outputImage: BufferedImage, maxX: Int, maxY: Int): Graphics2D? {
val createGraphics = outputImage.createGraphics()
val color = Color(255, 0, 0)
createGraphics.paint = color
createGraphics.fillRect(maxX, maxY, 1, 1)
return createGraphics
}
private fun calculateEnergy(x: Int, y: Int, bufferedImage: BufferedImage): Double {
return sqrt(getXGradient(x, y, bufferedImage) + getYGradient(x, y, bufferedImage))
}
fun getXGradient(x: Int, y: Int, inImage: BufferedImage): Double {
val width = inImage.width
var xx = x
var yy = y
if (x == 0) xx = 1
if (x == width - 1) xx = x - 1
val lc = Color(inImage.getRGB(xx - 1, yy))
val rc = Color(inImage.getRGB(xx + 1, yy))
return (lc.red - rc.red).toDouble().pow(2.0) + (lc.green - rc.green).toDouble().pow(2.0) + (lc.blue - rc.blue).toDouble().pow(2.0)
}
fun getYGradient(x: Int, y: Int, inImage: BufferedImage): Double {
val height = inImage.height
var xx = x
var yy = y
if (y == 0) yy = 1
if (y == height - 1) yy = y - 1
val lc = Color(inImage.getRGB(xx, yy - 1))
val rc = Color(inImage.getRGB(xx, yy + 1))
return (lc.red - rc.red).toDouble().pow(2.0) + (lc.green - rc.green).toDouble().pow(2.0) + (lc.blue - rc.blue).toDouble().pow(2.0)
}