Path.kt revision 4df8629cfe9f924b507ff26e53f896e025eb9ff7
1245f352e7e7a8b37faebc5aefd52ce98ae9f08e6Romain Guy/*
2245f352e7e7a8b37faebc5aefd52ce98ae9f08e6Romain Guy * Copyright (C) 2018 The Android Open Source Project
3245f352e7e7a8b37faebc5aefd52ce98ae9f08e6Romain Guy *
4245f352e7e7a8b37faebc5aefd52ce98ae9f08e6Romain Guy * Licensed under the Apache License, Version 2.0 (the "License");
5245f352e7e7a8b37faebc5aefd52ce98ae9f08e6Romain Guy * you may not use this file except in compliance with the License.
6245f352e7e7a8b37faebc5aefd52ce98ae9f08e6Romain Guy * You may obtain a copy of the License at
7245f352e7e7a8b37faebc5aefd52ce98ae9f08e6Romain Guy *
8245f352e7e7a8b37faebc5aefd52ce98ae9f08e6Romain Guy *       http://www.apache.org/licenses/LICENSE-2.0
9245f352e7e7a8b37faebc5aefd52ce98ae9f08e6Romain Guy *
10245f352e7e7a8b37faebc5aefd52ce98ae9f08e6Romain Guy * Unless required by applicable law or agreed to in writing, software
11245f352e7e7a8b37faebc5aefd52ce98ae9f08e6Romain Guy * distributed under the License is distributed on an "AS IS" BASIS,
12245f352e7e7a8b37faebc5aefd52ce98ae9f08e6Romain Guy * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13245f352e7e7a8b37faebc5aefd52ce98ae9f08e6Romain Guy * See the License for the specific language governing permissions and
14245f352e7e7a8b37faebc5aefd52ce98ae9f08e6Romain Guy * limitations under the License.
15245f352e7e7a8b37faebc5aefd52ce98ae9f08e6Romain Guy */
16245f352e7e7a8b37faebc5aefd52ce98ae9f08e6Romain Guy
174df8629cfe9f924b507ff26e53f896e025eb9ff7Romain Guy@file:Suppress("NOTHING_TO_INLINE")
184df8629cfe9f924b507ff26e53f896e025eb9ff7Romain Guy
19245f352e7e7a8b37faebc5aefd52ce98ae9f08e6Romain Guypackage androidx.graphics
20245f352e7e7a8b37faebc5aefd52ce98ae9f08e6Romain Guy
21245f352e7e7a8b37faebc5aefd52ce98ae9f08e6Romain Guyimport android.graphics.Path
22245f352e7e7a8b37faebc5aefd52ce98ae9f08e6Romain Guyimport android.graphics.PointF
23245f352e7e7a8b37faebc5aefd52ce98ae9f08e6Romain Guyimport android.support.annotation.RequiresApi
24245f352e7e7a8b37faebc5aefd52ce98ae9f08e6Romain Guy
25245f352e7e7a8b37faebc5aefd52ce98ae9f08e6Romain Guy/**
26245f352e7e7a8b37faebc5aefd52ce98ae9f08e6Romain Guy * A [PathSegment] is a line segment that represents an approximate fraction
27245f352e7e7a8b37faebc5aefd52ce98ae9f08e6Romain Guy * of a [Path] after [flattening][Path.flatten].
28245f352e7e7a8b37faebc5aefd52ce98ae9f08e6Romain Guy *
29245f352e7e7a8b37faebc5aefd52ce98ae9f08e6Romain Guy * @param start The start point of the line segment
30245f352e7e7a8b37faebc5aefd52ce98ae9f08e6Romain Guy * @param startFraction Fraction along the length of the path that the start point resides
31245f352e7e7a8b37faebc5aefd52ce98ae9f08e6Romain Guy * @param start The end point of the line segment
32245f352e7e7a8b37faebc5aefd52ce98ae9f08e6Romain Guy * @param startFraction Fraction along the length of the path that the end point resides
33245f352e7e7a8b37faebc5aefd52ce98ae9f08e6Romain Guy */
34245f352e7e7a8b37faebc5aefd52ce98ae9f08e6Romain Guydata class PathSegment(
35245f352e7e7a8b37faebc5aefd52ce98ae9f08e6Romain Guy        val start: PointF,
36245f352e7e7a8b37faebc5aefd52ce98ae9f08e6Romain Guy        val startFraction: Float,
37245f352e7e7a8b37faebc5aefd52ce98ae9f08e6Romain Guy        val end: PointF,
38245f352e7e7a8b37faebc5aefd52ce98ae9f08e6Romain Guy        val endFraction: Float)
39245f352e7e7a8b37faebc5aefd52ce98ae9f08e6Romain Guy
40245f352e7e7a8b37faebc5aefd52ce98ae9f08e6Romain Guy/**
41245f352e7e7a8b37faebc5aefd52ce98ae9f08e6Romain Guy * Flattens (or approximate) the [Path] with a series of line segments.
42245f352e7e7a8b37faebc5aefd52ce98ae9f08e6Romain Guy *
43245f352e7e7a8b37faebc5aefd52ce98ae9f08e6Romain Guy * @param error The acceptable error for a line on the Path. Typically this would be
44245f352e7e7a8b37faebc5aefd52ce98ae9f08e6Romain Guy *              0.5 so that the error is less than half a pixel. This value must be
45245f352e7e7a8b37faebc5aefd52ce98ae9f08e6Romain Guy *              positive and is set to 0.5 by default.
46245f352e7e7a8b37faebc5aefd52ce98ae9f08e6Romain Guy *
47245f352e7e7a8b37faebc5aefd52ce98ae9f08e6Romain Guy * @see Path.approximate
48245f352e7e7a8b37faebc5aefd52ce98ae9f08e6Romain Guy */
49245f352e7e7a8b37faebc5aefd52ce98ae9f08e6Romain Guy@RequiresApi(26)
50245f352e7e7a8b37faebc5aefd52ce98ae9f08e6Romain Guyfun Path.flatten(error: Float = 0.5f): Iterable<PathSegment> {
5104fa5880919c52cf71be74f085f9a5343f94d4d9Romain Guy    val pathData = approximate(error)
5204fa5880919c52cf71be74f085f9a5343f94d4d9Romain Guy    val pointCount = pathData.size / 3
5304fa5880919c52cf71be74f085f9a5343f94d4d9Romain Guy    val segments = ArrayList<PathSegment>(pointCount / 2)
5404fa5880919c52cf71be74f085f9a5343f94d4d9Romain Guy    for (i in 1 until pointCount) {
55245f352e7e7a8b37faebc5aefd52ce98ae9f08e6Romain Guy        val index = i * 3
56245f352e7e7a8b37faebc5aefd52ce98ae9f08e6Romain Guy        val prevIndex = (i - 1) * 3
57245f352e7e7a8b37faebc5aefd52ce98ae9f08e6Romain Guy
5804fa5880919c52cf71be74f085f9a5343f94d4d9Romain Guy        val d = pathData[index]
5904fa5880919c52cf71be74f085f9a5343f94d4d9Romain Guy        val x = pathData[index + 1]
6004fa5880919c52cf71be74f085f9a5343f94d4d9Romain Guy        val y = pathData[index + 2]
61245f352e7e7a8b37faebc5aefd52ce98ae9f08e6Romain Guy
6204fa5880919c52cf71be74f085f9a5343f94d4d9Romain Guy        val pd = pathData[prevIndex]
6304fa5880919c52cf71be74f085f9a5343f94d4d9Romain Guy        val px = pathData[prevIndex + 1]
6404fa5880919c52cf71be74f085f9a5343f94d4d9Romain Guy        val py = pathData[prevIndex + 2]
65245f352e7e7a8b37faebc5aefd52ce98ae9f08e6Romain Guy
66245f352e7e7a8b37faebc5aefd52ce98ae9f08e6Romain Guy        if (d != pd && (x != py || y != py)) {
67245f352e7e7a8b37faebc5aefd52ce98ae9f08e6Romain Guy            segments.add(PathSegment(PointF(px, py), pd, PointF(x, y), d))
68245f352e7e7a8b37faebc5aefd52ce98ae9f08e6Romain Guy        }
69245f352e7e7a8b37faebc5aefd52ce98ae9f08e6Romain Guy    }
70245f352e7e7a8b37faebc5aefd52ce98ae9f08e6Romain Guy    return segments
71245f352e7e7a8b37faebc5aefd52ce98ae9f08e6Romain Guy}
724df8629cfe9f924b507ff26e53f896e025eb9ff7Romain Guy
734df8629cfe9f924b507ff26e53f896e025eb9ff7Romain Guy/**
744df8629cfe9f924b507ff26e53f896e025eb9ff7Romain Guy * Returns the union of two paths as a new [Path].
754df8629cfe9f924b507ff26e53f896e025eb9ff7Romain Guy */
764df8629cfe9f924b507ff26e53f896e025eb9ff7Romain Guy@RequiresApi(19)
774df8629cfe9f924b507ff26e53f896e025eb9ff7Romain Guyinline operator fun Path.plus(p: Path): Path {
784df8629cfe9f924b507ff26e53f896e025eb9ff7Romain Guy    return Path(this).apply {
794df8629cfe9f924b507ff26e53f896e025eb9ff7Romain Guy        op(p, Path.Op.UNION)
804df8629cfe9f924b507ff26e53f896e025eb9ff7Romain Guy    }
814df8629cfe9f924b507ff26e53f896e025eb9ff7Romain Guy}
824df8629cfe9f924b507ff26e53f896e025eb9ff7Romain Guy
834df8629cfe9f924b507ff26e53f896e025eb9ff7Romain Guy/**
844df8629cfe9f924b507ff26e53f896e025eb9ff7Romain Guy * Returns the difference of two paths as a new [Path].
854df8629cfe9f924b507ff26e53f896e025eb9ff7Romain Guy */
864df8629cfe9f924b507ff26e53f896e025eb9ff7Romain Guy@RequiresApi(19)
874df8629cfe9f924b507ff26e53f896e025eb9ff7Romain Guyinline operator fun Path.minus(p: Path): Path {
884df8629cfe9f924b507ff26e53f896e025eb9ff7Romain Guy    return Path(this).apply {
894df8629cfe9f924b507ff26e53f896e025eb9ff7Romain Guy        op(p, Path.Op.DIFFERENCE)
904df8629cfe9f924b507ff26e53f896e025eb9ff7Romain Guy    }
914df8629cfe9f924b507ff26e53f896e025eb9ff7Romain Guy}
924df8629cfe9f924b507ff26e53f896e025eb9ff7Romain Guy
934df8629cfe9f924b507ff26e53f896e025eb9ff7Romain Guy/**
944df8629cfe9f924b507ff26e53f896e025eb9ff7Romain Guy * Returns the union of two paths as a new [Path].
954df8629cfe9f924b507ff26e53f896e025eb9ff7Romain Guy */
964df8629cfe9f924b507ff26e53f896e025eb9ff7Romain Guy@RequiresApi(19)
974df8629cfe9f924b507ff26e53f896e025eb9ff7Romain Guyinline infix fun Path.and(p: Path) = this + p
984df8629cfe9f924b507ff26e53f896e025eb9ff7Romain Guy
994df8629cfe9f924b507ff26e53f896e025eb9ff7Romain Guy/**
1004df8629cfe9f924b507ff26e53f896e025eb9ff7Romain Guy * Returns the intersection of two paths as a new [Path].
1014df8629cfe9f924b507ff26e53f896e025eb9ff7Romain Guy * If the paths do not intersect, returns an empty path.
1024df8629cfe9f924b507ff26e53f896e025eb9ff7Romain Guy */
1034df8629cfe9f924b507ff26e53f896e025eb9ff7Romain Guy@RequiresApi(19)
1044df8629cfe9f924b507ff26e53f896e025eb9ff7Romain Guyinline infix fun Path.or(p: Path): Path {
1054df8629cfe9f924b507ff26e53f896e025eb9ff7Romain Guy    return Path().apply {
1064df8629cfe9f924b507ff26e53f896e025eb9ff7Romain Guy        op(this@or, p, Path.Op.INTERSECT)
1074df8629cfe9f924b507ff26e53f896e025eb9ff7Romain Guy    }
1084df8629cfe9f924b507ff26e53f896e025eb9ff7Romain Guy}
1094df8629cfe9f924b507ff26e53f896e025eb9ff7Romain Guy
1104df8629cfe9f924b507ff26e53f896e025eb9ff7Romain Guy/**
1114df8629cfe9f924b507ff26e53f896e025eb9ff7Romain Guy * Returns the union minus the intersection of two paths as a new [Path].
1124df8629cfe9f924b507ff26e53f896e025eb9ff7Romain Guy */
1134df8629cfe9f924b507ff26e53f896e025eb9ff7Romain Guy@RequiresApi(19)
1144df8629cfe9f924b507ff26e53f896e025eb9ff7Romain Guyinline infix fun Path.xor(p: Path): Path {
1154df8629cfe9f924b507ff26e53f896e025eb9ff7Romain Guy    return Path(this).apply {
1164df8629cfe9f924b507ff26e53f896e025eb9ff7Romain Guy        op(p, Path.Op.XOR)
1174df8629cfe9f924b507ff26e53f896e025eb9ff7Romain Guy    }
1184df8629cfe9f924b507ff26e53f896e025eb9ff7Romain Guy}
119