一、背景
有些業務需要判斷圖片的寬高,來做一些圖片相關縮放,旋轉等基礎操作。
但是圖片縮放,旋轉,拼接等操作需要將圖片從某一格式(JPG/PNG/GIF...)轉成 RGBA 格式操作,操作完畢后,再轉回 (JPG/PNG/GIF...) 圖片。
那如何不做 RGBA 的轉換就得到圖片的寬和高呢?
如下通過分析常見的幾類圖片文件,並編寫一個簡單的代碼,從圖像文件中獲取寬度和高度。
二、JPG 圖片寬高獲取
2.1 JPG 圖像分析
分析一張 JPG 圖片時,關鍵的信息如下:
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
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
祝玩的開心~