You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 

153 lines
5.0 KiB

package example.imageviewer.storage
import android.content.Context
import android.graphics.Bitmap
import android.net.Uri
import androidx.compose.runtime.snapshots.SnapshotStateList
import androidx.compose.ui.graphics.ImageBitmap
import androidx.compose.ui.graphics.asAndroidBitmap
import androidx.compose.ui.graphics.asImageBitmap
import androidx.core.content.FileProvider
import androidx.core.graphics.scale
import example.imageviewer.ImageStorage
import example.imageviewer.PlatformStorableImage
import example.imageviewer.model.PictureData
import example.imageviewer.toImageBitmap
import imageviewer.shared.generated.resources.Res
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import kotlinx.serialization.encodeToString
import kotlinx.serialization.json.Json
import java.io.File
private const val maxStorableImageSizePx = 2000
private const val storableThumbnailSizePx = 200
private const val jpegCompressionQuality = 60
class AndroidImageStorage(
private val pictures: SnapshotStateList<PictureData>,
private val ioScope: CoroutineScope,
context: Context
) : ImageStorage {
private val savePictureDir = File(context.filesDir, "taken_photos")
private val sharedImagesDir = File(context.filesDir, "share_images")
private val PictureData.Camera.jpgFile get() = File(savePictureDir, "$id.jpg")
private val PictureData.Camera.thumbnailJpgFile
get() = File(
savePictureDir,
"$id-thumbnail.jpg"
)
private val PictureData.Camera.jsonFile get() = File(savePictureDir, "$id.json")
init {
if (savePictureDir.isDirectory) {
val files = savePictureDir.listFiles { _, name: String ->
name.endsWith(".json")
} ?: emptyArray()
pictures.addAll(
index = 0,
elements = files.map {
it.readText().toCameraMetadata()
}.sortedByDescending {
it.timeStampSeconds
}
)
} else {
savePictureDir.mkdirs()
}
}
override fun saveImage(picture: PictureData.Camera, image: PlatformStorableImage) {
if (image.imageBitmap.width == 0 || image.imageBitmap.height == 0) {
return
}
ioScope.launch {
with(image.imageBitmap) {
picture.jpgFile.writeJpeg(fitInto(maxStorableImageSizePx))
picture.thumbnailJpgFile.writeJpeg(fitInto(storableThumbnailSizePx))
}
pictures.add(0, picture)
picture.jsonFile.writeText(picture.toJson())
}
}
override fun delete(picture: PictureData.Camera) {
ioScope.launch {
picture.jsonFile.delete()
picture.jpgFile.delete()
picture.thumbnailJpgFile.delete()
}
}
override fun rewrite(picture: PictureData.Camera) {
ioScope.launch {
picture.jsonFile.delete()
picture.jsonFile.writeText(picture.toJson())
}
}
override suspend fun getThumbnail(picture: PictureData.Camera): ImageBitmap =
withContext(ioScope.coroutineContext) {
picture.thumbnailJpgFile.readBytes().toImageBitmap()
}
override suspend fun getImage(picture: PictureData.Camera): ImageBitmap =
withContext(ioScope.coroutineContext) {
picture.jpgFile.readBytes().toImageBitmap()
}
suspend fun getUri(context: Context, picture: PictureData): Uri = withContext(Dispatchers.IO) {
if (!sharedImagesDir.exists()) {
sharedImagesDir.mkdirs()
}
val tempFileToShare: File = sharedImagesDir.resolve("share_picture.jpg")
when (picture) {
is PictureData.Camera -> {
picture.jpgFile.copyTo(tempFileToShare, overwrite = true)
}
is PictureData.Resource -> {
if (!tempFileToShare.exists()) {
tempFileToShare.createNewFile()
}
tempFileToShare.writeBytes(Res.readBytes(picture.resource))
}
}
FileProvider.getUriForFile(
context,
"example.imageviewer.fileprovider",
tempFileToShare
)
}
}
private fun ImageBitmap.fitInto(px: Int): ImageBitmap {
val targetScale = maxOf(
px.toFloat() / width,
px.toFloat() / height
)
return if (targetScale < 1.0) {
asAndroidBitmap().scale(
width = (width * targetScale).toInt(),
height = (height * targetScale).toInt()
).asImageBitmap()
} else {
this
}
}
private fun PictureData.Camera.toJson(): String =
Json.Default.encodeToString(this)
private fun String.toCameraMetadata(): PictureData.Camera =
Json.Default.decodeFromString(this)
private fun File.writeJpeg(image: ImageBitmap, compressionQuality: Int = jpegCompressionQuality) {
outputStream().use {
image.asAndroidBitmap().compress(Bitmap.CompressFormat.JPEG, compressionQuality, it)
}
}