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
This commit is contained in:
dswitkin 2009-06-22 21:03:34 +00:00
parent 7fec06465a
commit b613112615
2 changed files with 162 additions and 42 deletions

View file

@ -17,12 +17,15 @@
package com.google.zxing.common; package com.google.zxing.common;
/** /**
* <p>Represents a square matrix of bits. In function arguments below, and throughout the common * <p>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. * 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.</p> * The origin is at the top-left.</p>
* *
* <p>Internally the bits are represented in a compact 1-D array of 32-bit ints. * <p>Internally the bits are represented in a 1-D array of 32-bit ints. However, each row begins
* The ordering of bits is row-major. Within each int, the least significant bits are used first, * with a new int. This is done intentionally so that we can copy out a row into a BitArray very
* efficiently.</p>
*
* <p>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.</p> * meaning they represent lower x values. This is compatible with BitArray's implementation.</p>
* *
* @author Sean Owen * @author Sean Owen
@ -30,20 +33,29 @@ package com.google.zxing.common;
*/ */
public final class BitMatrix { public final class BitMatrix {
private final int dimension; // TODO: Just like BitArray, these need to be public so ProGuard can inline them.
private final int[] bits; 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) { public BitMatrix(int dimension) {
if (dimension < 1) { this(dimension, dimension);
throw new IllegalArgumentException("dimension must be at least 1"); }
public BitMatrix(int width, int height) {
if (width < 1 || height < 1) {
throw new IllegalArgumentException("Both dimensions must be greater than 0");
} }
this.dimension = dimension; this.width = width;
int numBits = dimension * dimension; this.height = height;
int arraySize = numBits >> 5; // one int per 32 bits int rowSize = width >> 5;
if ((numBits & 0x1F) != 0) { // plus one more if there are leftovers if ((width & 0x1f) != 0) {
arraySize++; 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 * @return value of given bit in matrix
*/ */
public boolean get(int x, int y) { public boolean get(int x, int y) {
int offset = y * dimension + x; int offset = y * rowSize + (x >> 5);
return ((bits[offset >> 5] >>> (offset & 0x1F)) & 0x01) != 0; return ((bits[offset] >>> (x & 0x1f)) & 1) != 0;
} }
/** /**
@ -65,8 +77,8 @@ public final class BitMatrix {
* @param y The vertical component (i.e. which row) * @param y The vertical component (i.e. which row)
*/ */
public void set(int x, int y) { public void set(int x, int y) {
int offset = y * dimension + x; int offset = y * rowSize + (x >> 5);
bits[offset >> 5] |= 1 << (offset & 0x1F); bits[offset] |= 1 << (x & 0x1f);
} }
/** /**
@ -76,8 +88,8 @@ public final class BitMatrix {
* @param y The vertical component (i.e. which row) * @param y The vertical component (i.e. which row)
*/ */
public void flip(int x, int y) { public void flip(int x, int y) {
int offset = y * dimension + x; int offset = y * rowSize + (x >> 5);
bits[offset >> 5] ^= 1 << (offset & 0x1F); bits[offset] ^= 1 << (x & 0x1f);
} }
/** /**
@ -90,37 +102,74 @@ public final class BitMatrix {
*/ */
public void setRegion(int left, int top, int width, int height) { public void setRegion(int left, int top, int width, int height) {
if (top < 0 || left < 0) { 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) { 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 right = left + width;
int bottom = top + height; int bottom = top + height;
if (bottom > dimension || right > dimension) { if (bottom > this.height || right > this.width) {
throw new IllegalArgumentException( throw new IllegalArgumentException("The region must fit inside the matrix");
"top + height and left + width must be <= matrix dimension");
} }
for (int y = top; y < bottom; y++) { for (int y = top; y < bottom; y++) {
int yoffset = dimension * y; int offset = y * rowSize;
for (int x = left; x < right; x++) { for (int x = left; x < right; x++) {
int xoffset = yoffset + x; bits[offset + (x >> 5)] |= 1 << (x & 0x1f);
bits[xoffset >> 5] |= 1 << (xoffset & 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 * @return row/column dimension of this matrix
*/ */
public int getDimension() { 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() { public String toString() {
StringBuffer result = new StringBuffer(dimension * (dimension + 1)); StringBuffer result = new StringBuffer(height * (width + 1));
for (int y = 0; y < dimension; y++) { for (int y = 0; y < height; y++) {
for (int x = 0; x < dimension; x++) { for (int x = 0; x < width; x++) {
result.append(get(x, y) ? "X " : " "); result.append(get(x, y) ? "X " : " ");
} }
result.append('\n'); result.append('\n');

View file

@ -20,22 +20,23 @@ import junit.framework.TestCase;
/** /**
* @author Sean Owen * @author Sean Owen
* @author dswitkin@google.com (Daniel Switkin)
*/ */
public final class BitMatrixTestCase extends TestCase { public final class BitMatrixTestCase extends TestCase {
public void testGetSet() { public void testGetSet() {
BitMatrix matrix = new BitMatrix(33); BitMatrix matrix = new BitMatrix(33);
assertEquals(33, matrix.getDimension()); assertEquals(33, matrix.getDimension());
for (int i = 0; i < 33; i++) { for (int y = 0; y < 33; y++) {
for (int j = 0; j < 33; j++) { for (int x = 0; x < 33; x++) {
if (i * j % 3 == 0) { if (y * x % 3 == 0) {
matrix.set(j, i); matrix.set(x, y);
} }
} }
} }
for (int i = 0; i < 33; i++) { for (int y = 0; y < 33; y++) {
for (int j = 0; j < 33; j++) { for (int x = 0; x < 33; x++) {
assertEquals(i * j % 3 == 0, matrix.get(j, i)); assertEquals(y * x % 3 == 0, matrix.get(x, y));
} }
} }
} }
@ -43,11 +44,81 @@ public final class BitMatrixTestCase extends TestCase {
public void testSetRegion() { public void testSetRegion() {
BitMatrix matrix = new BitMatrix(5); BitMatrix matrix = new BitMatrix(5);
matrix.setRegion(1, 1, 3, 3); matrix.setRegion(1, 1, 3, 3);
for (int i = 0; i < 5; i++) { for (int y = 0; y < 5; y++) {
for (int j = 0; j < 5; j++) { for (int x = 0; x < 5; x++) {
assertEquals(i >= 1 && i <= 3 && j >= 1 && j <= 3, matrix.get(j, i)); assertEquals(y >= 1 && y <= 3 && x >= 1 && x <= 3, matrix.get(x, y));
} }
} }
} }
} 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));
}
}
}