1 module ithox.qrcode.common.bitmatrix; 2 3 import ithox.qrcode.common.bitutils; 4 import ithox.qrcode.common.bitarray; 5 6 /** 7 * Bit matrix. 8 * 9 * Represents a 2D matrix of bits. In function arguments below, and throughout 10 * the common module, x is the column position, and y is the row position. The 11 * ordering is always x, y. The origin is at the top-left. 12 */ 13 class BitMatrix 14 { 15 16 private 17 { 18 int width, height, rowSize; 19 BitArrayBitType[] bits; 20 } 21 22 public @property int Width() 23 { 24 return this.width; 25 } 26 27 public @property int Height() 28 { 29 return this.height; 30 } 31 32 public @property int RowSize() 33 { 34 return this.rowSize; 35 } 36 37 public @property BitArrayBitType[] Bits() 38 { 39 return this.bits; 40 } 41 42 this(int width) 43 { 44 this(width, width); 45 } 46 47 this(int width, int height) 48 { 49 if (width < 1 || height < 1) 50 { 51 throw new Exception("Both dimensions must be greater than zero"); 52 } 53 this.width = width; 54 this.height = height; 55 this.rowSize = (width + 31) >> 5; 56 this.bits = new BitArrayBitType[rowSize * height]; 57 } 58 59 /** 60 * Gets the requested bit, where true means black. 61 * 62 * @param integer $x 63 * @param integer $y 64 * @return boolean 65 */ 66 public bool get(int x, int y) 67 { 68 auto offset = y * this.rowSize + (x >> 5); 69 return (BitUtils.unsignedRightShift(this.bits[offset], (x & 0x1f)) & 1) != 0; 70 } 71 /** 72 * Sets the given bit to true. 73 * 74 * @param integer $x 75 * @param integer $y 76 * @return void 77 */ 78 public void set(int x, int y) 79 { 80 auto offset = y * this.rowSize + (x >> 5); 81 this.bits[offset] = this.bits[offset] | (1 << (x & 0x1f)); 82 } 83 84 /** 85 * Flips the given bit. 86 * 87 * @param integer x 88 * @param integer y 89 * @return void 90 */ 91 public void flip(int x, int y) 92 { 93 auto offset = y * this.rowSize + (x >> 5); 94 this.bits[offset] = this.bits[offset] ^ (1 << (x & 0x1f)); 95 } 96 /** 97 * Clears all bits (set to false). 98 * 99 * @return void 100 */ 101 public void clear() 102 { 103 this.bits[] = 0; 104 } 105 /** 106 * Sets a square region of the bit matrix to true. 107 * 108 * @param integer left 109 * @param integer top 110 * @param integer width 111 * @param integer height 112 * @return void 113 */ 114 public void setRegion(int left, int top, int width, int height) 115 { 116 if (top < 0 || left < 0) 117 { 118 throw new Exception("Left and top must be non-negative"); 119 } 120 if (height < 1 || width < 1) 121 { 122 throw new Exception("Width and height must be at least 1"); 123 } 124 auto right = left + width; 125 auto bottom = top + height; 126 if (bottom > this.height || right > this.width) 127 { 128 throw new Exception("The region must fit inside the matrix"); 129 } 130 for (int y = top; y < bottom; y++) 131 { 132 auto offset = y * this.rowSize; 133 for (auto x = left; x < right; x++) 134 { 135 auto index = offset + (x >> 5); 136 this.bits[index] = this.bits[index] | (1 << (x & 0x1f)); 137 } 138 } 139 } 140 /** 141 * A fast method to retrieve one row of data from the matrix as a BitArray. 142 * 143 * @param integer y 144 * @param BitArray row 145 * @return BitArray 146 */ 147 public BitArray getRow(int y, BitArray row) 148 { 149 if (row is null || row.getSize() < this.width) 150 { 151 row = new BitArray(this.width); 152 } 153 auto offset = y * this.rowSize; 154 for (auto x = 0; x < this.rowSize; x++) 155 { 156 row.setBulk(x << 5, this.bits[offset + x]); 157 } 158 return row; 159 } 160 161 public BitArray getRow(int y) 162 { 163 return getRow(y, new BitArray(this.width)); 164 } 165 166 /** 167 * Sets a row of data from a BitArray. 168 * 169 * @param integer $y 170 * @param BitArray $row 171 * @return void 172 */ 173 public void setRow(int y, BitArray row) 174 { 175 auto bits = row.getBitArray(); 176 for (auto i = 0; i < this.rowSize; i++) 177 { 178 this.bits[y * this.rowSize + i] = bits[i]; 179 } 180 } 181 182 /** 183 * This is useful in detecting the enclosing rectangle of a 'pure' barcode. 184 * 185 * @return SplFixedArray 186 */ 187 public int[] getEnclosingRectangle() 188 { 189 auto left = this.width; 190 auto top = this.height; 191 auto right = -1; 192 auto bottom = -1; 193 for (auto y = 0; y < this.height; y++) 194 { 195 for (auto x32 = 0; x32 < this.rowSize; x32++) 196 { 197 auto _bits = this.bits[y * this.rowSize + x32]; 198 if (_bits != 0) 199 { 200 if (y < top) 201 { 202 top = y; 203 } 204 if (y > bottom) 205 { 206 bottom = y; 207 } 208 if (x32 * 32 < left) 209 { 210 auto bit = 0; 211 while ((_bits << (31 - bit)) == 0) 212 { 213 bit++; 214 } 215 if ((x32 * 32 + bit) < left) 216 { 217 left = x32 * 32 + bit; 218 } 219 } 220 } 221 if (x32 * 32 + 31 > right) 222 { 223 auto bit = 31; 224 while (BitUtils.unsignedRightShift(_bits, bit) == 0) 225 { 226 bit--; 227 } 228 if ((x32 * 32 + bit) > right) 229 { 230 right = x32 * 32 + bit; 231 } 232 } 233 } 234 } 235 width = right - left; 236 height = bottom - top; 237 if (width < 0 || height < 0) 238 { 239 return null; 240 } 241 return [left, top, width, height]; 242 } 243 /** 244 * Gets the most top left set bit. 245 * 246 * This is useful in detecting a corner of a 'pure' barcode. 247 * 248 * @return SplFixedArray 249 */ 250 public int[] getTopLeftOnBit() 251 { 252 auto bitsOffset = 0; 253 while (bitsOffset < this.bits.length && this.bits[bitsOffset] == 0) 254 { 255 bitsOffset++; 256 } 257 if (bitsOffset == this.bits.length) 258 { 259 return null; 260 } 261 import std.conv; 262 263 auto x = to!int(bitsOffset / this.rowSize); 264 auto y = (bitsOffset % this.rowSize) << 5; 265 auto _bits = this.bits[bitsOffset]; 266 auto bit = 0; 267 while ((_bits << (31 - bit)) == 0) 268 { 269 bit++; 270 } 271 x += bit; 272 return [x, y]; 273 } 274 /** 275 * Gets the most bottom right set bit. 276 * 277 * This is useful in detecting a corner of a 'pure' barcode. 278 * 279 * @return SplFixedArray 280 */ 281 public int[] getBottomRightOnBit() 282 { 283 auto bitsOffset = this.bits.length - 1; 284 while (bitsOffset >= 0 && this.bits[bitsOffset] == 0) 285 { 286 bitsOffset--; 287 } 288 if (bitsOffset < 0) 289 { 290 return null; 291 } 292 import std.conv; 293 294 int x = to!int(bitsOffset / this.rowSize); 295 int y = to!int((bitsOffset % this.rowSize) << 5); 296 auto _bits = this.bits[bitsOffset]; 297 auto bit = 0; 298 while (BitUtils.unsignedRightShift(_bits, bit) == 0) 299 { 300 bit--; 301 } 302 x += bit; 303 return [x, y]; 304 } 305 306 }