開発合宿の成果の一部。 後でQiitaにも書こうかなぁ。
Serverless とも銘打ってるけど実質 Lambda の話。
AWS Lambda で C# が使える!よし画像処理だ!
C#(.NET Framework) は System.Drawing
名前空間でグラフィックス機能を提供しています。
これを使うとピクセル単位で画像の操作ができるので、画像処理プログラムを簡単に記述できたりします。
なので、これを使って Lambda 上で画像処理できたらアツいな…と妄想し、 開発合宿でやることとしてこの「AWS Lambda with C# で画像処理してみた」をテーマとして掲げました。
だがしかし
現実はそう甘くなかった。
.NET Core だと System.Drawing
が使えない問題
AWS Lambda 上の C# は .NET Framework
ではなく .NET Core 1.0.1 (C#)
で動いています。
なので、 .NET Framework
では使える機能が使えない、ということが割とあります。
今回使用しようとしていた System.Drawing
もその一つ。
.NET Core
だと利用できないため、画像処理をしたければ別のアプローチを考えなければいけません。
(リファレンスには載ってるんですが、こいつは何者なんでしょう…次期バージョンで使えるのかな
代替案
Nuget で拡張機能を追加すると、それらは自動的にデプロイパッケージに含まれる、とのことなので、
System.Drawing
の代わりになりそうな拡張を探して入れることでとりあえず代替できそうです。
今回見つけたのは "ImageSharp" というもの。
なんとなく画像の鮮明化とかしてくれそうな名前ですがそうではありません。
ImageSharp is a new cross-platform 2D graphics API designed to allow the processing of images without the use of System.Drawing.
System.Drawing
に依存しない画像処理を提供してくれます。
今回の私のような .NET Core で画像処理したいユーザ向け。
"ImageProcessor" という名前のプロジェクトから派生したもの…っぽいですが定かではないです。
使ってみる
代替案が見つかったので実際に使ってみましょう。
ImageSharp のインストール
NuGet で入れればいい…と思っていましたが、まだ正式リリース前のライブラリらしく、公式 NuGet ではインストールできません。
なので、 MyGet という別のサービスのリポジトリを手動でパッケージマネージャに登録してインストールする必要があります。
登録するのにも微妙に詰まったのですが、登録方法は別記事でまとめたいと思っています。
ImageSharp で画像を読み込んでリサイズしてみ…あれ、使えない
ImageSharp を導入すると ImageSharp.Image クラスが使えるようになります。
画像サイズを指定して初期化したり、stream
で初期化したり、 byte[]
で初期化したりできます。
(この辺はgithubでコードを見るか、IntelliSenseで見たほうが早い気がします。)
使い方は簡単で、
using ImageSharp; ~~~ var image = new Image(何か適当なイメージソース)
でインスタンスを作成できます。では実際に読み込んで…と行きたいところですが、実はこのままだと上手くいきません。 以下のようなエラーが出て処理に失敗します。
ImageSharp で画像を読み込むための別パッケージを追加する
公式リポジトリの Readme にちょこっと書いてあるのですが、
The ImageSharp library is made up of multiple packages, to make ImageSharp do anything useful you will want to make sure you include at least one format as a dependency otherwise you will not be able to save/load any images.
とあります。なので最低でも一つは何らかの画像フォーマット用のパッケージを追加でインクルードしておかないと動きません。
最初この記述に気付かずに謎エラーでしばらく悩んでいました… Installationに書いておいてくれ…
とりあえず JPEG と PNG、お好みで BMP とか入れておくといいんじゃないでしょうか。 これらの各種画像フォーマット用追加ライブラリも、上記のMyGetから入れられます。
MyGet(を登録したNuGet)でインストールすると、これらのパッケージは自動的に ImageSharp で使われるようになります。
画像をリサイズしてみる
画像が上記のパッケージのインストールでできるようになったので、実際に画像処理をしてみたい…のですが、 画像処理を行うためのライブラリはまた別に入れる必要があります。
ImageSharp.Processing
というのがその画像処理用のパッケージなので、これもインストールします。
ここまでやってようやく画像処理っぽいことができます。
ImageSharp で画像を読み込んで作成した Image のインスタンスに画像処理用メソッドが追加されています。
リサイズを行う場合は、
resizedImage = image.Resize(x, y)
だけで実現できます。他にもいろいろな処理が実装されていますが、とりあえず今回はリサイズで。
リサイズした画像はそのままでは見えませんので、
resizedImage.SaveAsJpeg(ファイルストリーム的なもの)
で書きだします。Lambdaだったら /tmp
以下の適当な場所に書き出せばいいかと思います。
ちなみに、ここで使っている SaveAs...
メソッドは、他の画像フォーマット用パッケージを入れていれば
それに対応した形式での出力が使える(はず)なので、お好きな形式をご利用ください。
SaveAsHoge
で画像を出力したら、無事リサイズされた画像が見れるようになります。
Lambda的にはこの後、出力した画像をさらにS3に上げたりとかします。
まとめ
まとめると、以下のようなコードで S3からの画像読み込み-> ImageSharpでの画像処理 -> S3へのJPG書き出し が実現できます。
using System.Collections.Generic; using System.Net; using System.IO; using System.Threading.Tasks; using System.Diagnostics; using Amazon.S3; using Amazon.S3.Model; using Amazon.Lambda.Core; using Amazon.Lambda.APIGatewayEvents; using ImageSharp; // Assembly attribute to enable the Lambda function's JSON input to be converted into a .NET class. [assembly: LambdaSerializerAttribute(typeof(Amazon.Lambda.Serialization.Json.JsonSerializer))] namespace LambdaImageConverter { public class Functions { private AmazonS3Client s3Client; /// <summary> /// Default constructor that Lambda will invoke. /// </summary> public Functions() { s3Client = new AmazonS3Client(); } /// <summary> /// A Lambda function to respond to HTTP Get methods from API Gateway /// </summary> /// <param name="request"></param> /// <returns>The list of blogs</returns> public async Task<APIGatewayProxyResponse> Get(APIGatewayProxyRequest request, ILambdaContext context) { context.Logger.LogLine("Get Request\n"); Image testImage = await LoadImage(context); var resizedImage = testImage.Resize(256, 256); resizedImage.SaveAsJpeg(new FileStream("/tmp/lena_resized.jpg", FileMode.Create)); var putReq = new PutObjectRequest { BucketName = "<Bucket名>", Key = "lena_resized.jpg", FilePath = "/tmp/lena_resized.jpg" }; await s3Client.PutObjectAsync(putReq); var response = new APIGatewayProxyResponse { StatusCode = (int)HttpStatusCode.OK, Body = "Hello AWS Serverless", Headers = new Dictionary<string, string> { { "Content-Type", "text/plain" } } }; return response; } private async Task<Image> LoadImage(ILambdaContext context) { var obj = await s3Client.GetObjectAsync("<Bucket名>", "lena.jpg"); Image img = new Image(obj.ResponseStream); return img; } private void Logging(string str, ILambdaContext context) { context.Logger.LogLine(str); } } }
以上、AWS Lambda with C# でも使える画像処理方法の紹介でした。
本当はS3に新しい画像を投げ込んだらそれをトリガーとして、投げ込まれた画像に何らかの処理を施し、 HTTPレスポンスとして変換後の画像を返したり、任意の画像URLから画像を取得してそれに対して画像処理する、 みたいなシステムにしたかったのですが、合宿中にそこまではたどり着かず…
上記の最終目標的なところは、今後の課題としてやっていきたいと思います。
Azure Functions だともっと簡単に出来るかもしれないっぽいし