Skip to content

Commit

Permalink
[expo-barcod-scanner] Return bounding box (expo#8865)
Browse files Browse the repository at this point in the history
# Why

Fixes expo#8754 and expo#2700.

# How

- Readd the bounding box information on Android (previously added by expo#2904).
- Add the same functionality to iOS.
- Update NCL and test-suite

Unfortunately, currently, we don't always return this information on iOS. For some barcode types, there is a problem with converting point coordinates from one space to another. 

# Test Plan

Tested using updated NCL and test-suite:
- iOS ✅
- Android ✅
  • Loading branch information
lukmccall authored Jun 23, 2020
1 parent 25ceeea commit bd4c824
Show file tree
Hide file tree
Showing 20 changed files with 367 additions and 107 deletions.
92 changes: 62 additions & 30 deletions apps/native-component-list/src/screens/BarCodeScannerScreen.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { BarCodeScanner } from 'expo-barcode-scanner';
import { BarCodeScanner, BarCodePoint, BarCodeEvent, BarCodeBounds } from 'expo-barcode-scanner';
import * as Permissions from 'expo-permissions';
import * as ScreenOrientation from 'expo-screen-orientation';
import React from 'react';
Expand All @@ -11,21 +11,16 @@ const BUTTON_COLOR = Platform.OS === 'ios' ? '#fff' : '#666';
interface State {
isPermissionsGranted: boolean;
type: any;
cornerPoints?: any[];
cornerPoints?: BarCodePoint[];
alerting: boolean;
haveDimensions: boolean;
canvasHeight?: number;
canvasWidth?: number;
boundingBox?: {
origin: {
x: number;
y: number;
};
size: {
width: number;
height: number;
};
};
boundingBox?: BarCodeBounds;
cornerPointsString?: string;
showBoundingBox: boolean;
showText: boolean;
data: string;
}

export default class BarcodeScannerExample extends React.Component<object, State> {
Expand All @@ -40,6 +35,9 @@ export default class BarcodeScannerExample extends React.Component<object, State
type: BarCodeScanner.Constants.Type.back,
alerting: false,
haveDimensions: false,
showBoundingBox: false,
data: '',
showText: false,
};

componentDidFocus = async () => {
Expand All @@ -48,7 +46,9 @@ export default class BarcodeScannerExample extends React.Component<object, State
};

toggleAlertingAboutResult = () => {
this.setState({ alerting: !this.state.alerting });
this.setState(({ alerting }) => ({
alerting: !alerting,
}));
};

toggleScreenOrientationState = () => {
Expand Down Expand Up @@ -86,10 +86,10 @@ export default class BarcodeScannerExample extends React.Component<object, State
<Svg.Circle
cx={point.x}
cy={point.y}
r={2}
strokeWidth={0.1}
stroke="gray"
fill="green"
r={3}
strokeWidth={0.5}
stroke="#CF4048"
fill="#CF4048"
/>
);
}
Expand Down Expand Up @@ -123,17 +123,25 @@ export default class BarcodeScannerExample extends React.Component<object, State
stroke="#e74c3c"
fill="#f1c40f"
/>
{this.state.boundingBox && (
<Svg.Rect
x={this.state.boundingBox.origin.x}
y={this.state.boundingBox.origin.y}
width={this.state.boundingBox.size.width}
height={this.state.boundingBox.size.height}
{this.state.showBoundingBox && this.state.cornerPointsString && (
<Svg.Polygon
points={this.state.cornerPointsString}
strokeWidth={2}
stroke="#9b59b6"
stroke="#582E6E"
fill="none"
/>
)}
{this.state.showText && this.state.boundingBox && (
<Svg.Text
fill="#CF4048"
stroke="#CF4048"
fontSize="14"
x={this.state.boundingBox.origin.x}
y={this.state.boundingBox.origin.y - 8}>
{this.state.data}
</Svg.Text>
)}

{circles}
</Svg.Svg>
)}
Expand All @@ -145,27 +153,51 @@ export default class BarcodeScannerExample extends React.Component<object, State
title="Orientation"
onPress={this.toggleScreenOrientationState}
/>
<Button color={BUTTON_COLOR} title="Bounding box" onPress={this.toggleBoundingBox} />
<Button color={BUTTON_COLOR} title="Text" onPress={this.toggleText} />
<Button color={BUTTON_COLOR} title="Alerting" onPress={this.toggleAlertingAboutResult} />
</View>
</View>
);
}

