Skip to content
Merged
Show file tree
Hide file tree
Changes from 16 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,10 @@
## NEXT

* [MacOS] Added the corners and size information to barcode results.
* [MacOS] Added support for `analyzeImage`.
* [web] Added the size information to barcode results.
* Added support for barcode formats to image analysis.

## 5.2.3

Deprecations:
Expand Down
8 changes: 4 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -35,8 +35,8 @@ See the example app for detailed implementation information.

| Features | Android | iOS | macOS | Web |
|------------------------|--------------------|--------------------|----------------------|-----|
| analyzeImage (Gallery) | :heavy_check_mark: | :heavy_check_mark: | :x: | :x: |
| returnImage | :heavy_check_mark: | :heavy_check_mark: | :x: | :x: |
| analyzeImage (Gallery) | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: | :x: |
| returnImage | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: | :x: |
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I forgot to update this one for returnImage

| scanWindow | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: | :x: |

## Platform Support
Expand Down Expand Up @@ -83,8 +83,8 @@ Ensure that you granted camera permission in XCode -> Signing & Capabilities:

## Web

As of version 5.0.0 adding the library to the `index.html` is no longer required,
as the library is automatically loaded on first use.
As of version 5.0.0 adding the barcode scanning library script to the `index.html` is no longer required,
as the script is automatically loaded on first use.

### Providing a mirror for the barcode scanning library

