Skip to content

Commit

Permalink
improve format selection by #870
Browse files Browse the repository at this point in the history
  • Loading branch information
T8RIN committed Mar 10, 2024
1 parent 3cd1b28 commit db159a9
Show file tree
Hide file tree
Showing 8 changed files with 201 additions and 32 deletions.
Expand Up @@ -175,8 +175,8 @@ sealed class ImageFormat(
else -> Default()
}

val alphaContainedEntries: List<ImageFormat>
get() = listOf(
val alphaContainedEntries by lazy {
listOf(
PngLossless,
PngLossy,
Webp.Lossy,
Expand All @@ -187,16 +187,18 @@ sealed class ImageFormat(
Jxl.Lossless,
Jxl.Lossy
)
}

val highLevelFormats
get() = listOf(
val highLevelFormats by lazy {
listOf(
Avif,
Heic,
Heif
)
}

val entries
get() = listOf(
val entries by lazy {
listOf(
Jpg,
Jpeg,
MozJpeg,
Expand All @@ -211,5 +213,6 @@ sealed class ImageFormat(
Jxl.Lossless,
Jxl.Lossy
)
}
}
}
@@ -0,0 +1,104 @@
/*
* ImageToolbox is an image editor for android
* Copyright (c) 2024 T8RIN (Malik Mukhametzyanov)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
* You should have received a copy of the Apache License
* along with this program. If not, see <http://www.apache.org/licenses/LICENSE-2.0>.
*/

package ru.tech.imageresizershrinker.core.domain.model

sealed class ImageFormatGroup(
val title: String,
val formats: List<ImageFormat>
) {
data object Jpg : ImageFormatGroup(
title = "JPG",
formats = listOf(
ImageFormat.Jpg,
ImageFormat.Jpeg,
ImageFormat.MozJpeg
)
)

data object Png : ImageFormatGroup(
title = "PNG",
formats = listOf(
ImageFormat.PngLossless,
ImageFormat.PngLossy
)
)

data object Bmp : ImageFormatGroup(
title = "BMP",
formats = listOf(
ImageFormat.Bmp
)
)

data object Webp : ImageFormatGroup(
title = "WEBP",
formats = listOf(
ImageFormat.Webp.Lossless,
ImageFormat.Webp.Lossy
)
)

data object Avif : ImageFormatGroup(
title = "AVIF",
formats = listOf(
ImageFormat.Avif
)
)

data object Heic : ImageFormatGroup(
title = "HEIC",
formats = listOf(
ImageFormat.Heic,
ImageFormat.Heif
)
)

data object Jxl : ImageFormatGroup(
title = "JXL",
formats = listOf(
ImageFormat.Jxl.Lossless,
ImageFormat.Jxl.Lossy
)
)

companion object {
val entries by lazy {
listOf(Jpg, Png, Webp, Avif, Heic, Jxl, Bmp)
}

val alphaContainedEntries
get() = listOf(
Png,
Webp,
Avif,
Heic,
Jxl
)

val highLevelFormats by lazy {
listOf(
Avif,
Heic
)
}

fun fromFormat(
imageFormat: ImageFormat
): ImageFormatGroup = entries.first { imageFormat in it.formats }
}
}
1 change: 1 addition & 0 deletions core/resources/src/main/res/values/strings.xml
Expand Up @@ -858,4 +858,5 @@
<string name="generate_previews_sub">Enables preview generation, this may help to avoid crashes on some devices, this also disables some editing functionality within single edit option</string>
<string name="lossy_compression">Lossy Compression</string>
<string name="lossy_compression_sub">Uses lossy compression to reduce file size instead of lossless</string>
<string name="compression_type">Compression Type</string>
</resources>
Expand Up @@ -19,10 +19,6 @@ package ru.tech.imageresizershrinker.core.ui.widget.controls

import android.os.Build
import androidx.compose.animation.AnimatedContent
import androidx.compose.animation.SizeTransform
import androidx.compose.animation.fadeIn
import androidx.compose.animation.fadeOut
import androidx.compose.animation.togetherWith
import androidx.compose.foundation.gestures.detectTapGestures
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
Expand All @@ -42,6 +38,8 @@ import androidx.compose.material3.Surface
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.remember
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.ui.Alignment
Expand All @@ -56,6 +54,7 @@ import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.unit.dp
import kotlinx.coroutines.launch
import ru.tech.imageresizershrinker.core.domain.model.ImageFormat
import ru.tech.imageresizershrinker.core.domain.model.ImageFormatGroup
import ru.tech.imageresizershrinker.core.resources.R
import ru.tech.imageresizershrinker.core.settings.presentation.LocalSettingsState
import ru.tech.imageresizershrinker.core.ui.widget.buttons.EnhancedChip
Expand All @@ -67,7 +66,7 @@ import ru.tech.imageresizershrinker.core.ui.widget.other.LocalToastHostState
fun ImageFormatSelector(
modifier: Modifier = Modifier,
backgroundColor: Color = Color.Unspecified,
entries: List<ImageFormat> = ImageFormat.entries,
entries: List<ImageFormatGroup> = ImageFormatGroup.entries,
forceEnabled: Boolean = false,
value: ImageFormat,
onValueChange: (ImageFormat) -> Unit
Expand All @@ -87,7 +86,7 @@ fun ImageFormatSelector(
}

LaunchedEffect(value, entries) {
if (value !in entries) onValueChange(ImageFormat.PngLossless)
if (value !in entries.flatMap { it.formats }) onValueChange(ImageFormat.PngLossless)
}

Box {
Expand All @@ -110,12 +109,7 @@ fun ImageFormatSelector(
)
Spacer(modifier = Modifier.height(8.dp))

AnimatedContent(
targetState = entries.filtered(),
transitionSpec = {
fadeIn().togetherWith(fadeOut()).using(SizeTransform(false))
}
) { items ->
AnimatedContent(entries.filtered()) { items ->
FlowRow(
verticalArrangement = Arrangement.spacedBy(
8.dp,
Expand All @@ -137,10 +131,10 @@ fun ImageFormatSelector(
EnhancedChip(
onClick = {
if (enabled) {
onValueChange(it)
onValueChange(it.formats[0])
} else cannotChangeFormat()
},
selected = it == value,
selected = value in it.formats,
label = {
Text(text = it.title)
},
Expand All @@ -150,6 +144,67 @@ fun ImageFormatSelector(
}
}
}

val formats by remember(value) {
derivedStateOf {
ImageFormatGroup.fromFormat(value).formats
}
}
AnimatedContent(formats.filteredFormats()) { items ->
if (items.size > 1) {
Column(
modifier = Modifier
.padding(start = 8.dp, end = 8.dp, bottom = 8.dp, top = 4.dp)
.container(
color = MaterialTheme.colorScheme.surface,
resultPadding = 0.dp
)
.fillMaxWidth()
.padding(horizontal = 16.dp, vertical = 12.dp),
horizontalAlignment = Alignment.CenterHorizontally
) {
Text(
text = stringResource(R.string.compression_type),
textAlign = TextAlign.Center,
fontWeight = FontWeight.Medium
)
Spacer(modifier = Modifier.height(16.dp))
FlowRow(
verticalArrangement = Arrangement.spacedBy(
8.dp,
Alignment.CenterVertically
),
horizontalArrangement = Arrangement.spacedBy(
8.dp,
Alignment.CenterHorizontally
),
modifier = Modifier.container(
color = MaterialTheme.colorScheme.surfaceContainerLowest,
resultPadding = 8.dp
)
) {
items.forEach {
EnhancedChip(
onClick = {
if (enabled) {
onValueChange(it)
} else cannotChangeFormat()
},
selected = value == it,
label = {
Text(text = it.title)
},
selectedColor = MaterialTheme.colorScheme.tertiary,
contentPadding = PaddingValues(
horizontal = 16.dp,
vertical = 6.dp
)
)
}
}
}
}
}
}
if (!enabled) {
Surface(
Expand All @@ -167,7 +222,15 @@ fun ImageFormatSelector(
}

@Composable
private fun List<ImageFormat>.filtered(): List<ImageFormat> = remember(this) {
private fun List<ImageFormatGroup>.filtered(): List<ImageFormatGroup> = remember(this) {
if (Build.VERSION.SDK_INT <= 24) toMutableList().apply {
removeAll(ImageFormatGroup.highLevelFormats)
}
else this
}

@Composable
private fun List<ImageFormat>.filteredFormats(): List<ImageFormat> = remember(this) {
if (Build.VERSION.SDK_INT <= 24) toMutableList().apply {
removeAll(ImageFormat.highLevelFormats)
}
Expand Down
Expand Up @@ -92,6 +92,7 @@ import dev.olshevski.navigation.reimagined.hilt.hiltViewModel
import kotlinx.coroutines.launch
import ru.tech.imageresizershrinker.core.domain.image.ImageFrames
import ru.tech.imageresizershrinker.core.domain.model.ImageFormat
import ru.tech.imageresizershrinker.core.domain.model.ImageFormatGroup
import ru.tech.imageresizershrinker.core.domain.model.ImageInfo
import ru.tech.imageresizershrinker.core.domain.model.IntegerSize
import ru.tech.imageresizershrinker.core.domain.model.Quality
Expand Down Expand Up @@ -432,7 +433,7 @@ fun ApngToolsScreen(
ImageFormatSelector(
value = viewModel.imageFormat,
onValueChange = viewModel::setImageFormat,
entries = ImageFormat.alphaContainedEntries
entries = ImageFormatGroup.alphaContainedEntries
)
Spacer(modifier = Modifier.height(8.dp))
QualityWidget(
Expand Down
Expand Up @@ -38,7 +38,6 @@ 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.saveable.rememberSaveable
import androidx.compose.runtime.setValue
Expand All @@ -51,7 +50,7 @@ import androidx.compose.ui.unit.dp
import com.t8rin.dynamic.theme.LocalDynamicThemeState
import dev.olshevski.navigation.reimagined.hilt.hiltViewModel
import kotlinx.coroutines.launch
import ru.tech.imageresizershrinker.core.domain.model.ImageFormat
import ru.tech.imageresizershrinker.core.domain.model.ImageFormatGroup
import ru.tech.imageresizershrinker.core.domain.model.ImageInfo
import ru.tech.imageresizershrinker.core.domain.model.Preset
import ru.tech.imageresizershrinker.core.resources.R
Expand Down Expand Up @@ -290,9 +289,7 @@ fun BytesResizeScreen(
ImageFormatSelector(
value = viewModel.imageFormat,
onValueChange = viewModel::setImageFormat,
entries = remember {
ImageFormat.entries - ImageFormat.PngLossy
}
entries = ImageFormatGroup.entries
)
Spacer(Modifier.height(8.dp))
ScaleModeSelector(
Expand Down
Expand Up @@ -91,7 +91,7 @@ import dev.olshevski.navigation.reimagined.hilt.hiltViewModel
import kotlinx.coroutines.Job
import kotlinx.coroutines.delay
import kotlinx.coroutines.launch
import ru.tech.imageresizershrinker.core.domain.model.ImageFormat
import ru.tech.imageresizershrinker.core.domain.model.ImageFormatGroup
import ru.tech.imageresizershrinker.core.resources.R
import ru.tech.imageresizershrinker.core.settings.presentation.LocalSettingsState
import ru.tech.imageresizershrinker.core.ui.icons.material.CropSmall
Expand Down Expand Up @@ -243,8 +243,8 @@ fun CropScreen(
.padding(horizontal = 16.dp)
.navigationBarsPadding(),
entries = if (viewModel.cropProperties.cropOutlineProperty.outlineType == OutlineType.Rect) {
ImageFormat.entries
} else ImageFormat.alphaContainedEntries,
ImageFormatGroup.entries
} else ImageFormatGroup.alphaContainedEntries,
value = viewModel.imageFormat,
onValueChange = {
viewModel.updateMimeType(it)
Expand Down
Expand Up @@ -101,7 +101,7 @@ import com.t8rin.dynamic.theme.LocalDynamicThemeState
import com.t8rin.dynamic.theme.observeAsState
import dev.olshevski.navigation.reimagined.hilt.hiltViewModel
import kotlinx.coroutines.launch
import ru.tech.imageresizershrinker.core.domain.model.ImageFormat
import ru.tech.imageresizershrinker.core.domain.model.ImageFormatGroup
import ru.tech.imageresizershrinker.core.resources.R
import ru.tech.imageresizershrinker.core.settings.presentation.LocalSettingsState
import ru.tech.imageresizershrinker.core.ui.theme.outlineVariant
Expand Down Expand Up @@ -520,7 +520,7 @@ fun EraseBackgroundScreen(
modifier = Modifier
.padding(start = 16.dp, end = 16.dp, top = 8.dp, bottom = 8.dp)
.navigationBarsPadding(),
entries = ImageFormat.alphaContainedEntries,
entries = ImageFormatGroup.alphaContainedEntries,
value = viewModel.imageFormat,
onValueChange = {
viewModel.setMime(it)
Expand Down

0 comments on commit db159a9

Please sign in to comment.