Android canvas , how to get a sub path from existing path? - android

I have a Bezier curved path in canvas starting from (0,0) and ending in (canvasWidth,0) , with a control point at (canvasWidth,canvasHeight)
its drawing properly and Im getting a curved line. Im drawing it using the path.quadTo method as shown below
path.moveTo(mPointStart.x, mPointStart.y);
path.quadTo(mControlPoint.x, mControlPoint.y, mPointEnd.x, mPointEnd.y);
canvas.drawPath(path, paint);
Now, I want to draw a sub path over this existing path. Say if I want to draw half of the path ,
I want to overdraw the same path till half of the way using some other paint, so that half of the path will be in one color, other half will be in the old color. How can I find the points till half of the existing path?

Ok so the way I did this (in Kotlin), was to use the PathMeasure class. It doesn't look the best, but it works! So please post any tidier answers.
private fun getSubPath(path: Path, start: Float, end: Float): Path {
val pathMeasure = PathMeasure(path, false)
val point = FloatArray(2)
val subPath = Path()
var startFound = false
var startDistance = start
var endDistance = end
while (pathMeasure.nextContour()) {
val length = pathMeasure.length
startDistance -= length
endDistance -= length
if (!startFound) {
if (startDistance <= 0) {
startFound = true
val startPoint = length + startDistance
pathMeasure.getPosTan(startPoint, point, null)
subPath.moveTo(point[0], point[1])
if (startDistance < 0) {
val endPoint = length + endDistance
pathMeasure.getPosTan(endPoint, point, null)
subPath.lineTo(point[0], point[1])
if (endDistance <= 0) {
break
}
}
}
} else {
val endPoint = length + endDistance
pathMeasure.getPosTan(endPoint, point, null)
subPath.lineTo(point[0], point[1])
if (endDistance <= 0) {
break
}
}
}
return subPath
}
The parameters start and end can't be smaller then 0 or larger then the path's total length.
The way to use this is by the following:
private fun usePathFunction() {
val start = pathsTotalLength * 0.25
val end = pathsTotalLength * 0.75
val subPath = getSubPath(path, start, end)
}

I think using getSegment is the proper answer to this question:
private fun getSubPath(path: Path, start: Float, end: Float): Path {
val subPath = Path()
val pathMeasure = PathMeasure(path, false)
pathMeasure.getSegment(start * pathMeasure.length, end * pathMeasure.length, subPath, true)
return subPath
}
usage:
val subPath = getSubPath(path = originalPath, start = 0.2f, end = 0.8f)

Related

Comparing two user-drawn Canvas lines in Kotlin

