1 /*
2 * Copyright 2016 Nu-book Inc.
3 * Copyright 2016 ZXing authors
4 * Copyright 2020 Axel Waggershauser
5 *
6 * Licensed under the Apache License, Version 2.0 (the "License");
7 * you may not use this file except in compliance with the License.
8 * You may obtain a copy of the License at
9 *
10 * http://www.apache.org/licenses/LICENSE-2.0
11 *
12 * Unless required by applicable law or agreed to in writing, software
13 * distributed under the License is distributed on an "AS IS" BASIS,
14 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15 * See the License for the specific language governing permissions and
16 * limitations under the License.
17 */
18
19 #include "ODReader.h"
20
21 #include "BinaryBitmap.h"
22 #include "BitArray.h"
23 #include "DecodeHints.h"
24 #include "ODCodabarReader.h"
25 #include "ODCode128Reader.h"
26 #include "ODCode39Reader.h"
27 #include "ODCode93Reader.h"
28 #include "ODDataBarExpandedReader.h"
29 #include "ODDataBarReader.h"
30 #include "ODITFReader.h"
31 #include "ODMultiUPCEANReader.h"
32 #include "Result.h"
33
34 #include <algorithm>
35 #include <utility>
36
37 namespace ZXing::OneD {
38
Reader(const DecodeHints & hints)39 Reader::Reader(const DecodeHints& hints) :
40 _tryHarder(hints.tryHarder()),
41 _tryRotate(hints.tryRotate()),
42 _isPure(hints.isPure())
43 {
44 _readers.reserve(8);
45
46 auto formats = hints.formats().empty() ? BarcodeFormat::Any : hints.formats();
47
48 if (formats.testFlags(BarcodeFormat::EAN13 | BarcodeFormat::UPCA | BarcodeFormat::EAN8 | BarcodeFormat::UPCE))
49 _readers.emplace_back(new MultiUPCEANReader(hints));
50
51 if (formats.testFlag(BarcodeFormat::Code39))
52 _readers.emplace_back(new Code39Reader(hints));
53 if (formats.testFlag(BarcodeFormat::Code93))
54 _readers.emplace_back(new Code93Reader());
55 if (formats.testFlag(BarcodeFormat::Code128))
56 _readers.emplace_back(new Code128Reader(hints));
57 if (formats.testFlag(BarcodeFormat::ITF))
58 _readers.emplace_back(new ITFReader(hints));
59 if (formats.testFlag(BarcodeFormat::Codabar))
60 _readers.emplace_back(new CodabarReader(hints));
61 if (formats.testFlags(BarcodeFormat::DataBar))
62 _readers.emplace_back(new DataBarReader(hints));
63 if (formats.testFlags(BarcodeFormat::DataBarExpanded))
64 _readers.emplace_back(new DataBarExpandedReader(hints));
65 }
66
67 Reader::~Reader() = default;
68
69 /**
70 * We're going to examine rows from the middle outward, searching alternately above and below the
71 * middle, and farther out each time. rowStep is the number of rows between each successive
72 * attempt above and below the middle. So we'd scan row middle, then middle - rowStep, then
73 * middle + rowStep, then middle - (2 * rowStep), etc.
74 * rowStep is bigger as the image is taller, but is always at least 1. We've somewhat arbitrarily
75 * decided that moving up and down by about 1/16 of the image is pretty good; we try more of the
76 * image if "trying harder".
77 *
78 * @param image The image to decode
79 * @param hints Any hints that were requested
80 * @return The contents of the decoded barcode
81 * @throws NotFoundException Any spontaneous errors which occur
82 */
83 static Result
DoDecode(const std::vector<std::unique_ptr<RowReader>> & readers,const BinaryBitmap & image,bool tryHarder,bool isPure)84 DoDecode(const std::vector<std::unique_ptr<RowReader>>& readers, const BinaryBitmap& image, bool tryHarder, bool isPure)
85 {
86 std::vector<std::unique_ptr<RowReader::DecodingState>> decodingState(readers.size());
87
88 int width = image.width();
89 int height = image.height();
90
91 int middle = height / 2;
92 int rowStep = std::max(1, height / (tryHarder ? 256 : 32));
93 int maxLines = tryHarder ?
94 height : // Look at the whole image, not just the center
95 15; // 15 rows spaced 1/32 apart is roughly the middle half of the image
96
97 PatternRow bars;
98 bars.reserve(128); // e.g. EAN-13 has 96 bars
99
100 for (int i = 0; i < maxLines; i++) {
101
102 // Scanning from the middle out. Determine which row we're looking at next:
103 int rowStepsAboveOrBelow = (i + 1) / 2;
104 bool isAbove = (i & 0x01) == 0; // i.e. is x even?
105 int rowNumber = middle + rowStep * (isAbove ? rowStepsAboveOrBelow : -rowStepsAboveOrBelow);
106 if (rowNumber < 0 || rowNumber >= height) {
107 // Oops, if we run off the top or bottom, stop
108 break;
109 }
110
111 if (!image.getPatternRow(rowNumber, bars))
112 continue;
113
114 // While we have the image data in a PatternRow, it's fairly cheap to reverse it in place to
115 // handle decoding upside down barcodes.
116 // Note: the DataBarExpanded decoder depends on seeing each line from both directions. This
117 // 'surprising' and inconsistent. It also requires the decoderState to be shared between
118 // normal and reversed scans, which makes no sense in general because it would mix partial
119 // detection data from two codes of the same type next to each other. TODO..
120 // See also https://github.com/nu-book/zxing-cpp/issues/87
121 for (bool upsideDown : {false, true}) {
122 // trying again?
123 if (upsideDown) {
124 // reverse the row and continue
125 std::reverse(bars.begin(), bars.end());
126 }
127 // Look for a barcode
128 for (size_t r = 0; r < readers.size(); ++r) {
129 Result result = readers[r]->decodePattern(rowNumber, bars, decodingState[r]);
130 if (result.isValid()) {
131 if (upsideDown) {
132 // update position (flip horizontally).
133 auto points = result.position();
134 for (auto& p : points) {
135 p = {width - p.x - 1, p.y};
136 }
137 result.setPosition(std::move(points));
138 }
139 return result;
140 }
141 }
142 }
143
144 // If this is a pure symbol, then checking a single non-empty line is sufficient
145 if (isPure)
146 break;
147 }
148 return Result(DecodeStatus::NotFound);
149 }
150
151 Result
decode(const BinaryBitmap & image) const152 Reader::decode(const BinaryBitmap& image) const
153 {
154 Result result = DoDecode(_readers, image, _tryHarder, _isPure);
155
156 if (!result.isValid() && _tryRotate && image.canRotate()) {
157 auto rotatedImage = image.rotated(270);
158 result = DoDecode(_readers, *rotatedImage, _tryHarder, _isPure);
159 if (result.isValid()) {
160 // Update position
161 auto points = result.position();
162 int height = rotatedImage->height();
163 for (auto& p : points) {
164 p = {height - p.y - 1, p.x};
165 }
166 result.setPosition(std::move(points));
167 }
168 }
169
170 return result;
171 }
172
173 } // namespace ZXing::OneD
174