Deprecation warning

Please note that this is outdated documentation for an older release of the Scandit Barcode Scanner SDK.

We are deprecating the 5.x API on all platforms (except Linux). Release 5.19 in April 2021 will be our final. Applications running 5.x will continue to work, and we will continue to release critical bug fixes and security patches only, for one year. We encourage you to migrate to 6.x and take advantage of our latest / advanced features and improved performance.

You'll find the updated documentation at: Data Capture SDK Documentation for Android

Scan multiple barcodes at once with MatrixScan

Motivation

Sometimes you might want to scan several codes at once in order to save time. Moreover, you might want to add some visual help for the user to find one particular barcode among several ones (e.g., highlighting the right barcode in green). You can achieve this by using MatrixScan.

MatrixScan

MatrixScan tracks recognized barcodes over time by making it easier to use.

Note: MatrixScan currently only works with 1D codes. We are working on adding 2D!

Enabling MatrixScan

To implement MatrixScan

  • Enable MatrixScan in the scan settings by setting setMatrixScanEnabled(boolean) to true.
  • Set the maximum number of codes to be decoded every frame through setMaxNumberOfCodesPerFrame(int) to something higher than 1, depending on the environment of the codes it is a good idea to set it higher than the number of codes that you actually want to scan.
  • Change the GUI style through setGuiStyle(int) to GUI_STYLE_MATRIX_SCAN .
  • In the didScan function, wait until the number of expected codes have been decoded, then store the codes and pause/stop the session. If you pause and plan to resume but want to start a new session, make sure that you clear the session first.

At this point you can start the barcode picker and any recognized barcodes of the enabled symbologies will be highlighted by a filled green rectangle. Barcodes that have been localized but not recognized will be highlighted by a green border.

public void initializeScanner() {
ScanSettings settings = ScanSettings.create();
// Enable the relevant symbologies.
settings.setSymbologyEnabled(Barcode.SYMBOLOGY_EAN13, true);
settings.setSymbologyEnabled(Barcode.SYMBOLOGY_CODE_39, true);
// Enable Matrix Scanning.
settings.setMatrixScanEnabled(true);
// The maximum number of codes to be decoded every frame.
settings.setMaxNumberOfCodesPerFrame(3);
// Create the picker and set the listener.
BarcodePicker picker = new BarcodePicker(scanSettings);
picker.setOnScanListener(this);
// Set the GUI style accordingly.
picker.getOverlayView().setGuiStyle(ScanOverlay.GUI_STYLE_MATRIX_SCAN);
// Add the picker to the view hierarchy and start it.
setContentView(picker)
picker.startScanning();
}
@Override
public void didScan(ScanSession session) {
// Number of expected barcodes.
int numExpectedCodes = 3;
// Get all the scanned barcodes from the session.
List<Barcode> allCodes = session.getAllRecognizedCodes();
// If the number of scanned codes is greater than or equal to the number of expected barcodes,
// pause the scanning and clear the session (to remove recognized barcodes).
if (allCodes.size() >= numExpectedCodes) {
// Stop scanning or pause and clear the session.
session.stopScanning();
...
}
}

Rejecting unwanted codes

Like normal scanning, MatrixScan provides you with the option to reject codes. Rejected codes are not added to the scan session and do not provide any feedback like vibrating or beeping. In MatrixScan the rejected codes are still drawn on the screen but in a different color. This gives the user visual feedback that a certain code is not the one you are looking for. To implement rejection

Note: Rejecting in the didScan function is not allowed and will throw an exception.

public void initializeScanner() {
... // Same initialization as above.
// Set the process frame listener.
picker.setProcessFrameListener();
}
@Override
public void didProcess(byte[] imageBuffer, int width, int height, ScanSession session) {
Map<Long, TrackedBarcode> trackedCodes = session.getTrackedCodes();
for (TrackedBarcode trackedCode : trackedCodes.values()) {
// Reject all Code39 barcodes.
if (trackedCode.getSymbology() == Barcode.SYMBOLOGY_CODE39) {
session.rejectTrackedCode(trackedCode);
}
}
}

Providing your own visualization

MatrixScan returns the locations of tracked barcodes with every processed frame. This allows you to replace the default visualization with your own. For example, you might want to overlay the barcodes with images of the products they refer to. To add your own visualization, you have two options:

  • Take advantage of the Advanced Overlays Framework, which is the high-level API for the MatrixScan that was designed and developed specifically for this purpose.
  • Implement your own mechanism for drawing and animating the visualizations for every tracked barcode.