I need to compare, for example, two straight lines. One of these lines will be set by me initially (or drawn), will have certain coordinates and thickness. The second one will be drawn by the user. Finally, I need to see the result of comparing these lines as a percentage. Can it be implement like in the following code below? I had an idea to draw my own line, put its coordinates from Path into an array somehow, and then compare it with the new one. But i don't know.
And, and yet, can you tell me how to make the background transparent? I am not satisfied with the replacement of a white background with a static picture, the animation will take place in the far background
Thank you!
enter image description here
import android.content.Context
import android.graphics.*
import android.util.Log
import android.view.View
import android.view.MotionEvent
import android.view.ViewConfiguration
import androidx.core.content.res.ResourcesCompat
class SomeDraw(context: Context) : View(context) {
// Holds the path you are currently drawing.
private var path = Path()
private val drawColor = ResourcesCompat.getColor(resources, R.color.purple_700, null)
private val backgroundColor = ResourcesCompat.getColor(resources, R.color.transparent, null)
private lateinit var extraCanvas: Canvas
private lateinit var extraBitmap: Bitmap
private lateinit var frame: Rect
// Set up the paint with which to draw.
private val paint = Paint().apply {
color = drawColor
// Smooths out edges of what is drawn without affecting shape.
isAntiAlias = true
// Dithering affects how colors with higher-precision than the device are down-sampled.
isDither = true
style = Paint.Style.STROKE // default: FILL
strokeJoin = Paint.Join.ROUND // default: MITER
strokeCap = Paint.Cap.ROUND // default: BUTT
strokeWidth = 70.0F // default: Hairline-width (really thin)
}
/**
* Don't draw every single pixel.
* If the finger has has moved less than this distance, don't draw. scaledTouchSlop, returns
* the distance in pixels a touch can wander before we think the user is scrolling.
*/
private val touchTolerance = ViewConfiguration.get(context).scaledTouchSlop
private var currentX = 0f
private var currentY = 0f
private var motionTouchEventX = 0f
private var motionTouchEventY = 0f
/**
* Called whenever the view changes size.
* Since the view starts out with no size, this is also called after
* the view has been inflated and has a valid size.
*/
override fun onSizeChanged(width: Int, height: Int, oldWidth: Int, oldHeight: Int) {
super.onSizeChanged(width, height, oldWidth, oldHeight)
if (::extraBitmap.isInitialized) extraBitmap.recycle()
extraBitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888)
extraCanvas = Canvas(extraBitmap)
extraCanvas.drawColor(backgroundColor)
// Calculate a rectangular frame around the picture.
val inset = 0
frame = Rect(inset, inset, width - inset, height - inset)
}
override fun onDraw(canvas: Canvas) {
// Draw the bitmap that has the saved path.
canvas.drawBitmap(extraBitmap, 0f, 0f, null)
// Draw a frame around the canvas.
extraCanvas.drawRect(frame, paint)
}
/**
* No need to call and implement MyCanvasView#performClick, because MyCanvasView custom view
* does not handle click actions.
*/
override fun onTouchEvent(event: MotionEvent): Boolean {
motionTouchEventX = event.x
motionTouchEventY = event.y
when (event.action) {
MotionEvent.ACTION_DOWN -> touchStart()
MotionEvent.ACTION_MOVE -> touchMove()
MotionEvent.ACTION_UP -> touchUp()
}
return true
}
/**
* The following methods factor out what happens for different touch events,
* as determined by the onTouchEvent() when statement.
* This keeps the when conditional block
* concise and makes it easier to change what happens for each event.
* No need to call invalidate because we are not drawing anything.
*/
private fun touchStart() {
path.reset()
path.moveTo(motionTouchEventX, motionTouchEventY)
currentX = motionTouchEventX
currentY = motionTouchEventY
}
private fun touchMove() {
val dx = Math.abs(motionTouchEventX - currentX)
val dy = Math.abs(motionTouchEventY - currentY)
if (dx >= touchTolerance || dy >= touchTolerance) {
// QuadTo() adds a quadratic bezier from the last point,
// approaching control point (x1,y1), and ending at (x2,y2).
path.quadTo(currentX, currentY, (motionTouchEventX + currentX) / 2, (motionTouchEventY + currentY) / 2)
currentX = motionTouchEventX
currentY = motionTouchEventY
// Draw the path in the extra bitmap to save it.
extraCanvas.drawPath(path, paint)
val pathTrue = path
Log.d("AAA", ""+pathTrue.equals(path))
}
// Invalidate() is inside the touchMove() under ACTION_MOVE because there are many other
// types of motion events passed into this listener, and we don't want to invalidate the
// view for those.
invalidate()
}
private fun touchUp() {
// Reset the path so it doesn't get drawn again.
path.reset()
}
}
I tried to work with Path but nothing worked. Tried various ways to set transparency to the background (color, apply alpha, delete) - nothing helped

How to draw "path from circles" from A point to B point in onDraw

I need to draw on the canvas in onDraw path from A to B with many circles (bubbles). Like that:
How to do this?
I already have pins that drawing by click on the image (point getting from array)
#SuppressLint("DrawAllocation")
override fun onDraw(canvas: Canvas) {
super.onDraw(canvas)
invertedPinsCoordinates.forEach {
val marker = getPinForCoordinates(it)
val matrixMarker = Matrix()
matrixMarker.setTranslate(it.x, it.y)
matrixMarker.postConcat(mMatrix)
canvas.drawBitmap(marker, matrixMarker, null)
}
}
But don't understand how to draw path from circles. Please help!
I created the solution. This is algorithm to get points between:
private fun getPointsToDraw(a: CoordinatesEntity, b: CoordinatesEntity): List<CoordinatesEntity> {
val points = ArrayList<CoordinatesEntity>()
val numberOfPoints = 5
val stepX = (b.x - a.x) / numberOfPoints
val stepY = (b.y - a.y) / numberOfPoints
points.add(a)
for (i in 0 until numberOfPoints) {
val lastPoint = points.last()
points.add(CoordinatesEntity(lastPoint.x + stepX, lastPoint.y + stepY))
}
points.add(b)
return points
}
data class:
data class CoordinatesEntity(var x: Float, var y: Float)
Of course we can get only fixed points between, cause there is indefinite count.
Here is how to write them.

