zxing-js/library

View on GitHub
src/core/common/detector/WhiteRectangleDetector.ts

Summary

Maintainability
F
4 days
Test Coverage
A
96%
/*
 * 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.
 */

/*namespace com.google.zxing.common.detector {*/

import ResultPoint from '../../ResultPoint';
import BitMatrix from '../BitMatrix';

import MathUtils from './MathUtils';
import NotFoundException from '../../NotFoundException';

/**
 * <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
 */
export default class WhiteRectangleDetector {

    private static INIT_SIZE = 10;
    private static CORR = 1;

    private height: number; /*int*/
    private width: number; /*int*/
    private leftInit: number; /*int*/
    private rightInit: number; /*int*/
    private downInit: number; /*int*/
    private upInit: number; /*int*/

    // public constructor(private image: BitMatrix) /*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 constructor(private image: BitMatrix, initSize?: number /*int*/, x?: number /*int*/, y?: number /*int*/) /*throws NotFoundException*/ {
        this.height = image.getHeight();
        this.width = image.getWidth();
        if (undefined === initSize || null === initSize) {
            initSize = WhiteRectangleDetector.INIT_SIZE;
        }
        if (undefined === x || null === x) {
            x = image.getWidth() / 2 | 0;
        }
        if (undefined === y || null === y) {
            y = image.getHeight() / 2 | 0;
        }
        const halfsize = initSize / 2 | 0;
        this.leftInit = x - halfsize;
        this.rightInit = x + halfsize;
        this.upInit = y - halfsize;
        this.downInit = y + halfsize;
        if (this.upInit < 0 || this.leftInit < 0 || this.downInit >= this.height || this.rightInit >= this.width) {
            throw new NotFoundException();
        }
    }

    /**
     * <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 detect(): Array<ResultPoint> /*throws NotFoundException*/ {
        let left = this.leftInit;
        let right = this.rightInit;
        let up = this.upInit;
        let down = this.downInit;
        let sizeExceeded: boolean = false;
        let aBlackPointFoundOnBorder: boolean = true;
        let atLeastOneBlackPointFoundOnBorder: boolean = false;

        let atLeastOneBlackPointFoundOnRight: boolean = false;
        let atLeastOneBlackPointFoundOnBottom: boolean = false;
        let atLeastOneBlackPointFoundOnLeft: boolean = false;
        let atLeastOneBlackPointFoundOnTop: boolean = false;

        const width = this.width;
        const height = this.height;

        while (aBlackPointFoundOnBorder) {

            aBlackPointFoundOnBorder = false;

            // .....
            // .   |
            // .....
            let rightBorderNotWhite: boolean = true;
            while ((rightBorderNotWhite || !atLeastOneBlackPointFoundOnRight) && right < width) {
                rightBorderNotWhite = this.containsBlackPoint(up, down, right, false);
                if (rightBorderNotWhite) {
                    right++;
                    aBlackPointFoundOnBorder = true;
                    atLeastOneBlackPointFoundOnRight = true;
                } else if (!atLeastOneBlackPointFoundOnRight) {
                    right++;
                }
            }

            if (right >= width) {
                sizeExceeded = true;
                break;
            }

            // .....
            // .   .
            // .___.
            let bottomBorderNotWhite: boolean = true;
            while ((bottomBorderNotWhite || !atLeastOneBlackPointFoundOnBottom) && down < height) {
                bottomBorderNotWhite = this.containsBlackPoint(left, right, down, true);
                if (bottomBorderNotWhite) {
                    down++;
                    aBlackPointFoundOnBorder = true;
                    atLeastOneBlackPointFoundOnBottom = true;
                } else if (!atLeastOneBlackPointFoundOnBottom) {
                    down++;
                }
            }

            if (down >= height) {
                sizeExceeded = true;
                break;
            }

            // .....
            // |   .
            // .....
            let leftBorderNotWhite: boolean = true;
            while ((leftBorderNotWhite || !atLeastOneBlackPointFoundOnLeft) && left >= 0) {
                leftBorderNotWhite = this.containsBlackPoint(up, down, left, false);
                if (leftBorderNotWhite) {
                    left--;
                    aBlackPointFoundOnBorder = true;
                    atLeastOneBlackPointFoundOnLeft = true;
                } else if (!atLeastOneBlackPointFoundOnLeft) {
                    left--;
                }
            }

            if (left < 0) {
                sizeExceeded = true;
                break;
            }

            // .___.
            // .   .
            // .....
            let topBorderNotWhite: boolean = true;
            while ((topBorderNotWhite || !atLeastOneBlackPointFoundOnTop) && up >= 0) {
                topBorderNotWhite = this.containsBlackPoint(left, right, up, true);
                if (topBorderNotWhite) {
                    up--;
                    aBlackPointFoundOnBorder = true;
                    atLeastOneBlackPointFoundOnTop = true;
                } else if (!atLeastOneBlackPointFoundOnTop) {
                    up--;
                }
            }

            if (up < 0) {
                sizeExceeded = true;
                break;
            }

            if (aBlackPointFoundOnBorder) {
                atLeastOneBlackPointFoundOnBorder = true;
            }

        }

        if (!sizeExceeded && atLeastOneBlackPointFoundOnBorder) {

            const maxSize = right - left;

            let z: ResultPoint | null = null;
            for (let i = 1; z === null && i < maxSize; i++) {
                z = this.getBlackPointOnSegment(left, down - i, left + i, down);
            }

            if (z == null) {
                throw new NotFoundException();
            }

            let t: ResultPoint | null = null;
            // go down right
            for (let i = 1; t === null && i < maxSize; i++) {
                t = this.getBlackPointOnSegment(left, up + i, left + i, up);
            }

            if (t == null) {
                throw new NotFoundException();
            }

            let x: ResultPoint | null = null;
            // go down left
            for (let i = 1; x === null && i < maxSize; i++) {
                x = this.getBlackPointOnSegment(right, up + i, right - i, up);
            }

            if (x == null) {
                throw new NotFoundException();
            }

            let y: ResultPoint | null = null;
            // go up left
            for (let i = 1; y === null && i < maxSize; i++) {
                y = this.getBlackPointOnSegment(right, down - i, right - i, down);
            }

            if (y == null) {
                throw new NotFoundException();
            }

            return this.centerEdges(y, z, x, t);

        } else {
            throw new NotFoundException();
        }
    }

    private getBlackPointOnSegment(aX: number/*float*/, aY: number/*float*/, bX: number/*float*/, bY: number/*float*/): ResultPoint | null {
        const dist = MathUtils.round(MathUtils.distance(aX, aY, bX, bY));
        const xStep: number /*float*/ = (bX - aX) / dist;
        const yStep: number /*float*/ = (bY - aY) / dist;

        const image = this.image;

        for (let i = 0; i < dist; i++) {
            const x = MathUtils.round(aX + i * xStep);
            const 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 centerEdges(y: ResultPoint, z: ResultPoint,
        x: ResultPoint, t: ResultPoint): Array<ResultPoint> {

        //
        //       t            t
        //  z                      x
        //        x    OR    z
        //   y                    y
        //

        const yi: number /*float*/ = y.getX();
        const yj: number /*float*/ = y.getY();
        const zi: number /*float*/ = z.getX();
        const zj: number /*float*/ = z.getY();
        const xi: number /*float*/ = x.getX();
        const xj: number /*float*/ = x.getY();
        const ti: number /*float*/ = t.getX();
        const tj: number /*float*/ = t.getY();

        const CORR = WhiteRectangleDetector.CORR;

        if (yi < this.width / 2.0) {
            return [
                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(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 containsBlackPoint(a: number /*int*/, b: number /*int*/, fixed: number /*int*/, horizontal: boolean): boolean {

        const image = this.image;

        if (horizontal) {
            for (let x = a; x <= b; x++) {
                if (image.get(x, fixed)) {
                    return true;
                }
            }
        } else {
            for (let y = a; y <= b; y++) {
                if (image.get(fixed, y)) {
                    return true;
                }
            }
        }

        return false;
    }

}