【影像處理】Golang 獲取JPG影像的寬高

一、背景

有些業務需要判斷圖片的寬高,來做一些圖片相關縮放,旋轉等基礎操作。

但是圖片縮放,旋轉,拼接等操作需要將圖片從 JPG 格式轉成 RGBA 格式操作,操作完畢後,再轉回 JPG 圖片。

那如何不做 RGBA 的轉換就能得到 JPG 圖片的寬和高呢?

如下通過 JPG 文件的分析,並編寫一個簡單的程式碼,從 JPG 文件中獲取寬度和高度。

 

二、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 圖片結束  

 

三、JPG開始,寬高分析

3.1 JPG格式判斷方法

JPG 圖片的開頭是0xFF 0xD8

因此,判斷 JPG 圖片的魔法文件 magic 標識就是0xFF 0xD8

在 golang 中也是通過0xFF 0xD8判斷圖片是否為 JPG 文件,如下所示:

&exactSig{[]byte("\xFF\xD8\xFF"), "image/jpeg"},

 

3.2 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

得到的寬高和預覽時的寬高一致。

 

3.3. JPG 寬高計算原理

eg: [ff c0] 00 11 08  [03 ea]   [04 b0]
    |                 |         |
    |                 |         |
     -> SOF1           ->height  ->width

腳本計算寬高如下:

% expr 3<<8|0xea
1002
% expr 4<<8|0xb0
1200

 

3.4 通過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
}

  

總結

通過分析 JPG 圖片的 SOF 資訊,就可以提取圖片的寬和高,而不用將其轉換成 RGBA,再獲取 RGBA 的寬高,可以節約一些計算資源。

後續再分析 JPG 圖片的其他資訊,如下離散餘弦變換和哈夫曼編碼等。

 

Done

 祝玩的開心~

 

Tags: