KatsuYuzuのブログ

.NET の開発をメインとした日記です。

ASP.NETとImageMagickで動的画像サーバーを作る #aspnetjp

この記事はOne ASP.NET Advent Calendar 2013 - Adventarの9日目の記事です。遅くなって大変申し訳なく。

ASP.NETとImageMagickで画像サーバーを作る

ImageMagickってなんぞやって方もいらっしゃるかと思いますが、とても有名な画像変換ツールです。

ImageMagick(イメージマジック)は画像を操作したり表示したりするためのソフトウェアスイートである。

ImageMagick - Wikipedia

僕とImageMagickとの出会いは、Silverlight(遠い目)。Silverlightのハイパー素敵機能DeepZoomの基ファイルを図面ファイルから生成したり、システムのファイルのサムネイル作ったり、ラジバンダリ。

運用

何らかのファイルをアップロード→サムネイルを複数解像度で作成→ストレージに保存→何らかで参照→偉い方「@KatsuYuzu君、これもっと大きくならん?」→DBからオリジナルファイル出してきて、しこしこ変換。

実にだるい

動的にやりたいよねって調べてたら、まさにクックパッドさんがTOFUなんて素敵システム作ったりしてました。

tofuは動的にサムネイル生成をするシステムです。動的と聞くと遅そうに感じる人もいるかも知れませんが、意外といけます。しかもImageMagickで。

私がクックパッドの画像配信野郎です - 昼メシ物語

ImageMagick

今回はお手軽にImageMagickの実行ファイルを呼び出して、出力されたファイルを参照してしまいます。ImageMagick自体は下記のようなコマンドで実行可能です。

convert [オプション] 基ファイル 出力ファイル

オプションは本当に、本当に多いので、割愛。ただ、一つ、とても大事なポイントが。

いつもの convert に「-define jpeg:size=...」をつけるだけで10倍速くなる

本当は速いImageMagick: サムネイル画像生成を10倍速くする方法 - 昼メシ物語

基ファイルがJPEGの場合にのみ、あらかじめ画像を小さく開くことで省メモリーで高速化されます。小さく開いてもJPEGのアルゴリズム的に問題ないよううまくやってくれるようです。

ASP.NET

今回は事例紹介のようなものなのでコードは必要な要素を散りばめてあるだけです。
コントローラー。システム上でファイルを扱っているIDとサイズを受け取って変換、出来上がったファイルを返します。

public class ImageController : Controller
{
    public ActionResult Thumbnail(string fileId, int width, int height, string fileName)
    {
        // 基ファイル
        var sourceFilePath = Path.Combine(Server.MapPath("~/Content"), fileId + ".jpg");
        // 出力ファイル
        var resultFilePath = Path.Combine(Server.MapPath("~/App_Data"), fileName);

        // ImageMagick のパス
        var tool = @"C:\Program Files\ImageMagick-6.8.7-Q16\convert.exe";

        // ImageMagick への引数
        var arguments = string.Format(
            @"-define jpeg:size={2}x{3} -resize {2}x{3} ""{0}"" ""{1}""",
            sourceFilePath,
            resultFilePath,
            width,
            height);

        // コマンドの実行
        ExecuteCommand(tool, arguments);

        // 結果
        return File(resultFilePath, "image/jpeg");
    }

    // コマンドの実行(コマンドプロンプトに引き渡すだけ)
    private static void ExecuteCommand(string tool, string arguments)...
}

ルーティング。URL にコントローラーとアクションが登場しない場合は defaults での指定が必要です。

routes.MapRoute(
    name: "Image",
    url: "{fileId}/{width}x{height}/{fileName}",
    defaults: new { controller = "Image", action = "Thumbnail" }
);

ビュー。適当に欲しいサイズで。

<div class="row">
    <img src="~/image1/320x240/image1-320-240.jpg" />
    <img src="~/image1/640x480/image1-640-480.jpg" />
    <img src="~/image1/800x600/image1-800-600.jpg" />
</div>

結果

f:id:KatsuYuzu:20131224232932j:plain
かわゆすなあ。

まとめ

やるだけであれば結構簡単にできちゃいます。ImageMagick便利。
ここからの発展では下記のようなことを考慮して、規模に合わせて*1運用していく必要がありますね。

  • 変換の非同期化
  • キャッシュ

追記 23:30

ああっと、お手軽紹介だけでストリームの紹介を忘れてました。
ImageMagickのコマンドは"jpg:-"*2とすることでStandardInputStreamにもStandardOutputStreamにも対応しているので、もっとおいしく使えますよ。

追記 12/11

続きを書きました。
nugetでインストールして.net wrapperから動かします。事前のインストールやコマンドライン実行などが不要となっています。
ASP.NETとImageMagickで動的画像サーバーを作る その2 #aspnetjp - KatsuYuzuのブログ

*1:規模次第ではストレージに気を付けてクリア機構を用意すれば、この記事のでも十分。

*2:形式:-