Skip to main content

rxing/pdf417/decoder/
detection_result_row_indicator_column.rs

1/*
2 * Copyright 2013 ZXing authors
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 *      http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17use crate::pdf417::pdf_417_common;
18
19use super::{
20    BarcodeMetadata, BarcodeValue, Codeword, DetectionRXingResultColumn,
21    DetectionRXingResultColumnTrait,
22};
23
24/**
25 * @author Guenther Grau
26 */
27pub trait DetectionRXingResultRowIndicatorColumn: DetectionRXingResultColumnTrait {
28    // TODO implement properly
29    // TODO maybe we should add missing codewords to store the correct row number to make
30    // finding row numbers for other columns easier
31    // use row height count to make detection of invalid row numbers more reliable
32    fn adjustCompleteIndicatorColumnRowNumbers(&mut self, barcodeMetadata: &BarcodeMetadata)
33    -> u32;
34    fn getRowHeights(&mut self) -> Option<Vec<u32>>;
35    fn getBarcodeMetadata(&mut self) -> Option<BarcodeMetadata>;
36    fn isLeft(&self) -> bool;
37}
38
39impl DetectionRXingResultRowIndicatorColumn for DetectionRXingResultColumn {
40    // TODO implement properly
41    // TODO maybe we should add missing codewords to store the correct row number to make
42    // finding row numbers for other columns easier
43    // use row height count to make detection of invalid row numbers more reliable
44    fn adjustCompleteIndicatorColumnRowNumbers(
45        &mut self,
46        barcodeMetadata: &BarcodeMetadata,
47    ) -> u32 {
48        setRowNumbers(self.getCodewordsMut());
49
50        let isLeft = matches!(self.isLeft, Some(true));
51
52        removeIncorrectCodewords(self.getCodewordsMut(), barcodeMetadata, isLeft);
53
54        let boundingBox = self.getBoundingBox();
55        let top = if self.isLeft() {
56            boundingBox.getTopLeft()
57        } else {
58            boundingBox.getTopRight()
59        };
60        let bottom = if self.isLeft() {
61            boundingBox.getBottomLeft()
62        } else {
63            boundingBox.getBottomRight()
64        };
65
66        let firstRow = self.imageRowToCodewordIndex(top.y as u32);
67        let lastRow = self.imageRowToCodewordIndex(bottom.y as u32);
68        // We need to be careful using the average row height. Barcode could be skewed so that we have smaller and
69        // taller rows
70        let averageRowHeight: f64 =
71            (lastRow as f64 - firstRow as f64) / barcodeMetadata.getRowCount() as f64;
72        let mut barcodeRow = -1;
73        let mut maxRowHeight = 1;
74        let mut currentRowHeight = 0;
75        for codewordsRow in firstRow..lastRow {
76            if let Some(codeword) = self.getCodewordsMut()[codewordsRow] {
77                let rowDifference = codeword.getRowNumber() - barcodeRow;
78
79                // TODO improve handling with case where first row indicator doesn't start with 0
80
81                if rowDifference == 0 {
82                    currentRowHeight += 1;
83                } else if rowDifference == 1 {
84                    maxRowHeight = std::cmp::max(maxRowHeight, currentRowHeight);
85                    currentRowHeight = 1;
86                    barcodeRow = codeword.getRowNumber();
87                } else if rowDifference < 0
88                    || codeword.getRowNumber() >= barcodeMetadata.getRowCount() as i32
89                    || rowDifference > codewordsRow as i32
90                {
91                    self.getCodewordsMut()[codewordsRow] = None;
92                } else {
93                    let checkedRows = if maxRowHeight > 2 {
94                        (maxRowHeight - 2) * rowDifference
95                    } else {
96                        rowDifference
97                    };
98                    let mut closePreviousCodewordFound = checkedRows >= codewordsRow as i32;
99                    let mut i = 1;
100                    while i <= checkedRows && !closePreviousCodewordFound {
101                        // there must be (height * rowDifference) number of codewords missing. For now we assume height = 1.
102                        // This should hopefully get rid of most problems already.
103                        closePreviousCodewordFound =
104                            self.getCodewords()[codewordsRow - i as usize].is_some();
105
106                        i += 1;
107                    }
108                    if closePreviousCodewordFound {
109                        self.getCodewordsMut()[codewordsRow] = None;
110                    } else {
111                        barcodeRow = codeword.getRowNumber();
112                        currentRowHeight = 1;
113                    }
114                }
115            } else {
116                continue;
117            }
118        }
119        (averageRowHeight + 0.5) as u32
120    }
121
122    fn getRowHeights(&mut self) -> Option<Vec<u32>> {
123        if let Some(barcodeMetadata) = self.getBarcodeMetadata() {
124            adjustIncompleteIndicatorColumnRowNumbers(self, &barcodeMetadata);
125            let mut result = vec![0; barcodeMetadata.getRowCount() as usize];
126            for codeword in self.getCodewords().iter().flatten() {
127                // if let Some(codeword) = codeword_opt {
128                let rowNumber = codeword.getRowNumber() as usize;
129                if rowNumber >= result.len() {
130                    // We have more rows than the barcode metadata allows for, ignore them.
131                    continue;
132                }
133                result[rowNumber] += 1;
134                // }
135                // else throw exception?
136                // else {
137                //     continue;
138                // }
139            }
140            Some(result)
141        } else {
142            None
143        }
144    }
145
146    fn getBarcodeMetadata(&mut self) -> Option<BarcodeMetadata> {
147        let isLeft = matches!(self.isLeft, Some(true));
148        let codewords = self.getCodewordsMut();
149        let mut barcodeColumnCount = BarcodeValue::new();
150        let mut barcodeRowCountUpperPart = BarcodeValue::new();
151        let mut barcodeRowCountLowerPart = BarcodeValue::new();
152        let mut barcodeECLevel = BarcodeValue::new();
153        for codeword in codewords.iter_mut().flatten() {
154            // for (Codeword codeword : codewords) {
155            // if let Some(codeword) = codeword_opt {
156            codeword.setRowNumberAsRowIndicatorColumn();
157            let rowIndicatorValue = codeword.getValue() % 30;
158            let mut codewordRowNumber = codeword.getRowNumber();
159            if !isLeft {
160                codewordRowNumber += 2;
161            }
162            match codewordRowNumber % 3 {
163                0 => barcodeRowCountUpperPart.setValue(rowIndicatorValue * 3 + 1),
164                1 => {
165                    barcodeECLevel.setValue(rowIndicatorValue / 3);
166                    barcodeRowCountLowerPart.setValue(rowIndicatorValue % 3);
167                }
168                2 => barcodeColumnCount.setValue(rowIndicatorValue + 1),
169                _ => {}
170            }
171            // } else {
172            //     continue;
173            // }
174        }
175        // Maybe we should check if we have ambiguous values?
176        if barcodeColumnCount.getValue().is_empty()
177            || barcodeRowCountUpperPart.getValue().is_empty()
178            || barcodeRowCountLowerPart.getValue().is_empty()
179            || barcodeECLevel.getValue().is_empty()
180            || barcodeColumnCount.getValue()[0] < 1
181            || barcodeRowCountUpperPart.getValue()[0] + barcodeRowCountLowerPart.getValue()[0]
182                < pdf_417_common::MIN_ROWS_IN_BARCODE
183            || barcodeRowCountUpperPart.getValue()[0] + barcodeRowCountLowerPart.getValue()[0]
184                > pdf_417_common::MAX_ROWS_IN_BARCODE
185        {
186            return None;
187        }
188        let barcodeMetadata = BarcodeMetadata::new(
189            barcodeColumnCount.getValue()[0],
190            barcodeRowCountUpperPart.getValue()[0],
191            barcodeRowCountLowerPart.getValue()[0],
192            barcodeECLevel.getValue()[0],
193        );
194        removeIncorrectCodewords(codewords, &barcodeMetadata, isLeft);
195
196        Some(barcodeMetadata)
197    }
198
199    fn isLeft(&self) -> bool {
200        matches!(self.isLeft, Some(true))
201    }
202}
203
204fn setRowNumbers(code_words: &mut [Option<Codeword>]) {
205    for codeword in code_words.iter_mut().flatten() {
206        codeword.setRowNumberAsRowIndicatorColumn();
207    }
208}
209
210fn removeIncorrectCodewords(
211    codewords: &mut [Option<Codeword>],
212    barcodeMetadata: &BarcodeMetadata,
213    isLeft: bool,
214) {
215    // Remove codewords which do not match the metadata
216    // TODO Maybe we should keep the incorrect codewords for the start and end positions?
217    for codeword_row in codewords.iter_mut() {
218        if let Some(codeword) = codeword_row {
219            let rowIndicatorValue = codeword.getValue() % 30;
220            let mut codewordRowNumber = codeword.getRowNumber();
221            if codewordRowNumber > barcodeMetadata.getRowCount() as i32 {
222                *codeword_row = None;
223                continue;
224            }
225            if !isLeft {
226                codewordRowNumber += 2;
227            }
228            match codewordRowNumber % 3 {
229                0 if rowIndicatorValue * 3 + 1 != barcodeMetadata.getRowCountUpperPart() => {
230                    *codeword_row = None;
231                }
232                1 if rowIndicatorValue / 3 != barcodeMetadata.getErrorCorrectionLevel()
233                    || rowIndicatorValue % 3 != barcodeMetadata.getRowCountLowerPart() =>
234                {
235                    *codeword_row = None;
236                }
237                2 if rowIndicatorValue + 1 != barcodeMetadata.getColumnCount() => {
238                    *codeword_row = None;
239                }
240                _ => {}
241            }
242        } else {
243            continue;
244        }
245    }
246}
247
248// TODO maybe we should add missing codewords to store the correct row number to make
249// finding row numbers for other columns easier
250// use row height count to make detection of invalid row numbers more reliable
251fn adjustIncompleteIndicatorColumnRowNumbers(
252    col: &mut DetectionRXingResultColumn,
253    barcodeMetadata: &BarcodeMetadata,
254) -> i32 {
255    let boundingBox = col.getBoundingBox();
256    let top = if col.isLeft() {
257        boundingBox.getTopLeft()
258    } else {
259        boundingBox.getTopRight()
260    };
261    let bottom = if col.isLeft() {
262        boundingBox.getBottomLeft()
263    } else {
264        boundingBox.getBottomRight()
265    };
266    let firstRow = col.imageRowToCodewordIndex(top.y as u32);
267    let lastRow = col.imageRowToCodewordIndex(bottom.y as u32);
268    let averageRowHeight: f64 =
269        (lastRow as f64 - firstRow as f64) / barcodeMetadata.getRowCount() as f64;
270    let codewords = col.getCodewordsMut();
271    let mut barcodeRow = -1;
272    let mut maxRowHeight = 1;
273    let mut currentRowHeight = 0;
274    // todo: It might be clearer what we're doing if we rewrote this a different way?
275    // Perhaps codewords.iter_mut().skip(firstRow).take(lastRow-firstRow)?
276    for codword_opt in codewords.iter_mut().take(lastRow).skip(firstRow) {
277        // for (int codewordsRow = firstRow; codewordsRow < lastRow; codewordsRow++) {
278
279        if let Some(codeword) = codword_opt {
280            codeword.setRowNumberAsRowIndicatorColumn();
281
282            let rowDifference = codeword.getRowNumber() - barcodeRow;
283
284            // TODO improve handling with case where first row indicator doesn't start with 0
285
286            if rowDifference == 0 {
287                currentRowHeight += 1;
288            } else if rowDifference == 1 {
289                maxRowHeight = maxRowHeight.max(currentRowHeight);
290                currentRowHeight = 1;
291                barcodeRow = codeword.getRowNumber();
292            } else if codeword.getRowNumber() >= barcodeMetadata.getRowCount() as i32 {
293                *codword_opt = None;
294            } else {
295                barcodeRow = codeword.getRowNumber();
296                currentRowHeight = 1;
297            }
298        } else {
299            continue;
300        }
301    }
302    (averageRowHeight + 0.5) as i32
303}