diff --git a/app/src/main/java/fr/free/nrw/commons/filepicker/FilePicker.kt b/app/src/main/java/fr/free/nrw/commons/filepicker/FilePicker.kt index a7e3a671db..c2c755b55d 100644 --- a/app/src/main/java/fr/free/nrw/commons/filepicker/FilePicker.kt +++ b/app/src/main/java/fr/free/nrw/commons/filepicker/FilePicker.kt @@ -15,6 +15,12 @@ import fr.free.nrw.commons.filepicker.PickedFiles.singleFileList import java.io.File import java.io.IOException import java.net.URISyntaxException +import android.graphics.Bitmap +import android.graphics.ImageDecoder +import android.os.Build +import android.widget.Toast +import androidx.appcompat.app.AlertDialog +import java.io.FileOutputStream object FilePicker : Constants { @@ -52,7 +58,7 @@ object FilePicker : Constants { ): Intent { // storing picked image type to shared preferences storeType(context, type) - // Supported types are SVG, PNG and JPEG, GIF, TIFF, WebP, XCF + // Supported types are SVG, PNG and JPEG, GIF, TIFF, WebP, XCF and HEIC (for future conversion) val mimeTypes = arrayOf( "image/jpg", "image/png", @@ -62,7 +68,8 @@ object FilePicker : Constants { "image/webp", "image/xcf", "image/svg+xml", - "image/webp" + "image/heic", + "image/heif" ) return plainGalleryPickerIntent(openDocumentIntentPreferred) .putExtra( @@ -349,7 +356,8 @@ object FilePicker : Constants { val images = data?.getParcelableArrayListExtra("Images") images?.forEach { image -> val uri = image.uri - val file = PickedFiles.pickedExistingPicture(activity, uri) + var file = PickedFiles.pickedExistingPicture(activity, uri) + file = handleHeicFile(file, activity) files.add(file) } @@ -379,6 +387,33 @@ object FilePicker : Constants { callbacks.onCanceled(ImageSource.GALLERY, restoreType(activity)) } } + @JvmStatic + fun convertHeicToJpg(file: File, context: Context): File { + val bitmap = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) { + val source = ImageDecoder.createSource(context.contentResolver, Uri.fromFile(file)) + ImageDecoder.decodeBitmap(source) + } else { + throw IllegalStateException("HEIC conversion requires Android P+") + } + + val jpgFile = File(file.parent, file.nameWithoutExtension + ".jpg") + FileOutputStream(jpgFile).use { out -> + bitmap.compress(Bitmap.CompressFormat.JPEG, 100, out) + } + return jpgFile + } + @JvmStatic + private fun handleHeicFile(file: UploadableFile, activity: Activity): UploadableFile { + val extension = file.file.extension.lowercase() + return if (extension == "heic" || extension == "heif") { + val jpgFile = convertHeicToJpg(file.file, activity) + val uploadableFile = UploadableFile(jpgFile) + uploadableFile.hasUnsupportedFormat = true + uploadableFile + } else { + file + } + } @Throws(IOException::class, SecurityException::class) @JvmStatic @@ -390,12 +425,14 @@ object FilePicker : Constants { val clipData = data?.clipData if (clipData == null) { val uri = data?.data - val file = PickedFiles.pickedExistingPicture(activity, uri!!) + var file = PickedFiles.pickedExistingPicture(activity, uri!!) + file = handleHeicFile(file, activity) files.add(file) } else { for (i in 0 until clipData.itemCount) { val uri = clipData.getItemAt(i).uri - val file = PickedFiles.pickedExistingPicture(activity, uri) + var file = PickedFiles.pickedExistingPicture(activity, uri) + file = handleHeicFile(file, activity) files.add(file) } } diff --git a/app/src/main/java/fr/free/nrw/commons/filepicker/UploadableFile.kt b/app/src/main/java/fr/free/nrw/commons/filepicker/UploadableFile.kt index d8109cb3df..d0a3c40ad1 100644 --- a/app/src/main/java/fr/free/nrw/commons/filepicker/UploadableFile.kt +++ b/app/src/main/java/fr/free/nrw/commons/filepicker/UploadableFile.kt @@ -20,6 +20,7 @@ class UploadableFile : Parcelable { val contentUri: Uri val file: File + var hasUnsupportedFormat: Boolean = false constructor(contentUri: Uri, file: File) { this.contentUri = contentUri @@ -34,6 +35,7 @@ class UploadableFile : Parcelable { private constructor(parcel: Parcel) { contentUri = parcel.readParcelable(Uri::class.java.classLoader)!! file = parcel.readSerializable() as File + hasUnsupportedFormat = parcel.readInt() == 1 } fun getFilePath(): String { @@ -126,6 +128,7 @@ class UploadableFile : Parcelable { override fun writeToParcel(parcel: Parcel, flags: Int) { parcel.writeParcelable(contentUri, flags) parcel.writeSerializable(file) + parcel.writeInt(if (hasUnsupportedFormat) 1 else 0) } class DateTimeWithSource { diff --git a/app/src/main/java/fr/free/nrw/commons/upload/ImageProcessingService.kt b/app/src/main/java/fr/free/nrw/commons/upload/ImageProcessingService.kt index cbec7559f0..177c56b542 100644 --- a/app/src/main/java/fr/free/nrw/commons/upload/ImageProcessingService.kt +++ b/app/src/main/java/fr/free/nrw/commons/upload/ImageProcessingService.kt @@ -59,11 +59,12 @@ class ImageProcessingService @Inject constructor( checkFBMD(filePath), checkEXIF(filePath) ) { duplicateImage: Int, wrongGeoLocation: Int, darkImage: Int, fbmd: Int, exif: Int -> + val unsupportedFormatResult = if (uploadItem.hasUnsupportedFormat) fr.free.nrw.commons.utils.ImageUtils.IMAGE_FORMAT_UNSUPPORTED else 0 Timber.d( - "duplicate: %d, geo: %d, dark: %d, fbmd: %d, exif: %d", - duplicateImage, wrongGeoLocation, darkImage, fbmd, exif + "duplicate: %d, geo: %d, dark: %d, fbmd: %d, exif: %d, unsupported: %d", + duplicateImage, wrongGeoLocation, darkImage, fbmd, exif, unsupportedFormatResult ) - return@zip duplicateImage or wrongGeoLocation or darkImage or fbmd or exif + return@zip duplicateImage or wrongGeoLocation or darkImage or fbmd or exif or unsupportedFormatResult } } diff --git a/app/src/main/java/fr/free/nrw/commons/upload/UploadItem.kt b/app/src/main/java/fr/free/nrw/commons/upload/UploadItem.kt index 6d2321defa..8db989422e 100644 --- a/app/src/main/java/fr/free/nrw/commons/upload/UploadItem.kt +++ b/app/src/main/java/fr/free/nrw/commons/upload/UploadItem.kt @@ -19,7 +19,8 @@ class UploadItem( */ var contentUri: Uri?, //according to EXIF data - val fileCreatedDateString: String? + val fileCreatedDateString: String?, + val hasUnsupportedFormat: Boolean = false ) { var imageQuality: Int = ImageUtils.IMAGE_WAIT var uploadMediaDetails: MutableList = mutableListOf(UploadMediaDetail()) diff --git a/app/src/main/java/fr/free/nrw/commons/upload/UploadModel.kt b/app/src/main/java/fr/free/nrw/commons/upload/UploadModel.kt index 954079a45e..ea829cf94c 100644 --- a/app/src/main/java/fr/free/nrw/commons/upload/UploadModel.kt +++ b/app/src/main/java/fr/free/nrw/commons/upload/UploadModel.kt @@ -145,7 +145,8 @@ class UploadModel @Inject internal constructor( uploadableFile?.getMimeType(context), imageCoordinates, place, fileCreatedDate, createdTimestampSource, uploadableFile?.contentUri, - fileCreatedDateString + fileCreatedDateString, + uploadableFile?.hasUnsupportedFormat ?: false ) // If an uploadItem of the same uploadableFile has been created before, we return that. diff --git a/app/src/main/java/fr/free/nrw/commons/utils/ImageUtils.kt b/app/src/main/java/fr/free/nrw/commons/utils/ImageUtils.kt index fa538bb215..6e27a018d3 100644 --- a/app/src/main/java/fr/free/nrw/commons/utils/ImageUtils.kt +++ b/app/src/main/java/fr/free/nrw/commons/utils/ImageUtils.kt @@ -73,6 +73,7 @@ object ImageUtils { const val EMPTY_CAPTION = -3 const val FILE_NAME_EXISTS = 1 shl 6 // 64 const val NO_CATEGORY_SELECTED = -5 + const val IMAGE_FORMAT_UNSUPPORTED = 1 shl 7 // 128 private var progressDialogWallpaper: ProgressDialog? = null @@ -90,7 +91,8 @@ object ImageUtils { EMPTY_CAPTION, FILE_NAME_EXISTS, NO_CATEGORY_SELECTED, - IMAGE_GEOLOCATION_DIFFERENT + IMAGE_GEOLOCATION_DIFFERENT, + IMAGE_FORMAT_UNSUPPORTED ] ) @Retention @@ -349,6 +351,10 @@ object ImageUtils { errorMessage.append("\n - "). append(context.getString(R.string.upload_problem_image_duplicate)) } + if (result and IMAGE_FORMAT_UNSUPPORTED != 0) { + errorMessage.append("\n - ") + .append(context.getString(R.string.upload_problem_image_format_unsupported)) + } if (result and IMAGE_GEOLOCATION_DIFFERENT != 0) { errorMessage.append("\n - ") .append(context.getString(R.string.upload_problem_different_geolocation)) diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 950ffd0754..2c9ecdbb91 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -260,6 +260,7 @@ Image is too dark. Image is blurry. Image is already on Commons. + The image type previously selected was not supported and has been converted to JPG. This picture was taken at a different location. Please only upload pictures that you have taken by yourself. Don\'t upload pictures that you have found on other people\'s Facebook accounts. Do you still want to upload this picture?