Warning, file /pim/kitinerary/src/lib/barcodedecoder.cpp was not indexed or was modified since last indexation (in which case cross-reference links may be missing, inaccurate or erroneous).

0001 /*
0002     SPDX-FileCopyrightText: 2018-2019 Volker Krause <vkrause@kde.org>
0003 
0004     SPDX-License-Identifier: LGPL-2.0-or-later
0005 */
0006 
0007 #include "config-kitinerary.h"
0008 
0009 #include "barcodedecoder.h"
0010 #include "logging.h"
0011 
0012 #include <QDebug>
0013 #include <QImage>
0014 #include <QString>
0015 
0016 #define ZX_USE_UTF8 1
0017 #include <ZXing/ReadBarcode.h>
0018 
0019 using namespace KItinerary;
0020 
0021 enum {
0022     // unit is pixels, assuming landscape orientation
0023     MinSourceImageHeight = 10,
0024     MinSourceImageWidth = 26,
0025     // OEBB uses 1044x1044 for its UIC 918.3 Aztec code
0026     MaxSourceImageHeight = 1100, // TODO what's a realistic value here?
0027     MaxSourceImageWidth = 2000
0028 };
0029 
0030 
0031 static constexpr const auto SQUARE_MAX_ASPECT = 1.25f;
0032 static constexpr const auto PDF417_MIN_ASPECT = 1.5f;
0033 static constexpr const auto PDF417_MAX_ASPECT = 6.5f;
0034 static constexpr const auto ANY1D_MIN_ASPECT = 1.95f;
0035 static constexpr const auto ANY1D_MAX_ASPECT = 8.0f;
0036 
0037 
0038 QByteArray BarcodeDecoder::Result::toByteArray() const
0039 {
0040     return (contentType & Result::ByteArray) ? content.toByteArray() : QByteArray();
0041 }
0042 
0043 QString BarcodeDecoder::Result::toString() const
0044 {
0045     return (contentType & Result::String) ? content.toString() : QString();
0046 }
0047 
0048 
0049 BarcodeDecoder::BarcodeDecoder() = default;
0050 BarcodeDecoder::~BarcodeDecoder() = default;
0051 
0052 BarcodeDecoder::Result BarcodeDecoder::decode(const QImage &img, BarcodeDecoder::BarcodeTypes hint) const
0053 {
0054     if ((hint & Any) == None || img.isNull()) {
0055         return {};
0056     }
0057 
0058     auto &results = m_cache[img.cacheKey()];
0059     if (results.size() > 1) {
0060         return Result{};
0061     }
0062     if (results.empty()) {
0063         results.push_back(Result{});
0064     }
0065     auto &result = results.front();
0066     decodeIfNeeded(img, hint, result);
0067     return (result.positive & hint) ? result : Result{};
0068 }
0069 
0070 std::vector<BarcodeDecoder::Result> BarcodeDecoder::decodeMulti(const QImage &img, BarcodeDecoder::BarcodeTypes hint) const
0071 {
0072     if ((hint & Any) == None || img.isNull()) {
0073         return {};
0074     }
0075 
0076     auto &results = m_cache[img.cacheKey()];
0077     decodeMultiIfNeeded(img, hint, results);
0078     return (results.size() == 1 && (results[0].positive & hint) == 0) ? std::vector<Result>{} : results;
0079 }
0080 
0081 QByteArray BarcodeDecoder::decodeBinary(const QImage &img, BarcodeDecoder::BarcodeTypes hint) const
0082 {
0083     return decode(img, hint).toByteArray();
0084 }
0085 
0086 QString BarcodeDecoder::decodeString(const QImage &img, BarcodeDecoder::BarcodeTypes hint) const
0087 {
0088     return decode(img, hint).toString();
0089 }
0090 
0091 void BarcodeDecoder::clearCache()
0092 {
0093     m_cache.clear();
0094 }
0095 
0096 BarcodeDecoder::BarcodeTypes BarcodeDecoder::isPlausibleSize(int width, int height, BarcodeDecoder::BarcodeTypes hint)
0097 {
0098     // normalize to landscape
0099     if (height > width) {
0100         std::swap(width, height);
0101     }
0102 
0103     if (width > MinSourceImageWidth && height > MinSourceImageHeight
0104         && ((width < MaxSourceImageWidth && height < MaxSourceImageHeight) || (hint & IgnoreAspectRatio))) {
0105         return hint;
0106     }
0107     return None;
0108 }
0109 
0110 BarcodeDecoder::BarcodeTypes BarcodeDecoder::isPlausibleAspectRatio(int width, int height, BarcodeDecoder::BarcodeTypes hint)
0111 {
0112     if (hint & IgnoreAspectRatio) {
0113         return hint;
0114     }
0115 
0116     // normalize to landscape
0117     if (height > width) {
0118         std::swap(width, height);
0119     }
0120 
0121     const auto aspectRatio = (float)width / (float)height;
0122 
0123     // almost square, assume Aztec or QR
0124     if (aspectRatio > SQUARE_MAX_ASPECT) {
0125         hint &= ~AnySquare;
0126     }
0127 
0128     // rectangular with medium aspect ratio, such as PDF 417
0129     if (aspectRatio < PDF417_MIN_ASPECT || aspectRatio > PDF417_MAX_ASPECT) {
0130         hint &= ~PDF417;
0131     }
0132 
0133     // 1D
0134     if (aspectRatio < ANY1D_MIN_ASPECT || aspectRatio > ANY1D_MAX_ASPECT) {
0135         hint &= ~Any1D;
0136     }
0137 
0138     return hint;
0139 }
0140 
0141 BarcodeDecoder::BarcodeTypes BarcodeDecoder::maybeBarcode(int width, int height, BarcodeDecoder::BarcodeTypes hint)
0142 {
0143     return isPlausibleSize(width, height, hint) & isPlausibleAspectRatio(width, height, hint);
0144 }
0145 
0146 struct {
0147     BarcodeDecoder::BarcodeType type;
0148     ZXing::BarcodeFormat zxingType;
0149 } static constexpr const zxing_format_map[] = {
0150 #if ZXING_VERSION > QT_VERSION_CHECK(1, 1, 1)
0151     { BarcodeDecoder::Aztec, ZXing::BarcodeFormat::Aztec },
0152     { BarcodeDecoder::QRCode, ZXing::BarcodeFormat::QRCode },
0153     { BarcodeDecoder::PDF417, ZXing::BarcodeFormat::PDF417 },
0154     { BarcodeDecoder::DataMatrix, ZXing::BarcodeFormat::DataMatrix },
0155     { BarcodeDecoder::Code39, ZXing::BarcodeFormat::Code39 },
0156     { BarcodeDecoder::Code93, ZXing::BarcodeFormat::Code93 },
0157     { BarcodeDecoder::Code128, ZXing::BarcodeFormat::Code128 },
0158 #else
0159     { BarcodeDecoder::Aztec, ZXing::BarcodeFormat::AZTEC },
0160     { BarcodeDecoder::QRCode, ZXing::BarcodeFormat::QR_CODE },
0161     { BarcodeDecoder::PDF417, ZXing::BarcodeFormat::PDF_417 },
0162     { BarcodeDecoder::DataMatrix, ZXing::BarcodeFormat::DATA_MATRIX },
0163     { BarcodeDecoder::Code39, ZXing::BarcodeFormat::CODE_39 },
0164     { BarcodeDecoder::Code93, ZXing::BarcodeFormat::CODE_93 },
0165     { BarcodeDecoder::Code128, ZXing::BarcodeFormat::CODE_128 },
0166 #endif
0167 };
0168 
0169 static auto typeToFormats(BarcodeDecoder::BarcodeTypes types)
0170 {
0171     ZXing::BarcodeFormats formats;
0172 
0173     for (auto i : zxing_format_map) {
0174         if (types & i.type) {
0175             formats |= i.zxingType;
0176         }
0177     }
0178     return formats;
0179 }
0180 
0181 BarcodeDecoder::BarcodeType formatToType(ZXing::BarcodeFormat format)
0182 {
0183     for (auto i : zxing_format_map) {
0184         if (format == i.zxingType) {
0185             return i.type;
0186         }
0187     }
0188     return BarcodeDecoder::None;
0189 }
0190 
0191 static ZXing::ImageFormat zxingImageFormat(QImage::Format format)
0192 {
0193     switch (format) {
0194         case QImage::Format_ARGB32:
0195         case QImage::Format_RGB32:
0196 #if Q_BYTE_ORDER == Q_LITTLE_ENDIAN
0197             return ZXing::ImageFormat::BGRX;
0198 #else
0199             return ZXing::ImageFormat::XRGB;
0200 #endif
0201         case QImage::Format_RGB888:
0202             return ZXing::ImageFormat::RGB;
0203         case QImage::Format_RGBX8888:
0204         case QImage::Format_RGBA8888:
0205             return ZXing::ImageFormat::RGBX;
0206         case QImage::Format_Grayscale8:
0207             return ZXing::ImageFormat::Lum;
0208         default:
0209             return ZXing::ImageFormat::None;
0210     }
0211     Q_UNREACHABLE();
0212 }
0213 
0214 static ZXing::ImageView zxingImageView(const QImage &img)
0215 {
0216     return ZXing::ImageView{img.bits(), img.width(), img.height(), zxingImageFormat(img.format()), static_cast<int>(img.bytesPerLine())};
0217 }
0218 
0219 static void applyZXingResult(BarcodeDecoder::Result &result, const ZXing::Result &zxingResult, BarcodeDecoder::BarcodeTypes format)
0220 {
0221     if (zxingResult.isValid()) {
0222 #if ZXING_VERSION >= QT_VERSION_CHECK(1, 4, 0)
0223         // detect content type
0224         std::string zxUtf8Text;
0225         if (zxingResult.contentType() == ZXing::ContentType::Text) {
0226             result.contentType = BarcodeDecoder::Result::Any;
0227             zxUtf8Text = zxingResult.text();
0228             // check if the text is ASCII-only (in which case we allow access as byte array as well)
0229             if (std::any_of(zxUtf8Text.begin(), zxUtf8Text.end(), [](unsigned char c) { return c > 0x7F; })) {
0230                 result.contentType &= ~BarcodeDecoder::Result::ByteArray;
0231             }
0232         } else {
0233             result.contentType = BarcodeDecoder::Result::ByteArray;
0234         }
0235 
0236         // decode content
0237         if (result.contentType & BarcodeDecoder::Result::ByteArray) {
0238             QByteArray b;
0239             b.resize(zxingResult.bytes().size());
0240             std::copy(zxingResult.bytes().begin(), zxingResult.bytes().end(), b.begin());
0241             result.content = b;
0242         } else {
0243             result.content = QString::fromStdString(zxUtf8Text);
0244         }
0245 #else
0246         // detect content type
0247         result.contentType = BarcodeDecoder::Result::Any;
0248         if (std::any_of(zxingResult.text().begin(), zxingResult.text().end(), [](const auto c) { return c > 255; })) {
0249             result.contentType &= ~BarcodeDecoder::Result::ByteArray;
0250         }
0251         if (std::any_of(zxingResult.text().begin(), zxingResult.text().end(), [](const auto c) { return c < 0x20; })) {
0252             result.contentType &= ~BarcodeDecoder::Result::String;
0253         }
0254 
0255         // decode content
0256         if (result.contentType & BarcodeDecoder::Result::ByteArray) {
0257             QByteArray b;
0258             b.resize(zxingResult.text().size());
0259             std::copy(zxingResult.text().begin(), zxingResult.text().end(), b.begin());
0260             result.content = b;
0261         } else {
0262             result.content = QString::fromStdWString(zxingResult.text());
0263         }
0264 #endif
0265         result.positive |= formatToType(zxingResult.format());
0266     } else {
0267         result.negative |= format;
0268     }
0269 }
0270 
0271 void BarcodeDecoder::decodeIfNeeded(const QImage &img, BarcodeDecoder::BarcodeTypes hint, BarcodeDecoder::Result &result) const
0272 {
0273     if ((result.positive & hint) || (result.negative & hint) == hint) {
0274         return;
0275     }
0276 
0277     ZXing::DecodeHints hints;
0278     hints.setFormats(typeToFormats(hint));
0279     hints.setBinarizer(ZXing::Binarizer::FixedThreshold);
0280     hints.setIsPure((hint & BarcodeDecoder::IgnoreAspectRatio) == 0);
0281 
0282     // convert if img is in a format ZXing can't handle directly
0283 #if ZXING_VERSION > QT_VERSION_CHECK(1, 3, 0)
0284     ZXing::Result res;
0285 #else
0286     ZXing::Result res(ZXing::DecodeStatus::NotFound);
0287 #endif
0288     if (zxingImageFormat(img.format()) == ZXing::ImageFormat::None) {
0289         res = ZXing::ReadBarcode(zxingImageView(img.convertToFormat(QImage::Format_Grayscale8)), hints);
0290     } else {
0291         res = ZXing::ReadBarcode(zxingImageView(img), hints);
0292     }
0293 
0294     applyZXingResult(result, res, hint);
0295 }
0296 
0297 void BarcodeDecoder::decodeMultiIfNeeded(const QImage &img, BarcodeDecoder::BarcodeTypes hint, std::vector<BarcodeDecoder::Result> &results) const
0298 {
0299 #if ZXING_VERSION > QT_VERSION_CHECK(1, 2, 0)
0300     if (std::any_of(results.begin(), results.end(), [hint](const auto &r) { return (r.positive & hint) || ((r.negative & hint) == hint); })) {
0301         return;
0302     }
0303 
0304     ZXing::DecodeHints hints;
0305     hints.setFormats(typeToFormats(hint));
0306     hints.setBinarizer(ZXing::Binarizer::FixedThreshold);
0307     hints.setIsPure(false);
0308 
0309     // convert if img is in a format ZXing can't handle directly
0310     std::vector<ZXing::Result> zxingResults;
0311     if (zxingImageFormat(img.format()) == ZXing::ImageFormat::None) {
0312         zxingResults = ZXing::ReadBarcodes(zxingImageView(img.convertToFormat(QImage::Format_Grayscale8)), hints);
0313     } else {
0314         zxingResults = ZXing::ReadBarcodes(zxingImageView(img), hints);
0315     }
0316 
0317     if (zxingResults.empty()) {
0318         Result r;
0319         r.negative |= hint;
0320         results.push_back(std::move(r));
0321     } else {
0322         // ### in theory we need to handle the case that we already have results from a previous run with different hints here...
0323         results.reserve(zxingResults.size());
0324         for (const auto &zxingRes : zxingResults) {
0325             Result r;
0326             applyZXingResult(r, zxingRes, hint);
0327             results.push_back(std::move(r));
0328         }
0329     }
0330 #else
0331     // ZXing 1.2 has no multi-decode support yet, so always treat this as no hit
0332     Result r;
0333     r.negative |= hint;
0334     results.push_back(std::move(r));
0335 #endif
0336 }