diff --git a/core/src/main/kotlin/plus/rua/project/RotationGeometry.kt b/core/src/main/kotlin/plus/rua/project/RotationGeometry.kt index 69dfb26..7184976 100644 --- a/core/src/main/kotlin/plus/rua/project/RotationGeometry.kt +++ b/core/src/main/kotlin/plus/rua/project/RotationGeometry.kt @@ -25,6 +25,26 @@ object RotationGeometry { fun stableAspect(srcAspect: Float, rotationDegrees: Int): Float = if (rotationDegrees % 180 == 0) srcAspect else 1f / srcAspect + /** + * 计算当前动画角度 [angle] 对应的最近 90° 倍数基准角度。 + * + * 动态基准角度避免了在动画刚开始时以终点角度直接切换转置状态导致的错位黑边。 + */ + fun baseRotation(angle: Float): Int = + kotlin.math.round(angle / 90f).toInt() * 90 + + /** + * 当前动画角度 [angle] 相对最近基准角度 [baseRotation] 的偏移角度 [-45°, 45°]。 + */ + fun offsetDegrees(angle: Float): Float = + angle - baseRotation(angle) + + /** + * 判断在给定的 90° 倍数基准角度下,宽高尺寸是否转置。 + */ + fun isSwapped(baseRotation: Int): Boolean = + baseRotation % 180 != 0 + /** * 让「与容器同比例的图片矩形旋转 [offsetDegrees] 后仍完整覆盖容器」所需的最小缩放因子。 * diff --git a/core/src/main/kotlin/plus/rua/project/ui/PhotoEditorScreen.kt b/core/src/main/kotlin/plus/rua/project/ui/PhotoEditorScreen.kt index 7a7c601..3e7eee3 100644 --- a/core/src/main/kotlin/plus/rua/project/ui/PhotoEditorScreen.kt +++ b/core/src/main/kotlin/plus/rua/project/ui/PhotoEditorScreen.kt @@ -380,14 +380,15 @@ private fun EditableImage( } ) { val angle = displayRotation.value - val offset = angle - state.rotationDegrees + val baseRotation = RotationGeometry.baseRotation(angle) + val isSwapped = RotationGeometry.isSwapped(baseRotation) + val offset = RotationGeometry.offsetDegrees(angle) val scale = RotationGeometry.coverScale(offset, containerAspect) Image( bitmap = state.sourceBitmap.asImageBitmap(), contentDescription = "编辑中的照片", modifier = Modifier .layout { measurable, constraints -> - val isSwapped = (state.rotationDegrees % 180 != 0) val targetWidth = if (isSwapped) constraints.maxHeight else constraints.maxWidth val targetHeight = if (isSwapped) constraints.maxWidth else constraints.maxHeight val placeable = measurable.measure( diff --git a/core/src/test/kotlin/plus/rua/project/RotationGeometryTest.kt b/core/src/test/kotlin/plus/rua/project/RotationGeometryTest.kt index 5c06b54..a871763 100644 --- a/core/src/test/kotlin/plus/rua/project/RotationGeometryTest.kt +++ b/core/src/test/kotlin/plus/rua/project/RotationGeometryTest.kt @@ -7,6 +7,7 @@ import kotlin.math.sin import kotlin.test.Test import kotlin.test.assertEquals import kotlin.test.assertTrue +import kotlin.test.assertFalse /** * [RotationGeometry] 单元测试。 @@ -101,4 +102,33 @@ class RotationGeometryTest { ) } } + @Test + fun baseRotation_and_isSwapped_dynamicAngleTransition() { + assertEquals(0, RotationGeometry.baseRotation(0f)) + assertEquals(0, RotationGeometry.baseRotation(20f)) + assertEquals(0, RotationGeometry.baseRotation(44.9f)) + + assertEquals(90, RotationGeometry.baseRotation(45.1f)) + assertEquals(90, RotationGeometry.baseRotation(80f)) + assertEquals(90, RotationGeometry.baseRotation(134f)) + + assertEquals(180, RotationGeometry.baseRotation(135.1f)) + assertEquals(270, RotationGeometry.baseRotation(270f)) + + assertEquals(-90, RotationGeometry.baseRotation(-45.1f)) + assertEquals(-180, RotationGeometry.baseRotation(-135.1f)) + + assertFalse(RotationGeometry.isSwapped(0)) + assertTrue(RotationGeometry.isSwapped(90)) + assertFalse(RotationGeometry.isSwapped(180)) + assertTrue(RotationGeometry.isSwapped(270)) + assertTrue(RotationGeometry.isSwapped(-90)) + assertFalse(RotationGeometry.isSwapped(-180)) + + assertEquals(0f, RotationGeometry.offsetDegrees(0f), 1e-5f) + assertEquals(20f, RotationGeometry.offsetDegrees(20f), 1e-5f) + assertEquals(-44.9f, RotationGeometry.offsetDegrees(45.1f), 1e-3f) + assertEquals(-10f, RotationGeometry.offsetDegrees(80f), 1e-5f) + assertEquals(0f, RotationGeometry.offsetDegrees(90f), 1e-5f) + } }