casvanluijtelaar

android implementation

... ... @@ -4,9 +4,11 @@ import android.Manifest
import android.app.Activity
import android.content.pm.PackageManager
import android.graphics.Point
import android.graphics.Rect
import android.net.Uri
import android.util.Log
import android.util.Size
import android.media.Image
import android.view.Surface
import androidx.annotation.NonNull
import androidx.camera.core.*
... ... @@ -18,6 +20,7 @@ import com.google.mlkit.vision.barcode.BarcodeScannerOptions
import com.google.mlkit.vision.barcode.BarcodeScanning
import com.google.mlkit.vision.barcode.common.Barcode
import com.google.mlkit.vision.common.InputImage
import com.google.mlkit.vision.common.InputImage.IMAGE_FORMAT_NV21
import io.flutter.plugin.common.EventChannel
import io.flutter.plugin.common.MethodCall
import io.flutter.plugin.common.MethodChannel
... ... @@ -40,6 +43,7 @@ class MobileScanner(private val activity: Activity, private val textureRegistry:
private var camera: Camera? = null
private var preview: Preview? = null
private var textureEntry: TextureRegistry.SurfaceTextureEntry? = null
private var scanWindow: Rect? = null;
// @AnalyzeMode
// private var analyzeMode: Int = AnalyzeMode.NONE
... ... @@ -99,7 +103,20 @@ class MobileScanner(private val activity: Activity, private val textureRegistry:
// when (analyzeMode) {
// AnalyzeMode.BARCODE -> {
val mediaImage = imageProxy.image ?: return@Analyzer
val inputImage = InputImage.fromMediaImage(mediaImage, imageProxy.imageInfo.rotationDegrees)
var inputImage: InputImage?;
if(scanWindow != null) {
val croppedImage = croppedNV21(mediaImage, scanWindow!!)
inputImage = InputImage.fromByteArray(
croppedImage,
scanWindow!!.width(),
scanWindow!!.height(),
imageProxy.imageInfo.rotationDegrees,
IMAGE_FORMAT_NV21,
)
} else {
inputImage = InputImage.fromMediaImage(mediaImage, imageProxy.imageInfo.rotationDegrees)
}
scanner.process(inputImage)
.addOnSuccessListener { barcodes ->
... ... @@ -115,6 +132,50 @@ class MobileScanner(private val activity: Activity, private val textureRegistry:
// }
}
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, mediaImage.height, cropRect)
}
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
}
private var scanner = BarcodeScanning.getClient()
... ... @@ -134,6 +195,9 @@ class MobileScanner(private val activity: Activity, private val textureRegistry:
val torch: Boolean = call.argument<Boolean>("torch") ?: false
val formats: List<Int>? = call.argument<List<Int>>("formats")
val scanWindowData: List<Int>? = call.argument("scanWindow")
if(scanWindowData != null) scanWindow = Rect(scanWindowData!![0], scanWindowData!![1], scanWindowData!![2], scanWindowData!![3])
if (formats != null) {
val formatsList: MutableList<Int> = mutableListOf()
for (index in formats) {
... ...
import 'dart:ui';
import 'package:flutter/material.dart';
import 'package:mobile_scanner/mobile_scanner.dart';
class BarcodeScannerWithScanWindow extends StatefulWidget {
const BarcodeScannerWithScanWindow({Key? key}) : super(key: key);
@override
_BarcodeScannerWithScanWindowState createState() =>
_BarcodeScannerWithScanWindowState();
}
class _BarcodeScannerWithScanWindowState
extends State<BarcodeScannerWithScanWindow>
with SingleTickerProviderStateMixin {
String? barcode;
late Rect scanWindow;
late MobileScannerController controller;
@override
void initState() {
super.initState();
final screenWidth =
window.physicalSize.shortestSide / window.devicePixelRatio;
final screenHeight =
window.physicalSize.longestSide / window.devicePixelRatio;
scanWindow = Rect.fromCenter(
center: Offset(screenWidth / 2, screenHeight / 2),
width: 200,
height: 200,
);
controller = MobileScannerController(
torchEnabled: true,
scanWindow: scanWindow,
);
}
bool isStarted = true;
@override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: Colors.black,
body: Builder(
builder: (context) {
return Stack(
children: [
MobileScanner(
fit: BoxFit.contain,
controller: controller,
onDetect: (barcode, args) {
setState(() {
this.barcode = barcode.rawValue;
});
},
),
CustomPaint(
painter: ScannerOverlay(scanWindow),
),
Align(
alignment: Alignment.bottomCenter,
child: Container(
alignment: Alignment.bottomCenter,
height: 100,
color: Colors.black.withOpacity(0.4),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: [
Center(
child: SizedBox(
width: MediaQuery.of(context).size.width - 120,
height: 50,
child: FittedBox(
child: Text(
barcode ?? 'Scan something!',
overflow: TextOverflow.fade,
style: Theme.of(context)
.textTheme
.headline4!
.copyWith(color: Colors.white),
),
),
),
),
],
),
),
),
],
);
},
),
);
}
}
class ScannerOverlay extends CustomPainter {
ScannerOverlay(this.scanWindow);
final Rect scanWindow;
@override
void paint(Canvas canvas, Size size) {
final backgroundPath = Path()..addRect(Rect.largest);
final cutoutPath = Path()..addRect(scanWindow);
final backgroundPaint = Paint()
..color = Colors.black.withOpacity(0.5)
..style = PaintingStyle.fill
..blendMode = BlendMode.dstOut;
final backgroundWithCutout = Path.combine(
PathOperation.difference,
backgroundPath,
cutoutPath,
);
canvas.drawPath(backgroundWithCutout, backgroundPaint);
}
@override
bool shouldRepaint(covariant CustomPainter oldDelegate) {
return false;
}
}
... ...
import 'package:flutter/material.dart';
import 'package:mobile_scanner_example/barcode_scanner_controller.dart';
import 'package:mobile_scanner_example/barcode_scanner_window.dart';
import 'package:mobile_scanner_example/barcode_scanner_without_controller.dart';
void main() => runApp(const MaterialApp(home: MyHome()));
class MyHome extends StatelessWidget {
... ... @@ -27,6 +29,17 @@ class MyHome extends StatelessWidget {
},
child: const Text('MobileScanner with Controller'),
),
ElevatedButton(
onPressed: () {
Navigator.of(context).push(
MaterialPageRoute(
builder: (context) => const BarcodeScannerWithScanWindow(),
),
);
},
child: const Text('MobileScanner with ScanWindow'),
),
ElevatedButton(
onPressed: () {
Navigator.of(context).push(
... ...
... ... @@ -27,6 +27,7 @@ class MobileScanner extends StatefulWidget {
/// Set to false if you don't want duplicate scans.
final bool allowDuplicates;
/// Create a [MobileScanner] with a [controller], the [controller] must has been initialized.
const MobileScanner({
Key? key,
... ...
... ... @@ -49,6 +49,9 @@ class MobileScannerController {
/// WARNING: On iOS, only 1 format is supported.
final List<BarcodeFormat>? formats;
/// can be used to limit the scan area to a portion of the screen
final Rect? scanWindow;
CameraFacing facing;
bool hasTorch = false;
late StreamController<Barcode> barcodesController;
... ... @@ -60,6 +63,7 @@ class MobileScannerController {
this.ratio,
this.torchEnabled,
this.formats,
this.scanWindow,
}) {
// In case a new instance is created before calling dispose()
if (_controllerHashcode != null) {
... ... @@ -156,6 +160,14 @@ class MobileScannerController {
// Set the starting arguments for the camera
final Map arguments = {};
arguments['facing'] = facing.index;
if (scanWindow != null) {
arguments['scanWindow'] = [
scanWindow!.left,
scanWindow!.top,
scanWindow!.right,
scanWindow!.bottom,
];
}
if (ratio != null) arguments['ratio'] = ratio;
if (torchEnabled != null) arguments['torch'] = torchEnabled;
... ...