Skip to content

Commit 3a76df2

Browse files
committed
Added an option to crop by a fixed aspect ratio in dynamic crop type
1 parent 3b6997c commit 3a76df2

File tree

3 files changed

+63
-13
lines changed

3 files changed

+63
-13
lines changed

cropper/src/main/java/com/smarttoolfactory/cropper/settings/CropDefaults.kt

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -32,11 +32,12 @@ object CropDefaults {
3232
contentScale: ContentScale = ContentScale.Fit,
3333
cropOutlineProperty: CropOutlineProperty,
3434
aspectRatio: AspectRatio = aspectRatios[2].aspectRatio,
35-
overlayRatio:Float = .9f,
35+
overlayRatio: Float = .9f,
3636
pannable: Boolean = true,
3737
fling: Boolean = false,
3838
zoomable: Boolean = true,
39-
rotatable: Boolean = false
39+
rotatable: Boolean = false,
40+
fixedAspectRatio: Boolean = false,
4041
): CropProperties {
4142
return CropProperties(
4243
cropType = cropType,
@@ -49,7 +50,8 @@ object CropDefaults {
4950
pannable = pannable,
5051
fling = fling,
5152
zoomable = zoomable,
52-
rotatable = rotatable
53+
rotatable = rotatable,
54+
fixedAspectRatio = fixedAspectRatio,
5355
)
5456
}
5557

@@ -94,6 +96,7 @@ data class CropProperties internal constructor(
9496
val rotatable: Boolean,
9597
val zoomable: Boolean,
9698
val maxZoom: Float,
99+
val fixedAspectRatio: Boolean = false,
97100
)
98101

99102
/**

cropper/src/main/java/com/smarttoolfactory/cropper/state/CropState.kt

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@ fun rememberCropState(
3838
val zoomable = cropProperties.zoomable
3939
val pannable = cropProperties.pannable
4040
val rotatable = cropProperties.rotatable
41-
41+
val fixedAspectRatio = cropProperties.fixedAspectRatio
4242

4343
return remember(*keys) {
4444
when (cropType) {
@@ -72,7 +72,8 @@ fun rememberCropState(
7272
zoomable = zoomable,
7373
pannable = pannable,
7474
rotatable = rotatable,
75-
limitPan = true
75+
limitPan = true,
76+
fixedAspectRatio = fixedAspectRatio,
7677
)
7778
}
7879
}

cropper/src/main/java/com/smarttoolfactory/cropper/state/DynamicCropState.kt

Lines changed: 54 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ import kotlinx.coroutines.coroutineScope
2828
* @param pannable when set to true pan is enabled
2929
* @param rotatable when set to true rotation is enabled
3030
* @param limitPan limits pan to bounds of parent Composable. Using this flag prevents creating
31+
* @param fixedAspectRatio when set to true aspect ratio of overlay is fixed
3132
* empty space on sides or edges of parent
3233
*/
3334
class DynamicCropState internal constructor(
@@ -43,7 +44,8 @@ class DynamicCropState internal constructor(
4344
zoomable: Boolean,
4445
pannable: Boolean,
4546
rotatable: Boolean,
46-
limitPan: Boolean
47+
limitPan: Boolean,
48+
private val fixedAspectRatio: Boolean,
4749
) : CropState(
4850
imageSize = imageSize,
4951
containerSize = containerSize,
@@ -86,7 +88,7 @@ class DynamicCropState internal constructor(
8688
override suspend fun updateProperties(cropProperties: CropProperties, forceUpdate: Boolean) {
8789
handleSize = cropProperties.handleSize
8890
minOverlaySize = handleSize * 2
89-
91+
9092
super.updateProperties(cropProperties, forceUpdate)
9193
}
9294

@@ -137,13 +139,23 @@ class DynamicCropState internal constructor(
137139
minDimension = minOverlaySize,
138140
rectTemp = rectTemp,
139141
overlayRect = overlayRect,
140-
change = change
142+
change = change,
143+
aspectRatio = getAspectRatio(),
144+
fixedAspectRatio = fixedAspectRatio,
141145
)
142146

143147
snapOverlayRectTo(newRect)
144148
}
145149
}
146150

151+
private fun getAspectRatio(): Float {
152+
return if (aspectRatio == AspectRatio.Unspecified) {
153+
imageSize.width / imageSize.height.toFloat()
154+
} else {
155+
aspectRatio.value
156+
}
157+
}
158+
147159
override suspend fun onUp(change: PointerInputChange) = coroutineScope {
148160
if (touchRegion != TouchRegion.None) {
149161

@@ -351,7 +363,9 @@ class DynamicCropState internal constructor(
351363
minDimension: Float,
352364
rectTemp: Rect,
353365
overlayRect: Rect,
354-
change: PointerInputChange
366+
change: PointerInputChange,
367+
aspectRatio: Float,
368+
fixedAspectRatio: Boolean,
355369
): Rect {
356370

357371
val position = change.position
@@ -368,7 +382,15 @@ class DynamicCropState internal constructor(
368382
// Set position of top left while moving with top left handle and
369383
// limit position to not intersect other handles
370384
val left = screenPositionX.coerceAtMost(rectTemp.right - minDimension)
371-
val top = screenPositionY.coerceAtMost(rectTemp.bottom - minDimension)
385+
val top = if (fixedAspectRatio) {
386+
// If aspect ratio is fixed we need to calculate top position based on
387+
// left position and aspect ratio
388+
val width = rectTemp.right - left
389+
val height = width / aspectRatio
390+
rectTemp.bottom - height
391+
} else {
392+
screenPositionY.coerceAtMost(rectTemp.bottom - minDimension)
393+
}
372394
Rect(
373395
left = left,
374396
top = top,
@@ -382,7 +404,15 @@ class DynamicCropState internal constructor(
382404
// Set position of top left while moving with bottom left handle and
383405
// limit position to not intersect other handles
384406
val left = screenPositionX.coerceAtMost(rectTemp.right - minDimension)
385-
val bottom = screenPositionY.coerceAtLeast(rectTemp.top + minDimension)
407+
val bottom = if (fixedAspectRatio) {
408+
// If aspect ratio is fixed we need to calculate bottom position based on
409+
// left position and aspect ratio
410+
val width = rectTemp.right - left
411+
val height = width / aspectRatio
412+
rectTemp.top + height
413+
} else {
414+
screenPositionY.coerceAtLeast(rectTemp.top + minDimension)
415+
}
386416
Rect(
387417
left = left,
388418
top = rectTemp.top,
@@ -396,7 +426,15 @@ class DynamicCropState internal constructor(
396426
// Set position of top left while moving with top right handle and
397427
// limit position to not intersect other handles
398428
val right = screenPositionX.coerceAtLeast(rectTemp.left + minDimension)
399-
val top = screenPositionY.coerceAtMost(rectTemp.bottom - minDimension)
429+
val top = if (fixedAspectRatio) {
430+
// If aspect ratio is fixed we need to calculate top position based on
431+
// right position and aspect ratio
432+
val width = right - rectTemp.left
433+
val height = width / aspectRatio
434+
rectTemp.bottom - height
435+
} else {
436+
screenPositionY.coerceAtMost(rectTemp.bottom - minDimension)
437+
}
400438

401439
Rect(
402440
left = rectTemp.left,
@@ -412,7 +450,15 @@ class DynamicCropState internal constructor(
412450
// Set position of top left while moving with bottom right handle and
413451
// limit position to not intersect other handles
414452
val right = screenPositionX.coerceAtLeast(rectTemp.left + minDimension)
415-
val bottom = screenPositionY.coerceAtLeast(rectTemp.top + minDimension)
453+
val bottom = if (fixedAspectRatio) {
454+
// If aspect ratio is fixed we need to calculate bottom position based on
455+
// right position and aspect ratio
456+
val width = right - rectTemp.left
457+
val height = width / aspectRatio
458+
rectTemp.top + height
459+
} else {
460+
screenPositionY.coerceAtLeast(rectTemp.top + minDimension)
461+
}
416462

417463
Rect(
418464
left = rectTemp.left,

0 commit comments

Comments
 (0)