From d0c59c4c16e5d344f2d86369345718a9741882e9 Mon Sep 17 00:00:00 2001 From: srowen Date: Tue, 4 Oct 2011 10:12:55 +0000 Subject: [PATCH] Issue 727 contributed initial Maxicode support git-svn-id: https://zxing.googlecode.com/svn/trunk@1949 59b500cc-1b3d-0410-9834-0bbf25fbcc57 --- AUTHORS | 2 + core/src/com/google/zxing/BarcodeFormat.java | 3 + .../com/google/zxing/MultiFormatReader.java | 7 +- .../com/google/zxing/common/BitMatrix.java | 53 +++++ .../zxing/common/reedsolomon/GenericGF.java | 3 +- .../google/zxing/maxicode/MaxiCodeReader.java | 121 +++++++++++ .../maxicode/decoder/BitMatrixParser.java | 90 ++++++++ .../decoder/DecodedBitStreamParser.java | 192 ++++++++++++++++++ .../zxing/maxicode/decoder/Decoder.java | 116 +++++++++++ .../zxing/client/j2se/CommandLineRunner.java | 1 + 10 files changed, 586 insertions(+), 2 deletions(-) create mode 100644 core/src/com/google/zxing/maxicode/MaxiCodeReader.java create mode 100644 core/src/com/google/zxing/maxicode/decoder/BitMatrixParser.java create mode 100644 core/src/com/google/zxing/maxicode/decoder/DecodedBitStreamParser.java create mode 100644 core/src/com/google/zxing/maxicode/decoder/Decoder.java diff --git a/AUTHORS b/AUTHORS index 0ef65c7f2..074ed34ac 100644 --- a/AUTHORS +++ b/AUTHORS @@ -45,11 +45,13 @@ Kevin Xue (NetDragon Websoft Inc., China) Lachezar Dobrev Luiz Silva Luka Finžgar +Manuel Kasten Marcelo Mateusz Jędrasik Matrix44 Matthew Schulkind (Google) Matt York (LifeMarks) +mike32767 Mohamad Fairol Morgan Courbet Nikolaos Ftylitakis diff --git a/core/src/com/google/zxing/BarcodeFormat.java b/core/src/com/google/zxing/BarcodeFormat.java index 1e5d47958..91a8c7186 100644 --- a/core/src/com/google/zxing/BarcodeFormat.java +++ b/core/src/com/google/zxing/BarcodeFormat.java @@ -56,6 +56,9 @@ public final class BarcodeFormat { /** ITF (Interleaved Two of Five) 1D format. */ public static final BarcodeFormat ITF = new BarcodeFormat("ITF"); + /** MaxiCode 2D barcode format. */ + public static final BarcodeFormat MAXICODE = new BarcodeFormat("MAXICODE"); + /** PDF417 format. */ public static final BarcodeFormat PDF_417 = new BarcodeFormat("PDF_417"); diff --git a/core/src/com/google/zxing/MultiFormatReader.java b/core/src/com/google/zxing/MultiFormatReader.java index 5addb0afa..a7ab265db 100644 --- a/core/src/com/google/zxing/MultiFormatReader.java +++ b/core/src/com/google/zxing/MultiFormatReader.java @@ -21,6 +21,7 @@ import com.google.zxing.datamatrix.DataMatrixReader; import com.google.zxing.oned.MultiFormatOneDReader; import com.google.zxing.pdf417.PDF417Reader; import com.google.zxing.qrcode.QRCodeReader; +import com.google.zxing.maxicode.MaxiCodeReader; import java.util.Hashtable; import java.util.Vector; @@ -122,7 +123,10 @@ public final class MultiFormatReader implements Reader { } if (formats.contains(BarcodeFormat.PDF_417)) { readers.addElement(new PDF417Reader()); - } + } + if (formats.contains(BarcodeFormat.MAXICODE)) { + readers.addElement(new MaxiCodeReader()); + } // At end in "try harder" mode if (addOneDReader && tryHarder) { readers.addElement(new MultiFormatOneDReader(hints)); @@ -137,6 +141,7 @@ public final class MultiFormatReader implements Reader { readers.addElement(new DataMatrixReader()); readers.addElement(new AztecReader()); readers.addElement(new PDF417Reader()); + readers.addElement(new MaxiCodeReader()); if (tryHarder) { readers.addElement(new MultiFormatOneDReader(hints)); diff --git a/core/src/com/google/zxing/common/BitMatrix.java b/core/src/com/google/zxing/common/BitMatrix.java index 8bf75b289..38aa73248 100755 --- a/core/src/com/google/zxing/common/BitMatrix.java +++ b/core/src/com/google/zxing/common/BitMatrix.java @@ -144,6 +144,59 @@ public final class BitMatrix { return row; } + /** + * This is useful in detecting the enclosing rectangle of a 'pure' barcode. + * + * @return {left,top,width,height} enclosing rectangle of all 1 bits, or null if it is all white + */ + public int[] getEnclosingRectangle() { + int left = width; + int top = height; + int right = -1; + int bottom = -1; + + for (int y = 0; y < height; y++) { + for (int x32 = 0; x32 < rowSize; x32++) { + int theBits = bits[y * rowSize + x32]; + if (theBits != 0) { + if (y < top) { + top = y; + } + if (y > bottom) { + bottom = y; + } + if (x32 * 32 < left) { + int bit = 0; + while ((theBits << (31 - bit)) == 0) { + bit++; + } + if ((x32 * 32 + bit) < left) { + left = x32 * 32 + bit; + } + } + if (x32 * 32 + 31 > right) { + int bit = 31; + while ((theBits >>> bit) == 0) { + bit--; + } + if ((x32 * 32 + bit) > right) { + right = x32 * 32 + bit; + } + } + } + } + } + + int width = right - left; + int height = bottom - top; + + if (width < 0 || height < 0) { + return null; + } + + return new int[] {left, top, width, height}; + } + /** * This is useful in detecting a corner of a 'pure' barcode. * diff --git a/core/src/com/google/zxing/common/reedsolomon/GenericGF.java b/core/src/com/google/zxing/common/reedsolomon/GenericGF.java index 859c379ee..902a48e15 100644 --- a/core/src/com/google/zxing/common/reedsolomon/GenericGF.java +++ b/core/src/com/google/zxing/common/reedsolomon/GenericGF.java @@ -36,6 +36,7 @@ public final class GenericGF { public static final GenericGF QR_CODE_FIELD_256 = new GenericGF(0x011D, 256); // x^8 + x^4 + x^3 + x^2 + 1 public static final GenericGF DATA_MATRIX_FIELD_256 = new GenericGF(0x012D, 256); // x^8 + x^5 + x^3 + x^2 + 1 public static final GenericGF AZTEC_DATA_8 = DATA_MATRIX_FIELD_256; + public static final GenericGF MAXICODE_FIELD_64 = AZTEC_DATA_6; private static final int INITIALIZATION_THRESHOLD = 0; @@ -59,7 +60,7 @@ public final class GenericGF { this.size = size; if (size <= INITIALIZATION_THRESHOLD){ - initialize(); + initialize(); } } diff --git a/core/src/com/google/zxing/maxicode/MaxiCodeReader.java b/core/src/com/google/zxing/maxicode/MaxiCodeReader.java new file mode 100644 index 000000000..8abffd3c7 --- /dev/null +++ b/core/src/com/google/zxing/maxicode/MaxiCodeReader.java @@ -0,0 +1,121 @@ +/* + * Copyright 2011 ZXing authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.zxing.maxicode; + +import com.google.zxing.BarcodeFormat; +import com.google.zxing.BinaryBitmap; +import com.google.zxing.ChecksumException; +import com.google.zxing.DecodeHintType; +import com.google.zxing.FormatException; +import com.google.zxing.NotFoundException; +import com.google.zxing.Reader; +import com.google.zxing.Result; +import com.google.zxing.ResultMetadataType; +import com.google.zxing.ResultPoint; +import com.google.zxing.common.BitMatrix; +import com.google.zxing.common.DecoderResult; +import com.google.zxing.maxicode.decoder.Decoder; + +import java.util.Hashtable; + +/** + * This implementation can detect and decode a MaxiCode in an image. + */ +public final class MaxiCodeReader implements Reader { + + private static final ResultPoint[] NO_POINTS = new ResultPoint[0]; + private static final int MATRIX_WIDTH = 30; + private static final int MATRIX_HEIGHT = 33; + + private final Decoder decoder = new Decoder(); + + Decoder getDecoder() { + return decoder; + } + + /** + * Locates and decodes a MaxiCode in an image. + * + * @return a String representing the content encoded by the MaxiCode + * @throws NotFoundException if a MaxiCode cannot be found + * @throws FormatException if a MaxiCode cannot be decoded + * @throws ChecksumException if error correction fails + */ + public Result decode(BinaryBitmap image) throws NotFoundException, ChecksumException, FormatException { + return decode(image, null); + } + + public Result decode(BinaryBitmap image, Hashtable hints) + throws NotFoundException, ChecksumException, FormatException { + DecoderResult decoderResult; + if (hints != null && hints.containsKey(DecodeHintType.PURE_BARCODE)) { + BitMatrix bits = extractPureBits(image.getBlackMatrix()); + decoderResult = decoder.decode(bits, hints); + } else { + throw NotFoundException.getNotFoundInstance(); + } + + ResultPoint[] points = NO_POINTS; + Result result = new Result(decoderResult.getText(), decoderResult.getRawBytes(), points, BarcodeFormat.MAXICODE); + + if (decoderResult.getECLevel() != null) { + result.putMetadata(ResultMetadataType.ERROR_CORRECTION_LEVEL, decoderResult.getECLevel()); + } + return result; + } + + public void reset() { + // do nothing + } + + /** + * This method detects a code in a "pure" image -- that is, pure monochrome image + * which contains only an unrotated, unskewed, image of a code, with some white border + * around it. This is a specialized method that works exceptionally fast in this special + * case. + * + * @see com.google.zxing.pdf417.PDF417Reader#extractPureBits(BitMatrix) + * @see com.google.zxing.datamatrix.DataMatrixReader#extractPureBits(BitMatrix) + * @see com.google.zxing.qrcode.QRCodeReader#extractPureBits(BitMatrix) + */ + private static BitMatrix extractPureBits(BitMatrix image) throws NotFoundException { + + int[] enclosingRectangle = image.getEnclosingRectangle(); + if (enclosingRectangle == null) { + throw NotFoundException.getNotFoundInstance(); + } + + int left = enclosingRectangle[0]; + int top = enclosingRectangle[1]; + int width = enclosingRectangle[2]; + int height = enclosingRectangle[3]; + + // Now just read off the bits + BitMatrix bits = new BitMatrix(MATRIX_WIDTH, MATRIX_HEIGHT); + for (int y = 0; y < MATRIX_HEIGHT; y++) { + int iy = top + (y * height + height / 2) / MATRIX_HEIGHT; + for (int x = 0; x < MATRIX_WIDTH; x++) { + int ix = left + (x * width + width / 2 + (y & 0x01) * width / 2) / MATRIX_WIDTH; + if (image.get(ix, iy)) { + bits.set(x, y); + } + } + } + return bits; + } + +} diff --git a/core/src/com/google/zxing/maxicode/decoder/BitMatrixParser.java b/core/src/com/google/zxing/maxicode/decoder/BitMatrixParser.java new file mode 100644 index 000000000..4fc9fe7b3 --- /dev/null +++ b/core/src/com/google/zxing/maxicode/decoder/BitMatrixParser.java @@ -0,0 +1,90 @@ +/* + * Copyright 2011 ZXing authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.zxing.maxicode.decoder; + +import com.google.zxing.FormatException; +import com.google.zxing.common.BitMatrix; + +/** + * @author mike32767 + * @author Manuel Kasten + */ +final class BitMatrixParser { + + private static final int[][] BITNR = { + {121,120,127,126,133,132,139,138,145,144,151,150,157,156,163,162,169,168,175,174,181,180,187,186,193,192,199,198, -2, -2}, + {123,122,129,128,135,134,141,140,147,146,153,152,159,158,165,164,171,170,177,176,183,182,189,188,195,194,201,200,816, -3}, + {125,124,131,130,137,136,143,142,149,148,155,154,161,160,167,166,173,172,179,178,185,184,191,190,197,196,203,202,818,817}, + {283,282,277,276,271,270,265,264,259,258,253,252,247,246,241,240,235,234,229,228,223,222,217,216,211,210,205,204,819, -3}, + {285,284,279,278,273,272,267,266,261,260,255,254,249,248,243,242,237,236,231,230,225,224,219,218,213,212,207,206,821,820}, + {287,286,281,280,275,274,269,268,263,262,257,256,251,250,245,244,239,238,233,232,227,226,221,220,215,214,209,208,822, -3}, + {289,288,295,294,301,300,307,306,313,312,319,318,325,324,331,330,337,336,343,342,349,348,355,354,361,360,367,366,824,823}, + {291,290,297,296,303,302,309,308,315,314,321,320,327,326,333,332,339,338,345,344,351,350,357,356,363,362,369,368,825, -3}, + {293,292,299,298,305,304,311,310,317,316,323,322,329,328,335,334,341,340,347,346,353,352,359,358,365,364,371,370,827,826}, + {409,408,403,402,397,396,391,390, 79, 78, -2, -2, 13, 12, 37, 36, 2, -1, 44, 43,109,108,385,384,379,378,373,372,828, -3}, + {411,410,405,404,399,398,393,392, 81, 80, 40, -2, 15, 14, 39, 38, 3, -1, -1, 45,111,110,387,386,381,380,375,374,830,829}, + {413,412,407,406,401,400,395,394, 83, 82, 41, -3, -3, -3, -3, -3, 5, 4, 47, 46,113,112,389,388,383,382,377,376,831, -3}, + {415,414,421,420,427,426,103,102, 55, 54, 16, -3, -3, -3, -3, -3, -3, -3, 20, 19, 85, 84,433,432,439,438,445,444,833,832}, + {417,416,423,422,429,428,105,104, 57, 56, -3, -3, -3, -3, -3, -3, -3, -3, 22, 21, 87, 86,435,434,441,440,447,446,834, -3}, + {419,418,425,424,431,430,107,106, 59, 58, -3, -3, -3, -3, -3, -3, -3, -3, -3, 23, 89, 88,437,436,443,442,449,448,836,835}, + {481,480,475,474,469,468, 48, -2, 30, -3, -3, -3, -3, -3, -3, -3, -3, -3, -3, 0, 53, 52,463,462,457,456,451,450,837, -3}, + {483,482,477,476,471,470, 49, -1, -2, -3, -3, -3, -3, -3, -3, -3, -3, -3, -3, -3, -2, -1,465,464,459,458,453,452,839,838}, + {485,484,479,478,473,472, 51, 50, 31, -3, -3, -3, -3, -3, -3, -3, -3, -3, -3, 1, -2, 42,467,466,461,460,455,454,840, -3}, + {487,486,493,492,499,498, 97, 96, 61, 60, -3, -3, -3, -3, -3, -3, -3, -3, -3, 26, 91, 90,505,504,511,510,517,516,842,841}, + {489,488,495,494,501,500, 99, 98, 63, 62, -3, -3, -3, -3, -3, -3, -3, -3, 28, 27, 93, 92,507,506,513,512,519,518,843, -3}, + {491,490,497,496,503,502,101,100, 65, 64, 17, -3, -3, -3, -3, -3, -3, -3, 18, 29, 95, 94,509,508,515,514,521,520,845,844}, + {559,558,553,552,547,546,541,540, 73, 72, 32, -3, -3, -3, -3, -3, -3, 10, 67, 66,115,114,535,534,529,528,523,522,846, -3}, + {561,560,555,554,549,548,543,542, 75, 74, -2, -1, 7, 6, 35, 34, 11, -2, 69, 68,117,116,537,536,531,530,525,524,848,847}, + {563,562,557,556,551,550,545,544, 77, 76, -2, 33, 9, 8, 25, 24, -1, -2, 71, 70,119,118,539,538,533,532,527,526,849, -3}, + {565,564,571,570,577,576,583,582,589,588,595,594,601,600,607,606,613,612,619,618,625,624,631,630,637,636,643,642,851,850}, + {567,566,573,572,579,578,585,584,591,590,597,596,603,602,609,608,615,614,621,620,627,626,633,632,639,638,645,644,852, -3}, + {569,568,575,574,581,580,587,586,593,592,599,598,605,604,611,610,617,616,623,622,629,628,635,634,641,640,647,646,854,853}, + {727,726,721,720,715,714,709,708,703,702,697,696,691,690,685,684,679,678,673,672,667,666,661,660,655,654,649,648,855, -3}, + {729,728,723,722,717,716,711,710,705,704,699,698,693,692,687,686,681,680,675,674,669,668,663,662,657,656,651,650,857,856}, + {731,730,725,724,719,718,713,712,707,706,701,700,695,694,689,688,683,682,677,676,671,670,665,664,659,658,653,652,858, -3}, + {733,732,739,738,745,744,751,750,757,756,763,762,769,768,775,774,781,780,787,786,793,792,799,798,805,804,811,810,860,859}, + {735,734,741,740,747,746,753,752,759,758,765,764,771,770,777,776,783,782,789,788,795,794,801,800,807,806,813,812,861, -3}, + {737,736,743,742,749,748,755,754,761,760,767,766,773,772,779,778,785,784,791,790,797,796,803,802,809,808,815,814,863,862} + }; + + private final BitMatrix bitMatrix; + + /** + * @param bitMatrix {@link BitMatrix} to parse + * @throws FormatException if height is not 33 or width is not 30 + */ + BitMatrixParser(BitMatrix bitMatrix) { + this.bitMatrix = bitMatrix; + } + + byte[] readCodewords() { + byte[] result = new byte[144]; + int height = bitMatrix.getHeight(); + int width = bitMatrix.getWidth(); + for (int y = 0; y < height; y++) { + int[] bitnrRow = BITNR[y]; + for (int x = 0; x < width; x++) { + int bit = bitnrRow[x]; + if (bit >= 0 && bitMatrix.get(x, y)) { + result[bit / 6] |= (byte) (1 << (5 - (bit % 6))); + } + } + } + return result; + } + +} diff --git a/core/src/com/google/zxing/maxicode/decoder/DecodedBitStreamParser.java b/core/src/com/google/zxing/maxicode/decoder/DecodedBitStreamParser.java new file mode 100644 index 000000000..4d6aeca7b --- /dev/null +++ b/core/src/com/google/zxing/maxicode/decoder/DecodedBitStreamParser.java @@ -0,0 +1,192 @@ +/* + * Copyright 2011 ZXing authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.zxing.maxicode.decoder; + +import com.google.zxing.common.DecoderResult; +import java.text.DecimalFormat; + +/** + *

