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

moo*_*eek 3 barcode pdf417 swift

我有以下 PDF417 条形码示例:

\n

示例条形码

\n

可以使用zxing等在线工具进行解码

\n

结果如下:5wwwwwxwww0app5p3pewi0edpeapifxe0ixiwwdfxxi0xf5e\xef\xbf\xbd\xc2\xbc\xc3\xb4\xef\xbf\xbd\xef\xbf\xbd\xef\xbf\xbd\xef\xbf\xbd\xef\xbf\xbd\xef\xbf\xbd\xef\xbf\xbd\xef\xbf\xbd\xef\xbf\xbd\xc2\xac\xe2\x80\x9aC`\xc3\x8ce%\xef\xbf\xbd\xc3\xa6\xe2\x80\xb9\xef\xbf\xbd\xc3\x80s\xc3\xb5b\xc3\xbfG)=\xe2\x80\xa1x\xe2\x80\x9a\xef\xbf\xbdq\xc3\x801\xc3\x9f\xe2\x80\x93[Fz\xc3\xb9\xc5\xbd\xc3\xbbV\xc3\xbb\xef\xbf\xbd\xc3\x89\xef\xbf\xbd\xc3\xbc\xc3\xa6\xc2\xb1RNI\xef\xbf\xbdY[.H\xc2\xbbE\xc3\xa0\xc3\xb3\xc2\xbc\xc3\xa5\xc3\xb1\xc3\xbc\xc3\xac\xc2\xb2\xef\xbf\xbdt\xc3\x98\xc2\xbf\xc2\xaaWp\xe2\x80\xa6\xc3\x83\xef\xbf\xbd{\xef\xbf\xbd\xc3\x95*

\n

在线二维码生成器

\n

as 5wwwwwxwww0app5p3pewi0edpeapifxe0ixiwwdfxxi0xf5e~|~~~~~~~~~~d~C`~e%~~~~;To~B~{~dj9v~~Z[Xm~~"HP3~~LH~~~O~"S~~,~~~~~~~k1~~~u~Iw}SQ~fqX4~mbc_ (我不知道使用哪种编码来对此进行编码)

\n

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

\n

它的第二部分可以从 base64 字符串解码,它始终包含 88 个字节。就我而言,它是:

\n

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

\n

我在 iOS 设备上使用 Swift 通过解码提供的 base64 字符串来生成此 PDF417 条形码,如下所示:

\n
let base64Str = "Frz0DAAAAAAAAAAArIJDYMxlJQDmiwHAc/Vi/0cpPYd4ghlxwDHflltGevmO+1b7GckT/OZ/sVJOSRpZWy5Iu0Xg87zl8fzssg502L+qV3CFwxZ/ewjVKg=="\nlet knownKey = "5wwwwwxwww0app5p3pewi0edpeapifxe0ixiwwdfxxi0xf5e"\nlet decodedData = Data(base64Encoded: base64Str.replacingOccurrences(of: "-", with: "+")\n                                        .replacingOccurrences(of: "_", with: "/"))\n\nvar codeData=knownKey.data(using: String.Encoding.ascii)\n\ncodeData?.append(decodedData)\nlet image = generatePDF417Barcode(from: codeData!)\nlet imageView = UIImageView(image: image!)\n\n//the function to generate PDF417 UIMAGE from parsed Data\nfunc generatePDF417Barcode(from codeData: Data) -> UIImage? {\n\n        if let filter = CIFilter(name: "CIPDF417BarcodeGenerator") {\n            filter.setValue(codeData, forKey: "inputMessage")\n            let transform = CGAffineTransform(scaleX: 3, y: 3)\n\n            if let output = filter.outputImage?.transformed(by: transform) {\n                return UIImage(ciImage: output)\n            }\n        }\n\n        return nil\n    }\n
Run Code Online (Sandbox Code Playgroud)\n

但我总是生成错误的条形码。可以通过肉眼看到。

\n

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

\n

我还有另一个条形码示例:

\n

在此输入图像描述

\n

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

\n

这是我尝试的方法:

\n
let knownKey = "5wwwwwxwww0app5p3pewi0edpeapifxe0ixiwwdfxxi0xf5e"\nlet 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] \nlet secretUInt8 = secretArray.map { UInt8(bitPattern: $0) }\nlet secretData = Data(secretUInt8)\n\n\nlet keyArray: [UInt8] = Array(knownKey.utf8)\nvar keyData = Data(keyArray)\n\nkeyData.append(secretData)\n\nlet image = generatePDF417Barcode(from: keyData!)\nlet imageView = UIImageView(image: image!)\n
Run Code Online (Sandbox Code Playgroud)\n

Rob*_*ier 5

这里发生了很多事情。Gereon 是正确的,有很多参数。选择不同的参数可能会导致解码效果截然不同的条码。您当前的条形码是“正确的”(尽管由于苹果错误而有点混乱)。只是不同而已。

我将从如何使您的数据与您拥有的条形码相匹配的简短答案开始。然后我将逐步介绍您实际上应该做什么,最后我将详细说明原因。

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

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
Run Code Online (Sandbox Code Playgroud)

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

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

您无法配置多种压缩方法,但您可以将其设置为字节,这就是值 3 的值。你的消息来源也是这样做的。

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

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

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


您现在可能想停止阅读。这是一个非常有趣的谜题,值得花一上午的时间来解决,所以我将在这里放弃很多细节。

如果您想深入了解这种格式的工作原理,除了ISO 15438 规范之外,我不知道目前有什么可用的,这将花费您大约 200 美元。但 GeoCities 上曾经有一些页面解释了很多这方面的内容,并且仍然可以通过Wayback Machine获取它们。

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

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

cat /tmp/barcode.jpeg | jpegtopnm | ppmtopgm | pamthreshold | pamtopnm > new.pbm && ./pdf417decode -c -e new.pbm
Run Code Online (Sandbox Code Playgroud)

这样,我们就可以解码现有条形码的前三行(旁边有我的评论)。在任何地方看到“函数输出”,这意味着该值是某个函数的输出,该函数将另一个函数作为输入:

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
Run Code Online (Sandbox Code Playgroud)

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

为此,我们不关心行数,但给定当前行n和 的总行数N,该函数为:

30 * (n/3) + ((N-1)/3)
Run Code Online (Sandbox Code Playgroud)

其中/“always”的意思是“整数,截断除法”。假设有 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
Run Code Online (Sandbox Code Playgroud)

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

30 * (n/3) + (c - 1)
=> 0 + c - 1 = 4
=> c = 5
Run Code Online (Sandbox Code Playgroud)

如果您查看数据行,您会发现它们与您的输入数据根本不匹配。那是因为它们有一个复杂的编码,我不会在这里详细介绍。但是对于字节压缩,您可以将其视为类似于 Base64 编码,但不是 64,而是 Base900。Base64 将 3 个字节的数据编码为 4 个字符,而 Base900 将 6 个字节的数据编码为 5 个码字。

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

总而言之,所有这些因素使得查看条形码并确定它与另一个条形码在内容方面有多么“不同”变得非常困难。您只需解码它们并看看发生了什么。

  • 非常感谢你,罗布!“inputCompactionMode”选项确实可以完成这项工作,现在当我使用在线工具比较生成的结果时,我收到相同的输出,但是在比较中两个条形码看起来不同。我很快就会用主扫描仪进行测试,希望一切顺利。特别感谢您提供如此详细的答案并对该算法的实际工作原理进行了出色的研究。 (2认同)