Go:通过 io.Writer 将 JPEG 转为 JFIF |
您所在的位置:网站首页 › image51jfif › Go:通过 io.Writer 将 JPEG 转为 JFIF |
[[439345]] 大家好,我是程序员幽鬼。 Go 的标准库可让你对 JPEG 图像进行编码。在 One of these JPEGs is not like the other[1] 一文中,Ben Cox 指出某些硬件不会解码这些 JPEG 图像,除非它们被增强为 JFIF 图像。JFIF 代表“JPEG 文件交换格式”,在概念上是原始 JPEG 格式的次要版本。 硬件缺乏支持有点令人惊讶,因为 JPEG 是一种无处不在的文件格式。他 fork[2] 并 修复[3] 标准 image/jpeg 包以插入必要的 JFIF 字节。 01 JPEG Wire 格式就网络(或磁盘)上的字节而言,JPEG 由一系列连接在一起的块组成。每个块要么是一个裸标记(两个字节,以 开头 0xff)要么是一个标记段(四个或更多字节是一个两字节标记,同样以 0xff 开头,一个两字节的长度,然后是一个额外的数据负载)。以下是 Wikipedia 的Example.jpg[4] 十六进制表示: $ wget --quiet https://upload.wikimedia.org/wikipedia/en/a/a9/Example.jpg $ hd Example.jpg | head -n 5 00000000 ff d8 ff e0 00 10 4a 46 49 46 00 01 01 01 00 48 |......JFIF.....H| 00000010 00 48 00 00 ff e1 00 16 45 78 69 66 00 00 4d 4d |.H......Exif..MM| 00000020 00 2a 00 00 00 08 00 00 00 00 00 00 ff fe 00 17 |.*..............| 00000030 43 72 65 61 74 65 64 20 77 69 74 68 20 54 68 65 |Created with The| 00000040 20 47 49 4d 50 ff db 00 43 00 05 03 04 04 04 03 | GIMP...C.......|在打开的 80 个字节标记: 一个 ff d8 SOI(图像的开始)标记。 一个 ff e0 APP0 标记段;有效载荷以 “JFIF” 开头。 一个 ff e1 APP1 标记段;有效载荷以 “Exif” 开头。 一个 ff fe 注释标记段,“Created 等等”。 一个 ff db DQT(定义量化表)标记段。file 命令也认为这是 JFIF(带 Exif),而不仅仅是 JPEG: $ file Example.jpg Example.jpg: JPEG image data, JFIF... Exif... baseline... 02 JFIF Wire 格式JFIF 文件是一个 JPEG 文件,它的第二个块(在作为第一个块的 SOI 之后)是一个 APP0 块,其有效载荷以 “JFIF” 开头。一个有趣的点是 JFIF 和 EXIF 规范在技术上不兼容,因为它们都想占用第二块(the second chunk): JFIF 规范[5]第 2 页提到:“JPEG FIF APP0 标记必须紧跟在 SOI 标记之后”。 EXIF 规范[6] 第 4.5.4 段提到:“APP1 是紧跟在 SOI 标记之后的”。在实践中,似乎 JFIF 'won' 和 EXIF 可以是第三个块。 03 生成普通的旧 JPEG这篇博文提供了不需要任何标准库补丁(或 forks)的 Cox 方法的替代方法。与往常一样,fork 具有从上游缓慢分叉的长期风险。Go 标准库的上游补丁受制于“3 个月的新功能,3 个月的稳定” 发布周期[7],并决定额外的 JFIF 块是强制性的还是可选的(如果可选,API 应该是什么,受兼容性限制[8])。 该方案的主要思想是 jpeg.Encode[9] 函数接受一个 io.Writer 参数,并且很容易包装 io.Writer 以在正确的位置插入 JFIF 字节。 首先,让我们编写一个简单的程序来生成一张 1x1 JPEG 图像。 package main import ( "image" "image/jpeg" "os" ) func main() { m := image.NewGray(image.Rect(0, 0, 1, 1)) if err := jpeg.Encode(os.Stdout, m, nil); err != nil { os.Stderr.WriteString(err.Error() + "\n") os.Exit(1) } }运行它会生成一个 JPEG(但不是 JFIF)文件。 $ go run from-jpeg-to-jfif.go > x $ hd x | head -n 5 00000000 ff d8 ff db 00 84 00 08 06 06 07 06 05 08 07 07 |................| 00000010 07 09 09 08 0a 0c 14 0d 0c 0b 0b 0c 19 12 13 0f |................| 00000020 14 1d 1a 1f 1e 1d 1a 1c 1c 20 24 2e 27 20 22 2c |......... $.' ",| 00000030 23 1c 1c 28 37 29 2c 30 31 34 34 34 1f 27 39 3d |#..(7),01444.'9=| 00000040 38 32 3c 2e 33 34 32 01 09 09 09 0c 0b 0c 18 0d |82 y $ hd y | head -n 5 00000000 ff d8 ff e0 00 10 4a 46 49 46 00 01 01 00 00 01 |......JFIF......| 00000010 00 01 00 00 ff db 00 84 00 08 06 06 07 06 05 08 |................| 00000020 07 07 07 09 09 08 0a 0c 14 0d 0c 0b 0b 0c 19 12 |................| 00000030 13 0f 14 1d 1a 1f 1e 1d 1a 1c 1c 20 24 2e 27 20 |........... $.' | 00000040 22 2c 23 1c 1c 28 37 29 2c 30 31 34 34 34 1f 27 |",#..(7),01444.'| $ file y y: JPEG image data, JFIF... baseline... 05 结论这里的细节是关于 JPEG 和 JFIF 的,但一般的想法是,如果 encoding 库(Go 中的一个包)缺少一个功能,你可以不通过更改该库来修复它(或以其他方式对其进行处理),而是预处理输入或处理输出。 原文链接:https://nigeltao.github.io/blog/2021/from-jpeg-to-jfif.html 参考资料 [1]One of these JPEGs is not like the other: https://blog.benjojo.co.uk/post/not-all-jpegs-are-the-same [2]fork: https://github.com/benjojo/app0-image-jpeg [3]修复: https://github.com/benjojo/app0-image-jpeg/commit/645750c1672807c80c08a57a684a0ada7bf371d9 [4]Example.jpg: https://en.wikipedia.org/wiki/File:Example.jpg [5]JFIF 规范: https://www.w3.org/Graphics/JPEG/jfif3.pdf [6]EXIF 规范: https://www.exif.org/Exif2-2.PDF [7]发布周期: https://github.com/golang/go/wiki/Go-Release-Cycle [8]兼容性限制: https://golang.org/doc/go1compat [9]jpeg.Encode: https://pkg.go.dev/image/jpeg#Encode 本文转载自微信公众号「程序员ug」,可以通过以下二维码关注。转载本文请联系程序员ug公众号。
|
CopyRight 2018-2019 办公设备维修网 版权所有 豫ICP备15022753号-3 |