Both of these approaches are described in the sections below.

Note: The Advanced Overlays Framework is the easier and preferred method of implementing custom visualizations, as it takes care of all the animations for you and should cover the majority of use cases.

With the Advanced Overlays Framework

As mentioned before, the Advanced Overlays Framework is a high-level interface designed to simplify the process of applying your own visualizations to MatrixScan. This framework consists of several classes and interfaces that can be found within the com.scandit.matrixscan package.

Below there are some implementation guidelines that will help you better understand how the Advanced Overlays Framework works and how it should be used.

The main component of the Advanced Overlays Framework is the MatrixScan class. In order to use the framework you will have to instantiate it - there are 2 constructors available, both of them require an instance of BarcodePicker and an instance of MatrixScanListener (more on that later). To add a custom overlay to be drawn on top of the tracked barcodes, simply call the addOverlay(MatrixScanOverlay) method. The MatrixScanOverlay is an abstract View inheriting from FrameLayout - every custom visualization added to the MatrixScan has to inherit from it.

To make things easier, the Advanced Overlays Framework provides two concrete subclasses of the MatrixScanOverlay : SimpleMatrixScanOverlay and ViewBasedMatrixScanOverlay . The first one enables you to draw colorful, rectangle overlays on top of the tracked barcodes - similar to the way MatrixScan without Advanced Overlays Framework works, except with SimpleMatrixScanOverlay you get more control over the color of every tracked barcode. The ViewBasedMatrixScanOverlay , on the other hand, uses View class instances as visualizations for all of the tracked barcodes.

The Advanced Overlays Framework doesn't limit you to just one custom overlay - you can call the addOverlay(MatrixScanOverlay) method multiple times. You just have to remember, that the overlays will be displayed on top of each other in the order of their addition. Note: Adding too many overlays can decrease the performance and responsiveness of your app, especially on low-end devices.

Finally, you can manipulate the behavior of the Advanced Overlays Framework with the MatrixScanListener , mentioned earlier in this section. This interface consists of 2 callbacks. The matrixScan(MatrixScan, Frame) method will be called every time a Frame is being processed by the MatrixScan. The shouldRejectCode(MatrixScan, TrackedBarcode) method, on the other hand, can be used to define an extra condition for rejecting a barcode (this is analogous to the code rejection described in the Rejecting unwanted codes section).

SimpleMatrixScanOverlay

In order to manipulate the color of the rectangle drawn by the SimpleMatrixScanOverlay for a particular barcode, you need to implement the SimpleMatrixScanOverlayListener and provide it to the SimpleMatrixScanOverlay's constructor. Within this listener there is a callback method named getColorForCode(TrackedBarcode, long) that will be used to set a color of the augmentation corresponding to the given tracked barcode. Example:

@Override
public int getColorForCode(TrackedBarcode barcode, long trackingId) {
if (barcode.getSymbology() == Barcode.SYMBOLOGY_EAN13) {
// every EAN13 barcode will have a gold augmentation drawn on top of it
return ContextCompat.getColor(context, R.color.gold);
} else {
// any other barcode with receive a crimson augmentation
return ContextCompat.getColor(context, R.color.crimson);
}
return bubbles.get(trackingId).highlightColor;
}

The SimpleMatrixScanOverlayListener also provides you with a onCodeTouched(TrackedBarcode, long) method, that will be called every time the given augmentation is touched, so that you can perform some extra action at that time e.g.:

@Override
public void onCodeTouched(TrackedBarcode barcode, long trackingId) {
// show a toast with basic data of the clicked barcode
Toast.makeText(context, barcode.getSymbologyName() + " : " + barcode.getData(), Toast.LENGTH_SHORT);
}
ViewBasedMatrixScanOverlay

Similarly to the SimpleMatrixScanOverlay , the ViewBasedMatrixScanOverlay is also equipped with a listener: ViewBasedMatrixScanOverlayListener . Apart from a onCodeTouched(TrackedBarcode, long) method (which works exactly like the one from SimpleMatrixScanOverlayListener ), it provides two useful callbacks for manipulating the view augmentation of each barcode: getViewForCode(TrackedBarcode, long) and getOffsetForCode(TrackedBarcode, long) . Example:

