Can't Repro
Status Update
Comments
le...@google.com <le...@google.com> #2
After some googling I found https://github.com/android/camera-samples/blob/master/CameraXBasic/app/src/main/java/com/android/example/cameraxbasic/utils/AutoFitPreviewBuilder.kt which does what I want. Would it be possible to fold this into the main library or at least make it configurable without the need for that class?
pr...@gmail.com <pr...@gmail.com> #3
Hi Charcoal,
Are we getting a crop mode here? Thanks!
Are we getting a crop mode here? Thanks!
wu...@google.com <wu...@google.com>
we...@google.com <we...@google.com> #4
Hello,
Thanks for raising this up. May we know did you set aspect ratio or resolution for Preview use case? And could you also attach the captured image file?
Thanks for raising this up. May we know did you set aspect ratio or resolution for Preview use case? And could you also attach the captured image file?
er...@google.com <er...@google.com> #5
The captured image file is part of the image file that I've already attached (the right part).
I did not set anything on the Preview use case other than the lens facing.
```
val cameraPreview = Preview(Builder()
.setLensFacing(CameraX.LensFacing.BACK)
.build())
cameraPreview.setOnPreviewOutputUpdateListener {
// Every time the viewfinder is updated, recompute the layout. By removing and re-adding it.
val parent = parent as ViewGroup
parent.removeView(this)
parent.addView(this, 0)
surfaceTexture = it.surfaceTexture
updateTransform()
}
CameraX.bindToLifecycle(lifecycleOwner, cameraPreview, *useCase)
```
I did not set anything on the Preview use case other than the lens facing.
```
val cameraPreview = Preview(Builder()
.setLensFacing(CameraX.LensFacing.BACK)
.build())
cameraPreview.setOnPreviewOutputUpdateListener {
// Every time the viewfinder is updated, recompute the layout. By removing and re-adding it.
val parent = parent as ViewGroup
parent.removeView(this)
parent.addView(this, 0)
surfaceTexture = it.surfaceTexture
updateTransform()
}
CameraX.bindToLifecycle(lifecycleOwner, cameraPreview, *useCase)
```
wu...@google.com <wu...@google.com> #6
Would you also set the same TargetRotation to Preview at onOrientationChanged? Let me know if it works.
Description
While capturing image i am getting below exception:-
Fri 2020/04/17 11:53:03 IST : androidx.camera.core.ImageCaptureException: Capture request failed with reason ERROR
at androidx.camera.core.ImageCapture$ImageCaptureRequest.lambda$notifyCallbackError$1$ImageCapture$ImageCaptureRequest(ImageCapture.java:1963)
at androidx.camera.core.-$$Lambda$ImageCapture$ImageCaptureRequest$1G7WSvt8TANxhZtOyewefm68pg4.run(Unknown Source:8)
at android.os.Handler.handleCallback(Handler.java:883)
at android.os.Handler.dispatchMessage(Handler.java:100)
at android.os.Looper.loop(Looper.java:237)
at android.app.ActivityThread.main(ActivityThread.java:7814)
at java.lang.reflect.Method.invoke(Native Method)
at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:493)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:1068)
Caused by: androidx.camera.core.ImageCapture$CaptureFailedException: Capture request failed with reason ERROR
at androidx.camera.core.ImageCapture$8.onCaptureFailed(ImageCapture.java:1240)
at androidx.camera.camera2.internal.CaptureCallbackAdapter.onCaptureFailed(CaptureCallbackAdapter.java:64)
at androidx.camera.camera2.internal.CameraBurstCaptureCallback.onCaptureFailed(CameraBurstCaptureCallback.java:70)
at android.hardware.camera2.impl.CameraCaptureSessionImpl$1.lambda$onCaptureFailed$4$CameraCaptureSessionImpl$1(CameraCaptureSessionImpl.java:654)
at android.hardware.camera2.impl.-$$Lambda$CameraCaptureSessionImpl$1$VsKq1alEqL3XH-hLTWXgi7fSF3s.run(Unknown Source:8)
at android.os.Handler.handleCallback(Handler.java:883)
at android.os.Handler.dispatchMessage(Handler.java:100)
at android.os.Looper.loop(Looper.java:237)
at android.os.HandlerThread.run(HandlerThread.java:67)
CAMERAX VERSION :- 1.0.0-beta03
camera-view:1.0.0-alpha10
CAMERA APPLICATION NAME AND VERSION: Camera and 10.0.01.65
ANDROID OS BUILD NUMBER: QP1A-190711.020.M307FXXU2BTC6
DEVICE NAME: SM-M307F
DESCRIPTION: Unable to capture the image
STEPS TO REPRODUCE:
1. click on camera and try to capture the image , it gives below error
Fri 2020/04/17 11:53:03 IST : androidx.camera.core.ImageCaptureException: Capture request failed with reason ERROR
at androidx.camera.core.ImageCapture$ImageCaptureRequest.lambda$notifyCallbackError$1$ImageCapture$ImageCaptureRequest(ImageCapture.java:1963)
at androidx.camera.core.-$$Lambda$ImageCapture$ImageCaptureRequest$1G7WSvt8TANxhZtOyewefm68pg4.run(Unknown Source:8)
at android.os.Handler.handleCallback(Handler.java:883)
at android.os.Handler.dispatchMessage(Handler.java:100)
at android.os.Looper.loop(Looper.java:237)
at android.app.ActivityThread.main(ActivityThread.java:7814)
at java.lang.reflect.Method.invoke(Native Method)
at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:493)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:1068)
Caused by: androidx.camera.core.ImageCapture$CaptureFailedException: Capture request failed with reason ERROR
at androidx.camera.core.ImageCapture$8.onCaptureFailed(ImageCapture.java:1240)
at androidx.camera.camera2.internal.CaptureCallbackAdapter.onCaptureFailed(CaptureCallbackAdapter.java:64)
at androidx.camera.camera2.internal.CameraBurstCaptureCallback.onCaptureFailed(CameraBurstCaptureCallback.java:70)
at android.hardware.camera2.impl.CameraCaptureSessionImpl$1.lambda$onCaptureFailed$4$CameraCaptureSessionImpl$1(CameraCaptureSessionImpl.java:654)
at android.hardware.camera2.impl.-$$Lambda$CameraCaptureSessionImpl$1$VsKq1alEqL3XH-hLTWXgi7fSF3s.run(Unknown Source:8)
at android.os.Handler.handleCallback(Handler.java:883)
at android.os.Handler.dispatchMessage(Handler.java:100)
at android.os.Looper.loop(Looper.java:237)
at android.os.HandlerThread.run(HandlerThread.java:67)
Expected Result: It should give the image URI
REPRODUCIBILITY: (5 of 5)
ADDITIONAL INFORMATION:
CODE FRAGMENTS (this will help us troubleshoot your issues):
package com.opensea.alliswellsecurity.activity
import android.annotation.SuppressLint
import android.app.Activity
import android.content.Context
import android.content.Intent
import android.content.res.Configuration
import android.graphics.*
import android.graphics.drawable.ColorDrawable
import android.hardware.display.DisplayManager
import android.media.ExifInterface
import android.media.Image
import android.net.Uri
import android.os.Build
import android.os.Bundle
import android.os.Environment
import android.util.DisplayMetrics
import android.util.Log
import android.view.ScaleGestureDetector
import android.view.View
import android.widget.ImageView
import androidx.camera.core.*
import androidx.camera.core.Camera
import androidx.camera.lifecycle.ProcessCameraProvider
import androidx.camera.view.PreviewView
import androidx.core.content.ContextCompat
import com.google.common.util.concurrent.ListenableFuture
import com.opensea.alliswellsecurity.R
import com.opensea.alliswellsecurity.base.AllIsWellBaseActivity
import com.opensea.alliswellsecurity.helpers.CommonMethods
import com.opensea.alliswellsecurity.helpers.IMAGE_PATH
import com.opensea.alliswellsecurity.helpers.ImageUtils
import com.squareup.picasso.Picasso
import java.io.*
import java.nio.ByteBuffer
import java.util.*
import java.util.concurrent.ExecutorService
import java.util.concurrent.Executors
import kotlin.collections.ArrayList
import kotlin.math.abs
import kotlin.math.log
import kotlin.math.max
import kotlin.math.min
private const val RATIO_4_3_VALUE = 4.0 / 3.0
private const val RATIO_16_9_VALUE = 16.0 / 9.0
const val ANIMATION_FAST_MILLIS = 50L
const val ANIMATION_SLOW_MILLIS = 100L
/** Helper type alias used for analysis use case callbacks */
typealias LumaListener = (luma: Double) -> Unit
class CameraActivity : AllIsWellBaseActivity() {
private lateinit var container: android.widget.RelativeLayout
private var savedUri:Uri? = null
private val TAG: String = "CameraActivity"
private lateinit var view_camera: PreviewView
private lateinit var captureButton:ImageView
private lateinit var switchcameraButton:ImageView
private lateinit var image_container: ImageView
private lateinit var image_done:ImageView
private lateinit var image_cancel:ImageView
private lateinit var outputDirectory: File
private var displayId: Int = -1
private var lensFacing: Int = CameraSelector.LENS_FACING_BACK
private var preview: Preview? = null
private var imageCapture: ImageCapture? = null
private var imageAnalyzer: ImageAnalysis? = null
private var camera: Camera? = null
private lateinit var cameraProviderFuture : ListenableFuture<ProcessCameraProvider>
//private lateinit var controls: View
private val displayManager by lazy {
getSystemService(Context.DISPLAY_SERVICE) as DisplayManager
}
/** Blocking camera operations are performed using this executor */
private lateinit var cameraExecutor: ExecutorService
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
try {
setContentView(R.layout.activity_camera)
ImageUtils().log(this.applicationContext, "request came")
container = (findViewById<View>(R.id.root) as android.widget.RelativeLayout)
view_camera = container.findViewById(R.id.view_camera)
captureButton = container.findViewById<ImageView>(R.id.captureButton)
switchcameraButton = container.findViewById<ImageView>(R.id.switchCameraButton)
image_container = container.findViewById(R.id.camera_ui_container)
image_done = container.findViewById(R.id.doneButton)
image_cancel = container.findViewById(R.id.backButton)
// Initialize our background executor
cameraExecutor = Executors.newSingleThreadExecutor()
// Every time the orientation of device changes, update rotation for use cases
displayManager.registerDisplayListener(displayListener, null)
// Determine the output directory
outputDirectory = getExternalFilesDir(Environment.DIRECTORY_PICTURES)
// Wait for the views to be properly laid out
// Keep track of the display in which this view is attached
displayId = view_camera.display.displayId
// Build UI controls
updateCameraUi()
ImageUtils().log(this.applicationContext, " oncreate updateCameraUi()..success")
// Bind use cases
bindCameraUseCases()
ImageUtils().log(this.applicationContext, "oncreate bindCameraUseCases()..success")
}
}catch (exception:Exception)
{
ImageUtils().log(this.applicationContext,"issue in oncreate"+exception.stackTrace)
}
}
override fun onDestroy() {
super.onDestroy()
cameraExecutor.shutdown()
displayManager.unregisterDisplayListener(displayListener)
}
/**
* Inflate camera controls and update the UI manually upon config changes to avoid removing
* and re-adding the view finder from the view hierarchy; this provides a seamless rotation
* transition on devices that support it.
*
* NOTE: The flag is supported starting in Android 8 but there still is a small flash on the
* screen for devices that run Android 9 or below.
*/
override fun onConfigurationChanged(newConfig: Configuration) {
super.onConfigurationChanged(newConfig)
updateCameraUi()
}
/**
* We need a display listener for orientation changes that do not trigger a configuration
* change, for example if we choose to override config change in manifest or for 180-degree
* orientation changes.
*/
private val displayListener = object : DisplayManager.DisplayListener {
override fun onDisplayAdded(displayId: Int) = Unit
override fun onDisplayRemoved(displayId: Int) = Unit
override fun onDisplayChanged(displayId: Int) = container?.let { view ->
if (displayId == displayId) {
Log.d(TAG, "Rotation changed: ${view.display.rotation}")
imageCapture?.targetRotation = view.display.rotation
imageAnalyzer?.targetRotation = view.display.rotation
}
} ?: Unit
}
/** Declare and bind preview, capture and analysis use cases */
private fun bindCameraUseCases() {
try {
// Get screen metrics used to setup camera for full screen resolution
val metrics = DisplayMetrics().also { view_camera.display.getRealMetrics(it) }
Log.d(TAG, "Screen metrics: ${metrics.widthPixels} x ${metrics.heightPixels}")
val screenAspectRatio = aspectRatio(metrics.widthPixels, metrics.heightPixels)
Log.d(TAG, "Preview aspect ratio: $screenAspectRatio")
val rotation = view_camera.display.rotation
// Bind the CameraProvider to the LifeCycleOwner
val cameraSelector = CameraSelector.Builder().requireLensFacing(lensFacing).build()
cameraProviderFuture = ProcessCameraProvider.getInstance(this)
cameraProviderFuture.addListener(Runnable {
// CameraProvider
val cameraProvider: ProcessCameraProvider = cameraProviderFuture.get()
// Preview
preview = Preview.Builder()
// We request aspect ratio but no resolution
.setTargetAspectRatio(screenAspectRatio)
// Set initial target rotation
.setTargetRotation(rotation)
.build()
// Attach the viewfinder's surface provider to preview use case
// preview?.setSurfaceProvider(view_camera.previewSurfaceProvider)
// ImageCapture
imageCapture = ImageCapture.Builder()
.setCaptureMode(ImageCapture.CAPTURE_MODE_MINIMIZE_LATENCY)
.setFlashMode(ImageCapture.FLASH_MODE_AUTO)
// We request aspect ratio but no resolution to match preview config, but letting
// CameraX optimize for whatever specific resolution best fits our use cases
.setTargetAspectRatio(screenAspectRatio)
// Set initial target rotation, we will have to call this again if rotation changes
// during the lifecycle of this use case
.setTargetRotation(rotation)
.build()
// ImageAnalysis
/* imageAnalyzer = ImageAnalysis.Builder()
// We request aspect ratio but no resolution
.setTargetAspectRatio(screenAspectRatio)
// .setTargetResolution(Size(1280,720))
// Set initial target rotation, we will have to call this again if rotation changes
// during the lifecycle of this use case
.setTargetRotation(rotation)
.build()
// The analyzer can then be assigned to the instance
.also {
it.setAnalyzer(cameraExecutor, LuminosityAnalyzer { luma ->
// Values returned from our analyzer are passed to the attached listener
// We log image analysis results here - you should do something useful
// instead!
Log.d(TAG, "Average luminosity: $luma")
})
}
*/
// Must unbind the use-cases before rebinding them
cameraProvider.unbindAll()
ImageUtils().log(this@CameraActivity, "cameraProvider.unbindAll called ")
try {
// A variable number of use-cases can be passed here -
// camera provides access to CameraControl & CameraInfo
camera =
cameraProvider.bindToLifecycle(this, cameraSelector, preview, imageCapture)
ImageUtils().log(
this@CameraActivity,
"cameraProvider bind to lifecycle called "
)
preview?.setSurfaceProvider(view_camera.createSurfaceProvider(camera?.cameraInfo))
} catch (exception: Exception) {
Log.e(TAG, "Use case binding failed", exception)
logStacktrace(exception,"bindCameraUseCases()")
}
setUpPinchToZoom()
}, ContextCompat.getMainExecutor(this))
}catch (exception:java.lang.Exception)
{
ImageUtils().log(
this@CameraActivity,
"Use case binding issue " + exception.stackTrace
)
}
}
/**
* [androidx.camera.core.ImageAnalysisConfig] requires enum value of
* [androidx.camera.core.AspectRatio]. Currently it has values of 4:3 & 16:9.
*
* Detecting the most suitable ratio for dimensions provided in @params by counting absolute
* of preview ratio to one of the provided values.
*
* @param width - preview width
* @param height - preview height
* @return suitable aspect ratio
*/
private fun aspectRatio(width: Int, height: Int): Int {
val previewRatio = max(width, height).toDouble() / min(width, height)
if (abs(previewRatio - RATIO_4_3_VALUE) <= abs(previewRatio - RATIO_16_9_VALUE)) {
return AspectRatio.RATIO_4_3
}
return AspectRatio.RATIO_16_9
//return AspectRatio.RATIO_4_3
}
/** Method used to re-draw the camera UI controls, called every time configuration changes. */
private fun updateCameraUi() {
try {
// Remove previous UI if any
container.findViewById<android.widget.RelativeLayout>(R.id.root)?.let {
container.removeView(it)
}
// Inflate a new view containing all UI for controlling the camera
// controls = View.inflate(this, R.layout.activity_camera, container)
disableLayout(false)
ImageUtils().log(this, "show camera container ui")
// In the background, load latest photo taken (if any) for gallery thumbnail
/*lifecycleScope.launch(Dispatchers.IO) {
outputDirectory.listFiles { file ->
EXTENSION_WHITELIST.contains(file.extension.toUpperCase(Locale.ROOT))
}?.max()?.let {
//setGalleryThumbnail(Uri.fromFile(it))
}
}
*/
// Listener for button used to capture photo
captureButton.setOnClickListener {
ImageUtils().log(this, "capture button clicked")
disableLayout(true)
// Get a stable reference of the modifiable image capture use case
imageCapture?.let { imageCapture ->
ImageUtils().log(this, "create image file called")
// Create output file to hold the image
val photoFile = createImageFile()
ImageUtils().log(
this,
"create image file creation success" + photoFile.length()
)
// Setup image capture metadata
val metadata = ImageCapture.Metadata().apply {
// Mirror image when using the front camera
isReversedHorizontal = lensFacing == CameraSelector.LENS_FACING_FRONT
}
// Create output options object which contains file + metadata
val outputOptions = ImageCapture.OutputFileOptions
.Builder(photoFile)
//.setMetadata(metadata)
.build()
// Setup image capture listener which is triggered after photo has been taken
imageCapture.takePicture(
outputOptions, cameraExecutor, object : ImageCapture.OnImageSavedCallback {
override fun onError(exc: ImageCaptureException) {
Log.e(TAG, "Photo capture failed: ${exc.message}", exc)
logStacktrace(exc,"imageCapture.takePicture()")
}
override fun onImageSaved(output: ImageCapture.OutputFileResults) {
savedUri = output.savedUri ?: Uri.fromFile(photoFile)
ImageUtils().log(this@CameraActivity,"successfully created image"+photoFile.length())
compressImage(savedUri?.path.toString())
runOnUiThread {
try {
disableLayout(true)
Picasso.get().load(photoFile).into(image_container)
}catch (exception:Exception)
{
logStacktrace(exception,"onImageSaved()")
}
}
}
})
/*imageCapture.takePicture(cameraExecutor,
object : ImageCapture.OnImageCapturedCallback() {
@SuppressLint("UnsafeExperimentalUsageError")
override fun onCaptureSuccess(image: ImageProxy) {
image.image?.let {
val rotationDegrees = image?.imageInfo.rotationDegrees
ImageUtils().log(
this@CameraActivity,
"image capture success calling bitmap now" + rotationDegrees
)
val bitmap = it.toBitmap(rotationDegrees)
ImageUtils().log(
this@CameraActivity,
"bitmap call successfull " + bitmap.width
)
ImageUtils().saveBitmap(bitmap, photoFile.absolutePath)
ImageUtils().log(
this@CameraActivity,
"bitmap save successfull " + photoFile.length()
)
try {
if (bitmap != null && !bitmap.isRecycled)
bitmap.recycle()
}
catch (exception:Exception)
{
ImageUtils().log(this@CameraActivity,"final bitmap recycled")
}
runOnUiThread {
disableLayout(true)
savedUri = Uri.fromFile(photoFile)
Picasso.get().load(savedUri).into(image_container)
}
super.onCaptureSuccess(image)
}
}
override fun onError(exception: ImageCaptureException) {
ImageUtils().log(
this@CameraActivity,
"error in take picture" + exception.localizedMessage
)
super.onError(exception)
}
})
*/
// We can only change the foreground Drawable using API level 23+ API
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
// Display flash animation to indicate that photo was captured
container.postDelayed({
container.foreground = ColorDrawable(Color.WHITE)
container.postDelayed(
{ container.foreground = null }, ANIMATION_FAST_MILLIS
)
}, ANIMATION_SLOW_MILLIS)
}
}
}
// Listener for button used to switch cameras
switchcameraButton.setOnClickListener {
lensFacing = if (CameraSelector.LENS_FACING_FRONT == lensFacing) {
CameraSelector.LENS_FACING_BACK
} else {
CameraSelector.LENS_FACING_FRONT
}
// Re-bind use cases to update selected camera
bindCameraUseCases()
}
// Listener for button used to send the file uri to patrol activity
image_done.findViewById<ImageView>(R.id.doneButton).setOnClickListener {
// Only navigate when the gallery has photos
if (savedUri != null) {
val captureIntent = Intent()
captureIntent.putExtra(IMAGE_PATH, savedUri?.path.toString())
setResult(Activity.RESULT_OK, captureIntent)
finish()
}
}
// Listener for button used to cancel the current image and display camera again
image_cancel.findViewById<ImageView>(R.id.backButton).setOnClickListener {
//camera_ui_container.
disableLayout(false)
//bindCameraUseCases()
}
}catch (exce:Exception)
{
ImageUtils().log(
this@CameraActivity,
"Use case binding failed " + exce.stackTrace
)
}
}
override fun onBackPressed() {
super.onBackPressed()
setResult(Activity.RESULT_CANCELED)
finish()
}
private fun setUpPinchToZoom() {
val listener = object : ScaleGestureDetector.SimpleOnScaleGestureListener() {
override fun onScale(detector: ScaleGestureDetector): Boolean {
val currentZoomRatio: Float = camera?.cameraInfo?.zoomState?.value?.zoomRatio ?: 0F
val delta = detector.scaleFactor
camera?.cameraControl?.setZoomRatio(currentZoomRatio * delta)
return true
}
}
val scaleGestureDetector = ScaleGestureDetector(this, listener)
view_camera.setOnTouchListener { _, event ->
scaleGestureDetector.onTouchEvent(event)
return@setOnTouchListener true
}
}
fun disableLayout(hideCameraControl: Boolean)
{
// val savePicLayout:LinearLayout =container.findViewById(R.id.savepic)
// val capturePicLayout:LinearLayout = container.findViewById(R.id.capturepic)
try {
image_container.setImageBitmap(null)
image_container.setImageLevel(0)
if (hideCameraControl) {
//savePicLayout.visibility=View.VISIBLE
image_cancel.visibility = View.VISIBLE
image_done.visibility = View.VISIBLE
image_container.visibility = View.VISIBLE
captureButton.visibility = View.GONE
switchcameraButton.visibility = View.GONE
view_camera.visibility = View.GONE
} else {
// capturePicLayout.visibility=View.VISIBLE
captureButton.visibility = View.VISIBLE
switchcameraButton.visibility = View.VISIBLE
view_camera.visibility = View.VISIBLE
image_container.visibility = View.GONE
image_cancel.visibility = View.GONE
image_done.visibility = View.GONE
}
}catch (exception:Exception)
{
logStacktrace(exception,"disableLayout()")
}
}
private fun Image.toBitmap(rotationDegrees: Int): Bitmap {
val buffer = planes[0].buffer
val bytes = ByteArray(buffer.capacity())
buffer.get(bytes)
val bitmap = BitmapFactory.decodeByteArray(bytes, 0, bytes.size, null)
ImageUtils().log(this@CameraActivity,"bitmap created "+ bitmap.width + ":::"+ bitmap.height)
val matrix = Matrix()
if (bitmap.width > 3000)
matrix.postRotate(rotationDegrees.toFloat())
val mutableBitmap =
Bitmap.createBitmap(bitmap, 0, 0, bitmap.width, bitmap.height, matrix, true)
val bmOverlay = mutableBitmap.copy(mutableBitmap.config, true)
//add text
val text = CommonMethods.getISTString()
val cs = Canvas(bmOverlay)
val tPaint = Paint()
tPaint.textSize = (min(bmOverlay.width, bmOverlay.height) * 0.07).toFloat()
tPaint.color = Color.LTGRAY
tPaint.style = Paint.Style.FILL
cs.drawBitmap(bmOverlay, 0f, 0f, null)
val width = tPaint.measureText(text)
val x = ((bmOverlay.width - width) * 0.5f)
val y = bmOverlay.height - 30f
cs.drawText(
text,
x,
y,
tPaint
)
try {
if(bitmap!=null && !bitmap.isRecycled)
bitmap.recycle()
}catch (exception:Exception)
{
logStacktrace(exception,"bitmap")
}
//add logo
val canvas = Canvas(bmOverlay)
canvas.drawBitmap(bmOverlay, Matrix(), null)
val logo =
BitmapFactory.decodeResource(applicationContext.resources, R.mipmap.logo_watermark)
if (logo != null) {
val logoWidth: Int = Math.round(bmOverlay.width * .15f)
val croppedLogo = Bitmap.createScaledBitmap(logo, logoWidth, logoWidth, false)
canvas.drawBitmap(croppedLogo, (bmOverlay.width - croppedLogo.width - 10f), 70f, null)
try {
if(croppedLogo!=null && !croppedLogo.isRecycled)
croppedLogo.recycle()
}catch (exception:Exception)
{
logStacktrace(exception,"croppedLogo")
}
}
try {
if(mutableBitmap!=null && !mutableBitmap.isRecycled)
mutableBitmap.recycle()
}catch (exception:Exception)
{
logStacktrace(exception,"mutableBitmap")
}
try {
if(logo!=null && !logo.isRecycled)
logo.recycle()
}catch (exception:Exception)
{
logStacktrace(exception,"logo")
}
return bmOverlay
//return ImageUtils().addLogoOnImage(applicationContext, ImageUtils().addTextToImage(bitmap))
}
fun logStacktrace(exception: Exception,methodname:String)
{
val sw = StringWriter()
exception.printStackTrace(PrintWriter(sw))
ImageUtils().log(applicationContext,sw.toString())
}
fun logStacktrace(exception: OutOfMemoryError,methodname:String)
{
val sw = StringWriter()
exception.printStackTrace(PrintWriter(sw))
ImageUtils().log(applicationContext,sw.toString())
}
fun compressImage(imagePath:String): String {
ImageUtils().log(applicationContext,"request came to compress image"+imagePath)
/* if(File(imagePath).length()< 1000000)
return imagePath
*/
var scaledBitmap : Bitmap? = null
val options :BitmapFactory.Options = BitmapFactory.Options()
options.inJustDecodeBounds = true;
var bmp = BitmapFactory.decodeFile(imagePath, options);
var actualHeight = options.outHeight;
var actualWidth = options.outWidth;
// max Height and width values of the compressed image is taken as 816x612
val maxHeight = 860.0f
val maxWidth = 612.0f;
var imgRatio = (actualWidth / actualHeight).toFloat();
var maxRatio = maxWidth / maxHeight;
// width and height values are set maintaining the aspect ratio of the image
/*if (actualHeight > maxHeight || actualWidth > maxWidth) {
if (imgRatio < maxRatio){
imgRatio = maxHeight / actualHeight
actualWidth = (imgRatio * actualWidth).toInt()
actualHeight = maxHeight.toInt()
} else if (imgRatio > maxRatio) {
imgRatio = maxWidth / actualWidth
actualHeight = (imgRatio * actualHeight).toInt()
actualWidth = maxWidth.toInt()
} else {
actualHeight = maxHeight.toInt()
actualWidth = maxWidth.toInt()
}
}*/
// setting inSampleSize value allows to load a scaled down version of the original image
options.inSampleSize = calculateInSampleSize(options, actualWidth, actualHeight);
// inJustDecodeBounds set to false to load the actual bitmap
options.inJustDecodeBounds = false;
// this options allow android to claim the bitmap memory if it runs low on memory
options.inBitmap
options.inTempStorage = ByteArray(16 * 1024)
try {
// load the bitmap from its path
bmp = BitmapFactory.decodeFile(imagePath, options);
} catch (exception:OutOfMemoryError) {
logStacktrace(exception,"outofmemory error while creating initial bitmap")
}
try {
scaledBitmap = Bitmap.createBitmap(actualWidth, actualHeight,Bitmap.Config.ARGB_4444);
} catch (exception:OutOfMemoryError) {
logStacktrace(exception,"outofmemory error while creating scaledBitmap")
}
val ratioX = actualWidth / options.outWidth
val ratioY = actualHeight / options.outHeight
val middleX = actualWidth / 2.0f;
val middleY = actualHeight / 2.0f;
val scaleMatrix = Matrix();
scaleMatrix.setScale(ratioX.toFloat(), ratioY.toFloat(), middleX, middleY)
val canvas = Canvas(scaledBitmap)
canvas.setMatrix(scaleMatrix);
canvas.drawBitmap(bmp, middleX - bmp.getWidth() / 2, middleY - bmp.getHeight() / 2, Paint(Paint.FILTER_BITMAP_FLAG))
// check the rotation of the image and display it properly
try {
val exif = ExifInterface(imagePath);
val orientation = exif.getAttributeInt(ExifInterface.TAG_ORIENTATION, 0)
ImageUtils().log(applicationContext,"actual orientation"+orientation)
var matrix = Matrix()
if (orientation == 6) {
matrix.postRotate(90f);
ImageUtils().log(applicationContext,"orientation 6"+orientation)
} else if (orientation == 3) {
matrix.postRotate(180f);
ImageUtils().log(applicationContext,"orientation 6"+orientation)
} else if (orientation == 8) {
matrix.postRotate(270f);
ImageUtils().log(applicationContext,"orientation 8"+orientation)
}
scaledBitmap = Bitmap.createBitmap(scaledBitmap, 0, 0,scaledBitmap!!.width, scaledBitmap!!.height, matrix, false);
} catch (e:OutOfMemoryError) {
logStacktrace(e, "sacleBitmap outof memory")
}
try {
var out = FileOutputStream(imagePath);
// write the compressed bitmap at the destination specified by filename.
scaledBitmap?.compress(Bitmap.CompressFormat.JPEG, 80, out);
out.close()
} catch (e:FileNotFoundException)
{
logStacktrace(e,"file name not found while creating image")
}
try {
if (scaledBitmap != null)
scaledBitmap.recycle()
if (bmp != null)
bmp.recycle()
ImageUtils().log(applicationContext,"Successfully cleared bitmap")
}catch (exception:Exception)
{
logStacktrace(exception,"error while clearing bitmap")
}
return imagePath;
}
fun calculateInSampleSize(options: BitmapFactory.Options, reqWidth: Int, reqHeight : Int) :Int {
val height = options.outHeight;
val width = options.outWidth;
var inSampleSize = 1
if (height > reqHeight || width > reqWidth)
{
val heightRatio = Math.round(height.toFloat() / reqHeight.toFloat())
val widthRatio = Math.round(width.toFloat() / reqWidth.toFloat())
if(heightRatio < widthRatio)
{
inSampleSize = heightRatio
}else
{
inSampleSize = widthRatio
}
}
val totalPixels = width * height;
val totalReqPixelsCap = reqWidth * reqHeight * 2;
while (totalPixels / (inSampleSize * inSampleSize) > totalReqPixelsCap) {
inSampleSize++;
}
return inSampleSize;
}
}
/**
* Our custom image analysis class.
*
* <p>All we need to do is override the function `analyze` with our desired operations. Here,
* we compute the average luminosity of the image by looking at the Y plane of the YUV frame.
*/
private class LuminosityAnalyzer(listener: LumaListener? = null) : ImageAnalysis.Analyzer {
private val frameRateWindow = 8
private val frameTimestamps = ArrayDeque<Long>(5)
private val listeners = ArrayList<LumaListener>().apply { listener?.let { add(it) } }
private var lastAnalyzedTimestamp = 0L
var framesPerSecond: Double = -1.0
private set
/**
* Used to add listeners that will be called with each luma computed
*/
fun onFrameAnalyzed(listener: LumaListener) = listeners.add(listener)
/**
* Helper extension function used to extract a byte array from an image plane buffer
*/
private fun ByteBuffer.toByteArray(): ByteArray {
rewind() // Rewind the buffer to zero
val data = ByteArray(remaining())
get(data) // Copy the buffer into a byte array
return data // Return the byte array
}
/**
* Analyzes an image to produce a result.
*
* <p>The caller is responsible for ensuring this analysis method can be executed quickly
* enough to prevent stalls in the image acquisition pipeline. Otherwise, newly available
* images will not be acquired and analyzed.
*
* <p>The image passed to this method becomes invalid after this method returns. The caller
* should not store external references to this image, as these references will become
* invalid.
*
* @param image image being analyzed VERY IMPORTANT: Analyzer method implementation must
* call image.close() on received images when finished using them. Otherwise, new images
* may not be received or the camera may stall, depending on back pressure setting.
*
*/
override fun analyze(image: ImageProxy) {
// If there are no listeners attached, we don't need to perform analysis
if (listeners.isEmpty()) {
image.close()
return
}
// Keep track of frames analyzed
val currentTime = System.currentTimeMillis()
frameTimestamps.push(currentTime)
// Compute the FPS using a moving average
while (frameTimestamps.size >= frameRateWindow) frameTimestamps.removeLast()
val timestampFirst = frameTimestamps.peekFirst() ?: currentTime
val timestampLast = frameTimestamps.peekLast() ?: currentTime
framesPerSecond = 1.0 / ((timestampFirst - timestampLast) /
frameTimestamps.size.coerceAtLeast(1).toDouble()) * 1000.0
// Analysis could take an arbitrarily long amount of time
// Since we are running in a different thread, it won't stall other use cases
lastAnalyzedTimestamp = frameTimestamps.first
// Since format in ImageAnalysis is YUV, image.planes[0] contains the luminance plane
val buffer = image.planes[0].buffer
// Extract image data from callback object
val data = buffer.toByteArray()
// Convert the data into an array of pixel values ranging 0-255
val pixels = data.map { it.toInt() and 0xFF }
// Compute average luminance for the image
val luma = pixels.average()
// Call all listeners with new value
listeners.forEach { it(luma) }
image.close()
}
}