Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix SkiaLayer transform #422

Merged
merged 9 commits into from Mar 10, 2023
Expand Up @@ -24,6 +24,7 @@ import androidx.compose.ui.geometry.toRect
import androidx.compose.ui.graphics.Canvas
import androidx.compose.ui.graphics.ClipOp
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.DefaultCameraDistance
import androidx.compose.ui.graphics.DefaultShadowColor
import androidx.compose.ui.graphics.Matrix
import androidx.compose.ui.graphics.Outline
Expand All @@ -40,13 +41,13 @@ import androidx.compose.ui.graphics.toArgb
import androidx.compose.ui.graphics.toSkiaRRect
import androidx.compose.ui.graphics.toSkiaRect
import androidx.compose.ui.node.OwnedLayer
import androidx.compose.ui.node.InvokeOnCanvas
import androidx.compose.ui.unit.Density
import androidx.compose.ui.unit.IntOffset
import androidx.compose.ui.unit.IntSize
import androidx.compose.ui.unit.LayoutDirection
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.toSize
import kotlin.math.max
import org.jetbrains.skia.ClipMode
import org.jetbrains.skia.Picture
import org.jetbrains.skia.PictureRecorder
Expand All @@ -65,6 +66,12 @@ internal class SkiaLayer(
OutlineCache(density, size, RectangleShape, LayoutDirection.Ltr)
// Internal for testing
internal val matrix = Matrix()
private val inverseMatrix: Matrix
get() = Matrix().apply {
setFrom(matrix)
invert()
}

private val pictureRecorder = PictureRecorder()
private var picture: Picture? = null
private var isDestroyed = false
Expand All @@ -75,6 +82,7 @@ internal class SkiaLayer(
private var rotationX: Float = 0f
private var rotationY: Float = 0f
private var rotationZ: Float = 0f
private var cameraDistance: Float = DefaultCameraDistance
private var scaleX: Float = 1f
private var scaleY: Float = 1f
private var alpha: Float = 1f
Expand Down Expand Up @@ -112,11 +120,19 @@ internal class SkiaLayer(
}

override fun mapOffset(point: Offset, inverse: Boolean): Offset {
return getMatrix(inverse).map(point)
return if (inverse) {
inverseMatrix
} else {
matrix
}.map(point)
}

override fun mapBounds(rect: MutableRect, inverse: Boolean) {
getMatrix(inverse).map(rect)
if (inverse) {
inverseMatrix
} else {
matrix
}.map(rect)
}

override fun isInLayer(position: Offset): Boolean {
Expand All @@ -133,17 +149,6 @@ internal class SkiaLayer(
return isInOutline(outlineCache.outline, x, y)
}

private fun getMatrix(inverse: Boolean): Matrix {
return if (inverse) {
Matrix().apply {
setFrom(matrix)
invert()
}
} else {
matrix
}
}

override fun updateLayerProperties(
scaleX: Float,
scaleY: Float,
Expand All @@ -170,6 +175,7 @@ internal class SkiaLayer(
this.rotationX = rotationX
this.rotationY = rotationY
this.rotationZ = rotationZ
this.cameraDistance = max(cameraDistance, 0.001f)
this.scaleX = scaleX
this.scaleY = scaleY
this.alpha = alpha
Expand All @@ -186,27 +192,26 @@ internal class SkiaLayer(
invalidate()
}

// TODO(demin): support perspective projection for rotationX/rotationY (as in Android)
// TODO(njawad) Add camera distance leveraging Sk3DView along with rotationX/rotationY
// see https://cs.android.com/search?q=RenderProperties.cpp&sq= updateMatrix method
// for how 3d transformations along with camera distance are applied. b/173402019
private fun updateMatrix() {
val pivotX = transformOrigin.pivotFractionX * size.width
val pivotY = transformOrigin.pivotFractionY * size.height

matrix.reset()
matrix.translate(x = -pivotX, y = -pivotY)
matrix *= Matrix().apply {
translate(x = -pivotX, y = -pivotY)
}
matrix *= Matrix().apply {
translate(translationX, translationY)
rotateX(rotationX)
rotateY(rotationY)
rotateZ(rotationZ)
rotateY(rotationY)
rotateX(rotationX)
scale(scaleX, scaleY)
}
matrix *= Matrix().apply {
translate(x = pivotX, y = pivotY)
// the camera location is passed in inches, set in pt
val depth = cameraDistance * 72f
MatkovIvan marked this conversation as resolved.
Show resolved Hide resolved
set(row = 2, column = 3, v = -1f / depth)
set(row = 2, column = 2, v = 0f)
}
matrix *= Matrix().apply {
translate(x = pivotX + translationX, y = pivotY + translationY)
}
}

Expand Down Expand Up @@ -234,11 +239,11 @@ internal class SkiaLayer(
}

override fun transform(matrix: Matrix) {
matrix.timesAssign(getMatrix(inverse = false))
matrix.timesAssign(this.matrix)
}

override fun inverseTransform(matrix: Matrix) {
matrix.timesAssign(getMatrix(inverse = true))
matrix.timesAssign(inverseMatrix)
}

private fun performDrawLayer(canvas: Canvas, bounds: Rect) {
Expand Down
Expand Up @@ -19,6 +19,7 @@ package androidx.compose.ui.platform
import androidx.compose.foundation.shape.CircleShape
import androidx.compose.ui.geometry.Offset
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.DefaultCameraDistance
import androidx.compose.ui.graphics.DefaultShadowColor
import androidx.compose.ui.graphics.RectangleShape
import androidx.compose.ui.graphics.RenderEffect
Expand Down Expand Up @@ -139,7 +140,8 @@ class SkiaLayerTest {
layer.resize(IntSize(100, 10))
layer.updateProperties(
rotationX = 45f,
transformOrigin = TransformOrigin(0f, 0f)
transformOrigin = TransformOrigin(0f, 0f),
cameraDistance = Float.MAX_VALUE
)
val matrix = layer.matrix

Expand All @@ -153,7 +155,8 @@ class SkiaLayerTest {
layer.resize(IntSize(100, 10))
layer.updateProperties(
rotationX = 45f,
transformOrigin = TransformOrigin(1f, 1f)
transformOrigin = TransformOrigin(1f, 1f),
cameraDistance = Float.MAX_VALUE
)
val matrix = layer.matrix

Expand All @@ -167,7 +170,8 @@ class SkiaLayerTest {
layer.resize(IntSize(100, 10))
layer.updateProperties(
rotationY = 45f,
transformOrigin = TransformOrigin(0f, 0f)
transformOrigin = TransformOrigin(0f, 0f),
cameraDistance = Float.MAX_VALUE
)
val matrix = layer.matrix

Expand All @@ -181,7 +185,8 @@ class SkiaLayerTest {
layer.resize(IntSize(100, 10))
layer.updateProperties(
rotationY = 45f,
transformOrigin = TransformOrigin(1f, 1f)
transformOrigin = TransformOrigin(1f, 1f),
cameraDistance = Float.MAX_VALUE
)
val matrix = layer.matrix

Expand Down Expand Up @@ -254,29 +259,31 @@ class SkiaLayerTest {
translationX = 60f,
translationY = 7f,
rotationX = 45f,
MatkovIvan marked this conversation as resolved.
Show resolved Hide resolved
transformOrigin = TransformOrigin(0f, 0f)
transformOrigin = TransformOrigin(0f, 0f),
cameraDistance = Float.MAX_VALUE
)
val matrix = layer.matrix

val y = (10 * cos45).roundToInt()
val translationY = (7 * cos45).roundToInt()
val translationY = 7
MatkovIvan marked this conversation as resolved.
Show resolved Hide resolved
assertEquals(IntOffset(0 + 60, 0 + translationY), matrix.map(Offset(0f, 0f)).round())
assertEquals(IntOffset(100 + 60, y + translationY), matrix.map(Offset(100f, 10f)).round())
}

@Test
fun translation_rotationY_left_top_origi() {
fun translation_rotationY_left_top_origin() {
layer.resize(IntSize(100, 10))
layer.updateProperties(
translationX = 60f,
translationY = 7f,
rotationY = 45f,
transformOrigin = TransformOrigin(0f, 0f)
transformOrigin = TransformOrigin(0f, 0f),
cameraDistance = Float.MAX_VALUE
)
val matrix = layer.matrix

val x = (100 * cos45).roundToInt()
val translationX = (60 * cos45).roundToInt()
val translationX = 60
assertEquals(IntOffset(0 + translationX, 0 + 7), matrix.map(Offset(0f, 0f)).round())
assertEquals(IntOffset(x + translationX, 10 + 7), matrix.map(Offset(100f, 10f)).round())
}
Expand Down Expand Up @@ -383,7 +390,7 @@ class SkiaLayerTest {
rotationX: Float = 0f,
rotationY: Float = 0f,
rotationZ: Float = 0f,
cameraDistance: Float = 0f,
cameraDistance: Float = DefaultCameraDistance,
transformOrigin: TransformOrigin = TransformOrigin.Center,
shape: Shape = RectangleShape,
clip: Boolean = false,
Expand Down
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.