@Override
public View getViewForCode(TrackedBarcode barcode, long trackingId) {
if (barcode.getSymbology() == Barcode.SYMBOLOGY_EAN13) {
return getImageViewForEan13();
} else {
return getDefaultImageView();
}
}
private ImageView getImageViewForEan13() {
...
}
private ImageView getDefaultImageView() {
...
}
@Override
public Point getOffsetForCode(TrackedBarcode barcode, long trackingId) {
// bear in mind that, even though every ViewBasedMatrixScanOverlay's augmentation is anchored
// in the middle of the tracked barcode, you still need to provide an offset to actually centralize
// the view, as the view is drawn from it's top-left corner.
if (barcode.getSymbology() == Barcode.SYMBOLOGY_EAN13) {
return new Point(- getEan13ViewSizeInPx() / 2, - getEan13ViewSizeInPx() / 2);
} else {
return new Point(- getDefaultViewSizeInPx() / 2, - getDefaultViewSizeInPx() / 2);
}
}
private int getEan13ViewSizeInPx() {
...
}
private int getDefaultViewSizeInPx() {
...
}

Note: In the documentation a visualization for a single barcode is also often called an augmentation. Therefore, a ViewBasedMatrixScanOverlay consists of multiple augmentations (one augmentation for every tracked barcode currently within the camera feed).

Important: By default, every ViewBasedMatrixScanOverlay's augmentation is anchored in the middle of its tracked barcode. This can be changed globally for the entire ViewBasedMatrixScanOverlay , by applying the TOP_EDGE_CENTER constant to the ViewBasedMatrixScanOverlay's constructor - as a result every augmentation will be anchored in the middle of the top edge of its tracked barcode. Such placement is dedicated for augmentations that provide information relevant to the code in a bubble-like shape above it.

Without the Advanced Overlays Framework

If for some reason the Advanced Overlays Framework doesn't suit your needs, you can still replace the default visualization with your own, by following the steps described below

  • Set a ProcessFrameListener on the picker setProcessFrameListener(ProcessFrameListener)
  • In the didProcess function you fetch the currently tracked barcodes from the session through getTrackedCodes() . The object is a map with Long keys which identify the TrackedBarcodes over different frames.
  • Compare the tracked barcodes of the current frame with the one from the previous one and change your visualization accordingly. Instead of using getLocation() which gives you the exact location of the barcode in the frame you should generally use getPredictedLocation() which gives you a predicted location for the barcode. Using the predicted location avoids lagging behind the camera feed that is displayed.
  • There are some state transitions where animating from the previous location of a tracked barcode to its current position can end up with weird artifacts as the edge ordering of the location is not stable. If your visualization depends on the edge ordering make sure that you query shouldAnimateFromPreviousToNextState() before animating. The default visualization makes use of this as it draws the location with all four edges and therefore depends on the edge ordering, if the order of the edges were to change it is possible that an animation would flip the visualization which is not what happens to the actual barcode. However, a visualization that just draws something in the center of the location does not depend on the edge ordering and can ignore shouldAnimateFromPreviousToNextState() .
  • When animating your visualization take into consideration getDeltaTimeForPrediction which tells you how long it will take the code to move to the predicted location. This time generally is about as long as it will take the barcode picker to process the next frame, giving you a new predicted location right as your previous animation is coming to an end.

Important: The coordinates you get back from getLocation() and getPredictedLocation() are in the coordinate system of the processed frame. To visualize them on top of the picker's camera feed you have to convert them to the picker's coordinate system which you do by calling convertPointToPickerCoordinates(Point) .

Important: Don't forget that the didProcess function is not called on the mainThread, you have to switch to it to change the UI.

public Map<Long, Visualization> visualizations = new HashMap<>();
public class Visualization {
// This is your view or an object holding data for your view.
}
public void initializeScanner() {
... // Same initialization as at the top.
// Set the process frame listener.
picker.setProcessFrameListener();
}
@Override
public void didScan(ScanSession session) {
Map<Long, TrackedBarcode> trackedCodes = session.getTrackedCodes();
// Iterate over the current visualizations and remove all that are no longer tracked.
List<Long> currentIds = new ArrayList(visualizations.keySet());
for (Long id : currentIds) {
if (!trackedCodes.containsKey(id)) {
visualizations.remove(id);
// Maybe add an animation for barcodes that vanish.
}
}
// Iterate over all the currently tracked barcodes, update old ones and add new ones.
for (Map.Entry<Long, TrackedBarcode> entry : trackedCodes.entrySet()) {
if (visualizations.containsKey(entry.getKey())) {
// Update the visualization
if (entry.getValue().shouldAnimateFromPreviousToNextState()) {
// Animate to the new predicted location.
}
} else {
// Add a new visualization, maybe animate the appearance.
}
}
}