Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

zxing ean13 barcode encoding with 'lead', 'separator' and 'trailer'

I'm using zxing to generate an EAN13 barcode. Everything works fine when I use this code:

new EAN13Writer().encode(eanValue, BarcodeFormat.EAN_13, requestedWidth, requestedHeight);

Result:

Barcode generated by zxing

Now, the customer wants to have a lead, separater and a trailer like this one:

Barcode format as demanded

(Image source)

As far as I understand the code of zxing (see here) I'm using above can't make those lines longer than others. But I've found out that there are two encode-methods in the EAN13Writer and it seems like, the second encode-function does exactly what I want to do. But the problem is that this function doesn't return a bitmatrix but a boolean array. (Edit: Nope the function does not achieve what I want to do)

Can anybody tell me how I can achieve the second barcode style?

like image 934
joschplusa Avatar asked Jun 25 '14 12:06

joschplusa


2 Answers

As I couldn't find a solution in the zxing code I wrote my own EAN13Writer. I had to copy paste some methods and constants as they were package-private in the zxing package.

Basically it just stores the position of the lines which shall be longer in a member variable. When the BitMatrix is rendered every line which should not be longer will be shortened by 5%. If anybody has the same problem this code might help a little bit.

/**
 * This is a custom implementation as the customer wants a modified barcode with longer and shorter lines for start, middle and end.
 * Most code is based on Code from the OneDimensionalCodeWriter, EAN13Writer and UPCEANReader but
 * had to be copied as the methods were package private
 *
 */
