I try to add simple functionality with getting picture from gallery or from camera in my Android app. And all works fine, I successfully obtain uri of picture and after that want to show this picture to user. And this code works well:
private fun showImageToUser(uri: Uri) {
val inputStream = contentResolver?.openInputStream(uri)
val bytes = inputStream?.readBytes() ?: return
val bitmapOriginal = BitmapFactory.decodeByteArray(bytes, 0, bytes.size)
// show bitmap in ImageView...
}
Then, I want to rotate image if need (for example, after camera all images have 90-degrees rotation). For this I use ExifInterface (from androidx.exifinterface:exifinterface:1.3.1). But there is something strange. In this code:
private fun showImageToUser(uri: Uri) {
val inputStream = contentResolver?.openInputStream(uri)
val exifInterface = ExifInterface(inputStream ?: return)
val rotation = exifInterface.getAttributeInt(ExifInterface.TAG_ORIENTATION, ExifInterface.ORIENTATION_NORMAL)
val bytes = inputStream.readBytes()
val bitmapOriginal = BitmapFactory.decodeByteArray(bytes, 0, bytes.size)
val bitmapRotated = bitmapOriginal.rotateImageIfRequired(rotation) // simple logic for rotating in rotateImageIfRequired method...
}
the bitmapOriginal is always null. As you can see, I create exifInterface object before inputStream.readBytes(). If I swap them, and try to run the following code:
private fun showImageToUser(uri: Uri) {
val inputStream = contentResolver?.openInputStream(uri)
val bytes = inputStream?.readBytes() ?: return
val exifInterface = ExifInterface(inputStream)
val rotation = exifInterface.getAttributeInt(ExifInterface.TAG_ORIENTATION, ExifInterface.ORIENTATION_NORMAL)
val bitmapOriginal = BitmapFactory.decodeByteArray(bytes, 0, bytes.size)
val bitmapRotated = bitmapOriginal.rotateImageIfRequired(rotation)
}
then bitmapOriginal will not be null, but the rotation value will be always ORIENTATION_UNDEFINED.
So, what am I doing wrong? How to get correctly bytes and image orientation from uri and create after that bitmap with correct orientation?
Related
So I have implement CameraX in my new application but the pictures when saving are looking very bad with distorsion. Like the image is blurry and with lines everywhere. I tried multipile converters from imageProxy to Bitmap but the result is the same. I tested on 2 devices: Samsung A72 and Samsung S20 FE.
// Camera Provider Future ------------
cameraProviderFuture.addListener({
imagePreview = Preview.Builder().apply {
setTargetAspectRatio(AspectRatio.RATIO_16_9)
}.build()
imageCapture = ImageCapture.Builder().apply {
setCaptureMode(ImageCapture.CAPTURE_MODE_MINIMIZE_LATENCY)
setTargetAspectRatio(AspectRatio.RATIO_16_9)
setFlashMode(ImageCapture.FLASH_MODE_AUTO)
}.build()
val cameraProvider = cameraProviderFuture.get()
cameraProvider.unbindAll()
cameraProvider.bindToLifecycle(this, cameraSelector, imagePreview, imageCapture)
binding.dtcCameraPreview.implementationMode = PreviewView.ImplementationMode.COMPATIBLE
imagePreview?.setSurfaceProvider(binding.dtcCameraPreview.surfaceProvider)
}, ContextCompat.getMainExecutor(this))
// Function to convert ImageProxy.image to Bitmap -----------
private fun Image.toBitmap(rotationDegrees: Int): Bitmap {
val buffer = planes[0].buffer
val bytes = ByteArray(buffer.remaining())
buffer.get(bytes)
val bitmap = BitmapFactory.decodeByteArray(bytes, 0, bytes.size, null)
val matrix = Matrix()
matrix.postRotate(rotationDegrees.toFloat())
return Bitmap.createBitmap(bitmap, 0, 0, bitmap.width, bitmap.height, matrix, true)
}
// imageCapture ---------------
imageCapture?.takePicture(ContextCompat.getMainExecutor(this), object :
ImageCapture.OnImageCapturedCallback() {
#SuppressLint("UnsafeOptInUsageError")
override fun onCaptureSuccess(imageProxy: ImageProxy) {
val stream = ByteArrayOutputStream()
imageProxy.image?.toBitmap(90)?.compress(
Bitmap.CompressFormat.JPEG,
100,
stream
)
IntentHelper.addObject(stream.toByteArray(), "dtc_camera_data")
val result = Intent().putExtra(
DTCCameraContract.DTC_CAMERA_ACTION,
DTCCameraAction(false, isPictureTaken = true)
)
setResult(Activity.RESULT_OK, result)
imageProxy.close()
finish()
}
I am trying to capture photos in my app using standard camera app intent (I am NOT interested in using JetpackX or other library to have a viewfinder in my app).
When I had the code in my Fragment like so:
// This is in response to user clicking a button
fun startPhotoTaking() {
val takePictureIntent = Intent(MediaStore.ACTION_IMAGE_CAPTURE)
resultLauncher.launch(takePictureIntent)
}
private var resultLauncher =
registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { result ->
if (result.resultCode == Activity.RESULT_OK) {
val photo = result.data?.extras?.get("data") as? Bitmap
photo?.let {
// ... do whatever
}
}
}
Then the photo Bitmap came back tiny as apparently Android caps intents at 1 Mb , but the orientation was correct.
Since I actually need the original large image, I have modified the code like so:
// This is in response to user clicking a button
fun startPhotoTaking() {
lastUri = getTmpFileUri()
if (lastUri != null) {
resultLauncher.launch(lastUri)
}
}
private fun getTmpFileUri(): Uri {
requireContext().cacheDir.listFiles()?.forEach { it.delete() }
val tmpFile = File
.createTempFile("tmp_image_file", ".jpg", requireContext().cacheDir)
.apply {
createNewFile()
}
return FileProvider.getUriForFile(
MyApplication.instance.applicationContext,
"${BuildConfig.APPLICATION_ID}.provider",
tmpFile
)
}
var lastUri: Uri? = null
private var resultLauncher =
registerForActivityResult(ActivityResultContracts.TakePicture()) { result ->
if (result) {
val photoUri = lastUri
if (photoUri != null) {
val stream = MyApplication.instance.applicationContext.contentResolver
.openInputStream(photoUri)
val photo = BitmapFactory.decodeStream(stream)
stream?.close()
// ... do whatever
// If i try ExifInterface(photoUri.path!!)
}
}
}
Now I do receive the actual large photo, but it is always landscape :(
I tried creating an instance of ExifInterface(photoUri.path) but that throws an exception for me (which I don't quite understand as I am only writing/reading to my own app's cache directory?):
java.io.FileNotFoundException: /cache/tmp_image_file333131952730914647.jpg: open failed: EACCES (Permission denied)
How can I get my photo to retain orientation when saved to file and/or get access to read EXIF parameters so I can rotate it myself?
Update
As a workaround, this did the trick but it's just... ungodly. So am very keen to find better solutions.
val stream = MyApplication.instance.applicationContext.contentResolver
.openInputStream(photoUri)
if (stream != null) {
val tempFile = File.createTempFile("tmp", ".jpg", requireContext().cacheDir)
.apply { createNewFile() }
val fos = FileOutputStream(tempFile)
val buf = ByteArray(8192)
var length: Int
while (stream.read(buf).also { length = it } > 0) {
fos.write(buf, 0, length)
}
fos.close()
val exif = ExifInterface(tempFile.path)
val orientation =
exif.getAttributeInt(ExifInterface.TAG_ORIENTATION, 1)
val matrix = Matrix()
if (orientation == ExifInterface.ORIENTATION_ROTATE_90) {
matrix.postRotate(90f)
} else if (orientation == ExifInterface.ORIENTATION_ROTATE_180) {
matrix.postRotate(180f)
} else if (orientation == ExifInterface.ORIENTATION_ROTATE_270) {
matrix.postRotate(270f)
var photo = BitmapFactory.decodeFile(tempFile.path)
photo = Bitmap.createBitmap(
photo,
0,
0,
photo.width,
photo.height,
matrix,
true
)
}
ExifInterface(photoUri.path)
That isnot an existing path as you have seen.
Better use:
ExifInterface( tmpFile.getAbsolutePath())
You can rotate image to the particular angle like this,
// To rotate image
private fun rotateImage(source: Bitmap, angle: Float): Bitmap? {
val matrix = Matrix()
matrix.postRotate(angle)
return Bitmap.createBitmap(
source, 0, 0, source.width, source.height,
matrix, true
)
}
In your case set angle to 90f to get image in portrait orientation.
I'm trying to convert my tflite output location to position that user can see on screen.
I don't know what I'm doing wrong but the tflite supposed to give me the location (left,right,top,bottom) between 0 and 1. but it give the output between 320 and 320 (this my model input).
this is my image analyzer class:(It's live detection and I use CameraX)
// Initializing the jaw detection model
private val jawDetectionModel = JawDetection.newInstance(context)
val imageProcessor = ImageProcessor.Builder()
.add(ResizeWithCropOrPadOp(320, 320))
.add(ResizeOp(320, 320, ResizeOp.ResizeMethod.NEAREST_NEIGHBOR))
.add(NormalizeOp(0f, 1f))
.build()
// Convert Image to Bitmap then to TensorImage
val tfImage = TensorImage.fromBitmap(imageProxy.convertImageProxyToBitmap(context))
// Process the image using the trained model, sort and pick out the top results
val outputs = jawDetectionModel.process(imageProcessor.process(tfImage))
.detectionResultList.take(MaxResultDisplay)
val left = outputs.first().locationAsRectF.left
val top = outputs.first().locationAsRectF.top
val right = outputs.first().locationAsRectF.right
val bottom = outputs.first().locationAsRectF.bottom
and this for convert image proxy to bitmap:
#SuppressLint("UnsafeExperimentalUsageError")
fun ImageProxy.convertImageProxyToBitmap(context: Context): Bitmap? {
val yuvToRgbConverter = YuvToRgbConverter(context)
val image = this.image ?: return null
// Initialise Buffer
if (!::bitmapBuffer.isInitialized) {
rotationMatrix = Matrix()
rotationMatrix.postRotate(this.imageInfo.rotationDegrees.toFloat())
bitmapBuffer = Bitmap.createBitmap(
this.width, this.height, Bitmap.Config.ARGB_8888
)
}
// Pass image to an image analyser
yuvToRgbConverter.yuvToRgb(image, bitmapBuffer)
// Create the Bitmap in the correct orientation
return Bitmap.createBitmap(
bitmapBuffer,
0,
0,
bitmapBuffer.width,
bitmapBuffer.height,
rotationMatrix,
false
)
}
I have seen a lot of posts on this topic with all solutions being based on Java and not Kotlin. Is there a solution similiar to the one using Java but in Kotlin?
first get a screenshot of the layout:
getViewScreenshot(view: View): Bitmap {
view.setDrawingCacheEnabled(true)
val bitmap = Bitmap.createBitmap(view.getDrawingCache())
view.setDrawingCacheEnabled(false)
return bitmap
}
then blur it with this function:
fun blurBitmap(bitmap: Bitmap, applicationContext: Context): Bitmap {
lateinit var rsContext: RenderScript
try {
// Create the output bitmap
val output = Bitmap.createBitmap(
bitmap.width, bitmap.height, bitmap.config)
// Blur the image
rsContext = RenderScript.create(applicationContext, RenderScript.ContextType.DEBUG)
val inAlloc = Allocation.createFromBitmap(rsContext, bitmap)
val outAlloc = Allocation.createTyped(rsContext, inAlloc.type)
val theIntrinsic = ScriptIntrinsicBlur.create(rsContext, Element.U8_4(rsContext))
theIntrinsic.apply {
setRadius(10f)
theIntrinsic.setInput(inAlloc)
theIntrinsic.forEach(outAlloc)
}
outAlloc.copyTo(output)
return output
} finally {
rsContext.finish()
}
}
I am working on Android application, where I am creating a QR code by using the QR generation library. I have achieved the QR code generation as by sending data from previews Activity to the main Activity where I have generated and shown the QR code.
The issue takes place in the process of saving QR code to the gallery and sharing the same QR code.
I have implemented sharing intent but it says the intent cannot share an empty file.
The same issue appears when I try to save the file.
Basically file with the QR code is always empty.
Here is my code for sharing and QR generation:
class QRGenerationAll : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.qr_generation_all)
val byteArray = intent.getByteArrayExtra("logoimage")
val bmp = BitmapFactory.decodeByteArray(byteArray, 0, byteArray.size)
img_logoimage.setImageBitmap(bmp)
val valuecatchedvalues: String = intent.getStringExtra("sendedvalues")
tv_textforqr.setText(valuecatchedvalues)
val tv_textforqr: String = intent.getStringExtra("logotext")
tv_textforlogoname.setText(tv_textforqr)
try {
//setting size of qr code
val manager = getSystemService(WINDOW_SERVICE) as WindowManager
val display = manager.defaultDisplay
val point = Point()
display.getSize(point)
val width = point.x
val height = point.y
val smallestDimension = if (width < height) width else height
// val qrInput = findViewById(R.id.qrInput) as EditText
//setting parameters for qr code
val charset = "UTF-8" // or "ISO-8859-1"
val hintMap: MutableMap<EncodeHintType, ErrorCorrectionLevel> = HashMap()
hintMap[EncodeHintType.ERROR_CORRECTION] = ErrorCorrectionLevel.L
if (valuecatchedvalues!=null) {
createQRCodeText(
valuecatchedvalues,
charset,
hintMap,
smallestDimension,
smallestDimension
) //MAIN METHOD FOR QR GENERATE
}
} catch (ex: Exception) {
Log.e("QrGenerate", ex.message)
}
buttonshare.setOnClickListener(View.OnClickListener {
val sharingIntent = Intent(Intent.ACTION_SEND)
sharingIntent.type = "text/plain"
sharingIntent.putExtra(Intent.EXTRA_SUBJECT, "Subject Here")
sharingIntent.putExtra(Intent.EXTRA_TEXT, bmp)
startActivity(
Intent.createChooser(
sharingIntent,
resources.getString(R.string.app_name)
)
)
})
}
private fun createQRCodeText(
valueqrTextData: String,
charset: String,
hintMap: MutableMap<EncodeHintType, ErrorCorrectionLevel>,
smallestDimension: Int,
smallestDimension1: Int
) {
try {
//generating qr code in bitmatrix type
val matrix = MultiFormatWriter().encode(
String(
valueqrTextData.toByteArray(charset(charset)), kotlin.text.charset(charset)
), BarcodeFormat.QR_CODE, smallestDimension, smallestDimension1, hintMap
)
//converting bitmatrix to bitmap
val width = matrix.width
val height = matrix.height
val pixels = IntArray(width * height)
// All are 0, or black, by default
for (y in 0 until height) {
val offset = y * width
for (x in 0 until width) {
pixels[offset + x] = if (matrix[x, y]) Color.BLACK else Color.WHITE
}
}
val bitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888)
bitmap.setPixels(pixels, 0, width, 0, 0, width, height)
//setting bitmap to image view
val myImage = findViewById(R.id.imageView1) as ImageView
myImage.setImageBitmap(bitmap)
} catch (er: java.lang.Exception) {
Log.e("QrGenerate", er.message)
}
}
}
The following snippet is related to extracting text from previous Activity and generating QR in the receiving Activity.
val byteArray = intent.getByteArrayExtra("logoimage")
val bmp = BitmapFactory.decodeByteArray(byteArray, 0, byteArray.size)
img_logoimage.setImageBitmap(bmp)
val valuecatchedvalues: String = intent.getStringExtra("sendedvalues")
tv_textforqr.setText(valuecatchedvalues)
val tv_textforqr: String = intent.getStringExtra("logotext")
tv_textforlogoname.setText(tv_textforqr)
There is the only issue I am Facing here. I have QR image generated but as I try to share and save it it seems impossible for me.
Here is the Message after Share intent
I have searched this link for guidance Link 1
You've passed the wrong argument value for EXTRA_TEXT key. Instead of bmp you should use valuecatchedvalues:
sharingIntent.putExtra(Intent.EXTRA_TEXT, valuecatchedvalues)
Why?
Because this is the value you create your QR code from;
bmp is of type Bitmap. Bitmap cannot be processed when the value for EXTRA_TEXT key is extracted because String is expected.