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.
Related
Heyho,
I'm quiet new to Kotlin, but I came across this problem:
I have a library function, that generates an image(QR-Code). Now I'd like to display that image... But I've no idea how. The documentation only explains how to save the image locally. But I'm not really interested in saving it. So I can either get the image as a FileStream or as a ByteArray. Any possibility to display any of these as an Image in the UI?
An Example:
#Composable
fun QrCode(stand: String) {
Text(text = "QR-Code:", fontSize = 16.sp)
//? Image(QRCode(stand).render().getBytes()) // this obviously won't work
}
Any ideas?
In order to display an Image, you can refer this.
#Composable
fun BitmapImage(bitmap: Bitmap) {
Image(
bitmap = bitmap.asImageBitmap(),
contentDescription = "some useful description",
)
}
So the remaining is to find a way to convert your target input into a Bitmap.
If you have ByteArray of the image file, you can refer to this.
fun convertImageByteArrayToBitmap(imageData: ByteArray): Bitmap {
return BitmapFactory.decodeByteArray(imageData, 0, imageData.size)
}
If you just possess the QR String, you can refer this to convert QR String to a Bitmap.
fun encodeAsBitmap(source: String, width: Int, height: Int): Bitmap? {
val result: BitMatrix = try {
MultiFormatWriter().encode(source, BarcodeFormat.QR_CODE, width, height, null)
} catch (e: Exception) {
return null
}
val w = result.width
val h = result.height
val pixels = IntArray(w * h)
for (y in 0 until h) {
val offset = y * w
for (x in 0 until w) {
pixels[offset + x] = if (result[x, y]) Color.BLACK else Color.WHITE
}
}
val bitmap = Bitmap.createBitmap(w, h, Bitmap.Config.ARGB_8888)
bitmap.setPixels(pixels, 0, width, 0, 0, w, h)
return bitmap
}
And you need to add this dependency implementation 'com.google.zxing:core:3.3.1' when you decide to use the above code.
I was doing the simple feature that I am taking the photo with Camerax and uploading the photo to Imagga to do the face detection by using this API, and here is the API result [Face(confidence=100.0, coordinates=Coordinates(height=1243, width=1243, xmax=2660, xmin=1417, ymax=3359, ymin=2116), face_id=)]
And base on the API document, the Coordinates are parameters that I needed to input to drawRect(xmin, ymin, xmax, ymax, paint), but the rectangle is always in the wrong position on the screen.
I've tried total 64 combinations of those four points but non is correct.
What have I done wrong? here's my code
take and upload photo to Imagga:
override fun onImageSaved(output: ImageCapture.OutputFileResults) {
Log.e(TAG, "onImageSaved: " + output.savedUri)
savedUri = output.savedUri
val file = File(getPath(savedUri, applicationContext) ?: "")
val mFile = RequestBody.create(MediaType.parse("image/*"), file)
val fileToUpload = MultipartBody.Part.createFormData("image", file.name, mFile)
photoJob?.cancel()
photoJob = lifecycleScope.launch {
binding.isLoading = true
mainViewMode.startFaceDetection(fileToUpload)
}
}
fun getPath(contentUri: Uri?, context: Context): String? {
var res: String? = null
val proj = arrayOf(MediaStore.Images.Media.DATA)
val cursor: Cursor = context.contentResolver.query(contentUri!!, proj, null, null, null)!!
if (cursor.moveToFirst()) {
val index = cursor.getColumnIndexOrThrow(MediaStore.Images.Media.DATA)
res = cursor.getString(index)
}
cursor.close()
return res
}
receive face detection results from Imagga:
mainViewMode.apply {
faceResult.observe(this#MainActivity) {
showDetectionResult(it, savedUri)
}
}
private fun showDetectionResult(it: FaceDetectionResp, savedUri: Uri?) {
Log.e(TAG, "startFaceDetection: ${it.result.faces}")
val originBitmap = MediaStore.Images.Media.getBitmap(contentResolver, savedUri)
val bmp = originBitmap!!.copy(originBitmap.config, true)
val canvas = Canvas(bmp)
val paint = Paint().apply {
color = Color.GREEN
style = Paint.Style.STROKE
strokeWidth = 5f
}
val left = it.result.faces[0].coordinates.xmin.toFloat()
val top = it.result.faces[0].coordinates.ymin.toFloat()
val right = it.result.faces[0].coordinates.xmax.toFloat()
val bottom = it.result.faces[0].coordinates.ymax.toFloat()
// it did draw the rectangle but never in the right position that I expected
canvas.drawRect(left, top, right, bottom, paint)
//because I got landscape photo, so I rotate the bitmap to portrait
binding.ivFace.setImageBitmap(rotateImage(bmp, -90f))
}
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
)
}
I've been stuck in this for a week, can anyone help me?
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 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?
I'm using CameraX's Analyzer use case with the MLKit's BarcodeScanner. I would like to crop portion of the image received from the camera, before passing it to the scanner.
What I'm doing right now is I convert ImageProxy (that I recieve in the Analyzer) to a Bitmap, crop it and then pass it to the BarcodeScanner. The downside is that it's not a very fast and efficient process.
I've also noticed the warning I get in the Logcat when running this code:
ML Kit has detected that you seem to pass camera frames to the
detector as a Bitmap object. This is inefficient. Please use
YUV_420_888 format for camera2 API or NV21 format for (legacy) camera
API and directly pass down the byte array to ML Kit.
It would be nice to not to do ImageProxy conversion, but how do I crop the rectangle I want to analyze?
What I've already tried is to set a cropRect field of the Image (imageProxy.image.cropRect) class, but it doesn't seem to affect the end result.
Yes, it's true that if you use ViewPort and set viewport to yours UseCases(imageCapture or imageAnalysis as here https://developer.android.com/training/camerax/configuration) you can get only information about crop rectangle especially if you use ImageAnalysis(because if you use imageCapture, for on-disk the image is cropped before saving and it doesn't work for ImageAnalysis and if you use imageCapture without saving on disk) and here solution how I solved this problem:
First of all set view port for use cases as here: https://developer.android.com/training/camerax/configuration
Get cropped bitmap to analyze
override fun analyze(imageProxy: ImageProxy) {
val mediaImage = imageProxy.image
if (mediaImage != null && mediaImage.format == ImageFormat.YUV_420_888) {
croppedBitmap(mediaImage, imageProxy.cropRect).let { bitmap ->
requestDetectInImage(InputImage.fromBitmap(bitmap, rotation))
.addOnCompleteListener { imageProxy.close() }
}
} else {
imageProxy.close()
}
}
private fun croppedBitmap(mediaImage: Image, cropRect: Rect): Bitmap {
val yBuffer = mediaImage.planes[0].buffer // Y
val vuBuffer = mediaImage.planes[2].buffer // VU
val ySize = yBuffer.remaining()
val vuSize = vuBuffer.remaining()
val nv21 = ByteArray(ySize + vuSize)
yBuffer.get(nv21, 0, ySize)
vuBuffer.get(nv21, ySize, vuSize)
val yuvImage = YuvImage(nv21, ImageFormat.NV21, mediaImage.width, mediaImage.height, null)
val outputStream = ByteArrayOutputStream()
yuvImage.compressToJpeg(cropRect, 100, outputStream)
val imageBytes = outputStream.toByteArray()
return BitmapFactory.decodeByteArray(imageBytes, 0, imageBytes.size)
}
Possibly there is a loss in conversion speed, but on my devices I did not notice the difference. I set 100 quality in method compressToJpeg, but mb if set less quality it can improve speed, it need test.
upd: May 02 '21 :
I found another way without convert to jpeg and then to bitmap. This should be a faster way.
Set viewport as previous.
Convert YUV_420_888 to NV21, then crop and analyze.
override fun analyze(imageProxy: ImageProxy) {
val mediaImage = imageProxy.image
if (mediaImage != null && mediaImage.format == ImageFormat.YUV_420_888) {
croppedNV21(mediaImage, imageProxy.cropRect).let { byteArray ->
requestDetectInImage(
InputImage.fromByteArray(
byteArray,
imageProxy.cropRect.width(),
imageProxy.cropRect.height(),
rotation,
IMAGE_FORMAT_NV21,
)
)
.addOnCompleteListener { imageProxy.close() }
}
} else {
imageProxy.close()
}
}
private fun croppedNV21(mediaImage: Image, cropRect: Rect): ByteArray {
val yBuffer = mediaImage.planes[0].buffer // Y
val vuBuffer = mediaImage.planes[2].buffer // VU
val ySize = yBuffer.remaining()
val vuSize = vuBuffer.remaining()
val nv21 = ByteArray(ySize + vuSize)
yBuffer.get(nv21, 0, ySize)
vuBuffer.get(nv21, ySize, vuSize)
return cropByteArray(nv21, mediaImage.width, cropRect)
}
private fun cropByteArray(array: ByteArray, imageWidth: Int, cropRect: Rect): ByteArray {
val croppedArray = ByteArray(cropRect.width() * cropRect.height())
var i = 0
array.forEachIndexed { index, byte ->
val x = index % imageWidth
val y = index / imageWidth
if (cropRect.left <= x && x < cropRect.right && cropRect.top <= y && y < cropRect.bottom) {
croppedArray[i] = byte
i++
}
}
return croppedArray
}
First crop fun I took from here: Android: How to crop images using CameraX?
And I found also another crop fun, it seems that it is more complicated:
private fun cropByteArray(src: ByteArray, width: Int, height: Int, cropRect: Rect, ): ByteArray {
val x = cropRect.left * 2 / 2
val y = cropRect.top * 2 / 2
val w = cropRect.width() * 2 / 2
val h = cropRect.height() * 2 / 2
val yUnit = w * h
val uv = yUnit / 2
val nData = ByteArray(yUnit + uv)
val uvIndexDst = w * h - y / 2 * w
val uvIndexSrc = width * height + x
var srcPos0 = y * width
var destPos0 = 0
var uvSrcPos0 = uvIndexSrc
var uvDestPos0 = uvIndexDst
for (i in y until y + h) {
System.arraycopy(src, srcPos0 + x, nData, destPos0, w) //y memory block copy
srcPos0 += width
destPos0 += w
if (i and 1 == 0) {
System.arraycopy(src, uvSrcPos0, nData, uvDestPos0, w) //uv memory block copy
uvSrcPos0 += width
uvDestPos0 += w
}
}
return nData
}
Second crop fun I took from here:
https://www.programmersought.com/article/75461140907/
I would be glad if someone can help improve the code.
I'm still improving the way to do it. But this will work for me now
CameraX crop image before sending to analyze
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:paddingBottom="#dimen/_40sdp">
<androidx.camera.view.PreviewView
android:id="#+id/previewView"
android:layout_width="match_parent"
android:layout_height="0dp"
app:layout_constraintDimensionRatio="1:1"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" /></androidx.constraintlayout.widget.ConstraintLayout>
Cropping an image into 1:1 before passing it to analyze
override fun onCaptureSuccess(image: ImageProxy) {
super.onCaptureSuccess(image)
var bitmap: Bitmap = imageProxyToBitmap(image)
val dimension: Int = min(bitmap.width, bitmap.height)
bitmap = ThumbnailUtils.extractThumbnail(bitmap, dimension, dimension)
imageView.setImageBitmap(bitmap) //Here you can pass the crop[from the center] image to analyze
image.close()
}
**Function for converting into bitmap **
private fun imageProxyToBitmap(image: ImageProxy): Bitmap {
val buffer: ByteBuffer = image.planes[0].buffer
val bytes = ByteArray(buffer.remaining())
buffer.get(bytes)
return BitmapFactory.decodeByteArray(bytes, 0, bytes.size)
}
You would use ImageProxy.SetCroprect to get the rect and then use CropRect to set it.
For example if you had imageProxy, you would do : ImageProxy.setCropRect(Rect) and then you would do ImageProxy.CropRect.