【图像处理】Golang 获取JPG图像的宽高
一、背景
有些业务需要判断图片的宽高,来做一些图片相关缩放,旋转等基础操作。
但是图片缩放,旋转,拼接等操作需要将图片从某一格式(JPG/PNG/GIF...)转成 RGBA 格式操作,操作完毕后,再转回 (JPG/PNG/GIF...) 图片。
那如何不做 RGBA 的转换就得到图片的宽和高呢?
如下通过分析常见的几类图片文件,并编写一个简单的代码,从图像文件中获取宽度和高度。
二、JPG 图片宽高获取
2.1 JPG 图像分析
分析一张 JPG 图片时,关键的信息如下:
| 简写 | 字节标识 | 负载信息 | 说明 | 详细介绍 |
|---|---|---|---|---|
| SOI | 0xFF, 0xD8 | none | JPG 开始标识 | |
| SOF0 | 0xFF, 0xC0 | variable size | 开始帧 (baseline DCT) | Indicates that this is a baseline DCT-based JPEG, and specifies the width, height, number of components, and component subsampling (e.g., 4:2:0). |
| SOF1 | 0xFF, 0xC1 | variable size | 开始帧 (extended sequential DCT) | Indicates that this is a extended sequential DCT-based JPEG, and specifies the width, height, number of components, and component subsampling (e.g., 4:2:0). |
| SOF2 | 0xFF, 0xC2 | variable size | 开始帧 (progressive DCT) | Indicates that this is a progressive DCT-based JPEG, and specifies the width, height, number of components, and component subsampling (e.g., 4:2:0). |
| DHT | 0xFF, 0xC4 | variable size | 哈夫曼编码定义表 | Specifies one or more Huffman tables. |
| DQT | 0xFF, 0xDB | variable size | Define Quantization Table(s) | Specifies one or more quantization tables. |
| DRI | 0xFF, 0xDD | 4 bytes | Define Restart Interval | Specifies the interval between RSTn markers, in Minimum Coded Units (MCUs). This marker is followed by two bytes indicating the fixed size so it can be treated like any other variable size segment. |
| SOS | 0xFF, 0xDA | variable size | Start Of Scan | Begins a top-to-bottom scan of the image. In baseline DCT JPEG images, there is generally a single scan. Progressive DCT JPEG images usually contain multiple scans. This marker specifies which slice of data it will contain, and is immediately followed by entropy-coded data. |
| RSTn | 0xFF, 0xDn (n=0..7) | none | Restart | Inserted every r macroblocks, where r is the restart interval set by a DRI marker. Not used if there was no DRI marker. The low three bits of the marker code cycle in value from 0 to 7. |
| APPn | 0xFF, 0xEn | variable size | Application-specific | For example, an Exif JPEG file uses an APP1 marker to store metadata, laid out in a structure based closely on TIFF. |
| COM | 0xFF, 0xFE | variable size | 图片注释信息 | Contains a text comment. |
| EOI | 0xFF, 0xD9 | none | 图片结束 |
2.2 JPG格式判断方法
JPG 图片的开头是0xFF 0xD8
因此,判断 JPG 图片的魔法文件 magic 标识就是0xFF 0xD8
在 golang 中也是通过0xFF 0xD8判断图片是否为 JPG 文件,如下所示:
&exactSig{[]byte("\xFF\xD8\xFF"), "image/jpeg"},
2.3 JPG 图片宽高获取
本文通过分析JPG 图片的开始帧SOF 获取图片的宽高。
预览一张图片获取图像的宽高基本信息。

宽:1200,高:1002

可以使用二进制方式打开文件,查看 JPG 图片的头部信息,获取 JPG 图片开始帧信息如SOF0, SOF1, SOF2。
SOF0 表示baseline DCT, 基线 DCT(离散余弦变换),开头的标识是 0xFF 0xC0
SOF1 表示extended sequential DCT,扩展序列 DCT ,开头的标识是 0xFF 0xC1
SOF2 表示progressive DCT,升级 DCT, 开头的标识是 0xFF 0xC2
如下是一个 JPG 的头部信息:

