I am trying to make a fake horizontal ListView. For that, I want to rotate the content of each cell in the adapter. It almost works:
class ImageAdapter(ctx: Context, viewResourceId: Int, imageResourceId: Int, strings: Array[String], icons: TypedArray, colorSelected: Int, colorNotSelected: Int, colorBorder: Int) extends ArrayAdapter[String](ctx, viewResourceId, strings) {
// Useful variables
private val mInflater: LayoutInflater = ctx.getSystemService(
Context.LAYOUT_INFLATER_SERVICE).asInstanceOf[LayoutInflater]
private val mStrings: Array[String] = strings
private val mIcons: TypedArray = icons
private val mViewResourceId: Int = viewResourceId
private var mSelected : Int = 0
def setSelected(s: Int) = mSelected = s
// Inherited from ArrayAdapter
override def getCount():Int = mStrings.length
override def getItem(position: Int): String = mStrings(position)
override def getItemId(position: Int): Long = position
// The rotation machinery
var rotation = 90
private var matrix = new Matrix()
val rect1 = new RectF(0, 0, 0, 0)
val rect2 = new RectF(0, 0, 0, 0)
val cst = Matrix.ScaleToFit.CENTER
// Getting the view.
override def getView(position: Int, convertView: View, parent: ViewGroup): View = {
var result = if(convertView == null) mInflater.inflate(mViewResourceId, null) else convertView
var iv: ImageView = result.findViewById(imageResourceId).asInstanceOf[ImageView]
val drawable = mIcons.getDrawable(position)
iv.setScaleType(ScaleType.MATRIX)
matrix.reset()
val height = drawable.getIntrinsicHeight()
val width = drawable.getIntrinsicWidth()
rotation match {
case 0 =>
rect1.set(0, 0, width, height)
rect2.set(0, 0, iv.getWidth(), iv.getHeight())
matrix.setRectToRect(rect1, rect2, cst)
case 90 =>
rect1.set(0, 0, width, height)
rect2.set(-iv.getHeight()/2, -iv.getWidth()/2, iv.getHeight()/2, iv.getWidth()/2)
matrix.setRectToRect(rect1, rect2, cst)
matrix.postRotate(rotation, 0, 0)
matrix.postTranslate(iv.getWidth()/2, iv.getHeight()/2)
...
case _ =>
}
iv.setImageMatrix(matrix)
iv.setImageDrawable(drawable)
iv.postInvalidate()
if(position == mSelected) {
result.setBackgroundColor(colorSelected)
} else {
result.setBackgroundColor(colorNotSelected)
}
result.setTag(mStrings(position))
result
}
}
The only problem is that it does not display the first picture. Instead, it displays this:
For the first list to appear, I need to click on one element of each list. The onclick method is the following:
listGraphismesView.setOnItemClickListener(new OnItemClickListener {
override def onItemClick(parent: AdapterView[_], view: View, position: Int, notUsedId: Long) = {
var graphismeName = view.getTag().asInstanceOf[String]
mAdapter.setSelected(position)
mAdapter.notifyDataSetChanged()
showGChallenge(position, graphismeName)
}
})
I tried then to put mAdapter.notifyDataSetChanged() in the onResume function of the fragment, but it does not update the list from the beginning. I still need to click on something for all images to appear.
Any idea why ?
I found out that iv.getWidth() and iv.getHeight() were both returning zero because the getview would be called by the onActivityCreated function. Putting notifyDataSetChanged() in the onResume function was too early too. So the drawable would be drawn with size 0, and reloaded when I could click on the button.
So to change that, I wrote the following piece of code, calling the resize function with a listener.
var iv: ImageView = result.findViewById(imageResourceId).asInstanceOf[ImageView]
val drawable = mIcons.getDrawable(position)
iv.setImageDrawable(drawable)
iv.setScaleType(ScaleType.MATRIX)
val vto = iv.getViewTreeObserver()
vto.addOnGlobalLayoutListener(new OnGlobalLayoutListener() {
private var matrix = new Matrix()
val rect1 = new RectF(0, 0, 0, 0)
val rect2 = new RectF(0, 0, 0, 0)
private var height = drawable.getIntrinsicHeight()
private var width = drawable.getIntrinsicWidth()
override def onGlobalLayout() {
val iv_width = iv.getWidth
val iv_height = iv.getHeight
matrix.reset()
rotation match {
case 0 =>
rect1.set(0, 0, width, height)
rect2.set(0, 0, iv_width, iv_height)
matrix.setRectToRect(rect1, rect2, cst)
case 90 =>
rect1.set(0, 0, width, height)
rect2.set(-iv_height/2, -iv_width/2, iv_height/2, iv_width/2)
matrix.setRectToRect(rect1, rect2, cst)
matrix.postRotate(rotation, 0, 0)
matrix.postTranslate(iv_width/2, iv_height/2)
...
case _ =>
}
iv.setImageMatrix(matrix)
}
})
Related
I have managed to export my recyclerview in a PDF file but I am realizing that the image that is as a header is not showing in the pdf, I only know the questions and the answers that are the check boxes
I have tried the following code to show the recyclerview in pdf.
This is my activity
`
fun getScreenshotFromRecyclerView(view: RecyclerView): Bitmap? {
val adapter = view.adapter
var bigBitmap: Bitmap? = null
if (adapter != null) {
val size = listado2.size
var height = 0
val paint = Paint()
var iHeight = 0
val maxMemory = (Runtime.getRuntime().maxMemory() / 1024).toInt()
// Use 1/8th of the available memory for this memory cache.
val cacheSize = maxMemory / 8
val bitmaCache =
LruCache<String, Bitmap>(cacheSize)
for (i in 0 until size) {
val holder =
adapter.createViewHolder(view, adapter.getItemViewType(i))
adapter.onBindViewHolder(holder, i)
holder.itemView.measure(
View.MeasureSpec.makeMeasureSpec(
view.width,
View.MeasureSpec.EXACTLY
),
View.MeasureSpec.makeMeasureSpec(
0,
View.MeasureSpec.UNSPECIFIED
)
)
holder.itemView.layout(
0,
0,
holder.itemView.measuredWidth,
holder.itemView.measuredHeight
)
holder.itemView.isDrawingCacheEnabled = true
holder.itemView.buildDrawingCache()
val drawingCache = holder.itemView.drawingCache
if (drawingCache != null) {
bitmaCache.put(i.toString(), drawingCache)
}
// holder.itemView.setDrawingCacheEnabled(false);
// holder.itemView.destroyDrawingCache();
height += holder.itemView.measuredHeight
}
bigBitmap =
Bitmap.createBitmap(view.measuredWidth, height, Bitmap.Config.ARGB_8888)
val bigCanvas = Canvas(bigBitmap)
bigCanvas.drawColor(Color.WHITE)
for (i in 0 until size) {
val bitmap = bitmaCache[i.toString()]
bigCanvas.drawBitmap(bitmap!!, 0f, iHeight.toFloat(), paint)
iHeight += bitmap.height
bitmap.recycle()
}
}
return bigBitmap
}
`
This is my CreateFile.kt class
`
open fun salvarPDF(bitmap: Bitmap, nombreDeArchivo : String): String?{
archivo = File(pasta, "$nombreDeArchivo.pdf")
val archivoPDF = PdfDocument()
val pageInfo: PdfDocument.PageInfo = PageInfo.Builder(bitmap.width, bitmap.height, 1).create()
val pagina : PdfDocument.Page = archivoPDF.startPage(pageInfo)
val canvas : Canvas = pagina.canvas
canvas.drawBitmap(bitmap,null, Rect(0,0,bitmap.width, bitmap.height),null)
archivoPDF.finishPage(pagina)
try {
archivo!!.createNewFile()
val streamDeSalidad: OutputStream = FileOutputStream(archivo)
archivoPDF.writeTo(streamDeSalidad)
streamDeSalidad.close()
archivoPDF.close()
}catch (e: IOException){
return "error en crear$e"
}
return "creado"
}
`
This is the result in PDF
But it should come out as follows.
Any idea where in the code I am failing.
I'm using CameraX and then FirebaseVision to read some text from the image. when I'm analyzing the Image I want to select a portion of the image, not the entire Image, something like when you use a barcode scanner.
class Analyzer : ImageAnalysis.Analyzer {
override fun analyze(imageProxy: ImageProxy?, rotationDegrees: Int) {
// how to crop the image in here?
val image = imageProxy.image
val imageRotation = degreesToFirebaseRotation(degrees)
if (image != null) {
val visionImage = FirebaseVisionImage.fromMediaImage(image, imageRotation)
val textRecognizer = FirebaseVision.getInstance().onDeviceTextRecognizer
textRecognizer.processImage(visionImage)
}
}
I want to know, is there any way to crop the image?
Your problem is exactly what I have tackled 2 months ago...
object YuvNV21Util {
fun yuv420toNV21(image: Image): ByteArray {
val crop = image.cropRect
val format = image.format
val width = crop.width()
val height = crop.height()
val planes = image.planes
val data =
ByteArray(width * height * ImageFormat.getBitsPerPixel(format) / 8)
val rowData = ByteArray(planes[0].rowStride)
var channelOffset = 0
var outputStride = 1
for (i in planes.indices) {
when (i) {
0 -> {
channelOffset = 0
outputStride = 1
}
1 -> {
channelOffset = width * height + 1
outputStride = 2
}
2 -> {
channelOffset = width * height
outputStride = 2
}
}
val buffer = planes[i].buffer
val rowStride = planes[i].rowStride
val pixelStride = planes[i].pixelStride
val shift = if (i == 0) 0 else 1
val w = width shr shift
val h = height shr shift
buffer.position(rowStride * (crop.top shr shift) + pixelStride * (crop.left shr shift))
for (row in 0 until h) {
var length: Int
if (pixelStride == 1 && outputStride == 1) {
length = w
buffer[data, channelOffset, length]
channelOffset += length
} else {
length = (w - 1) * pixelStride + 1
buffer[rowData, 0, length]
for (col in 0 until w) {
data[channelOffset] = rowData[col * pixelStride]
channelOffset += outputStride
}
}
if (row < h - 1) {
buffer.position(buffer.position() + rowStride - length)
}
}
}
return data
}
}
then convert bytearray into bitmap
object BitmapUtil {
fun getBitmap(data: ByteArray, metadata: FrameMetadata): Bitmap {
val image = YuvImage(
data, ImageFormat.NV21, metadata.width, metadata.height, null
)
val stream = ByteArrayOutputStream()
image.compressToJpeg(
Rect(0, 0, metadata.width, metadata.height),
80,
stream
)
val bmp = BitmapFactory.decodeByteArray(stream.toByteArray(), 0, stream.size())
stream.close()
return rotateBitmap(bmp, metadata.rotation, false, false)
}
private fun rotateBitmap(
bitmap: Bitmap, rotationDegrees: Int, flipX: Boolean, flipY: Boolean
): Bitmap {
val matrix = Matrix()
// Rotate the image back to straight.
matrix.postRotate(rotationDegrees.toFloat())
// Mirror the image along the X or Y axis.
matrix.postScale(if (flipX) -1.0f else 1.0f, if (flipY) -1.0f else 1.0f)
val rotatedBitmap =
Bitmap.createBitmap(bitmap, 0, 0, bitmap.width, bitmap.height, matrix, true)
// Recycle the old bitmap if it has changed.
if (rotatedBitmap != bitmap) {
bitmap.recycle()
}
return rotatedBitmap
}
}
Please have a look at my open source project https://github.com/minkiapps/Firebase-ML-Kit-Scanner-Demo, I build a demo app where portion of the image proxy is cropped before it is processed by ml kit.
Okay, I went through different posts and find out that depending on mobile manufacturers there can be a complications such as capture images get rotated, so you have to be aware of that. What I did was:
fun rotateBitmap(bitmap: Bitmap): Bitmap? {
val matrix = Matrix()
when (getImageOrientation(bitmap)) {
ExifInterface.ORIENTATION_NORMAL -> return bitmap
ExifInterface.ORIENTATION_FLIP_HORIZONTAL -> matrix.setScale(-1f, 1f)
ExifInterface.ORIENTATION_ROTATE_270 -> matrix.setRotate(-90f)
ExifInterface.ORIENTATION_ROTATE_180 -> matrix.setRotate(180f)
ExifInterface.ORIENTATION_ROTATE_90 -> matrix.setRotate(90f)
ExifInterface.ORIENTATION_FLIP_VERTICAL -> {
matrix.setRotate(180f)
matrix.postScale(-1f, 1f)
}
ExifInterface.ORIENTATION_TRANSPOSE -> {
matrix.setRotate(90f)
matrix.postScale(-1f, 1f)
}
ExifInterface.ORIENTATION_TRANSVERSE -> {
matrix.setRotate(-90f)
matrix.postScale(-1f, 1f)
}
else -> return bitmap
}
This worked. But then I noticed something really weird and that might be related with how I configured Camera X configuration.
With the same device I get differently rotated Bitmaps (well, this should not happen. If devices rotates image weirdly, it should rotate images in both modes - in ImageAnalysesUseCase and ImageCaptureUseCase).
So, why is this happening and how can I fix it?
Code implementation:
Binding camera X to life-cycle:
CameraX.bindToLifecycle(
this,
buildPreviewUseCase(),
buildImageAnalysisUseCase(),
buildImageCaptureUseCase()
)
Preview use case:
private fun buildPreviewUseCase(): Preview {
val previewConfig = PreviewConfig.Builder()
.setTargetAspectRatio(config.aspectRatio)
.setTargetResolution(config.resolution)
.setTargetRotation(Surface.ROTATION_0)
.setLensFacing(config.lensFacing)
.build()
return AutoFitPreviewBuilder.build(previewConfig, cameraTextureView)
}
Capture use case:
private fun buildImageCaptureUseCase(): ImageCapture {
val captureConfig = ImageCaptureConfig.Builder()
.setTargetAspectRatio(config.aspectRatio)
.setTargetRotation(Surface.ROTATION_0)
.setTargetResolution(config.resolution)
.setCaptureMode(config.captureMode)
.build()
val capture = ImageCapture(captureConfig)
manualModeTakePhotoButton.setOnClickListener {
capture.takePicture(object : ImageCapture.OnImageCapturedListener() {
override fun onCaptureSuccess(imageProxy: ImageProxy, rotationDegrees: Int) {
viewModel.onManualCameraModeAnalysis(imageProxy, rotationDegrees)
}
override fun onError(useCaseError: ImageCapture.UseCaseError?, message: String?, cause: Throwable?) {
//
}
})
}
return capture
}
Analysis use case:
private fun buildImageAnalysisUseCase(): ImageAnalysis {
val analysisConfig = ImageAnalysisConfig.Builder().apply {
val analyzerThread = HandlerThread("xAnalyzer").apply { start() }
analyzerHandler = Handler(analyzerThread.looper)
setCallbackHandler(analyzerHandler!!)
setTargetAspectRatio(config.aspectRatio)
setTargetRotation(Surface.ROTATION_0)
setTargetResolution(config.resolution)
setImageReaderMode(config.readerMode)
setImageQueueDepth(config.queueDepth)
}.build()
val analysis = ImageAnalysis(analysisConfig)
analysis.analyzer = ImageRecognitionAnalyzer(viewModel)
return analysis
}
AutoFitPreviewBuilder:
class AutoFitPreviewBuilder private constructor(config: PreviewConfig,
viewFinderRef: WeakReference<TextureView>) {
/** Public instance of preview use-case which can be used by consumers of this adapter */
val useCase: Preview
/** Internal variable used to keep track of the use-case's output rotation */
private var bufferRotation: Int = 0
/** Internal variable used to keep track of the view's rotation */
private var viewFinderRotation: Int? = null
/** Internal variable used to keep track of the use-case's output dimension */
private var bufferDimens: Size = Size(0, 0)
/** Internal variable used to keep track of the view's dimension */
private var viewFinderDimens: Size = Size(0, 0)
/** Internal variable used to keep track of the view's display */
private var viewFinderDisplay: Int = -1
/** Internal reference of the [DisplayManager] */
private lateinit var displayManager: DisplayManager
/**
* 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) {
val viewFinder = viewFinderRef.get() ?: return
if (displayId == viewFinderDisplay) {
val display = displayManager.getDisplay(displayId)
val rotation = getDisplaySurfaceRotation(display)
updateTransform(viewFinder, rotation, bufferDimens, viewFinderDimens)
}
}
}
init {
// Make sure that the view finder reference is valid
val viewFinder = viewFinderRef.get() ?:
throw IllegalArgumentException("Invalid reference to view finder used")
// Initialize the display and rotation from texture view information
viewFinderDisplay = viewFinder.display.displayId
viewFinderRotation = getDisplaySurfaceRotation(viewFinder.display) ?: 0
// Initialize public use-case with the given config
useCase = Preview(config)
// Every time the view finder is updated, recompute layout
useCase.onPreviewOutputUpdateListener = Preview.OnPreviewOutputUpdateListener {
val viewFinder =
viewFinderRef.get() ?: return#OnPreviewOutputUpdateListener
// To update the SurfaceTexture, we have to remove it and re-add it
val parent = viewFinder.parent as ViewGroup
parent.removeView(viewFinder)
parent.addView(viewFinder, 0)
viewFinder.surfaceTexture = it.surfaceTexture
bufferRotation = it.rotationDegrees
val rotation = getDisplaySurfaceRotation(viewFinder.display)
updateTransform(viewFinder, rotation, it.textureSize, viewFinderDimens)
}
// Every time the provided texture view changes, recompute layout
viewFinder.addOnLayoutChangeListener { view, left, top, right, bottom, _, _, _, _ ->
val viewFinder = view as TextureView
val newViewFinderDimens = Size(right - left, bottom - top)
val rotation = getDisplaySurfaceRotation(viewFinder.display)
updateTransform(viewFinder, rotation, bufferDimens, newViewFinderDimens)
}
// Every time the orientation of device changes, recompute layout
displayManager = viewFinder.context
.getSystemService(Context.DISPLAY_SERVICE) as DisplayManager
displayManager.registerDisplayListener(displayListener, null)
// Remove the display listeners when the view is detached to avoid
// holding a reference to the View outside of a Fragment.
// NOTE: Even though using a weak reference should take care of this,
// we still try to avoid unnecessary calls to the listener this way.
viewFinder.addOnAttachStateChangeListener(object : View.OnAttachStateChangeListener {
override fun onViewAttachedToWindow(view: View?) {
displayManager.registerDisplayListener(displayListener, null)
}
override fun onViewDetachedFromWindow(view: View?) {
displayManager.unregisterDisplayListener(displayListener)
}
})
}
/** Helper function that fits a camera preview into the given [TextureView] */
private fun updateTransform(textureView: TextureView?, rotation: Int?, newBufferDimens: Size,
newViewFinderDimens: Size) {
// This should not happen anyway, but now the linter knows
val textureView = textureView ?: return
if (rotation == viewFinderRotation &&
Objects.equals(newBufferDimens, bufferDimens) &&
Objects.equals(newViewFinderDimens, viewFinderDimens)) {
// Nothing has changed, no need to transform output again
return
}
if (rotation == null) {
// Invalid rotation - wait for valid inputs before setting matrix
return
} else {
// Update internal field with new inputs
viewFinderRotation = rotation
}
if (newBufferDimens.width == 0 || newBufferDimens.height == 0) {
// Invalid buffer dimens - wait for valid inputs before setting matrix
return
} else {
// Update internal field with new inputs
bufferDimens = newBufferDimens
}
if (newViewFinderDimens.width == 0 || newViewFinderDimens.height == 0) {
// Invalid view finder dimens - wait for valid inputs before setting matrix
return
} else {
// Update internal field with new inputs
viewFinderDimens = newViewFinderDimens
}
val matrix = Matrix()
// Compute the center of the view finder
val centerX = viewFinderDimens.width / 2f
val centerY = viewFinderDimens.height / 2f
// Correct preview output to account for display rotation
matrix.postRotate(-viewFinderRotation!!.toFloat(), centerX, centerY)
// Buffers are rotated relative to the device's 'natural' orientation: swap width and height
val bufferRatio = bufferDimens.height / bufferDimens.width.toFloat()
val scaledWidth: Int
val scaledHeight: Int
// Match longest sides together -- i.e. apply center-crop transformation
if (viewFinderDimens.width > viewFinderDimens.height) {
scaledHeight = viewFinderDimens.width
scaledWidth = Math.round(viewFinderDimens.width * bufferRatio)
} else {
scaledHeight = viewFinderDimens.height
scaledWidth = Math.round(viewFinderDimens.height * bufferRatio)
}
// Compute the relative scale value
val xScale = scaledWidth / viewFinderDimens.width.toFloat()
val yScale = scaledHeight / viewFinderDimens.height.toFloat()
// Scale input buffers to fill the view finder
matrix.preScale(xScale, yScale, centerX, centerY)
// Finally, apply transformations to our TextureView
textureView.setTransform(matrix)
}
companion object {
/** Helper function that gets the rotation of a [Display] in degrees */
fun getDisplaySurfaceRotation(display: Display?) = when(display?.rotation) {
Surface.ROTATION_0 -> 0
Surface.ROTATION_90 -> 90
Surface.ROTATION_180 -> 180
Surface.ROTATION_270 -> 270
else -> null
}
/**
* Main entrypoint for users of this class: instantiates the adapter and returns an instance
* of [Preview] which automatically adjusts in size and rotation to compensate for
* config changes.
*/
fun build(config: PreviewConfig, viewFinder: TextureView) =
AutoFitPreviewBuilder(config, WeakReference(viewFinder)).useCase
}
}
If configuration is correct (it looks okay to me), then next idea was that maybe converting captured images objects to bitmap might be faulty. Below you can see implementation.
Capture mode uses this function:
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)
}
Analysis mode uses this function:
fun toBitmapFromImage(image: Image?): Bitmap? {
try {
if (image == null || image.planes[0] == null || image.planes[1] == null || image.planes[2] == null) {
return null
}
val yBuffer = image.planes[0].buffer
val uBuffer = image.planes[1].buffer
val vBuffer = image.planes[2].buffer
val ySize = yBuffer.remaining()
val uSize = uBuffer.remaining()
val vSize = vBuffer.remaining()
val nv21 = ByteArray(ySize + uSize + vSize)
/* U and V are swapped */
yBuffer.get(nv21, 0, ySize)
vBuffer.get(nv21, ySize, vSize)
uBuffer.get(nv21, ySize + vSize, uSize)
val yuvImage = YuvImage(nv21, ImageFormat.NV21, image.width, image.height, null)
val out = ByteArrayOutputStream()
yuvImage.compressToJpeg(Rect(0, 0, yuvImage.width, yuvImage.height), 50, out)
val imageBytes = out.toByteArray()
return BitmapFactory.decodeByteArray(imageBytes, 0, imageBytes.size)
} catch (e: IllegalStateException) {
Log.e("IllegalStateException", "#ImageUtils.toBitmapFromImage(): Can't read the image file.")
return null
}
}
So, weirdly, on few devices toBitmapFromImage() sometimes comes up upwards, but at the same time (same device) imageProxyToBitmap() returns image in correct rotation - it has to be the image to bitmap functions fault, right?Why is this happening (because capture mode returns image normally) and how to fix this?
Inside onImageCaptureSuccess, get the rotationDegrees and rotate your bitmap by that degree to get the correct orientation.
override fun onImageCaptureSuccess(image: ImageProxy) {
val capturedImageBitmap = image.image?.toBitmap()?.rotate(image.imageInfo.rotationDegrees.toFloat())
mBinding.previewImage.setImageBitmap(capturedImageBitmap)
showPostClickViews()
mCurrentFlow = FLOW_CAMERA
}
toBitmap() and rotate() are extension functions.
fun Image.toBitmap(): Bitmap {
val buffer = planes[0].buffer
buffer.rewind()
val bytes = ByteArray(buffer.capacity())
buffer.get(bytes)
return BitmapFactory.decodeByteArray(bytes, 0, bytes.size)
}
fun Bitmap.rotate(degrees: Float): Bitmap =
Bitmap.createBitmap(this, 0, 0, width, height, Matrix().apply { postRotate(degrees) }, true)
CameraX returns the captured image with a rotation value in the callback, which can be used to rotate the image.
https://developer.android.com/reference/androidx/camera/core/ImageCapture.OnImageCapturedListener.html#onCaptureSuccess(androidx.camera.core.ImageProxy,%20int)
For Analyzer UseCases, you have to get rotationDegree coming through analyze method of ImageAnalysis.Analyzer and work accordingly.
Hope it helps!
I create a Camera by SurfaceView, But when I press home and go back to the activity again. The SurfaceView didn't show anything anymore. It didn't invoke
surfaceCreated(SurfaceHolder holder)
while I back to the activity. What should I do?
here is my code write by Kotlin
class MyCameraView : SurfaceView ,
SurfaceHolder.Callback,Camera.PreviewCallback,Camera.FaceDetectionListener {
private lateinit var mCamera : Camera
var sizes : List<Camera.Size>? = null
constructor( ctx : Context, attrs : AttributeSet?) : super(ctx,attrs){
holder.setType(SurfaceHolder.SURFACE_TYPE_PUSH_BUFFERS);
holder.addCallback(this)
}
constructor(ctx : Context):this(ctx,null){}
override fun surfaceChanged(p0: SurfaceHolder?, format: Int, width: Int, height: Int) {
Log.e("MyCameraView","CHANGE ")
val sizes = mCamera.parameters.supportedPreviewSizes
val size = getOptimalSzie(sizes)
val supportWith = size.height
val supportHeight : Int = size.width
val radio : Float = width.toFloat()/supportWith
setMeasuredDimension(width,(supportHeight*radio).toInt())
layout(0, (-(supportHeight*radio)/5).toInt(),width, ((supportHeight*radio).toInt()-(supportHeight*radio)/5).toInt())
}
override fun surfaceDestroyed(p0: SurfaceHolder?) {
Log.e("MyCameraView","DESTORY")
holder.removeCallback(this)
mCamera.setPreviewCallback(null)
mCamera.stopFaceDetection()
mCamera.stopPreview()
mCamera.lock()
mCamera.release()
}
override fun surfaceCreated(p0: SurfaceHolder?) {
Log.e("MyCameraView","OPEN")
mCamera = Camera.open(Camera.CameraInfo.CAMERA_FACING_FRONT)
mCamera.setDisplayOrientation(90)
mCamera.setPreviewCallback(this)
mCamera.setPreviewDisplay(holder)
mCamera.setFaceDetectionListener(this)
mCamera.startPreview();
mCamera.startFaceDetection()
}
private fun getOptimalSzie(sizes: List<Camera.Size>): Camera.Size {
var pos = 0
var ratio = 0
var viewRatio = height/width
sizes.forEachIndexed { index, size ->
val curRatio = size.width/size.height
if(ratio == 0 ) ratio = curRatio
else if( (viewRatio - curRatio) < (viewRatio - ratio) ){
ratio = curRatio
pos = index
}
}
return sizes[pos]
}
override fun onPreviewFrame(data: ByteArray?, camera: Camera) {
if(allowTake) {
val parameters = camera.getParameters()
val width = parameters.previewSize.width
val height = parameters.previewSize.height
val yuv = YuvImage(data, parameters.previewFormat, width, height, null)
val out = ByteArrayOutputStream()
yuv.compressToJpeg(Rect(0, 0, width, height), 50, out)
val bytes = out.toByteArray()
val b = BitmapFactory.decodeByteArray(bytes, 0, bytes.size)
detected?.invoke(bytes)
allowTake = false
hasTake = true
}
}
var hasTake = false
private var allowTake = false
override fun onFaceDetection(faces: Array<out Camera.Face>?, camera: Camera?) {
if(!hasTake) {
allowTake = true
}
}
private var detected: ((bitmap : ByteArray) -> Unit)? = null
fun onFaceDetected(detected : ( bitmap : ByteArray )->Unit){
this.detected = detected
}
}
I slove it! I change to use TextureView. And it has four callback method. here is one of them
onSurfaceTextureAvailable(surface: SurfaceTexture?, width: Int, height: Int)
it was called while i back to the activity!!!
so i can open the camera when i back again
I need to add Shimmer effect to image view as given in the link for my ImageView. The animation should be from bottom to top instead of left to right as in the sample picture. I have tried the facebook shimmer libraby but it supports from API 16 above only. I need to support if from 14 above. I have also tried this library but it doesn't have support for ImageViews as well as bottom to top animation. Is there any library to achieve the shimmer effect for an Imageview (with bottom to top animation)? or is there any way to implement this feature using ImageView?
You can create your own custom view!
class ShimmerView : View, ValueAnimator.AnimatorUpdateListener{
constructor(context: Context)
: super(context) { init() }
constructor(context: Context, attrs: AttributeSet)
: super(context, attrs) { init() }
constructor(context: Context, attrs: AttributeSet, defStyleAttr: Int)
: super(context, attrs, defStyleAttr) { init() }
companion object {
const val EDGE_ALPHA = 12
const val SHADER_COLOR_R = 170
const val SHADER_COLOR_G = 170
const val SHADER_COLOR_B = 170
const val CENTER_ALPHA = 100
const val ITEM_BG_COLOR = Color.WHITE
val EDGE_COLOR = Color.argb(EDGE_ALPHA, SHADER_COLOR_R, SHADER_COLOR_G, SHADER_COLOR_B)
val CENTER_COLOR = Color.argb(CENTER_ALPHA, SHADER_COLOR_R, SHADER_COLOR_G, SHADER_COLOR_B)
const val LIST_ITEM_LINES = 3
const val CORNER_RADIUS = 2
const val LINE_HEIGHT = 15
const val H_SPACING = 12
const val W_SPACING = 16
const val IMAGE_SIZE = 50
const val ANIMATION_DURATION = 1500L
}
private var listItems: Bitmap? = null
private var animator: ValueAnimator? = null
private var paint: Paint? = null
private var shaderPaint: Paint? = null
private var shaderColors: IntArray? = null
private var lineHeight: Float = 0F
private var hSpacing: Float = 0F
private var wSpacing: Float = 0F
private var imageSize: Float = 0F
private var cornerRadius: Float = 0F
// 1. Инициализируем переменные.
// 1. Initialize variables.
fun init() {
val metric = context.resources.displayMetrics
cornerRadius = dpToPixels(metric, CORNER_RADIUS)
hSpacing = dpToPixels(metric, H_SPACING)
wSpacing = dpToPixels(metric, W_SPACING)
lineHeight = spToPixels(metric, LINE_HEIGHT)
imageSize = dpToPixels(metric, IMAGE_SIZE)
animator = ValueAnimator.ofFloat(-1F, 2F)
animator?.duration = ANIMATION_DURATION
animator?.interpolator = LinearInterpolator()
animator?.repeatCount = ValueAnimator.INFINITE
animator?.addUpdateListener(this)
paint = Paint()
shaderPaint = Paint()
shaderPaint?.isAntiAlias = true
shaderColors = intArrayOf(EDGE_COLOR, CENTER_COLOR, EDGE_COLOR)
}
// 2. Когда View отобразилась на экране, запускаем анимацию.
// 2. When View is displayed on the screen, run the animation.
override fun onVisibilityChanged(changedView: View?, visibility: Int) {
super.onVisibilityChanged(changedView, visibility)
when(visibility) {
VISIBLE -> animator?.start()
INVISIBLE, GONE -> animator?.cancel()
}
}
// 3. При выполнении анимации, изменяем положение шейдера и перерисовываем View.
// 3. When the animation, change the position of the shader and redraw View.
override fun onAnimationUpdate(valueAnimator: ValueAnimator) {
if(isAttachedToWindow) {
val factor: Float = valueAnimator.animatedValue as Float
updateShader(width = width.toFloat(), factor = factor)
invalidate()
}
}
// 4. Одновременно со стартом анимации, рисуем элементы.
// 4. Simultaneously with the start of the animation, draw the elements.
override fun onSizeChanged(w: Int, h: Int, oldw: Int, oldh: Int) {
super.onSizeChanged(w, h, oldw, oldh)
updateShader(width = w.toFloat())
if (h > 0 && w > 0) {
drawListItems(w, h)
} else {
listItems = null
animator?.cancel()
}
}
private fun updateShader(width: Float, factor: Float = -1F) {
val left = width * factor
val shader = LinearGradient(
left, 0F, left+width, 0F, shaderColors, floatArrayOf(0f, 0.5f, 1f), Shader.TileMode.CLAMP)
shaderPaint?.shader = shader
}
override fun onDraw(canvas: Canvas) {
canvas.drawColor(EDGE_COLOR)
canvas.drawRect(0F, 0F, canvas.width.toFloat(), canvas.height.toFloat(), shaderPaint)
if (listItems != null) { canvas.drawBitmap(listItems, 0F, 0F, paint) }
}
private fun drawListItems(w: Int, h: Int) {
listItems = Bitmap.createBitmap(w, h, Bitmap.Config.ARGB_8888)
val canvas = Canvas(listItems)
val item = getItemBitmap(w)
var top = 0
do {
canvas.drawBitmap(item, 0F, top.toFloat(), paint)
top += item.height
} while (top < canvas.height)
canvas.drawColor(ITEM_BG_COLOR, PorterDuff.Mode.SRC_IN)
}
private fun getItemBitmap(w: Int): Bitmap {
val h = calculateListItemHeight(LIST_ITEM_LINES)
val item = Bitmap.createBitmap(w, h, Bitmap.Config.ALPHA_8)
val canvas = Canvas(item)
canvas.drawColor(Color.argb(255, 0, 0, 0))
val itemPaint = Paint()
itemPaint.isAntiAlias = true
itemPaint.color = Color.argb(0, 0, 0, 0)
itemPaint.xfermode = PorterDuffXfermode(PorterDuff.Mode.DST_IN)
//Avatar
val rectF = RectF(wSpacing, hSpacing, wSpacing+imageSize, hSpacing+imageSize)
canvas.drawOval(rectF, itemPaint)
val textLeft = rectF.right + hSpacing
val textRight = canvas.width - wSpacing
//Title line
val titleWidth = (textRight - textLeft)*0.5F
rectF.set(textLeft, hSpacing, textLeft+titleWidth, hSpacing+lineHeight)
canvas.drawRoundRect(rectF, cornerRadius, cornerRadius, itemPaint)
//Time stamp
val timeWidth = (textRight - textLeft)*0.2F
rectF.set(textRight-timeWidth, hSpacing, textRight, hSpacing+lineHeight)
canvas.drawRoundRect(rectF, cornerRadius, cornerRadius, itemPaint)
//Text lines
for (i in 0..LIST_ITEM_LINES-1) {
val lineTop = rectF.bottom + hSpacing
rectF.set(textLeft, lineTop, textRight, lineTop+lineHeight)
canvas.drawRoundRect(rectF, cornerRadius, cornerRadius, itemPaint)
}
return item
}
private fun calculateListItemHeight(lines: Int): Int {
return ((lines*lineHeight) + (hSpacing*(lines+1))).toInt()
}
private fun dpToPixels(metrics: DisplayMetrics, dp: Int): Float {
return TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, dp.toFloat(), metrics)
}
private fun spToPixels(metrics: DisplayMetrics, sp: Int): Float {
return TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_SP, sp.toFloat(), metrics)
}
override fun onDetachedFromWindow() {
super.onDetachedFromWindow()
animator?.removeAllUpdateListeners()
animator = null
listItems = null
}
}
Source
Facebook Shimmer Library does support API level 14. I am using it in a project with these settings:
defaultConfig {
....
minSdkVersion 14
targetSdkVersion 23
....
}
With this dependency:
dependencies {
....
compile 'com.facebook.shimmer:shimmer:0.1.0'
}