OpenConcerto

Dépôt officiel du code source de l'ERP OpenConcerto
sonarqube

svn://code.openconcerto.org/openconcerto

Rev

Details | Last modification | View Log | RSS feed

Rev Author Line No. Line
181 ilm 1
/*
2
 * Copyright 2014 Robin Stuart
3
 *
4
 * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
5
 * in compliance with the License. You may obtain a copy of the License at
6
 *
7
 * http://www.apache.org/licenses/LICENSE-2.0
8
 *
9
 * Unless required by applicable law or agreed to in writing, software distributed under the License
10
 * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
11
 * or implied. See the License for the specific language governing permissions and limitations under
12
 * the License.
13
 */
14
package uk.org.okapibarcode.backend;
15
 
16
import static uk.org.okapibarcode.util.Arrays.positionOf;
17
 
18
import java.io.UnsupportedEncodingException;
19
 
20
/**
21
 * Implements Micro QR Code According to ISO/IEC 18004:2006 <br>
22
 * A miniature version of the QR Code symbol for short messages. QR Code symbols can encode
23
 * characters in the Latin-1 set and Kanji characters which are members of the Shift-JIS encoding
24
 * scheme.
25
 *
26
 * @author <a href="mailto:rstuart114@gmail.com">Robin Stuart</a>
27
 */