toggleType = () =>
this.setState({
this.setState(({ type }) => ({
type:
this.state.type === BarCodeScanner.Constants.Type.back
type === BarCodeScanner.Constants.Type.back
? BarCodeScanner.Constants.Type.front
: BarCodeScanner.Constants.Type.back,
});
}));

handleBarCodeScanned = (data: any) => {
toggleText = () =>
this.setState(({ showText }) => ({
showText: !showText,
}));

toggleBoundingBox = () =>
this.setState(({ showBoundingBox }) => ({
showBoundingBox: !showBoundingBox,
}));

getPointsString = (barCodePoints?: BarCodePoint[]): string | undefined => {
if (!barCodePoints) {
return;
}
return barCodePoints.map(({ x, y }) => `${Math.round(x)},${Math.round(y)}`).join(' ');
};

handleBarCodeScanned = (barCodeEvent: BarCodeEvent) => {
if (this.state.alerting) {
requestAnimationFrame(() => {
alert(JSON.stringify(data));
alert(JSON.stringify(barCodeEvent));
});
}
this.setState({ cornerPoints: data.cornerPoints, boundingBox: data.bounds });
this.setState({
data: barCodeEvent.data,
cornerPoints: barCodeEvent.cornerPoints,
boundingBox: barCodeEvent.bounds,
cornerPointsString: this.getPointsString(barCodeEvent.cornerPoints),
});
};
}

Expand Down
80 changes: 76 additions & 4 deletions apps/test-suite/tests/BarCodeScanner.js
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
'use strict';

import { Asset } from 'expo-asset';
import { BarCodeScanner } from 'expo-barcode-scanner';
import * as Permissions from 'expo-permissions';
import React from 'react';
import { Platform } from 'react-native';
import * as Permissions from 'expo-permissions';
import { BarCodeScanner } from 'expo-barcode-scanner';
import { Asset } from 'expo-asset';
import * as TestUtils from '../TestUtils';

import * as TestUtils from '../TestUtils';
import { mountAndWaitFor as originalMountAndWaitFor } from './helpers';

