Skip to content

Commit

Permalink
Merge pull request #15 from YahiaAngelo/14-enhance-images-quality
Browse files Browse the repository at this point in the history
14-enhance-images-quality
  • Loading branch information
YahiaAngelo authored Dec 16, 2024
2 parents 74364ef + b98bf59 commit ededb2e
Show file tree
Hide file tree
Showing 14 changed files with 91 additions and 55 deletions.
6 changes: 5 additions & 1 deletion gradle/libs.versions.toml
Original file line number Diff line number Diff line change
@@ -1,11 +1,13 @@
[versions]
agp = "8.2.2"
coilCompose = "3.0.4"
filekitCore = "0.8.8"
imageLoader = "1.9.0"
kotlin = "2.0.21"
compose = "1.7.5"
compose-material3 = "1.3.1"
androidx-activityCompose = "1.9.3"
kotlinxDatetime = "0.6.1"
logging = "1.4.2"
materialKolor = "1.4.0-rc03"
multiplatformSettings = "1.1.1"
Expand All @@ -29,6 +31,7 @@ android-driver = { module = "app.cash.sqldelight:android-driver", version.ref =
androidx-lifecycle-runtime-compose = { module = "androidx.lifecycle:lifecycle-runtime-compose", version.ref = "lifecycleViewmodelKtx" }
androidx-lifecycle-viewmodel-compose = { module = "androidx.lifecycle:lifecycle-viewmodel-compose", version.ref = "lifecycleViewmodelKtx" }
androidx-lifecycle-viewmodel-ktx = { module = "androidx.lifecycle:lifecycle-viewmodel-ktx", version.ref = "lifecycleViewmodelKtx" }
coil-compose = { module = "io.coil-kt.coil3:coil-compose", version.ref = "coilCompose" }
coroutines-extensions = { module = "app.cash.sqldelight:coroutines-extensions", version.ref = "nativeDriver" }
filekit-compose = { module = "io.github.vinceglb:filekit-compose", version.ref = "filekitCore" }
filekit-core = { module = "io.github.vinceglb:filekit-core", version.ref = "filekitCore" }
Expand All @@ -41,6 +44,7 @@ compose-ui-tooling = { module = "androidx.compose.ui:ui-tooling", version.ref =
compose-ui-tooling-preview = { module = "androidx.compose.ui:ui-tooling-preview", version.ref = "compose" }
compose-foundation = { module = "androidx.compose.foundation:foundation", version.ref = "compose" }
compose-material3 = { module = "androidx.compose.material3:material3", version.ref = "compose-material3" }
kotlinx-datetime = { module = "org.jetbrains.kotlinx:kotlinx-datetime", version.ref = "kotlinxDatetime" }
ktor-client-android = { module = "io.ktor:ktor-client-android", version.ref = "ktorVersion" }
ktor-client-content-negotiation = { module = "io.ktor:ktor-client-content-negotiation", version.ref = "ktorVersion" }
ktor-client-core = { module = "io.ktor:ktor-client-core", version.ref = "ktorVersion" }
Expand Down Expand Up @@ -75,7 +79,7 @@ peekaboo-image-picker = { module = "io.github.onseok:peekaboo-image-picker", ver
okio = {module = "com.squareup.okio:okio", version.ref = "okio"}
androidx-datastore-core = { group = "androidx.datastore", name = "datastore-core", version.ref = "datastoreCore" }
kotlinx-coroutines-core = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-core", version.ref = "kotlinxCoroutinesCore" }
ffmpeg-kit-min = { module = "com.arthenica:ffmpeg-kit-min", version.ref = "ffmpegKitMin" }
ffmpeg-kit-min = { module = "com.arthenica:ffmpeg-kit-min-gpl", version.ref = "ffmpegKitMin" }

[plugins]
androidApplication = { id = "com.android.application", version.ref = "agp" }
Expand Down
4 changes: 2 additions & 2 deletions shared/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -86,17 +86,17 @@ kotlin {
implementation(libs.koin.test)
implementation(libs.koin.compose)
implementation(libs.stately.common)
implementation(libs.peekaboo.ui)
implementation(libs.peekaboo.image.picker)
implementation(libs.okio)
implementation(libs.coroutines.extensions)
implementation(libs.multiplatform.settings)
implementation(libs.multiplatform.settings.no.arg)
implementation(libs.multiplatform.settings.serialization)
implementation(libs.multiplatform.settings.coroutines)
api(libs.image.loader)
implementation(libs.coil.compose)
implementation(libs.filekit.core)
implementation(libs.filekit.compose)
implementation(libs.kotlinx.datetime)
//api(libs.image.loader.extension.blur)
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ actual suspend fun apply3dLut(inputFile: String, lutFile: String, outputFile: St

deleteFile(outputFileDir)
withContext(Dispatchers.IO) {
FFmpegKit.executeAsync("-i $inputFileDir -vf lut3d=$lutFileDir -c:a copy $outputFileDir") {
FFmpegKit.executeAsync("-i $inputFileDir -vf lut3d=$lutFileDir -q:v 1 $outputFileDir") {
onComplete()
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
import okio.FileSystem
import okio.Path.Companion.toPath
import org.jetbrains.compose.resources.decodeToImageBitmap
import java.io.IOException

val systemTemporaryPath = FileSystem.SYSTEM_TEMPORARY_DIRECTORY
Expand All @@ -27,14 +28,14 @@ actual fun saveImageFile(fileName: String, image: ByteArray) {

}

actual suspend fun readImageFile(fileName: String): ImageBitmap {
actual suspend fun readImageFile(fileName: String): ByteArray {
val path = "${systemTemporaryPath/fileName}".toPath()
var imageByteArray = ByteArray(0)
FileSystem.SYSTEM.read(path) {
imageByteArray = readByteArray()
}

return BitmapFactory.decodeByteArray(imageByteArray, 0, imageByteArray.size).asImageBitmap()
return imageByteArray
}

actual fun saveLutFile(fileName: String, lut: ByteArray) {
Expand All @@ -50,7 +51,7 @@ suspend fun deleteFile(filePath: String) {
}
}

actual suspend fun saveImageToGallery(image: ImageBitmap, appContext: AppContext) {
actual suspend fun saveImageToGallery(image: String, appContext: AppContext) {
val context: Context = appContext.get()!!
val settings = SettingsStorageImpl()

Expand All @@ -65,7 +66,7 @@ actual suspend fun saveImageToGallery(image: ImageBitmap, appContext: AppContext
uri?.let {
resolver.openOutputStream(it).use { outputStream ->
if (outputStream != null) {
image.asAndroidBitmap().compress(Bitmap.CompressFormat.JPEG, settings.exportQuality, outputStream)
readImageFile(image).decodeToImageBitmap().asAndroidBitmap().compress(Bitmap.CompressFormat.JPEG, settings.exportQuality, outputStream)
}
}
} ?: throw IOException("Failed to create new MediaStore record.")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,4 @@ actual suspend fun ImageBitmap.readPixels(): ByteArray {

}

actual suspend fun ByteArray.fixImageOrientation(): ByteArray {
return this //Image picker library already fixes it
}
actual suspend fun fixImageOrientation(image: String): String = image
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import org.koin.dsl.bind
import org.koin.dsl.module
import util.EDITED_IMAGE_FILE_NAME
import util.apply3dLut
import util.readImageFile
import util.saveImageFile
Expand Down Expand Up @@ -86,29 +87,26 @@ internal class DefaultFilmRepository(
}


override suspend fun applyFilmLut(scope: CoroutineScope, filmLut: FilmLut, imageBitmap: ImageBitmap, onComplete: (ImageBitmap) -> Unit){
override suspend fun applyFilmLut(scope: CoroutineScope, filmLut: FilmLut, image: String, onComplete: (String) -> Unit){
var lutCube = getLutCube(filmLut.lut_name)
if (lutCube == null) {
downloadLutCube(filmLut.lut_name)
lutCube = getLutCube(filmLut.lut_name)
}

applyLutFile(scope = scope, lutCube = lutCube!!, imageBitmap = imageBitmap) {
applyLutFile(scope = scope, lutCube = lutCube!!, image = image) {
onComplete(it)
}
}

private suspend fun applyLutFile(scope: CoroutineScope, lutCube: LutCube, imageBitmap: ImageBitmap, onComplete: (ImageBitmap) -> Unit) {
private suspend fun applyLutFile(scope: CoroutineScope, lutCube: LutCube, image: String, onComplete: (String) -> Unit) {
withContext(Dispatchers.IO) {
val inputFile = "image.jpeg".also { saveImageFile(fileName = it, image = imageBitmap.readPixels()) }
val lutFile = "lut.cube".also { saveLutFile(fileName = it, lut = lutCube.file_) }
val outputFile = "image-new.jpeg"
apply3dLut(inputFile = inputFile, lutFile = lutFile, outputFile = outputFile) {
//TODO refactor this function to take input and output file names
val outputFile = EDITED_IMAGE_FILE_NAME
apply3dLut(inputFile = image, lutFile = lutFile, outputFile = outputFile) {
scope.launch {
val resultImage = withContext(Dispatchers.IO) {
readImageFile(outputFile)
}
onComplete(resultImage)
onComplete(outputFile)
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,5 +29,5 @@ interface FilmRepository {

suspend fun downloadLutCube(name: String)

suspend fun applyFilmLut(scope: CoroutineScope, filmLut: FilmLut, imageBitmap: ImageBitmap, onComplete: (ImageBitmap) -> Unit)
suspend fun applyFilmLut(scope: CoroutineScope, filmLut: FilmLut, image: String, onComplete: (String) -> Unit)
}
Original file line number Diff line number Diff line change
Expand Up @@ -45,14 +45,17 @@ import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.ImageBitmap
import androidx.compose.ui.layout.ContentScale
import androidx.compose.ui.text.TextStyle
import androidx.compose.ui.unit.dp
import cafe.adriel.voyager.core.screen.Screen
import cafe.adriel.voyager.koin.getScreenModel
import cafe.adriel.voyager.navigator.LocalNavigator
import cafe.adriel.voyager.navigator.currentOrThrow
import coil3.compose.AsyncImage
import coil3.compose.LocalPlatformContext
import coil3.request.CachePolicy
import coil3.request.ImageRequest
import com.seiko.imageloader.rememberImagePainter

import film_simulator.shared.generated.resources.Res
Expand All @@ -70,6 +73,7 @@ import io.github.yahiaangelo.filmsimulator.data.source.network.GITHUB_BASE_URL
import io.github.yahiaangelo.filmsimulator.screens.settings.SettingsScreen
import io.github.yahiaangelo.filmsimulator.view.AppScaffold
import io.github.yahiaangelo.filmsimulator.view.ProgressDialog
import okio.FileSystem
import org.jetbrains.compose.resources.painterResource
import org.jetbrains.compose.resources.stringResource

Expand Down Expand Up @@ -104,7 +108,7 @@ data class HomeScreen(
) { innerPadding ->

HomeContent(
imageBitmap = uiState.image,
image = uiState.image,
selectedFilm = uiState.lut,
onRefresh = vm::refresh,
onImageChooseClick = singleImagePicker::launch,
Expand All @@ -131,15 +135,14 @@ data class HomeScreen(

@Composable
private fun HomeContent(
imageBitmap: ImageBitmap?,
image: String?,
selectedFilm : FilmLut?,
onRefresh: () -> Unit,
onImageChooseClick: () -> Unit,
onFilmBoxClick: () -> Unit,
modifier: Modifier = Modifier
) {


Column(modifier = modifier.padding(horizontal = 18.dp)) {

Spacer(modifier = Modifier.size(23.dp))
Expand All @@ -156,8 +159,17 @@ data class HomeScreen(
onClick = onImageChooseClick
) {
Box(modifier = Modifier.fillMaxSize()) {
imageBitmap?.let {
Image(modifier = Modifier.fillMaxSize(), bitmap = imageBitmap, contentDescription = null)
image?.let {
AsyncImage(
modifier = Modifier.fillMaxSize(), model = ImageRequest.Builder(
LocalPlatformContext.current
)
.data("${FileSystem.SYSTEM_TEMPORARY_DIRECTORY}/${image.substringBefore("?")}") // removing the string added after "?" that was used to identify the state
.memoryCacheKey(image)
.diskCacheKey(image)
.diskCachePolicy(CachePolicy.DISABLED)
.build(), contentDescription = null
)
} ?: IconButton(modifier = Modifier.align(Alignment.Center).size(150.dp), onClick = onImageChooseClick ) {
Column {
Icon(painter = painterResource(Res.drawable.ic_image_add_24),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@ package screens.home
import androidx.compose.ui.graphics.ImageBitmap
import cafe.adriel.voyager.core.model.ScreenModel
import cafe.adriel.voyager.core.model.screenModelScope
import com.preat.peekaboo.image.picker.toImageBitmap
import io.github.vinceglb.filekit.core.PlatformFile
import io.github.yahiaangelo.filmsimulator.FilmLut
import io.github.yahiaangelo.filmsimulator.data.source.FilmRepository
Expand All @@ -16,7 +15,11 @@ import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.first
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import kotlinx.datetime.Clock
import org.koin.dsl.module
import util.EDITED_IMAGE_FILE_NAME
import util.IMAGE_FILE_NAME
import util.saveImageFile
import util.saveImageToGallery

val homeScreenModule = module {
Expand All @@ -27,7 +30,7 @@ val homeScreenModule = module {
* UiState for the Main Screen
*/
data class HomeUiState(
val image: ImageBitmap? = null,
val image: String? = null,
val lut: FilmLut? = null,
val filmLutsList: List<FilmLut> = emptyList(),
val isLoading: Boolean = false,
Expand All @@ -51,8 +54,8 @@ data class HomeScreenModel(val repository: FilmRepository) : ScreenModel {
_uiState.value = update(_uiState.value)
}

private val _originalImage: MutableStateFlow<ImageBitmap?> = MutableStateFlow(null)
private val _editedImage: MutableStateFlow<ImageBitmap?> = MutableStateFlow(null)
private val _originalImage: MutableStateFlow<String?> = MutableStateFlow(null)
private val _editedImage: MutableStateFlow<String?> = MutableStateFlow(null)

fun refresh() {
screenModelScope.launch {
Expand All @@ -75,9 +78,10 @@ data class HomeScreenModel(val repository: FilmRepository) : ScreenModel {
try {
updateUiState { it.copy(isLoading = true, loadingMessage = "Applying Film LUT...") }
withContext(Dispatchers.IO) {
repository.applyFilmLut(scope = screenModelScope, filmLut = filmLut, imageBitmap = image) {resultImage ->
repository.applyFilmLut(scope = screenModelScope, filmLut = filmLut, image = image) {resultImage ->
screenModelScope.launch { _editedImage.emit(resultImage) }
updateUiState { it.copy(image = resultImage, lut = filmLut) }
updateUiState { it.copy(lut = filmLut) }
emitImage(resultImage)
}
}
} catch (e: Exception) {
Expand All @@ -92,11 +96,12 @@ data class HomeScreenModel(val repository: FilmRepository) : ScreenModel {
fun onImagePickerResult(file: PlatformFile?) {
file?.let {
screenModelScope.launch {
val fixedImage = it.readBytes().fixImageOrientation()
val image = fixedImage.toImageBitmap()
_originalImage.emit(image)
_editedImage.emit(image)
updateUiState { it.copy(image = image) }
saveImageFile(IMAGE_FILE_NAME, it.readBytes())
saveImageFile(EDITED_IMAGE_FILE_NAME, it.readBytes())
fixImageOrientation(image = IMAGE_FILE_NAME)
_originalImage.emit(IMAGE_FILE_NAME)
_editedImage.emit(IMAGE_FILE_NAME)
emitImage(IMAGE_FILE_NAME)
_uiState.value.lut?.let { selectFilmLut(it) }
}
}
Expand All @@ -110,20 +115,25 @@ data class HomeScreenModel(val repository: FilmRepository) : ScreenModel {
updateUiState { it.copy(showBottomSheet = false) }
}

fun emitImage(image: String) {
updateUiState { it.copy(image = "$image?${Clock.System.now().epochSeconds}") }
}

fun snackbarMessageShown() {
updateUiState { it.copy(userMessage = null) }
}

fun showOriginalImage(show: Boolean) {
screenModelScope.launch {
val targetImage = if (show) _originalImage.value else _editedImage.value
targetImage?.let { updateUiState { it.copy(image = targetImage) } }
targetImage?.let { emitImage(targetImage) }
}
}

fun resetImage() {
_originalImage.value?.let {originalImage ->
updateUiState { it.copy(image = originalImage, lut = null) }
updateUiState { it.copy(lut = null) }
emitImage(originalImage)
}
}

Expand All @@ -132,7 +142,7 @@ data class HomeScreenModel(val repository: FilmRepository) : ScreenModel {
screenModelScope.launch {
try {
updateUiState { it.copy(isLoading = true, loadingMessage = "Exporting image...") }
saveImageToGallery(it, appContext = AppContext)
saveImageToGallery(EDITED_IMAGE_FILE_NAME, appContext = AppContext)
updateUiState { it.copy(userMessage = "Image exported successfully.") }
} catch (e: Exception) {
updateUiState { it.copy(userMessage = "Error exporting image: ${e.message}") }
Expand Down
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
package util

import androidx.compose.ui.graphics.ImageBitmap
import io.github.yahiaangelo.filmsimulator.util.AppContext

const val IMAGE_FILE_NAME = "image.jpeg"
const val EDITED_IMAGE_FILE_NAME = "image-new.jpeg"
/**
* Save an image file to cache
*/
Expand All @@ -11,7 +12,7 @@ expect fun saveImageFile(fileName: String, image: ByteArray)
/**
* Read an image file from cache
*/
expect suspend fun readImageFile(fileName: String): ImageBitmap
expect suspend fun readImageFile(fileName: String): ByteArray

/**
* Save cube lut file to cache
Expand All @@ -21,4 +22,4 @@ expect fun saveLutFile(fileName: String, lut: ByteArray)
/**
* Export image to gallery
*/
expect suspend fun saveImageToGallery(image: ImageBitmap, appContext: AppContext)
expect suspend fun saveImageToGallery(image: String, appContext: AppContext)
Original file line number Diff line number Diff line change
Expand Up @@ -19,4 +19,4 @@ expect suspend fun ImageBitmap.readPixels(): ByteArray
/**
* Fix image's Exif orientation
*/
expect suspend fun ByteArray.fixImageOrientation(): ByteArray
expect suspend fun fixImageOrientation(image: String): String
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ actual suspend fun apply3dLut(inputFile: String, lutFile: String, outputFile: St

deleteFile(outputFileDir)
withContext(Dispatchers.IO) {
FFmpegKit.executeAsync("-i $inputFileDir -vf lut3d=$lutFileDir -c:a copy $outputFileDir") {
FFmpegKit.executeAsync("-i $inputFileDir -vf lut3d=$lutFileDir -q:v 1 $outputFileDir") {
onComplete()
}
}
Expand Down
Loading

0 comments on commit ededb2e

Please sign in to comment.