1 /* 2 * Copyright 2007 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 17 package com.google.zxing.common; 18 19 import java.util.Arrays; 20 21 /** 22 * <p>Represents a 2D matrix of bits. In function arguments below, and throughout the common 23 * module, x is the column position, and y is the row position. The ordering is always x, y. 24 * The origin is at the top-left.</p> 25 * 26 * <p>Internally the bits are represented in a 1-D array of 32-bit ints. However, each row begins 27 * with a new int. This is done intentionally so that we can copy out a row into a BitArray very 28 * efficiently.</p> 29 * 30 * <p>The ordering of bits is row-major. Within each int, the least significant bits are used first, 31 * meaning they represent lower x values. This is compatible with BitArray's implementation.</p> 32 * 33 * @author Sean Owen 34 * @author dswitkin@google.com (Daniel Switkin) 35 */ 36 public final class BitMatrix implements Cloneable { 37 38 private int width; 39 private int height; 40 private int rowSize; 41 private int[] bits; 42 43 /** 44 * Creates an empty square {@code BitMatrix}. 45 * 46 * @param dimension height and width 47 */ BitMatrix(int dimension)48 public BitMatrix(int dimension) { 49 this(dimension, dimension); 50 } 51 52 /** 53 * Creates an empty {@code BitMatrix}. 54 * 55 * @param width bit matrix width 56 * @param height bit matrix height 57 */ BitMatrix(int width, int height)58 public BitMatrix(int width, int height) { 59 if (width < 1 || height < 1) { 60 throw new IllegalArgumentException("Both dimensions must be greater than 0"); 61 } 62 this.width = width; 63 this.height = height; 64 this.rowSize = (width + 31) / 32; 65 bits = new int[rowSize * height]; 66 } 67 BitMatrix(int width, int height, int rowSize, int[] bits)68 private BitMatrix(int width, int height, int rowSize, int[] bits) { 69 this.width = width; 70 this.height = height; 71 this.rowSize = rowSize; 72 this.bits = bits; 73 } 74 75 /** 76 * Interprets a 2D array of booleans as a {@code BitMatrix}, where "true" means an "on" bit. 77 * 78 * @param image bits of the image, as a row-major 2D array. Elements are arrays representing rows 79 * @return {@code BitMatrix} representation of image 80 */ parse(boolean[][] image)81 public static BitMatrix parse(boolean[][] image) { 82 int height = image.length; 83 int width = image[0].length; 84 BitMatrix bits = new BitMatrix(width, height); 85 for (int i = 0; i < height; i++) { 86 boolean[] imageI = image[i]; 87 for (int j = 0; j < width; j++) { 88 if (imageI[j]) { 89 bits.set(j, i); 90 } 91 } 92 } 93 return bits; 94 } 95 parse(String stringRepresentation, String setString, String unsetString)96 public static BitMatrix parse(String stringRepresentation, String setString, String unsetString) { 97 if (stringRepresentation == null) { 98 throw new IllegalArgumentException(); 99 } 100 101 boolean[] bits = new boolean[stringRepresentation.length()]; 102 int bitsPos = 0; 103 int rowStartPos = 0; 104 int rowLength = -1; 105 int nRows = 0; 106 int pos = 0; 107 while (pos < stringRepresentation.length()) { 108 if (stringRepresentation.charAt(pos) == '\n' || 109 stringRepresentation.charAt(pos) == '\r') { 110 if (bitsPos > rowStartPos) { 111 if (rowLength == -1) { 112 rowLength = bitsPos - rowStartPos; 113 } else if (bitsPos - rowStartPos != rowLength) { 114 throw new IllegalArgumentException("row lengths do not match"); 115 } 116 rowStartPos = bitsPos; 117 nRows++; 118 } 119 pos++; 120 } else if (stringRepresentation.startsWith(setString, pos)) { 121 pos += setString.length(); 122 bits[bitsPos] = true; 123 bitsPos++; 124 } else if (stringRepresentation.startsWith(unsetString, pos)) { 125 pos += unsetString.length(); 126 bits[bitsPos] = false; 127 bitsPos++; 128 } else { 129 throw new IllegalArgumentException( 130 "illegal character encountered: " + stringRepresentation.substring(pos)); 131 } 132 } 133 134 // no EOL at end? 135 if (bitsPos > rowStartPos) { 136 if (rowLength == -1) { 137 rowLength = bitsPos - rowStartPos; 138 } else if (bitsPos - rowStartPos != rowLength) { 139 throw new IllegalArgumentException("row lengths do not match"); 140 } 141 nRows++; 142 } 143 144 BitMatrix matrix = new BitMatrix(rowLength, nRows); 145 for (int i = 0; i < bitsPos; i++) { 146 if (bits[i]) { 147 matrix.set(i % rowLength, i / rowLength); 148 } 149 } 150 return matrix; 151 } 152 153 /** 154 * <p>Gets the requested bit, where true means black.</p> 155 * 156 * @param x The horizontal component (i.e. which column) 157 * @param y The vertical component (i.e. which row) 158 * @return value of given bit in matrix 159 */ get(int x, int y)160 public boolean get(int x, int y) { 161 int offset = y * rowSize + (x / 32); 162 return ((bits[offset] >>> (x & 0x1f)) & 1) != 0; 163 } 164 165 /** 166 * <p>Sets the given bit to true.</p> 167 * 168 * @param x The horizontal component (i.e. which column) 169 * @param y The vertical component (i.e. which row) 170 */ set(int x, int y)171 public void set(int x, int y) { 172 int offset = y * rowSize + (x / 32); 173 bits[offset] |= 1 << (x & 0x1f); 174 } 175 unset(int x, int y)176 public void unset(int x, int y) { 177 int offset = y * rowSize + (x / 32); 178 bits[offset] &= ~(1 << (x & 0x1f)); 179 } 180 181 /** 182 * <p>Flips the given bit.</p> 183 * 184 * @param x The horizontal component (i.e. which column) 185 * @param y The vertical component (i.e. which row) 186 */ flip(int x, int y)187 public void flip(int x, int y) { 188 int offset = y * rowSize + (x / 32); 189 bits[offset] ^= 1 << (x & 0x1f); 190 } 191 192 /** 193 * <p>Flips every bit in the matrix.</p> 194 */ flip()195 public void flip() { 196 int max = bits.length; 197 for (int i = 0; i < max; i++) { 198 bits[i] = ~bits[i]; 199 } 200 } 201 202 /** 203 * Exclusive-or (XOR): Flip the bit in this {@code BitMatrix} if the corresponding 204 * mask bit is set. 205 * 206 * @param mask XOR mask 207 */ xor(BitMatrix mask)208 public void xor(BitMatrix mask) { 209 if (width != mask.width || height != mask.height || rowSize != mask.rowSize) { 210 throw new IllegalArgumentException("input matrix dimensions do not match"); 211 } 212 BitArray rowArray = new BitArray(width); 213 for (int y = 0; y < height; y++) { 214 int offset = y * rowSize; 215 int[] row = mask.getRow(y, rowArray).getBitArray(); 216 for (int x = 0; x < rowSize; x++) { 217 bits[offset + x] ^= row[x]; 218 } 219 } 220 } 221 222 /** 223 * Clears all bits (sets to false). 224 */ clear()225 public void clear() { 226 int max = bits.length; 227 for (int i = 0; i < max; i++) { 228 bits[i] = 0; 229 } 230 } 231 232 /** 233 * <p>Sets a square region of the bit matrix to true.</p> 234 * 235 * @param left The horizontal position to begin at (inclusive) 236 * @param top The vertical position to begin at (inclusive) 237 * @param width The width of the region 238 * @param height The height of the region 239 */ setRegion(int left, int top, int width, int height)240 public void setRegion(int left, int top, int width, int height) { 241 if (top < 0 || left < 0) { 242 throw new IllegalArgumentException("Left and top must be nonnegative"); 243 } 244 if (height < 1 || width < 1) { 245 throw new IllegalArgumentException("Height and width must be at least 1"); 246 } 247 int right = left + width; 248 int bottom = top + height; 249 if (bottom > this.height || right > this.width) { 250 throw new IllegalArgumentException("The region must fit inside the matrix"); 251 } 252 for (int y = top; y < bottom; y++) { 253 int offset = y * rowSize; 254 for (int x = left; x < right; x++) { 255 bits[offset + (x / 32)] |= 1 << (x & 0x1f); 256 } 257 } 258 } 259 260 /** 261 * A fast method to retrieve one row of data from the matrix as a BitArray. 262 * 263 * @param y The row to retrieve 264 * @param row An optional caller-allocated BitArray, will be allocated if null or too small 265 * @return The resulting BitArray - this reference should always be used even when passing 266 * your own row 267 */ getRow(int y, BitArray row)268 public BitArray getRow(int y, BitArray row) { 269 if (row == null || row.getSize() < width) { 270 row = new BitArray(width); 271 } else { 272 row.clear(); 273 } 274 int offset = y * rowSize; 275 for (int x = 0; x < rowSize; x++) { 276 row.setBulk(x * 32, bits[offset + x]); 277 } 278 return row; 279 } 280 281 /** 282 * @param y row to set 283 * @param row {@link BitArray} to copy from 284 */ setRow(int y, BitArray row)285 public void setRow(int y, BitArray row) { 286 System.arraycopy(row.getBitArray(), 0, bits, y * rowSize, rowSize); 287 } 288 289 /** 290 * Modifies this {@code BitMatrix} to represent the same but rotated the given degrees (0, 90, 180, 270) 291 * 292 * @param degrees number of degrees to rotate through counter-clockwise (0, 90, 180, 270) 293 */ rotate(int degrees)294 public void rotate(int degrees) { 295 switch (degrees % 360) { 296 case 0: 297 return; 298 case 90: 299 rotate90(); 300 return; 301 case 180: 302 rotate180(); 303 return; 304 case 270: 305 rotate90(); 306 rotate180(); 307 return; 308 } 309 throw new IllegalArgumentException("degrees must be a multiple of 0, 90, 180, or 270"); 310 } 311 312 /** 313 * Modifies this {@code BitMatrix} to represent the same but rotated 180 degrees 314 */ rotate180()315 public void rotate180() { 316 BitArray topRow = new BitArray(width); 317 BitArray bottomRow = new BitArray(width); 318 int maxHeight = (height + 1) / 2; 319 for (int i = 0; i < maxHeight; i++) { 320 topRow = getRow(i, topRow); 321 int bottomRowIndex = height - 1 - i; 322 bottomRow = getRow(bottomRowIndex, bottomRow); 323 topRow.reverse(); 324 bottomRow.reverse(); 325 setRow(i, bottomRow); 326 setRow(bottomRowIndex, topRow); 327 } 328 } 329 330 /** 331 * Modifies this {@code BitMatrix} to represent the same but rotated 90 degrees counterclockwise 332 */ rotate90()333 public void rotate90() { 334 int newWidth = height; 335 int newHeight = width; 336 int newRowSize = (newWidth + 31) / 32; 337 int[] newBits = new int[newRowSize * newHeight]; 338 339 for (int y = 0; y < height; y++) { 340 for (int x = 0; x < width; x++) { 341 int offset = y * rowSize + (x / 32); 342 if (((bits[offset] >>> (x & 0x1f)) & 1) != 0) { 343 int newOffset = (newHeight - 1 - x) * newRowSize + (y / 32); 344 newBits[newOffset] |= 1 << (y & 0x1f); 345 } 346 } 347 } 348 width = newWidth; 349 height = newHeight; 350 rowSize = newRowSize; 351 bits = newBits; 352 } 353 354 /** 355 * This is useful in detecting the enclosing rectangle of a 'pure' barcode. 356 * 357 * @return {@code left,top,width,height} enclosing rectangle of all 1 bits, or null if it is all white 358 */ getEnclosingRectangle()359 public int[] getEnclosingRectangle() { 360 int left = width; 361 int top = height; 362 int right = -1; 363 int bottom = -1; 364 365 for (int y = 0; y < height; y++) { 366 for (int x32 = 0; x32 < rowSize; x32++) { 367 int theBits = bits[y * rowSize + x32]; 368 if (theBits != 0) { 369 if (y < top) { 370 top = y; 371 } 372 if (y > bottom) { 373 bottom = y; 374 } 375 if (x32 * 32 < left) { 376 int bit = 0; 377 while ((theBits << (31 - bit)) == 0) { 378 bit++; 379 } 380 if ((x32 * 32 + bit) < left) { 381 left = x32 * 32 + bit; 382 } 383 } 384 if (x32 * 32 + 31 > right) { 385 int bit = 31; 386 while ((theBits >>> bit) == 0) { 387 bit--; 388 } 389 if ((x32 * 32 + bit) > right) { 390 right = x32 * 32 + bit; 391 } 392 } 393 } 394 } 395 } 396 397 if (right < left || bottom < top) { 398 return null; 399 } 400 401 return new int[] {left, top, right - left + 1, bottom - top + 1}; 402 } 403 404 /** 405 * This is useful in detecting a corner of a 'pure' barcode. 406 * 407 * @return {@code x,y} coordinate of top-left-most 1 bit, or null if it is all white 408 */ getTopLeftOnBit()409 public int[] getTopLeftOnBit() { 410 int bitsOffset = 0; 411 while (bitsOffset < bits.length && bits[bitsOffset] == 0) { 412 bitsOffset++; 413 } 414 if (bitsOffset == bits.length) { 415 return null; 416 } 417 int y = bitsOffset / rowSize; 418 int x = (bitsOffset % rowSize) * 32; 419 420 int theBits = bits[bitsOffset]; 421 int bit = 0; 422 while ((theBits << (31 - bit)) == 0) { 423 bit++; 424 } 425 x += bit; 426 return new int[] {x, y}; 427 } 428 getBottomRightOnBit()429 public int[] getBottomRightOnBit() { 430 int bitsOffset = bits.length - 1; 431 while (bitsOffset >= 0 && bits[bitsOffset] == 0) { 432 bitsOffset--; 433 } 434 if (bitsOffset < 0) { 435 return null; 436 } 437 438 int y = bitsOffset / rowSize; 439 int x = (bitsOffset % rowSize) * 32; 440 441 int theBits = bits[bitsOffset]; 442 int bit = 31; 443 while ((theBits >>> bit) == 0) { 444 bit--; 445 } 446 x += bit; 447 448 return new int[] {x, y}; 449 } 450 451 /** 452 * @return The width of the matrix 453 */ getWidth()454 public int getWidth() { 455 return width; 456 } 457 458 /** 459 * @return The height of the matrix 460 */ getHeight()461 public int getHeight() { 462 return height; 463 } 464 465 /** 466 * @return The row size of the matrix 467 */ getRowSize()468 public int getRowSize() { 469 return rowSize; 470 } 471 472 @Override equals(Object o)473 public boolean equals(Object o) { 474 if (!(o instanceof BitMatrix)) { 475 return false; 476 } 477 BitMatrix other = (BitMatrix) o; 478 return width == other.width && height == other.height && rowSize == other.rowSize && 479 Arrays.equals(bits, other.bits); 480 } 481 482 @Override hashCode()483 public int hashCode() { 484 int hash = width; 485 hash = 31 * hash + width; 486 hash = 31 * hash + height; 487 hash = 31 * hash + rowSize; 488 hash = 31 * hash + Arrays.hashCode(bits); 489 return hash; 490 } 491 492 /** 493 * @return string representation using "X" for set and " " for unset bits 494 */ 495 @Override toString()496 public String toString() { 497 return toString("X ", " "); 498 } 499 500 /** 501 * @param setString representation of a set bit 502 * @param unsetString representation of an unset bit 503 * @return string representation of entire matrix utilizing given strings 504 */ toString(String setString, String unsetString)505 public String toString(String setString, String unsetString) { 506 return buildToString(setString, unsetString, "\n"); 507 } 508 509 /** 510 * @param setString representation of a set bit 511 * @param unsetString representation of an unset bit 512 * @param lineSeparator newline character in string representation 513 * @return string representation of entire matrix utilizing given strings and line separator 514 * @deprecated call {@link #toString(String,String)} only, which uses \n line separator always 515 */ 516 @Deprecated toString(String setString, String unsetString, String lineSeparator)517 public String toString(String setString, String unsetString, String lineSeparator) { 518 return buildToString(setString, unsetString, lineSeparator); 519 } 520 buildToString(String setString, String unsetString, String lineSeparator)521 private String buildToString(String setString, String unsetString, String lineSeparator) { 522 StringBuilder result = new StringBuilder(height * (width + 1)); 523 for (int y = 0; y < height; y++) { 524 for (int x = 0; x < width; x++) { 525 result.append(get(x, y) ? setString : unsetString); 526 } 527 result.append(lineSeparator); 528 } 529 return result.toString(); 530 } 531 532 @Override clone()533 public BitMatrix clone() { 534 return new BitMatrix(width, height, rowSize, bits.clone()); 535 } 536 537 } 538