package jjil.algorithm; import java.util.HashMap; public class Ean13Barcode1D { /** * Description of the barcode layout. */ public static final int LeftDigits = 6; // number of digits in the left half of the barcode public static final int RightDigits = 6; // number of digits in the right half of the barcode public static final int LeftWidth = 3; // number of elementary bars in the left-side pattern public static final int RightWidth = 3; // number of elementary bars in the right-side pattern public static final int MidWidth = 5; // number of elementary bars in the middle pattern public static final int DigitWidth = 7; // number of elementary bars in a digit // the offset to the middle pattern, in elementary bars public static final int MidOffset = LeftWidth + LeftDigits*DigitWidth; // the offset to the right-side pattern, in elementary bars public static final int RightOffset = LeftWidth + (LeftDigits+RightDigits)*DigitWidth + MidWidth; // the total number of elementary bars in a UPC barcode public static final int TotalWidth = LeftWidth + DigitWidth * LeftDigits + MidWidth + DigitWidth * RightDigits + RightWidth; // Main procedure, for unit test. // input is a string of digits representing a scanned barcode pattern // each digit is an image brightness value 0 = black 9 = white // the string can be of any length so long as it is at least 95 digits // (i.e., has at least 1 digit / barcode stripe) public static void main(String[] args) { Ean13Barcode1D eb = new Ean13Barcode1D(); int[] nValues = new int[args[0].length()]; int nMid = 0; for (int i=0; i<args[0].length(); i++) { if (!Character.isDigit(args[0].charAt(i))) { System.err.println("argument must be a numeric barcode string"); return; } nValues[i] = Character.digit(args[0].charAt(i), 10); nMid += nValues[i]; } nMid /= nValues.length; System.out.println(eb.decodeBarcode(nValues, 0, nValues.length, nMid)); } public Ean13Barcode1D() { initDigitCodes(); } private void initDigitCodes() { /* The odd parity left (character set A) barcodes for the ten digits are: 0 = 3-2-1-1 = 0001101 = 0x0d 1 = 2-2-2-1 = 0011001 = 0x19 2 = 2-1-2-2 = 0010011 = 0x13 3 = 1-4-1-1 = 0111101 = 0x3d 4 = 1-1-3-2 = 0100011 = 0x23 5 = 1-2-3-1 = 0110001 = 0x31 6 = 1-1-1-4 = 0101111 = 0x2f 7 = 1-3-1-2 = 0111011 = 0x3b 8 = 1-2-1-3 = 0110111 = 0x37 9 = 3-1-1-2 = 0001011 = 0x0b */ mhOddLeft = new HashMap<Integer, Character>(); mhOddLeft.put(0x0d, '0'); mhOddLeft.put(0x19, '1'); mhOddLeft.put(0x13, '2'); mhOddLeft.put(0x23, '4'); mhOddLeft.put(0x31, '5'); mhOddLeft.put(0x2f, '6'); mhOddLeft.put(0x3b, '7'); mhOddLeft.put(0x37, '8'); mhOddLeft.put(0x0b, '9'); // The R barcodes for the digits are the 1-complements // of the L odd barcodes. But we encode the R digits by // encoding a white bar as 1 and a black bar as 0 // so we automatically get the 1-complement without // while using the same table /* The even parity left (character set B) barcodes for the ten digits are: 0 = 1-1-2-3 = 0100111 = 0x27 1 = 1-2-2-2 = 0110011 = 0x33 2 = 2-2-1-2 = 0011011 = 0x1b 3 = 1-1-4-1 = 0100001 = 0x21 4 = 2-3-1-1 = 0011101 = 0x1d 5 = 1-3-2-1 = 0111001 = 0x39 6 = 4-1-1-1 = 0000101 = 0x05 7 = 2-1-3-1 = 0010001 = 0x11 8 = 3-1-2-1 = 0001001 = 0x09 9 = 2-1-1-3 = 0010111 = 0x17 */ mhEvenLeft = new HashMap<Integer, Character>(); mhEvenLeft.put(0x27, '0'); mhEvenLeft.put(0x33, '1'); mhEvenLeft.put(0x1b, '2'); mhEvenLeft.put(0x21, '3'); mhEvenLeft.put(0x1d, '4'); mhEvenLeft.put(0x39, '5'); mhEvenLeft.put(0x05, '6'); mhEvenLeft.put(0x11, '7'); mhEvenLeft.put(0x09, '8'); mhEvenLeft.put(0x17, '9'); /** * The first digit is implied based on * the parity of the other digits in the * left half. Below 1 = odd parity 0 = even * 0 111111 = 0x3f * 1 110100 = 0x34 * 2 110010 = 0x32 * 3 110001 = 0x31 * 4 101100 = 0x2c * 5 100110 = 0x26 * 6 100011 = 0x23 * 7 101010 = 0x2a * 8 101001 = 0x29 * 9 100101 = 0x25 */ mhFirstDigit = new HashMap<Integer, Character>(); mhFirstDigit.put(0x3f, '0'); mhFirstDigit.put(0x34, '1'); mhFirstDigit.put(0x32, '2'); mhFirstDigit.put(0x31, '3'); mhFirstDigit.put(0x2c, '4'); mhFirstDigit.put(0x26, '5'); mhFirstDigit.put(0x23, '6'); mhFirstDigit.put(0x2a, '7'); mhFirstDigit.put(0x29, '8'); mhFirstDigit.put(0x25, '9'); } /** * Decode an EAN-13 barcode from the image values in nValues, which should be * thresholded at the value 'nMid', starting at position nStart and ending * at position nEnd * @param nValues -- the image values * @param nStart -- column to start decoding the barcode * @param nEnd -- column to end decoding the barcode * @param nMid -- the threshold * @return the 13-digit decoded barcode if one was found, or null if not */ public String decodeBarcode(int[] nValues, int nStart, int nEnd, int nMid) { mbSuccess = false; // the image has to be at least the width of a barcode if (nEnd - nStart < TotalWidth) { return null; } // let's start by turning the image pixels into a bit pattern byte[] bStripes = new byte[TotalWidth]; int nInterpSum = 0; int nPixVal = 0, nPixCount = 0; int j = 0; // compress the image values into an array of width TotalWidth // using a Bresenham-like interpolation technique for (int i = nStart; i < nEnd; i++) { nPixVal += nValues[i]; nPixCount ++; nInterpSum += TotalWidth; if (nInterpSum >= nEnd - nStart) { bStripes[j++] = (byte) ((nPixVal > nPixCount * nMid) ? 1 : 0); nInterpSum -= nEnd - nStart; nPixVal = 0; nPixCount = 0; } } // skip past the marker on the left int nCurr = LeftWidth; StringBuilder sbBarcode = new StringBuilder(); int nLeftParity = 0; // decode each digit, detecting the parity of each for (int nDigit = 0; nDigit < LeftDigits; nDigit++) { int nSum = 0; // build an index into digitCodes for this pattern for (int l = 0; l < DigitWidth; l++) { nSum = nSum * 2 + bStripes[nCurr++]; } if (nDigit == 0) { // in EAN-13 the first digit always has odd parity if (mhOddLeft.containsKey(nSum)) { sbBarcode.append(mhOddLeft.get(nSum)); nLeftParity = 1; } else { // the first digit didn't match any of the codes return sbBarcode.toString(); } } else { // determine the parity of the digit if (mhOddLeft.containsKey(nSum)) { sbBarcode.append(mhOddLeft.get(nSum)); nLeftParity = (nLeftParity * 2) + 1; } else if (mhEvenLeft.containsKey(nSum)) { sbBarcode.append(mhEvenLeft.get(nSum)); nLeftParity = nLeftParity * 2; } else { return sbBarcode.toString(); } } } // check parity and add prefix character if (mhFirstDigit.containsKey(nLeftParity)) { sbBarcode.insert(0, mhFirstDigit.get(nLeftParity)); } else { return sbBarcode.toString(); } // now do the right side digits nCurr += MidWidth; for (int nDigit = 0; nDigit < RightDigits; nDigit++) { int nSum = 0; // build an index into digitCodes for this pattern for (int l = 0; l < DigitWidth; l++) { nSum = nSum * 2 + (1 - bStripes[nCurr++]); } if (mhOddLeft.containsKey(nSum)) { sbBarcode.append(mhOddLeft.get(nSum)); } else { // the first digit didn't match any of the codes return sbBarcode.toString(); } } String szBarcode = sbBarcode.toString(); mbSuccess = verifyCheckDigit(szBarcode); return szBarcode; } /** * Verifies the check digit in a decoded barcode string. Returns true * if the check digit passes the verify test. * * @param digits the barcode to be verified. * @return true iff the barcode passes the check digit test. */ private static boolean verifyCheckDigit(String digits) { // compute check digit // add odd digits int nOddSum = 0; for (int i=1; i<digits.length()-1; i+=2) { nOddSum += Character.digit(digits.charAt(i), 10); } // add even digits int nEvenSum = 0; for (int i=0;i<digits.length()-1; i+=2) { nEvenSum += Character.digit(digits.charAt(i), 10); } // compute even digit sum * 3 + odd digit sum; int nTotal = nOddSum*3 + nEvenSum; // check digit is this sum subtracted from the next higher multiple of 10 int checkDigit = (nTotal/10 + 1) * 10 - nTotal; return Character.digit( digits.charAt(digits.length()-1), 10) == checkDigit; } public boolean wasSuccessful() { return mbSuccess; } /** * Set to true when barcode passes all tests */ boolean mbSuccess; /** * Hashmaps for the various digit patterns */ HashMap<Integer, Character> mhOddLeft, mhEvenLeft, mhFirstDigit; }