Android Path Not Closing, I can't fill with color

I have a custom squircle android view. I am drawing a Path but and I cannot fill the path with color.
StackOverflow is asking me to provide more detail, but I don't think I can explain this better. All I need is to fill the path with code. If you need to see the whole class let me know.
init {
val typedValue = TypedValue()
val theme = context!!.theme
theme.resolveAttribute(R.attr.colorAccentTheme, typedValue, true)
paint.color = typedValue.data
paint.strokeWidth = 6f
paint.style = Paint.Style.FILL_AND_STROKE
paint.isAntiAlias = true
shapePadding = 10f
}
open fun onLayoutInit() {
val hW = (this.measuredW / 2) - shapePadding
val hH = (this.measuredH / 2) - shapePadding
/*
Returns a series of Vectors along the path
of the squircle
*/
points = Array(360) { i ->
val angle = toRadians(i.toDouble())
val x = pow(abs(cos(angle)), corners) * hW * sgn(cos(angle))
val y = pow(abs(sin(angle)), corners) * hH * sgn(sin(angle))
Pair(x.toFloat(), y.toFloat())
}
/*
Match the path to the points
*/
for (i in 0..points.size - 2) {
val p1 = points[i]
val p2 = points[i + 1]
path.moveTo(p1.first, p1.second)
path.lineTo(p2.first, p2.second)
}
/*
Finish closing the path's points
*/
val fst = points[0]
val lst = points[points.size - 1]
path.moveTo(lst.first, lst.second)
path.lineTo(fst.first, fst.second)
path.fillType = Path.FillType.EVEN_ODD
path.close()
postInvalidate()
}
override fun onDraw(canvas: Canvas?) {
canvas?.save()
canvas?.translate(measuredW / 2, measuredH / 2)
canvas?.drawPath(path, paint)
canvas?.restore()
super.onDraw(canvas)
}
}
The path stroke works fine, but I can't fill it with color.

Android - Fill different colors between two lines using MPAndroidChart

