culturered_client/PhotoDisplay/qzxing/QZXing.cpp

729 lines
22 KiB
C++
Raw Permalink Normal View History

2024-09-07 03:34:44 +00:00
#include "QZXing.h"
#include <zxing/common/GlobalHistogramBinarizer.h>
#include <zxing/Binarizer.h>
#include <zxing/BinaryBitmap.h>
#include <zxing/MultiFormatReader.h>
#include <zxing/DecodeHints.h>
#include <zxing/ResultMetadata.h>
#include <zxing/common/detector/WhiteRectangleDetector.h>
#include <zxing/InvertedLuminanceSource.h>
#include "CameraImageWrapper.h"
#include "ImageHandler.h"
#include <QTime>
#include <QUrl>
#include <QFileInfo>
#include <QColor>
#if QT_VERSION < QT_VERSION_CHECK(6, 0, 0)
#include <QtCore/QTextCodec>
#else
#include <QStringDecoder>
#endif
#include <QDebug>
#ifdef ENABLE_ENCODER_QR_CODE
#include <zxing/qrcode/encoder/Encoder.h>
#include <zxing/qrcode/ErrorCorrectionLevel.h>
#endif // ENABLE_ENCODER_QR_CODE
#ifdef QZXING_MULTIMEDIA
#if QT_VERSION >= 0x060200
#include "QZXingFilterVideoSink.h"
#else
#include "QZXingFilter.h"
#endif //QT_VERSION
#endif //QZXING_MULTIMEDIA
#ifdef QZXING_QML
#if QT_VERSION >= 0x040700 && QT_VERSION < 0x050000
#include <QtDeclarative>
#elif QT_VERSION >= 0x050000
#include <QtQml/qqml.h>
#endif
#include <QQmlEngine>
#include <QQmlContext>
#include <QQuickImageProvider>
#include "QZXingImageProvider.h"
#endif //QZXING_QML
using namespace zxing;
QZXing::QZXing(QObject *parent) : QObject(parent), tryHarder_(false), lastDecodeOperationSucceded_(false)
{
decoder = new MultiFormatReader();
setDecoder(DecoderFormat_QR_CODE |
DecoderFormat_DATA_MATRIX |
DecoderFormat_UPC_E |
DecoderFormat_UPC_A |
DecoderFormat_UPC_EAN_EXTENSION |
DecoderFormat_RSS_14 |
DecoderFormat_RSS_EXPANDED |
DecoderFormat_PDF_417 |
DecoderFormat_MAXICODE |
DecoderFormat_EAN_8 |
DecoderFormat_EAN_13 |
DecoderFormat_CODE_128 |
DecoderFormat_CODE_93 |
DecoderFormat_CODE_39 |
DecoderFormat_CODABAR |
DecoderFormat_ITF |
DecoderFormat_Aztec);
setTryHarderBehaviour(TryHarderBehaviour_Rotate |
TryHarderBehaviour_ThoroughScanning);
setSourceFilterType(SourceFilter_ImageNormal);
imageHandler = new ImageHandler();
}
QZXing::~QZXing()
{
if (imageHandler)
delete imageHandler;
if (decoder)
delete decoder;
}
QZXing::QZXing(QZXing::DecoderFormat decodeHints, QObject *parent) : QObject(parent), lastDecodeOperationSucceded_(false)
{
decoder = new MultiFormatReader();
imageHandler = new ImageHandler();
setDecoder(decodeHints);
setSourceFilterType(SourceFilter_ImageNormal);
}
#ifdef QZXING_QML
#if QT_VERSION >= 0x040700
void QZXing::registerQMLTypes()
{
qmlRegisterType<QZXing>("QZXing", 3, 3, "QZXing");
#ifdef QZXING_MULTIMEDIA
qmlRegisterType<QZXingFilter>("QZXing", 3, 3, "QZXingFilter");
#endif //QZXING_MULTIMEDIA
}
#endif //QT_VERSION >= Qt 4.7
#if QT_VERSION >= 0x050000
void QZXing::registerQMLImageProvider(QQmlEngine& engine)
{
engine.addImageProvider(QLatin1String("QZXing"), new QZXingImageProvider());
}
#endif //QT_VERSION >= Qt 5.0
#endif //QZXING_QML
void QZXing::setTryHarder(bool tryHarder)
{
tryHarder_ = tryHarder;
}
bool QZXing::getTryHarder()
{
return tryHarder_;
}
void QZXing::setTryHarderBehaviour(QZXing::TryHarderBehaviourType tryHarderBehaviour)
{
tryHarderType = tryHarderBehaviour;
}
QZXing::TryHarderBehaviourType QZXing::getTryHarderBehaviour()
{
return tryHarderType;
}
void QZXing::setSourceFilterType(QZXing::SourceFilterType sourceFilter)
{
imageSourceFilter = sourceFilter;
}
QZXing::SourceFilterType QZXing::getSourceFilterType()
{
return imageSourceFilter;
}
void QZXing::setAllowedExtensions(const QVariantList& extensions)
{
std::set<int> allowedExtensions;
for (const QVariant& extension: extensions) {
allowedExtensions.insert(extension.toInt());
}
allowedExtensions_ = allowedExtensions;
}
QVariantList QZXing::getAllowedExtensions()
{
QVariantList allowedExtensions;
for (const int& extension: allowedExtensions_) {
allowedExtensions << extension;
}
return allowedExtensions;
}
QString QZXing::decoderFormatToString(int fmt)
{
switch (fmt) {
case DecoderFormat_Aztec:
return "AZTEC";
case DecoderFormat_CODABAR:
return "CODABAR";
case DecoderFormat_CODE_39:
return "CODE_39";
case DecoderFormat_CODE_93:
return "CODE_93";
case DecoderFormat_CODE_128:
return "CODE_128";
case DecoderFormat_CODE_128_GS1:
return "CODE_128_GS1";
case DecoderFormat_DATA_MATRIX:
return "DATA_MATRIX";
case DecoderFormat_EAN_8:
return "EAN_8";
case DecoderFormat_EAN_13:
return "EAN_13";
case DecoderFormat_ITF:
return "ITF";
case DecoderFormat_MAXICODE:
return "MAXICODE";
case DecoderFormat_PDF_417:
return "PDF_417";
case DecoderFormat_QR_CODE:
return "QR_CODE";
case DecoderFormat_RSS_14:
return "RSS_14";
case DecoderFormat_RSS_EXPANDED:
return "RSS_EXPANDED";
case DecoderFormat_UPC_A:
return "UPC_A";
case DecoderFormat_UPC_E:
return "UPC_E";
case DecoderFormat_UPC_EAN_EXTENSION:
return "UPC_EAN_EXTENSION";
} // switch
return QString();
}
QString QZXing::foundedFormat() const
{
return decodedFormat;
}
QString QZXing::charSet() const
{
return charSet_;
}
bool QZXing::getLastDecodeOperationSucceded()
{
return lastDecodeOperationSucceded_;
}
QVariantMap QZXing::metadataToMap(const ResultMetadata &metadata)
{
QVariantMap obj;
for (const ResultMetadata::Key &key: metadata.keys()) {
QString keyName = QString::fromStdString(metadata.keyToString(key));
switch (key) {
case ResultMetadata::ORIENTATION:
case ResultMetadata::ISSUE_NUMBER:
case ResultMetadata::STRUCTURED_APPEND_SEQUENCE:
case ResultMetadata::STRUCTURED_APPEND_CODE_COUNT:
case ResultMetadata::STRUCTURED_APPEND_PARITY:
obj[keyName] = QVariant(metadata.getInt(key));
break;
case ResultMetadata::ERROR_CORRECTION_LEVEL:
case ResultMetadata::SUGGESTED_PRICE:
case ResultMetadata::POSSIBLE_COUNTRY:
case ResultMetadata::UPC_EAN_EXTENSION:
obj[keyName] = QVariant(metadata.getString(key).c_str());
break;
case ResultMetadata::OTHER:
case ResultMetadata::PDF417_EXTRA_METADATA:
case ResultMetadata::BYTE_SEGMENTS:
break;
}
}
return obj;
}
void QZXing::setDecoder(const uint &hint)
{
unsigned int newHints = 0;
if(hint & DecoderFormat_Aztec)
newHints |= DecodeHints::AZTEC_HINT;
if(hint & DecoderFormat_CODABAR)
newHints |= DecodeHints::CODABAR_HINT;
if(hint & DecoderFormat_CODE_39)
newHints |= DecodeHints::CODE_39_HINT;
if(hint & DecoderFormat_CODE_93)
newHints |= DecodeHints::CODE_93_HINT;
if(hint & DecoderFormat_CODE_128)
newHints |= DecodeHints::CODE_128_HINT;
if(hint & DecoderFormat_DATA_MATRIX)
newHints |= DecodeHints::DATA_MATRIX_HINT;
if(hint & DecoderFormat_EAN_8)
newHints |= DecodeHints::EAN_8_HINT;
if(hint & DecoderFormat_EAN_13)
newHints |= DecodeHints::EAN_13_HINT;
if(hint & DecoderFormat_ITF)
newHints |= DecodeHints::ITF_HINT;
if(hint & DecoderFormat_MAXICODE)
newHints |= DecodeHints::MAXICODE_HINT;
if(hint & DecoderFormat_PDF_417)
newHints |= DecodeHints::PDF_417_HINT;
if(hint & DecoderFormat_QR_CODE)
newHints |= DecodeHints::QR_CODE_HINT;
if(hint & DecoderFormat_RSS_14)
newHints |= DecodeHints::RSS_14_HINT;
if(hint & DecoderFormat_RSS_EXPANDED)
newHints |= DecodeHints::RSS_EXPANDED_HINT;
if(hint & DecoderFormat_UPC_A)
newHints |= DecodeHints::UPC_A_HINT;
if(hint & DecoderFormat_UPC_E)
newHints |= DecodeHints::UPC_E_HINT;
if(hint & DecoderFormat_UPC_EAN_EXTENSION)
newHints |= DecodeHints::UPC_EAN_EXTENSION_HINT;
if(hint & DecoderFormat_CODE_128_GS1)
{
newHints |= DecodeHints::CODE_128_HINT;
newHints |= DecodeHints::ASSUME_GS1;
}
enabledDecoders = newHints;
emit enabledFormatsChanged();
}
/*!
* \brief getTagRec - returns rectangle containing the tag.
*
* To be able display tag rectangle regardless of the size of the bit matrix rect is in related coordinates [0; 1].
* \param resultPoints
* \param bitMatrix
* \return
*/
QRectF getTagRect(const QSharedPointer<std::vector<QSharedPointer<ResultPoint>> > &resultPoints, const QSharedPointer<BitMatrix> &bitMatrix)
{
if (resultPoints->size() < 2)
return QRectF();
int matrixWidth = bitMatrix->getWidth();
int matrixHeight = bitMatrix->getHeight();
// 1D barcode
if (resultPoints->size() == 2) {
WhiteRectangleDetector detector(bitMatrix);
std::vector<QSharedPointer<ResultPoint> > resultRectPoints = detector.detect();
if (resultRectPoints.size() != 4)
return QRectF();
qreal xMin = qreal((*resultPoints)[0]->getX());
qreal xMax = xMin;
for (int i = 1; i < resultPoints->size(); ++i) {
qreal x = qreal((*resultPoints)[i]->getX());
if (x < xMin)
xMin = x;
if (x > xMax)
xMax = x;
}
qreal yMin = qreal(resultRectPoints[0]->getY());
qreal yMax = yMin;
for (size_t i = 1; i < resultRectPoints.size(); ++i) {
qreal y = qreal(resultRectPoints[i]->getY());
if (y < yMin)
yMin = y;
if (y > yMax)
yMax = y;
}
return QRectF(QPointF(xMin / matrixWidth, yMax / matrixHeight), QPointF(xMax / matrixWidth, yMin / matrixHeight));
}
// 2D QR code
if (resultPoints->size() == 4) {
qreal xMin = qreal((*resultPoints)[0]->getX());
qreal xMax = xMin;
qreal yMin = qreal((*resultPoints)[0]->getY());
qreal yMax = yMin;
for (int i = 1; i < resultPoints->size(); ++i) {
qreal x = qreal((*resultPoints)[i]->getX());
qreal y = qreal((*resultPoints)[i]->getY());
if (x < xMin)
xMin = x;
if (x > xMax)
xMax = x;
if (y < yMin)
yMin = y;
if (y > yMax)
yMax = y;
}
return QRectF(QPointF(xMin / matrixWidth, yMax / matrixHeight), QPointF(xMax / matrixWidth, yMin / matrixHeight));
}
return QRectF();
}
QString QZXing::decodeImage(const QImage &image, int maxWidth, int maxHeight, bool smoothTransformation)
{
//qDebug() << "Start decoding";
QElapsedTimer t;
t.start();
processingTime = -1;
QSharedPointer<Result> res;
emit decodingStarted();
if(image.isNull())
{
processingTime = t.elapsed();
emit decodingFinished(false);
//qDebug() << "End decoding 1";
return "";
}
QSharedPointer<CameraImageWrapper> ciw;
if ((maxWidth > 0) || (maxHeight > 0))
ciw = CameraImageWrapper::Factory(image, maxWidth, maxHeight, smoothTransformation);
else
ciw = CameraImageWrapper::Factory(image, 999, 999, true);
QString errorMessage = "Unknown";
QSharedPointer<LuminanceSource> imageRefOriginal = ciw;
QSharedPointer<LuminanceSource> imageRef = imageRefOriginal;
QSharedPointer<GlobalHistogramBinarizer> binz;
QSharedPointer<BinaryBitmap> bb;
size_t numberOfIterations = 0;
if (imageSourceFilter & SourceFilter_ImageNormal)
numberOfIterations++;
if (imageSourceFilter & SourceFilter_ImageInverted)
numberOfIterations++;
//qDebug() << "Iterations: "<< numberOfIterations << ", sourceFilter: " << imageSourceFilter;
for(size_t i=0; i<numberOfIterations; ++i){
try {
if((numberOfIterations == 1 && (imageSourceFilter & SourceFilter_ImageInverted)) || i == 1) {
//qDebug() << "Selecting Inverted Luminance source";
imageRef = QSharedPointer<LuminanceSource>((LuminanceSource*)(new InvertedLuminanceSource(imageRefOriginal)));
}
binz = QSharedPointer<GlobalHistogramBinarizer>( new GlobalHistogramBinarizer(imageRef) );
bb = QSharedPointer<BinaryBitmap>( new BinaryBitmap(binz) );
DecodeHints hints(static_cast<DecodeHintType>(enabledDecoders));
if (hints.containsFormat(BarcodeFormat::UPC_EAN_EXTENSION)) {
hints.setAllowedEanExtensions(allowedExtensions_);
}
lastDecodeOperationSucceded_ = false;
try {
//qDebug() << "Decoding phase 1: started";
res = decoder->decode(bb, hints);
processingTime = t.elapsed();
lastDecodeOperationSucceded_ = true;
break;
} catch(zxing::Exception &/*e*/){
//qDebug() << "Decoding phase 1: failed";
}
if(!lastDecodeOperationSucceded_ && tryHarder_ && (tryHarderType & TryHarderBehaviour_ThoroughScanning))
{
//qDebug() << "Decoding phase 2, thorought scan: starting";
hints.setTryHarder(true);
if(hints.containsFormat(BarcodeFormat::UPC_EAN_EXTENSION) &&
!allowedExtensions_.empty() &&
!(hints & DecodeHints::PRODUCT_HINT).isEmpty() )
hints.setAllowedEanExtensions(std::set<int>());
try {
res = decoder->decode(bb, hints);
processingTime = t.elapsed();
lastDecodeOperationSucceded_ = true;
break;
} catch(zxing::Exception &/*e*/) {
//qDebug() << "Decoding phase 2, thorought scan: failed";
}
}
if (!lastDecodeOperationSucceded_&& tryHarder_ && (tryHarderType & TryHarderBehaviour_Rotate) && bb->isRotateSupported()) {
QSharedPointer<BinaryBitmap> bbTmp = bb;
//qDebug() << "Decoding phase 2, rotate: starting";
hints.setTryHarder(true);
for (int i=0; (i<3 && !lastDecodeOperationSucceded_); i++) {
QSharedPointer<BinaryBitmap> rotatedImage(bbTmp->rotateCounterClockwise());
bbTmp = rotatedImage;
try {
res = decoder->decode(rotatedImage, hints);
processingTime = t.elapsed();
lastDecodeOperationSucceded_ = true;
break;
} catch(zxing::Exception &/*e*/) {
//qDebug() << "Decoding phase 2, rotate: failed";
}
}
}
}
catch(zxing::Exception &e)
{
errorMessage = QString(e.what());
//qDebug() << "Decoding failed: " << errorMessage;
}
}
if (lastDecodeOperationSucceded_) {
//qDebug() << "Decoding succeeded.";
QString string = QString(res->getText()->getText().c_str());
if (!string.isEmpty() && (string.length() > 0)) {
int fmt = res->getBarcodeFormat().value;
decodedFormat = decoderFormatToString(1<<fmt);
charSet_ = QString::fromStdString(res->getCharSet());
if (!charSet_.isEmpty()) {
#if QT_VERSION < QT_VERSION_CHECK(6, 0, 0)
QTextCodec *codec = QTextCodec::codecForName(res->getCharSet().c_str());
if (codec)
string = codec->toUnicode(res->getText()->getText().c_str());
#else
QStringDecoder decoder(res->getCharSet().c_str());
if(decoder.isValid()) {
string = decoder.decode(QByteArray(res->getText()->getText().c_str()));
}
#endif
}
emit tagFound(string);
emit tagFoundAdvanced(string, decodedFormat, charSet_);
QVariantMap metadataMap = metadataToMap(res->getMetadata());
emit tagFoundAdvanced(string, decodedFormat, charSet_, metadataMap);
try {
const QRectF rect = getTagRect(res->getResultPoints(), binz->getBlackMatrix());
emit tagFoundAdvanced(string, decodedFormat, charSet_, rect);
}catch(zxing::Exception &/*e*/){}
}
processingTime = t.elapsed();
emit decodingFinished(true);
return string;
}
processingTime = t.elapsed();
emit error(errorMessage);
emit decodingFinished(false);
return "";
}
QString QZXing::decodeImageFromFile(const QString& imageFilePath, int maxWidth, int maxHeight, bool smoothTransformation)
{
// used to have a check if this image exists
// but was removed because if the image file path doesn't point to a valid image
// then the QImage::isNull will return true and the decoding will fail eitherway.
const QString header = "file://";
QString filePath = imageFilePath;
if(imageFilePath.startsWith(header))
filePath = imageFilePath.right(imageFilePath.size() - header.size());
QUrl imageUrl = QUrl::fromLocalFile(filePath);
QImage tmpImage = QImage(imageUrl.toLocalFile());
return decodeImage(tmpImage, maxWidth, maxHeight, smoothTransformation);
}
QString QZXing::decodeImageQML(QObject *item)
{
return decodeSubImageQML(item);
}
QString QZXing::decodeSubImageQML(QObject *item,
const int offsetX, const int offsetY,
const int width, const int height)
{
if(item == ZXING_NULLPTR)
{
processingTime = 0;
emit decodingFinished(false);
return "";
}
QImage img = imageHandler->extractQImage(item, offsetX, offsetY, width, height);
return decodeImage(img);
}
QString QZXing::decodeImageQML(const QUrl &imageUrl)
{
return decodeSubImageQML(imageUrl);
}
QString QZXing::decodeSubImageQML(const QUrl &imageUrl,
const int offsetX, const int offsetY,
const int width, const int height)
{
#ifdef QZXING_QML
QString imagePath = imageUrl.path();
imagePath = imagePath.trimmed();
QImage img;
if (imageUrl.scheme() == "image") {
if (imagePath.startsWith("/"))
imagePath = imagePath.right(imagePath.length() - 1);
QQmlEngine *engine = QQmlEngine::contextForObject(this)->engine();
QQuickImageProvider *imageProvider = dynamic_cast<QQuickImageProvider *>(engine->imageProvider(imageUrl.host()));
QSize imgSize;
img = imageProvider->requestImage(imagePath, &imgSize, QSize());
} else {
QFileInfo fileInfo(imagePath);
if (!fileInfo.exists()) {
qDebug() << "[decodeSubImageQML()] The file" << imagePath << "does not exist.";
emit decodingFinished(false);
return "";
}
img = QImage(imagePath);
}
if (offsetX || offsetY || width || height)
img = img.copy(offsetX, offsetY, width, height);
return decodeImage(img);
#else
Q_UNUSED(imageUrl);
Q_UNUSED(offsetX);
Q_UNUSED(offsetY);
Q_UNUSED(width);
Q_UNUSED(height);
return decodeImage(QImage());
#endif //QZXING_QML
}
#ifdef ENABLE_ENCODER_GENERIC
QImage QZXing::encodeData(const QString& data,
const EncoderFormat encoderFormat,
const QSize encoderImageSize,
const EncodeErrorCorrectionLevel errorCorrectionLevel,
const bool border,
const bool transparent)
{
return encodeData(data,
QZXingEncoderConfig(encoderFormat,
encoderImageSize,
errorCorrectionLevel,
border,
transparent));
}
QImage QZXing::encodeData(const QString &data, const QZXingEncoderConfig &encoderConfig)
{
QImage image;
try {
switch (encoderConfig.format) {
#ifdef ENABLE_ENCODER_QR_CODE
case EncoderFormat_QR_CODE:
{
QSharedPointer<qrcode::QRCode> barcode = qrcode::Encoder::encode(
data.toStdWString(),
encoderConfig.errorCorrectionLevel == EncodeErrorCorrectionLevel_H ?
qrcode::ErrorCorrectionLevel::H :
(encoderConfig.errorCorrectionLevel == EncodeErrorCorrectionLevel_Q ?
qrcode::ErrorCorrectionLevel::Q :
(encoderConfig.errorCorrectionLevel == EncodeErrorCorrectionLevel_M ?
qrcode::ErrorCorrectionLevel::M :
qrcode::ErrorCorrectionLevel::L)));
QSharedPointer<qrcode::ByteMatrix> bytesRef = barcode->getMatrix();
const std::vector< std::vector <zxing::byte> >& bytes = bytesRef->getArray();
const int width = int(bytesRef->getWidth()) + (encoderConfig.border ? 2 : 0);
const int height = int(bytesRef->getHeight()) + (encoderConfig.border ? 2 : 0);
const QRgb black = qRgba(0, 0, 0, encoderConfig.transparent ? 0 : 255);
const QRgb white = qRgba(255, 255, 255, 255);
image = QImage(width, height, QImage::Format_ARGB32);
image.fill(white);
int offset = encoderConfig.border ? 1 : 0;
for (size_t i=0; i<bytesRef->getWidth(); ++i) {
for (size_t j=0; j<bytesRef->getHeight(); ++j) {
if (bytes[j][i]) {
image.setPixel(offset+int(i), offset+int(j), black);
}
}
}
image = image.scaled(encoderConfig.imageSize);
break;
}
#endif // ENABLE_ENCODER_QR_CODE
case EncoderFormat_INVALID:
break;
}
} catch (std::exception& e) {
std::cout << "Error: " << e.what() << std::endl;
}
return image;
}
#endif // ENABLE_ENCODER_GENERIC
int QZXing::getProcessTimeOfLastDecoding()
{
return processingTime;
}
uint QZXing::getEnabledFormats() const
{
return enabledDecoders;
}