Skip to content

Commit

Permalink
Merge pull request #12 from timhuang1018/feature/sudoku-grid-crop-hint
Browse files Browse the repository at this point in the history
Feature/sudoku grid crop hint window
  • Loading branch information
timhuang1018 authored Oct 10, 2024
2 parents a4a2ed2 + 8f937b1 commit 57b65d0
Show file tree
Hide file tree
Showing 8 changed files with 165 additions and 17 deletions.
4 changes: 2 additions & 2 deletions krop-core/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ plugins {
}
//for publishToMavenLocal, can check ~/.m2 to find build artifacts
group = "io.keeppro"
version = "1.0.1"
version = "1.0.2"

kotlin {
js(IR) {
Expand Down Expand Up @@ -98,7 +98,7 @@ mavenPublishing {
coordinates(
groupId = "io.keeppro",
artifactId = "krop",
version = "1.0.1"
version = "1.0.2"
)

// Configure POM metadata for the published artifact
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@ import java.io.ByteArrayOutputStream
typealias Bitmap = android.graphics.Bitmap

actual fun cropBitmap(bitmap: Bitmap, x: Int, y: Int, width: Int, height: Int): ByteArray {
println("cropBitmap, x: $x, y: $y, width: $width, height: $height")
val cropBitmap = Bitmap.createBitmap(bitmap, x, y, width, height)

val format = android.graphics.Bitmap.CompressFormat.PNG
Expand Down
24 changes: 15 additions & 9 deletions krop-core/src/commonMain/kotlin/io/keeppro/Croppable.kt
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,8 @@ import androidx.compose.ui.geometry.Offset
import androidx.compose.ui.input.pointer.pointerInput
import androidx.compose.ui.layout.ContentScale
import androidx.compose.ui.layout.layout
import io.keeppro.widget.CropHintWindow
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.launch

/**
Expand Down Expand Up @@ -49,23 +51,18 @@ fun Croppable(
val settingTranslationX by transition.animateFloat(label = "") { it.translateX }
val settingTranslationY by transition.animateFloat(label = "") { it.translateY }
val scope = rememberCoroutineScope()
val windowStateFlow = remember { MutableStateFlow(false) }


val boxModifier = if (cropHint != null) {
modifier.border(cropHint.borderWidth, cropHint.borderColor).background(cropHint.backgroundColor)
} else {
modifier
}.clipToBounds()
BoxWithConstraints(modifier = boxModifier) {
}
BoxWithConstraints(modifier = boxModifier.clipToBounds()) {
var childWidth by remember { mutableStateOf(0) }
var childHeight by remember { mutableStateOf(0) }

LaunchedEffect(
childHeight,
childWidth,
) {

}

LaunchedEffect(
childHeight,
childWidth,
Expand Down Expand Up @@ -131,6 +128,7 @@ fun Croppable(

scope.launch {
state.drag(pan - adjustedOffset)
windowStateFlow.value = true
}
}
}
Expand Down Expand Up @@ -167,6 +165,14 @@ fun Croppable(
) {
content.invoke(this)
}

if (cropHint != null){
CropHintWindow(
cropHint.gridLineColor,
state.cropWindow.value,
windowStateFlow,
)
}
}

}
Expand Down
25 changes: 23 additions & 2 deletions krop-core/src/commonMain/kotlin/io/keeppro/CroppableState.kt
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,15 @@ import androidx.compose.animation.core.AnimationVector1D
import androidx.compose.animation.core.exponentialDecay
import androidx.compose.animation.core.spring
import androidx.compose.runtime.Composable
import androidx.compose.runtime.MutableState
import androidx.compose.runtime.Stable
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.saveable.Saver
import androidx.compose.runtime.saveable.listSaver
import androidx.compose.runtime.saveable.rememberSaveable
import androidx.compose.ui.geometry.Offset
import androidx.compose.ui.geometry.Rect
import androidx.compose.ui.geometry.Size
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.input.pointer.util.VelocityTracker
import androidx.compose.ui.layout.ContentScale
Expand Down Expand Up @@ -79,8 +83,11 @@ class CroppableState(
private var childHeight = 0
private var startPoint = Offset(0f, 0f)
private var cropArea = Offset(0f, 0f) //width and height
private var updateChildScale: Float? = null
private var originalImageWidth: Int = 0
/**
* The window of the crop area from user's perspective
*/
val cropWindow: MutableState<Rect> = mutableStateOf(Rect(0f, 0f, 0f, 0f))

init {
require(minScale < maxScale) { "minScale must be < maxScale" }
Expand Down Expand Up @@ -213,6 +220,17 @@ class CroppableState(
min(childWidth * scale, containerWidth.toFloat()),
min(childHeight * scale, containerHeight.toFloat())
)

cropWindow.value = Rect(
offset = Offset(
if(childWidth * scale < containerWidth.toFloat()) (childWidth * scale - containerWidth) / 2 else 0f,
if (childHeight * scale < containerHeight.toFloat()) (childHeight * scale - containerHeight) / 2 else 0f,
),
size = Size(
cropArea.x,
cropArea.y
)
)
}

fun crop(): ByteArray {
Expand Down Expand Up @@ -319,16 +337,19 @@ class CroppableState(
}
}

class CropHint(
data class CropHint(
val backgroundColor: Color,
val borderColor: Color,
val borderWidth: Dp,
val gridLineColor: Color
){

companion object{
val Default = CropHint(
backgroundColor = Color(0xFFBABABA),
borderColor = Color(0xFFBABABA),
borderWidth = 2.dp,
gridLineColor = Color.Black,
)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
package io.keeppro.widget

import androidx.compose.animation.AnimatedVisibility
import androidx.compose.animation.EnterTransition
import androidx.compose.animation.fadeOut
import androidx.compose.foundation.layout.BoxScope
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
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier
import androidx.compose.ui.geometry.Rect
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.layout.layout
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.Job
import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.collectLatest
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext

@Composable
fun BoxScope.CropHintWindow(
gridLineColor: Color,
cropWindow: Rect,
windowStateFlow: MutableStateFlow<Boolean>,
) {
var cropWindowVisible by remember { mutableStateOf(false) }
var delayJob by remember { mutableStateOf<Job?>(null) }
val scope = rememberCoroutineScope()

LaunchedEffect(Unit) {
windowStateFlow.collectLatest {
cropWindowVisible = it
if (it) {
delayJob?.cancel()
delayJob = scope.launch {
delay(1500)
withContext(Dispatchers.Main) {
windowStateFlow.value = false
}
}
} else {
delayJob?.cancel()
delayJob = null
}
}
}


AnimatedVisibility(
visible = cropWindowVisible,
enter = EnterTransition.None,
exit = fadeOut()
) {

SudokuGrid(
gridLineColor = gridLineColor,
modifier = Modifier.layout { measurable, constraints ->
val placeable = measurable.measure(
constraints = constraints.copy(
maxWidth = cropWindow.width.toInt(),
maxHeight = cropWindow.height.toInt(),
minWidth = cropWindow.width.toInt(),
minHeight = cropWindow.height.toInt()
)
)
layout(
width = cropWindow.width.toInt(),
height = cropWindow.height.toInt()
) {
placeable.place(
(-cropWindow.topLeft.x.toInt()),
(-cropWindow.topLeft.y.toInt())
)
}
})
}

}
39 changes: 39 additions & 0 deletions krop-core/src/commonMain/kotlin/io/keeppro/widget/SudokuGrid.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
package io.keeppro.widget

import androidx.compose.foundation.Canvas
import androidx.compose.foundation.layout.BoxScope
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.geometry.Offset
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.unit.dp

@Composable
fun BoxScope.SudokuGrid(gridLineColor: Color, modifier: Modifier = Modifier) {
Canvas(modifier = modifier) {
val gridSize = 3 // For a 3x3 grid
val lineThickness = 2.dp.toPx()
val cellSizeX = size.width / gridSize
val cellSizeY = size.height / gridSize

// Draw the grid lines
for (i in 1 until gridSize) {
val x = i * cellSizeX
val y = i * cellSizeY
if (i < gridSize) {
drawLine(
color = gridLineColor,
start = Offset(x, 0f),
end = Offset(x, size.height),
strokeWidth = lineThickness
)
}
drawLine(
color = gridLineColor,
start = Offset(0f, y),
end = Offset(size.width, y),
strokeWidth = lineThickness
)
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@ import org.jetbrains.skia.ImageInfo

//1. bytearray
actual fun cropBitmap(bitmap: Bitmap, x: Int, y: Int, width: Int, height: Int): ByteArray {
println("cropBitmap, x: $x, y: $y, width: $width, height: $height")
val croppedBitmap = cropIntoBitmap(bitmap, x, y, width, height)

// Convert the cropped bitmap to an Image
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.layout.ContentScale
import androidx.compose.ui.layout.layout
import androidx.compose.ui.unit.dp
Expand Down Expand Up @@ -101,7 +102,7 @@ fun CroppableSample(croppableState: CroppableState, aspectRatio: Float) {
Croppable(
state = croppableState,
contentScale = ContentScale.Crop,
cropHint = CropHint.Default,
cropHint = CropHint.Default.copy(gridLineColor = Color.White.copy(alpha = 0.3f)),
modifier = Modifier
.layout { measurable, constraints ->
val newConstraints = if (aspectRatio >= 1f){
Expand All @@ -123,7 +124,6 @@ fun CroppableSample(croppableState: CroppableState, aspectRatio: Float) {
imageLoader = loader,
contentScale = ContentScale.Inside,
onSuccess = {
io.keeppro.krop.println("image width: ${it.result.image.width}, image height: ${it.result.image.height}")
croppableState.prepareImage(it.result.image)
},
)
Expand Down

0 comments on commit 57b65d0

Please sign in to comment.