| /* |
| * Copyright 2010 ZXing authors |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| |
| package com.google.zxing.common.detector; |
| |
| import com.google.zxing.NotFoundException; |
| import com.google.zxing.ResultPoint; |
| import com.google.zxing.common.BitMatrix; |
| |
| /** |
| * <p> |
| * Detects a candidate barcode-like rectangular region within an image. It |
| * starts around the center of the image, increases the size of the candidate |
| * region until it finds a white rectangular region. By keeping track of the |
| * last black points it encountered, it determines the corners of the barcode. |
| * </p> |
| * |
| * @author David Olivier |
| */ |
| public final class WhiteRectangleDetector { |
| |
| private static final int INIT_SIZE = 10; |
| private static final int CORR = 1; |
| |
| private final BitMatrix image; |
| private final int height; |
| private final int width; |
| private final int leftInit; |
| private final int rightInit; |
| private final int downInit; |
| private final int upInit; |
| |
| public WhiteRectangleDetector(BitMatrix image) throws NotFoundException { |
| this(image, INIT_SIZE, image.getWidth() / 2, image.getHeight() / 2); |
| } |
| |
| /** |
| * @param image barcode image to find a rectangle in |
| * @param initSize initial size of search area around center |
| * @param x x position of search center |
| * @param y y position of search center |
| * @throws NotFoundException if image is too small to accommodate {@code initSize} |
| */ |
| public WhiteRectangleDetector(BitMatrix image, int initSize, int x, int y) throws NotFoundException { |
| this.image = image; |
| height = image.getHeight(); |
| width = image.getWidth(); |
| int halfsize = initSize / 2; |
| leftInit = x - halfsize; |
| rightInit = x + halfsize; |
| upInit = y - halfsize; |
| downInit = y + halfsize; |
| if (upInit < 0 || leftInit < 0 || downInit >= height || rightInit >= width) { |
| throw NotFoundException.getNotFoundInstance(); |
| } |
| } |
| |
| /** |
| * <p> |
| * Detects a candidate barcode-like rectangular region within an image. It |
| * starts around the center of the image, increases the size of the candidate |
| * region until it finds a white rectangular region. |
| * </p> |
| * |
| * @return {@link ResultPoint}[] describing the corners of the rectangular |
| * region. The first and last points are opposed on the diagonal, as |
| * are the second and third. The first point will be the topmost |
| * point and the last, the bottommost. The second point will be |
| * leftmost and the third, the rightmost |
| * @throws NotFoundException if no Data Matrix Code can be found |
| */ |
| public ResultPoint[] detect() throws NotFoundException { |
| |
| int left = leftInit; |
| int right = rightInit; |
| int up = upInit; |
| int down = downInit; |
| boolean sizeExceeded = false; |
| boolean aBlackPointFoundOnBorder = true; |
| |
| boolean atLeastOneBlackPointFoundOnRight = false; |
| boolean atLeastOneBlackPointFoundOnBottom = false; |
| boolean atLeastOneBlackPointFoundOnLeft = false; |
| boolean atLeastOneBlackPointFoundOnTop = false; |
| |
| while (aBlackPointFoundOnBorder) { |
| |
| aBlackPointFoundOnBorder = false; |
| |
| // ..... |
| // . | |
| // ..... |
| boolean rightBorderNotWhite = true; |
| while ((rightBorderNotWhite || !atLeastOneBlackPointFoundOnRight) && right < width) { |
| rightBorderNotWhite = containsBlackPoint(up, down, right, false); |
| if (rightBorderNotWhite) { |
| right++; |
| aBlackPointFoundOnBorder = true; |
| atLeastOneBlackPointFoundOnRight = true; |
| } else if (!atLeastOneBlackPointFoundOnRight) { |
| right++; |
| } |
| } |
| |
| if (right >= width) { |
| sizeExceeded = true; |
| break; |
| } |
| |
| // ..... |
| // . . |
| // .___. |
| boolean bottomBorderNotWhite = true; |
| while ((bottomBorderNotWhite || !atLeastOneBlackPointFoundOnBottom) && down < height) { |
| bottomBorderNotWhite = containsBlackPoint(left, right, down, true); |
| if (bottomBorderNotWhite) { |
| down++; |
| aBlackPointFoundOnBorder = true; |
| atLeastOneBlackPointFoundOnBottom = true; |
| } else if (!atLeastOneBlackPointFoundOnBottom) { |
| down++; |
| } |
| } |
| |
| if (down >= height) { |
| sizeExceeded = true; |
| break; |
| } |
| |
| // ..... |
| // | . |
| // ..... |
| boolean leftBorderNotWhite = true; |
| while ((leftBorderNotWhite || !atLeastOneBlackPointFoundOnLeft) && left >= 0) { |
| leftBorderNotWhite = containsBlackPoint(up, down, left, false); |
| if (leftBorderNotWhite) { |
| left--; |
| aBlackPointFoundOnBorder = true; |
| atLeastOneBlackPointFoundOnLeft = true; |
| } else if (!atLeastOneBlackPointFoundOnLeft) { |
| left--; |
| } |
| } |
| |
| if (left < 0) { |
| sizeExceeded = true; |
| break; |
| } |
| |
| // .___. |
| // . . |
| // ..... |
| boolean topBorderNotWhite = true; |
| while ((topBorderNotWhite || !atLeastOneBlackPointFoundOnTop) && up >= 0) { |
| topBorderNotWhite = containsBlackPoint(left, right, up, true); |
| if (topBorderNotWhite) { |
| up--; |
| aBlackPointFoundOnBorder = true; |
| atLeastOneBlackPointFoundOnTop = true; |
| } else if (!atLeastOneBlackPointFoundOnTop) { |
| up--; |
| } |
| } |
| |
| if (up < 0) { |
| sizeExceeded = true; |
| break; |
| } |
| |
| } |
| |
| if (!sizeExceeded) { |
| |
| int maxSize = right - left; |
| |
| ResultPoint z = null; |
| for (int i = 1; z == null && i < maxSize; i++) { |
| z = getBlackPointOnSegment(left, down - i, left + i, down); |
| } |
| |
| if (z == null) { |
| throw NotFoundException.getNotFoundInstance(); |
| } |
| |
| ResultPoint t = null; |
| //go down right |
| for (int i = 1; t == null && i < maxSize; i++) { |
| t = getBlackPointOnSegment(left, up + i, left + i, up); |
| } |
| |
| if (t == null) { |
| throw NotFoundException.getNotFoundInstance(); |
| } |
| |
| ResultPoint x = null; |
| //go down left |
| for (int i = 1; x == null && i < maxSize; i++) { |
| x = getBlackPointOnSegment(right, up + i, right - i, up); |
| } |
| |
| if (x == null) { |
| throw NotFoundException.getNotFoundInstance(); |
| } |
| |
| ResultPoint y = null; |
| //go up left |
| for (int i = 1; y == null && i < maxSize; i++) { |
| y = getBlackPointOnSegment(right, down - i, right - i, down); |
| } |
| |
| if (y == null) { |
| throw NotFoundException.getNotFoundInstance(); |
| } |
| |
| return centerEdges(y, z, x, t); |
| |
| } else { |
| throw NotFoundException.getNotFoundInstance(); |
| } |
| } |
| |
| private ResultPoint getBlackPointOnSegment(float aX, float aY, float bX, float bY) { |
| int dist = MathUtils.round(MathUtils.distance(aX, aY, bX, bY)); |
| float xStep = (bX - aX) / dist; |
| float yStep = (bY - aY) / dist; |
| |
| for (int i = 0; i < dist; i++) { |
| int x = MathUtils.round(aX + i * xStep); |
| int y = MathUtils.round(aY + i * yStep); |
| if (image.get(x, y)) { |
| return new ResultPoint(x, y); |
| } |
| } |
| return null; |
| } |
| |
| /** |
| * recenters the points of a constant distance towards the center |
| * |
| * @param y bottom most point |
| * @param z left most point |
| * @param x right most point |
| * @param t top most point |
| * @return {@link ResultPoint}[] describing the corners of the rectangular |
| * region. The first and last points are opposed on the diagonal, as |
| * are the second and third. The first point will be the topmost |
| * point and the last, the bottommost. The second point will be |
| * leftmost and the third, the rightmost |
| */ |
| private ResultPoint[] centerEdges(ResultPoint y, ResultPoint z, |
| ResultPoint x, ResultPoint t) { |
| |
| // |
| // t t |
| // z x |
| // x OR z |
| // y y |
| // |
| |
| float yi = y.getX(); |
| float yj = y.getY(); |
| float zi = z.getX(); |
| float zj = z.getY(); |
| float xi = x.getX(); |
| float xj = x.getY(); |
| float ti = t.getX(); |
| float tj = t.getY(); |
| |
| if (yi < width / 2.0f) { |
| return new ResultPoint[]{ |
| new ResultPoint(ti - CORR, tj + CORR), |
| new ResultPoint(zi + CORR, zj + CORR), |
| new ResultPoint(xi - CORR, xj - CORR), |
| new ResultPoint(yi + CORR, yj - CORR)}; |
| } else { |
| return new ResultPoint[]{ |
| new ResultPoint(ti + CORR, tj + CORR), |
| new ResultPoint(zi + CORR, zj - CORR), |
| new ResultPoint(xi - CORR, xj + CORR), |
| new ResultPoint(yi - CORR, yj - CORR)}; |
| } |
| } |
| |
| /** |
| * Determines whether a segment contains a black point |
| * |
| * @param a min value of the scanned coordinate |
| * @param b max value of the scanned coordinate |
| * @param fixed value of fixed coordinate |
| * @param horizontal set to true if scan must be horizontal, false if vertical |
| * @return true if a black point has been found, else false. |
| */ |
| private boolean containsBlackPoint(int a, int b, int fixed, boolean horizontal) { |
| |
| if (horizontal) { |
| for (int x = a; x <= b; x++) { |
| if (image.get(x, fixed)) { |
| return true; |
| } |
| } |
| } else { |
| for (int y = a; y <= b; y++) { |
| if (image.get(fixed, y)) { |
| return true; |
| } |
| } |
| } |
| |
| return false; |
| } |
| |
| } |