@@ -19,6 +19,7 @@ import androidx.exifinterface.media.ExifInterface
1919import java.io.ByteArrayOutputStream
2020import java.io.IOException
2121import java.lang.Exception
22+ import java.lang.reflect.Method
2223import java.nio.ByteBuffer
2324
2425/*
@@ -40,137 +41,170 @@ import java.nio.ByteBuffer
4041
4142
4243/* * Utils functions for bitmap conversions. */
43- object BitmapUtils {
44- private const val TAG = " BitmapUtils"
45-
46- private var yuvToRgbConverter: YuvToRgbConverter ? = null
47-
48- // based on androidx.camera.core.ImageSaver#imageToJpegByteArray(),
49- // optimized to avoid extracting uncropped image twice and to close ImageProxy sooner
50- @SuppressLint(" RestrictedApi" )
51- @Throws(ImageUtil .CodecFailedException ::class )
52- public fun extractJpegBytes (image : ImageProxy , jpegQuality : Int ): ByteArray {
53- // try {
54- var cropRect = if (ImageUtil .shouldCropImage(image)) image.cropRect else null
44+ class BitmapUtils {
45+
46+
47+ companion object {
48+ private const val TAG = " BitmapUtils"
49+ private var yuvToRgbConverter: YuvToRgbConverter ? = null
50+
51+ // private var post1dot4: Boolean = true
52+ // private var yuvImageToJpegByteArray: Method
53+
54+ // init {
55+ // // we try handle the fact yuvImageToJpegByteArray changed number of parameter from 1.3.2 to later
56+ // yuvImageToJpegByteArray =
57+ // ImageUtil::class.java.declaredMethods.first { it.name == "yuvImageToJpegByteArray" }
58+ // post1dot4 = yuvImageToJpegByteArray.parameterCount == 4
59+ // }
60+
61+ // based on androidx.camera.core.ImageSaver#imageToJpegByteArray(),
62+ // optimized to avoid extracting uncropped image twice and to close ImageProxy sooner
63+ // @SuppressLint("RestrictedApi")
64+ // @Throws(ImageUtil.CodecFailedException::class)
65+ // public fun extractJpegBytes(image: ImageProxy, jpegQuality: Int, rotation: Int): ByteArray {
66+ // var cropRect = if (ImageUtil.shouldCropImage(image)) image.cropRect else null
67+ // val imageFormat = image.format
68+ //
69+ // var origJpegBytes = if (imageFormat == ImageFormat.JPEG) {
70+ // ImageUtil.jpegImageToJpegByteArray(image)
71+ // } else if (imageFormat == ImageFormat.YUV_420_888) {
72+ // if (post1dot4) {
73+ // yuvImageToJpegByteArray!!.invoke(
74+ // null,
75+ // image,
76+ // cropRect,
77+ // jpegQuality,
78+ // rotation
79+ // ) as ByteArray
80+ // // ImageUtil.yuvImageToJpegByteArray(image, cropRect, jpegQuality, rotation)
81+ //
82+ // } else {
83+ // yuvImageToJpegByteArray!!.invoke(
84+ // null,
85+ // image,
86+ // cropRect,
87+ // jpegQuality
88+ // ) as ByteArray
89+ // // ImageUtil.yuvImageToJpegByteArray(image, cropRect, jpegQuality)
90+ // }
91+ // } else {
92+ // throw IllegalStateException("unknown imageFormat $imageFormat")
93+ // }
94+ // return origJpegBytes!!;
95+ // }
96+
97+ // @OptIn(ExperimentalGetImage::class)
98+ // public fun byteArrayFromProxy(image: ImageProxy, jpegQuality: Int): ByteArray? {
99+ // return extractJpegBytes(image, jpegQuality, image.imageInfo.rotationDegrees);
100+ // }
101+
102+ @SuppressLint(" RestrictedApi" )
103+ @OptIn(ExperimentalGetImage ::class )
104+ public fun getBitmap (context : Context , image : ImageProxy , jpegQuality : Int = 95): Bitmap {
55105 val imageFormat = image.format
56-
57- var origJpegBytes = if (imageFormat == ImageFormat .JPEG ) {
58- ImageUtil .jpegImageToJpegByteArray(image)
59- } else if (imageFormat == ImageFormat .YUV_420_888 ) {
60- ImageUtil .yuvImageToJpegByteArray(image, cropRect, jpegQuality)
106+ if (imageFormat == ImageFormat .YUV_420_888 ) {
107+ // TODO: for now extractJpegBytes is around 3/4 times slower than yuvToRgbConverter
108+ if (yuvToRgbConverter == null ) {
109+ yuvToRgbConverter = YuvToRgbConverter (context)
110+ }
111+ var bm = Bitmap .createBitmap(image.width, image.height, Bitmap .Config .ARGB_8888 )
112+ yuvToRgbConverter!! .yuvToRgb(image.image!! , bm)
113+ return bm;
61114 } else {
62- throw IllegalStateException (" unknown imageFormat $imageFormat " )
63- }
64- return origJpegBytes!! ;
65- // } finally {
66- /*
67- from javadoc of the Image class:
68- Since Images are often directly produced or consumed by hardware components, they are
69- a limited resource shared across the system, and should be closed as soon as
70- they are no longer needed.
71- */
72- // image.close()
73- // }
74- }
75- @OptIn(ExperimentalGetImage ::class ) public fun byteArrayFromProxy (image : ImageProxy , jpegQuality : Int ): ByteArray? {
76- return extractJpegBytes(image,jpegQuality);
77- }
78- @OptIn(ExperimentalGetImage ::class ) public fun getBitmap (context : Context , image : ImageProxy , jpegQuality : Int = 95): Bitmap {
79- val imageFormat = image.format
80- if (imageFormat == ImageFormat .YUV_420_888 ) {
81- // TODO: for now extractJpegBytes is around 3/4 times slower than yuvToRgbConverter
82- if (yuvToRgbConverter == null ) {
83- yuvToRgbConverter = YuvToRgbConverter (context)
115+ val byteArray = ImageUtil .jpegImageToJpegByteArray(image);
116+ var bm = BitmapFactory .decodeByteArray(byteArray, 0 , byteArray.size);
117+ return bm;
84118 }
85- var bm = Bitmap .createBitmap(image.width, image.height, Bitmap .Config .ARGB_8888 )
86- yuvToRgbConverter!! .yuvToRgb(image.image!! , bm)
87- return bm;
88- } else {
89- val byteArray = extractJpegBytes(image, if (jpegQuality > 0 ) jpegQuality else 95 );
90- var bm = BitmapFactory .decodeByteArray(byteArray, 0 , byteArray.size);
91- return bm;
92- }
93119
94120
95121// var bm = Bitmap.createBitmap(image.width, image.height, Bitmap.Config.ARGB_8888)
96122// yuvToRgbConverter!!.yuvToRgb(image.image!!, bm)
97123// return bm;
98- }
124+ }
99125
100- /* * Rotates a bitmap if it is converted from a bytebuffer. */
101- private fun rotateBitmap (
102- bitmap : Bitmap , rotationDegrees : Int , flipX : Boolean , flipY : Boolean
103- ): Bitmap {
104- val matrix = Matrix ()
126+ /* * Rotates a bitmap if it is converted from a bytebuffer. */
127+ private fun rotateBitmap (
128+ bitmap : Bitmap , rotationDegrees : Int , flipX : Boolean , flipY : Boolean
129+ ): Bitmap {
130+ val matrix = Matrix ()
105131
106- // Rotate the image back to straight.
107- matrix.postRotate(rotationDegrees.toFloat())
132+ // Rotate the image back to straight.
133+ matrix.postRotate(rotationDegrees.toFloat())
108134
109- // Mirror the image along the X or Y axis.
110- matrix.postScale(if (flipX) - 1.0f else 1.0f , if (flipY) - 1.0f else 1.0f )
111- val rotatedBitmap =
112- Bitmap .createBitmap(bitmap, 0 , 0 , bitmap.width, bitmap.height, matrix, true )
135+ // Mirror the image along the X or Y axis.
136+ matrix.postScale(if (flipX) - 1.0f else 1.0f , if (flipY) - 1.0f else 1.0f )
137+ val rotatedBitmap =
138+ Bitmap .createBitmap(bitmap, 0 , 0 , bitmap.width, bitmap.height, matrix, true )
113139
114- // Recycle the old bitmap if it has changed.
115- if (rotatedBitmap != bitmap) {
116- bitmap.recycle()
140+ // Recycle the old bitmap if it has changed.
141+ if (rotatedBitmap != bitmap) {
142+ bitmap.recycle()
143+ }
144+ return rotatedBitmap
117145 }
118- return rotatedBitmap
119- }
120146
121- @Throws(IOException ::class )
122- fun getBitmapFromContentUri (contentResolver : ContentResolver , imageUri : Uri ): Bitmap ? {
123- val decodedBitmap =
124- MediaStore .Images .Media .getBitmap(contentResolver, imageUri)
125- ? : return null
126- val orientation = getExifOrientationTag(contentResolver, imageUri)
127- var rotationDegrees = 0
128- var flipX = false
129- var flipY = false
130- when (orientation) {
131- ExifInterface .ORIENTATION_FLIP_HORIZONTAL -> flipX = true
132- ExifInterface .ORIENTATION_ROTATE_90 -> rotationDegrees = 90
133- ExifInterface .ORIENTATION_TRANSPOSE -> {
134- rotationDegrees = 90
135- flipX = true
136- }
137- ExifInterface .ORIENTATION_ROTATE_180 -> rotationDegrees = 180
138- ExifInterface .ORIENTATION_FLIP_VERTICAL -> flipY = true
139- ExifInterface .ORIENTATION_ROTATE_270 -> rotationDegrees = - 90
140- ExifInterface .ORIENTATION_TRANSVERSE -> {
141- rotationDegrees = - 90
142- flipX = true
147+ @Throws(IOException ::class )
148+ fun getBitmapFromContentUri (contentResolver : ContentResolver , imageUri : Uri ): Bitmap ? {
149+ val decodedBitmap =
150+ MediaStore .Images .Media .getBitmap(contentResolver, imageUri)
151+ ? : return null
152+ val orientation = getExifOrientationTag(contentResolver, imageUri)
153+ var rotationDegrees = 0
154+ var flipX = false
155+ var flipY = false
156+ when (orientation) {
157+ ExifInterface .ORIENTATION_FLIP_HORIZONTAL -> flipX = true
158+ ExifInterface .ORIENTATION_ROTATE_90 -> rotationDegrees = 90
159+ ExifInterface .ORIENTATION_TRANSPOSE -> {
160+ rotationDegrees = 90
161+ flipX = true
162+ }
163+
164+ ExifInterface .ORIENTATION_ROTATE_180 -> rotationDegrees = 180
165+ ExifInterface .ORIENTATION_FLIP_VERTICAL -> flipY = true
166+ ExifInterface .ORIENTATION_ROTATE_270 -> rotationDegrees = - 90
167+ ExifInterface .ORIENTATION_TRANSVERSE -> {
168+ rotationDegrees = - 90
169+ flipX = true
170+ }
171+
172+ ExifInterface .ORIENTATION_UNDEFINED , ExifInterface .ORIENTATION_NORMAL -> {}
173+ else -> {}
143174 }
144- ExifInterface .ORIENTATION_UNDEFINED , ExifInterface .ORIENTATION_NORMAL -> {}
145- else -> {}
175+ return rotateBitmap(decodedBitmap, rotationDegrees, flipX, flipY)
146176 }
147- return rotateBitmap(decodedBitmap, rotationDegrees, flipX, flipY)
148- }
149177
150- private fun getExifOrientationTag (resolver : ContentResolver , imageUri : Uri ): Int {
151- // We only support parsing EXIF orientation tag from local file on the device.
152- // See also:
153- // https://android-developers.googleblog.com/2016/12/introducing-the-exifinterface-support-library.html
154- if (ContentResolver .SCHEME_CONTENT != imageUri.scheme
155- && ContentResolver .SCHEME_FILE != imageUri.scheme
156- ) {
157- return 0
158- }
159- var exif: ExifInterface
160- try {
161- resolver.openInputStream(imageUri).use { inputStream ->
162- if (inputStream == null ) {
163- return 0
178+ private fun getExifOrientationTag (resolver : ContentResolver , imageUri : Uri ): Int {
179+ // We only support parsing EXIF orientation tag from local file on the device.
180+ // See also:
181+ // https://android-developers.googleblog.com/2016/12/introducing-the-exifinterface-support-library.html
182+ if (ContentResolver .SCHEME_CONTENT != imageUri.scheme
183+ && ContentResolver .SCHEME_FILE != imageUri.scheme
184+ ) {
185+ return 0
186+ }
187+ var exif: ExifInterface
188+ try {
189+ resolver.openInputStream(imageUri).use { inputStream ->
190+ if (inputStream == null ) {
191+ return 0
192+ }
193+ exif = ExifInterface (inputStream)
164194 }
165- exif = ExifInterface (inputStream)
195+ } catch (e: IOException ) {
196+ Log .e(
197+ TAG ,
198+ " failed to open file to read rotation meta data: $imageUri " , e
199+ )
200+ return 0
166201 }
167- } catch (e: IOException ) {
168- Log .e(
169- TAG ,
170- " failed to open file to read rotation meta data: $imageUri " , e
202+ return exif.getAttributeInt(
203+ ExifInterface .TAG_ORIENTATION ,
204+ ExifInterface .ORIENTATION_NORMAL
171205 )
172- return 0
173206 }
174- return exif.getAttributeInt(ExifInterface .TAG_ORIENTATION , ExifInterface .ORIENTATION_NORMAL )
175207 }
208+
209+
176210}
0 commit comments