【圖像處理】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
取值 Bytes Content
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

 

6.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
}

  

七、 使用官方的庫寬高的獲取

上代碼

// ImageDim image width and height
type ImageDim struct {
	Width  float64 `json:"width"`  //寬
	Height float64 `json:"height"` //高
}

// DecodeImageWidthHeight 解析圖片的寬高信息
func DecodeImageWidthHeight(imgBytes []byte, fileType string) (*ImageDim, error) {
	var (
		imgConf image.Config
		err     error
	)
	switch strings.ToLower(fileType) {
	case "jpg", "jpeg":
		imgConf, err = jpeg.DecodeConfig(bytes.NewReader(imgBytes))
	case "webp":
		imgConf, err = webp.DecodeConfig(bytes.NewReader(imgBytes))
	case "png":
		imgConf, err = png.DecodeConfig(bytes.NewReader(imgBytes))
	case "tif", "tiff":
		imgConf, err = tiff.DecodeConfig(bytes.NewReader(imgBytes))
	case "gif":
		imgConf, err = gif.DecodeConfig(bytes.NewReader(imgBytes))
	case "bmp":
		imgConf, err = bmp.DecodeConfig(bytes.NewReader(imgBytes))
	default:
		return nil, errors.New("unknown file type")
	}
	if err != nil {
		return nil, err
	}
	return &ImageDim{
		Width:  float64(imgConf.Width),
		Height: float64(imgConf.Height),
	}, nil
}

  

總結

通過分析 JPG 圖片的 SOF 信息,PNG ,webp 等的頭信息,可以圖片的寬和高。

 

Done

 祝玩的開心~

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM