PDF417 使用 Swift 解码并生成相同的条形码

PDF417 decode and generate the same barcode using Swift

我有以下PDF417条码的例子:

可以用zxing

等在线工具解码

结果如下:5wwwwwxwww0app5p3pewi0edpeapifxe0ixiwwdfxxi0xf5e�¼ô���������¬‚C`Ìe%�æ‹�ÀsõbÿG)=‡x‚�qÀ1ß–[FzùŽûVû�É�üæ±RNI�Y[.H»Eàó¼åñüì²�tØ¿ªWp…Ã�{�Õ*

online-qrcode-generator

as 5wwwwwxwww0app5p3pewi0edpeapifxe0ixiwwdfxxi0xf5e~|~~~~~~~~~~d~C`~e%~~~~;To~B~{~dj9v~~Z[Xm~~"HP3~~LH~~~O~"S~~,~~~~~~~k1~~~u~Iw}SQ~fqX4~mbc_(我不知道用什么编码来编码这个)

包含条形码的编码密钥的第一部分始终是已知的,它是 5wwwwwxwww0app5p3pewi0edpeapifxe0ixiwwdfxxi0xf5e

它的第二部分可以从 base64string 中解码出来,它总是包含 88 个字节。在我的例子中是:

Frz0DAAAAAAAAAAArIJDYMxlJQDmiwHAc/Vi/0cpPYd4ghlxwDHflltGevmO+1b7GckT/OZ/sVJOSRpZWy5Iu0Xg87zl8fzssg502L+qV3CFwxZ/ewjVKg==

我在 iOS 设备上使用 Swift 通过像这样解码提供的 base64 字符串来生成此 PDF417 条形码:

let base64Str = "Frz0DAAAAAAAAAAArIJDYMxlJQDmiwHAc/Vi/0cpPYd4ghlxwDHflltGevmO+1b7GckT/OZ/sVJOSRpZWy5Iu0Xg87zl8fzssg502L+qV3CFwxZ/ewjVKg=="
let knownKey = "5wwwwwxwww0app5p3pewi0edpeapifxe0ixiwwdfxxi0xf5e"
let decodedData = Data(base64Encoded: base64Str.replacingOccurrences(of: "-", with: "+")
                                        .replacingOccurrences(of: "_", with: "/"))

var codeData=knownKey.data(using: String.Encoding.ascii)

codeData?.append(decodedData)
let image = generatePDF417Barcode(from: codeData!)
let imageView = UIImageView(image: image!)

//the function to generate PDF417 UIMAGE from parsed Data
func generatePDF417Barcode(from codeData: Data) -> UIImage? {

        if let filter = CIFilter(name: "CIPDF417BarcodeGenerator") {
            filter.setValue(codeData, forKey: "inputMessage")
            let transform = CGAffineTransform(scaleX: 3, y: 3)

            if let output = filter.outputImage?.transformed(by: transform) {
                return UIImage(ciImage: output)
            }
        }

        return nil
    }

但我总是生成错误的条形码。肉眼可见

请帮我更正代码以获得与第一个条形码图像相同的结果。

我还有另一个条形码的例子:

密钥的第一部分是相同的,但它的第二部分被称为 int8 字节数组,我也不知道如何从中正确生成 PDF417 条码(带有前置密钥)。

以下是我的尝试:

let knownKey = "5wwwwwxwww0app5p3pewi0edpeapifxe0ixiwwdfxxi0xf5e"
let secretArray: [Int8] = [22, 124, 24, 12, 0, 0, 0, 0, 0, 0, 0, 0, 100, 127, 67, 96, -52, 101, 37, 0, -85, -123, 1, -64, 111, -28, 66, -27, 123, -25, 100, 106, 57, 118, -4, 16, 90, 91, 88, 109, -105, 126, 34, 72, 80, 51, -116, 28, 76, 72, -37, -24, -93, 79, -115, 34, 83, 18, -61, 44, -12, -13, -8, -59, -107, -9, -128, 107, 49, -50, 126, 13, -59, 50, -24, -43, 127, 81, -85, 102, 113, 88, 52, -60, 109, 98, 99, 95] 
let secretUInt8 = secretArray.map { UInt8(bitPattern: [=12=]) }
let secretData = Data(secretUInt8)


let keyArray: [UInt8] = Array(knownKey.utf8)
var keyData = Data(keyArray)

keyData.append(secretData)

let image = generatePDF417Barcode(from: keyData!)
let imageView = UIImageView(image: image!)

CIPDF417BarcodeGenerator 除了 inputMessage 之外还有一些输入参数可以影响生成的条形码的外观 - 请参阅 documentation。仅当您知道所有这些参数(最重要的是 inputCorrectionLevel 对于两个生成器都相等时,两个代码的视觉 inspection/comparison 才有意义。

因此,与其进行视觉比较,不如尝试使用众多扫描仪应用程序之一解码条形码,然后比较解码后的字节。

对于你的第二个例子,试试这个:

// ...

var keyData = knownKey.data(using: .isoLatin1)!
keyData.append(secretData)

let image = generatePDF417Barcode(from: keyData)

这里发生了很多事情。 Gereon 是正确的,有很多参数。选择不同的参数可能会导致完全不同的条码解码相同。您当前的条形码是“正确的”(尽管由于 Apple 错误而有点混乱)。就是不一样。

我将从如何使您的数据与您拥有的条形码匹配的简短回答开始。然后我将介绍您可能实际应该做的事情,最后我将详细说明原因。

首先,这是您要查找的代码(但可能不是您想要的代码,除非您 匹配此条形码):

filter.setValue(codeData, forKey: "inputMessage")
filter.setValue(3, forKey: "inputCompactionMode")  // This is good (and the big difference)
filter.setValue(5, forKey: "inputDataColumns")     // This is fine, but probably unneeded
filter.setValue(0, forKey: "inputCorrectionLevel") // This is bad

PDF 417 定义了几种“压缩模式”,使其能够将真正令人印象深刻的信息量压缩到非常小的 space 中,同时仍然提供出色的错误检测和纠正,并处理大量真实世界的扫描担忧。默认压缩模式仅支持拉丁文本和基本标点符号。 (如果你只使用大写拉丁字母和 space,它会压缩得更多。)你的字符串的第一部分可以用文本压缩存储,但其余部分不能,所以它必须切换到字节压缩。

Core Image 实际上在默认情况下非常糟糕地执行此切换(我打开 FB9032718 进行跟踪)。它不是在文本中编码然后切换到字节,或者只是以字节进行所有操作,而是不必要地一遍又一遍地切换到字节。

你没办法配置多种compaction方法,你可以设置为byte,也就是3的值。这也是您的消息来源的做法。

第二个区别是数据列的数量,它决定了输出的宽度。您的来源使用 5,但 Core Image 根据其默认规则(未完整记录)选择 6。

最后,您的源将纠错级别设置为0,不建议这样做。对于这种大小的消息,推荐的最低纠错级别是3,这是Core Image默认选择的。

如果你只是想要一个好的条形码,而不必匹配这个输入,我的建议是将 inputCompactionMode 设置为 3,其余的保持默认。如果您想要不同的宽高比,我会使用 inputPreferredAspectRatio 而不是直接修改数据列的数量。


您现在可能想停止阅读。这是一个非常有趣的谜题,花了一个早上的时间,所以我要在这里转储很多细节。

如果您想深入了解这种格式的工作原理,除了 ISO 15438 Spec, which will cost you around US0. But there used to be some pages at GeoCities that explained a lot of this, and they're still available through the Wayback Machine

目前我不知道有什么可用的

也没有很多工具可以在命令行上解码这些东西,但是 pdf417decode 做得很好。我将使用它的输出来解释我是如何知道所有值的。

您需要的最后一个工具是将 jpeg 输出转换为黑白 pbm 文件,以便 pdf417decode 可以读取它们。为此,我使用以下命令(在安装 netpbm 之后):

cat /tmp/barcode.jpeg | jpegtopnm | ppmtopgm | pamthreshold | pamtopnm > new.pbm && ./pdf417decode -c -e new.pbm

接下来,让我们解码您现有条形码的前三行(附上我的评论)。你到处都能看到“函数输出”,这意味着这个值是某个函数的输出,该函数将另一个东西作为输入:

0 7f54 0x02030000 (0)    // Left marker
0 6a38 0x00000007 (7)    // Number of rows function output
0 218c 0x00000076 (118)  // Total number of non-error correcting codewords
0 0211 0x00000385 (901)  // Latch to Byte Compaction mode
0 68cf 0x00000059 (89)   // Data
0 18ec 0x0000021c (540)
0 02e7 0x00000330 (816)
0 753c 0x00000004 (4)    // Number of columns function output
0 7e8a 0x00030001 (1)    // Right marker

1 7f54 0x02030000 (0)    // Left marker
1 7520 0x00010002 (2)    // Security Level function output
1 704a 0x00010334 (820)  // Data
1 31f2 0x000101a7 (423)
1 507b 0x000100c9 (201)
1 5e5f 0x00010319 (793)
1 6cf3 0x00010176 (374)
1 7d47 0x00010007 (7)    // Number of rows function output
1 7e8a 0x00030001 (1)    // Right marker

2 7f54 0x02030000 (0)    // Left marker
2 6a7e 0x00020004 (4)    // Number of columns function output
2 0fb2 0x0002037a (890)  // Data
2 6dfa 0x000200d9 (217)
2 5b3e 0x000200bc (188)
2 3bbc 0x00020180 (384)
2 5e0b 0x00020268 (616)
2 29e0 0x00020002 (2)    // Security Level function output 
2 7e8a 0x00030001 (1)    // Right marker

接下来的 3 行将继续这种函数输出模式。请注意,相同的信息在左侧和右侧编码,但顺序不同。系统有很多冗余,可以检测到它看到的是条码的镜像。

这里我们不关心行数,但是给定当前行数n和总行数N,函数为:

30 * (n/3) + ((N-1)/3)

其中 / 始终表示“整数,截断除法”。假设有 24 行,在第 0 行,这是 0 + (24-1)/3 = 7.

安全级别函数的输出为 2。给定安全级别 e,函数为:

30 * (n/3) + 3*e + (N-1) % 3
=> 0 + 3*e + (23%3) = 2
=> 3*e + 2 = 2
=> 3*e = 0
=> e = 0

最后,可以在输出中计算列数。为了完整起见,给定列数 c,函数为:

30 * (n/3) + (c - 1)
=> 0 + c - 1 = 4
=> c = 5

如果您查看数据行,您会发现它们与您的输入数据完全不匹配。那是因为它们有一个复杂的编码,我不会在这里详细说明。但是对于Byte compaction,你可以认为它类似于Base64编码,只不过不是64,而是Base900。其中Base64将3字节数据编码成4个字符,Base900将6字节数据编码成5个码字。

最后,所有这些代码字都转换为符号(实际行和 spaces)。使用哪个符号取决于行。能被 3 整除的行使用一个符号集,之后的行使用第二个,之后的行使用第三个。所以相同的代码字在第 7 行和第 8 行看起来完全不同。

综合起来,所有这些因素使得查看条形码并确定它与另一个条形码在内容方面的“不同”程度变得非常困难。你只需要解码它们,看看发生了什么。