28
public class MicroQrCode extends Symbol {
29
 
30
    public enum EccMode {
31
        L, M, Q, H
32
    }
33
 
34
    private enum qrMode {
35
        NULL, KANJI, BINARY, ALPHANUM, NUMERIC
36
    }
37
 
38
    /* Table 5 - Encoding/Decoding table for Alphanumeric mode */
39
    private static final char[] RHODIUM = { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U',
40
            'V', 'W', 'X', 'Y', 'Z', ' ', '$', '%', '*', '+', '-', '.', '/', ':' };
41
 
42
    private static final int[] QR_ANNEX_C1 = {
43
            /* Micro QR Code format information */
44
            0x4445, 0x4172, 0x4e2b, 0x4b1c, 0x55ae, 0x5099, 0x5fc0, 0x5af7, 0x6793, 0x62a4, 0x6dfd, 0x68ca, 0x7678, 0x734f, 0x7c16, 0x7921, 0x06de, 0x03e9, 0x0cb0, 0x0987, 0x1735, 0x1202, 0x1d5b,
45
            0x186c, 0x2508, 0x203f, 0x2f66, 0x2a51, 0x34e3, 0x31d4, 0x3e8d, 0x3bba };
46
 
47
    private static final int[] MICRO_QR_SIZES = { 11, 13, 15, 17 };
48
 
49
    // user-specified values and settings
50
 
51
    private int preferredVersion;
52
    private EccMode preferredEccLevel = EccMode.L;
53
 
54
    // internal state calculated when setContent() is called
55
 
56
    private qrMode[] inputMode;
57
    private StringBuilder binary;
58
    private final int[] binaryCount = new int[4];
59
    private int[] grid;
60
    private int[] eval;
61
 
62
    /**
63
     * <p>
64
     * Sets the preferred symbol size. This value may be ignored if the data string is too large to
65
     * fit into the specified symbol. Input values correspond to symbol sizes as shown in the
66
     * following table.
67
     *
68
     * <table summary="Range of Micro QR symbol sizes">
69
     * <tbody>
70
     * <tr>
71
     * <th>Input</th>
72
     * <th>Version</th>
73
     * <th>Symbol Size</th>
74
     * </tr>
75
     * <tr>
76
     * <td>1</td>
77
     * <td>M1</td>
78
     * <td>11 x 11</td>
79
     * </tr>
80
     * <tr>
81
     * <td>2</td>
82
     * <td>M2</td>
83
     * <td>13 x 13</td>
84
     * </tr>
85
     * <tr>
86
     * <td>3</td>
87
     * <td>M3</td>
88
     * <td>15 x 15</td>
89
     * </tr>
90
     * <tr>
91
     * <td>4</td>
92
     * <td>M4</td>
93
     * <td>17 x 17</td>
94
     * </tr>
95
     * </tbody>
96
     * </table>
97
     *
98
     * @param version symbol size
99
     */
100
    public void setPreferredVersion(final int version) {
101
        if (version < 0 || version > 4) { // TODO: min 1
102
            throw new IllegalArgumentException("Invalid version: " + version);
103
        }
104
        this.preferredVersion = version;
105
    }
106
 
107
    /**
108
     * Returns the preferred symbol size.
109
     *
110
     * @return the preferred symbol size
111
     * @see #setPreferredVersion(int)
112
     */
113
    public int getPreferredVersion() {
114
        return this.preferredVersion;
115
    }
116
 
117
    /**
118
     * <p>
119
     * Set the amount of symbol space allocated to error correction. Levels are predefined according
120
     * to the following table:
121
     *
122
     * <table summary="Micro QR Error correction levels">
123
     * <tbody>
124
     * <tr>
125
     * <th>ECC Level</th>
126
     * <th>Error Correction Capacity</th>
127
     * <th>Recovery Capacity</th>
128
     * </tr>
129
     * <tr>
130
     * <td>L (default)</td>
131
     * <td>Approx 20% of symbol</td>
132
     * <td>Approx 7%</td>
133
     * </tr>
134
     * <tr>
135
     * <td>M</td>
136
     * <td>Approx 37% of symbol</td>
137
     * <td>Approx 15%</td>
138
     * </tr>
139
     * <tr>
140
     * <td>Q</td>
141
     * <td>Approx 55% of symbol</td>
142
     * <td>Approx 25%</td>
143
     * </tr>
144
     * <tr>
145
     * <td>H</td>
146
     * <td>Approx 65% of symbol</td>
147
     * <td>Approx 30%</td>
148
     * </tr>
149
     * </tbody>
150
     * </table>
151
     *
152
     * @param eccMode error correction level
153
     */
154
    public void setEccMode(final EccMode eccMode) {
155
        this.preferredEccLevel = eccMode;
156
    }
157
 
158
    /**
159
     * Returns the preferred ECC mode (error correction level).
160
     *
161
     * @return the preferred ECC mode
162
     * @see #setEccMode(EccMode)
163
     */
164
    public EccMode getEccMode() {
165
        return this.preferredEccLevel;
166
    }
167
 
168
    @Override
169
    protected void encode() {
170
        int i, j, size;
171
        final boolean[] version_valid = new boolean[4];
172
        int n_count, a_count;
173
        EccMode ecc_level;
174
        int version, autoversion;
175
        int bitmask;
176
        int format, format_full;
177
        final StringBuilder bin = new StringBuilder();
178
        boolean byteModeUsed;
179
        boolean alphanumModeUsed;
180
        boolean kanjiModeUsed;
181
 
182
        if (this.content.length() > 35) {
183
            throw new OkapiException("Input data too long");
184
        }
185
 
186
        inputCharCheck();
187
 
188
        for (i = 0; i < 4; i++) {
189
            version_valid[i] = true;
190
        }
191
 
192
        this.inputMode = new qrMode[40];
193
        selectEncodingMode();
194
 
195
        n_count = 0;
196
        a_count = 0;
197
        for (i = 0; i < this.content.length(); i++) {
198
            if (this.content.charAt(i) >= '0' && this.content.charAt(i) <= '9') {
199
                n_count++;
200
            }
201
            if (isAlphanumeric(this.content.charAt(i))) {
202
                a_count++;
203
            }
204
        }
205
 
206
        if (a_count == this.content.length()) {
207
            /* All data can be encoded in Alphanumeric mode */
208
            for (i = 0; i < this.content.length(); i++) {
209
                this.inputMode[i] = qrMode.ALPHANUM;
210
            }
211
        }
212
 
213
        if (n_count == this.content.length()) {
214
            /* All data can be encoded in Numeric mode */
215
            for (i = 0; i < this.content.length(); i++) {
216
                this.inputMode[i] = qrMode.NUMERIC;
217
            }
218
        }
219
 
220
        byteModeUsed = false;
221
        alphanumModeUsed = false;
222
        kanjiModeUsed = false;
223
 
224
        for (i = 0; i < this.content.length(); i++) {
225
            if (this.inputMode[i] == qrMode.BINARY) {
226
                byteModeUsed = true;
227
            }
228
 
229
            if (this.inputMode[i] == qrMode.ALPHANUM) {
230
                alphanumModeUsed = true;
231
            }
232
 
233
            if (this.inputMode[i] == qrMode.KANJI) {
234
                kanjiModeUsed = true;
235
            }
236
        }
237
 
238
        getBinaryLength();
239
 
240
        /* Eliminate possible versions depending on type of content */
241
        if (byteModeUsed) {
242
            version_valid[0] = false;
243
            version_valid[1] = false;
244
        }
245
 
246
        if (alphanumModeUsed) {
247
            version_valid[0] = false;
248
        }
249
 
250
        if (kanjiModeUsed) {
251
            version_valid[0] = false;
252
            version_valid[1] = false;
253
        }
254
 
255
        /* Eliminate possible versions depending on length of binary data */
256
        if (this.binaryCount[0] > 20) {
257
            version_valid[0] = false;
258
        }
259
        if (this.binaryCount[1] > 40) {
260
            version_valid[1] = false;
261
        }
262
        if (this.binaryCount[2] > 84) {
263
            version_valid[2] = false;
264
        }
265
        if (this.binaryCount[3] > 128) {
266
            throw new OkapiException("Input data too long");
267
        }
268
 
269
        /* Eliminate possible versions depending on error correction level specified */
270
        ecc_level = this.preferredEccLevel;
271
 
272
        if (ecc_level == EccMode.H) {
273
            throw new OkapiException("Error correction level H not available");
274
        }
275
 
276
        if (ecc_level == EccMode.Q) {
277
            version_valid[0] = false;
278
            version_valid[1] = false;
279
            version_valid[2] = false;
280
            if (this.binaryCount[3] > 80) {
281
                throw new OkapiException("Input data too long");
282
            }
283
        }
284
 
285
        if (ecc_level == EccMode.M) {
286
            version_valid[0] = false;
287
            if (this.binaryCount[1] > 32) {
288
                version_valid[1] = false;
289
            }
290
            if (this.binaryCount[2] > 68) {
291
                version_valid[2] = false;
292
            }
293
            if (this.binaryCount[3] > 112) {
294
                throw new OkapiException("Input data too long");
295
            }
296
        }
297
 
298
        autoversion = 3;
299
        if (version_valid[2]) {
300
            autoversion = 2;
301
        }
302
        if (version_valid[1]) {
303
            autoversion = 1;
304
        }
305
        if (version_valid[0]) {
306
            autoversion = 0;
307
        }
308
 
309
        version = autoversion;
310
        /* Get version from user */
311
        if (this.preferredVersion >= 1 && this.preferredVersion <= 4) {
312
            if (this.preferredVersion - 1 >= autoversion) {
313
                version = this.preferredVersion - 1;
314
            }
315
        }
316
 
317
        /* If there is enough unused space then increase the error correction level */
318
        if (version == 3) {
319
            if (this.binaryCount[3] <= 112) {
320
                ecc_level = EccMode.M;
321
            }
322
            if (this.binaryCount[3] <= 80) {
323
                ecc_level = EccMode.Q;
324
            }
325
        }
326
 
327
        if (version == 2 && this.binaryCount[2] <= 68) {
328
            ecc_level = EccMode.M;
329
        }
330
 
331
        if (version == 1 && this.binaryCount[1] <= 32) {
332
            ecc_level = EccMode.M;
333
        }
334
 
335
        this.binary = new StringBuilder();
336
        generateBinary(version);
337
        if (this.binary.length() > 128) {
338
            throw new OkapiException("Input data too long");
339
        }
340
 
341
        switch (version) {
342
        case 0:
343
            generateM1Symbol();
344
            infoLine("Version: M1");
345
            break;
346
        case 1:
347
            generateM2Symbol(ecc_level);
348
            infoLine("Version: M2");
349
            infoLine("ECC Level: " + levelToLetter(ecc_level));
350
            break;
351
        case 2:
352
            generateM3Symbol(ecc_level);
353
            infoLine("Version: M3");
354
            infoLine("ECC Level: " + levelToLetter(ecc_level));
355
            break;
356
        case 3:
357
            generateM4Symbol(ecc_level);
358
            infoLine("Version: M4");
359
            infoLine("ECC Level: " + levelToLetter(ecc_level));
360
            break;
361
        }
362
 
363
        size = MICRO_QR_SIZES[version];
364
 
365
        this.grid = new int[size * size];
366
 
367
        for (i = 0; i < size; i++) {
368
            for (j = 0; j < size; j++) {
369
                this.grid[i * size + j] = 0;
370
            }
371
        }
372
 
373
        setupBitGrid(size);
374
        populateBitGrid(size);
375
        bitmask = applyBitmask(size);
376
 
377
        infoLine("Mask Pattern: " + Integer.toBinaryString(bitmask));
378
 
379
        /* Add format data */
380
        format = 0;
381
        switch (version) {
382
        case 1:
383
            switch (ecc_level) {
384
            case L:
385
                format = 1;
386
                break;
387
            case M:
388
                format = 2;
389
                break;
390
            }
391
            break;
392
        case 2:
393
            switch (ecc_level) {
394
            case L:
395
                format = 3;
396
                break;
397
            case M:
398
                format = 4;
399
                break;
400
            }
401
            break;
402
        case 3:
403
            switch (ecc_level) {
404
            case L:
405
                format = 5;
406
                break;
407
            case M:
408
                format = 6;
409
                break;
410
            case Q:
411
                format = 7;
412
                break;
413
            }
414
            break;
415
        }
416
 
417
        format_full = QR_ANNEX_C1[(format << 2) + bitmask];
418
 
419
        if ((format_full & 0x4000) != 0) {
420
            this.grid[8 * size + 1] += 0x01;
421
        }
422
        if ((format_full & 0x2000) != 0) {
423
            this.grid[8 * size + 2] += 0x01;
424
        }
425
        if ((format_full & 0x1000) != 0) {
426
            this.grid[8 * size + 3] += 0x01;
427
        }
428
        if ((format_full & 0x800) != 0) {
429
            this.grid[8 * size + 4] += 0x01;
430
        }
431
        if ((format_full & 0x400) != 0) {
432
            this.grid[8 * size + 5] += 0x01;
433
        }
434
        if ((format_full & 0x200) != 0) {
435
            this.grid[8 * size + 6] += 0x01;
436
        }
437
        if ((format_full & 0x100) != 0) {
438
            this.grid[8 * size + 7] += 0x01;
439
        }
440
        if ((format_full & 0x80) != 0) {
441
            this.grid[8 * size + 8] += 0x01;
442
        }
443
        if ((format_full & 0x40) != 0) {
444
            this.grid[7 * size + 8] += 0x01;
445
        }
446
        if ((format_full & 0x20) != 0) {
447
            this.grid[6 * size + 8] += 0x01;
448
        }
449
        if ((format_full & 0x10) != 0) {
450
            this.grid[5 * size + 8] += 0x01;
451
        }
452
        if ((format_full & 0x08) != 0) {
453
            this.grid[4 * size + 8] += 0x01;
454
        }
455
        if ((format_full & 0x04) != 0) {
456
            this.grid[3 * size + 8] += 0x01;
457
        }
458
        if ((format_full & 0x02) != 0) {
459
            this.grid[2 * size + 8] += 0x01;
460
        }
461
        if ((format_full & 0x01) != 0) {
462
            this.grid[1 * size + 8] += 0x01;
463
        }
464
 
465
        this.readable = "";
466
        this.pattern = new String[size];
467
        this.row_count = size;
468
        this.row_height = new int[size];
469
        for (i = 0; i < size; i++) {
470
            bin.setLength(0);
471
            for (j = 0; j < size; j++) {
472
                if ((this.grid[i * size + j] & 0x01) != 0) {
473
                    bin.append('1');
474
                } else {
475
                    bin.append('0');
476
                }
477
            }
478
            this.pattern[i] = bin2pat(bin);
479
            this.row_height[i] = 1;
480
        }
481
    }
482
 
483
    private void inputCharCheck() {
484
        int qmarkBefore, qmarkAfter;
485
        int i;
486
        byte[] temp;
487
 
488
        /* Check that input includes valid characters */
489
 
490
        if (this.content.matches("[\u0000-\u00FF]+")) {
491
            /* All characters in ISO 8859-1 */
492
            return;
493
        }
494
 
495
        /* Otherwise check for Shift-JIS characters */
496
        qmarkBefore = 0;
497
        for (i = 0; i < this.content.length(); i++) {
498
            if (this.content.charAt(i) == '?') {
499
                qmarkBefore++;
500
            }
501
        }
502
 
503
        try {
504
            temp = this.content.getBytes("SJIS");
505
        } catch (final UnsupportedEncodingException e) {
506
            throw new OkapiException("Character encoding error");
507
        }
508
 
509
        qmarkAfter = 0;
510
        for (i = 0; i < temp.length; i++) {
511
            if (temp[i] == '?') {
512
                qmarkAfter++;
513
            }
514
        }
515
 
516
        /* If these values are the same, conversion was successful */
517
        if (qmarkBefore != qmarkAfter) {
518
            throw new OkapiException("Invalid characters in input data");
519
        }
520
    }
521
 
522
    private char levelToLetter(final EccMode ecc_mode) {
523
        switch (ecc_mode) {
524
        case L:
525
            return 'L';
526
        case M:
527
            return 'M';
528
        case Q:
529
            return 'Q';
530
        case H:
531
            return 'H';
532
        default:
533
            return ' ';
534
        }
535
    }
536
 
537
    private void selectEncodingMode() {
538
        int i, j;
539
        int mlen;
540
        final int length = this.content.length();
541
 
542
        for (i = 0; i < length; i++) {
543
            if (this.content.charAt(i) > 0xff) {
544
                this.inputMode[i] = qrMode.KANJI;
545
            } else {
546
                this.inputMode[i] = qrMode.BINARY;
547
                if (isAlphanumeric(this.content.charAt(i))) {
548
                    this.inputMode[i] = qrMode.ALPHANUM;
549
                }
550
                if (this.content.charAt(i) >= '0' && this.content.charAt(i) <= '9') {
551
                    this.inputMode[i] = qrMode.NUMERIC;
552
                }
553
            }
554
        }
555
 
556
        /* If less than 6 numeric digits together then don't use numeric mode */
557
        for (i = 0; i < length; i++) {
558
            if (this.inputMode[i] == qrMode.NUMERIC) {
559
                if (i != 0 && this.inputMode[i - 1] != qrMode.NUMERIC || i == 0) {
560
                    mlen = 0;
561
                    while (mlen + i < length && this.inputMode[mlen + i] == qrMode.NUMERIC) {
562
                        mlen++;
563
                    }
564
                    if (mlen < 6) {
565
                        for (j = 0; j < mlen; j++) {
566
                            this.inputMode[i + j] = qrMode.ALPHANUM;
567
                        }
568
                    }
569
                }
570
            }
571
        }
572
 
573
        /* If less than 4 alphanumeric characters together then don't use alphanumeric mode */
574
        for (i = 0; i < length; i++) {
575
            if (this.inputMode[i] == qrMode.ALPHANUM) {
576
                if (i != 0 && this.inputMode[i - 1] != qrMode.ALPHANUM || i == 0) {
577
                    mlen = 0;
578
                    while (mlen + i < length && this.inputMode[mlen + i] == qrMode.ALPHANUM) {
579
                        mlen++;
580
                    }
581
                    if (mlen < 6) {
582
                        for (j = 0; j < mlen; j++) {
583
                            this.inputMode[i + j] = qrMode.BINARY;
584
                        }
585
                    }
586
                }
587
            }
588
        }
589
    }
590
 
591
    private boolean isAlphanumeric(final char cglyph) {
592
        /* Returns true if input glyph is in the Alphanumeric set */
593
        boolean retval = false;
594
 
595
        if (cglyph >= '0' && cglyph <= '9') {
596
            retval = true;
597
        }
598
        if (cglyph >= 'A' && cglyph <= 'Z') {
599
            retval = true;
600
        }
601
        switch (cglyph) {
602
        case ' ':
603
        case '$':
604
        case '%':
605
        case '*':
606
        case '+':
607
        case '-':
608
        case '.':
609
        case '/':
610
        case ':':
611
            retval = true;
612
            break;
613
        }
614
 
615
        return retval;
616
    }
617
 
618
    private String toBinary(final int data, int h) {
619
        final StringBuilder binary = new StringBuilder();
620
        for (; h != 0; h >>= 1) {
621
            if ((data & h) != 0) {
622
                binary.append('1');
623
            } else {
624
                binary.append('0');
625
            }
626
        }
627
        return binary.toString();
628
    }
629
 
630
    private void getBinaryLength() {
631
        int i;
632
        qrMode currentMode = qrMode.NULL;
633
        int blockLength;
634
 
635
        /* Always include a terminator */
636
        for (i = 0; i < 4; i++) {
637
            this.binaryCount[i] = 0;
638
        }
639
 
640
        for (i = 0; i < this.content.length(); i++) {
641
            if (currentMode != this.inputMode[i]) {
642
 
643
                blockLength = 0;
644
                do {
645
                    blockLength++;
646
                } while (i + blockLength < this.content.length() && this.inputMode[i + blockLength] == this.inputMode[i]);
647
 
648
                switch (this.inputMode[i]) {
649
                case KANJI:
650
                    this.binaryCount[2] += 5 + blockLength * 13;
651
                    this.binaryCount[3] += 7 + blockLength * 13;
652
 
653
                    break;
654
                case BINARY:
655
                    this.binaryCount[2] += 6 + blockLength * 8;
656
                    this.binaryCount[3] += 8 + blockLength * 8;
657
                    break;
658
                case ALPHANUM:
659
                    int alphaLength;
660
 
661
                    if (blockLength % 2 == 1) {
662
                        /* Odd length block */
663
                        alphaLength = (blockLength - 1) / 2 * 11;
664
                        alphaLength += 6;
665
                    } else {
666
                        /* Even length block */
667
                        alphaLength = blockLength / 2 * 11;
668
                    }
669
 
670
                    this.binaryCount[1] += 4 + alphaLength;
671
                    this.binaryCount[2] += 6 + alphaLength;
672
                    this.binaryCount[3] += 8 + alphaLength;
673
                    break;
674
                case NUMERIC:
675
                    int numLength;
676
 
677
                    switch (blockLength % 3) {
678
                    case 1:
679
                        /* one digit left over */
680
                        numLength = (blockLength - 1) / 3 * 10;
681
                        numLength += 4;
682
                        break;
683
                    case 2:
684
                        /* two digits left over */
685
                        numLength = (blockLength - 2) / 3 * 10;
686
                        numLength += 7;
687
                        break;
688
                    default:
689
                        /* blockLength is a multiple of 3 */
690
                        numLength = blockLength / 3 * 10;
691
                        break;
692
                    }
693
 
694
                    this.binaryCount[0] += 3 + numLength;
695
                    this.binaryCount[1] += 5 + numLength;
696
                    this.binaryCount[2] += 7 + numLength;
697
                    this.binaryCount[3] += 9 + numLength;
698
                    break;
699
                }
700
                currentMode = this.inputMode[i];
701
            }
702
        }
703
 
704
        /* Add terminator */
705
        if (this.binaryCount[1] < 37) {
706
            this.binaryCount[1] += 5;
707
        }
708
 
709
        if (this.binaryCount[2] < 81) {
710
            this.binaryCount[2] += 7;
711
        }
712
 
713
        if (this.binaryCount[3] < 125) {
714
            this.binaryCount[3] += 9;
715
        }
716
    }
717
 
718
    private void generateBinary(final int version) {
719
        int position = 0;
720
        int blockLength, i;
721
        qrMode data_block;
722
        int msb, lsb, prod, jis;
723
        String oneChar;
724
        byte[] jisBytes;
725
        int count, first, second, third;
726
 
727
        info("Encoding: ");
728
 
729
        do {
730
            data_block = this.inputMode[position];
731
            blockLength = 0;
732
            do {
733
                blockLength++;
734
            } while (blockLength + position < this.content.length() && this.inputMode[position + blockLength] == data_block);
735
 
736
            switch (data_block) {
737
            case KANJI:
738
                /* Kanji mode */
739
                /* Mode indicator */
740
                switch (version) {
741
                case 2:
742
                    this.binary.append("11");
743
                    break;
744
                case 3:
745
                    this.binary.append("011");
746
                    break;
747
                }
748
 
749
                /* Character count indicator */
750
                this.binary.append(toBinary(blockLength, 1 << version)); /* version = 2..3 */
751
 
752
                info("KANJ (" + blockLength + ") ");
753
 
754
                /* Character representation */
755
                for (i = 0; i < blockLength; i++) {
756
                    oneChar = "";
757
                    oneChar += this.content.charAt(position + i);
758
 
759
                    /* Convert Unicode input to Shift-JIS */
760
                    try {
761
                        jisBytes = oneChar.getBytes("SJIS");
762
                    } catch (final UnsupportedEncodingException e) {
763
                        throw new OkapiException("Character encoding error");
764
                    }
765
 
766
                    jis = (jisBytes[0] & 0xFF) << 8;
767
                    if (jisBytes.length > 1) {
768
                        jis += jisBytes[1] & 0xFF;
769
                    }
770
 
771
                    if (jis > 0x9fff) {
772
                        jis -= 0xc140;
773
                    } else {
774
                        jis -= 0x8140;
775
                    }
776
                    msb = (jis & 0xff00) >> 8;
777
                    lsb = jis & 0xff;
778
                    prod = msb * 0xc0 + lsb;
779
 
780
                    this.binary.append(toBinary(prod, 0x1000));
781
 
782
                    infoSpace(prod);
783
                }
784
 
785
                break;
786
            case BINARY:
787
                /* Byte mode */
788
                /* Mode indicator */
789
                switch (version) {
790
                case 2:
791
                    this.binary.append("10");
792
                    break;
793
                case 3:
794
                    this.binary.append("010");
795
                    break;
796
                }
797
 
798
                /* Character count indicator */
799
                this.binary.append(toBinary(blockLength, 2 << version)); /* version = 2..3 */
800
 
801
                info("BYTE (" + blockLength + ") ");
802
 
803
                /* Character representation */
804
                for (i = 0; i < blockLength; i++) {
805
                    final int lbyte = this.content.charAt(position + i);
806
                    this.binary.append(toBinary(lbyte, 0x80));
807
                    infoSpace(lbyte);
808
                }
809
 
810
                break;
811
            case ALPHANUM:
812
                /* Alphanumeric mode */
813
                /* Mode indicator */
814
                switch (version) {
815
                case 1:
816
                    this.binary.append("1");
817
                    break;
818
                case 2:
819
                    this.binary.append("01");
820
                    break;
821
                case 3:
822
                    this.binary.append("001");
823
                    break;
824
                }
825
 
826
                /* Character count indicator */
827
                this.binary.append(toBinary(blockLength, 2 << version)); /* version = 1..3 */
828
 
829
                info("ALPH (" + blockLength + ") ");
830
 
831
                /* Character representation */
832
                i = 0;
833
                while (i < blockLength) {
834
                    first = positionOf(this.content.charAt(position + i), RHODIUM);
835
                    count = 1;
836
                    prod = first;
837
 
838
                    if (i + 1 < blockLength) {
839
                        if (this.inputMode[position + i + 1] == qrMode.ALPHANUM) {
840
                            second = positionOf(this.content.charAt(position + i + 1), RHODIUM);
841
                            count = 2;
842
                            prod = first * 45 + second;
843
                        }
844
                    }
845
 
846
                    this.binary.append(toBinary(prod, 1 << 5 * count)); /* count = 1..2 */
847
 
848
                    infoSpace(prod);
849
 
850
                    i += 2;
851
                }
852
 
853
                break;
854
            case NUMERIC:
855
                /* Numeric mode */
856
                /* Mode indicator */
857
                switch (version) {
858
                case 1:
859
                    this.binary.append("0");
860
                    break;
861
                case 2:
862
                    this.binary.append("00");
863
                    break;
864
                case 3:
865
                    this.binary.append("000");
866
                    break;
867
                }
868
 
869
                /* Character count indicator */
870
                this.binary.append(toBinary(blockLength, 4 << version)); /* version = 0..3 */
871
 
872
                info("NUMB (" + blockLength + ") ");
873
 
874
                /* Character representation */
875
                i = 0;
876
                while (i < blockLength) {
877
                    first = Character.getNumericValue(this.content.charAt(position + i));
878
                    count = 1;
879
                    prod = first;
880
 
881
                    if (i + 1 < blockLength) {
882
                        if (this.inputMode[position + i + 1] == qrMode.NUMERIC) {
883
                            second = Character.getNumericValue(this.content.charAt(position + i + 1));
884
                            count = 2;
885
                            prod = prod * 10 + second;
886
                        }
887
                    }
888
 
889
                    if (i + 2 < blockLength) {
890
                        if (this.inputMode[position + i + 2] == qrMode.NUMERIC) {
891
                            third = Character.getNumericValue(this.content.charAt(position + i + 2));
892
                            count = 3;
893
                            prod = prod * 10 + third;
894
                        }
895
                    }
896
 
897
                    this.binary.append(toBinary(prod, 1 << 3 * count)); /* count = 1..3 */
898
 
899
                    infoSpace(prod);
900
 
901
                    i += 3;
902
                }
903
                break;
904
            }
905
 
906
            position += blockLength;
907
        } while (position < this.content.length() - 1);
908
 
909
        /* Add terminator */
910
        switch (version) {
911
        case 0:
912
            this.binary.append("000");
913
            break;
914
        case 1:
915
            if (this.binary.length() < 37) {
916
                this.binary.append("00000");
917
            }
918
            break;
919
        case 2:
920
            if (this.binary.length() < 81) {
921
                this.binary.append("0000000");
922
            }
923
            break;
924
        case 3:
925
            if (this.binary.length() < 125) {
926
                this.binary.append("000000000");
927
            }
928
            break;
929
        }
930
 
931
        infoLine();
932
    }
933
 
934
    private void generateM1Symbol() {
935
        int i, latch;
936
        int bits_total, bits_left, remainder;
937
        int data_codewords, ecc_codewords;
938
        final int[] data_blocks = new int[4];
939
        final int[] ecc_blocks = new int[3];
940
        final ReedSolomon rs = new ReedSolomon();
941
 
942
        bits_total = 20;
943
        latch = 0;
944
 
945
        /* Manage last (4-bit) block */
946
        bits_left = bits_total - this.binary.length();
947
        if (bits_left <= 4) {
948
            for (i = 0; i < bits_left; i++) {
949
                this.binary.append("0");
950
            }
951
            latch = 1;
952
        }
953
 
954
        if (latch == 0) {
955
            /* Complete current byte */
956
            remainder = 8 - this.binary.length() % 8;
957
            if (remainder == 8) {
958
                remainder = 0;
959
            }
960
            for (i = 0; i < remainder; i++) {
961
                this.binary.append("0");
962
            }
963
 
964
            /* Add padding */
965
            bits_left = bits_total - this.binary.length();
966
            if (bits_left > 4) {
967
                remainder = (bits_left - 4) / 8;
968
                for (i = 0; i < remainder; i++) {
969
                    if ((i & 1) != 0) {
970
                        this.binary.append("00010001");
971
                    } else {
972
                        this.binary.append("11101100");
973
                    }
974
                }
975
            }
976
            this.binary.append("0000");
977
        }
978
 
979
        data_codewords = 3;
980
        ecc_codewords = 2;
981
 
982
        /* Copy data into codewords */
983
        for (i = 0; i < data_codewords - 1; i++) {
984
            data_blocks[i] = 0;
985
            if (this.binary.charAt(i * 8) == '1') {
986
                data_blocks[i] += 0x80;
987
            }
988
            if (this.binary.charAt(i * 8 + 1) == '1') {
989
                data_blocks[i] += 0x40;
990
            }
991
            if (this.binary.charAt(i * 8 + 2) == '1') {
992
                data_blocks[i] += 0x20;
993
            }
994
            if (this.binary.charAt(i * 8 + 3) == '1') {
995
                data_blocks[i] += 0x10;
996
            }
997
            if (this.binary.charAt(i * 8 + 4) == '1') {
998
                data_blocks[i] += 0x08;
999
            }
1000
            if (this.binary.charAt(i * 8 + 5) == '1') {
1001
                data_blocks[i] += 0x04;
1002
            }
1003
            if (this.binary.charAt(i * 8 + 6) == '1') {
1004
                data_blocks[i] += 0x02;
1005
            }
1006
            if (this.binary.charAt(i * 8 + 7) == '1') {
1007
                data_blocks[i] += 0x01;
1008
            }
1009
        }
1010
        data_blocks[2] = 0;
1011
        if (this.binary.charAt(16) == '1') {
1012
            data_blocks[2] += 0x08;
1013
        }
1014
        if (this.binary.charAt(17) == '1') {
1015
            data_blocks[2] += 0x04;
1016
        }
1017
        if (this.binary.charAt(18) == '1') {
1018
            data_blocks[2] += 0x02;
1019
        }
1020
        if (this.binary.charAt(19) == '1') {
1021
            data_blocks[2] += 0x01;
1022
        }
1023
 
1024
        info("Codewords: ");
1025
        for (i = 0; i < data_codewords; i++) {
1026
            infoSpace(data_blocks[i]);
1027
        }
1028
        infoLine();
1029
 
1030
        /* Calculate Reed-Solomon error codewords */
1031
        rs.init_gf(0x11d);
1032
        rs.init_code(ecc_codewords, 0);
1033
        rs.encode(data_codewords, data_blocks);
1034
        for (i = 0; i < ecc_codewords; i++) {
1035
            ecc_blocks[i] = rs.getResult(i);
1036
        }
1037
 
1038
        /* Add Reed-Solomon codewords to binary data */
1039
        for (i = 0; i < ecc_codewords; i++) {
1040
            this.binary.append(toBinary(ecc_blocks[ecc_codewords - i - 1], 0x80));
1041
        }
1042
    }
1043
 
1044
    private void generateM2Symbol(final EccMode ecc_mode) {
1045
        int i;
1046
        int bits_total, bits_left, remainder;
1047
        int data_codewords, ecc_codewords;
1048
        final int[] data_blocks = new int[6];
1049
        final int[] ecc_blocks = new int[7];
1050
        final ReedSolomon rs = new ReedSolomon();
1051
 
1052
        bits_total = 40; // ecc_mode == EccMode.L
1053
        if (ecc_mode == EccMode.M) {
1054
            bits_total = 32;
1055
        }
1056
 
1057
        /* Complete current byte */
1058
        remainder = 8 - this.binary.length() % 8;
1059
        if (remainder == 8) {
1060
            remainder = 0;
1061
        }
1062
        for (i = 0; i < remainder; i++) {
1063
            this.binary.append("0");
1064
        }
1065
 
1066
        /* Add padding */
1067
        bits_left = bits_total - this.binary.length();
1068
        remainder = bits_left / 8;
1069
        for (i = 0; i < remainder; i++) {
1070
            if ((i & 1) != 0) {
1071
                this.binary.append("00010001");
1072
            } else {
1073
                this.binary.append("11101100");
1074
            }
1075
        }
1076
 
1077
        data_codewords = 5;
1078
        ecc_codewords = 5; // ecc_mode == EccMode.L
1079
        if (ecc_mode == EccMode.M) {
1080
            data_codewords = 4;
1081
            ecc_codewords = 6;
1082
        }
1083
 
1084
        /* Copy data into codewords */
1085
        for (i = 0; i < data_codewords; i++) {
1086
            data_blocks[i] = 0;
1087
            if (this.binary.charAt(i * 8) == '1') {
1088
                data_blocks[i] += 0x80;
1089
            }
1090
            if (this.binary.charAt(i * 8 + 1) == '1') {
1091
                data_blocks[i] += 0x40;
1092
            }
1093
            if (this.binary.charAt(i * 8 + 2) == '1') {
1094
                data_blocks[i] += 0x20;
1095
            }
1096
            if (this.binary.charAt(i * 8 + 3) == '1') {
1097
                data_blocks[i] += 0x10;
1098
            }
1099
            if (this.binary.charAt(i * 8 + 4) == '1') {
1100
                data_blocks[i] += 0x08;
1101
            }
1102
            if (this.binary.charAt(i * 8 + 5) == '1') {
1103
                data_blocks[i] += 0x04;
1104
            }
1105
            if (this.binary.charAt(i * 8 + 6) == '1') {
1106
                data_blocks[i] += 0x02;
1107
            }
1108
            if (this.binary.charAt(i * 8 + 7) == '1') {
1109
                data_blocks[i] += 0x01;
1110
            }
1111
        }
1112
 
1113
        info("Codewords: ");
1114
        for (i = 0; i < data_codewords; i++) {
1115
            infoSpace(data_blocks[i]);
1116
        }
1117
        infoLine();
1118
 
1119
        /* Calculate Reed-Solomon error codewords */
1120
        rs.init_gf(0x11d);
1121
        rs.init_code(ecc_codewords, 0);
1122
        rs.encode(data_codewords, data_blocks);
1123
        for (i = 0; i < ecc_codewords; i++) {
1124
            ecc_blocks[i] = rs.getResult(i);
1125
        }
1126
 
1127
        /* Add Reed-Solomon codewords to binary data */
1128
        for (i = 0; i < ecc_codewords; i++) {
1129
            this.binary.append(toBinary(ecc_blocks[ecc_codewords - i - 1], 0x80));
1130
        }
1131
    }
1132
 
1133
    private void generateM3Symbol(final EccMode ecc_mode) {
1134
        int i, latch;
1135
        int bits_total, bits_left, remainder;
1136
        int data_codewords, ecc_codewords;
1137
        final int[] data_blocks = new int[12];
1138
        final int[] ecc_blocks = new int[12];
1139
        final ReedSolomon rs = new ReedSolomon();
1140
 
1141
        latch = 0;
1142
 
1143
        bits_total = 84; // ecc_mode == EccMode.L
1144
        if (ecc_mode == EccMode.M) {
1145
            bits_total = 68;
1146
        }
1147
 
1148
        /* Manage last (4-bit) block */
1149
        bits_left = bits_total - this.binary.length();
1150
        if (bits_left <= 4) {
1151
            for (i = 0; i < bits_left; i++) {
1152
                this.binary.append("0");
1153
            }
1154
            latch = 1;
1155
        }
1156
 
1157
        if (latch == 0) {
1158
            /* Complete current byte */
1159
            remainder = 8 - this.binary.length() % 8;
1160
            if (remainder == 8) {
1161
                remainder = 0;
1162
            }
1163
            for (i = 0; i < remainder; i++) {
1164
                this.binary.append("0");
1165
            }
1166
 
1167
            /* Add padding */
1168
            bits_left = bits_total - this.binary.length();
1169
            if (bits_left > 4) {
1170
                remainder = (bits_left - 4) / 8;
1171
                for (i = 0; i < remainder; i++) {
1172
                    if ((i & 1) != 0) {
1173
                        this.binary.append("00010001");
1174
                    } else {
1175
                        this.binary.append("11101100");
1176
                    }
1177
                }
1178
            }
1179
            this.binary.append("0000");
1180
        }
1181
 
1182
        data_codewords = 11;
1183
        ecc_codewords = 6; // ecc_mode == EccMode.L
1184
        if (ecc_mode == EccMode.M) {
1185
            data_codewords = 9;
1186
            ecc_codewords = 8;
1187
        }
1188
 
1189
        /* Copy data into codewords */
1190
        for (i = 0; i < data_codewords - 1; i++) {
1191
            data_blocks[i] = 0;
1192
            if (this.binary.charAt(i * 8) == '1') {
1193
                data_blocks[i] += 0x80;
1194
            }
1195
            if (this.binary.charAt(i * 8 + 1) == '1') {
1196
                data_blocks[i] += 0x40;
1197
            }
1198
            if (this.binary.charAt(i * 8 + 2) == '1') {
1199
                data_blocks[i] += 0x20;
1200
            }
1201
            if (this.binary.charAt(i * 8 + 3) == '1') {
1202
                data_blocks[i] += 0x10;
1203
            }
1204
            if (this.binary.charAt(i * 8 + 4) == '1') {
1205
                data_blocks[i] += 0x08;
1206
            }
1207
            if (this.binary.charAt(i * 8 + 5) == '1') {
1208
                data_blocks[i] += 0x04;
1209
            }
1210
            if (this.binary.charAt(i * 8 + 6) == '1') {
1211
                data_blocks[i] += 0x02;
1212
            }
1213
            if (this.binary.charAt(i * 8 + 7) == '1') {
1214
                data_blocks[i] += 0x01;
1215
            }
1216
        }
1217
 
1218
        if (ecc_mode == EccMode.L) {
1219
            data_blocks[10] = 0;
1220
            if (this.binary.charAt(80) == '1') {
1221
                data_blocks[10] += 0x08;
1222
            }
1223
            if (this.binary.charAt(81) == '1') {
1224
                data_blocks[10] += 0x04;
1225
            }
1226
            if (this.binary.charAt(82) == '1') {
1227
                data_blocks[10] += 0x02;
1228
            }
1229
            if (this.binary.charAt(83) == '1') {
1230
                data_blocks[10] += 0x01;
1231
            }
1232
        }
1233
 
1234
        if (ecc_mode == EccMode.M) {
1235
            data_blocks[8] = 0;
1236
            if (this.binary.charAt(64) == '1') {
1237
                data_blocks[8] += 0x08;
1238
            }
1239
            if (this.binary.charAt(65) == '1') {
1240
                data_blocks[8] += 0x04;
1241
            }
1242
            if (this.binary.charAt(66) == '1') {
1243
                data_blocks[8] += 0x02;
1244
            }
1245
            if (this.binary.charAt(67) == '1') {
1246
                data_blocks[8] += 0x01;
1247
            }
1248
        }
1249
 
1250
        info("Codewords: ");
1251
        for (i = 0; i < data_codewords; i++) {
1252
            infoSpace(data_blocks[i]);
1253
        }
1254
        infoLine();
1255
 
1256
        /* Calculate Reed-Solomon error codewords */
1257
        rs.init_gf(0x11d);
1258
        rs.init_code(ecc_codewords, 0);
1259
        rs.encode(data_codewords, data_blocks);
1260
        for (i = 0; i < ecc_codewords; i++) {
1261
            ecc_blocks[i] = rs.getResult(i);
1262
        }
1263
 
1264
        /* Add Reed-Solomon codewords to binary data */
1265
        for (i = 0; i < ecc_codewords; i++) {
1266
            this.binary.append(toBinary(ecc_blocks[ecc_codewords - i - 1], 0x80));
1267
        }
1268
    }
1269
 
1270
    private void generateM4Symbol(final EccMode ecc_mode) {
1271
        int i;
1272
        int bits_total, bits_left, remainder;
1273
        int data_codewords, ecc_codewords;
1274
        final int[] data_blocks = new int[17];
1275
        final int[] ecc_blocks = new int[15];
1276
        final ReedSolomon rs = new ReedSolomon();
1277
 
1278
        bits_total = 128; // ecc_mode == EccMode.L
1279
        if (ecc_mode == EccMode.M) {
1280
            bits_total = 112;
1281
        }
1282
        if (ecc_mode == EccMode.Q) {
1283
            bits_total = 80;
1284
        }
1285
 
1286
        /* Complete current byte */
1287
        remainder = 8 - this.binary.length() % 8;
1288
        if (remainder == 8) {
1289
            remainder = 0;
1290
        }
1291
        for (i = 0; i < remainder; i++) {
1292
            this.binary.append("0");
1293
        }
1294
 
1295
        /* Add padding */
1296
        bits_left = bits_total - this.binary.length();
1297
        remainder = bits_left / 8;
1298
        for (i = 0; i < remainder; i++) {
1299
            if ((i & 1) != 0) {
1300
                this.binary.append("00010001");
1301
            } else {
1302
                this.binary.append("11101100");
1303
            }
1304
        }
1305
 
1306
        data_codewords = 16;
1307
        ecc_codewords = 8; // ecc_mode == EccMode.L
1308
        if (ecc_mode == EccMode.M) {
1309
            data_codewords = 14;
1310
            ecc_codewords = 10;
1311
        }
1312
        if (ecc_mode == EccMode.Q) {
1313
            data_codewords = 10;
1314
            ecc_codewords = 14;
1315
        }
1316
 
1317
        /* Copy data into codewords */
1318
        for (i = 0; i < data_codewords; i++) {
1319
            data_blocks[i] = 0;
1320
            if (this.binary.charAt(i * 8) == '1') {
1321
                data_blocks[i] += 0x80;
1322
            }
1323
            if (this.binary.charAt(i * 8 + 1) == '1') {
1324
                data_blocks[i] += 0x40;
1325
            }
1326
            if (this.binary.charAt(i * 8 + 2) == '1') {
1327
                data_blocks[i] += 0x20;
1328
            }
1329
            if (this.binary.charAt(i * 8 + 3) == '1') {
1330
                data_blocks[i] += 0x10;
1331
            }
1332
            if (this.binary.charAt(i * 8 + 4) == '1') {
1333
                data_blocks[i] += 0x08;
1334
            }
1335
            if (this.binary.charAt(i * 8 + 5) == '1') {
1336
                data_blocks[i] += 0x04;
1337
            }
1338
            if (this.binary.charAt(i * 8 + 6) == '1') {
1339
                data_blocks[i] += 0x02;
1340
            }
1341
            if (this.binary.charAt(i * 8 + 7) == '1') {
1342
                data_blocks[i] += 0x01;
1343
            }
1344
        }
1345
 
1346
        info("Codewords: ");
1347
        for (i = 0; i < data_codewords; i++) {
1348
            infoSpace(data_blocks[i]);
1349
        }
1350
        infoLine();
1351
 
1352
        /* Calculate Reed-Solomon error codewords */
1353
        rs.init_gf(0x11d);
1354
        rs.init_code(ecc_codewords, 0);
1355
        rs.encode(data_codewords, data_blocks);
1356
        for (i = 0; i < ecc_codewords; i++) {
1357
            ecc_blocks[i] = rs.getResult(i);
1358
        }
1359
 
1360
        /* Add Reed-Solomon codewords to binary data */
1361
        for (i = 0; i < ecc_codewords; i++) {
1362
            this.binary.append(toBinary(ecc_blocks[ecc_codewords - i - 1], 0x80));
1363
        }
1364
    }
1365
 
1366
    private void setupBitGrid(final int size) {
1367
        int i, toggle = 1;
1368
 
1369
        /* Add timing patterns */
1370
        for (i = 0; i < size; i++) {
1371
            if (toggle == 1) {
1372
                this.grid[i] = 0x21;
1373
                this.grid[i * size] = 0x21;
1374
                toggle = 0;
1375
            } else {
1376
                this.grid[i] = 0x20;
1377
                this.grid[i * size] = 0x20;
1378
                toggle = 1;
1379
            }
1380
        }
1381
 
1382
        /* Add finder patterns */
1383
        placeFinderPattern(size, 0, 0);
1384
 
1385
        /* Add separators */
1386
        for (i = 0; i < 7; i++) {
1387
            this.grid[7 * size + i] = 0x10;
1388
            this.grid[i * size + 7] = 0x10;
1389
        }
1390
        this.grid[7 * size + 7] = 0x10;
1391
 
1392
        /* Reserve space for format information */
1393
        for (i = 0; i < 8; i++) {
1394
            this.grid[8 * size + i] += 0x20;
1395
            this.grid[i * size + 8] += 0x20;
1396
        }
1397
        this.grid[8 * size + 8] += 0x20;
1398
    }
1399
 
1400
    private void placeFinderPattern(final int size, final int x, final int y) {
1401
        int xp, yp;
1402
 
1403
        final int[] finder = { 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 1, 1, 0, 1, 1, 1, 0, 1, 1, 0, 1, 1, 1, 0, 1, 1, 0, 1, 1, 1, 0, 1, 1, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1 };
1404
 
1405
        for (xp = 0; xp < 7; xp++) {
1406
            for (yp = 0; yp < 7; yp++) {
1407
                if (finder[xp + 7 * yp] == 1) {
1408
                    this.grid[(yp + y) * size + xp + x] = 0x11;
1409
                } else {
1410
                    this.grid[(yp + y) * size + xp + x] = 0x10;
1411
                }
1412
            }
1413
        }
1414
    }
1415
 
1416
    private void populateBitGrid(final int size) {
1417
        boolean goingUp = true;
1418
        int row = 0; /* right hand side */
1419
 
1420
        int i, n, x, y;
1421
 
1422
        n = this.binary.length();
1423
        y = size - 1;
1424
        i = 0;
1425
        do {
1426
            x = size - 2 - row * 2;
1427
 
1428
            if ((this.grid[y * size + x + 1] & 0xf0) == 0) {
1429
                if (this.binary.charAt(i) == '1') {
1430
                    this.grid[y * size + x + 1] = 0x01;
1431
                } else {
1432
                    this.grid[y * size + x + 1] = 0x00;
1433
                }
1434
                i++;
1435
            }
1436
 
1437
            if (i < n) {
1438
                if ((this.grid[y * size + x] & 0xf0) == 0) {
1439
                    if (this.binary.charAt(i) == '1') {
1440
                        this.grid[y * size + x] = 0x01;
1441
                    } else {
1442
                        this.grid[y * size + x] = 0x00;
1443
                    }
1444
                    i++;
1445
                }
1446
            }
1447
 
1448
            if (goingUp) {
1449
                y--;
1450
            } else {
1451
                y++;
1452
            }
1453
            if (y == 0) {
1454
                /* reached the top */
1455
                row++;
1456
                y = 1;
1457
                goingUp = false;
1458
            }
1459
            if (y == size) {
1460
                /* reached the bottom */
1461
                row++;
1462
                y = size - 1;
1463
                goingUp = true;
1464
            }
1465
        } while (i < n);
1466
    }
1467
 
1468
    private int applyBitmask(final int size) {
1469
        int x, y;
1470
        int p;
1471
        int local_pattern;
1472
        final int[] value = new int[8];
1473
        int best_val, best_pattern;
1474
        int bit;
1475
 
1476
        final int[] mask = new int[size * size];
1477
        this.eval = new int[size * size];
1478
 
1479
        /* Perform data masking */
1480
        for (x = 0; x < size; x++) {
1481
            for (y = 0; y < size; y++) {
1482
                mask[y * size + x] = 0x00;
1483
 
1484
                if ((this.grid[y * size + x] & 0xf0) == 0) {
1485
                    if ((y & 1) == 0) {
1486
                        mask[y * size + x] += 0x01;
1487
                    }
1488
 
1489
                    if ((y / 2 + x / 3 & 1) == 0) {
1490
                        mask[y * size + x] += 0x02;
1491
                    }
1492
 
1493
                    if (((y * x & 1) + y * x % 3 & 1) == 0) {
1494
                        mask[y * size + x] += 0x04;
1495
                    }
1496
 
1497
                    if (((y + x & 1) + y * x % 3 & 1) == 0) {
1498
                        mask[y * size + x] += 0x08;
1499
                    }
1500
                }
1501
            }
1502
        }
1503
 
1504
        for (x = 0; x < size; x++) {
1505
            for (y = 0; y < size; y++) {
1506
                if ((this.grid[y * size + x] & 0x01) != 0) {
1507
                    p = 0xff;
1508
                } else {
1509
                    p = 0x00;
1510
                }
1511
 
1512
                this.eval[y * size + x] = mask[y * size + x] ^ p;
1513
            }
1514
        }
1515
 
1516
        /* Evaluate result */
1517
        for (local_pattern = 0; local_pattern < 4; local_pattern++) {
1518
            value[local_pattern] = evaluateBitmask(size, local_pattern);
1519
        }
1520
 
1521
        best_pattern = 0;
1522
        best_val = value[0];
1523
        for (local_pattern = 1; local_pattern < 4; local_pattern++) {
1524
            if (value[local_pattern] > best_val) {
1525
                best_pattern = local_pattern;
1526
                best_val = value[local_pattern];
1527
            }
1528
        }
1529
 
1530
        /* Apply mask */
1531
        for (x = 0; x < size; x++) {
1532
            for (y = 0; y < size; y++) {
1533
                bit = 0;
1534
                switch (best_pattern) {
1535
                case 0:
1536
                    if ((mask[y * size + x] & 0x01) != 0) {
1537
                        bit = 1;
1538
                    }
1539
                    break;
1540
                case 1:
1541
                    if ((mask[y * size + x] & 0x02) != 0) {
1542
                        bit = 1;
1543
                    }
1544
                    break;
1545
                case 2:
1546
                    if ((mask[y * size + x] & 0x04) != 0) {
1547
                        bit = 1;
1548
                    }
1549
                    break;
1550
                case 3:
1551
                    if ((mask[y * size + x] & 0x08) != 0) {
1552
                        bit = 1;
1553
                    }
1554
                    break;
1555
                }
1556
                if (bit == 1) {
1557
                    if ((this.grid[y * size + x] & 0x01) != 0) {
1558
                        this.grid[y * size + x] = 0x00;
1559
                    } else {
1560
                        this.grid[y * size + x] = 0x01;
1561
                    }
1562
                }
1563
            }
1564
        }
1565
 
1566
        return best_pattern;
1567
    }
1568
 
1569
    private int evaluateBitmask(final int size, final int pattern) {
1570
        int sum1, sum2, i, filter = 0, retval;
1571
 
1572
        switch (pattern) {
1573
        case 0:
1574
            filter = 0x01;
1575
            break;
1576
        case 1:
1577
            filter = 0x02;
1578
            break;
1579
        case 2:
1580
            filter = 0x04;
1581
            break;
1582
        case 3:
1583
            filter = 0x08;
1584
            break;
1585
        }
1586
 
1587
        sum1 = 0;
1588
        sum2 = 0;
1589
        for (i = 1; i < size; i++) {
1590
            if ((this.eval[i * size + size - 1] & filter) != 0) {
1591
                sum1++;
1592
            }
1593
            if ((this.eval[(size - 1) * size + i] & filter) != 0) {
1594
                sum2++;
1595
            }
1596
        }
1597
 
1598
        if (sum1 <= sum2) {
1599
            retval = sum1 * 16 + sum2;
1600
        } else {
1601
            retval = sum2 * 16 + sum1;
1602
        }
1603
 
1604
        return retval;
1605
    }
1606
}