Cutting one shape from an other shape in Jetpack Compose - kotlin

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:

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

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

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
...
}
)

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)
}

Setting SKLabelNode to centre of SKSpriteNode Swift

I'm trying to set a SKLabelNode's position to the center of a SKSpriteNode.
I've looked at other questions on this but none of these work with Swift 3.
This is my code:
var letter1background = SKSpriteNode(imageNamed: "letterbackground")
var letter1text = SKLabelNode()
override func didMove(to view: SKView) {
letter1background.position = CGPoint(x: self.size.width / 8, y: self.size.height / 7.5)
letter1background.size = CGSize(width: self.size.width / 8, height: self.size.width / 8)
letter1background.zPosition = -5
self.addChild(letter1background)
letter1text.text = "C"
letter1text.fontName = "Verdana-Bold "
letter1text.fontSize = 28
letter1text.fontColor = UIColor.white
letter1text.zPosition = 0
letter1text.horizontalAlignmentMode = letter1background.center
letter1text.verticalAlignmentMode = letter1background.center
self.letter1background.addChild(letter1text)
}
This code looks like it would work but in Swift 3 I now get this error:
Value of type 'SKSpriteNode' has no member 'center'
I have also tried:
self.letter1text.position = CGPoint(x: letter1background.frame.width/2 - (letter1text.frame.width/2), y: letter1background.frame.height/2 -(letter1text.frame.height/2))
&
letter1text.position = CGPoint(x:letter1background.frame.midX, y:letter1background.frame.midY)
But I still get results like this:
Any ideas?
Your calculations of label's position are wrong. You just have to add a label to the at 0,0 position and it will be centered because of SpriteKit's coordinate system rules (0,0 is at the center of the node).
override func didMove(to view: SKView) {
let label = SKLabelNode(fontNamed: "Arial")
label.text = "C"
label.fontColor = .black
label.verticalAlignmentMode = .center
label.fontSize = 29.0
label.zPosition = 1
let background = SKSpriteNode(color: .green, size: CGSize(width: 50, height: 50))
background.addChild(label)
addChild(background)
}
And you will get this: