Skip to main content

rxing/datamatrix/detector/
datamatrix_detector.rs

1/*
2 * Copyright 2008 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::{
18    Exceptions, Point,
19    common::{
20        BitMatrix, DefaultGridSampler, GridSampler, Quadrilateral, Result,
21        detector::WhiteRectangleDetector,
22    },
23    point,
24};
25
26use super::DatamatrixDetectorResult;
27
28/**
29 * <p>Encapsulates logic that can detect a Data Matrix Code in an image, even if the Data Matrix Code
30 * is rotated or skewed, or partially obscured.</p>
31 *
32 * @author Sean Owen
33 */
34pub struct Detector<'a> {
35    image: &'a BitMatrix,
36    rectangleDetector: WhiteRectangleDetector<'a>,
37}
38impl<'a> Detector<'_> {
39    pub fn new(image: &'a BitMatrix) -> Result<Detector<'a>> {
40        Ok(Detector {
41            rectangleDetector: WhiteRectangleDetector::new_from_image(image)?,
42            image,
43        })
44    }
45
46    /**
47     * <p>Detects a Data Matrix Code in an image.</p>
48     *
49     * @return {@link DetectorRXingResult} encapsulating results of detecting a Data Matrix Code
50     * @throws NotFoundException if no Data Matrix Code can be found
51     */
52    pub fn detect(&self) -> Result<DatamatrixDetectorResult> {
53        let cornerPoints = self.rectangleDetector.detect()?;
54
55        let mut points = self.detectSolid1(cornerPoints);
56        points = self.detectSolid2(points);
57        if let Some(point) = self.correctTopRight(&points) {
58            points[3] = point;
59        } else {
60            return Err(Exceptions::not_found_with("point 4 unfound"));
61        }
62        // points[3] = self.correctTopRight(&points);
63        // if points[3] == null {
64        //   throw NotFoundException.getNotFoundInstance();
65        // }
66        points = self.shiftToModuleCenter(points);
67
68        let topLeft = points[0];
69        let bottomLeft = points[1];
70        let bottomRight = points[2];
71        let topRight = points[3];
72
73        let mut dimensionTop = self.transitionsBetween(topLeft, topRight) + 1;
74        let mut dimensionRight = self.transitionsBetween(bottomRight, topRight) + 1;
75        if (dimensionTop & 0x01) == 1 {
76            dimensionTop += 1;
77        }
78        if (dimensionRight & 0x01) == 1 {
79            dimensionRight += 1;
80        }
81
82        if 4 * dimensionTop < 6 * dimensionRight && 4 * dimensionRight < 6 * dimensionTop {
83            // The matrix is square
84            dimensionTop = dimensionTop.max(dimensionRight);
85            dimensionRight = dimensionTop.max(dimensionRight);
86        }
87
88        let bits = Self::sampleGrid(
89            self.image,
90            topLeft,
91            bottomLeft,
92            bottomRight,
93            topRight,
94            dimensionTop,
95            dimensionRight,
96        )?;
97
98        Ok(DatamatrixDetectorResult::new(
99            bits,
100            vec![topLeft, bottomLeft, bottomRight, topRight],
101        ))
102    }
103
104    #[inline]
105    fn shiftPoint(p: Point, to: Point, div: u32) -> Point {
106        let x = (to.x - p.x) / (div as f32 + 1.0);
107        let y = (to.y - p.y) / (div as f32 + 1.0);
108        point(p.x + x, p.y + y)
109    }
110
111    #[inline]
112    fn moveAway(p: Point, fromX: f32, fromY: f32) -> Point {
113        let mut x = p.x;
114        let mut y = p.y;
115
116        if x < fromX {
117            x -= 1.0;
118        } else {
119            x += 1.0;
120        }
121
122        if y < fromY {
123            y -= 1.0;
124        } else {
125            y += 1.0;
126        }
127
128        point(x, y)
129    }
130
131    /**
132     * Detect a solid side which has minimum transition.
133     */
134    fn detectSolid1(&self, cornerPoints: [Point; 4]) -> [Point; 4] {
135        // 0  2
136        // 1  3
137        let pointA = cornerPoints[0];
138        let pointB = cornerPoints[1];
139        let pointC = cornerPoints[3];
140        let pointD = cornerPoints[2];
141
142        let trAB = self.transitionsBetween(pointA, pointB);
143        let trBC = self.transitionsBetween(pointB, pointC);
144        let trCD = self.transitionsBetween(pointC, pointD);
145        let trDA = self.transitionsBetween(pointD, pointA);
146
147        // 0..3
148        // :  :
149        // 1--2
150        let mut min = trAB;
151        let mut points = [pointD, pointA, pointB, pointC];
152        if min > trBC {
153            min = trBC;
154            points[0] = pointA;
155            points[1] = pointB;
156            points[2] = pointC;
157            points[3] = pointD;
158        }
159        if min > trCD {
160            min = trCD;
161            points[0] = pointB;
162            points[1] = pointC;
163            points[2] = pointD;
164            points[3] = pointA;
165        }
166        if min > trDA {
167            points[0] = pointC;
168            points[1] = pointD;
169            points[2] = pointA;
170            points[3] = pointB;
171        }
172
173        points
174    }
175
176    /**
177     * Detect a second solid side next to first solid side.
178     */
179    fn detectSolid2(&self, points: [Point; 4]) -> [Point; 4] {
180        // A..D
181        // :  :
182        // B--C
183        let [pointA, pointB, pointC, pointD] = points;
184
185        // Transition detection on the edge is not stable.
186        // To safely detect, shift the points to the module center.
187        let tr = self.transitionsBetween(pointA, pointD);
188        let pointBs = Self::shiftPoint(pointB, pointC, (tr + 1) * 4);
189        let pointCs = Self::shiftPoint(pointC, pointB, (tr + 1) * 4);
190        let trBA = self.transitionsBetween(pointBs, pointA);
191        let trCD = self.transitionsBetween(pointCs, pointD);
192
193        // 0..3
194        // |  :
195        // 1--2
196        if trBA < trCD {
197            // solid sides: A-B-C
198            [pointA, pointB, pointC, pointD]
199            // points[0] = pointA;
200            // points[1] = pointB;
201            // points[2] = pointC;
202            // points[3] = pointD;
203        } else {
204            // solid sides: B-C-D
205            [pointB, pointC, pointD, pointA]
206            // points[0] = pointB;
207            // points[1] = pointC;
208            // points[2] = pointD;
209            // points[3] = pointA;
210        }
211    }
212
213    /**
214     * Calculates the corner position of the white top right module.
215     */
216    fn correctTopRight(&self, points: &[Point; 4]) -> Option<Point> {
217        // A..D
218        // |  :
219        // B--C
220        let pointA = points[0];
221        let pointB = points[1];
222        let pointC = points[2];
223        let pointD = points[3];
224
225        // shift points for safe transition detection.
226        let mut trTop = self.transitionsBetween(pointA, pointD);
227        let mut trRight = self.transitionsBetween(pointB, pointD);
228        let pointAs = Self::shiftPoint(pointA, pointB, (trRight + 1) * 4);
229        let pointCs = Self::shiftPoint(pointC, pointB, (trTop + 1) * 4);
230
231        trTop = self.transitionsBetween(pointAs, pointD);
232        trRight = self.transitionsBetween(pointCs, pointD);
233
234        let candidate1 = point(
235            pointD.x + (pointC.x - pointB.x) / (trTop as f32 + 1.0),
236            pointD.y + (pointC.y - pointB.y) / (trTop as f32 + 1.0),
237        );
238        let candidate2 = point(
239            pointD.x + (pointA.x - pointB.x) / (trRight as f32 + 1.0),
240            pointD.y + (pointA.y - pointB.y) / (trRight as f32 + 1.0),
241        );
242
243        if !self.isValid(candidate1) {
244            if self.isValid(candidate2) {
245                return Some(candidate2);
246            }
247            return None;
248        }
249        if !self.isValid(candidate2) {
250            return Some(candidate1);
251        }
252
253        let sumc1 = self.transitionsBetween(pointAs, candidate1)
254            + self.transitionsBetween(pointCs, candidate1);
255        let sumc2 = self.transitionsBetween(pointAs, candidate2)
256            + self.transitionsBetween(pointCs, candidate2);
257
258        if sumc1 > sumc2 {
259            Some(candidate1)
260        } else {
261            Some(candidate2)
262        }
263    }
264
265    /**
266     * Shift the edge points to the module center.
267     */
268    fn shiftToModuleCenter(&self, points: [Point; 4]) -> [Point; 4] {
269        // A..D
270        // |  :
271        // B--C
272        let [mut pointA, mut pointB, mut pointC, mut pointD] = points;
273
274        // calculate pseudo dimensions
275        let mut dimH = self.transitionsBetween(pointA, pointD) + 1;
276        let mut dimV = self.transitionsBetween(pointC, pointD) + 1;
277
278        // shift points for safe dimension detection
279        let mut pointAs = Self::shiftPoint(pointA, pointB, dimV * 4);
280        let mut pointCs = Self::shiftPoint(pointC, pointB, dimH * 4);
281
282        //  calculate more precise dimensions
283        dimH = self.transitionsBetween(pointAs, pointD) + 1;
284        dimV = self.transitionsBetween(pointCs, pointD) + 1;
285        if (dimH & 0x01) == 1 {
286            dimH += 1;
287        }
288        if (dimV & 0x01) == 1 {
289            dimV += 1;
290        }
291
292        // WhiteRectangleDetector returns points inside of the rectangle.
293        // I want points on the edges.
294        let centerX = (pointA.x + pointB.x + pointC.x + pointD.x) / 4.0;
295        let centerY = (pointA.y + pointB.y + pointC.y + pointD.y) / 4.0;
296        pointA = Self::moveAway(pointA, centerX, centerY);
297        pointB = Self::moveAway(pointB, centerX, centerY);
298        pointC = Self::moveAway(pointC, centerX, centerY);
299        pointD = Self::moveAway(pointD, centerX, centerY);
300
301        let mut pointBs;
302        let mut pointDs;
303
304        // shift points to the center of each modules
305        pointAs = Self::shiftPoint(pointA, pointB, dimV * 4);
306        pointAs = Self::shiftPoint(pointAs, pointD, dimH * 4);
307        pointBs = Self::shiftPoint(pointB, pointA, dimV * 4);
308        pointBs = Self::shiftPoint(pointBs, pointC, dimH * 4);
309        pointCs = Self::shiftPoint(pointC, pointD, dimV * 4);
310        pointCs = Self::shiftPoint(pointCs, pointB, dimH * 4);
311        pointDs = Self::shiftPoint(pointD, pointC, dimV * 4);
312        pointDs = Self::shiftPoint(pointDs, pointA, dimH * 4);
313
314        [pointAs, pointBs, pointCs, pointDs]
315    }
316
317    #[inline]
318    fn isValid(&self, p: Point) -> bool {
319        p.x >= 0.0
320            && p.x <= self.image.getWidth() as f32 - 1.0
321            && p.y > 0.0
322            && p.y <= self.image.getHeight() as f32 - 1.0
323    }
324
325    fn sampleGrid(
326        image: &BitMatrix,
327        topLeft: Point,
328        bottomLeft: Point,
329        bottomRight: Point,
330        topRight: Point,
331        dimensionX: u32,
332        dimensionY: u32,
333    ) -> Result<BitMatrix> {
334        let sampler = DefaultGridSampler;
335
336        let dst = Quadrilateral::new(
337            point(0.5, 0.5),
338            point(dimensionX as f32 - 0.5, 0.5),
339            point(dimensionX as f32 - 0.5, dimensionY as f32 - 0.5),
340            point(0.5, dimensionY as f32 - 0.5),
341        );
342        let src = Quadrilateral::new(topRight, topLeft, bottomRight, bottomLeft);
343
344        let (res, _) = sampler.sample_grid_detailed(image, dimensionX, dimensionY, dst, src)?;
345        Ok(res)
346    }
347
348    /**
349     * Counts the number of black/white transitions between two points, using something like Bresenham's algorithm.
350     */
351    fn transitionsBetween(&self, from: Point, to: Point) -> u32 {
352        // See QR Code Detector, sizeOfBlackWhiteBlackRun()
353        let mut fromX = from.x.floor() as i32;
354        let mut fromY = from.y.floor() as i32;
355        let mut toX = to.x.floor() as i32;
356        let mut toY = (self.image.getHeight() - 1).min(to.y.floor() as u32) as i32;
357
358        let steep = (toY - fromY).abs() > (toX - fromX).abs();
359        if steep {
360            std::mem::swap(&mut fromX, &mut fromY);
361            std::mem::swap(&mut toX, &mut toY);
362        }
363
364        let dx = (toX - fromX).abs();
365        let dy = (toY - fromY).abs();
366        let mut error = -dx / 2;
367        let ystep = if fromY < toY { 1 } else { -1 };
368        let xstep = if fromX < toX { 1 } else { -1 };
369        let mut transitions = 0;
370        let mut inBlack = self.image.get(
371            if steep { fromY as u32 } else { fromX as u32 },
372            if steep { fromX as u32 } else { fromY as u32 },
373        );
374        let mut x = fromX;
375        let mut y = fromY;
376        while x != toX {
377            // for (int x = fromX, y = fromY; x != toX; x += xstep) {
378            let isBlack = self.image.get(
379                if steep { y as u32 } else { x as u32 },
380                if steep { x as u32 } else { y as u32 },
381            );
382            if isBlack != inBlack {
383                transitions += 1;
384                inBlack = isBlack;
385            }
386            error += dy;
387            if error > 0 {
388                if y == toY {
389                    break;
390                }
391                y += ystep;
392                error -= dx;
393            }
394
395            x += xstep;
396        }
397        transitions
398    }
399}