Skip to content

Commit

Permalink
refactor: simplify image crop measures calculation logic
Browse files Browse the repository at this point in the history
  • Loading branch information
Bnyro committed Nov 6, 2024
1 parent 1f80c62 commit ee5c83e
Show file tree
Hide file tree
Showing 2 changed files with 61 additions and 66 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ import androidx.compose.material3.CenterAlignedTopAppBar
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
Expand All @@ -52,6 +53,7 @@ import com.bnyro.translate.R
import com.bnyro.translate.ext.toastFromMainThread
import com.bnyro.translate.ui.dialogs.FullscreenDialog
import com.bnyro.translate.util.ImageHelper
import com.bnyro.translate.util.ImageTransform
import kotlin.math.absoluteValue
import kotlin.math.roundToInt
import kotlinx.coroutines.Dispatchers
Expand All @@ -77,9 +79,7 @@ fun ImageCropDialog(
val scope = rememberCoroutineScope()
val context = LocalContext.current

var canvasSize = remember { Size(0f, 0f) }
var size = remember { Size(0f, 0f) }
var offset = remember { Offset(0f, 0f) }
var imageTransform: ImageTransform? = remember { null }

FullscreenDialog(
onDismissRequest = onDismissRequest,
Expand All @@ -96,39 +96,14 @@ fun ImageCropDialog(
StyledIconButton(
imageVector = Icons.Default.Done,
onClick = {
val (imageCanvasWidth, imageCanvasHeight) = fitImageSizeToDimensions(
bitmap,
canvasSize.width,
canvasSize.height
)
val widthFactor = bitmap.width.toFloat() / imageCanvasWidth
val heightFactor = bitmap.height.toFloat() / imageCanvasHeight
val emptyHorizontalSpace = canvasSize.width - imageCanvasWidth
val emptyVerticalSpace = canvasSize.height - imageCanvasHeight

val cropSize = Size(
size.width * widthFactor,
size.height * heightFactor
)
val cropOffset = Offset(
(offset.x - emptyHorizontalSpace / 2) * widthFactor,
(offset.y - emptyVerticalSpace / 2) * heightFactor
)
if (arrayOf(
cropSize.height,
cropSize.width,
cropOffset.x,
cropOffset.y
).any { it < 0 } || cropOffset.x + cropSize.width > bitmap.width ||
cropOffset.y + cropSize.height > bitmap.height
) {
val currentTransform = imageTransform ?: run {
context.toastFromMainThread(R.string.invalid_selection_area)
return@StyledIconButton
}

scope.launch(Dispatchers.IO) {
val resizedBitmap =
ImageHelper.cropImage(bitmap, cropSize, cropOffset)
ImageHelper.cropImage(bitmap, currentTransform)

withContext(Dispatchers.Main) {
onEditedBitmap(resizedBitmap)
Expand All @@ -142,23 +117,17 @@ fun ImageCropDialog(
}
) {
CropImageView(
image = ImageHelper.setAlpha(bitmap, 100),
onSizeChanged = { size = it },
onOffsetChanged = { offset = it },
onCanvasSizeChanged = { width, height ->
canvasSize = Size(width, height)
}
bitmap = ImageHelper.setAlpha(bitmap, 100),
onImageTransformChanged = { imageTransform = it }
)
}
}

@Composable
fun CropImageView(
modifier: Modifier = Modifier,
image: Bitmap,
onSizeChanged: (size: Size) -> Unit,
onOffsetChanged: (offset: Offset) -> Unit,
onCanvasSizeChanged: (width: Float, height: Float) -> Unit
bitmap: Bitmap,
onImageTransformChanged: (transform: ImageTransform?) -> Unit,
) {
BoxWithConstraints(
modifier = modifier
Expand All @@ -168,27 +137,59 @@ fun CropImageView(
val (maxWidth, maxHeight) = remember {
with(density) {
maxWidth.toPx() to maxHeight.toPx()
}.also {
onCanvasSizeChanged(it.first, it.second)
}
}

var selectableAreaSize by remember {
val size = Size(maxWidth / 3, maxHeight / 3)
onSizeChanged(size)
mutableStateOf(size)
}

var selectableAreaOffset by remember {
val offsetX = (maxWidth - selectableAreaSize.width) / 2
val offsetY = (maxHeight - selectableAreaSize.height) / 2
val offset = Offset(offsetX, offsetY)
onOffsetChanged(offset)
mutableStateOf(offset)
}

val (imageWidthScaled, imageHeightScaled) = remember {
fitImageSizeToDimensions(image, maxWidth, maxHeight)
fitImageSizeToDimensions(bitmap, maxWidth, maxHeight)
}

LaunchedEffect(Unit, selectableAreaSize, selectableAreaOffset) {
val widthFactor = bitmap.width.toFloat() / imageWidthScaled
val heightFactor = bitmap.height.toFloat() / imageHeightScaled
val emptyHorizontalSpace = maxWidth - imageWidthScaled
val emptyVerticalSpace = maxHeight - imageHeightScaled

val cropSize = Size(
selectableAreaSize.width * widthFactor,
selectableAreaSize.height * heightFactor
)
val cropOffset = Offset(
(selectableAreaOffset.x - emptyHorizontalSpace / 2) * widthFactor,
(selectableAreaOffset.y - emptyVerticalSpace / 2) * heightFactor
)
if (arrayOf(
cropSize.height,
cropSize.width,
cropOffset.x,
cropOffset.y
).any { it < 0 } || cropOffset.x + cropSize.width > bitmap.width ||
cropOffset.y + cropSize.height > bitmap.height
) {
onImageTransformChanged(null)
return@LaunchedEffect
}

onImageTransformChanged(
ImageTransform(
cropSize.width.toInt(),
cropSize.height.toInt(),
cropOffset.x.toInt(),
cropOffset.y.toInt()
)
)
}

Canvas(modifier = Modifier
Expand All @@ -205,22 +206,17 @@ fun CropImageView(
if (borders.contains(BorderType.LEFT)) selectableAreaOffset.x + dragAmount.x else selectableAreaOffset.x,
if (borders.contains(BorderType.TOP)) selectableAreaOffset.y + dragAmount.y else selectableAreaOffset.y,
)

onSizeChanged(selectableAreaSize)
onOffsetChanged(selectableAreaOffset)
} else if (isInArea(change, selectableAreaSize, selectableAreaOffset)) {
selectableAreaOffset = Offset(
selectableAreaOffset.x + dragAmount.x,
selectableAreaOffset.y + dragAmount.y
)

onOffsetChanged(selectableAreaOffset)
}
}
}
) {
drawImage(
image.asImageBitmap(),
bitmap.asImageBitmap(),
dstOffset = IntOffset(
((maxWidth - imageWidthScaled) / 2).toInt(),
((maxHeight - imageHeightScaled) / 2).toInt()
Expand Down
29 changes: 14 additions & 15 deletions app/src/main/java/com/bnyro/translate/util/ImageHelper.kt
Original file line number Diff line number Diff line change
Expand Up @@ -23,9 +23,13 @@ import android.graphics.BitmapFactory
import android.graphics.Canvas
import android.graphics.Paint
import android.net.Uri
import androidx.compose.ui.geometry.Offset
import androidx.compose.ui.geometry.Size

data class ImageTransform(
val width: Int,
val height: Int,
val offsetX: Int,
val offsetY: Int
)

object ImageHelper {
fun getImage(context: Context, uri: Uri): Bitmap? {
Expand All @@ -48,17 +52,12 @@ object ImageHelper {

fun cropImage(
targetBitmap: Bitmap,
cropSize: Size,
cropOffset: Offset,
): Bitmap {
val croppedBitmap = Bitmap.createBitmap(
targetBitmap,
cropOffset.x.toInt(),
cropOffset.y.toInt(),
cropSize.width.toInt(),
cropSize.height.toInt()
)

return croppedBitmap
}
transform: ImageTransform
): Bitmap = Bitmap.createBitmap(
targetBitmap,
transform.offsetX,
transform.offsetY,
transform.width,
transform.height
)
}

0 comments on commit ee5c83e

Please sign in to comment.