Skip to content

Commit

Permalink
Merge pull request #1169 from navaronbracke/size_fix_android
Browse files Browse the repository at this point in the history
fix: Fix discrepancy between barcode bounding box and barcode capture size
  • Loading branch information
navaronbracke authored Sep 5, 2024
2 parents 5b6436e + 75418f2 commit c4bf426
Show file tree
Hide file tree
Showing 16 changed files with 257 additions and 131 deletions.
14 changes: 13 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,15 @@
## 5.2.2

Improvements:
* [MacOS] Adds Swift Package Manager support.
* [MacOS] Adds support for `returnImage`.
* Added a new `size` property to `Barcode`, that denotes the bounding box of the barcode.

Bugs fixed:
* Fixed some documentation errors for the `size` and `image` of `BarcodeCapture`.
* [iOS] Fixed a bug with `returnImage`.
* [Android/iOS] Adjusted the raw barcode scan value to pass the raw event data, like on MacOS.

## 5.2.1

* Updates the `package:web` dependency to use a version range.
Expand Down Expand Up @@ -44,7 +56,7 @@ Improvements:
This major release contains all the changes from the 5.0.0 beta releases, along with the following changes:

Improvements:
- [Android] Remove the Kotlin Standard Library from the dependencies, as it is automatically included in Kotlin 1.4+
* [Android] Remove the Kotlin Standard Library from the dependencies, as it is automatically included in Kotlin 1.4+

