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,
|
height: k.photoLargeSize,
|
||||||
a: true,
|
a: true,
|
||||||
));
|
));
|
||||||
|
// no need to set shouldfixOrientation because the previews are always in
|
||||||
|
// the correct orientation
|
||||||
_src = await ImageLoader.loadUri(
|
_src = await ImageLoader.loadUri(
|
||||||
"file://${fileInfo!.file.path}",
|
"file://${fileInfo!.file.path}",
|
||||||
_previewWidth,
|
_previewWidth,
|
||||||
|
|
|
@ -3,7 +3,9 @@ package com.nkming.nc_photos.plugin
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.graphics.Bitmap
|
import android.graphics.Bitmap
|
||||||
import android.graphics.BitmapFactory
|
import android.graphics.BitmapFactory
|
||||||
|
import android.graphics.Matrix
|
||||||
import android.net.Uri
|
import android.net.Uri
|
||||||
|
import androidx.exifinterface.media.ExifInterface
|
||||||
import java.io.InputStream
|
import java.io.InputStream
|
||||||
import kotlin.contracts.ExperimentalContracts
|
import kotlin.contracts.ExperimentalContracts
|
||||||
import kotlin.contracts.InvocationKind
|
import kotlin.contracts.InvocationKind
|
||||||
|
@ -91,6 +93,7 @@ interface BitmapUtil {
|
||||||
resizeMethod: BitmapResizeMethod,
|
resizeMethod: BitmapResizeMethod,
|
||||||
isAllowSwapSide: Boolean = false,
|
isAllowSwapSide: Boolean = false,
|
||||||
shouldUpscale: Boolean = true,
|
shouldUpscale: Boolean = true,
|
||||||
|
shouldFixOrientation: Boolean = false,
|
||||||
): Bitmap {
|
): Bitmap {
|
||||||
val opt = loadImageBounds(context, uri)
|
val opt = loadImageBounds(context, uri)
|
||||||
val shouldSwapSide = isAllowSwapSide &&
|
val shouldSwapSide = isAllowSwapSide &&
|
||||||
|
@ -116,9 +119,13 @@ interface BitmapUtil {
|
||||||
logD(TAG, "Bitmap subsampled: ${bitmap.width}x${bitmap.height}")
|
logD(TAG, "Bitmap subsampled: ${bitmap.width}x${bitmap.height}")
|
||||||
}
|
}
|
||||||
if (bitmap.width < dstW && bitmap.height < dstH && !shouldUpscale) {
|
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(
|
BitmapResizeMethod.FIT -> Bitmap.createScaledBitmap(
|
||||||
bitmap,
|
bitmap,
|
||||||
minOf(dstW, (dstH * bitmap.aspectRatio()).toInt()),
|
minOf(dstW, (dstH * bitmap.aspectRatio()).toInt()),
|
||||||
|
@ -133,6 +140,62 @@ interface BitmapUtil {
|
||||||
true
|
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(
|
private fun openUriInputStream(
|
||||||
|
|
|
@ -24,6 +24,7 @@ class ImageLoaderChannelHandler(context: Context) :
|
||||||
call.argument("resizeMethod")!!,
|
call.argument("resizeMethod")!!,
|
||||||
call.argument("isAllowSwapSide")!!,
|
call.argument("isAllowSwapSide")!!,
|
||||||
call.argument("shouldUpscale")!!,
|
call.argument("shouldUpscale")!!,
|
||||||
|
call.argument("shouldFixOrientation")!!,
|
||||||
result
|
result
|
||||||
)
|
)
|
||||||
} catch (e: Throwable) {
|
} catch (e: Throwable) {
|
||||||
|
@ -44,17 +45,18 @@ class ImageLoaderChannelHandler(context: Context) :
|
||||||
* @param resizeMethod
|
* @param resizeMethod
|
||||||
* @param isAllowSwapSide
|
* @param isAllowSwapSide
|
||||||
* @param shouldUpscale
|
* @param shouldUpscale
|
||||||
|
* @param shouldFixOrientation
|
||||||
* @param result
|
* @param result
|
||||||
*/
|
*/
|
||||||
private fun loadUri(
|
private fun loadUri(
|
||||||
fileUri: String, maxWidth: Int, maxHeight: Int, resizeMethod: Int,
|
fileUri: String, maxWidth: Int, maxHeight: Int, resizeMethod: Int,
|
||||||
isAllowSwapSide: Boolean, shouldUpscale: Boolean,
|
isAllowSwapSide: Boolean, shouldUpscale: Boolean,
|
||||||
result: MethodChannel.Result
|
shouldFixOrientation: Boolean, result: MethodChannel.Result
|
||||||
) {
|
) {
|
||||||
val image = BitmapUtil.loadImage(
|
val image = BitmapUtil.loadImage(
|
||||||
context, Uri.parse(fileUri), maxWidth, maxHeight,
|
context, Uri.parse(fileUri), maxWidth, maxHeight,
|
||||||
BitmapResizeMethod.values()[resizeMethod], isAllowSwapSide,
|
BitmapResizeMethod.values()[resizeMethod], isAllowSwapSide,
|
||||||
shouldUpscale
|
shouldUpscale, shouldFixOrientation
|
||||||
).use {
|
).use {
|
||||||
Rgba8Image(TfLiteHelper.bitmapToRgba8Array(it), it.width, it.height)
|
Rgba8Image(TfLiteHelper.bitmapToRgba8Array(it), it.width, it.height)
|
||||||
}
|
}
|
||||||
|
|
|
@ -518,7 +518,9 @@ private open class ImageProcessorCommandTask(context: Context) :
|
||||||
ExifInterface.TAG_IMAGE_DESCRIPTION,
|
ExifInterface.TAG_IMAGE_DESCRIPTION,
|
||||||
ExifInterface.TAG_MAKE,
|
ExifInterface.TAG_MAKE,
|
||||||
ExifInterface.TAG_MODEL,
|
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_X_RESOLUTION,
|
||||||
ExifInterface.TAG_Y_RESOLUTION,
|
ExifInterface.TAG_Y_RESOLUTION,
|
||||||
ExifInterface.TAG_DATETIME,
|
ExifInterface.TAG_DATETIME,
|
||||||
|
|
|
@ -22,7 +22,8 @@ class ArbitraryStyleTransfer(
|
||||||
val height: Int
|
val height: Int
|
||||||
val rgb8Image = BitmapUtil.loadImage(
|
val rgb8Image = BitmapUtil.loadImage(
|
||||||
context, imageUri, maxWidth, maxHeight, BitmapResizeMethod.FIT,
|
context, imageUri, maxWidth, maxHeight, BitmapResizeMethod.FIT,
|
||||||
isAllowSwapSide = true, shouldUpscale = false
|
isAllowSwapSide = true, shouldUpscale = false,
|
||||||
|
shouldFixOrientation = true
|
||||||
).use {
|
).use {
|
||||||
width = it.width
|
width = it.width
|
||||||
height = it.height
|
height = it.height
|
||||||
|
|
|
@ -23,7 +23,8 @@ class DeepLab3Portrait(
|
||||||
val height: Int
|
val height: Int
|
||||||
val rgb8Image = BitmapUtil.loadImage(
|
val rgb8Image = BitmapUtil.loadImage(
|
||||||
context, imageUri, maxWidth, maxHeight, BitmapResizeMethod.FIT,
|
context, imageUri, maxWidth, maxHeight, BitmapResizeMethod.FIT,
|
||||||
isAllowSwapSide = true, shouldUpscale = false
|
isAllowSwapSide = true, shouldUpscale = false,
|
||||||
|
shouldFixOrientation = true
|
||||||
).use {
|
).use {
|
||||||
width = it.width
|
width = it.width
|
||||||
height = it.height
|
height = it.height
|
||||||
|
@ -54,7 +55,8 @@ class DeepLab3ColorPop(
|
||||||
val height: Int
|
val height: Int
|
||||||
val rgb8Image = BitmapUtil.loadImage(
|
val rgb8Image = BitmapUtil.loadImage(
|
||||||
context, imageUri, maxWidth, maxHeight, BitmapResizeMethod.FIT,
|
context, imageUri, maxWidth, maxHeight, BitmapResizeMethod.FIT,
|
||||||
isAllowSwapSide = true, shouldUpscale = false
|
isAllowSwapSide = true, shouldUpscale = false,
|
||||||
|
shouldFixOrientation = true
|
||||||
).use {
|
).use {
|
||||||
width = it.width
|
width = it.width
|
||||||
height = it.height
|
height = it.height
|
||||||
|
|
|
@ -15,7 +15,7 @@ class Esrgan(context: Context, maxWidth: Int, maxHeight: Int) {
|
||||||
val rgb8Image = BitmapUtil.loadImage(
|
val rgb8Image = BitmapUtil.loadImage(
|
||||||
context, imageUri, maxWidth / 4, maxHeight / 4,
|
context, imageUri, maxWidth / 4, maxHeight / 4,
|
||||||
BitmapResizeMethod.FIT, isAllowSwapSide = true,
|
BitmapResizeMethod.FIT, isAllowSwapSide = true,
|
||||||
shouldUpscale = false
|
shouldUpscale = false, shouldFixOrientation = true
|
||||||
).use {
|
).use {
|
||||||
width = it.width
|
width = it.width
|
||||||
height = it.height
|
height = it.height
|
||||||
|
|
|
@ -18,7 +18,8 @@ class ImageFilterProcessor(
|
||||||
fun apply(imageUri: Uri): Bitmap {
|
fun apply(imageUri: Uri): Bitmap {
|
||||||
var img = BitmapUtil.loadImage(
|
var img = BitmapUtil.loadImage(
|
||||||
context, imageUri, maxWidth, maxHeight, BitmapResizeMethod.FIT,
|
context, imageUri, maxWidth, maxHeight, BitmapResizeMethod.FIT,
|
||||||
isAllowSwapSide = true, shouldUpscale = false
|
isAllowSwapSide = true, shouldUpscale = false,
|
||||||
|
shouldFixOrientation = true
|
||||||
).use {
|
).use {
|
||||||
Rgba8Image(TfLiteHelper.bitmapToRgba8Array(it), it.width, it.height)
|
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 height: Int
|
||||||
val rgb8Image = BitmapUtil.loadImage(
|
val rgb8Image = BitmapUtil.loadImage(
|
||||||
context, imageUri, maxWidth, maxHeight, BitmapResizeMethod.FIT,
|
context, imageUri, maxWidth, maxHeight, BitmapResizeMethod.FIT,
|
||||||
isAllowSwapSide = true, shouldUpscale = false
|
isAllowSwapSide = true, shouldUpscale = false,
|
||||||
|
shouldFixOrientation = true
|
||||||
).use {
|
).use {
|
||||||
width = it.width
|
width = it.width
|
||||||
height = it.height
|
height = it.height
|
||||||
|
|
|
@ -17,6 +17,7 @@ class ImageLoader {
|
||||||
ImageLoaderResizeMethod resizeMethod, {
|
ImageLoaderResizeMethod resizeMethod, {
|
||||||
bool isAllowSwapSide = false,
|
bool isAllowSwapSide = false,
|
||||||
bool shouldUpscale = false,
|
bool shouldUpscale = false,
|
||||||
|
bool shouldFixOrientation = false,
|
||||||
}) async {
|
}) async {
|
||||||
final result =
|
final result =
|
||||||
await _methodChannel.invokeMethod<Map>("loadUri", <String, dynamic>{
|
await _methodChannel.invokeMethod<Map>("loadUri", <String, dynamic>{
|
||||||
|
@ -26,6 +27,7 @@ class ImageLoader {
|
||||||
"resizeMethod": resizeMethod.index,
|
"resizeMethod": resizeMethod.index,
|
||||||
"isAllowSwapSide": isAllowSwapSide,
|
"isAllowSwapSide": isAllowSwapSide,
|
||||||
"shouldUpscale": shouldUpscale,
|
"shouldUpscale": shouldUpscale,
|
||||||
|
"shouldFixOrientation": shouldFixOrientation,
|
||||||
});
|
});
|
||||||
return Rgba8Image.fromJson(result!.cast<String, dynamic>());
|
return Rgba8Image.fromJson(result!.cast<String, dynamic>());
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue