diff --git a/core/resources/src/main/res/values/strings.xml b/core/resources/src/main/res/values/strings.xml
index 1d78af7f4..314b4d154 100644
--- a/core/resources/src/main/res/values/strings.xml
+++ b/core/resources/src/main/res/values/strings.xml
@@ -1742,4 +1742,5 @@
Lens Correction
Target lens profile file in JSON format
Download ready lens profiles
+ Part percents
diff --git a/feature/image-splitting/src/main/java/com/t8rin/imagetoolbox/image_splitting/data/AndroidImageSplitter.kt b/feature/image-splitting/src/main/java/com/t8rin/imagetoolbox/image_splitting/data/AndroidImageSplitter.kt
index dedd0bb25..08ab3ee0c 100644
--- a/feature/image-splitting/src/main/java/com/t8rin/imagetoolbox/image_splitting/data/AndroidImageSplitter.kt
+++ b/feature/image-splitting/src/main/java/com/t8rin/imagetoolbox/image_splitting/data/AndroidImageSplitter.kt
@@ -39,8 +39,7 @@ internal class AndroidImageSplitter @Inject constructor(
override suspend fun split(
imageUri: String,
- params: SplitParams,
- onProgress: (Int) -> Unit
+ params: SplitParams
): List = withContext(defaultDispatcher) {
if (params.columnsCount <= 1 && params.rowsCount <= 1) {
return@withContext listOf(imageUri)
@@ -56,14 +55,16 @@ internal class AndroidImageSplitter @Inject constructor(
image = image,
count = params.columnsCount,
imageFormat = params.imageFormat,
- quality = params.quality
+ quality = params.quality,
+ columnPercentages = params.columnPercentages,
)
} else if (params.columnsCount <= 1) {
splitForRows(
image = image,
count = params.rowsCount,
imageFormat = params.imageFormat,
- quality = params.quality
+ quality = params.quality,
+ rowPercentages = params.rowPercentages
)
} else {
splitBoth(
@@ -71,7 +72,9 @@ internal class AndroidImageSplitter @Inject constructor(
rowsCount = params.rowsCount,
columnsCount = params.columnsCount,
imageFormat = params.imageFormat,
- quality = params.quality
+ quality = params.quality,
+ rowPercentages = params.rowPercentages,
+ columnPercentages = params.columnPercentages
)
}
}
@@ -81,28 +84,29 @@ internal class AndroidImageSplitter @Inject constructor(
rowsCount: Int,
columnsCount: Int,
imageFormat: ImageFormat,
- quality: Quality
+ quality: Quality,
+ rowPercentages: List = emptyList(),
+ columnPercentages: List = emptyList()
): List = withContext(defaultDispatcher) {
- val cellHeight = image.height / rowsCount.toFloat()
+ val rowHeights = calculatePartSizes(image.height, rowPercentages, rowsCount)
val uris = mutableListOf>>()
+ var currentY = 0
for (row in 0 until rowsCount) {
- val y = (row * cellHeight).toInt()
- val height = if (y + cellHeight.toInt() > image.height) {
- image.height - y
- } else cellHeight.toInt()
-
- val rowBitmap = Bitmap.createBitmap(image, 0, y, image.width, height)
+ val height = rowHeights[row]
+ val rowBitmap = Bitmap.createBitmap(image, 0, currentY, image.width, height)
val rowUris = async {
splitForColumns(
image = rowBitmap,
count = columnsCount,
imageFormat = imageFormat,
- quality = quality
+ quality = quality,
+ columnPercentages = columnPercentages
)
}
uris.add(rowUris)
+ currentY += height
}
uris.flatMap { it.await() }
@@ -112,19 +116,16 @@ internal class AndroidImageSplitter @Inject constructor(
image: Bitmap,
count: Int,
imageFormat: ImageFormat,
- quality: Quality
+ quality: Quality,
+ rowPercentages: List = emptyList()
): List = withContext(defaultDispatcher) {
- val cellHeight = image.height / count.toFloat()
-
+ val rowHeights = calculatePartSizes(image.height, rowPercentages, count)
val uris = mutableListOf()
+ var currentY = 0
for (i in 0 until count) {
- val y = (i * cellHeight).toInt()
- val height = if (y + cellHeight.toInt() > image.height) {
- image.height - y
- } else cellHeight.toInt()
-
- val cell = Bitmap.createBitmap(image, 0, y, image.width, height)
+ val height = rowHeights[i]
+ val cell = Bitmap.createBitmap(image, 0, currentY, image.width, height)
uris.add(
shareProvider.cacheImage(
@@ -137,6 +138,7 @@ internal class AndroidImageSplitter @Inject constructor(
)
)
)
+ currentY += height
}
uris.filterNotNull()
@@ -146,19 +148,16 @@ internal class AndroidImageSplitter @Inject constructor(
image: Bitmap,
count: Int,
imageFormat: ImageFormat,
- quality: Quality
+ quality: Quality,
+ columnPercentages: List = emptyList()
): List = withContext(defaultDispatcher) {
- val cellWidth = image.width / count.toFloat()
-
+ val columnWidths = calculatePartSizes(image.width, columnPercentages, count)
val uris = mutableListOf()
+ var currentX = 0
for (i in 0 until count) {
- val x = (i * cellWidth).toInt()
- val width = if (x + cellWidth.toInt() > image.width) {
- image.width - x
- } else cellWidth.toInt()
-
- val cell = Bitmap.createBitmap(image, x, 0, width, image.height)
+ val width = columnWidths[i]
+ val cell = Bitmap.createBitmap(image, currentX, 0, width, image.height)
uris.add(
shareProvider.cacheImage(
@@ -171,8 +170,51 @@ internal class AndroidImageSplitter @Inject constructor(
)
)
)
+ currentX += width
}
uris.filterNotNull()
}
+ private fun calculatePartSizes(
+ totalSize: Int,
+ percentages: List,
+ count: Int
+ ): List {
+ if (percentages.isEmpty()) {
+ val partSize = totalSize / count
+ return List(count) { index ->
+ if (index == count - 1) {
+ totalSize - (partSize * (count - 1))
+ } else {
+ partSize
+ }
+ }
+ }
+
+ val normalizedPercentages = if (percentages.size < count) {
+ val remainingPercentage = 100f - percentages.sum()
+ val remainingParts = count - percentages.size
+ val equalPercentage = remainingPercentage / remainingParts
+ percentages + List(remainingParts) { equalPercentage }
+ } else if (percentages.size > count) {
+ percentages.take(count)
+ } else {
+ percentages
+ }
+
+ val totalPercentage = normalizedPercentages.sum()
+ val normalized = normalizedPercentages.map { it / totalPercentage }
+
+ return normalized.map { percentage ->
+ (totalSize * percentage).toInt()
+ }.let { sizes ->
+ val calculatedTotal = sizes.sum()
+ if (calculatedTotal != totalSize) {
+ sizes.dropLast(1) + (totalSize - sizes.dropLast(1).sum())
+ } else {
+ sizes
+ }
+ }
+ }
+
}
\ No newline at end of file
diff --git a/feature/image-splitting/src/main/java/com/t8rin/imagetoolbox/image_splitting/domain/ImageSplitter.kt b/feature/image-splitting/src/main/java/com/t8rin/imagetoolbox/image_splitting/domain/ImageSplitter.kt
index aa0c17067..25bb1aa88 100644
--- a/feature/image-splitting/src/main/java/com/t8rin/imagetoolbox/image_splitting/domain/ImageSplitter.kt
+++ b/feature/image-splitting/src/main/java/com/t8rin/imagetoolbox/image_splitting/domain/ImageSplitter.kt
@@ -21,8 +21,7 @@ interface ImageSplitter {
suspend fun split(
imageUri: String,
- params: SplitParams,
- onProgress: (Int) -> Unit
+ params: SplitParams
): List
}
\ No newline at end of file
diff --git a/feature/image-splitting/src/main/java/com/t8rin/imagetoolbox/image_splitting/domain/SplitParams.kt b/feature/image-splitting/src/main/java/com/t8rin/imagetoolbox/image_splitting/domain/SplitParams.kt
index b161de04d..8b07f35c1 100644
--- a/feature/image-splitting/src/main/java/com/t8rin/imagetoolbox/image_splitting/domain/SplitParams.kt
+++ b/feature/image-splitting/src/main/java/com/t8rin/imagetoolbox/image_splitting/domain/SplitParams.kt
@@ -23,14 +23,18 @@ import com.t8rin.imagetoolbox.core.domain.image.model.Quality
data class SplitParams(
val rowsCount: Int,
val columnsCount: Int,
+ val rowPercentages: List,
+ val columnPercentages: List,
val imageFormat: ImageFormat,
- val quality: Quality
+ val quality: Quality,
) {
companion object {
val Default by lazy {
SplitParams(
rowsCount = 2,
columnsCount = 2,
+ rowPercentages = emptyList(),
+ columnPercentages = emptyList(),
imageFormat = ImageFormat.Default,
quality = Quality.Base()
)
diff --git a/feature/image-splitting/src/main/java/com/t8rin/imagetoolbox/image_splitting/presentation/components/SplitParamsSelector.kt b/feature/image-splitting/src/main/java/com/t8rin/imagetoolbox/image_splitting/presentation/components/SplitParamsSelector.kt
index 64590a6d7..ea723dea1 100644
--- a/feature/image-splitting/src/main/java/com/t8rin/imagetoolbox/image_splitting/presentation/components/SplitParamsSelector.kt
+++ b/feature/image-splitting/src/main/java/com/t8rin/imagetoolbox/image_splitting/presentation/components/SplitParamsSelector.kt
@@ -23,19 +23,25 @@ import androidx.compose.foundation.layout.padding
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.rounded.TableRows
import androidx.compose.material.icons.rounded.ViewColumn
+import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
+import androidx.compose.runtime.LaunchedEffect
+import androidx.compose.runtime.derivedStateOf
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableIntStateOf
+import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.dp
+import com.smarttoolfactory.extendedcolors.util.roundToTwoDigits
import com.t8rin.imagetoolbox.core.resources.R
import com.t8rin.imagetoolbox.core.ui.widget.controls.selection.ImageFormatSelector
import com.t8rin.imagetoolbox.core.ui.widget.controls.selection.QualitySelector
import com.t8rin.imagetoolbox.core.ui.widget.enhanced.EnhancedSliderItem
import com.t8rin.imagetoolbox.core.ui.widget.modifier.ShapeDefaults
+import com.t8rin.imagetoolbox.core.ui.widget.text.RoundedTextField
import com.t8rin.imagetoolbox.image_splitting.domain.SplitParams
import kotlin.math.roundToInt
@@ -71,7 +77,22 @@ internal fun SplitParamsSelector(
rowsCount = it.roundToInt()
},
onValueChange = {},
- shape = ShapeDefaults.top
+ shape = ShapeDefaults.top,
+ additionalContent = if (rowsCount > 1) {
+ {
+ PercentagesField(
+ totalSize = rowsCount,
+ percentageValues = value.rowPercentages,
+ onValueChange = {
+ onValueChange(
+ value.copy(
+ rowPercentages = it
+ )
+ )
+ }
+ )
+ }
+ } else null
)
Spacer(Modifier.height(4.dp))
@@ -102,7 +123,22 @@ internal fun SplitParamsSelector(
columnsCount = it.roundToInt()
},
onValueChange = {},
- shape = ShapeDefaults.bottom
+ shape = ShapeDefaults.bottom,
+ additionalContent = if (columnsCount > 1) {
+ {
+ PercentagesField(
+ totalSize = columnsCount,
+ percentageValues = value.columnPercentages,
+ onValueChange = {
+ onValueChange(
+ value.copy(
+ columnPercentages = it
+ )
+ )
+ }
+ )
+ }
+ } else null
)
if (value.imageFormat.canChangeCompressionValue) {
Spacer(Modifier.height(8.dp))
@@ -125,4 +161,54 @@ internal fun SplitParamsSelector(
)
}
)
+}
+
+@Composable
+private fun PercentagesField(
+ totalSize: Int,
+ percentageValues: List,
+ onValueChange: (List) -> Unit
+) {
+ val default by remember(totalSize) {
+ derivedStateOf {
+ List(totalSize) { 1f / totalSize }.joinToString("/") {
+ it.roundToTwoDigits().toString()
+ }
+ }
+ }
+ var percentages by remember(default) {
+ mutableStateOf(
+ percentageValues.joinToString("/") {
+ it.roundToTwoDigits().toString()
+ }.ifEmpty { default }
+ )
+ }
+
+ LaunchedEffect(percentageValues, totalSize) {
+ if (percentageValues.size > totalSize) {
+ percentages = percentageValues.take(totalSize).joinToString("/") {
+ it.roundToTwoDigits().toString()
+ }
+ }
+ }
+
+ LaunchedEffect(percentages) {
+ onValueChange(
+ percentages.split("/").mapNotNull { it.toFloatOrNull() }
+ )
+ }
+
+ RoundedTextField(
+ value = percentages,
+ onValueChange = {
+ percentages = it
+ },
+ label = {
+ Text(text = stringResource(R.string.part_percents))
+ },
+ hint = {
+ Text(text = default)
+ },
+ modifier = Modifier.padding(8.dp)
+ )
}
\ No newline at end of file
diff --git a/feature/image-splitting/src/main/java/com/t8rin/imagetoolbox/image_splitting/presentation/screenLogic/ImageSplitterComponent.kt b/feature/image-splitting/src/main/java/com/t8rin/imagetoolbox/image_splitting/presentation/screenLogic/ImageSplitterComponent.kt
index db0a66bb4..51a4091d8 100644
--- a/feature/image-splitting/src/main/java/com/t8rin/imagetoolbox/image_splitting/presentation/screenLogic/ImageSplitterComponent.kt
+++ b/feature/image-splitting/src/main/java/com/t8rin/imagetoolbox/image_splitting/presentation/screenLogic/ImageSplitterComponent.kt
@@ -82,8 +82,7 @@ class ImageSplitterComponent @AssistedInject internal constructor(
_uris.update {
imageSplitter.split(
imageUri = uri!!.toString(),
- params = params,
- onProgress = {}
+ params = params
).map { it.toUri() }
}
}
@@ -145,10 +144,12 @@ class ImageSplitterComponent @AssistedInject internal constructor(
fun shareBitmaps(onComplete: () -> Unit) {
savingJob = componentScope.launch {
_isSaving.value = true
+ _done.value = 0
shareProvider.shareUris(
uris = uris.map { it.toString() }
)
onComplete()
+ _isSaving.value = false
}
}