From b61311261585f5b43fe510798837bbbcbd9fe9a4 Mon Sep 17 00:00:00 2001 From: dswitkin Date: Mon, 22 Jun 2009 21:03:34 +0000 Subject: [PATCH] Rewrote BitMatrix to allow rectangular 2D arrays, and so that every row begins with a new int, which makes it fast to copy out rows into BitArrays. This will be the basis of the upcoming bitmap refactoring for 1D Readers. git-svn-id: https://zxing.googlecode.com/svn/trunk@978 59b500cc-1b3d-0410-9834-0bbf25fbcc57 --- .../com/google/zxing/common/BitMatrix.java | 111 +++++++++++++----- .../zxing/common/BitMatrixTestCase.java | 93 +++++++++++++-- 2 files changed, 162 insertions(+), 42 deletions(-) diff --git a/core/src/com/google/zxing/common/BitMatrix.java b/core/src/com/google/zxing/common/BitMatrix.java index d72fe4a6f..b1689e7ff 100755 --- a/core/src/com/google/zxing/common/BitMatrix.java +++ b/core/src/com/google/zxing/common/BitMatrix.java @@ -17,12 +17,15 @@ package com.google.zxing.common; /** - *

Represents a square matrix of bits. In function arguments below, and throughout the common + *

Represents a 2D matrix of bits. In function arguments below, and throughout the common * module, x is the column position, and y is the row position. The ordering is always x, y. * The origin is at the top-left.

* - *

Internally the bits are represented in a compact 1-D array of 32-bit ints. - * The ordering of bits is row-major. Within each int, the least significant bits are used first, + *

Internally the bits are represented in a 1-D array of 32-bit ints. However, each row begins + * with a new int. This is done intentionally so that we can copy out a row into a BitArray very + * efficiently.

+ * + *

The ordering of bits is row-major. Within each int, the least significant bits are used first, * meaning they represent lower x values. This is compatible with BitArray's implementation.