## 5.0.0-beta.3
**BREAKING CHANGES:**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -50,9 +50,11 @@ class MobileScannerHandler(
barcodeHandler.publishEvent(mapOf(
"name" to "barcode",
"data" to barcodes,
"image" to image,
"width" to width!!.toDouble(),
"height" to height!!.toDouble()
"image" to mapOf(
"bytes" to image,
"width" to width?.toDouble(),
"height" to height?.toDouble(),
)
))
} else {
barcodeHandler.publishEvent(mapOf(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,12 +28,22 @@ fun Image.toByteArray(): ByteArray {

val Barcode.data: Map<String, Any?>
get() = mapOf(
"corners" to cornerPoints?.map { corner -> corner.data }, "format" to format,
"rawBytes" to rawBytes, "rawValue" to rawValue, "type" to valueType,
"calendarEvent" to calendarEvent?.data, "contactInfo" to contactInfo?.data,
"driverLicense" to driverLicense?.data, "email" to email?.data,
"geoPoint" to geoPoint?.data, "phone" to phone?.data, "sms" to sms?.data,
"url" to url?.data, "wifi" to wifi?.data, "displayValue" to displayValue
"calendarEvent" to calendarEvent?.data,
"contactInfo" to contactInfo?.data,
"corners" to cornerPoints?.map { corner -> corner.data },
"displayValue" to displayValue,
"driverLicense" to driverLicense?.data,
"email" to email?.data,
"format" to format,
"geoPoint" to geoPoint?.data,
"phone" to phone?.data,
"rawBytes" to rawBytes,
"rawValue" to rawValue,
"size" to boundingBox?.size,
"sms" to sms?.data,
"type" to valueType,
"url" to url?.data,
"wifi" to wifi?.data,
)

private val Point.data: Map<String, Double>
Expand Down Expand Up @@ -92,4 +102,14 @@ private val Barcode.UrlBookmark.data: Map<String, Any?>
get() = mapOf("title" to title, "url" to url)

private val Barcode.WiFi.data: Map<String, Any?>
get() = mapOf("encryptionType" to encryptionType, "password" to password, "ssid" to ssid)
get() = mapOf("encryptionType" to encryptionType, "password" to password, "ssid" to ssid)

private val Rect.size: Map<String, Any?>
get() {
// Rect.isValid can't be accessed for some reason, so just do the check manually.
if (left <= right && top <= bottom) {
return mapOf("width" to width().toDouble(), "height" to height().toDouble())
}

return emptyMap()
}
2 changes: 1 addition & 1 deletion example/ios/Runner/AppDelegate.swift
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import UIKit
import Flutter

@UIApplicationMain
@main
@objc class AppDelegate: FlutterAppDelegate {
override func application(
_ application: UIApplication,
Expand Down
2 changes: 1 addition & 1 deletion example/macos/Runner/AppDelegate.swift
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import Cocoa
import FlutterMacOS

@NSApplicationMain
@main
class AppDelegate: FlutterAppDelegate {
override func applicationShouldTerminateAfterLastWindowClosed(_ sender: NSApplication) -> Bool {
return true
Expand Down
21 changes: 4 additions & 17 deletions ios/Classes/MobileScanner.swift
Original file line number Diff line number Diff line change
Expand Up @@ -25,9 +25,6 @@ public class MobileScanner: NSObject, AVCaptureVideoDataOutputSampleBufferDelega
/// Barcode scanner for results
var scanner = BarcodeScanner.barcodeScanner()

/// Return image buffer with the Barcode event
var returnImage: Bool = false

/// Default position of camera
var videoPosition: AVCaptureDevice.Position = AVCaptureDevice.Position.back

Expand Down Expand Up @@ -127,7 +124,6 @@ public class MobileScanner: NSObject, AVCaptureVideoDataOutputSampleBufferDelega
/// Gets called when a new image is added to the buffer
public func captureOutput(_ output: AVCaptureOutput, didOutput sampleBuffer: CMSampleBuffer, from connection: AVCaptureConnection) {
guard let imageBuffer = CMSampleBufferGetImageBuffer(sampleBuffer) else {
print("Failed to get image buffer from sample buffer.")
return
}
latestBuffer = imageBuffer
Expand Down Expand Up @@ -160,7 +156,9 @@ public class MobileScanner: NSObject, AVCaptureVideoDataOutputSampleBufferDelega

if (error == nil && barcodesString != nil && newScannedBarcodes != nil && barcodesString!.elementsEqual(newScannedBarcodes!)) {
return
} else if (newScannedBarcodes?.isEmpty == false) {
}

if (newScannedBarcodes?.isEmpty == false) {
barcodesString = newScannedBarcodes
}
}
Expand All @@ -171,7 +169,7 @@ public class MobileScanner: NSObject, AVCaptureVideoDataOutputSampleBufferDelega
}

/// Start scanning for barcodes
func start(barcodeScannerOptions: BarcodeScannerOptions?, returnImage: Bool, cameraPosition: AVCaptureDevice.Position, torch: Bool, detectionSpeed: DetectionSpeed, completion: @escaping (MobileScannerStartParameters) -> ()) throws {
func start(barcodeScannerOptions: BarcodeScannerOptions?, cameraPosition: AVCaptureDevice.Position, torch: Bool, detectionSpeed: DetectionSpeed, completion: @escaping (MobileScannerStartParameters) -> ()) throws {
self.detectionSpeed = detectionSpeed
if (device != nil || captureSession != nil) {
throw MobileScannerError.alreadyStarted
Expand Down Expand Up @@ -446,17 +444,6 @@ public class MobileScanner: NSObject, AVCaptureVideoDataOutputSampleBufferDelega

var barcodesString: Array<String?>?

// /// Convert image buffer to jpeg
// private func ciImageToJpeg(ciImage: CIImage) -> Data {
//
// // let ciImage = CIImage(cvPixelBuffer: latestBuffer)
// let context:CIContext = CIContext.init(options: nil)
// let cgImage:CGImage = context.createCGImage(ciImage, from: ciImage.extent)!
// let uiImage:UIImage = UIImage(cgImage: cgImage, scale: 1, orientation: UIImage.Orientation.up)
//
// return uiImage.jpegData(compressionQuality: 0.8)!
// }

/// Rotates images accordingly
func imageOrientation(
deviceOrientation: UIDeviceOrientation,
Expand Down
73 changes: 52 additions & 21 deletions ios/Classes/MobileScannerPlugin.swift
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,10 @@ public class MobileScannerPlugin: NSObject, FlutterPlugin {

/// The handler sends all information via an event channel back to Flutter
private let barcodeHandler: BarcodeHandler

/// Whether to return the input image with the barcode event.
/// This is static to avoid accessing `self` in the callback in the constructor.
private static var returnImage: Bool = false

/// The points for the scan window.
static var scanWindow: [CGFloat]?
Expand All @@ -37,24 +41,48 @@ public class MobileScannerPlugin: NSObject, FlutterPlugin {

init(barcodeHandler: BarcodeHandler, registry: FlutterTextureRegistry) {
self.mobileScanner = MobileScanner(registry: registry, mobileScannerCallback: { barcodes, error, image in
if barcodes != nil {
let barcodesMap: [Any?] = barcodes!.compactMap { barcode in
if (MobileScannerPlugin.scanWindow != nil) {
if (MobileScannerPlugin.isBarcodeInScanWindow(barcode: barcode, imageSize: image.size)) {
return barcode.data
} else {
return nil
}
} else {
return barcode.data
}
if error != nil {
barcodeHandler.publishEvent(["name": "error", "data": error!.localizedDescription])
return
}

if barcodes == nil {
return
}

let barcodesMap: [Any?] = barcodes!.compactMap { barcode in
if (MobileScannerPlugin.scanWindow == nil) {
return barcode.data
}
if (!barcodesMap.isEmpty) {
barcodeHandler.publishEvent(["name": "barcode", "data": barcodesMap, "image": FlutterStandardTypedData(bytes: image.jpegData(compressionQuality: 0.8)!), "width": image.size.width, "height": image.size.height])

if (MobileScannerPlugin.isBarcodeInScanWindow(barcode: barcode, imageSize: image.size)) {
return barcode.data
}
} else if (error != nil){
barcodeHandler.publishEvent(["name": "error", "data": error!.localizedDescription])

return nil
}

if (barcodesMap.isEmpty) {
return
}

if (!MobileScannerPlugin.returnImage) {
barcodeHandler.publishEvent([
"name": "barcode",
"data": barcodesMap,
])
return
}

barcodeHandler.publishEvent([
"name": "barcode",
"data": barcodesMap,
"image": [
"bytes": FlutterStandardTypedData(bytes: image.jpegData(compressionQuality: 0.8)!),
"width": image.size.width,
"height": image.size.height,
],
])
}, torchModeChangeCallback: { torchState in
barcodeHandler.publishEvent(["name": "torchState", "data": torchState])
}, zoomScaleChangeCallback: { zoomScale in
Expand Down Expand Up @@ -104,6 +132,7 @@ public class MobileScannerPlugin: NSObject, FlutterPlugin {
let speed: Int = (call.arguments as! Dictionary<String, Any?>)["speed"] as? Int ?? 0
let timeoutMs: Int = (call.arguments as! Dictionary<String, Any?>)["timeout"] as? Int ?? 0
self.mobileScanner.timeoutSeconds = Double(timeoutMs) / Double(1000)
MobileScannerPlugin.returnImage = returnImage

let formatList = formats.map { format in return BarcodeFormat(rawValue: format)}
var barcodeOptions: BarcodeScannerOptions? = nil
Expand All @@ -120,7 +149,7 @@ public class MobileScannerPlugin: NSObject, FlutterPlugin {
let detectionSpeed: DetectionSpeed = DetectionSpeed(rawValue: speed)!

do {
try mobileScanner.start(barcodeScannerOptions: barcodeOptions, returnImage: returnImage, cameraPosition: position, torch: torch, detectionSpeed: detectionSpeed) { parameters in
try mobileScanner.start(barcodeScannerOptions: barcodeOptions, cameraPosition: position, torch: torch, detectionSpeed: detectionSpeed) { parameters in
DispatchQueue.main.async {
result([
"textureId": parameters.textureId,
Expand Down Expand Up @@ -257,12 +286,14 @@ public class MobileScannerPlugin: NSObject, FlutterPlugin {
DispatchQueue.main.async {
result(nil)
}
} else {
let barcodesMap: [Any?] = barcodes!.compactMap { barcode in barcode.data }

DispatchQueue.main.async {
result(["name": "barcode", "data": barcodesMap])
}
return
}

let barcodesMap: [Any?] = barcodes!.compactMap { barcode in barcode.data }

DispatchQueue.main.async {
result(["name": "barcode", "data": barcodesMap])
}
})
}
Expand Down
23 changes: 21 additions & 2 deletions ios/Classes/MobileScannerUtilities.swift
Original file line number Diff line number Diff line change
Expand Up @@ -29,8 +29,27 @@ extension UIDeviceOrientation {

extension Barcode {
var data: [String: Any?] {
let corners = cornerPoints?.map({$0.cgPointValue.data})
return ["corners": corners, "format": format.rawValue, "rawBytes": rawData, "rawValue": rawValue, "type": valueType.rawValue, "calendarEvent": calendarEvent?.data, "contactInfo": contactInfo?.data, "driverLicense": driverLicense?.data, "email": email?.data, "geoPoint": geoPoint?.data, "phone": phone?.data, "sms": sms?.data, "url": url?.data, "wifi": wifi?.data, "displayValue": displayValue]
return [
"calendarEvent": calendarEvent?.data,
"contactInfo": contactInfo?.data,
"corners": cornerPoints?.map({$0.cgPointValue.data}),
"displayValue": displayValue,
"driverLicense": driverLicense?.data,
"email": email?.data,
"format": format.rawValue,
"geoPoint": geoPoint?.data,
"phone": phone?.data,
"rawBytes": rawData,
"rawValue": rawValue,
"size": frame.isNull ? nil : [
"width": frame.width,
"height": frame.height,
],
"sms": sms?.data,
"type": valueType.rawValue,
"url": url?.data,
"wifi": wifi?.data,
]
}
}

Expand Down
2 changes: 1 addition & 1 deletion ios/mobile_scanner.podspec
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
#
Pod::Spec.new do |s|
s.name = 'mobile_scanner'
s.version = '5.2.1'
s.version = '5.2.2'
s.summary = 'An universal scanner for Flutter based on MLKit.'
s.description = <<-DESC
An universal scanner for Flutter based on MLKit.
Expand Down
35 changes: 11 additions & 24 deletions lib/src/method_channel/mobile_scanner_method_channel.dart
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@ 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 @@ -54,31 +53,19 @@ class MethodChannelMobileScanner extends MobileScannerPlatform {
final List<Map<Object?, Object?>> barcodes =
data.cast<Map<Object?, Object?>>();

if (defaultTargetPlatform == TargetPlatform.macOS) {
return BarcodeCapture(
raw: event,
barcodes: barcodes
.map(
(barcode) => Barcode(
rawValue: barcode['payload'] as String?,
format: BarcodeFormat.fromRawValue(
barcode['symbology'] as int? ?? -1,
),
),
)
.toList(),
);
}

if (defaultTargetPlatform == TargetPlatform.android ||
defaultTargetPlatform == TargetPlatform.iOS) {
final double? width = event['width'] as double?;
final double? height = event['height'] as double?;
defaultTargetPlatform == TargetPlatform.iOS ||
defaultTargetPlatform == TargetPlatform.macOS) {
final Map<Object?, Object?>? imageData =
event['image'] as Map<Object?, Object?>?;
final Uint8List? image = imageData?['bytes'] as Uint8List?;
final double? width = imageData?['width'] as double?;
final double? height = imageData?['height'] as double?;

return BarcodeCapture(
raw: data,
raw: event,
barcodes: barcodes.map(Barcode.fromNative).toList(),
image: event['image'] as Uint8List?,
image: image,
size: width == null || height == null ? Size.zero : Size(width, height),
);
}
Expand Down Expand Up @@ -154,8 +141,8 @@ class MethodChannelMobileScanner extends MobileScannerPlatform {

@override
Future<BarcodeCapture?> analyzeImage(String path) async {
final Map<String, Object?>? result =
await methodChannel.invokeMapMethod<String, Object?>(
final Map<Object?, Object?>? result =
await methodChannel.invokeMapMethod<Object?, Object?>(
'analyzeImage',
path,
);
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 @@ -80,7 +80,7 @@ class MobileScannerController extends ValueNotifier<MobileScannerState> {
///
/// If this is false, [BarcodeCapture.image] will always be null.
///
/// Defaults to false, and is only supported on iOS and Android.
/// Defaults to false, and is only supported on iOS, MacOS and Android.
final bool returnImage;

/// Whether the flashlight should be turned on when the camera is started.
Expand Down
Loading

0 comments on commit c4bf426

Please sign in to comment.