3

我有以下 PDF417 条形码示例:

示例条码

可以用zxing等在线工具解码

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

在线二维码生成器

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: $0) }
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!)
4

2 回答 2

4

这里发生了很多事情。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 定义了几种“压缩模式”,使其能够将真正令人印象深刻的信息量打包到一个非常小的空间中,同时仍然提供出色的错误检测和纠正,并处理许多现实世界的扫描问题。默认压缩模式仅支持拉丁文本和基本标点符号。(如果你只使用大写拉丁字母和空格,它会压缩得更多。)你的字符串的第一部分可以用文本压缩存储,但其余部分不能,所以它必须切换到字节压缩。

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

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

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

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

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


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

如果您想深入了解这种格式的工作原理,除了ISO 15438 Spec之外,我不知道目前可用的任何东西,这将花费您大约 200 美元。但过去 GeoCities 上的一些页面解释了很多这一点,它们仍然可以通过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)

where/总是意味着“整数,截断除法”。假设有 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 个码字。

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

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

于 2021-03-06T20:25:17.650 回答
1

CIPDF417BarcodeGenerator除此之外,还有一些输入参数inputMessage会影响生成的条形码的外观 - 请参阅文档inputCorrectionLevel仅当您知道所有这些参数(最重要的是两个生成器都相等)时,对两个代码的目视检查/比较才有意义。

因此,无需进行视觉比较,只需尝试使用众多扫描仪应用程序之一对条形码进行解码,然后比较解码后的字节数。

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

// ...

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

let image = generatePDF417Barcode(from: keyData)
于 2021-03-06T15:29:24.560 回答