Julian Steenbakker

hotfix: cherry pick updated yuvtorgb converter, fix resource not released

@@ -153,6 +153,7 @@ class MobileScanner( @@ -153,6 +153,7 @@ class MobileScanner(
153 153
154 bmResult.recycle() 154 bmResult.recycle()
155 imageProxy.close() 155 imageProxy.close()
  156 + imageFormat.release()
156 157
157 mobileScannerCallback( 158 mobileScannerCallback(
158 barcodeMap, 159 barcodeMap,
@@ -2,18 +2,14 @@ package dev.steenbakker.mobile_scanner.utils @@ -2,18 +2,14 @@ package dev.steenbakker.mobile_scanner.utils
2 2
3 import android.content.Context 3 import android.content.Context
4 import android.graphics.Bitmap 4 import android.graphics.Bitmap
5 -import android.graphics.ImageFormat  
6 import android.media.Image 5 import android.media.Image
7 -import android.os.Build  
8 import android.renderscript.Allocation 6 import android.renderscript.Allocation
9 import android.renderscript.Element 7 import android.renderscript.Element
10 import android.renderscript.RenderScript 8 import android.renderscript.RenderScript
11 import android.renderscript.ScriptIntrinsicYuvToRGB 9 import android.renderscript.ScriptIntrinsicYuvToRGB
12 import android.renderscript.Type 10 import android.renderscript.Type
13 -import androidx.annotation.RequiresApi  
14 import java.nio.ByteBuffer 11 import java.nio.ByteBuffer
15 12
16 -  
17 /** 13 /**
18 * Helper class used to efficiently convert a [Media.Image] object from 14 * Helper class used to efficiently convert a [Media.Image] object from
19 * YUV_420_888 format to an RGB [Bitmap] object. 15 * YUV_420_888 format to an RGB [Bitmap] object.
@@ -23,59 +19,84 @@ import java.nio.ByteBuffer @@ -23,59 +19,84 @@ import java.nio.ByteBuffer
23 * The [yuvToRgb] method is able to achieve the same FPS as the CameraX image 19 * The [yuvToRgb] method is able to achieve the same FPS as the CameraX image
24 * analysis use case at the default analyzer resolution, which is 30 FPS with 20 * analysis use case at the default analyzer resolution, which is 30 FPS with
25 * 640x480 on a Pixel 3 XL device. 21 * 640x480 on a Pixel 3 XL device.
26 - */class YuvToRgbConverter(context: Context) { 22 + */
  23 +/// TODO: Upgrade to implementation without deprecated android.renderscript, but with same or better performance. See https://github.com/juliansteenbakker/mobile_scanner/issues/1142
  24 +class YuvToRgbConverter(context: Context) {
  25 + @Suppress("DEPRECATION")
27 private val rs = RenderScript.create(context) 26 private val rs = RenderScript.create(context)
  27 + @Suppress("DEPRECATION")
28 private val scriptYuvToRgb = 28 private val scriptYuvToRgb =
29 ScriptIntrinsicYuvToRGB.create(rs, Element.U8_4(rs)) 29 ScriptIntrinsicYuvToRGB.create(rs, Element.U8_4(rs))
30 30
31 - // Do not add getters/setters functions to these private variables  
32 - // because yuvToRgb() assume they won't be modified elsewhere  
33 private var yuvBits: ByteBuffer? = null 31 private var yuvBits: ByteBuffer? = null
34 private var bytes: ByteArray = ByteArray(0) 32 private var bytes: ByteArray = ByteArray(0)
  33 + @Suppress("DEPRECATION")
35 private var inputAllocation: Allocation? = null 34 private var inputAllocation: Allocation? = null
  35 + @Suppress("DEPRECATION")
36 private var outputAllocation: Allocation? = null 36 private var outputAllocation: Allocation? = null
37 37
38 @Synchronized 38 @Synchronized
39 fun yuvToRgb(image: Image, output: Bitmap) { 39 fun yuvToRgb(image: Image, output: Bitmap) {
40 - val yuvBuffer = YuvByteBuffer(image, yuvBits)  
41 - yuvBits = yuvBuffer.buffer 40 + try {
  41 + val yuvBuffer = YuvByteBuffer(image, yuvBits)
  42 + yuvBits = yuvBuffer.buffer
42 43
43 - if (needCreateAllocations(image, yuvBuffer)) {  
44 - val yuvType = Type.Builder(rs, Element.U8(rs))  
45 - .setX(image.width)  
46 - .setY(image.height)  
47 - .setYuvFormat(yuvBuffer.type)  
48 - inputAllocation = Allocation.createTyped(  
49 - rs,  
50 - yuvType.create(),  
51 - Allocation.USAGE_SCRIPT  
52 - )  
53 - bytes = ByteArray(yuvBuffer.buffer.capacity())  
54 - val rgbaType = Type.Builder(rs, Element.RGBA_8888(rs))  
55 - .setX(image.width)  
56 - .setY(image.height)  
57 - outputAllocation = Allocation.createTyped(  
58 - rs,  
59 - rgbaType.create(),  
60 - Allocation.USAGE_SCRIPT  
61 - )  
62 - } 44 + if (needCreateAllocations(image, yuvBuffer)) {
  45 + createAllocations(image, yuvBuffer)
  46 + }
63 47
64 - yuvBuffer.buffer.get(bytes)  
65 - inputAllocation!!.copyFrom(bytes) 48 + yuvBuffer.buffer.get(bytes)
  49 + @Suppress("DEPRECATION")
  50 + inputAllocation!!.copyFrom(bytes)
66 51
67 - // Convert NV21 or YUV_420_888 format to RGB  
68 - inputAllocation!!.copyFrom(bytes)  
69 - scriptYuvToRgb.setInput(inputAllocation)  
70 - scriptYuvToRgb.forEach(outputAllocation)  
71 - outputAllocation!!.copyTo(output) 52 + @Suppress("DEPRECATION")
  53 + scriptYuvToRgb.setInput(inputAllocation)
  54 + @Suppress("DEPRECATION")
  55 + scriptYuvToRgb.forEach(outputAllocation)
  56 + @Suppress("DEPRECATION")
  57 + outputAllocation!!.copyTo(output)
  58 + } catch (e: Exception) {
  59 + throw IllegalStateException("Failed to convert YUV to RGB", e)
  60 + }
72 } 61 }
73 62
74 private fun needCreateAllocations(image: Image, yuvBuffer: YuvByteBuffer): Boolean { 63 private fun needCreateAllocations(image: Image, yuvBuffer: YuvByteBuffer): Boolean {
75 - return (inputAllocation == null || // the very 1st call  
76 - inputAllocation!!.type.x != image.width || // image size changed  
77 - inputAllocation!!.type.y != image.height ||  
78 - inputAllocation!!.type.yuv != yuvBuffer.type || // image format changed  
79 - bytes.size == yuvBuffer.buffer.capacity()) 64 + @Suppress("DEPRECATION")
  65 + return inputAllocation?.type?.x != image.width ||
  66 + inputAllocation?.type?.y != image.height ||
  67 + inputAllocation?.type?.yuv != yuvBuffer.type
  68 + }
  69 +
  70 + private fun createAllocations(image: Image, yuvBuffer: YuvByteBuffer) {
  71 + @Suppress("DEPRECATION")
  72 + val yuvType = Type.Builder(rs, Element.U8(rs))
  73 + .setX(image.width)
  74 + .setY(image.height)
  75 + .setYuvFormat(yuvBuffer.type)
  76 + @Suppress("DEPRECATION")
  77 + inputAllocation = Allocation.createTyped(
  78 + rs,
  79 + yuvType.create(),
  80 + Allocation.USAGE_SCRIPT
  81 + )
  82 + bytes = ByteArray(yuvBuffer.buffer.capacity())
  83 + @Suppress("DEPRECATION")
  84 + val rgbaType = Type.Builder(rs, Element.RGBA_8888(rs))
  85 + .setX(image.width)
  86 + .setY(image.height)
  87 + @Suppress("DEPRECATION")
  88 + outputAllocation = Allocation.createTyped(
  89 + rs,
  90 + rgbaType.create(),
  91 + Allocation.USAGE_SCRIPT
  92 + )
  93 + }
  94 +
  95 + @Suppress("DEPRECATION")
  96 + fun release() {
  97 + inputAllocation?.destroy()
  98 + outputAllocation?.destroy()
  99 + scriptYuvToRgb.destroy()
  100 + rs.destroy()
80 } 101 }
81 } 102 }