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
2 changes: 1 addition & 1 deletion app/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ android {

applicationId "space.taran.arkretouch2"
minSdk 26
targetSdk 32
targetSdk 33
versionCode 1
versionName "1.0"
setProperty("archivesBaseName", "ark-retouch")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -350,6 +350,9 @@ private fun BoxScope.TopMenu(
onPositiveClick = { savePath ->
viewModel.saveImage(savePath)
viewModel.showSavePathDialog = false
},
onCompressFormatChanged = {
viewModel.compressionFormat = it
}
)
if (viewModel.showMoreOptionsPopup)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package space.taran.arkretouch.presentation.edit
import android.content.Context
import android.content.Intent
import android.graphics.Bitmap
import android.graphics.Bitmap.CompressFormat
import android.graphics.Matrix
import android.graphics.drawable.Drawable
import android.net.Uri
Expand Down Expand Up @@ -47,6 +48,8 @@ import space.taran.arkretouch.presentation.edit.resize.ResizeOperation
import timber.log.Timber
import java.io.File
import java.nio.file.Path
import kotlin.io.path.Path
import kotlin.io.path.extension
import kotlin.io.path.outputStream
import kotlin.system.measureTimeMillis

Expand All @@ -72,6 +75,7 @@ class EditViewModel(
var showEyeDropperHint by mutableStateOf(false)
val showConfirmClearDialog = mutableStateOf(false)
var isLoaded by mutableStateOf(false)
var compressionFormat by mutableStateOf(CompressFormat.PNG)
var exitConfirmed = false
private set
val bottomButtonsScrollIsAtStart = mutableStateOf(true)
Expand Down Expand Up @@ -113,6 +117,7 @@ class EditViewModel(
imagePath,
editManager
)
extractCompressionFormat(it.extension)
return
}
imageUri?.let {
Expand All @@ -121,6 +126,9 @@ class EditViewModel(
imageUri,
editManager
)
Uri.parse(it)?.path?.let { path ->
extractCompressionFormat(Path(path).extension)
}
return
}
editManager.scaleToFit()
Expand All @@ -133,7 +141,7 @@ class EditViewModel(

savePath.outputStream().use { out ->
combinedBitmap.asAndroidBitmap()
.compress(Bitmap.CompressFormat.PNG, 100, out)
.compress(compressionFormat, 100, out)
}
imageSaved = true
isSavingImage = false
Expand Down Expand Up @@ -392,6 +400,15 @@ class EditViewModel(
}
}

private fun extractCompressionFormat(extension: String) {
compressionFormat = when (extension) {
ImageExtensions.PNG -> CompressFormat.PNG
ImageExtensions.JPEG, ImageExtensions.JPG -> CompressFormat.JPEG
ImageExtensions.WEBP -> CompressFormat.WEBP
else -> CompressFormat.PNG
}
}

companion object {
private const val KEEP_USED_COLORS = 20
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
package space.taran.arkretouch.presentation.edit

import android.graphics.Bitmap.CompressFormat
import android.os.Build
import androidx.compose.foundation.background
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Arrangement
Expand All @@ -11,6 +13,7 @@ import androidx.compose.foundation.layout.wrapContentHeight
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.wrapContentSize
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material.Button
import androidx.compose.material.Checkbox
Expand Down Expand Up @@ -42,18 +45,27 @@ import space.taran.arkretouch.R
import space.taran.arkretouch.presentation.utils.findNotExistCopyName
import kotlin.io.path.name
import androidx.compose.material.CircularProgressIndicator
import androidx.compose.material.Icon
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.KeyboardArrowDown
import androidx.compose.material.icons.filled.KeyboardArrowUp
import androidx.compose.runtime.key
import androidx.compose.ui.ExperimentalComposeUiApi
import androidx.compose.ui.unit.IntOffset
import androidx.compose.ui.window.Popup
import androidx.compose.ui.window.PopupProperties
import space.taran.arkretouch.presentation.picker.toPx
import java.nio.file.Files
import java.util.Locale
import kotlin.io.path.extension
import kotlin.streams.toList

@OptIn(ExperimentalComposeUiApi::class)
@Composable
fun SavePathDialog(
initialImagePath: Path?,
fragmentManager: FragmentManager,
onDismissClick: () -> Unit,
onPositiveClick: (Path) -> Unit
onPositiveClick: (Path) -> Unit,
onCompressFormatChanged: (CompressFormat) -> Unit
) {
var currentPath by remember { mutableStateOf(initialImagePath?.parent) }
var imagePath by remember { mutableStateOf(initialImagePath) }
Expand All @@ -66,9 +78,49 @@ fun SavePathDialog(
} ?: "image.png"
)
}
var compressionFormat by remember {
var format = initialImagePath?.let {
when (it.extension) {
ImageExtensions.PNG,
ImageExtensions.JPEG,
ImageExtensions.WEBP -> it.extension
ImageExtensions.JPG -> ImageExtensions.JPEG
else -> ImageExtensions.PNG
}
} ?: ImageExtensions.PNG
if (format == ImageExtensions.WEBP) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R)
format = ImageExtensions.Webp.WEBP_LOSSLESS
}
mutableStateOf(format.uppercase(Locale.getDefault()))
}
var showCompressionFormats by remember { mutableStateOf(false) }

val lifecycleOwner = LocalLifecycleOwner.current

