Unityを使っている時にスクリーンキャプチャを取ることが稀に良くあります。その時、いつも使っているのはApplication.CaptureScreenshot()メソッドでしたが、これをiOSで使うとDocuments内に保存されてしまい、カメラロールには保存されません。カメラロールに保存するためには、Documentsに保存した画像のパスを取得し、一度UIImageやNSDataを作成したあと、カメラロールに保存することができます。
しかし、この方法だと、Documentsに保存するまで待たねばならず、保存が完了したコールバックもないので、ファイルの存在を確認するループ処理書く必要があります。そして、保存するときにファイル名をユニークにしなければ、連続で撮影したいときに保存の確認がとれず、しばしば前回撮影した画像を保存してしまうことがありました。また、一時保存としてDocumentsを使用しているため、カメラロールに書き込みが終わったら画像を削除しなければなりません。
これらのことがあり、もっとちゃんと保存できるようにいろいろ試行錯誤した結果、Unity側で画面をTextureで取得し、byteデータにしてネイティブ側に渡すことで、一時保存させずにカメラロールに画像を保存することができました。ちなみに環境はUnity4.6.7f1、Xcode6.4です。
Unity
Unity側では画面をTextureで取得し、byteデータに変換したあと、ネイティブに渡しています。また、ネイティブから書き込み完了のコールバックを受け取るとインディケーターを停止するようにしています。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 |
using UnityEngine; using System; using System.Collections; using System.Runtime.InteropServices; public class CaptureScreenshot : MonoBehaviour { [DllImport("__Internal")] private static extern void _PlaySystemShutterSound (); [DllImport("__Internal")] private static extern void _SendTexture (byte[] textureByte, int length); public void CaptureScreenShot () { _PlaySystemShutterSound (); Handheld.SetActivityIndicatorStyle(iOSActivityIndicatorStyle.Gray); Handheld.StartActivityIndicator (); StartCoroutine (TakeScreenshot ()); } private IEnumerator TakeScreenshot() { yield return new WaitForEndOfFrame(); var width = Screen.width; var height = Screen.height; var tex = new Texture2D(width, height, TextureFormat.RGB24, false); tex.ReadPixels(new Rect(0, 0, width, height), 0, 0); tex.Apply(); byte[] screenshot = tex.EncodeToPNG(); _SendTexture (screenshot, screenshot.Length); } void DidImageWriteToAlbum (string errorDescription) { Handheld.StopActivityIndicator (); } } |
iOS
iOS側では、送られてきたbyteデータを、まずNSDateに変換し、NSDataをUIImageへ変換しています。変換したUIImageはUIImageWriteToSavedPhotosAlbumを使用し、カメラロールに保存しています。CaptureCallbackは、UIImageWriteToSavedPhotosAlbumの第三引数でコールバックを受け取るためにだけに使用しています。本来ならエラーログを出すべきですが、今回はそこまで重要ではないので省いています。また、これらのファイルはPlugins/iOSに配置し、ビルドした時に同時に吐出されるようにしています。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
#import <Foundation/Foundation.h> #import <AssetsLibrary/AssetsLibrary.h> #import <AVFoundation/AVFoundation.h> extern "C" void _PlaySystemShutterSound () { AudioServicesPlaySystemSound(1108); } extern "C" void _GetTexture (const void* byte, int length) { NSData *data = [NSData dataWithBytes:byte length:length]; UIImage *image = [[UIImage alloc]initWithData:data]; CaptureCallback *callback = [[CaptureCallback alloc]init]; UIImageWriteToSavedPhotosAlbum(image, callback, @selector(savingImageIsFinished:didFinishSavingWithError:contextInfo:), nil); } |
1 2 3 4 5 6 7 |
#import <Foundation/Foundation.h> @interface CaptureCallback : NSObject - (void) savingImageIsFinished:(UIImage *)_image didFinishSavingWithError:(NSError *)_error contextInfo:(void *)_contextInfo; @end |
1 2 3 4 5 6 7 8 9 10 |
#import "CaptureCallback.h" @implementation CaptureCallback - (void) savingImageIsFinished:(UIImage *)_image didFinishSavingWithError:(NSError *)_error contextInfo:(void *)_contextInfo { UnitySendMessage("CaptureScreenShot", "DidImageWriteToAlbum", ""); } @en |
おわりに
今回作成したプログラムで今までずっとモヤモヤしていたものが解消されました。byteデータをやりとりするので、動作速度がどうなるか不安でしたが、結果的には全くストレスなく動作しました。むしろ今までのプログラムのほうが、書き込み待ちをしている分若干遅かったように思います。以前作ったアプリを次のイベントに向けてアップデートしようとしていた時にこの改修ができたので良かったです。
では、また。