Files:
The Custom Barcode Scanner example allows the user to scan a QR Code with the camera and view the decoded textual representation.
In this example we'll learn how to use the Camera and the build in ZXing barcode decoding library to scan a QR code and decode it.
The UI of this sample application consists of the custom Camera control and a Label, which shows a preview of the current camera viewport and uses the Label to display decoded barcode data.
Container { horizontalAlignment: HorizontalAlignment.Center verticalAlignment: VerticalAlignment.Top // Custom control for reading barcodes BarcodeDecoder { id: barcodeDecoder onNewBarcodeDetected: { barcodeLabel.text = barcode _barcodeScanner.newBarcodeDetected(barcode) } } }
The BarcodeDecoder custom control shows the video stream that is currently provided by the camera viewport. It configures and opens the Camera upon its creation.
Container { horizontalAlignment: HorizontalAlignment.Center verticalAlignment: VerticalAlignment.Bottom bottomPadding: 20 // Label for displaying required action and barcode scan result Label { id: barcodeLabel text: qsTr("Scan a barcode") textStyle.color: Color.White } }
This Label is used to display the required action and the resulting decoded barcode data after a successful barcode scan has been performed.
BarcodeDecoderControl::BarcodeDecoderControl(Container *parent) : CustomControl(parent) , m_camera(new Camera(parent)) , m_cameraSettings(new CameraSettings(this)) , m_landscapePreviewFrames(false) , m_nbuffers(2) { setRoot(m_camera); bool ok = connect(m_camera, SIGNAL(cameraOpened()), this, SLOT(onCameraOpened())); Q_ASSERT(ok); ok = connect(m_camera, SIGNAL(viewfinderStopped()), this, SLOT(onViewfinderStopped())); Q_ASSERT(ok); //Prepare the camera m_camera->open(CameraUnit::Rear); //Configure camera settings m_camera->getSettings(m_cameraSettings); m_cameraSettings->setCameraMode(CameraMode::Photo); m_cameraSettings->setFocusMode(CameraFocusMode::ContinuousMacro); if (m_camera->applySettings(m_cameraSettings)) qDebug() << "settings applied successfully"; //Prepare the decoder m_reader = Ref<MultiFormatReader>(new MultiFormatReader()); DecodeHints *hints = new DecodeHints(); hints->addFormat(BarcodeFormat_QR_CODE); hints->addFormat(BarcodeFormat_EAN_8); hints->addFormat(BarcodeFormat_EAN_13); hints->addFormat(BarcodeFormat_UPC_A); hints->addFormat(BarcodeFormat_UPC_E); hints->addFormat(BarcodeFormat_DATA_MATRIX); hints->addFormat(BarcodeFormat_CODE_128); hints->addFormat(BarcodeFormat_CODE_39); hints->addFormat(BarcodeFormat_ITF); hints->addFormat(BarcodeFormat_AZTEC); m_reader.object_->setHints(*hints); }
The constructor initializes the member variables. It creates and configures the Camera using CameraSettings and connects its methods to the signals/slots mechanism. It initializes the reader for decoding barcodes from an image, and invokes open() on the Camera.
If the open() call was successful, the cameraOpened() signal is emitted, which we react to in the cameraOpened() signal handler.
void BarcodeDecoderControl::onPreviewFrameAvailable( SharedUCharPointer previewBuffer, quint64 size, unsigned int width, unsigned int height, unsigned int stride) { try { Ref<LuminanceSource> source( new GreyscaleLuminanceSource(previewBuffer.data(), stride, size / stride, 0, 0, width, height)); Ref<Binarizer> binarizer(new HybridBinarizer(source)); Ref<BinaryBitmap> bitmap(new BinaryBitmap(binarizer)); Ref<Result> result; // If the preview buffer is in landscape, we can rotate out bitmap to let us scan 1D codes if (m_landscapePreviewFrames) { Ref<BinaryBitmap> rotated = bitmap->rotateCounterClockwise(); result = m_reader->decode(rotated); } else { result = m_reader->decode(bitmap); } const QString newBarcodeData = QString::fromStdString(result->getText()->getText().data()); if (newBarcodeData != m_barcodeData) { m_barcodeData = newBarcodeData; emit newBarcodeDetected(m_barcodeData); } } catch (std::exception &e) { qDebug() << "+++++++ ERROR: " << e.what() << endl; } m_camera->addPreviewBuffer(previewBuffer, size); }
This method is invoked when a preview frame buffer is available with data. Using ZXing classes to convert the buffer into a BinaryBitmap, which in turn is used with the reader to decode the barcode and save the results. After barcode decoding is successful, it emits the newBarcodeDetected() signal with the textual data as parameter.
void BarcodeDecoderControl::onCameraOpened() { const quint64 bufferSize = m_camera->previewBufferSize(); //Use two buffers for double buffering goodness. for (int i = 0; i < m_nbuffers; i++) m_camera->addPreviewBuffer(QSharedPointer<unsigned char>(new unsigned char[bufferSize]), bufferSize); bool ok = connect(m_camera, SIGNAL(previewFrameAvailable(bb::cascades::multimedia::SharedUCharPointer, quint64, unsigned int, unsigned int, unsigned int)), this, SLOT(onPreviewFrameAvailable(bb::cascades::multimedia::SharedUCharPointer, quint64, unsigned int, unsigned int, unsigned int))); Q_ASSERT(ok); Q_UNUSED(ok); // If the preview frames are oriented East or West, it means we will have to rotate them 90 degrees to reliably detect 1D barcodes if (m_camera->devicePreviewFrameDirection() == DisplayDirection::East || m_camera->devicePreviewFrameDirection() == DisplayDirection::West) m_landscapePreviewFrames = true; m_camera->startViewfinder(); }
This method is invoked when the Camera is opened. It adds unsigned char buffers to store the data from the preview frame and connects to the signal/slots mechanism to recieve previewFrameAvailable() signals. Before the method returns it starts the view finder, which begins streaming preview frames upon its success and emits previewFrameAvailable() signals.
void BarcodeDecoderControl::startScanning() const { m_camera->startViewfinder(); } void BarcodeDecoderControl::stopScanning() const { m_camera->stopViewfinder(); } void BarcodeDecoderControl::onViewfinderStopped() { m_barcodeData.clear(); }
These are control methods to start/stop view finder streaming and clearing of the barcode data variable.
BarcodeScannerApp::BarcodeScannerApp(QObject* parent) : QObject(parent) , m_invokeManager(new InvokeManager(this)) , m_invoked(false) , m_pooled(false) , m_player(new MediaPlayer(this)) { m_player->setSourceUrl(QDir::currentPath() + "/app/native/assets/sounds/boopdoop.mp3"); bool ok = connect(m_invokeManager, SIGNAL(invoked(const bb::system::InvokeRequest&)), this, SLOT(onInvoked(const bb::system::InvokeRequest&))); Q_ASSERT(ok); ok = connect(m_invokeManager, SIGNAL(cardPooled(const bb::system::CardDoneMessage&)), this, SLOT(onCardPooled(const bb::system::CardDoneMessage&))); Q_ASSERT(ok); switch (m_invokeManager->startupMode()) { case ApplicationStartupMode::LaunchApplication: m_invoked = false; break; case ApplicationStartupMode::InvokeApplication: case ApplicationStartupMode::InvokeCard: m_invoked = true; break; default: break; } qDebug() << "+++++++++ Application invoked: " << m_invoked << endl; }
The constructor initializes various member variables. It creates a MediaPlayer for playing sounds upon barcode detection and intializes a InvokeManager to deal with invoke requests when the sample is launched as a card by the "barcodeinvoker" sample. Here it distinguishes how it was launched and sets the appropriate member variables to dictate its behaviour as an application or as a card.
void BarcodeScannerApp::newBarcodeDetected(const QString &barcode) { m_player->play(); if (m_invoked) { CardDoneMessage message; message.setData(barcode); message.setDataType("text/plain"); message.setReason("Barcode scanned!"); m_invokeManager->sendCardDone(message); } } void BarcodeScannerApp::onCardPooled(const bb::system::CardDoneMessage&) { m_pooled = true; Q_EMIT stopScan(); } void BarcodeScannerApp::onInvoked(const bb::system::InvokeRequest&) { if (m_pooled) Q_EMIT startScan(); }
This method is invoked when a newBarcodeDetected() signal is received from the BarcodeDecoder class. It plays a sound upon receiving the data. If the sample was launched as a Card, a CardDoneMessage is generated and sent as response to the parent of this Card. Once the card is done, it transitions off screen and is pooled (ready to be invoked again) at which time it fires a cardPooled() signal.