mirror of
https://gitlab.com/nkming2/nc-photos.git
synced 2025-01-22 16:56:19 +01:00
Fix cropping and rotating photos with EXIF orientation set
This commit is contained in:
parent
0862f9af6e
commit
ddf059e609
10 changed files with 87 additions and 11 deletions
|
@ -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,
|
||||
|
|
|
@ -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(
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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>());
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue