Skip to content

Commit

Permalink
Add support for inverted codes
Browse files Browse the repository at this point in the history
  • Loading branch information
georgy-komarov committed Feb 9, 2021
1 parent 18b40a7 commit f4d6845
Show file tree
Hide file tree
Showing 2 changed files with 115 additions and 28 deletions.
4 changes: 2 additions & 2 deletions app/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,8 @@ android {
applicationId "ml.komarov.markscanner"
minSdkVersion 24
targetSdkVersion 30
versionCode 1
versionName "0.1"
versionCode 2
versionName "0.2"

testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
}
Expand Down
139 changes: 113 additions & 26 deletions app/src/main/java/ml/komarov/markscanner/BarcodeActivity.kt
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,15 @@ package ml.komarov.markscanner
import android.Manifest
import android.annotation.SuppressLint
import android.content.Intent
import android.graphics.*
import android.graphics.ImageFormat.NV21
import android.media.Image
import android.os.Bundle
import android.renderscript.*
import android.util.Log
import android.util.Size
import androidx.appcompat.app.AppCompatActivity
import androidx.camera.core.CameraSelector
import androidx.camera.core.ImageAnalysis
import androidx.camera.core.Preview
import androidx.camera.core.*
import androidx.camera.lifecycle.ProcessCameraProvider
import androidx.core.content.ContextCompat
import com.github.florent37.runtimepermission.kotlin.askPermission
Expand All @@ -19,6 +21,8 @@ import com.google.mlkit.vision.barcode.BarcodeScannerOptions
import com.google.mlkit.vision.barcode.BarcodeScanning
import com.google.mlkit.vision.common.InputImage
import ml.komarov.markscanner.databinding.ActivityBacodeBinding
import java.io.ByteArrayOutputStream
import java.nio.ByteBuffer
import java.util.concurrent.ExecutorService
import java.util.concurrent.Executors

Expand All @@ -39,13 +43,116 @@ class BarcodeActivity : AppCompatActivity() {
Runtime.getRuntime().availableProcessors()
)

private fun invert(original: Bitmap): Bitmap? {
val rgbMask = 0x00FFFFFF

// Create mutable Bitmap to invert, argument true makes it mutable
val inversion = original.copy(Bitmap.Config.ARGB_8888, true)

// Get info about Bitmap
val width = inversion.width
val height = inversion.height
val pixels = width * height

// Get original pixels
val pixel = IntArray(pixels)
inversion.getPixels(pixel, 0, width, 0, 0, width, height)

// Modify pixels
for (i in 0 until pixels) pixel[i] = pixel[i] xor rgbMask
inversion.setPixels(pixel, 0, width, 0, 0, width, height)

// Return inverted Bitmap
return inversion
}

private fun JPEGToBitmap(bytes: ByteArray): Bitmap {
return BitmapFactory.decodeByteArray(bytes, 0, bytes.size)
}

private fun NV21toJPEG(nv21: ByteArray, width: Int, height: Int, quality: Int): ByteArray? {
val out = ByteArrayOutputStream()
val yuv = YuvImage(nv21, NV21, width, height, null)
yuv.compressToJpeg(Rect(0, 0, width, height), quality, out)
return out.toByteArray()
}

private fun YUV420toNV21(image: Image): ByteArray? {
val crop: Rect = image.cropRect
val format: Int = image.format
val width = crop.width()
val height = crop.height()
val planes: Array<Image.Plane> = 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: ByteBuffer = planes[i].buffer
val rowStride: Int = planes[i].rowStride
val pixelStride: Int = 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.get(data, channelOffset, length)
channelOffset += length
} else {
length = (w - 1) * pixelStride + 1
buffer.get(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
}

@SuppressLint("UnsafeExperimentalUsageError")
private val barcodeAnalyser: ImageAnalysis.Analyzer = ImageAnalysis.Analyzer { img ->
val mediaImage = img.image
if (mediaImage != null) {
val inputImage = InputImage.fromMediaImage(mediaImage, img.imageInfo.rotationDegrees)
val reversedBitmap = YUV420toNV21(mediaImage)?.let { it ->
NV21toJPEG(
it, img.width, img.height, quality = 75
)?.let { invert(JPEGToBitmap(it)) }
}
if (reversedBitmap != null) {
val reversedImage = InputImage.fromBitmap(
reversedBitmap,
img.imageInfo.rotationDegrees
)
// Original image
Tasks.await(barcodeScanner.process(reversedImage))
.forEach(this@BarcodeActivity::returnBarcodeResult)
}
// Negative image
Tasks.await(barcodeScanner.process(inputImage))
.forEach(this@BarcodeActivity::prettyPrintBarcodeResult)
.forEach(this@BarcodeActivity::returnBarcodeResult)
}
img.close()
}
Expand Down Expand Up @@ -106,31 +213,11 @@ class BarcodeActivity : AppCompatActivity() {
super.onDestroy()
}

private fun prettyPrintBarcodeResult(barcodeResult: Barcode) {
val typeStr = when (barcodeResult.format) {
Barcode.FORMAT_AZTEC -> "Aztec"
Barcode.FORMAT_CODABAR -> "Codabar"
Barcode.FORMAT_CODE_128 -> "Code 128"
Barcode.FORMAT_CODE_39 -> "Code 39"
Barcode.FORMAT_CODE_93 -> "Code 93"
Barcode.FORMAT_DATA_MATRIX -> "Data Matrix"
Barcode.FORMAT_EAN_13 -> "Ean 13"
Barcode.FORMAT_EAN_8 -> "Ean 8"
Barcode.FORMAT_ITF -> "Itf"
Barcode.FORMAT_PDF417 -> "Pdf 417"
Barcode.FORMAT_QR_CODE -> "Qr Code"
Barcode.FORMAT_UPC_A -> "Upc A"
Barcode.FORMAT_UPC_E -> "Upc E"
Barcode.FORMAT_UNKNOWN -> "Unknown"
else -> "Unknown"
}
Log.d(TAG, "Type $typeStr, Value ${barcodeResult.displayValue}")

private fun returnBarcodeResult(barcodeResult: Barcode) {
val intent = Intent()
intent.putExtra("DATA", barcodeResult.displayValue)
setResult(RESULT_OK, intent)
finish() //finishing activity

finish() // finishing activity
}

companion object {
Expand Down

0 comments on commit f4d6845

Please sign in to comment.