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 }