following this (Android - Fill the color between two lines using MPAndroidChart) answer I was able to fill with color the space between two lines using AndroidMPChart library.
But now I want to customize the filling color to have:
the areas above the boundarySet filled with blue color;
the areas below the boundarySet filled with green color.
Like in the following screenshot (please note that the blue line is a lineSet, so it could be that it is not a limit line):
I would like to customize the line color of the chart, setting it as the filling:
blue color for the line above the boundarySet;
green color for the line below the boundary set.
Is it possible?
I'm not able to find anything similar in the examples using MPAndroidChart.
Thank you!
you can try to override the drawLinear in LineChartRender
This has worked for me:
dataSet.setFillFormatter(new DefaultFillFormatter() {
#Override
public float getFillLinePosition(ILineDataSet dataSet, LineDataProvider dataProvider) {
return 22500;// its value of midel Y line
}
});
I've done this with this custom chart render) I use colors from line here, but you can use other colors. Change your colors here :drawFilledPath(c, filled, dataSet.colors[index], dataSet.fillAlpha)
class CustomLineChartRender(
lineDataProvider: LineDataProvider,
chartAnimator: ChartAnimator,
port: ViewPortHandler
)
: LineChartRenderer(lineDataProvider, chartAnimator, port) {
override fun drawLinearFill(
c: Canvas?,
dataSet: ILineDataSet?,
trans: Transformer?,
bounds: XBounds?
) {
val filled = mGenerateFilledPathBuffer
val startingIndex = bounds!!.min
val endingIndex = bounds!!.range + bounds!!.min
val indexInterval = 1
var currentStartIndex = 0
var currentEndIndex = indexInterval
var iterations = 0
do {
currentStartIndex = startingIndex + iterations * indexInterval
currentEndIndex = currentStartIndex + indexInterval
currentEndIndex = if (currentEndIndex > endingIndex) endingIndex else currentEndIndex
if (currentStartIndex <= currentEndIndex) {
generateFilledPath(dataSet!!, currentStartIndex, currentEndIndex, filled)
trans!!.pathValueToPixel(filled)
val drawable = dataSet.fillDrawable
if (drawable != null) {
drawFilledPath(c, filled, drawable)
} else {
val index = startingIndex + iterations
drawFilledPath(c, filled, dataSet.colors[index], dataSet.fillAlpha)
}
}
iterations++
} while (currentStartIndex <= currentEndIndex)
}
private fun generateFilledPath(
dataSet: ILineDataSet,
startIndex: Int,
endIndex: Int,
outputPath: Path
) {
val fillMin = dataSet.fillFormatter.getFillLinePosition(dataSet, mChart)
val phaseY = mAnimator.phaseY
val isDrawSteppedEnabled = dataSet.mode == LineDataSet.Mode.STEPPED
outputPath.reset()
val entry = dataSet.getEntryForIndex(startIndex)
outputPath.moveTo(entry.x, fillMin)
outputPath.lineTo(entry.x, entry.y * phaseY)
// create a new path
var currentEntry: Entry? = null
var previousEntry = entry
for (x in startIndex + 1..endIndex) {
currentEntry = dataSet.getEntryForIndex(x)
if (isDrawSteppedEnabled) {
outputPath.lineTo(currentEntry.x, previousEntry!!.y * phaseY)
}
outputPath.lineTo(currentEntry.x, currentEntry.y * phaseY)
previousEntry = currentEntry
}
// close up
if (currentEntry != null) {
outputPath.lineTo(currentEntry.x, fillMin)
}
outputPath.close()
}
}

Intersection of two paths or a path and a point never returns true

I came across this and this questions, all about detection intersections in Android. Well, I couldn't manage to make them work with the final code, so I made an example where 2 lines definitely intersect. Not even lucky in that case. I've made an example code with two straight paths, regions that fit them, and a point that definitely crosses it. Completely unlucky.
var theyCross = false
val intersectionPath = Path()
val clipArea = Region(0, 0, 100, 100)
val path1 = Path()
path1.moveTo(50f, 0f)
path1.lineTo(50f, 100f)
val path2 = Path()
path2.moveTo(0f, 50f)
path2.lineTo(100f, 50f)
val newRegion1 = Region()
newRegion1.setPath(path1, clipArea)
val newRegion2 = Region()
newRegion2.setPath(path2, clipArea)
if(
!newRegion1.quickReject(newRegion2) &&
newRegion1.op(newRegion2, Region.Op.INTERSECT)
) {
// lines should cross!
theyCross = true
}
if (intersectionPath.op(path1, path2, Path.Op.INTERSECT)) {
if (!intersectionPath.isEmpty) {
// lines should cross!
theyCross = true
}
}
if (newRegion1.contains(50, 50)) {
// lines should cross!
theyCross = true
}
if (newRegion1.quickContains(49, 49, 51, 51)) {
// lines should cross!
theyCross = true
}
In this example I'm not using a Canvas, but in my original code, I am, and each path is made of a Paint with strokeWidth. No luck. Has any of you faced this before?
It only works if the paths are surfaces, not lines, e.g. :
val clipArea = Region(0, 0, 100, 100)
val path1 = Path()
path1.moveTo(50f, 0f)
path1.lineTo(50f, 100f)
path1.lineTo(51f, 100f)
path1.lineTo(51f, 0f)
path1.close()
val path2 = Path()
path2.moveTo(0f, 50f)
path2.lineTo(100f, 50f)
path2.lineTo(100f, 51f)
path2.lineTo(0f, 51f)
path2.close()
By the way the (ignored) return value of newRegion1.setPath(path1, clipArea) is now true (non-empty) instead of false

Categories

Resources