Skip to content

Commit

Permalink
Merge pull request #1248 from juliansteenbakker/ios-vision-update
Browse files Browse the repository at this point in the history
chore: merge ios vission update into ios vision beta
  • Loading branch information
navaronbracke authored Nov 26, 2024
2 parents b7d6599 + c1bb161 commit 23360aa
Show file tree
Hide file tree
Showing 30 changed files with 412 additions and 250 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/flutter.yml
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ jobs:
- uses: subosito/[email protected]
with:
cache: true
flutter-version: '3.22'
flutter-version: '3.24'
channel: 'stable'
- name: Version
run: flutter doctor -v
Expand Down
51 changes: 34 additions & 17 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,34 +1,51 @@
## NEXT
- Fixed an issue which caused the scanWindow to always be present if provided, even when scanWindow is updated to null
- Integrated basic barcode overlay and scanner overlay into the package.
- Made updateScanWindow private, because logic within the MobileScanner widget is needed in order to pass a correct scanWindow.
The scanWindow can be updated by directly changing the scanWindow in the MobileScanner widget.

**BREAKING CHANGES:**

* The `updateScanWindow` method is now private. Instead, update the scan window in the `MobileScanner` widget directly.
* The deprecated `EncryptionType.none` constant has been removed. Use `EncryptionType.unknown` instead.

Bugs fixed:
* [Apple] Fixed an issue which caused the scanWindow to always be present, even when reset to no value.
* [Apple] Fixed an issue that caused the barcode size to report the wrong height.
* [Apple] Fixed a bug that caused the corner points to not be returned in clockwise orientation.

Improvements:
* Added a basic barcode overlay widget, for use with the camera preview.
* Added a basic scan window overlay widget, for use with the camera preview.
* Update the bundled MLKit model for Android to version `17.3.0`.
* Added documentation in places where it was missing.
* Added `color` and `style` properties to the `BarcodePainter` widget.

## 7.0.0-beta.3
Fix build issues on macOS

* Fixed a build issue on macOS.

## 7.0.0-beta.2

Bugs fixed:
* [Apple] Fixed an issue with the zoom slider being non-functional.
* [Apple] Fixed an issue where the flash would briefly show when the camera is turned on.
* [Apple] Fixed an issue that prevented the scan window from working.
* [Apple] Fixed an issue that caused the barcode overlay to use the wrong dimensions.

Improvements:
* [iOS] Adds support for Swift Package Manager.
* [Apple] Fixes zoom slider
* Fixed torch at start not working
* Fixed scanWindow not being correct
* Fixed barcode overlay not being correct

Known issues:
* BoxFit.cover & BoxFit.fitHeight produces wrong width in barcodeOverlay
* BoxFit.cover & BoxFit.fitHeight produce the wrong width in the barcode overlay.

## 7.0.0-beta.1

This version replaces MLKit on iOS with Apple's Vision API and merges the iOS and MacOS sources.
The requirement for the minimum iOS version has been relaxed back down to iOS 12.0.
Improvements:
* [iOS] Migrate to the Vision API.
* [iOS] Updated the minimum iOS version back down to 12.0.
* [Apple] Merged the iOS and MacOS sources.

There are still some problems with this build.
* Zoom slider not working
* scanWindow not working
* Flash shows briefly when starting scanner.
* Other issues, not fully tested yet.
Known issues:
* [Apple] The zoom slider does not work correctly.
* [Apple] The scan window does not work correctly.
* [Apple] The camera flash briefly shows when the camera is started.

## 6.0.2

Expand Down
4 changes: 3 additions & 1 deletion analysis_options.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@ include: package:lint/analysis_options_package.yaml
linter:
rules:
- combinators_ordering
- comment_references
- require_trailing_commas
- unnecessary_library_directive
- prefer_single_quotes
- prefer_single_quotes
- public_member_api_docs
2 changes: 1 addition & 1 deletion android/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,7 @@ dependencies {
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'
implementation 'com.google.mlkit:barcode-scanning:17.3.0'
}

// org.jetbrains.kotlin:kotlin-bom artifact purpose is to align kotlin stdlib and related code versions.
Expand Down
109 changes: 63 additions & 46 deletions darwin/mobile_scanner/Sources/mobile_scanner/MobileScannerPlugin.swift
Original file line number Diff line number Diff line change
Expand Up @@ -167,21 +167,16 @@ public class MobileScannerPlugin: NSObject, FlutterPlugin, FlutterStreamHandler,
return
}