从上图中可以看到开始帧信息是 SOF0,即 绿色标记的 ffc0。
找到 SOF 后,向后偏移5个字节得到高和宽
高:03 ea,计算得到高等于 3<<8|0xea = 1002
宽:04 b0,计算得到宽等于4<<8|0xb0 = 1200
得到的宽高和预览时的宽高一致。
2.4. JPG 宽高计算原理
eg: [ff c0] 00 11 08 [03 ea] [04 b0]
| | |
| | |
-> SOF1 ->height ->width
脚本计算宽高如下:
% expr 3<<8|0xea
1002
% expr 4<<8|0xb0
1200
2.5 通过golang 实现 JPG 图片宽高的获取
知道了 JPG 获取图片宽高的原理后,使用 golang代码获取JPG 图片的宽高如下:
/**
* 入参: JPG 图片文件的二进制数据
* 出参:JPG 图片的宽和高
**/
func GetWidthHeightForJpg(imgBytes []byte) (int, int) {
var offset int
imgByteLen := len(imgBytes)
for i := 0; i < imgByteLen-1; i++ {
if imgBytes[i] != 0xff {
continue
}
if imgBytes[i+1] == 0xC0 || imgBytes[i+1] == 0xC1 || imgBytes[i+1] == 0xC2 {
offset = i
break
}
}
offset += 5
if offset >= imgByteLen {
return 0, 0
}
height := int(imgBytes[offset])<<8 + int(imgBytes[offset+1])
width := int(imgBytes[offset+2])<<8 + int(imgBytes[offset+3])
return width, height
}
三、PNG图片宽高获取
3.1 PNG图片简单分析
|
89 50 4E 47 0D 0A 1A 0A PNG签名 |
IHDR 图像头信息 |
IDAT 图像信息 |
IEND 图像结尾 |
3.2 PNG格式判断方法
从PNG 图片的格式中可以看到,PNG图片的头信息是 \x89PNG\r\n\x1a\n
golang 判断 PNG 图片的魔法文件 magic 标识,方法如下
&exactSig{[]byte("\x89PNG\x0D\x0A\x1A\x0A"), "image/png"},
3.3 PNG图片宽高获取

预览 PNG 图片
宽1240,高1822

宽高分析
eg: [49 48 44 52] [00 00 04 d8] [00 00 07 1e]
| | |
| | |
-> IHDR ->width ->height
脚本计算宽高
% expr 0xd8|0x04<<8|0<<16|0<<24
1240
% expr 0x1e|0x07<<8|0<<16|0<<24
1822
3.4 通过golang实现PNG图片宽高的获取
// 获取 PNG 图片的宽高
func GetPngWidthHeight(imgBytes []byte) (int, int) {
pngHeader := "\x89PNG\r\n\x1a\n"
if string(imgBytes[:len(pngHeader)]) != pngHeader {
return 0, 0
}
offset := 12
if "IHDR" != string(imgBytes[offset:offset+4]) {
return 0, 0
}
offset += 4
width := int(binary.BigEndian.Uint32(imgBytes[offset:offset+4]))
height := int(binary.BigEndian.Uint32(imgBytes[offset+4:offset+8]))
return width, height
}
四、GIF图片宽高获取
4.1 GIF图片简单分析
GIF 图片以GIF87a或者GIF89a开头,宽和高分别占2个字节按照大端编码。
4.2 GIF格式判断方法
从GIF图片的格式中可以看到,GIF图片的头信息是 GIF89a或GIF87a
golang 判断 PNG 图片的魔法文件 magic 标识,方法如下
&exactSig{[]byte("GIF87a"), "image/gif"},
&exactSig{[]byte("GIF89a"), "image/gif"},
4.3 GIF图片宽高获取

上图中的 GIF 开头为 GIF89a
红色标记的两个字节是宽 0xc8 0x00
绿色标记的两个字节是高 0xc3 0x00
预览图片
宽200,高195

