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 }