public class CustomEAN13Writer extends OneDimensionalCodeWriter {

// For an EAN-13 barcode, the first digit is represented by the parities used
// to encode the next six digits, according to the table below. For example,
// if the barcode is 5 123456 789012 then the value of the first digit is
// signified by using odd for '1', even for '2', even for '3', odd for '4',
// odd for '5', and even for '6'. See http://en.wikipedia.org/wiki/EAN-13
//
//                Parity of next 6 digits
//    Digit   0     1     2     3     4     5
//       0    Odd   Odd   Odd   Odd   Odd   Odd
//       1    Odd   Odd   Even  Odd   Even  Even
//       2    Odd   Odd   Even  Even  Odd   Even
//       3    Odd   Odd   Even  Even  Even  Odd
//       4    Odd   Even  Odd   Odd   Even  Even
//       5    Odd   Even  Even  Odd   Odd   Even
//       6    Odd   Even  Even  Even  Odd   Odd
//       7    Odd   Even  Odd   Even  Odd   Even
//       8    Odd   Even  Odd   Even  Even  Odd
//       9    Odd   Even  Even  Odd   Even  Odd
//
// Note that the encoding for '0' uses the same parity as a UPC barcode. Hence
// a UPC barcode can be converted to an EAN-13 barcode by prepending a 0.
//
// The encoding is represented by the following array, which is a bit pattern
// using Odd = 0 and Even = 1. For example, 5 is represented by:
//
//              Odd Even Even Odd Odd Even
// in binary:
//                0    1    1   0   0    1   == 0x19
//
static final int[] FIRST_DIGIT_ENCODINGS = {
        0x00, 0x0B, 0x0D, 0xE, 0x13, 0x19, 0x1C, 0x15, 0x16, 0x1A
};

/**
 * Start/end guard pattern.
 */
static final int[] START_END_PATTERN = {1, 1, 1,};

/**
 * As above but also including the "even", or "G" patterns used to encode UPC/EAN digits.
 */
static final int[][] L_AND_G_PATTERNS;

/**
 * "Odd", or "L" patterns used to encode UPC/EAN digits.
 */
static final int[][] L_PATTERNS = {
        {3, 2, 1, 1}, // 0
        {2, 2, 2, 1}, // 1
        {2, 1, 2, 2}, // 2
        {1, 4, 1, 1}, // 3
        {1, 1, 3, 2}, // 4
        {1, 2, 3, 1}, // 5
        {1, 1, 1, 4}, // 6
        {1, 3, 1, 2}, // 7
        {1, 2, 1, 3}, // 8
        {3, 1, 1, 2}  // 9
};

/**
 * Pattern marking the middle of a UPC/EAN pattern, separating the two halves.
 */
static final int[] MIDDLE_PATTERN = {1, 1, 1, 1, 1};

static {
    L_AND_G_PATTERNS = new int[20][];
    System.arraycopy(L_PATTERNS, 0, L_AND_G_PATTERNS, 0, 10);
    for (int i = 10; i < 20; i++) {
        int[] widths = L_PATTERNS[i - 10];
        int[] reversedWidths = new int[widths.length];
        for (int j = 0; j < widths.length; j++) {
            reversedWidths[j] = widths[widths.length - j - 1];
        }
        L_AND_G_PATTERNS[i] = reversedWidths;
    }
}

private static final int CODE_WIDTH = 3 + // start guard
        (7 * 6) + // left bars
        5 + // middle guard
        (7 * 6) + // right bars
        3; // end guard

private static final int DEFAULT_MARGIN = 10;

//This list should contain all positions of lines which should be longer than the other lines
private List<Integer> mLongLinePositions;

public CustomEAN13Writer() {
    mLongLinePositions = new ArrayList<>();
}

/**
 * @param target     encode black/white pattern into this array
 * @param pos        position to start encoding at in {@code target}
 * @param pattern    lengths of black/white runs to encode
 * @param startColor starting color - false for white, true for black
 * @return the number of elements added to target.
 */
public int appendPatternAndConsiderLongLinePosition(boolean[] target, int pos, int[] pattern,
        boolean startColor) {
    boolean color = startColor;
    int numAdded = 0;
    for (int len : pattern) {
        for (int j = 0; j < len; j++) {
            //If the pattern is the start-, middle- or end-pattern save the position for rendering later
            if (pattern.equals(START_END_PATTERN) || pattern.equals(MIDDLE_PATTERN) || pattern
                    .equals(START_END_PATTERN)) {
                mLongLinePositions.add(pos);
            }
            target[pos++] = color;
        }
        numAdded += len;
        color = !color; // flip color after each segment
    }
    return numAdded;
}

@Override
public boolean[] encode(final String contents) {
    if (contents.length() != 13) {
        throw new IllegalArgumentException(
                "Requested contents should be 13 digits long, but got " + contents.length());
    }
    try {
        if (!checkStandardUPCEANChecksum(contents)) {
            throw new IllegalArgumentException("Contents do not pass checksum");
        }
    } catch (FormatException ignored) {
        throw new IllegalArgumentException("Illegal contents");
    }

    int firstDigit = Integer.parseInt(contents.substring(0, 1));
    int parities = FIRST_DIGIT_ENCODINGS[firstDigit];
    boolean[] result = new boolean[CODE_WIDTH];
    int pos = 0;

    pos += appendPatternAndConsiderLongLinePosition(result, pos, START_END_PATTERN, true);

    // See {@link #EAN13Reader} for a description of how the first digit & left bars are encoded
    for (int i = 1; i <= 6; i++) {
        int digit = Integer.parseInt(contents.substring(i, i + 1));
        if ((parities >> (6 - i) & 1) == 1) {
            digit += 10;
        }
        pos += appendPatternAndConsiderLongLinePosition(result, pos, L_AND_G_PATTERNS[digit],
                false);
    }

    pos += appendPatternAndConsiderLongLinePosition(result, pos, MIDDLE_PATTERN, false);

    for (int i = 7; i <= 12; i++) {
        int digit = Integer.parseInt(contents.substring(i, i + 1));
        pos += appendPatternAndConsiderLongLinePosition(result, pos, L_PATTERNS[digit], true);
    }
    appendPatternAndConsiderLongLinePosition(result, pos, START_END_PATTERN, true);

    return result;
}

public BitMatrix encodeAndRender(String contents, int width, int height) {
    boolean[] code = encode(contents);

    int inputWidth = code.length;
    // Add quiet zone on both sides.
    int fullWidth = inputWidth + DEFAULT_MARGIN;
    int outputWidth = Math.max(width, fullWidth);
    int outputHeight = Math.max(1, height);

    int multiple = outputWidth / fullWidth;
    int leftPadding = (outputWidth - (inputWidth * multiple)) / 2;

    BitMatrix output = new BitMatrix(outputWidth, outputHeight);
    for (int inputX = 0, outputX = leftPadding; inputX < inputWidth;
            inputX++, outputX += multiple) {
        if (code[inputX]) {
            int barcodeHeight = outputHeight;
            //if the position isn't in the list for long lines we have to shorten the line by 5%
            if (!mLongLinePositions.contains(inputX)) {
                barcodeHeight = (int) ((float) outputHeight * 0.95f);
            }
            output.setRegion(outputX, 0, multiple, barcodeHeight);

        }
    }
    return output;
}

/**
 * Computes the UPC/EAN checksum on a string of digits, and reports whether the checksum is
 * correct or not.
 *
 * @param s string of digits to check
 * @return true iff string of digits passes the UPC/EAN checksum algorithm
 * @throws FormatException if the string does not contain only digits
 */
static boolean checkStandardUPCEANChecksum(CharSequence s) throws FormatException {
    int length = s.length();
    if (length == 0) {
        return false;
    }

    int sum = 0;
    for (int i = length - 2; i >= 0; i -= 2) {
        int digit = (int) s.charAt(i) - (int) '0';
        if (digit < 0 || digit > 9) {
            throw FormatException.getFormatInstance();
        }
        sum += digit;
    }
    sum *= 3;
    for (int i = length - 1; i >= 0; i -= 2) {
        int digit = (int) s.charAt(i) - (int) '0';
        if (digit < 0 || digit > 9) {
            throw FormatException.getFormatInstance();
        }
        sum += digit;
    }
    return sum % 10 == 0;
}
}
like image 131
joschplusa Avatar answered Nov 14 '22 22:11

joschplusa


The second encode method returns an array of boolean that is a row of pixels. A true value is black, a false value is white. That array is extrapolated into your first image by: OneDimensionalCodeWriter::renderResult().

That method makes no provision for the human readable image. The separators are there in your first image, but they are not extended nor, obviously, is the human readable rendering of the number that is encoded.

My best advice is to fork the github repo and modify the renderResult method to do what you want. Obviously you need to provide something to your client, so I'd just do something quick in it's own branch. Ultimately the best thing is to do it correctly and offer the changes back to the zxing team.

I would assume that there should be a hint (EncodeHintType) added to allow a choice to make the barcode human readable. To save yourself time, I would post a message to their dev mailing list and see what they feel would be the best way to add this feature.

like image 45
Erik Nedwidek Avatar answered Nov 14 '22 22:11

Erik Nedwidek