脚本计算宽高
% expr 0xc8|0<<8
200
% expr 0xc3|0<<8
195
4.4 通过golang实现GIF图片宽高的获取
// 获取 GIF 图片的宽高
func GetGifWidthHeight(imgBytes []byte) (int, int) {
version := string(imgBytes[:6])
if version != "GIF87a" && version != "GIF89a" {
return 0, 0
}
width := int(imgBytes[6]) + int(imgBytes[7])<<8
height := int(imgBytes[8]) + int(imgBytes[9])<<8
return width, height
}
五、WEBP图片宽高获取
5.1 webp图片简单分析
webp的文件格式可以参考之前的一篇 webp 分析小文 https://www.cnblogs.com/voipman/p/15244037.html
| 取值 | Bytes | Content | |||
|---|---|---|---|---|---|
RIFF
|
0- 3
|
R | I | F | F |
4- 7
|
length+8 | ||||
WEBP
|
8-11
|
W | E | B | P |
|
|
12-15
|
V | P | 8 | (space) |
16-19
|
length (padded) | ||||
20- …
|
VP8 key frame | ||||
| pad | ? (even length) | ||||
RIFF头信息由21个字节组成。
0-3 四个字节是 RIFF 四个字符,表示 资源交换格式Resource Interchange File Format的简写。
4-7 四个字节是 WEBP文件的全部长度,这个长度包含RIFF
8-11 四个字节是 资源交换格式的名称,填WEBP这四个字符
12-15 四个字节是数据块Chunk的负载信息的编码格式,取值有VP8表示无损vp8压缩,VP8L表示有损vp8压缩,VP8X表示扩展的编码。
16-19 四个字节是有损压缩时的VP8数据负载信息的长度
20-以后数vp8格式的图像数据帧。
5.2 webp格式判断方法
从图片的格式中可以看到,WEBP图片的RIFF 信息是 GIF89a或GIF87a
golang 判断 PNG 图片的魔法文件 magic 标识,方法如下
image.RegisterFormat("webp", "RIFF????WEBPVP8", Decode, DecodeConfig)
// net/http/sniff.go
&maskedSig{
mask: []byte("\xFF\xFF\xFF\xFF\x00\x00\x00\x00\xFF\xFF\xFF\xFF\xFF\xFF"),
pat: []byte("RIFF\x00\x00\x00\x00WEBPVP"),
ct: "image/webp",
},
5.3 webp图片宽高获取

上图中的 WEBP 开头为 RIFF
0x56 0x50 0x38 0x20是VP8空格,表示无损vp8压缩
红色标记的两个字节是宽 0x80 0x00
绿色标记的两个字节是高 0x80 0x00
预览图片
宽128,高128

宽高分析
eg: [52 49 46 46].... [57 45 42 50][56 50 38 20].... [80 00] [80 00]
| | | | |
| | | | |
-> RIFF -> WEBP -> VP8 ->width ->height
脚本计算宽高
% expr 0&0x3f<<8|0x80
128
可以参考 golang.org/x/image 包中的 webp可以解码 webp 图片(包含无损压缩VP8空格,有损压缩的 VP8L和VP8X表示扩展的编码)
无损压缩VB8 获取宽高的计算如下
offset := 26
width = int(b[offset+1]&0x3f)<<8 | int(b[offset])
height = int(b[offset+3]&0x3f)<<8 | int(b[offset+2])
六、BMP图片宽高获取
6.1 bmp图片简单分析
6.2 bmp格式判断方法
从BMP图片的格式中可以看到,BMP图片的头信息是 BM????\x00\x00\x00\x00
golang 判断的魔法文件 magic 标识,方法如下
&exactSig{[]byte("BM"), "image/bmp"},
6.3 bmp图片宽高获取

预览图片
宽128,高142