let barcodes: [VNBarcodeObservation] = results.compactMap({ barcode in
// If there is a scan window, check if the barcode is within said scan window.
if self?.scanWindow != nil && cgImage != nil && !(self?.isBarcodeInsideScanWindow(barcodeObservation: barcode, imageSize: CGSize(width: cgImage!.width, height: cgImage!.height)) ?? false) {
return nil
}

let barcodes: [VNBarcodeObservation] = results.compactMap({ barcode in
return barcode
})

DispatchQueue.main.async {
// If the image is nil, use zero as the size.
guard let image = cgImage else {
// Image not known, default image size to 1
self?.sink?([
"name": "barcode",
"data": barcodes.map({ $0.toMap(width: 1, height: 1) }),
"data": barcodes.map({ $0.toMap(imageWidth: 0, imageHeight: 0, scanWindow: nil)}),
])
return
}
Expand All @@ -194,10 +189,9 @@ public class MobileScannerPlugin: NSObject, FlutterPlugin, FlutterStreamHandler,
"height": Double(image.height),
]


self?.sink?([
"name": "barcode",
"data": barcodes.map({ $0.toMap(width: image.width, height: image.height) }),
"data": barcodes.map({ $0.toMap(imageWidth: image.width, imageHeight: image.height, scanWindow: self?.scanWindow) }),
"image": imageData,
])
}
Expand All @@ -207,6 +201,11 @@ public class MobileScannerPlugin: NSObject, FlutterPlugin, FlutterStreamHandler,
// Add the symbologies the user wishes to support.
barcodeRequest.symbologies = self!.symbologies
}

// Set the region of interest to match scanWindow
if let scanWindow = self?.scanWindow {
barcodeRequest.regionOfInterest = scanWindow
}

try imageRequestHandler.perform([barcodeRequest])
} catch let error {
Expand Down Expand Up @@ -264,24 +263,6 @@ public class MobileScannerPlugin: NSObject, FlutterPlugin, FlutterStreamHandler,
result(nil)
}

func isBarcodeInsideScanWindow(barcodeObservation: VNBarcodeObservation, imageSize: CGSize) -> Bool {
let boundingBox = barcodeObservation.boundingBox

// Adjust boundingBox by inverting the y-axis
let adjustedBoundingBox = CGRect(
x: boundingBox.minX,
y: 1.0 - boundingBox.maxY,
width: boundingBox.width,
height: boundingBox.height
)

let intersects = scanWindow!.contains(adjustedBoundingBox)

// Check if the adjusted bounding box intersects with or is within the scan window
return intersects
}


private func getVideoOrientation() -> AVCaptureVideoOrientation {
#if os(iOS)
var videoOrientation: AVCaptureVideoOrientation
Expand Down Expand Up @@ -473,7 +454,7 @@ public class MobileScannerPlugin: NSObject, FlutterPlugin, FlutterStreamHandler,
device.torchMode = .on
device.unlockForConfiguration()
} catch(_) {

// Do nothing.
}
}

Expand Down Expand Up @@ -526,7 +507,6 @@ public class MobileScannerPlugin: NSObject, FlutterPlugin, FlutterStreamHandler,

/// Set the zoom factor of the camera
func setScaleInternal(_ scale: CGFloat) throws {

if (device == nil) {
throw MobileScannerError.zoomWhenStopped
}
Expand All @@ -545,10 +525,8 @@ public class MobileScannerPlugin: NSObject, FlutterPlugin, FlutterStreamHandler,
actualScale = min(maxZoomFactor, actualScale)

// Limit to 1.0 scale

device.videoZoomFactor = actualScale


device.unlockForConfiguration()
#endif
} catch {
Expand Down Expand Up @@ -620,7 +598,9 @@ public class MobileScannerPlugin: NSObject, FlutterPlugin, FlutterStreamHandler,
try device.lockForConfiguration()
device.torchMode = newTorchMode
device.unlockForConfiguration()
} catch(_) {}
} catch(_) {
// Do nothing.
}

result(nil)
}
Expand Down Expand Up @@ -690,7 +670,7 @@ public class MobileScannerPlugin: NSObject, FlutterPlugin, FlutterStreamHandler,

result([
"name": "barcode",
"data": barcodes.map({ $0.toMap(width: 1, height: 1) }),
"data": barcodes.map({ $0.toMap(imageWidth: Int(ciImage.extent.width), imageHeight: Int(ciImage.extent.height), scanWindow: self.scanWindow) }),
])
})

Expand Down Expand Up @@ -803,28 +783,65 @@ extension VNBarcodeObservation {
return sqrt(pow(p1.x - p2.x, 2) + pow(p1.y - p2.y, 2))
}

public func toMap(width: Int, height: Int) -> [String: Any?] {
let topLeftX = topLeft.x * CGFloat(width)
let topRightX = topRight.x * CGFloat(width)
let bottomRightX = bottomRight.x * CGFloat(width)
let bottomLeftX = bottomLeft.x * CGFloat(width)
let topLeftY = (1 - topLeft.y) * CGFloat(height)
let topRightY = (1 - topRight.y) * CGFloat(height)
let bottomRightY = (1 - bottomRight.y) * CGFloat(height)
let bottomLeftY = (1 - bottomLeft.y) * CGFloat(height)
/// Map this `VNBarcodeObservation` to a dictionary.
///
/// The `imageWidth` and `imageHeight` indicate the width and height of the input image that contains this observation.
public func toMap(imageWidth: Int, imageHeight: Int, scanWindow: CGRect?) -> [String: Any?] {

// Calculate adjusted points based on whether scanWindow is set
let adjustedTopLeft: CGPoint
let adjustedTopRight: CGPoint
let adjustedBottomRight: CGPoint
let adjustedBottomLeft: CGPoint

if let scanWindow = scanWindow {
// When a scanWindow is set, adjust the barcode coordinates to the full image
func adjustPoint(_ point: CGPoint) -> CGPoint {
let x = scanWindow.minX + point.x * scanWindow.width
let y = scanWindow.minY + point.y * scanWindow.height
return CGPoint(x: x, y: y)
}

adjustedTopLeft = adjustPoint(topLeft)
adjustedTopRight = adjustPoint(topRight)
adjustedBottomRight = adjustPoint(bottomRight)
adjustedBottomLeft = adjustPoint(bottomLeft)
} else {
// If no scanWindow, use original points (already normalized to the full image)
adjustedTopLeft = topLeft
adjustedTopRight = topRight
adjustedBottomRight = bottomRight
adjustedBottomLeft = bottomLeft
}

// Convert adjusted points from normalized coordinates to image pixel coordinates
let topLeftX = adjustedTopLeft.x * CGFloat(imageWidth)
let topRightX = adjustedTopRight.x * CGFloat(imageWidth)
let bottomRightX = adjustedBottomRight.x * CGFloat(imageWidth)
let bottomLeftX = adjustedBottomLeft.x * CGFloat(imageWidth)
let topLeftY = (1 - adjustedTopLeft.y) * CGFloat(imageHeight)
let topRightY = (1 - adjustedTopRight.y) * CGFloat(imageHeight)
let bottomRightY = (1 - adjustedBottomRight.y) * CGFloat(imageHeight)
let bottomLeftY = (1 - adjustedBottomLeft.y) * CGFloat(imageHeight)

// Calculate the width and height of the barcode based on adjusted coordinates
let width = distanceBetween(adjustedTopLeft, adjustedTopRight) * CGFloat(imageWidth)
let height = distanceBetween(adjustedTopLeft, adjustedBottomLeft) * CGFloat(imageHeight)

let data = [
// Clockwise, starting from the top-left corner.
"corners": [
["x": bottomLeftX, "y": bottomLeftY],
["x": topLeftX, "y": topLeftY],
["x": topRightX, "y": topRightY],
["x": bottomRightX, "y": bottomRightY],
["x": bottomLeftX, "y": bottomLeftY],
],
"format": symbology.toInt ?? -1,
"rawValue": payloadStringValue ?? "",
"displayValue": payloadStringValue ?? "",
"size": [
"width": distanceBetween(topLeft, topRight) * CGFloat(width),
"height": distanceBetween(topLeft, bottomLeft) * CGFloat(width),
"width": width,
"height": height,
],
] as [String : Any]
return data
Expand Down
6 changes: 0 additions & 6 deletions example/lib/barcode_scanner_controller.dart
Original file line number Diff line number Diff line change
Expand Up @@ -21,8 +21,6 @@ class _BarcodeScannerWithControllerState
torchEnabled: true,
);

StreamSubscription<Object?>? _subscription;

@override
void initState() {
super.initState();
Expand All @@ -44,8 +42,6 @@ class _BarcodeScannerWithControllerState
case AppLifecycleState.resumed:
unawaited(controller.start());
case AppLifecycleState.inactive:
unawaited(_subscription?.cancel());
_subscription = null;
unawaited(controller.stop());
}
}
Expand Down Expand Up @@ -96,8 +92,6 @@ class _BarcodeScannerWithControllerState
@override
Future<void> dispose() async {
WidgetsBinding.instance.removeObserver(this);
unawaited(_subscription?.cancel());
_subscription = null;
super.dispose();
await controller.dispose();
}
Expand Down
2 changes: 1 addition & 1 deletion example/lib/barcode_scanner_window.dart
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ class _BarcodeScannerWithScanWindowState
},
),
BarcodeOverlay(controller: controller, boxFit: boxFit),
ScannerOverlay(
ScanWindowOverlay(
scanWindow: scanWindow,
controller: controller,
),
Expand Down
Loading

0 comments on commit 23360aa

Please sign in to comment.