4
4

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

Unity WebGLでおえかきした画像の保存

Last updated at Posted at 2022-01-19

はじめに

おえかきアプリを作っていて保存機能を実装することになったが、WebGLなのでFile.WriteAllBytes()Application.persistentDataPath使えない…となったので、javascriptプラグインを作ったお話。
※おえかきアプリの実装はメインではないので割愛します。

開発環境

  • Unity 2020.3.25f1
  • Visual Studio Code

プラグインの作成(.jslib)

Unityには、ブラウザのスクリプトと通信を行う方法が大きく2つあります。

  1. Application.ExternalCall()SendMessage()を使う。
  2. プラグイン(.jslib)を作り、コードを記述。
    今回は後者の方法で実装を進めます。
plugin.jslib
mergeInto(LibraryManager.library, {
  ExportTextureJS:function(base64){
    // 手順1
    const image = Pointer_stringify(base64);
  
    // 手順2
    var bin = atob(image.replace(/^.*,/, ''));
    var buffer = new Uint8Array(bin.length);
    for (var i = 0; i < bin.length; i++) {
      buffer[i] = bin.charCodeAt(i);
    }

    try{
      var blob = new Blob([buffer.buffer], {
        type: 'image/png'
      });
    }catch (e){
      return;
    }

    // 手順3
    var url = (window.URL || window.webkitURL);
    var dataUrl = url.createObjectURL(blob);
    
    var a = document.createElement('a');
    a.download = "picture.png";
    a.href = dataUrl;

    a.click();
  },
});

手順1 javascript文字列への変換

const image = Pointer_stringify(base64);

まず引数から入ってきたbase64文字列を、ヘルパー関数Pointer_stringify()を使用してJavaScript文字列に変換します。
これは文字列がemscripten ヒープ内のポインターとして渡されるためです。

手順2 Blob形式への変換

var bin = atob(image.replace(/^.*,/, ''));
var buffer = new Uint8Array(bin.length);
for (var i = 0; i < bin.length; i++) {
  buffer[i] = bin.charCodeAt(i);
}

try{
  var blob = new Blob([buffer.buffer], {
    type: 'image/png'
  });
}catch (e){
  return;
}

変換した文字列をBlob形式のFileに変換します。

手順3 アンカー要素を使用して保存

var url = (window.URL || window.webkitURL);
var dataUrl = url.createObjectURL(blob);
    
var a = document.createElement('a');
a.download = "picture.png";
a.href = dataUrl;

a.click();

HTMLのアンカー要素<a>を作ってdataURLを渡し、クリックイベントを強制的に呼び出します。

プラグイン呼び出し側の実装(C#)

using System;
using UnityEngine;
using UnityEngine.UI;
using System.Runtime.InteropServices;

public class ExportTexture : MonoBehaviour
{
#if UNITY_WEBGL && !UNITY_EDITOR
    [DllImport("__Internal")]
    private static extern void ExportTextureJS(string base64);
#endif

	[SerializeField]
	private RawImage _image;

    public void OnPressExport()
	{
#if UNITY_WEBGL && !UNITY_EDITOR
		var texture = (Texture2D)_image.texture;
		var png = texture.EncodeToPNG();
		var base64 = Convert.ToBase64String(png);

        ExportTextureJS("data:image/png;base64," + base64);
#endif
	}
}

ブラウザでのみ動くため#if UNITY_WEBGL && !UNITY_EDITOR #endifで括っています。

プラグインの関数のインポート

[DllImport("__Internal")]
private static extern void ExportTextureJS(string base64);

jslibに記述した関数を呼び出せるようにしています。
この時、関数名と引数を一致させる必要があります。

テクスチャのbase64文字列への変換、関数の呼び出し

var texture = (Texture2D)_image.texture;
var png = texture.EncodeToPNG();
var base64 = Convert.ToBase64String(png);

ExportTextureJS("data:image/png;base64," + base64);

今回はRawImageを使用しているので、RawImagetextureTexture2Dにキャストして、EncodeToPNG()を使用してbyte[]に変換。さらにそれをSystem.Convert.ToBase64String()に渡してbase64形式に変換しています。

ExportTextureJS()に渡す際に"data:image/png;base64,"と連結しているのは、今回HTMLのアンカー要素に埋め込む手順を踏んでいるためです。(ただここでやる理由はない)

動作物

おわりに

物凄く雑に紹介しましたが、面倒くさいというのが伝わったなら幸いです。
追伸:PlayCanvasならこんなに面倒くさくない

リンク・参考記事など

GitLab
Unity(WebGL)のC#とブラウザのJavaScriptの連携
JS: base64文字列をBlob形式のFileに変換する

4
4
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
4
4

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?