From 0496c608c042e07f8d9faafe477f4e1a5e6a1096 Mon Sep 17 00:00:00 2001 From: FooIbar <118464521+FooIbar@users.noreply.github.com> Date: Mon, 8 Jul 2024 18:02:50 +0800 Subject: [PATCH] Fix some issues when reading/saving images (mihonapp/mihon#993) * Fix unsupported mime type error when saving images Avoid using platform mime type map to get extensions as it may not have all mime types we support. * Fix jxl images downloading/reading (cherry picked from commit daa47e049327c4d8b1fe4724ed1b84897d81fcf2) --- .../data/download/DownloadManager.kt | 3 ++- .../tachiyomi/data/download/Downloader.kt | 10 ++----- .../kanade/tachiyomi/data/saver/ImageSaver.kt | 18 ++++++++----- .../core/common/util/system/ImageUtil.kt | 26 +++++-------------- 4 files changed, 23 insertions(+), 34 deletions(-) diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/download/DownloadManager.kt b/app/src/main/java/eu/kanade/tachiyomi/data/download/DownloadManager.kt index e714ed2b2a..e2eee84143 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/download/DownloadManager.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/download/DownloadManager.kt @@ -21,6 +21,7 @@ import logcat.LogPriority import tachiyomi.core.common.i18n.stringResource import tachiyomi.core.common.storage.extension import tachiyomi.core.common.util.lang.launchIO +import tachiyomi.core.common.util.system.ImageUtil import tachiyomi.core.common.util.system.logcat import tachiyomi.domain.category.interactor.GetCategories import tachiyomi.domain.chapter.model.Chapter @@ -170,7 +171,7 @@ class DownloadManager( source, ) val files = chapterDir?.listFiles().orEmpty() - .filter { "image" in it.type.orEmpty() } + .filter { it.isFile && ImageUtil.isImage(it.name) { it.openInputStream() } } if (files.isEmpty()) { throw Exception(context.stringResource(MR.strings.page_list_empty_error)) diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/download/Downloader.kt b/app/src/main/java/eu/kanade/tachiyomi/data/download/Downloader.kt index fcf45e96c8..58377a5fd4 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/download/Downloader.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/download/Downloader.kt @@ -553,14 +553,8 @@ class Downloader( * @param file the file where the image is already downloaded. */ private fun getImageExtension(response: Response, file: UniFile): String { - // Read content type if available. val mime = response.body.contentType()?.run { if (type == "image") "image/$subtype" else null } - // Else guess from the uri. - ?: context.contentResolver.getType(file.uri) - // Else read magic numbers. - ?: ImageUtil.findImageType { file.openInputStream() }?.mime - - return ImageUtil.getExtensionFromMimeType(mime) + return ImageUtil.getExtensionFromMimeType(mime) { file.openInputStream() } } private fun splitTallImageIfNeeded(page: Page, tmpDir: UniFile) { @@ -758,7 +752,7 @@ class Downloader( download.status = Download.State.NOT_DOWNLOADED } } - queue - downloads + queue - downloads.toSet() } } diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/saver/ImageSaver.kt b/app/src/main/java/eu/kanade/tachiyomi/data/saver/ImageSaver.kt index b7b53d835e..0cc0ebc04d 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/saver/ImageSaver.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/saver/ImageSaver.kt @@ -7,6 +7,7 @@ import android.net.Uri import android.os.Build import android.os.Environment import android.provider.MediaStore +import android.webkit.MimeTypeMap import androidx.annotation.RequiresApi import androidx.core.content.contentValuesOf import androidx.core.net.toUri @@ -65,21 +66,26 @@ class ImageSaver( filename: String, data: () -> InputStream, ): Uri { - val pictureDir = + val isMimeTypeSupported = MimeTypeMap.getSingleton().hasMimeType(type.mime) + + val pictureDir = if (isMimeTypeSupported) { MediaStore.Images.Media.getContentUri(MediaStore.VOLUME_EXTERNAL_PRIMARY) + } else { + MediaStore.Files.getContentUri(MediaStore.VOLUME_EXTERNAL_PRIMARY) + } val imageLocation = (image.location as Location.Pictures).relativePath val relativePath = listOf( - Environment.DIRECTORY_PICTURES, + if (isMimeTypeSupported) Environment.DIRECTORY_PICTURES else Environment.DIRECTORY_DOCUMENTS, context.stringResource(MR.strings.app_name), imageLocation, ).joinToString(File.separator) val contentValues = contentValuesOf( - MediaStore.Images.Media.RELATIVE_PATH to relativePath, - MediaStore.Images.Media.DISPLAY_NAME to image.name, - MediaStore.Images.Media.MIME_TYPE to type.mime, - MediaStore.Images.Media.DATE_MODIFIED to Instant.now().epochSecond, + MediaStore.MediaColumns.RELATIVE_PATH to relativePath, + MediaStore.MediaColumns.DISPLAY_NAME to if (isMimeTypeSupported) image.name else filename, + MediaStore.MediaColumns.MIME_TYPE to type.mime, + MediaStore.MediaColumns.DATE_MODIFIED to Instant.now().epochSecond, ) val picture = findUriOrDefault(relativePath, filename) { diff --git a/core/common/src/main/kotlin/tachiyomi/core/common/util/system/ImageUtil.kt b/core/common/src/main/kotlin/tachiyomi/core/common/util/system/ImageUtil.kt index 8602fba946..076758aae5 100644 --- a/core/common/src/main/kotlin/tachiyomi/core/common/util/system/ImageUtil.kt +++ b/core/common/src/main/kotlin/tachiyomi/core/common/util/system/ImageUtil.kt @@ -14,7 +14,6 @@ import android.graphics.drawable.ColorDrawable import android.graphics.drawable.Drawable import android.graphics.drawable.GradientDrawable import android.os.Build -import android.webkit.MimeTypeMap import androidx.annotation.ColorInt import androidx.core.graphics.alpha import androidx.core.graphics.applyCanvas @@ -32,7 +31,6 @@ import tachiyomi.decoder.Format import tachiyomi.decoder.ImageDecoder import java.io.File import java.io.InputStream -import java.net.URLConnection import java.security.SecureRandom import java.util.Locale import kotlin.math.abs @@ -47,12 +45,8 @@ object ImageUtil { if (File(name).extension.equals("cbi", ignoreCase = true)) return true // SY <-- - val contentType = try { - URLConnection.guessContentTypeFromName(name) - } catch (e: Exception) { - null - } ?: openStream?.let { findImageType(it)?.mime } - return contentType?.startsWith("image/") ?: false + val extension = name.substringAfterLast('.') + return ImageType.entries.any { it.extension == extension } || openStream?.let { findImageType(it) } != null } fun findImageType(openStream: () -> InputStream): ImageType? { @@ -76,10 +70,9 @@ object ImageUtil { } } - fun getExtensionFromMimeType(mime: String?): String { - return MimeTypeMap.getSingleton().getExtensionFromMimeType(mime) - ?: SUPPLEMENTARY_MIMETYPE_MAPPING[mime] - ?: "jpg" + fun getExtensionFromMimeType(mime: String?, openStream: () -> InputStream): String { + val type = mime?.let { ImageType.entries.find { it.mime == mime } } ?: findImageType(openStream) + return type?.extension ?: "jpg" } fun isAnimatedAndSupported(source: BufferedSource): Boolean { @@ -217,7 +210,6 @@ object ImageUtil { * new image with added center padding scaled relative to the height of the display view * to compensate for scaling. */ - fun addHorizontalCenterMargin(imageSource: BufferedSource, viewHeight: Int, backgroundContext: Context): BufferedSource { val imageBitmap = ImageDecoder.newInstance(imageSource.inputStream())?.decode()!! val height = imageBitmap.height @@ -623,12 +615,7 @@ object ImageUtil { private val optimalImageHeight = getDisplayMaxHeightInPx * 2 - // Android doesn't include some mappings - private val SUPPLEMENTARY_MIMETYPE_MAPPING = mapOf( - // https://issuetracker.google.com/issues/182703810 - "image/jxl" to "jxl", - ) - + // SY --> fun mergeBitmaps( imageBitmap: Bitmap, imageBitmap2: Bitmap, @@ -674,6 +661,7 @@ object ImageUtil { private val Bitmap.rect: Rect get() = Rect(0, 0, width, height) + // SY <-- } val getDisplayMaxHeightInPx: Int