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