背景
線上執行了一個圖片合成程式,預設的小程式二維碼中獎是小程式LOGO,不滿足需求,所以將微信小程式二維碼和使用者頭像合成在一張圖片。
由於微信圖片有時候返回的Content-Type不對應(比如內容是PNG的,頭確是image/jpeg)所以使用jpeg/png/gif的順序進行圖片資料解析,哪個成功就返回解析結果。
問題
總是出現諸如invalid JPEG format: missing SOI marker
解決過程
我去檢視jpeg.Decode的原始碼,如下:
func (d *decoder) decode(r io.Reader, configOnly bool) (image.Image, error) {
d.r = r
// Check for the Start Of Image marker.
if err := d.readFull(d.tmp[:2]); err != nil {
return nil, err
}
if d.tmp[0] != 0xff || d.tmp[1] != soiMarker {
return nil, FormatError("missing SOI marker")
}
...
soiMarker常量
soiMarker = 0xd8 // Start Of Image.
可以看到判斷了第1個位元組如果不是0xff
或者第2個位元組不是0xd8
就報錯。列印圖片的bytes前幾個位元組如下:
[]byte{0xff, 0xd8, 0xff, 0xe0, 0x0, 0x10}
可以看到第1個位元組和第2個位元組滿足要求,按理說不會出現這個問題,無奈只能求助於Google,搜尋了invalid JPEG format: missing SOI marker
關鍵字出現一篇Covert base64 string to JPG引起了我的注意。
開啟看到答案
You need to create a new reader for each decoder:
pngI, errPng := png.Decode(bytes.NewReader(unbased))
// ...
jpgI, errJpg := jpeg.Decode(bytes.NewReader(unbased))
原來需要重新建立讀取器,重新建立讀取器後問題解決。
後續
抱著打破砂鍋問到底的心態,檢視了一下bytes.Reader
的原始碼,發現遊標讀取完後並未重置
// Read implements the io.Reader interface.
func (r *Reader) Read(b []byte) (n int, err error) {
if r.i >= int64(len(r.s)) {
return 0, io.EOF
}
r.prevRune = -1
n = copy(b, r.s[r.i:])
r.i += int64(n)
return
}
Reader定義
type Reader struct {
s []byte
i int64 // current reading index
prevRune int // index of previous rune; or < 0
}
可以看到r.i
就是遊標了。問題圓滿解決