Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -64,5 +64,4 @@ class CropController(
is CropStateChangeActions.CanvasSizeChanged -> stateManager.updateCanvasSize(action.size)
}
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ object CropDefaults {
gridLinesVisibility: GridLinesVisibility = GridLinesVisibility.ON_TOUCH,
gridLinesType: GridLinesType = GridLinesType.GRID,
handleRadius: Dp = 8.dp,
touchPadding: Dp = 10.dp
touchPadding: Dp = 20.dp
) = CropOptions(
cropShape = cropShape,
contentScale = contentScale,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -203,9 +203,7 @@ fun ImageCropper(
style = Fill
)
}

}

}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,6 @@ import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.flow.update
import kotlinx.coroutines.launch
import kotlin.math.abs

internal class CropStateManager(
bitmap: Bitmap,
Expand All @@ -32,13 +31,13 @@ internal class CropStateManager(
private val handleRadius: Dp,
private val touchPadding: Dp
) {

private val _state = MutableStateFlow(CropState(bitmap))
val state = _state.asStateFlow()
private val coroutineScope = CoroutineScope(Dispatchers.Main)
private var dragMode: DragMode = DragMode.None
private val density get() = Resources.getSystem().displayMetrics.density
private val handleRadiusPx: Float get() = handleRadius.value * density
private var dragOffset: Offset = Offset.Zero

init {
reset(bitmap)
Expand Down Expand Up @@ -77,15 +76,21 @@ internal class CropStateManager(
}

fun onDragStart(offset: Offset) {

val activeHandle = findActiveHandle(offset)
val currentRect = state.value.cropRect

dragMode = when {
activeHandle != null -> DragMode.Handle(activeHandle)
offset.isInsideRect(state.value.cropRect) -> DragMode.Move
offset.isInsideRect(currentRect) -> DragMode.Move
else -> DragMode.None
}

dragOffset = when (val mode = dragMode) {
DragMode.None -> Offset.Zero
DragMode.Move -> Offset(currentRect.left, currentRect.top)
is DragMode.Handle -> GestureUtils.getHandleOffset(mode.handle, currentRect)
}

_state.update { cropState ->
cropState.copy(
isDragging = dragMode != DragMode.None,
Expand Down Expand Up @@ -113,10 +118,8 @@ internal class CropStateManager(

fun onDrag(dragAmount: Offset) {
when (val dragMode = dragMode) {
is DragMode.Handle -> dragHandles(dragMode.handle, dragAmount)

is DragMode.Handle -> moveDragHandle(dragMode.handle, dragAmount)
DragMode.Move -> moveCropRect(dragAmount)

DragMode.None -> {}
}
}
Expand All @@ -141,15 +144,19 @@ internal class CropStateManager(
}

private fun moveCropRect(dragAmount: Offset) {

val currentRect = state.value.cropRect
val imageRect = state.value.imageRect

val newLeft = (currentRect.left + dragAmount.x).coerceInOrderAgnostic(
val correctedDragAmount = GestureUtils.getCorrectDragAmount(
dragOffset = dragOffset,
dragAmount = dragAmount
)

val newLeft = correctedDragAmount.x.coerceInOrderAgnostic(
imageRect.left,
imageRect.right - currentRect.width
)
val newTop = (currentRect.top + dragAmount.y).coerceInOrderAgnostic(
val newTop = correctedDragAmount.y.coerceInOrderAgnostic(
imageRect.top,
imageRect.bottom - currentRect.height
)
Expand All @@ -162,29 +169,30 @@ internal class CropStateManager(
)

_state.update {
dragOffset = correctedDragAmount
it.copy(
cropRect = newRect,
handles = GestureUtils.getNewHandleMeasures(newRect, handleRadiusPx)
)
}

}

private fun dragHandles(activeHandle: DragHandle, dragAmount: Offset) {
val adjustedDragAmount = if (cropShape is CropShape.FreeForm) {
dragAmount
} else {
getDragAmountForShape(dragAmount, activeHandle)
}
private fun moveDragHandle(activeHandle: DragHandle, dragAmount: Offset) {
val correctedDragAmount = GestureUtils.getCorrectDragAmount(
dragOffset = dragOffset,
dragAmount = dragAmount
)

GestureUtils.getNewRectMeasures(
GestureUtils.calculateNewCropRect(
activeHandle = activeHandle,
dragAmount = adjustedDragAmount,
handleOffset = correctedDragAmount,
imageRect = state.value.imageRect,
cropRect = state.value.cropRect,
minCropSize = MIN_CROP_SIZE
)?.let { newRect ->
minCropSize = MIN_CROP_SIZE,
aspectRatio = state.value.aspectRatio
).let { newRect ->
_state.update {
dragOffset = correctedDragAmount
it.copy(
cropRect = newRect,
handles = GestureUtils.getNewHandleMeasures(
Expand All @@ -196,74 +204,9 @@ internal class CropStateManager(
}
}

private fun getDragAmountForShape(dragAmount: Offset, handle: DragHandle): Offset {

val aspectRatio = state.value.aspectRatio
val dx = dragAmount.x
val dy = dragAmount.y
val xConstraint = abs(dragAmount.x)
val xConstraintDeltaY = xConstraint / aspectRatio
val yConstraint = abs(dragAmount.y)
val yConstraintDeltaX = yConstraint * aspectRatio

return when (handle) {
DragHandle.TopLeft -> {
val sign = if (dx < 0 && dy < 0) -1 else 1 // prioritize cropping in
val xConstraintCropIn = minOf(xConstraint, xConstraintDeltaY)
val yConstraintCropIn = minOf(yConstraint, yConstraintDeltaX)
return if (xConstraintCropIn <= yConstraintCropIn) {
Offset(sign * xConstraint, sign * xConstraintDeltaY)
} else {
Offset(sign * yConstraintDeltaX, sign * yConstraint)
}
}

DragHandle.TopRight -> {
val sign = if (dx > 0 && dy < 0) -1 else 1 // prioritize cropping in
val xConstraintCropIn = minOf(xConstraint, xConstraintDeltaY)
val yConstraintCropIn = minOf(yConstraint, yConstraintDeltaX)
return if (xConstraintCropIn <= yConstraintCropIn) {
Offset(-sign * xConstraint, sign * xConstraintDeltaY)
} else {
Offset(-sign * yConstraintDeltaX, sign * yConstraint)
}

}

DragHandle.BottomLeft -> {
val sign = if (dx < 0 && dy > 0) -1 else 1 // prioritize cropping in
val xConstraintCropIn = minOf(xConstraint, xConstraintDeltaY)
val yConstraintCropIn = minOf(yConstraint, yConstraintDeltaX)
return if (xConstraintCropIn <= yConstraintCropIn) {
Offset(sign * xConstraint, -sign * xConstraintDeltaY)
} else {
Offset(sign * yConstraintDeltaX, -sign * yConstraint)
}
}

DragHandle.BottomRight -> {
val sign = if (dx > 0 && dy > 0) -1 else 1 // prioritize cropping in
val xConstraintCropIn = minOf(xConstraint, xConstraintDeltaY)
val yConstraintCropIn = minOf(yConstraint, yConstraintDeltaX)
return if (xConstraintCropIn <= yConstraintCropIn) {
Offset(-sign * xConstraint, -sign * xConstraintDeltaY)
} else {
Offset(-sign * yConstraintDeltaX, -sign * yConstraint)
}
}

else -> Offset.Zero
}

}

private fun findActiveHandle(offset: Offset): DragHandle? {
// TODO: Allow cropping with all handles in locked aspect ratios
val handles = if (cropShape is CropShape.FreeForm) {
state.value.handles.getAllNamedHandles()
} else {
state.value.handles.getCornerNamedHandles()
}
val handles = if (cropShape is CropShape.FreeForm) state.value.handles.getAllNamedHandles()
else state.value.handles.getCornerNamedHandles()

handles.forEach { (handle, handleType) ->
val padding = touchPadding.value * density
Expand All @@ -277,7 +220,6 @@ internal class CropStateManager(
}

return null

}

private fun reset(bitmap: Bitmap) {
Expand All @@ -301,18 +243,25 @@ internal class CropStateManager(
val imageWidth = bitmap.width.toFloat()
val imageHeight = bitmap.height.toFloat()

// Add content padding equal to touchPadding so handles at edges
// always have their full touch area within the canvas bounds
val contentPadding = touchPadding.value * density
val availableWidth = canvasSize.width - contentPadding * 2
val availableHeight = canvasSize.height - contentPadding * 2

val scaledSize = MathUtils.calculateScaledSize(
srcWidth = imageWidth,
srcHeight = imageHeight,
dstWidth = canvasSize.width,
dstHeight = canvasSize.height,
dstWidth = availableWidth,
dstHeight = availableHeight,
contentScale = contentScale
)

val newBitmap = bitmap.scale(scaledSize.width.toInt(), scaledSize.height.toInt())

val offsetX = (canvasSize.width - scaledSize.width) / 2f
val offsetY = (canvasSize.height - scaledSize.height) / 2f
// Center within available space, then add padding offset
val offsetX = contentPadding + (availableWidth - scaledSize.width) / 2f
val offsetY = contentPadding + (availableHeight - scaledSize.height) / 2f

val aspectRatio = when (cropShape) {
is CropShape.FreeForm -> null
Expand Down
Original file line number Diff line number Diff line change
@@ -1,9 +1,7 @@
package com.tanishranjan.cropkit.internal

internal sealed interface DragMode {

data object None : DragMode
data object Move : DragMode
data class Handle(val handle: DragHandle) : DragMode

}
Loading