はじめに
GolangでのCLIツール作成の練習として画像をリサイズしてくれるツールを作っていた際に、1つのstreamから複数回読み込みを行う必要がでてきたのでその備忘録
なぜ複数回の読み込みが必要?
- 画像の形式に関しては利用者側は意識しなくても良いように
image.Decode()
で取得できるformat
を使い処理を条件分岐してました。(この処理で1つの読み込みが必要
img, format, _ := image.Decode(in)
switch format {
case "png":
-
png
とjpeg
に関してはimage.Decode
で取得できるImage
インスタンスをそれ移行の処理で使えばいいだけなのですが、gif
はgif.DecodeAll()
という別の関数を呼び出す必要があるらしく、こちらの引数がio.Reader
でした(2つ目
どうしたのか
buf := bytes.NewBuffer(nil)
gifDecodeTarget := bytes.NewBuffer(nil)
w := io.MultiWriter(buf, gifDecodeTarget)
io.Copy(w, in)
img, format, err := image.Decode(buf)
switch format {
case "gif":
gifimg, err := gif.DecodeAll(gifDecodeTarget)
io.MultiWriter
を使うことで複数のoutに対して書き込みができるので、io.Copy
を利用してinを複数のout(両方ともBuffer
)に書き込み、bytes.Buffer
はRead関数を持ってるためio.Reader
を満たしているので、読み込みが必要な場面で利用していきました
その他指摘いただいた方法
Seekを使うパターン
Seekerが実装されているos.File
などで受け取ること前提になってしまいますが、Seekを使うパターン
img, format, err := image.Decode(in)
switch format {
case "gif":
in.Seek(0, 0) // インデックスを先頭に戻す
gifimg, err := gif.DecodeAll(in)
io.TeeReaderを使うパターン
こちらだとio.Reader
のまま行けそうです
buf := bytes.NewBuffer(nil)
r := io.TeeReader(in, buf)
img, format, err := image.Decode(r) // このタイミングでbufに書き込みが走る
switch format {
case "gif":
gifimg, err := gif.DecodeAll(buf)