export const name = 'BarCodeScanner';
Expand All @@ -16,6 +16,27 @@ export async function test(t, { setPortalChild, cleanupPortal }) {
const shouldSkipTestsRequiringPermissions = await TestUtils.shouldSkipTestsRequiringPermissionsAsync();
const describeWithPermissions = shouldSkipTestsRequiringPermissions ? t.xdescribe : t.describe;

const testPoint = (value, expected, inaccuracy) => {
t.expect(value).toBeGreaterThanOrEqual(expected - inaccuracy);
t.expect(value).toBeLessThan(expected + inaccuracy);
};

const testBarCodeBounds = (bounds, expectedBounds, sizeInaccuracy, originInaccuracy) => {
t.expect(bounds).toBeDefined();
t.expect(typeof bounds.origin).toBe('object');
t.expect(typeof bounds.origin.x).toBe('number');
t.expect(typeof bounds.origin.y).toBe('number');
t.expect(typeof bounds.size).toBe('object');
t.expect(typeof bounds.size.width).toBe('number');
t.expect(typeof bounds.size.height).toBe('number');

testPoint(bounds.origin.x, expectedBounds.origin.x, originInaccuracy);
testPoint(bounds.origin.y, expectedBounds.origin.y, originInaccuracy);

testPoint(bounds.size.width, expectedBounds.size.width, sizeInaccuracy);
testPoint(bounds.size.height, expectedBounds.size.height, sizeInaccuracy);
};

describeWithPermissions('BarCodeScanner', () => {
const mountAndWaitFor = (child, propName = 'ref') =>
new Promise(resolve => {
Expand Down Expand Up @@ -62,6 +83,23 @@ export async function test(t, { setPortalChild, cleanupPortal }) {
t.expect(result[0]).toBeDefined();
t.expect(result[0].type).toEqual(BarCodeScanner.Constants.BarCodeType.qr);
t.expect(result[0].data).toEqual('https://expo.io/');
testBarCodeBounds(
result[0].bounds,
{
origin: {
x: 40,
y: 40,
},
size: {
width: 210,
height: 210,
},
},
1,
1
);
t.expect(result[0].cornerPoints).toBeDefined();
t.expect(result[0].cornerPoints.length).toEqual(4);
});

t.it('scans a QR code from photo asset', async () => {
Expand All @@ -74,6 +112,23 @@ export async function test(t, { setPortalChild, cleanupPortal }) {
t.expect(result[0]).toBeDefined();
t.expect(result[0].type).toEqual(BarCodeScanner.Constants.BarCodeType.qr);
t.expect(result[0].data).toEqual('http://en.m.wikipedia.org');
testBarCodeBounds(
result[0].bounds,
{
origin: {
x: 94,
y: 94,
},
size: {
width: 294,
height: 296,
},
},
10,
10
);
t.expect(result[0].cornerPoints).toBeDefined();
t.expect(result[0].cornerPoints.length).toEqual(4);
});

t.it('scans a QR code from base64 URL', async () => {
Expand Down Expand Up @@ -101,6 +156,23 @@ export async function test(t, { setPortalChild, cleanupPortal }) {
t.expect(result[0]).toBeDefined();
t.expect(result[0].type).toEqual(BarCodeScanner.Constants.BarCodeType.datamatrix);
t.expect(result[0].data).toEqual('https://expo.io/');
testBarCodeBounds(
result[0].bounds,
{
origin: {
x: 7,
y: 7,
},
size: {
width: 141,
height: 141,
},
},
1,
1
);
t.expect(result[0].cornerPoints).toBeDefined();
t.expect(result[0].cornerPoints.length).toEqual(4);
});
}

Expand Down
38 changes: 37 additions & 1 deletion docs/pages/versions/unversioned/sdk/bar-code-scanner.md
Original file line number Diff line number Diff line change
Expand Up @@ -113,7 +113,7 @@ import { BarCodeScanner } from 'expo-barcode-scanner';

- **barCodeTypes (_Array\<string\>_)** -- An array of bar code types. Usage: `BarCodeScanner.Constants.BarCodeType.<codeType>` where `codeType` is one of these [listed above](#supported-formats). Defaults to all supported bar code types. It is recommended to provide only the bar code formats you expect to scan to minimize battery usage. For example: `barCodeTypes={[BarCodeScanner.Constants.BarCodeType.qr]}`.

- **onBarCodeScanned (_function_)** -- A callback that is invoked when a bar code has been successfully scanned. The callback is provided with an object of the shape `{ type: BarCodeScanner.Constants.BarCodeType, data: string }`, where the type refers to the bar code type that was scanned and the data is the information encoded in the bar code (in this case of QR codes, this is often a URL).
- **onBarCodeScanned (_function_)** -- A callback that is invoked when a bar code has been successfully scanned. The callback is provided with an [BarCodeScanner.BarCodeScannerResult](#barcodescannerbarcodescannerresult).

## Methods

Expand All @@ -130,3 +130,39 @@ Scan bar codes from the image given by the URL.
#### Returns

A possibly empty array of objects of the shape `{ type: BarCodeScanner.Constants.BarCodeType, data: string }`, where the type refers to the bar code type that was scanned and the data is the information encoded in the bar code.

## Types

### `BarCodeScanner.BarCodePoint`

Object of type `BarCodePoint` contains following keys:

- **x (_number_)** -- The x value.
- **y (_number_)** -- The y value.

Those coordinates are represented in the coordinate space of the barcode source (e.g. when you are using the barcode scanner view, these values are adjusted to the dimensions of the view).

### `BarCodeScanner.BarCodeSize`

Object of type `BarCodeSize` contains following keys:

- **height (_number_)** -- The height value.
- **width (_number_)** -- The width value.

### `BarCodeBounds`

Object of type `BarCodeBounds` contains following keys:

- **origin : [BarCodeScanner.BarCodePoint](#barcodescannerbarcodepoint)** -- The origin point of the bounding box.
- **size : [BarCodeScanner.BarCodeSize](#barcodescannerbarcodesize)** -- The size of the bounding box.

### `BarCodeScanner.BarCodeScannerResult`

Object of type `BarCodeScannerResult` contains following keys:

- **type (_BarCodeScanner.Constants.BarCodeType_)** -- The barcode type.
- **data (_string_)** -- The information encoded in the bar code.
- **bounds : [BarCodeScanner.BarCodeBounds](#barcodescannerbarcodebounds)** -- (_Optional_) The `BarCodeBounds` object.
- **cornerPoints : Array\<[BarCodeScanner.BarCodePoint](#barcodescannerbarcodepoint)\>** -- (_Optional_) Corner points of the bounding box.

> **NOTE** `bounds` and `cornerPoints` are not always available. On iOS, for `code39` and `pdf417` you don't get those values. Moreover, on iOS, those values don't have to bounds the whole barcode. For some types, they will represent the area used by the scanner.
1 change: 1 addition & 0 deletions packages/expo-barcode-scanner/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
### 🎉 New features

- Delete `prop-types` in favor of TypeScript. ([#8678](https://github.com/expo/expo/pull/8678) by [@EvanBacon](https://github.com/EvanBacon))
- `BarCodeScanner` is now returning barcode's bounding box on iOS. ([#8865](https://github.com/expo/expo/pull/8865) by [@lukmccall](https://github.com/lukmccall))

### 🐛 Bug fixes

Expand Down
Original file line number Diff line number Diff line change
@@ -1,23 +1,20 @@
package expo.modules.barcodescanner;

import android.os.Bundle;
import androidx.core.util.Pools;
import android.util.Pair;

import java.util.List;

import org.unimodules.core.interfaces.services.EventEmitter;
import org.unimodules.interfaces.barcodescanner.BarCodeScannerResult;
import expo.modules.barcodescanner.utils.BarCodeScannerEventHelper;

import androidx.core.util.Pools;
import expo.modules.barcodescanner.utils.BarCodeScannerResultSerializer;

public class BarCodeScannedEvent extends EventEmitter.BaseEvent {
private static final Pools.SynchronizedPool<BarCodeScannedEvent> EVENTS_POOL =
new Pools.SynchronizedPool<>(3);

private BarCodeScannerResult mBarCode;
private int mViewTag;
private List<Bundle> mCornerPoints;
private Bundle mBoundingBox;
private float mDensity;

private BarCodeScannedEvent() {}

Expand All @@ -33,9 +30,7 @@ public static BarCodeScannedEvent obtain(int viewTag, BarCodeScannerResult barCo
private void init(int viewTag, BarCodeScannerResult barCode, float density) {
mViewTag = viewTag;
mBarCode = barCode;
Pair<List<Bundle>, Bundle> bundles = BarCodeScannerEventHelper.getCornerPointsAndBoundingBox(barCode.getCornerPoints(), density);
mCornerPoints = bundles.first;
mBoundingBox = bundles.second;
mDensity = density;
}

/**
Expand All @@ -58,16 +53,8 @@ public String getEventName() {

@Override
public Bundle getEventBody() {
Bundle event = new Bundle();
Bundle event = BarCodeScannerResultSerializer.toBundle(mBarCode, mDensity);
event.putInt("target", mViewTag);
event.putString("data", mBarCode.getValue());
event.putInt("type", mBarCode.getType());
if (!mCornerPoints.isEmpty()) {
Bundle cornerPoints[] = new Bundle[mCornerPoints.size()];
event.putParcelableArray("cornerPoints", mCornerPoints.toArray(cornerPoints));
event.putBundle("bounds", mBoundingBox);
}

return event;
}
}
Loading

0 comments on commit bd4c824

Please sign in to comment.