Fix image orientation, reduce sensitivity, fix overlay clipping
Image orientation: - Actually rotate captured bitmap using ImageProxy.rotationDegrees - Save with EXIF ORIENTATION_NORMAL (bitmap already correctly oriented) - Handle front camera mirroring Gesture sensitivity (halved again): - Position drag: 0.15x (was 0.3x) - Rotation: 0.2x (was 0.4x) - Size pinch: 0.25x (was 0.5x) - Zoom pinch: 0.4x (was 0.6x) Overlay drawing: - Use screen diagonal to calculate extended geometry - Draw lines and rectangles that extend beyond screen bounds - Prevents clipping when tilt-shift effect is rotated Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
parent
3bbf4f9d14
commit
e3e05af0b8
2 changed files with 71 additions and 31 deletions
|
|
@ -51,8 +51,11 @@ class ImageCaptureHandler(
|
|||
object : ImageCapture.OnImageCapturedCallback() {
|
||||
override fun onCaptureSuccess(imageProxy: ImageProxy) {
|
||||
try {
|
||||
// Get rotation from ImageProxy (sensor orientation)
|
||||
val imageRotation = imageProxy.imageInfo.rotationDegrees
|
||||
|
||||
// Convert ImageProxy to Bitmap
|
||||
val bitmap = imageProxyToBitmap(imageProxy)
|
||||
var bitmap = imageProxyToBitmap(imageProxy)
|
||||
imageProxy.close()
|
||||
|
||||
if (bitmap == null) {
|
||||
|
|
@ -60,21 +63,19 @@ class ImageCaptureHandler(
|
|||
return
|
||||
}
|
||||
|
||||
// Apply tilt-shift effect to captured image
|
||||
// Rotate bitmap to correct orientation
|
||||
// Camera sensor is landscape, we need to rotate for portrait
|
||||
bitmap = rotateBitmap(bitmap, imageRotation, isFrontCamera)
|
||||
|
||||
// Apply tilt-shift effect to the correctly oriented image
|
||||
val processedBitmap = applyTiltShiftEffect(bitmap, blurParams)
|
||||
bitmap.recycle()
|
||||
|
||||
// Determine EXIF orientation
|
||||
val rotationDegrees = OrientationDetector.rotationToDegrees(deviceRotation)
|
||||
val exifOrientation = OrientationDetector.degreesToExifOrientation(
|
||||
rotationDegrees, isFrontCamera
|
||||
)
|
||||
|
||||
// Save with EXIF data
|
||||
// Save with EXIF orientation as NORMAL (bitmap is already rotated)
|
||||
kotlinx.coroutines.runBlocking {
|
||||
val result = photoSaver.saveBitmap(
|
||||
processedBitmap,
|
||||
exifOrientation,
|
||||
ExifInterface.ORIENTATION_NORMAL,
|
||||
location
|
||||
)
|
||||
processedBitmap.recycle()
|
||||
|
|
@ -94,6 +95,37 @@ class ImageCaptureHandler(
|
|||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Rotates a bitmap to the correct orientation.
|
||||
*/
|
||||
private fun rotateBitmap(bitmap: Bitmap, rotationDegrees: Int, isFrontCamera: Boolean): Bitmap {
|
||||
if (rotationDegrees == 0 && !isFrontCamera) {
|
||||
return bitmap
|
||||
}
|
||||
|
||||
val matrix = Matrix()
|
||||
|
||||
// Apply rotation
|
||||
if (rotationDegrees != 0) {
|
||||
matrix.postRotate(rotationDegrees.toFloat())
|
||||
}
|
||||
|
||||
// Mirror for front camera
|
||||
if (isFrontCamera) {
|
||||
matrix.postScale(-1f, 1f)
|
||||
}
|
||||
|
||||
val rotated = Bitmap.createBitmap(
|
||||
bitmap, 0, 0, bitmap.width, bitmap.height, matrix, true
|
||||
)
|
||||
|
||||
if (rotated != bitmap) {
|
||||
bitmap.recycle()
|
||||
}
|
||||
|
||||
return rotated
|
||||
}
|
||||
|
||||
private fun imageProxyToBitmap(imageProxy: ImageProxy): Bitmap? {
|
||||
val buffer = imageProxy.planes[0].buffer
|
||||
val bytes = ByteArray(buffer.remaining())
|
||||
|
|
|
|||
|
|
@ -40,10 +40,10 @@ private enum class GestureType {
|
|||
}
|
||||
|
||||
// Sensitivity factors for gesture controls (lower = less sensitive)
|
||||
private const val POSITION_SENSITIVITY = 0.3f // Drag to move focus line
|
||||
private const val ROTATION_SENSITIVITY = 0.4f // Two-finger rotation
|
||||
private const val SIZE_SENSITIVITY = 0.5f // Pinch to resize blur zone
|
||||
private const val ZOOM_SENSITIVITY = 0.6f // Pinch to zoom camera
|
||||
private const val POSITION_SENSITIVITY = 0.15f // Drag to move focus line
|
||||
private const val ROTATION_SENSITIVITY = 0.2f // Two-finger rotation
|
||||
private const val SIZE_SENSITIVITY = 0.25f // Pinch to resize blur zone
|
||||
private const val ZOOM_SENSITIVITY = 0.4f // Pinch to zoom camera
|
||||
|
||||
/**
|
||||
* Overlay that shows tilt-shift effect controls and handles gestures.
|
||||
|
|
@ -201,11 +201,13 @@ private fun determineGestureType(
|
|||
|
||||
/**
|
||||
* Draws the tilt-shift visualization overlay.
|
||||
* Uses extended geometry so rotated elements don't clip at screen edges.
|
||||
*/
|
||||
private fun DrawScope.drawTiltShiftOverlay(params: BlurParameters) {
|
||||
val width = size.width
|
||||
val height = size.height
|
||||
|
||||
val centerX = width / 2f
|
||||
val centerY = height * params.position
|
||||
val focusHalfHeight = height * params.size * 0.5f
|
||||
val angleDegrees = params.angle * (180f / PI.toFloat())
|
||||
|
|
@ -215,40 +217,46 @@ private fun DrawScope.drawTiltShiftOverlay(params: BlurParameters) {
|
|||
val blurZoneColor = Color(0x40FFFFFF) // Semi-transparent white
|
||||
val dashEffect = PathEffect.dashPathEffect(floatArrayOf(20f, 10f), 0f)
|
||||
|
||||
rotate(angleDegrees, pivot = Offset(width / 2f, centerY)) {
|
||||
// Draw blur zone indicators (top and bottom)
|
||||
// Calculate diagonal for extended drawing (ensures coverage when rotated)
|
||||
val diagonal = kotlin.math.sqrt(width * width + height * height)
|
||||
val extendedHalf = diagonal // Extend lines/rects well beyond screen
|
||||
|
||||
rotate(angleDegrees, pivot = Offset(centerX, centerY)) {
|
||||
// Draw blur zone indicators (top and bottom) - extended horizontally
|
||||
// Top blur zone: from far above to the top edge of focus area
|
||||
drawRect(
|
||||
color = blurZoneColor,
|
||||
topLeft = Offset(0f, 0f),
|
||||
size = androidx.compose.ui.geometry.Size(width, centerY - focusHalfHeight)
|
||||
topLeft = Offset(centerX - extendedHalf, centerY - focusHalfHeight - extendedHalf),
|
||||
size = androidx.compose.ui.geometry.Size(extendedHalf * 2, extendedHalf)
|
||||
)
|
||||
// Bottom blur zone: from bottom edge of focus area to far below
|
||||
drawRect(
|
||||
color = blurZoneColor,
|
||||
topLeft = Offset(0f, centerY + focusHalfHeight),
|
||||
size = androidx.compose.ui.geometry.Size(width, height - (centerY + focusHalfHeight))
|
||||
topLeft = Offset(centerX - extendedHalf, centerY + focusHalfHeight),
|
||||
size = androidx.compose.ui.geometry.Size(extendedHalf * 2, extendedHalf)
|
||||
)
|
||||
|
||||
// Draw focus zone boundary lines
|
||||
// Draw focus zone boundary lines - extended horizontally
|
||||
drawLine(
|
||||
color = focusLineColor,
|
||||
start = Offset(0f, centerY - focusHalfHeight),
|
||||
end = Offset(width, centerY - focusHalfHeight),
|
||||
start = Offset(centerX - extendedHalf, centerY - focusHalfHeight),
|
||||
end = Offset(centerX + extendedHalf, centerY - focusHalfHeight),
|
||||
strokeWidth = 2.dp.toPx(),
|
||||
pathEffect = dashEffect
|
||||
)
|
||||
drawLine(
|
||||
color = focusLineColor,
|
||||
start = Offset(0f, centerY + focusHalfHeight),
|
||||
end = Offset(width, centerY + focusHalfHeight),
|
||||
start = Offset(centerX - extendedHalf, centerY + focusHalfHeight),
|
||||
end = Offset(centerX + extendedHalf, centerY + focusHalfHeight),
|
||||
strokeWidth = 2.dp.toPx(),
|
||||
pathEffect = dashEffect
|
||||
)
|
||||
|
||||
// Draw center focus line
|
||||
// Draw center focus line - extended horizontally
|
||||
drawLine(
|
||||
color = focusLineColor,
|
||||
start = Offset(0f, centerY),
|
||||
end = Offset(width, centerY),
|
||||
start = Offset(centerX - extendedHalf, centerY),
|
||||
end = Offset(centerX + extendedHalf, centerY),
|
||||
strokeWidth = 3.dp.toPx()
|
||||
)
|
||||
|
||||
|
|
@ -257,7 +265,7 @@ private fun DrawScope.drawTiltShiftOverlay(params: BlurParameters) {
|
|||
drawCircle(
|
||||
color = focusLineColor.copy(alpha = 0.5f),
|
||||
radius = indicatorRadius,
|
||||
center = Offset(width / 2f, centerY),
|
||||
center = Offset(centerX, centerY),
|
||||
style = Stroke(width = 2.dp.toPx())
|
||||
)
|
||||
|
||||
|
|
@ -265,8 +273,8 @@ private fun DrawScope.drawTiltShiftOverlay(params: BlurParameters) {
|
|||
val tickLength = 15.dp.toPx()
|
||||
drawLine(
|
||||
color = focusLineColor,
|
||||
start = Offset(width / 2f, centerY - indicatorRadius + tickLength),
|
||||
end = Offset(width / 2f, centerY - indicatorRadius - 5.dp.toPx()),
|
||||
start = Offset(centerX, centerY - indicatorRadius + tickLength),
|
||||
end = Offset(centerX, centerY - indicatorRadius - 5.dp.toPx()),
|
||||
strokeWidth = 3.dp.toPx()
|
||||
)
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue