KItinerary

barcodedecoder.cpp
1/*
2 SPDX-FileCopyrightText: 2018-2019 Volker Krause <vkrause@kde.org>
3
4 SPDX-License-Identifier: LGPL-2.0-or-later
5*/
6
7#include "config-kitinerary.h"
8
9#include "barcodedecoder.h"
10#include "logging.h"
11
12#include <QDebug>
13#include <QImage>
14#include <QString>
15
16#define ZX_USE_UTF8 1
17#include <ZXing/ReadBarcode.h>
18
19using namespace KItinerary;
20
21enum {
22 // unit is pixels, assuming landscape orientation
23 MinSourceImageHeight = 10,
24 MinSourceImageWidth = 26,
25 // OEBB uses 1044x1044^W 2179x2179 for its UIC 918.3 Aztec code
26 MaxSourceImageHeight = 2200,
27 MaxSourceImageWidth = 2200
28};
29
30
31static constexpr const auto SQUARE_MAX_ASPECT = 1.25f;
32static constexpr const auto PDF417_MIN_ASPECT = 1.5f;
33static constexpr const auto PDF417_MAX_ASPECT = 6.5f;
34static constexpr const auto ANY1D_MIN_ASPECT = 1.95f;
35static constexpr const auto ANY1D_MAX_ASPECT = 8.0f;
36
37
38QByteArray BarcodeDecoder::Result::toByteArray() const
39{
40 return (contentType & Result::ByteArray) ? content.toByteArray() : QByteArray();
41}
42
43QString BarcodeDecoder::Result::toString() const
44{
45 return (contentType & Result::String) ? content.toString() : QString();
46}
47
48
49BarcodeDecoder::BarcodeDecoder() = default;
50BarcodeDecoder::~BarcodeDecoder() = default;
51
53{
54 if ((hint & Any) == None || img.isNull()) {
55 return {};
56 }
57
58 auto &results = m_cache[img.cacheKey()];
59 if (results.size() > 1) {
60 return Result{};
61 }
62 if (results.empty()) {
63 results.push_back(Result{});
64 }
65 auto &result = results.front();
66 decodeIfNeeded(img, hint, result);
67 return (result.positive & hint) ? result : Result{};
68}
69
70std::vector<BarcodeDecoder::Result> BarcodeDecoder::decodeMulti(const QImage &img, BarcodeDecoder::BarcodeTypes hint) const
71{
72 if ((hint & Any) == None || img.isNull()) {
73 return {};
74 }
75
76 auto &results = m_cache[img.cacheKey()];
77 decodeMultiIfNeeded(img, hint, results);
78 return (results.size() == 1 && (results[0].positive & hint) == 0) ? std::vector<Result>{} : results;
79}
80
82{
83 return decode(img, hint).toByteArray();
84}
85
87{
88 return decode(img, hint).toString();
89}
90
92{
93 m_cache.clear();
94}
95
97{
98 // normalize to landscape
99 if (height > width) {
100 std::swap(width, height);
101 }
102
103 if (width > MinSourceImageWidth && height > MinSourceImageHeight
104 && ((width < MaxSourceImageWidth && height < MaxSourceImageHeight) || (hint & IgnoreAspectRatio))) {
105 return hint;
106 }
107 return None;
108}
109
111{
112 if (hint & IgnoreAspectRatio) {
113 return hint;
114 }
115
116 // normalize to landscape
117 if (height > width) {
118 std::swap(width, height);
119 }
120
121 const auto aspectRatio = (float)width / (float)height;
122
123 // almost square, assume Aztec or QR
124 if (aspectRatio > SQUARE_MAX_ASPECT) {
125 hint &= ~AnySquare;
126 }
127
128 // rectangular with medium aspect ratio, such as PDF 417
129 if (aspectRatio < PDF417_MIN_ASPECT || aspectRatio > PDF417_MAX_ASPECT) {
130 hint &= ~PDF417;
131 }
132
133 // 1D
134 if (aspectRatio < ANY1D_MIN_ASPECT || aspectRatio > ANY1D_MAX_ASPECT) {
135 hint &= ~Any1D;
136 }
137
138 return hint;
139}
140
142{
143 return isPlausibleSize(width, height, hint) & isPlausibleAspectRatio(width, height, hint);
144}
145
146struct {
148 ZXing::BarcodeFormat zxingType;
149} static constexpr const zxing_format_map[] = {
150#if ZXING_VERSION > QT_VERSION_CHECK(1, 1, 1)
151 { BarcodeDecoder::Aztec, ZXing::BarcodeFormat::Aztec },
152 { BarcodeDecoder::QRCode, ZXing::BarcodeFormat::QRCode },
153 { BarcodeDecoder::PDF417, ZXing::BarcodeFormat::PDF417 },
154 { BarcodeDecoder::DataMatrix, ZXing::BarcodeFormat::DataMatrix },
155 { BarcodeDecoder::Code39, ZXing::BarcodeFormat::Code39 },
156 { BarcodeDecoder::Code93, ZXing::BarcodeFormat::Code93 },
157 { BarcodeDecoder::Code128, ZXing::BarcodeFormat::Code128 },
158#else
159 { BarcodeDecoder::Aztec, ZXing::BarcodeFormat::AZTEC },
160 { BarcodeDecoder::QRCode, ZXing::BarcodeFormat::QR_CODE },
161 { BarcodeDecoder::PDF417, ZXing::BarcodeFormat::PDF_417 },
162 { BarcodeDecoder::DataMatrix, ZXing::BarcodeFormat::DATA_MATRIX },
163 { BarcodeDecoder::Code39, ZXing::BarcodeFormat::CODE_39 },
164 { BarcodeDecoder::Code93, ZXing::BarcodeFormat::CODE_93 },
165 { BarcodeDecoder::Code128, ZXing::BarcodeFormat::CODE_128 },
166#endif
167};
168
169static auto typeToFormats(BarcodeDecoder::BarcodeTypes types)
170{
171 ZXing::BarcodeFormats formats;
172
173 for (auto i : zxing_format_map) {
174 if (types & i.type) {
175 formats |= i.zxingType;
176 }
177 }
178 return formats;
179}
180
181BarcodeDecoder::BarcodeType formatToType(ZXing::BarcodeFormat format)
182{
183 for (auto i : zxing_format_map) {
184 if (format == i.zxingType) {
185 return i.type;
186 }
187 }
188 return BarcodeDecoder::None;
189}
190
191static ZXing::ImageFormat zxingImageFormat(QImage::Format format)
192{
193 switch (format) {
196#if Q_BYTE_ORDER == Q_LITTLE_ENDIAN
197 return ZXing::ImageFormat::BGRX;
198#else
199 return ZXing::ImageFormat::XRGB;
200#endif
202 return ZXing::ImageFormat::RGB;
205 return ZXing::ImageFormat::RGBX;
207 return ZXing::ImageFormat::Lum;
208 default:
209 return ZXing::ImageFormat::None;
210 }
211 Q_UNREACHABLE();
212}
213
214static ZXing::ImageView zxingImageView(const QImage &img)
215{
216 return ZXing::ImageView{img.bits(), img.width(), img.height(), zxingImageFormat(img.format()), static_cast<int>(img.bytesPerLine())};
217}
218
219static void applyZXingResult(BarcodeDecoder::Result &result, const ZXing::Result &zxingResult, BarcodeDecoder::BarcodeTypes format)
220{
221 if (zxingResult.isValid()) {
222#if ZXING_VERSION >= QT_VERSION_CHECK(1, 4, 0)
223 // detect content type
224 std::string zxUtf8Text;
225 if (zxingResult.contentType() == ZXing::ContentType::Text) {
226 result.contentType = BarcodeDecoder::Result::Any;
227 zxUtf8Text = zxingResult.text();
228 // check if the text is ASCII-only (in which case we allow access as byte array as well)
229 if (std::any_of(zxUtf8Text.begin(), zxUtf8Text.end(), [](unsigned char c) { return c > 0x7F; })) {
230 result.contentType &= ~BarcodeDecoder::Result::ByteArray;
231 }
232 } else {
233 result.contentType = BarcodeDecoder::Result::ByteArray;
234 }
235
236 // decode content
237 if (result.contentType & BarcodeDecoder::Result::ByteArray) {
238 QByteArray b;
239 b.resize(zxingResult.bytes().size());
240 std::copy(zxingResult.bytes().begin(), zxingResult.bytes().end(), b.begin());
241 result.content = b;
242 } else {
243 result.content = QString::fromStdString(zxUtf8Text);
244 }
245#else
246 // detect content type
247 result.contentType = BarcodeDecoder::Result::Any;
248 if (std::any_of(zxingResult.text().begin(), zxingResult.text().end(), [](const auto c) { return c > 255; })) {
249 result.contentType &= ~BarcodeDecoder::Result::ByteArray;
250 }
251 if (std::any_of(zxingResult.text().begin(), zxingResult.text().end(), [](const auto c) { return c < 0x20; })) {
252 result.contentType &= ~BarcodeDecoder::Result::String;
253 }
254
255 // decode content
256 if (result.contentType & BarcodeDecoder::Result::ByteArray) {
257 QByteArray b;
258 b.resize(zxingResult.text().size());
259 std::copy(zxingResult.text().begin(), zxingResult.text().end(), b.begin());
260 result.content = b;
261 } else {
262 result.content = QString::fromStdWString(zxingResult.text());
263 }
264#endif
265 result.positive |= formatToType(zxingResult.format());
266 } else {
267 result.negative |= format;
268 }
269}
270
271void BarcodeDecoder::decodeIfNeeded(const QImage &img, BarcodeDecoder::BarcodeTypes hint, BarcodeDecoder::Result &result) const
272{
273 if ((result.positive & hint) || (result.negative & hint) == hint) {
274 return;
275 }
276
277 ZXing::DecodeHints hints;
278 hints.setFormats(typeToFormats(hint));
279 hints.setBinarizer(ZXing::Binarizer::FixedThreshold);
280 hints.setIsPure((hint & BarcodeDecoder::IgnoreAspectRatio) == 0);
281
282 // convert if img is in a format ZXing can't handle directly
283#if ZXING_VERSION > QT_VERSION_CHECK(1, 3, 0)
284 ZXing::Result res;
285#else
286 ZXing::Result res(ZXing::DecodeStatus::NotFound);
287#endif
288 if (zxingImageFormat(img.format()) == ZXing::ImageFormat::None) {
289 res = ZXing::ReadBarcode(zxingImageView(img.convertToFormat(QImage::Format_Grayscale8)), hints);
290 } else {
291 res = ZXing::ReadBarcode(zxingImageView(img), hints);
292 }
293
294 applyZXingResult(result, res, hint);
295}
296
297void BarcodeDecoder::decodeMultiIfNeeded(const QImage &img, BarcodeDecoder::BarcodeTypes hint, std::vector<BarcodeDecoder::Result> &results) const
298{
299#if ZXING_VERSION > QT_VERSION_CHECK(1, 2, 0)
300 if (std::any_of(results.begin(), results.end(), [hint](const auto &r) { return (r.positive & hint) || ((r.negative & hint) == hint); })) {
301 return;
302 }
303
304 ZXing::DecodeHints hints;
305 hints.setFormats(typeToFormats(hint));
306 hints.setBinarizer(ZXing::Binarizer::FixedThreshold);
307 hints.setIsPure(false);
308
309 // convert if img is in a format ZXing can't handle directly
310 std::vector<ZXing::Result> zxingResults;
311 if (zxingImageFormat(img.format()) == ZXing::ImageFormat::None) {
312 zxingResults = ZXing::ReadBarcodes(zxingImageView(img.convertToFormat(QImage::Format_Grayscale8)), hints);
313 } else {
314 zxingResults = ZXing::ReadBarcodes(zxingImageView(img), hints);
315 }
316
317 if (zxingResults.empty()) {
318 Result r;
319 r.negative |= hint;
320 results.push_back(std::move(r));
321 } else {
322 // ### in theory we need to handle the case that we already have results from a previous run with different hints here...
323 results.reserve(zxingResults.size());
324 for (const auto &zxingRes : zxingResults) {
325 Result r;
326 applyZXingResult(r, zxingRes, hint);
327 results.push_back(std::move(r));
328 }
329 }
330#else
331 // ZXing 1.2 has no multi-decode support yet, so always treat this as no hit
332 Result r;
333 r.negative |= hint;
334 results.push_back(std::move(r));
335#endif
336}
Barcode decoding with result caching.
static BarcodeTypes isPlausibleSize(int width, int height, BarcodeTypes hint)
Checks if the given image dimensions are plausible for a barcode.
QByteArray decodeBinary(const QImage &img, BarcodeTypes hint) const
Decodes a binary payload barcode in img of type hint.
static BarcodeTypes maybeBarcode(int width, int height, BarcodeTypes hint)
The combination of the above.
Result decode(const QImage &img, BarcodeTypes hint) const
Decodes a barcode in img based on hint.
static BarcodeTypes isPlausibleAspectRatio(int width, int height, BarcodeTypes hint)
Checks if the given image dimensions are a barcode of type hint.
void clearCache()
Clears the internal cache.
QString decodeString(const QImage &img, BarcodeTypes hint) const
Decodes a textual payload barcode in img of type hint.
std::vector< Result > decodeMulti(const QImage &img, BarcodeTypes hint) const
Decodes multiple barcodes in img based on hint.
QAction * hint(const QObject *recvr, const char *slot, QObject *parent)
Classes for reservation/travel data models, data extraction and data augmentation.
Definition berelement.h:17
iterator begin()
void resize(qsizetype newSize, char c)
uchar * bits()
qsizetype bytesPerLine() const const
qint64 cacheKey() const const
QImage convertToFormat(Format format, Qt::ImageConversionFlags flags) &&
Format format() const const
int height() const const
bool isNull() const const
int width() const const
QString fromStdString(const std::string &str)
QString fromStdWString(const std::wstring &str)
QByteArray toByteArray() const const
This file is part of the KDE documentation.
Documentation copyright © 1996-2024 The KDE developers.
Generated on Tue Mar 26 2024 11:14:48 by doxygen 1.10.0 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.