• 【图像处理】Golang 获取常用图像的宽高总结


    一、背景

    有些业务需要判断图片的宽高,来做一些图片相关缩放,旋转等基础操作。

    但是图片缩放,旋转,拼接等操作需要将图片从某一格式(JPG/PNG/GIF...)转成 RGBA 格式操作,操作完毕后,再转回 (JPG/PNG/GIF...) 图片。

    那如何不做 RGBA 的转换就得到图片的宽和高呢?

    如下通过分析常见的几类图片文件,并编写一个简单的代码,从图像文件中获取宽度和高度。

    二、JPG 图片宽高获取

    2.1 JPG 图像分析

    分析一张 JPG 图片时,关键的信息如下:

    Common JPEG markers
    简写字节标识负载信息说明详细介绍
    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图片简单分析

     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

    Simple WebP
    取值BytesContent
    RIFF  0- 3 R I F F
       4- 7 length+8
    WEBP  8-11 W E B P

    VP8 

    VP8L

    VP8X

    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

     祝玩的开心~

  • 相关阅读:
    如何快速且深入的学习一门新技术
    为什么说云原生会成为未来企业技术变迁的趋势
    高并发场景下锁的使用技巧
    开箱即用~基于.NET Core的敏捷开发框架规划
    为什么在做微服务设计的时候需要DDD?
    为什么我使用了索引,查询还是慢?
    解读中兴通信在物联网行业如何践行DDD
    服务发现技术是如何演进出来的?
    关于盘点和总结的那点事儿
    文件上传 通过 ServletContext.getRealPath()获取不到路径&war与war exploded的区别
  • 原文地址:https://www.cnblogs.com/voipman/p/16108320.html
Copyright © 2020-2023  润新知