Fix cropping and rotating photos with EXIF orientation set

This commit is contained in:
Ming Ming 2022-09-11 15:31:04 +08:00
parent 0862f9af6e
commit ddf059e609
10 changed files with 87 additions and 11 deletions

View file

@ -90,6 +90,8 @@ class _ImageEditorState extends State<ImageEditor> {
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,

View file

@ -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(

View file

@ -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)
}

View file

@ -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,

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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)
}

View file

@ -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

View file

@ -17,6 +17,7 @@ class ImageLoader {
ImageLoaderResizeMethod resizeMethod, {
bool isAllowSwapSide = false,
bool shouldUpscale = false,
bool shouldFixOrientation = false,
}) async {
final result =
await _methodChannel.invokeMethod<Map>("loadUri", <String, dynamic>{
@ -26,6 +27,7 @@ class ImageLoader {
"resizeMethod": resizeMethod.index,
"isAllowSwapSide": isAllowSwapSide,
"shouldUpscale": shouldUpscale,
"shouldFixOrientation": shouldFixOrientation,
});
return Rgba8Image.fromJson(result!.cast<String, dynamic>());
}