宽高分析
eg: [42 4d] .... [80 00 00 00] [72 ff ff ff]
| | |
| | |
-> BM ->width ->height
7.4 通过golang实现BMP图片宽高的获取
// 获取 BMP 图片的宽高
func GetBmpWidthHeight(imgBytes []byte) (int, int) {
if string(imgBytes[:2]) != "BM" {
return 0, 0
}
width := int(binary.LittleEndian.Uint32(imgBytes[18:22]))
height := int(int32(binary.LittleEndian.Uint32(imgBytes[22:26])))
if height < 0 {
height = -height
}
return width, height
}
总结
通过分析 JPG 图片的 SOF 信息,PNG ,webp 等的头信息,可以图片的宽和高。
Done
祝玩的开心~
【图像处理】Golang 获取JPG图像的宽高的更多相关文章
- 获取img的真实宽高
之前项目后台上传图片时需要对图片的宽高做限制,一开始百度了之后使用js进行判断,可是这种方式存在一定问题,后来就改在后台判断了.现在吧这两种方式都贴出来. 一.用js获取: 先说第一个方法:obj.s ...
- js 获取页面可视区域宽高
获取浏览器窗口的可视区域高度和宽度,滚动条高度有需要的朋友可参考一下. 1.IE中,浏览器显示窗口大小只能以下获取: 代码如下复制代码 代码如下 document.body.offsetWidth d ...
- js获取精确的元素宽高(普通获取高度会有误差)
当js获取元素宽高时, 并不是一个精确的数字,如果想获取真正的宽高大致方法如下 var oStyle = obj.currentStyle ? obj.currentStyle : window.ge ...
- Android在onCreate中获取控件的宽高
在某些需求下,我们需要在onCreate的时候就获取到控件的宽高,但是如果直接用view.getWidth()或view.getHeight()会得到0.这是因为在onCreate执行的时候,控件还没 ...
- activity 中获取控件的宽高
1.第一种方式: TextView textview3 = findViewById(R.id.textview3); textView3.post(new Runnable() { @Overrid ...
- 解决获取图片实际尺寸(宽高)的bug
需求:获取图片的宽高其实是为了预先做好排版样式布局做准备. 可以利用图片onload事件监听获取图片的宽高属性值.在IE9以下版本只能使用图片的width与height属性,HTMl5中新加入了nat ...
- 多媒体开发之sps---解析sps得到图像的宽高
(1)通过块的宽高解析出真个h264的分辨率 如何解析SDP中包含的H.264的SPS和PPS串 http://www.pernet.tv.sixxs.org/thread-109-1-1.html ...
- js 获取屏幕或元素宽高...
窗口相对于屏幕顶部距离 window.screenTop 窗口相对于屏幕左边距离 window.screenLeft, 屏幕分辨率的高 window.screen.height, 屏幕分辨率的宽 wi ...
- JS 获取浏览器和屏幕宽高信息
网页可见区域宽:document.body.clientWidth网页可见区域高:document.body.clientHeight网页可见区域宽:document.body.offsetWidth ...
随机推荐
- 28 面向对象编程 instanceof 代码 小结 父类引用指向子类对象
instanceof 代码 // main // Object > Person >Student Object object = new Student(); // 提取公式:XY之间是 ...
- 一文让你明白CPU上下文切换
我们都知道,Linux 是一个多任务操作系统,它支持远大于 CPU 数量的任务同时运行.当然,这些任务实际上并不是真的在同时运行,而是因为系统在很短的时间内,将 CPU 轮流分配给它们,造成多任务同时 ...
- 搭建域环境,安装Exchange Server 2013,复现CVE-2019-1040
搭建域环境 操作系统: 域控:Windows server 2008 R2 域成员: Windows Server 2012 . Windows 7 对于将要安装成为DC的服务器来讲,其系统配置 ...
- 选择Key-Value Store
[IT168 专稿]在之前的文章中,给大家介绍了<Redis快速入门:Key-Value存储系统简介>,今天进一步给大家介绍为什么选择Key-Value Store.Key-Value S ...
- CF1225E Rock Is Push (计数)
观察性质计数题orz小贺 考场上跟榜才切 我们只能往下和往右走,那么只有连续的往下和往右可能会造成不合法的情况!如果当前这一步是向右,那么只有它前面连续的一段向右可能影响到它. 考虑把连续的向右/下一 ...
- 采用WPF开发第二版OFD阅读器,持续完善中,敬请期待!
本人研究OFD多年,采用C#和QT开发了一系列ofd相关软件.在这些产品中,阅读器始终占据着非常重要的位置.可以说,阅读器是直接面向最终客户的产品.是集OFD各类知识之大成的产品.市面上的阅读器产品林 ...
- 论文解读(GRACE)《Deep Graph Contrastive Representation Learning》
Paper Information 论文标题:Deep Graph Contrastive Representation Learning论文作者:Yanqiao Zhu, Yichen Xu, Fe ...
- MariaDB ZIP方式安装(Window系统)
Maria DB ZIP方式安装 Windows上ZIP包的入门非常简单-此发行版包括预构建的数据库文件,这些文件可以在解压缩ZIP后立即使用. 您可以从命令提示符运行mysqld.exe,如下所示: ...
- 什么是 Spring 的依赖注入?
依赖注入,是 IOC 的一个方面,是个通常的概念,它有多种解释.这概念是说你 不用创建对象,而只需要描述它如何被创建.你不在代码里直接组装你的组件和 服务,但是要在配置文件里描述哪些组件需要哪些服务, ...
- java后端使用token处理表单重复提交
保证接口幂等性,表单重复提交 前台解决方案:提交后按钮禁用.置灰.页面出现遮罩后台解决方案: 使用token,每个token只能使用一次1.在调用接口之前生成对应的Token,存放至redis 2 ...