* * @author Sean Owen @@ -30,20 +33,29 @@ package com.google.zxing.common; */ public final class BitMatrix { - private final int dimension; - private final int[] bits; + // TODO: Just like BitArray, these need to be public so ProGuard can inline them. + public final int width; + public final int height; + public final int rowSize; + public final int[] bits; + // A helper to construct a square matrix. public BitMatrix(int dimension) { - if (dimension < 1) { - throw new IllegalArgumentException("dimension must be at least 1"); + this(dimension, dimension); + } + + public BitMatrix(int width, int height) { + if (width < 1 || height < 1) { + throw new IllegalArgumentException("Both dimensions must be greater than 0"); } - this.dimension = dimension; - int numBits = dimension * dimension; - int arraySize = numBits >> 5; // one int per 32 bits - if ((numBits & 0x1F) != 0) { // plus one more if there are leftovers - arraySize++; + this.width = width; + this.height = height; + int rowSize = width >> 5; + if ((width & 0x1f) != 0) { + rowSize++; } - bits = new int[arraySize]; + this.rowSize = rowSize; + bits = new int[rowSize * height]; } /** @@ -54,8 +66,8 @@ public final class BitMatrix { * @return value of given bit in matrix */ public boolean get(int x, int y) { - int offset = y * dimension + x; - return ((bits[offset >> 5] >>> (offset & 0x1F)) & 0x01) != 0; + int offset = y * rowSize + (x >> 5); + return ((bits[offset] >>> (x & 0x1f)) & 1) != 0; } /** @@ -65,8 +77,8 @@ public final class BitMatrix { * @param y The vertical component (i.e. which row) */ public void set(int x, int y) { - int offset = y * dimension + x; - bits[offset >> 5] |= 1 << (offset & 0x1F); + int offset = y * rowSize + (x >> 5); + bits[offset] |= 1 << (x & 0x1f); } /** @@ -76,8 +88,8 @@ public final class BitMatrix { * @param y The vertical component (i.e. which row) */ public void flip(int x, int y) { - int offset = y * dimension + x; - bits[offset >> 5] ^= 1 << (offset & 0x1F); + int offset = y * rowSize + (x >> 5); + bits[offset] ^= 1 << (x & 0x1f); } /** @@ -90,37 +102,74 @@ public final class BitMatrix { */ public void setRegion(int left, int top, int width, int height) { if (top < 0 || left < 0) { - throw new IllegalArgumentException("left and top must be nonnegative"); + throw new IllegalArgumentException("Left and top must be nonnegative"); } if (height < 1 || width < 1) { - throw new IllegalArgumentException("height and width must be at least 1"); + throw new IllegalArgumentException("Height and width must be at least 1"); } int right = left + width; int bottom = top + height; - if (bottom > dimension || right > dimension) { - throw new IllegalArgumentException( - "top + height and left + width must be <= matrix dimension"); + if (bottom > this.height || right > this.width) { + throw new IllegalArgumentException("The region must fit inside the matrix"); } for (int y = top; y < bottom; y++) { - int yoffset = dimension * y; + int offset = y * rowSize; for (int x = left; x < right; x++) { - int xoffset = yoffset + x; - bits[xoffset >> 5] |= 1 << (xoffset & 0x1F); + bits[offset + (x >> 5)] |= 1 << (x & 0x1f); } } } /** + * A fast method to retrieve one row of data from the matrix as a BitArray. + * + * @param y The row to retrieve + * @param row An optional caller-allocated BitArray, will be allocated if null or too small + * @return The resulting BitArray - this reference should always be used even when passing + * your own row + */ + BitArray getRow(int y, BitArray row) { + if (row == null || row.getSize() < width) { + row = new BitArray(width); + } + int offset = y * rowSize; + for (int x = 0; x < rowSize; x++) { + row.setBulk(x * 32, bits[offset + x]); + } + return row; + } + + /** + * @return The width of the matrix + */ + public int getWidth() { + return width; + } + + /** + * @return The height of the matrix + */ + public int getHeight() { + return height; + } + + /** + * This method is for compatibility with older code. It's only logical to call if the matrix + * is square, so I'm throwing if that's not the case. + * * @return row/column dimension of this matrix */ public int getDimension() { - return dimension; + if (width != height) { + throw new RuntimeException("Can't call getDimension() on a non-square matrix"); + } + return width; } public String toString() { - StringBuffer result = new StringBuffer(dimension * (dimension + 1)); - for (int y = 0; y < dimension; y++) { - for (int x = 0; x < dimension; x++) { + StringBuffer result = new StringBuffer(height * (width + 1)); + for (int y = 0; y < height; y++) { + for (int x = 0; x < width; x++) { result.append(get(x, y) ? "X " : " "); } result.append('\n'); diff --git a/core/test/src/com/google/zxing/common/BitMatrixTestCase.java b/core/test/src/com/google/zxing/common/BitMatrixTestCase.java index 9ece356bd..91c876928 100644 --- a/core/test/src/com/google/zxing/common/BitMatrixTestCase.java +++ b/core/test/src/com/google/zxing/common/BitMatrixTestCase.java @@ -20,22 +20,23 @@ import junit.framework.TestCase; /** * @author Sean Owen + * @author dswitkin@google.com (Daniel Switkin) */ public final class BitMatrixTestCase extends TestCase { public void testGetSet() { BitMatrix matrix = new BitMatrix(33); assertEquals(33, matrix.getDimension()); - for (int i = 0; i < 33; i++) { - for (int j = 0; j < 33; j++) { - if (i * j % 3 == 0) { - matrix.set(j, i); + for (int y = 0; y < 33; y++) { + for (int x = 0; x < 33; x++) { + if (y * x % 3 == 0) { + matrix.set(x, y); } } } - for (int i = 0; i < 33; i++) { - for (int j = 0; j < 33; j++) { - assertEquals(i * j % 3 == 0, matrix.get(j, i)); + for (int y = 0; y < 33; y++) { + for (int x = 0; x < 33; x++) { + assertEquals(y * x % 3 == 0, matrix.get(x, y)); } } } @@ -43,11 +44,81 @@ public final class BitMatrixTestCase extends TestCase { public void testSetRegion() { BitMatrix matrix = new BitMatrix(5); matrix.setRegion(1, 1, 3, 3); - for (int i = 0; i < 5; i++) { - for (int j = 0; j < 5; j++) { - assertEquals(i >= 1 && i <= 3 && j >= 1 && j <= 3, matrix.get(j, i)); + for (int y = 0; y < 5; y++) { + for (int x = 0; x < 5; x++) { + assertEquals(y >= 1 && y <= 3 && x >= 1 && x <= 3, matrix.get(x, y)); } } } -} \ No newline at end of file + public void testRectangularMatrix() { + BitMatrix matrix = new BitMatrix(75, 20); + assertEquals(75, matrix.getWidth()); + assertEquals(20, matrix.getHeight()); + matrix.set(10, 0); + matrix.set(11, 1); + matrix.set(50, 2); + matrix.set(51, 3); + matrix.flip(74, 4); + matrix.flip(0, 5); + + // Should all be on + assertTrue(matrix.get(10, 0)); + assertTrue(matrix.get(11, 1)); + assertTrue(matrix.get(50, 2)); + assertTrue(matrix.get(51, 3)); + assertTrue(matrix.get(74, 4)); + assertTrue(matrix.get(0, 5)); + + // Flip a couple back off + matrix.flip(50, 2); + matrix.flip(51, 3); + assertFalse(matrix.get(50, 2)); + assertFalse(matrix.get(51, 3)); + } + + public void testRectangularSetRegion() { + BitMatrix matrix = new BitMatrix(320, 240); + assertEquals(320, matrix.getWidth()); + assertEquals(240, matrix.getHeight()); + matrix.setRegion(105, 22, 80, 12); + + // Only bits in the region should be on + for (int y = 0; y < 240; y++) { + for (int x = 0; x < 320; x++) { + assertEquals(y >= 22 && y < 34 && x >= 105 && x < 185, matrix.get(x, y)); + } + } + } + + public void testGetRow() { + BitMatrix matrix = new BitMatrix(102, 5); + for (int x = 0; x < 102; x++) { + if ((x & 3) == 0) { + matrix.set(x, 2); + } + } + + // Should allocate + BitArray array = matrix.getRow(2, null); + assertEquals(102, array.getSize()); + + // Should reallocate + BitArray array2 = new BitArray(60); + array2 = matrix.getRow(2, array2); + assertEquals(102, array2.getSize()); + + // Should use provided object, with original BitArray size + BitArray array3 = new BitArray(200); + array3 = matrix.getRow(2, array3); + assertEquals(200, array3.getSize()); + + for (int x = 0; x < 102; x++) { + boolean on = ((x & 3) == 0); + assertEquals(on, array.get(x)); + assertEquals(on, array2.get(x)); + assertEquals(on, array3.get(x)); + } + } + +}