使用'lead'、'separator'和'trailer'对EAN13条形码进行ZXing编码

8
我将使用zxing来生成EAN13条形码。当我使用下面这段代码时,一切都运行良好:
new EAN13Writer().encode(eanValue, BarcodeFormat.EAN_13, requestedWidth, requestedHeight);

结果:

zxing生成的条形码

现在,客户想要像这样的引导符分隔符结束符

所需的条形码格式

(图片来源)

据我所知,上面使用的zxing代码(请参见此处)不能使这些线比其他线长。但是我发现EAN13Writer中有两个encode方法,似乎第二个encode函数正好可以实现我想做的事情。但问题在于,该函数不返回位矩阵,而是一个布尔数组。(编辑:不,该函数无法实现我想做的事情)

有人能告诉我如何实现第二种条形码样式吗?


这两者有什么区别?防护模式都存在于条形码中。 - Sean Owen
从技术上讲,没有任何区别。客户只是希望条形码看起来不同而已。条形码本身没问题! - joschplusa
是的,它只会生成“原始”的条形码。装饰取决于调用者。 - Sean Owen
1
@SeanOwen 那真遗憾,那将是一个不错的功能! - poussma
请随意编写并发布一个补丁! - Sean Owen
@ZNK-M请看下面我的答案。我写了一个简单的方法,完全实现了我想要达到的目的。 - joschplusa
2个回答

9

由于在zxing代码中找不到解决方案,我编写了自己的EAN13Writer。由于它们在zxing包中是package-private,所以我不得不复制粘贴一些方法和常量。

基本上,它只是将应该更长的线的位置存储在成员变量中。当BitMatrix被渲染时,每条不应该更长的线都会缩短5%。如果有人有同样的问题,这段代码可能会有帮助。

/**
 * 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;
}
}

1
第二种编码方法返回一个布尔数组,该数组是像素行。true值为黑色,false值为白色。该数组通过OneDimensionalCodeWriter :: renderResult()扩展到您的第一张图像中。
该方法未提供人类可读图像。分隔符在第一张图像中存在,但它们没有被扩展,显然也没有编码数字的人类可读渲染。
我最好的建议是fork github存储库并修改renderResult方法以实现您想要的功能。显然,您需要向客户提供一些东西,因此我会在其自己的分支中快速完成一些工作。最终最好的做法是正确地执行它并向zxing团队提供更改。
我认为应该添加提示(EncodeHintType),以允许选择使条形码人类可读。为节省时间,我会在他们的开发邮件列表上发布消息,并查看他们觉得添加此功能的最佳方法是什么。

1
谢谢,这正是我所做的。我编写了自己的EAN13Writer来使这些行更长。稍后我会将其发布为答案。 - joschplusa
1
好的,我一定会稍后查看它。如果您不想跟进将其纳入官方代码中,我会考虑将其合并到我的zxing分支中。我一定会以您为更新基础进行致谢。 ;) - Erik Nedwidek

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接