Webassemblyを試してみたく、Goでアップロードされた画像を白黒、セピア調に変換するプログラムを書いてみました。
前回の記事
https://yuki-ikegaya.net/2023/08/20/docker%e3%81%a8gin%e3%81%a7go%ef%bc%8bwebassembly%e3%83%97%e3%83%ad%e3%82%b8%e3%82%a7%e3%82%af%e3%83%88%e3%81%ae%e9%9b%9b%e5%bd%a2%e3%82%92%e4%bd%9c%e3%81%a3%e3%81%9f/ Github https://github.com/ikeyu0806/webassembly-image-app
このうち、セピア調に変換するプログラムについて実装を振り返ってみます。白黒に変換するプログラムも大体内容は同じです。
書いたコード(Goの画像処理部分)
package convert_image import ( "bytes" "encoding/base64" "image" "image/color" "image/png" "syscall/js" ) func ConvertToSepia(this js.Value, args []js.Value) interface{} { imageData := args[0] uint8Array := js.Global().Get("Uint8Array").New(imageData) imageBytes := make([]byte, uint8Array.Length()) js.CopyBytesToGo(imageBytes, uint8Array) img, _, err := image.Decode(bytes.NewReader(imageBytes)) if err != nil { return err.Error() } sepiaImg := convertToSepiaImage(img) var encodedImage bytes.Buffer err = png.Encode(&encodedImage, sepiaImg) if err != nil { return err.Error() } dataURI := "data:image/png;base64," + base64.StdEncoding.EncodeToString(encodedImage.Bytes()) imgElement := js.Global().Get("document").Call("createElement", "img") imgElement.Set("src", dataURI) var body = js.Global().Get("document").Get("body") body.Call("appendChild", imgElement) return nil func convertToSepiaImage(src image.Image) image.Image { bounds := src.Bounds() sepiaImg := image.NewRGBA(bounds) for y := bounds.Min.Y; y < bounds.Max.Y; y++ { for x := bounds.Min.X; x < bounds.Max.X; x++ { originalColor := src.At(x, y) originalRGBA := color.RGBAModel.Convert(originalColor).(color.RGBA) // 赤、緑、青の合計を3で割ることでグレースケールの輝度値を取得 gray := (uint32(originalRGBA.R) + uint32(originalRGBA.G) + uint32(originalRGBA.B)) / 3 // グレースケールの輝度値にセピア調のフィルター適用 sepiaR := gray + 112 sepiaG := gray + 66 sepiaB := gray + 20 if sepiaR > 255 { sepiaR = 255 } if sepiaG > 255 { sepiaG = 255 } if sepiaB > 255 { sepiaB = 255 } sepiaImg.Set(x, y, color.RGBA{R: uint8(sepiaR), G: uint8(sepiaG), B: uint8(sepiaB), A: originalRGBA.A}) } } return sepiaImg }
コードの解説
ConvertToSepia関数
- imageData := args[0]でjs.Value型でブラウザからinputタグで取得したファイルを取得できます
- js.Global().Get("Uint8Array").New(imageData) でJavaScriptのUint8Arrayオブジェクトにデータを変換します。これにより、データがJavaScriptのバイト配列として利用できるようになります。
- uint8Array.Length()でUint8Arrayの長さを取得し、それを使用してGoのバイトスライス imageBytes を作成します。次に、- js.CopyBytesToGo(imageBytes, uint8Array)を使用してJavaScriptからGoのバイトスライスへデータをコピーします。
- image.Decode(bytes.NewReader(imageBytes)) でimageパッケージで扱えるよう画像データをデコードします。デコードに失敗した場合、エラーが返されます。
- convertToSepiaImage関数を使用して、デコードされた画像をセピア調に変換します。
- png.Encode(&encodedImage, sepiaImg)を使用して、セピア調に変換された画像をPNG形式でエンコードします。
- エンコードされた画像をData URI形式に変換し、dataURI 変数に格納します。Data URIは、画像データをBase64エンコードして、HTML内で直接表示できる形式です。
- js.Global().Get("document").Call("createElement", "img")を使用して、HTMLのimg要素を作成し、そのsrc属性に先ほど生成したData URIを設定します。最後に、body.Call("appendChild", imgElement)を使用して、img要素をHTML文書に追加し、画像を表示します。
convertToSepiaImage関数
- bounds := src.Bounds()
- 画像の境界情報 (幅と高さ) を取得します。これにより、後続のループで画像のピクセルにアクセスできます。
- sepiaImg := image.NewRGBA(bounds)
- 画像をRGBA型で取得してカラーコードを扱えるようにします。
- gray := (uint32(originalRGBA.R) + uint32(originalRGBA.G) + uint32(originalRGBA.B)) / 3
- 元の色情報から赤、緑、青の平均値を取得してグレースケールの輝度値を計算しています。
上記関数の呼び出し
main.go
package main import ( "syscall/js" "go-webassembly/webassembly/analyze_image" "go-webassembly/webassembly/convert_image" ) func main() { c := make(chan struct{}, 0) js.Global().Set("getFileSize", js.FuncOf(analyze_image.GetFileSize)) js.Global().Set("showUploadedImage", js.FuncOf(analyze_image.ShowUploadedImage)) js.Global().Set("convertToBlackAndWhite", js.FuncOf(convert_image.ConvertToBlackAndWhite)) js.Global().Set("convertToSepia", js.FuncOf(convert_image.ConvertToSepia)) <-c }
js.Global().Set
でGoの関数をJSの関数として登録できます。
HTML実装
<!DOCTYPE html> <html> <head> <title>Go WebAssembly</title> </head> <body> <h1>Go WebAssembly</h1> <input type="file" id="fileInput" accept="image/*"> <button id="convertToSepiaButton">選択画像をセピア調に変換</button> <script src="/js/wasm_exec.js"></script> <script> const go = new Go(); WebAssembly.instantiateStreaming(fetch("/wasm/main.wasm"), go.importObject) .then(result => { go.run(result.instance); const fileInput = document.getElementById('fileInput'); const convertToSepiaButton = document.getElementById('convertToSepiaButton'); convertToSepiaButton.addEventListener('click', function() { const file = fileInput.files[0]; if (file) { const reader = new FileReader(); reader.onload = function() { const arrayBuffer = this.result; convertToSepia(arrayBuffer); }; reader.readAsArrayBuffer(file); } }); }) .catch(err => { console.error(err); }); </script> </body> </html>
これでファイルをアップロードしたのちに「選択画像をセピア調に変換」ボタンを押すとセピア調の画像が画面に表示されます。