MaxiCodes can encode text or structured information as bits in one of several modes, + * with multiple character sets in one code. This class decodes the bits back into text.

+ * + * @author mike32767 + * @author Manuel Kasten + */ +final class DecodedBitStreamParser { + + private static final char SHIFTA = '\uFFF0'; + private static final char SHIFTB = '\uFFF1'; + private static final char SHIFTC = '\uFFF2'; + private static final char SHIFTD = '\uFFF3'; + private static final char SHIFTE = '\uFFF4'; + private static final char TWOSHIFTA = '\uFFF5'; + private static final char THREESHIFTA = '\uFFF6'; + private static final char LATCHA = '\uFFF7'; + private static final char LATCHB = '\uFFF8'; + private static final char LOCK = '\uFFF9'; + private static final char ECI = '\uFFFA'; + private static final char NS = '\uFFFB'; + private static final char PAD = '\uFFFC'; + private static final char FS = '\u001C'; + private static final char GS = '\u001D'; + private static final char RS = '\u001E'; + private static final DecimalFormat NINE_DIGITS = new DecimalFormat("000000000"); + private static final DecimalFormat THREE_DIGITS = new DecimalFormat("000"); + + private static final String[] SETS = { + "\nABCDEFGHIJKLMNOPQRSTUVWXYZ"+ECI+FS+GS+RS+NS+' '+PAD+"\"#$%&'()*+,-./0123456789:"+SHIFTB+SHIFTC+SHIFTD+SHIFTE+LATCHB, + "`abcdefghijklmnopqrstuvwxyz"+ECI+FS+GS+RS+NS+'{'+PAD+"}~\u007F;<=>?[\\]^_ ,./:@!|"+PAD+TWOSHIFTA+THREESHIFTA+PAD+SHIFTA+SHIFTC+SHIFTD+SHIFTE+LATCHA, + "\u00C0\u00C1\u00C2\u00C3\u00C4\u00C5\u00C6\u00C7\u00C8\u00C9\u00CA\u00CB\u00CC\u00CD\u00CE\u00CF\u00D0\u00D1\u00D2\u00D3\u00D4\u00D5\u00D6\u00D7\u00D8\u00D9\u00DA"+ECI+FS+GS+RS+"\u00DB\u00DC\u00DD\u00DE\u00DF\u00AA\u00AC\u00B1\u00B2\u00B3\u00B5\u00B9\u00BA\u00BC\u00BD\u00BE\u0080\u0081\u0082\u0083\u0084\u0085\u0086\u0087\u0088\u0089"+LATCHA+' '+LOCK+SHIFTD+SHIFTE+LATCHB, + "\u00E0\u00E1\u00E2\u00E3\u00E4\u00E5\u00E6\u00E7\u00E8\u00E9\u00EA\u00EB\u00EC\u00ED\u00EE\u00EF\u00F0\u00F1\u00F2\u00F3\u00F4\u00F5\u00F6\u00F7\u00F8\u00F9\u00FA"+ECI+FS+GS+RS+NS+"\u00FB\u00FC\u00FD\u00FE\u00FF\u00A1\u00A8\u00AB\u00AF\u00B0\u00B4\u00B7\u00B8\u00BB\u00BF\u008A\u008B\u008C\u008D\u008E\u008F\u0090\u0091\u0092\u0093\u0094"+LATCHA+' '+SHIFTC+LOCK+SHIFTE+LATCHB, + "\u0000\u0001\u0002\u0003\u0004\u0005\u0006\u0007\u0008\u0009\n\u000B\u000C\r\u000E\u000F\u0010\u0011\u0012\u0013\u0014\u0015\u0016\u0017\u0018\u0019\u001A"+ECI+PAD+PAD+'\u001B'+NS+FS+GS+RS+"\u001F\u009F\u00A0\u00A2\u00A3\u00A4\u00A5\u00A6\u00A7\u00A9\u00AD\u00AE\u00B6\u0095\u0096\u0097\u0098\u0099\u009A\u009B\u009C\u009D\u009E"+LATCHA+' '+SHIFTC+SHIFTD+LOCK+LATCHB, + "\u0000\u0001\u0002\u0003\u0004\u0005\u0006\u0007\u0008\u0009\n\u000B\u000C\r\u000E\u000F\u0010\u0011\u0012\u0013\u0014\u0015\u0016\u0017\u0018\u0019\u001A\u001B\u001C\u001D\u001E\u001F\u0020\u0021\"\u0023\u0024\u0025\u0026\u0027\u0028\u0029\u002A\u002B\u002C\u002D\u002E\u002F\u0030\u0031\u0032\u0033\u0034\u0035\u0036\u0037\u0038\u0039\u003A\u003B\u003C\u003D\u003E\u003F" + }; + + private DecodedBitStreamParser() { + } + + static DecoderResult decode(byte[] bytes, int mode) { + StringBuffer result = new StringBuffer(144); + switch (mode) { + case 2: + case 3: + String postcode; + if (mode == 2) { + int pc = getPostCode2(bytes); + DecimalFormat df = new DecimalFormat("0000000000".substring(0, getPostCode2Length(bytes))); + postcode = df.format(pc); + } else { + postcode = getPostCode3(bytes); + } + String country = THREE_DIGITS.format(getCountry(bytes)); + String service = THREE_DIGITS.format(getServiceClass(bytes)); + result.append(getMessage(bytes, 10, 84)); + if (result.toString().startsWith("[)>"+RS+"01"+GS)) { + result.insert(9, postcode + GS + country + GS + service + GS); + } else { + result.insert(0, postcode + GS + country + GS + service + GS); + } + break; + case 4: + result.append(getMessage(bytes, 1, 93)); + break; + case 5: + result.append(getMessage(bytes, 1, 77)); + break; + } + return new DecoderResult(bytes, result.toString(), null, String.valueOf(mode)); + } + + private static int getBit(int bit, byte[] bytes) { + bit--; + return (bytes[bit / 6] & (1 << (5 - (bit % 6)))) == 0 ? 0 : 1; + } + + private static int getInt(byte[] bytes, byte[] x) { + int val = 0; + for (int i = 0; i < x.length; i++) { + val += getBit(x[i], bytes) << (x.length - i - 1); + } + return val; + } + + private static int getCountry(byte[] bytes) { + return getInt(bytes, new byte[] {53, 54, 43, 44, 45, 46, 47, 48, 37, 38}); + } + + private static int getServiceClass(byte[] bytes) { + return getInt(bytes, new byte[] {55, 56, 57, 58, 59, 60, 49, 50, 51, 52}); + } + + private static int getPostCode2Length(byte[] bytes) { + return getInt(bytes, new byte[] {39, 40, 41, 42, 31, 32}); + } + + private static int getPostCode2(byte[] bytes) { + return getInt(bytes, new byte[] {33, 34, 35, 36, 25, 26, 27, 28, 29, 30, 19, + 20, 21, 22, 23, 24, 13, 14, 15, 16, 17, 18, 7, 8, 9, 10, 11, 12, 1, 2}); + } + + private static String getPostCode3(byte[] bytes) { + return String.valueOf( + new char[] { + SETS[0].charAt(getInt(bytes, new byte[] {39, 40, 41, 42, 31, 32})), + SETS[0].charAt(getInt(bytes, new byte[] {33, 34, 35, 36, 25, 26})), + SETS[0].charAt(getInt(bytes, new byte[] {27, 28, 29, 30, 19, 20})), + SETS[0].charAt(getInt(bytes, new byte[] {21, 22, 23, 24, 13, 14})), + SETS[0].charAt(getInt(bytes, new byte[] {15, 16, 17, 18, 7, 8})), + SETS[0].charAt(getInt(bytes, new byte[] { 9, 10, 11, 12, 1, 2})), + } + ); + } + + private static String getMessage(byte[] bytes, int start, int len) { + StringBuffer sb = new StringBuffer(); + int shift = -1; + int set = 0; + int lastset = 0; + for (int i = start; i < start + len; i++) { + char c = SETS[set].charAt(bytes[i]); + switch (c) { + case LATCHA: + set = 0; + shift = -1; + break; + case LATCHB: + set = 1; + shift = -1; + break; + case SHIFTA: + case SHIFTB: + case SHIFTC: + case SHIFTD: + case SHIFTE: + lastset = set; + set = c - SHIFTA; + shift = 1; + break; + case TWOSHIFTA: + lastset = set; + set = 0; + shift = 2; + break; + case THREESHIFTA: + lastset = set; + set = 0; + shift = 3; + break; + case NS: + int nsval = (bytes[++i] << 24) + (bytes[++i] << 18) + (bytes[++i] << 12) + (bytes[++i] << 6) + bytes[++i]; + sb.append(NINE_DIGITS.format(nsval)); + break; + case LOCK: + shift = -1; + break; + default: + sb.append(c); + } + if (shift-- == 0) { + set = lastset; + } + } + while (sb.length() > 0 && sb.charAt(sb.length() - 1) == PAD) { + sb.setLength(sb.length() - 1); + } + return sb.toString(); + } + +} diff --git a/core/src/com/google/zxing/maxicode/decoder/Decoder.java b/core/src/com/google/zxing/maxicode/decoder/Decoder.java new file mode 100644 index 000000000..83f022ce7 --- /dev/null +++ b/core/src/com/google/zxing/maxicode/decoder/Decoder.java @@ -0,0 +1,116 @@ +/* + * Copyright 2011 ZXing authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.zxing.maxicode.decoder; + +import com.google.zxing.ChecksumException; +import com.google.zxing.FormatException; +import com.google.zxing.common.BitMatrix; +import com.google.zxing.common.DecoderResult; +import com.google.zxing.common.reedsolomon.GenericGF; +import com.google.zxing.common.reedsolomon.ReedSolomonDecoder; +import com.google.zxing.common.reedsolomon.ReedSolomonException; + +import java.util.Hashtable; + +/** + *

The main class which implements MaxiCode decoding -- as opposed to locating and extracting + * the MaxiCode from an image.

+ * + * @author Manuel Kasten + */ +public final class Decoder { + + private static final int ALL = 0; + private static final int EVEN = 1; + private static final int ODD = 2; + + private final ReedSolomonDecoder rsDecoder; + + public Decoder() { + rsDecoder = new ReedSolomonDecoder(GenericGF.MAXICODE_FIELD_64); + } + + public DecoderResult decode(BitMatrix bits) throws ChecksumException, FormatException { + return decode(bits, null); + } + + public DecoderResult decode(BitMatrix bits, Hashtable hints) throws FormatException, ChecksumException { + BitMatrixParser parser = new BitMatrixParser(bits); + byte[] codewords = parser.readCodewords(); + + correctErrors(codewords, 0, 10, 10, ALL); + int mode = codewords[0] & 0x0F; + byte[] datawords; + switch (mode) { + case 2: + case 3: + case 4: + correctErrors(codewords, 20, 84, 40, EVEN); + correctErrors(codewords, 20, 84, 40, ODD); + datawords = new byte[94]; + break; + case 5: + correctErrors(codewords, 20, 68, 56, EVEN); + correctErrors(codewords, 20, 68, 56, ODD); + datawords = new byte[78]; + break; + default: + throw FormatException.getFormatInstance(); + } + + for (int i = 0; i < 10; i++) { + datawords[i] = codewords[i]; + } + for (int i = 20; i < datawords.length + 10; i++) { + datawords[i - 10] = codewords[i]; + } + + return DecodedBitStreamParser.decode(datawords, mode); + } + + private void correctErrors(byte[] codewordBytes, + int start, + int dataCodewords, + int ecCodewords, + int mode) throws ChecksumException { + int codewords = dataCodewords + ecCodewords; + + // in EVEN or ODD mode only half the codewords + int divisor = mode == ALL ? 1 : 2; + + // First read into an array of ints + int[] codewordsInts = new int[codewords / divisor]; + for (int i = 0; i < codewords; i++) { + if ((mode == ALL) || (i % 2 == (mode - 1))) { + codewordsInts[i / divisor] = codewordBytes[i + start] & 0xFF; + } + } + try { + rsDecoder.decode(codewordsInts, ecCodewords / divisor); + } catch (ReedSolomonException rse) { + throw ChecksumException.getChecksumInstance(); + } + // Copy back into array of bytes -- only need to worry about the bytes that were data + // We don't care about errors in the error-correction codewords + for (int i = 0; i < dataCodewords; i++) { + if ((mode == ALL) || (i % 2 == (mode - 1))) { + codewordBytes[i + start] = (byte) codewordsInts[i / divisor]; + } + } + } + +} diff --git a/javase/src/com/google/zxing/client/j2se/CommandLineRunner.java b/javase/src/com/google/zxing/client/j2se/CommandLineRunner.java index 4ff266a9f..aed6e5aaf 100644 --- a/javase/src/com/google/zxing/client/j2se/CommandLineRunner.java +++ b/javase/src/com/google/zxing/client/j2se/CommandLineRunner.java @@ -163,6 +163,7 @@ public final class CommandLineRunner { vector.addElement(BarcodeFormat.AZTEC); vector.addElement(BarcodeFormat.PDF_417); vector.addElement(BarcodeFormat.CODABAR); + vector.addElement(BarcodeFormat.MAXICODE); } hints.put(DecodeHintType.POSSIBLE_FORMATS, vector); if (config.isTryHarder()) {