diff --git a/app/lib/widget/image_editor.dart b/app/lib/widget/image_editor.dart index 17135233..c85986e7 100644 --- a/app/lib/widget/image_editor.dart +++ b/app/lib/widget/image_editor.dart @@ -90,6 +90,8 @@ class _ImageEditorState extends State { height: k.photoLargeSize, a: true, )); + // no need to set shouldfixOrientation because the previews are always in + // the correct orientation _src = await ImageLoader.loadUri( "file://${fileInfo!.file.path}", _previewWidth, diff --git a/plugin/android/src/main/kotlin/com/nkming/nc_photos/plugin/BitmapUtil.kt b/plugin/android/src/main/kotlin/com/nkming/nc_photos/plugin/BitmapUtil.kt index 65de88ed..260753a2 100644 --- a/plugin/android/src/main/kotlin/com/nkming/nc_photos/plugin/BitmapUtil.kt +++ b/plugin/android/src/main/kotlin/com/nkming/nc_photos/plugin/BitmapUtil.kt @@ -3,7 +3,9 @@ package com.nkming.nc_photos.plugin import android.content.Context import android.graphics.Bitmap import android.graphics.BitmapFactory +import android.graphics.Matrix import android.net.Uri +import androidx.exifinterface.media.ExifInterface import java.io.InputStream import kotlin.contracts.ExperimentalContracts import kotlin.contracts.InvocationKind @@ -91,6 +93,7 @@ interface BitmapUtil { resizeMethod: BitmapResizeMethod, isAllowSwapSide: Boolean = false, shouldUpscale: Boolean = true, + shouldFixOrientation: Boolean = false, ): Bitmap { val opt = loadImageBounds(context, uri) val shouldSwapSide = isAllowSwapSide && @@ -116,9 +119,13 @@ interface BitmapUtil { logD(TAG, "Bitmap subsampled: ${bitmap.width}x${bitmap.height}") } if (bitmap.width < dstW && bitmap.height < dstH && !shouldUpscale) { - return bitmap + return if (shouldFixOrientation) { + fixOrientation(context, uri, bitmap) + } else { + bitmap + } } - return when (resizeMethod) { + val result = when (resizeMethod) { BitmapResizeMethod.FIT -> Bitmap.createScaledBitmap( bitmap, minOf(dstW, (dstH * bitmap.aspectRatio()).toInt()), @@ -133,6 +140,62 @@ interface BitmapUtil { true ) } + return if (shouldFixOrientation) { + fixOrientation(context, uri, result) + } else { + result + } + } + + /** + * Rotate the image to its visible orientation + */ + private fun fixOrientation( + context: Context, uri: Uri, bitmap: Bitmap + ): Bitmap { + return try { + openUriInputStream(context, uri)!!.use { + val iExif = ExifInterface(it) + val orientation = + iExif.getAttributeInt(ExifInterface.TAG_ORIENTATION, 1) + logI( + TAG, + "[fixOrientation] Input file orientation: $orientation" + ) + val rotate = when (orientation) { + ExifInterface.ORIENTATION_ROTATE_90, + ExifInterface.ORIENTATION_TRANSPOSE -> 90f + + ExifInterface.ORIENTATION_ROTATE_180, + ExifInterface.ORIENTATION_FLIP_VERTICAL -> 180f + + ExifInterface.ORIENTATION_ROTATE_270, + ExifInterface.ORIENTATION_TRANSVERSE -> 270f + + ExifInterface.ORIENTATION_FLIP_HORIZONTAL -> 0f + else -> return bitmap + } + val mat = Matrix() + mat.postRotate(rotate) + if (orientation == ExifInterface.ORIENTATION_FLIP_HORIZONTAL || + orientation == ExifInterface.ORIENTATION_TRANSVERSE || + orientation == ExifInterface.ORIENTATION_FLIP_VERTICAL || + orientation == ExifInterface.ORIENTATION_TRANSPOSE + ) { + mat.postScale(-1f, 1f) + } + Bitmap.createBitmap( + bitmap, 0, 0, bitmap.width, bitmap.height, mat, true + ) + } + } catch (e: Throwable) { + logE( + TAG, + "[fixOrientation] Failed fixing, assume normal orientation", + e + ) + bitmap + } } private fun openUriInputStream( diff --git a/plugin/android/src/main/kotlin/com/nkming/nc_photos/plugin/ImageLoaderChannelHandler.kt b/plugin/android/src/main/kotlin/com/nkming/nc_photos/plugin/ImageLoaderChannelHandler.kt index 5e27875d..0f157430 100644 --- a/plugin/android/src/main/kotlin/com/nkming/nc_photos/plugin/ImageLoaderChannelHandler.kt +++ b/plugin/android/src/main/kotlin/com/nkming/nc_photos/plugin/ImageLoaderChannelHandler.kt @@ -24,6 +24,7 @@ class ImageLoaderChannelHandler(context: Context) : call.argument("resizeMethod")!!, call.argument("isAllowSwapSide")!!, call.argument("shouldUpscale")!!, + call.argument("shouldFixOrientation")!!, result ) } catch (e: Throwable) { @@ -44,17 +45,18 @@ class ImageLoaderChannelHandler(context: Context) : * @param resizeMethod * @param isAllowSwapSide * @param shouldUpscale + * @param shouldFixOrientation * @param result */ private fun loadUri( fileUri: String, maxWidth: Int, maxHeight: Int, resizeMethod: Int, isAllowSwapSide: Boolean, shouldUpscale: Boolean, - result: MethodChannel.Result + shouldFixOrientation: Boolean, result: MethodChannel.Result ) { val image = BitmapUtil.loadImage( context, Uri.parse(fileUri), maxWidth, maxHeight, BitmapResizeMethod.values()[resizeMethod], isAllowSwapSide, - shouldUpscale + shouldUpscale, shouldFixOrientation ).use { Rgba8Image(TfLiteHelper.bitmapToRgba8Array(it), it.width, it.height) } diff --git a/plugin/android/src/main/kotlin/com/nkming/nc_photos/plugin/ImageProcessorService.kt b/plugin/android/src/main/kotlin/com/nkming/nc_photos/plugin/ImageProcessorService.kt index fc82d5b9..91353c13 100644 --- a/plugin/android/src/main/kotlin/com/nkming/nc_photos/plugin/ImageProcessorService.kt +++ b/plugin/android/src/main/kotlin/com/nkming/nc_photos/plugin/ImageProcessorService.kt @@ -518,7 +518,9 @@ private open class ImageProcessorCommandTask(context: Context) : ExifInterface.TAG_IMAGE_DESCRIPTION, ExifInterface.TAG_MAKE, ExifInterface.TAG_MODEL, - ExifInterface.TAG_ORIENTATION, + // while processing, we'll correct the orientation, if we copy the + // value to the resulting image, the orientation will be wrong +// ExifInterface.TAG_ORIENTATION, ExifInterface.TAG_X_RESOLUTION, ExifInterface.TAG_Y_RESOLUTION, ExifInterface.TAG_DATETIME, diff --git a/plugin/android/src/main/kotlin/com/nkming/nc_photos/plugin/image_processor/ArbitraryStyleTransfer.kt b/plugin/android/src/main/kotlin/com/nkming/nc_photos/plugin/image_processor/ArbitraryStyleTransfer.kt index 4fd8b545..ccea8c97 100644 --- a/plugin/android/src/main/kotlin/com/nkming/nc_photos/plugin/image_processor/ArbitraryStyleTransfer.kt +++ b/plugin/android/src/main/kotlin/com/nkming/nc_photos/plugin/image_processor/ArbitraryStyleTransfer.kt @@ -22,7 +22,8 @@ class ArbitraryStyleTransfer( val height: Int val rgb8Image = BitmapUtil.loadImage( context, imageUri, maxWidth, maxHeight, BitmapResizeMethod.FIT, - isAllowSwapSide = true, shouldUpscale = false + isAllowSwapSide = true, shouldUpscale = false, + shouldFixOrientation = true ).use { width = it.width height = it.height diff --git a/plugin/android/src/main/kotlin/com/nkming/nc_photos/plugin/image_processor/DeepLab3.kt b/plugin/android/src/main/kotlin/com/nkming/nc_photos/plugin/image_processor/DeepLab3.kt index a0eff4a0..70747d40 100644 --- a/plugin/android/src/main/kotlin/com/nkming/nc_photos/plugin/image_processor/DeepLab3.kt +++ b/plugin/android/src/main/kotlin/com/nkming/nc_photos/plugin/image_processor/DeepLab3.kt @@ -23,7 +23,8 @@ class DeepLab3Portrait( val height: Int val rgb8Image = BitmapUtil.loadImage( context, imageUri, maxWidth, maxHeight, BitmapResizeMethod.FIT, - isAllowSwapSide = true, shouldUpscale = false + isAllowSwapSide = true, shouldUpscale = false, + shouldFixOrientation = true ).use { width = it.width height = it.height @@ -54,7 +55,8 @@ class DeepLab3ColorPop( val height: Int val rgb8Image = BitmapUtil.loadImage( context, imageUri, maxWidth, maxHeight, BitmapResizeMethod.FIT, - isAllowSwapSide = true, shouldUpscale = false + isAllowSwapSide = true, shouldUpscale = false, + shouldFixOrientation = true ).use { width = it.width height = it.height diff --git a/plugin/android/src/main/kotlin/com/nkming/nc_photos/plugin/image_processor/Esrgan.kt b/plugin/android/src/main/kotlin/com/nkming/nc_photos/plugin/image_processor/Esrgan.kt index 6fafce7b..b02e5ee4 100644 --- a/plugin/android/src/main/kotlin/com/nkming/nc_photos/plugin/image_processor/Esrgan.kt +++ b/plugin/android/src/main/kotlin/com/nkming/nc_photos/plugin/image_processor/Esrgan.kt @@ -15,7 +15,7 @@ class Esrgan(context: Context, maxWidth: Int, maxHeight: Int) { val rgb8Image = BitmapUtil.loadImage( context, imageUri, maxWidth / 4, maxHeight / 4, BitmapResizeMethod.FIT, isAllowSwapSide = true, - shouldUpscale = false + shouldUpscale = false, shouldFixOrientation = true ).use { width = it.width height = it.height diff --git a/plugin/android/src/main/kotlin/com/nkming/nc_photos/plugin/image_processor/ImageFilterProcessor.kt b/plugin/android/src/main/kotlin/com/nkming/nc_photos/plugin/image_processor/ImageFilterProcessor.kt index 9d010075..6fb04620 100644 --- a/plugin/android/src/main/kotlin/com/nkming/nc_photos/plugin/image_processor/ImageFilterProcessor.kt +++ b/plugin/android/src/main/kotlin/com/nkming/nc_photos/plugin/image_processor/ImageFilterProcessor.kt @@ -18,7 +18,8 @@ class ImageFilterProcessor( fun apply(imageUri: Uri): Bitmap { var img = BitmapUtil.loadImage( context, imageUri, maxWidth, maxHeight, BitmapResizeMethod.FIT, - isAllowSwapSide = true, shouldUpscale = false + isAllowSwapSide = true, shouldUpscale = false, + shouldFixOrientation = true ).use { Rgba8Image(TfLiteHelper.bitmapToRgba8Array(it), it.width, it.height) } diff --git a/plugin/android/src/main/kotlin/com/nkming/nc_photos/plugin/image_processor/ZeroDce.kt b/plugin/android/src/main/kotlin/com/nkming/nc_photos/plugin/image_processor/ZeroDce.kt index 75721830..b9b20483 100644 --- a/plugin/android/src/main/kotlin/com/nkming/nc_photos/plugin/image_processor/ZeroDce.kt +++ b/plugin/android/src/main/kotlin/com/nkming/nc_photos/plugin/image_processor/ZeroDce.kt @@ -14,7 +14,8 @@ class ZeroDce(context: Context, maxWidth: Int, maxHeight: Int, iteration: Int) { val height: Int val rgb8Image = BitmapUtil.loadImage( context, imageUri, maxWidth, maxHeight, BitmapResizeMethod.FIT, - isAllowSwapSide = true, shouldUpscale = false + isAllowSwapSide = true, shouldUpscale = false, + shouldFixOrientation = true ).use { width = it.width height = it.height diff --git a/plugin/lib/src/image_loader.dart b/plugin/lib/src/image_loader.dart index 2780ec72..7fb3de3e 100644 --- a/plugin/lib/src/image_loader.dart +++ b/plugin/lib/src/image_loader.dart @@ -17,6 +17,7 @@ class ImageLoader { ImageLoaderResizeMethod resizeMethod, { bool isAllowSwapSide = false, bool shouldUpscale = false, + bool shouldFixOrientation = false, }) async { final result = await _methodChannel.invokeMethod("loadUri", { @@ -26,6 +27,7 @@ class ImageLoader { "resizeMethod": resizeMethod.index, "isAllowSwapSide": isAllowSwapSide, "shouldUpscale": shouldUpscale, + "shouldFixOrientation": shouldFixOrientation, }); return Rgba8Image.fromJson(result!.cast()); }