fun updateImagePath(imageName: String) {
var extension =
compressionFormat.lowercase(Locale.getDefault())
if (
extension == ImageExtensions.Webp.WEBP_LOSSLESS ||
extension == ImageExtensions.Webp.WEBP_LOSSY
) extension = ImageExtensions.WEBP

name = "$imageName.$extension"

currentPath?.let { path ->
imagePath = path.resolve(name)
showOverwriteCheckbox.value =
Files.list(path).toList()
.contains(imagePath)
if (showOverwriteCheckbox.value) {
name = path.findNotExistCopyName(
imagePath?.fileName!!
).name
}
}
}

LaunchedEffect(overwriteOriginalPath) {
if (overwriteOriginalPath) {
imagePath?.let {
Expand Down Expand Up @@ -123,25 +175,42 @@ fun SavePathDialog(
?: stringResource(R.string.pick_folder)
)
}
OutlinedTextField(
modifier = Modifier.padding(5.dp),
value = name,
onValueChange = {
name = it
currentPath?.let { path ->
imagePath = path.resolve(name)
showOverwriteCheckbox.value = Files.list(path).toList()
.contains(imagePath)
if (showOverwriteCheckbox.value) {
name = path.findNotExistCopyName(
imagePath?.fileName!!
).name
}
}
},
label = { Text(text = stringResource(R.string.name)) },
singleLine = true
)
Row(
Modifier.fillMaxWidth(),
verticalAlignment = Alignment.CenterVertically
) {
OutlinedTextField(
modifier = Modifier
.fillMaxWidth(0.8f)
.padding(5.dp),
value = name.substringBeforeLast(
ImageExtensions.Delimeters.PERIOD
),
onValueChange = {
updateImagePath(it)
},
label = { Text(text = stringResource(R.string.name)) },
singleLine = true
)
Column(
Modifier
.wrapContentHeight()
.fillMaxWidth()
.clickable {
showCompressionFormats = !showCompressionFormats
},
horizontalAlignment = Alignment.CenterHorizontally
) {
Icon(
if (showCompressionFormats)
Icons.Filled.KeyboardArrowDown
else Icons.Filled.KeyboardArrowUp,
null,
Modifier.size(32.dp)
)
Text(compressionFormat, maxLines = 1)
}
}
if (showOverwriteCheckbox.value) {
Row(
modifier = Modifier
Expand Down Expand Up @@ -175,14 +244,29 @@ fun SavePathDialog(
Button(
modifier = Modifier.padding(5.dp),
onClick = {
if (currentPath != null && name != null)
if (currentPath != null)
onPositiveClick(currentPath!!.resolve(name))
}
) {
Text(text = stringResource(R.string.ok))
}
}
}
if (showCompressionFormats) {
CompressionFormats(
{ formatName, format ->
compressionFormat = formatName
updateImagePath(
name.substringBeforeLast(
ImageExtensions.Delimeters.PERIOD
)
)
onCompressFormatChanged(format)
showCompressionFormats = false
},
{ showCompressionFormats = false }
)
}
}
}
}
Expand All @@ -202,9 +286,96 @@ fun SaveProgress() {
}
}

@Composable
fun CompressionFormats(
onFormatClick: (String, CompressFormat) -> Unit,
onDismiss: () -> Unit
) {
Popup(
alignment = Alignment.TopEnd,
offset = IntOffset(
-5.dp.toPx().toInt(),
-10.dp.toPx().toInt()
),
onDismissRequest = onDismiss,
properties = PopupProperties(focusable = true)
) {
Column(
Modifier
.wrapContentSize()
.background(Color.LightGray, RoundedCornerShape(5)),
horizontalAlignment = Alignment.CenterHorizontally
) {
val png = stringResource(R.string.png)
val jpeg = stringResource(R.string.jpeg)
val webpLossless = stringResource(R.string.webp_lossless)
val webpLossy = stringResource(R.string.webp_lossy)
val webp = stringResource(R.string.webp)
Text(
png,
Modifier
.padding(8.dp)
.clickable {
onFormatClick(png, CompressFormat.PNG)
}
)
Text(
jpeg,
Modifier
.padding(8.dp)
.clickable {
onFormatClick(jpeg, CompressFormat.JPEG)
}
)
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
Text(
webpLossless,
Modifier
.padding(8.dp)
.clickable {
onFormatClick(webpLossless, CompressFormat.WEBP_LOSSLESS)
}
)
Text(
webpLossy,
Modifier
.padding(8.dp)
.clickable {
onFormatClick(webpLossy, CompressFormat.WEBP_LOSSY)
}
)
} else {
Text(
webp,
Modifier
.padding(8.dp)
.clickable {
onFormatClick(webp, CompressFormat.WEBP)
}
)
}
}
}
}

fun folderFilePickerConfig(initialPath: Path?) = ArkFilePickerConfig(
mode = ArkFilePickerMode.FOLDER,
initialPath = initialPath,
showRoots = true,
rootsFirstPage = true
)

object ImageExtensions {
const val PNG = "png"
const val JPEG = "jpeg"
const val JPG = "jpg"
const val WEBP = "webp"
object Webp {
const val WEBP_LOSSLESS = "webp_lossless"
const val WEBP_LOSSY = "webp_lossy"
}

object Delimeters {
const val PERIOD = '.'
}
}
5 changes: 5 additions & 0 deletions app/src/main/res/values/strings.xml
Original file line number Diff line number Diff line change
Expand Up @@ -34,4 +34,9 @@
<string name="width_not_accepted">Width cannot be %s</string>
<string name="width_empty">Please enter width</string>
<string name="height_empty">Please enter height</string>
<string name="webp_lossy">WEBP_LOSSY</string>
<string name="webp_lossless">WEBP_LOSSLESS</string>
<string name="jpeg">JPEG</string>
<string name="png">PNG</string>
<string name="webp">WEBP</string>
</resources>