Expand Down
6 changes: 3 additions & 3 deletions android/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,7 @@ dependencies {
def useUnbundled = project.findProperty('dev.steenbakker.mobile_scanner.useUnbundled') ?: false
if (useUnbundled.toBoolean()) {
// Dynamically downloaded model via Google Play Services
implementation 'com.google.android.gms:play-services-mlkit-barcode-scanning:18.3.0'
implementation 'com.google.android.gms:play-services-mlkit-barcode-scanning:18.3.1'
} else {
// Bundled model in app
implementation 'com.google.mlkit:barcode-scanning:17.2.0'
Expand All @@ -77,8 +77,8 @@ dependencies {
// See: https://youtrack.jetbrains.com/issue/KT-55297/kotlin-stdlib-should-declare-constraints-on-kotlin-stdlib-jdk8-and-kotlin-stdlib-jdk7
implementation(platform("org.jetbrains.kotlin:kotlin-bom:1.8.22"))

implementation 'androidx.camera:camera-lifecycle:1.3.3'
implementation 'androidx.camera:camera-camera2:1.3.3'
implementation 'androidx.camera:camera-lifecycle:1.3.4'
implementation 'androidx.camera:camera-camera2:1.3.4'

testImplementation 'org.jetbrains.kotlin:kotlin-test'
testImplementation 'org.mockito:mockito-core:5.12.0'
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -151,28 +151,16 @@ class MobileScannerHandler(
null
}

var barcodeScannerOptions: BarcodeScannerOptions? = null
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This was refactored & moved to reuse it.

if (formats != null) {
val formatsList: MutableList<Int> = mutableListOf()
for (formatValue in formats) {
formatsList.add(BarcodeFormats.fromRawValue(formatValue).intValue)
}
barcodeScannerOptions = if (formatsList.size == 1) {
BarcodeScannerOptions.Builder().setBarcodeFormats(formatsList.first())
.build()
} else {
BarcodeScannerOptions.Builder().setBarcodeFormats(
formatsList.first(),
*formatsList.subList(1, formatsList.size).toIntArray()
).build()
}
}
val barcodeScannerOptions: BarcodeScannerOptions? = buildBarcodeScannerOptions(formats)

val position =
if (facing == 0) CameraSelector.DEFAULT_FRONT_CAMERA else CameraSelector.DEFAULT_BACK_CAMERA

val detectionSpeed: DetectionSpeed = if (speed == 0) DetectionSpeed.NO_DUPLICATES
else if (speed ==1) DetectionSpeed.NORMAL else DetectionSpeed.UNRESTRICTED
val detectionSpeed: DetectionSpeed = when (speed) {
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This was a refactor that was proposed by the Kotlin language tooling

0 -> DetectionSpeed.NO_DUPLICATES
1 -> DetectionSpeed.NORMAL
else -> DetectionSpeed.UNRESTRICTED
}

mobileScanner!!.start(
barcodeScannerOptions,
Expand Down Expand Up @@ -243,13 +231,13 @@ class MobileScannerHandler(

private fun analyzeImage(call: MethodCall, result: MethodChannel.Result) {
analyzerResult = result
val uri = Uri.fromFile(File(call.arguments.toString()))

// TODO: parse options from the method call
// See https://github.com/juliansteenbakker/mobile_scanner/issues/1069
val formats: List<Int>? = call.argument<List<Int>>("formats")
val filePath: String = call.argument<String>("filePath")!!

mobileScanner!!.analyzeImage(
uri,
null,
Uri.fromFile(File(filePath)),
buildBarcodeScannerOptions(formats),
analyzeImageSuccessCallback,
analyzeImageErrorCallback)
}
Expand Down Expand Up @@ -284,4 +272,26 @@ class MobileScannerHandler(

result.success(null)
}

private fun buildBarcodeScannerOptions(formats: List<Int>?): BarcodeScannerOptions? {
if (formats == null) {
return null
}

val formatsList: MutableList<Int> = mutableListOf()

for (formatValue in formats) {
formatsList.add(BarcodeFormats.fromRawValue(formatValue).intValue)
}

if (formatsList.size == 1) {
return BarcodeScannerOptions.Builder().setBarcodeFormats(formatsList.first())
.build()
}

return BarcodeScannerOptions.Builder().setBarcodeFormats(
formatsList.first(),
*formatsList.subList(1, formatsList.size).toIntArray()
).build()
}
}
8 changes: 4 additions & 4 deletions example/lib/barcode_scanner_window.dart
Original file line number Diff line number Diff line change
Expand Up @@ -39,16 +39,16 @@ class _BarcodeScannerWithScanWindowState
final scannedBarcode = barcodeCapture.barcodes.first;

// No barcode corners, or size, or no camera preview size.
if (scannedBarcode.corners.isEmpty ||
value.size.isEmpty ||
barcodeCapture.size.isEmpty) {
if (value.size.isEmpty ||
scannedBarcode.size.isEmpty ||
scannedBarcode.corners.isEmpty) {
return const SizedBox();
}

return CustomPaint(
painter: BarcodeOverlay(
barcodeCorners: scannedBarcode.corners,
barcodeSize: barcodeCapture.size,
barcodeSize: scannedBarcode.size,
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The "barcode size" is wrong here

boxFit: BoxFit.contain,
cameraPreviewSize: value.size,
),
Expand Down
12 changes: 8 additions & 4 deletions ios/Classes/MobileScanner.swift
Original file line number Diff line number Diff line change
Expand Up @@ -22,8 +22,8 @@ public class MobileScanner: NSObject, AVCaptureVideoDataOutputSampleBufferDelega
/// The selected camera
var device: AVCaptureDevice!

/// Barcode scanner for results
var scanner = BarcodeScanner.barcodeScanner()
/// The long lived barcode scanner for scanning barcodes from a camera preview.
var scanner: BarcodeScanner? = nil
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This was a bit weird. We never deallocated the scanner for iOS.

This is similar to #988 and possibly related to #862


/// Default position of camera
var videoPosition: AVCaptureDevice.Position = AVCaptureDevice.Position.back
Expand Down Expand Up @@ -146,7 +146,7 @@ public class MobileScanner: NSObject, AVCaptureVideoDataOutputSampleBufferDelega
position: videoPosition
)

scanner.process(image) { [self] barcodes, error in
scanner?.process(image) { [self] barcodes, error in
imagesCurrentlyBeingProcessed = false

if (detectionSpeed == DetectionSpeed.noDuplicates) {
Expand Down Expand Up @@ -314,6 +314,7 @@ public class MobileScanner: NSObject, AVCaptureVideoDataOutputSampleBufferDelega
textureId = nil
captureSession = nil
device = nil
scanner = nil
}

/// Toggle the torch.
Expand Down Expand Up @@ -431,13 +432,16 @@ public class MobileScanner: NSObject, AVCaptureVideoDataOutputSampleBufferDelega
}

/// Analyze a single image
func analyzeImage(image: UIImage, position: AVCaptureDevice.Position, callback: @escaping BarcodeScanningCallback) {
func analyzeImage(image: UIImage, position: AVCaptureDevice.Position,
barcodeScannerOptions: BarcodeScannerOptions?, callback: @escaping BarcodeScanningCallback) {
let image = VisionImage(image: image)
image.orientation = imageOrientation(
deviceOrientation: UIDevice.current.orientation,
defaultOrientation: .portrait,
position: position
)

let scanner: BarcodeScanner = barcodeScannerOptions != nil ? BarcodeScanner.barcodeScanner(options: barcodeScannerOptions!) : BarcodeScanner.barcodeScanner()
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Create a short lived scanner for the analyze image call


scanner.process(image, completion: callback)
}
Expand Down
32 changes: 20 additions & 12 deletions ios/Classes/MobileScannerPlugin.swift
Original file line number Diff line number Diff line change
Expand Up @@ -134,16 +134,7 @@ public class MobileScannerPlugin: NSObject, FlutterPlugin {
self.mobileScanner.timeoutSeconds = Double(timeoutMs) / Double(1000)
MobileScannerPlugin.returnImage = returnImage

let formatList = formats.map { format in return BarcodeFormat(rawValue: format)}
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This was refactored & moved to reuse it

var barcodeOptions: BarcodeScannerOptions? = nil

if (formatList.count != 0) {
var barcodeFormats: BarcodeFormat = []
for index in formats {
barcodeFormats.insert(BarcodeFormat(rawValue: index))
}
barcodeOptions = BarcodeScannerOptions(formats: barcodeFormats)
}
let barcodeOptions: BarcodeScannerOptions? = buildBarcodeScannerOptions(formats)

let position = facing == 0 ? AVCaptureDevice.Position.front : .back
let detectionSpeed: DetectionSpeed = DetectionSpeed(rawValue: speed)!
Expand Down Expand Up @@ -262,7 +253,9 @@ public class MobileScannerPlugin: NSObject, FlutterPlugin {

/// Analyzes a single image.
private func analyzeImage(_ call: FlutterMethodCall, _ result: @escaping FlutterResult) {
let uiImage = UIImage(contentsOfFile: call.arguments as? String ?? "")
let formats: Array<Int> = (call.arguments as! Dictionary<String, Any?>)["formats"] as? Array ?? []
let scannerOptions: BarcodeScannerOptions? = buildBarcodeScannerOptions(formats)
let uiImage = UIImage(contentsOfFile: (call.arguments as! Dictionary<String, Any?>)["filePath"] as? String ?? "")

if (uiImage == nil) {
result(FlutterError(code: "MobileScanner",
Expand All @@ -271,7 +264,8 @@ public class MobileScannerPlugin: NSObject, FlutterPlugin {
return
}

mobileScanner.analyzeImage(image: uiImage!, position: AVCaptureDevice.Position.back, callback: { barcodes, error in
mobileScanner.analyzeImage(image: uiImage!, position: AVCaptureDevice.Position.back,
barcodeScannerOptions: scannerOptions, callback: { barcodes, error in
if error != nil {
DispatchQueue.main.async {
result(FlutterError(code: "MobileScanner",
Expand All @@ -297,4 +291,18 @@ public class MobileScannerPlugin: NSObject, FlutterPlugin {
}
})
}

private func buildBarcodeScannerOptions(_ formats: [Int]) -> BarcodeScannerOptions? {
guard !formats.isEmpty else {
return nil
}

var barcodeFormats: BarcodeFormat = []

for format in formats {
barcodeFormats.insert(BarcodeFormat(rawValue: format))
}

return BarcodeScannerOptions(formats: barcodeFormats)
}
}
16 changes: 14 additions & 2 deletions lib/src/method_channel/mobile_scanner_method_channel.dart
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import 'dart:async';
import 'package:flutter/foundation.dart';
import 'package:flutter/services.dart';
import 'package:flutter/widgets.dart';
import 'package:mobile_scanner/src/enums/barcode_format.dart';
import 'package:mobile_scanner/src/enums/mobile_scanner_authorization_state.dart';
import 'package:mobile_scanner/src/enums/mobile_scanner_error_code.dart';
import 'package:mobile_scanner/src/enums/torch_state.dart';
Expand Down Expand Up @@ -140,11 +141,22 @@ class MethodChannelMobileScanner extends MobileScannerPlatform {
}

@override
Future<BarcodeCapture?> analyzeImage(String path) async {
Future<BarcodeCapture?> analyzeImage(
String path, {
List<BarcodeFormat> formats = const <BarcodeFormat>[],
}) async {
final Map<Object?, Object?>? result =
await methodChannel.invokeMapMethod<Object?, Object?>(
'analyzeImage',
path,
{
'filePath': path,
'formats': formats.isEmpty
? null
: [
for (final BarcodeFormat format in formats)
if (format != BarcodeFormat.unknown) format.rawValue,
],
},
);

return _parseBarcode(result);
Expand Down
2 changes: 1 addition & 1 deletion lib/src/mobile_scanner_controller.dart
Original file line number Diff line number Diff line change
Expand Up @@ -174,7 +174,7 @@ class MobileScannerController extends ValueNotifier<MobileScannerState> {
///
/// The [path] points to a file on the device.
///
/// This is only supported on Android and iOS.
/// This is only supported on Android, iOS and MacOS.
///
/// Returns the [BarcodeCapture] that was found in the image.
Future<BarcodeCapture?> analyzeImage(String path) {
Expand Down
9 changes: 8 additions & 1 deletion lib/src/mobile_scanner_platform_interface.dart
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import 'package:flutter/widgets.dart';
import 'package:mobile_scanner/src/enums/barcode_format.dart';
import 'package:mobile_scanner/src/enums/torch_state.dart';
import 'package:mobile_scanner/src/method_channel/mobile_scanner_method_channel.dart';
import 'package:mobile_scanner/src/mobile_scanner_view_attributes.dart';
Expand Down Expand Up @@ -46,9 +47,15 @@ abstract class MobileScannerPlatform extends PlatformInterface {
/// Analyze a local image file for barcodes.
///
/// The [path] is the path to the file on disk.
/// The [formats] specify the barcode formats that should be detected.
///
/// If [formats] is empty, all barcode formats will be detected.
///
/// Returns the barcodes that were found in the image.
Future<BarcodeCapture?> analyzeImage(String path) {
Future<BarcodeCapture?> analyzeImage(
String path, {
List<BarcodeFormat> formats = const <BarcodeFormat>[],
}) {
throw UnimplementedError('analyzeImage() has not been implemented.');
}

Expand Down
2 changes: 1 addition & 1 deletion lib/src/objects/barcode.dart
Original file line number Diff line number Diff line change
Expand Up @@ -152,7 +152,7 @@ class Barcode {
/// This is null if the raw value is not available.
final String? rawValue;

/// The size of the barcode bounding box.
/// The normalized size of the barcode bounding box.
///
/// If the bounding box is unavailable, this will be [Size.zero].
final Size size;
Expand Down
6 changes: 5 additions & 1 deletion lib/src/web/mobile_scanner_web.dart
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import 'dart:ui_web' as ui_web;

import 'package:flutter/widgets.dart';
import 'package:flutter_web_plugins/flutter_web_plugins.dart';
import 'package:mobile_scanner/src/enums/barcode_format.dart';
import 'package:mobile_scanner/src/enums/camera_facing.dart';
import 'package:mobile_scanner/src/enums/mobile_scanner_error_code.dart';
import 'package:mobile_scanner/src/enums/torch_state.dart';
Expand Down Expand Up @@ -232,7 +233,10 @@ class MobileScannerWeb extends MobileScannerPlatform {
}

@override
Future<BarcodeCapture?> analyzeImage(String path) {
Future<BarcodeCapture?> analyzeImage(
String path, {
List<BarcodeFormat> formats = const <BarcodeFormat>[],
}) {
throw UnsupportedError('analyzeImage() is not supported on the web.');
}

Expand Down
22 changes: 21 additions & 1 deletion lib/src/web/zxing/result.dart
Original file line number Diff line number Diff line change
Expand Up @@ -75,13 +75,33 @@ extension type Result(JSObject _) implements JSObject {

/// Convert this result to a [Barcode].
Barcode get toBarcode {
final List<Offset> corners = resultPoints;

return Barcode(
corners: resultPoints,
corners: corners,
format: barcodeFormat,
displayValue: text,
rawBytes: rawBytes,
rawValue: text,
size: _computeSize(corners),
type: BarcodeType.text,
);
}

Size _computeSize(List<Offset> points) {
if (points.length != 4) {
return Size.zero;
}

final Iterable<double> xCoords = points.map((p) => p.dx);
final Iterable<double> yCoords = points.map((p) => p.dy);

// Find the minimum and maximum x and y coordinates.
final double xMin = xCoords.reduce((a, b) => a < b ? a : b);
final double xMax = xCoords.reduce((a, b) => a > b ? a : b);
final double yMin = yCoords.reduce((a, b) => a < b ? a : b);
final double yMax = yCoords.reduce((a, b) => a > b ? a : b);

return Size(xMax - xMin, yMax - yMin);
}
}
Loading
Loading