Skip to content

Commit 60d5384

Browse files
add utility functions for transformation
1 parent 4ed08e8 commit 60d5384

File tree

1 file changed

+155
-0
lines changed

1 file changed

+155
-0
lines changed
Lines changed: 155 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,155 @@
1+
package com.smarttoolfactory.image.transform
2+
3+
import androidx.compose.ui.geometry.Offset
4+
import androidx.compose.ui.geometry.Rect
5+
import androidx.compose.ui.graphics.Color
6+
import androidx.compose.ui.graphics.drawscope.DrawScope
7+
import androidx.compose.ui.graphics.drawscope.Stroke
8+
import androidx.compose.ui.unit.dp
9+
import kotlin.math.cos
10+
import kotlin.math.sin
11+
12+
/**
13+
* Returns region of this [position] is at inside [rect] using threshold to determine distance
14+
* from corners or sides of [rect]
15+
* @param position position in or outside of [rect]
16+
* @param rect to determine which region is touched
17+
* @param threshold is distance from any corner or side to [position]
18+
*/
19+
internal fun getTouchRegion(
20+
position: Offset,
21+
rect: Rect,
22+
threshold: Float,
23+
handlePlacement: HandlePlacement
24+
): TouchRegion {
25+
26+
// Instead of using square check for power of 2 of threshold
27+
val squareOfThreshold = threshold * threshold
28+
29+
return when (handlePlacement) {
30+
HandlePlacement.Corner -> {
31+
getCornerTouchRegion(position, rect, squareOfThreshold)
32+
}
33+
34+
HandlePlacement.Side -> {
35+
getSideTouchRegion(position, rect, squareOfThreshold)
36+
}
37+
else -> {
38+
val touchRegion = getCornerTouchRegion(position, rect, squareOfThreshold)
39+
if (touchRegion == TouchRegion.Inside) {
40+
getSideTouchRegion(position, rect, squareOfThreshold)
41+
} else {
42+
touchRegion
43+
}
44+
}
45+
}
46+
}
47+
48+
private fun getCornerTouchRegion(
49+
position: Offset,
50+
rect: Rect,
51+
squareOfThreshold: Float
52+
): TouchRegion {
53+
return when {
54+
55+
inDistanceSquared(
56+
position,
57+
rect.topLeft,
58+
squareOfThreshold
59+
) -> TouchRegion.TopLeft
60+
inDistanceSquared(
61+
position,
62+
rect.topRight,
63+
squareOfThreshold
64+
) -> TouchRegion.TopRight
65+
inDistanceSquared(
66+
position,
67+
rect.bottomLeft,
68+
squareOfThreshold
69+
) -> TouchRegion.BottomLeft
70+
inDistanceSquared(
71+
position,
72+
rect.bottomRight,
73+
squareOfThreshold
74+
) -> TouchRegion.BottomRight
75+
rect.contains(offset = position) -> TouchRegion.Inside
76+
else -> TouchRegion.None
77+
}
78+
}
79+
80+
private fun getSideTouchRegion(
81+
position: Offset,
82+
rect: Rect,
83+
squareOfThreshold: Float
84+
): TouchRegion {
85+
return when {
86+
87+
inDistanceSquared(
88+
position,
89+
rect.centerLeft,
90+
squareOfThreshold
91+
) -> TouchRegion.CenterLeft
92+
inDistanceSquared(
93+
position,
94+
rect.topCenter,
95+
squareOfThreshold
96+
) -> TouchRegion.TopCenter
97+
inDistanceSquared(
98+
position,
99+
rect.centerRight,
100+
squareOfThreshold
101+
) -> TouchRegion.CenterRight
102+
inDistanceSquared(
103+
position,
104+
rect.bottomCenter,
105+
squareOfThreshold
106+
) -> TouchRegion.BottomCenter
107+
rect.contains(offset = position) -> TouchRegion.Inside
108+
else -> TouchRegion.None
109+
}
110+
}
111+
112+
/**
113+
* Check if [target] which is power of 2 of actual value to not use square to make this
114+
* operation cheaper
115+
*/
116+
internal fun inDistanceSquared(offset1: Offset, offset2: Offset, target: Float): Boolean {
117+
val x1 = offset1.x
118+
val y1 = offset1.y
119+
120+
val x2 = offset2.x
121+
val y2 = offset2.y
122+
123+
val distance = ((x2 - x1) * (x2 - x1) + (y2 - y1) * (y2 - y1))
124+
return distance < target
125+
}
126+
127+
/**
128+
* Draw one transparent and one white circle for border for handle
129+
*/
130+
internal fun DrawScope.drawBorderCircle(
131+
radius: Float,
132+
center: Offset
133+
) {
134+
drawCircle(color = Color.White.copy(alpha = .7f), radius = radius, center = center)
135+
drawCircle(color = Color.White, radius = radius, center = center, style = Stroke(1.dp.toPx()))
136+
}
137+
138+
/**
139+
* Rotates the given offset around the origin by the given angle in degrees.
140+
*
141+
* A positive angle indicates a counterclockwise rotation around the right-handed 2D Cartesian
142+
* coordinate system.
143+
*
144+
* See: [Rotation matrix](https://en.wikipedia.org/wiki/Rotation_matrix)
145+
*/
146+
fun Offset.rotateBy(
147+
angle: Float
148+
): Offset {
149+
val angleInRadians = ROTATION_CONST * angle
150+
val newX = x * cos(angleInRadians) - y * sin(angleInRadians)
151+
val newY = x * sin(angleInRadians) + y * cos(angleInRadians)
152+
return Offset(newX, newY)
153+
}
154+
155+
internal const val ROTATION_CONST = (Math.PI / 180f).toFloat()

0 commit comments

Comments
 (0)