MagickNetことはじめ

What’s MagickNet.

MagickNetは画像処理ライブラリとして有名なImageMagickのC++ API Magick++をラッピングしてManaged Objectとして.NET Framework上で利用できるようにしたものです。

Getting Started.

NuGet Galleryに登録されているので、そこから利用することが可能となっていますが、色の分解能、アプリケーションが32bitか64bitによって以下の4種類に分けられています。
16bit per pixelは8bit per pixelのものよりリソースを余分に使います。

16bit per pixel

  • (x86向け)Magick.NET-Q16-x86
  • (x64向け)Magick.NET-Q16-x64

8bit per pixel

  • (x86向け)Magick.NET-Q8-x86
  • (x64向け)Magick.NET-Q8-x64

では、使ってみましょうか。
適当な画像をリサイズして保存するというところまで行ってみたいと思います。

以下の画像の原寸が1936 × 2592で、これを0.2倍の387×518のサイズにリサイズします。
IMG_0619

追記ここから
利用するためには、以下が必要です。
Visual C++ Redistributable for Visual Studio 2012 Update 4

ここまで
Visual Studio 2013を起動し、適当なコンソールアプリのプロジェクトを新規作成し、Package Manager Consoleから

PM> Install-Package Magick.NET-Q16-x86

と入力します。上記パッケージ名は自分の環境に合わせて指定してください。

‘Magick.NET-Q16-x86 6.8.7.901’ をインストールしています。
‘Magick.NET-Q16-x86 6.8.7.901’ が正常にインストールされました。
‘Magick.NET-Q16-x86 6.8.7.901’ を MagickNetSample に追加しています。
‘Magick.NET-Q16-x86 6.8.7.901’ が MagickNetSample に正常に追加されました。

となったら、インストール成功です。

上記の原寸画像を一旦ダウンロードし、デスクトップにsample.jpgという名前で保存します。

Magick.NetはImageMagick名前空間の下に実装されていますので、以下のようにusingしておきます。

using ImageMagick;
namespace プロジェクト名
{

では実際に画像を読み込んでみます。
いろいろ設定がありますが、今回はシンプルな実装でとりあえず事始めしてみたいと思います。

Magick.Net上での画像処理はすべてMagickImageクラスをベースとして行っていきます。
もちろん、UnmanagedなリソースをラップしているのでDisposableです。
ので、usingでくくるかたちでインスタンスを生成します。
MagickImageのコンストラクタにファイルのパスを渡すと、そのファイルを読み込んでMagickImageインスタンスを生成してくれます。
(他にもバイト配列を読み込んだり、読み込むときの設定を一緒に渡したりとかもできる)

            var path = System.IO.Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.Desktop),"sample.jpg");
            using (var image = new MagickImage(path))
            {
            }

これで、MagickImageインスタンスが生成されたので、次にリサイズして保存してみます。
とりあえず保存先は、プロジェクトのルートでsample-mini.jpgという名前にしてみましょう。

                //リサイズ他にもスケールする%を渡したりなど、いろいろできる。
                image.Resize(387, 518);

                var outputPath = System.IO.Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.Desktop), "sample-mini.jpg");
                //これで保存
                image.Write(outputPath);

これで、デスクトップに以下のような画像ができていると思います。
原寸が387×518になっています。
sample-mini

コード全体

using System;
using ImageMagick;
namespace MagickNetSample
{
    class Program
    {
        static void Main(string[] args)
        {
            var path = System.IO.Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.Desktop),"sample.jpg");
            using (var image = new MagickImage(path))
            {
                //リサイズ他にもスケールする%を渡したりなど、いろいろできる。
                image.Resize(387, 518);

                var outputPath = System.IO.Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.Desktop), "sample-mini.jpg");
                //これで保存
                image.Write(outputPath);
            }
        }
    }
}

今回はことはじめということで、使えるようになるまでと、簡単なサンプルで実際に使ってみるところまでやってみました。

ハマりどころもなくはないのですが、ImageMagickでできることは基本できるし、ImageMagickのAPIがほぼそのままラップされているので、メソッド名のイメージがつきやすかったりします。
実際に、新しい何かをしたいときはImageMagickのWeb上のリソースが案外役に立ったりします。
System.Drawingでの画像処理に疲れたらMagick.Netを検討してみるのも良いのではないでしょうか?

Share

映画と寿司

寿司 Advent Calendar 2013 23日目の記事です。

そして、18日から続いた弊社達による寿司 Advent Calendar Jackの最終日です。

映画

寿司好きなら一度は訪れてみたい寿司屋があります。 銀座 すきやばし次郎 です。

今年で6年連続ミシュラン三ツ星をいただいています。 今日は、そんな寿司屋「銀座 すきやばし次郎」に行ってきた・・・わけではなく、
映画二郎は鮨の夢を見る について話したいと思います。

時間にしてだいたい1時間半、すきやばし次郎の小野二郎さんと、その息子に焦点をあてたドキュメンタリー映画です。
2011年にアメリカで公開されています。 作りとしては情熱大陸的で
映画を撮影した時点で小野二郎さんは、すでに80歳を超えているのですが、なお満足せず、美味しい寿司を追及し続ける。
仕事に対して真摯に対峙し続ける。 そんな様が強烈に浮かび上がる作品になっています。

それは、自分自身のみではなく息子たち、弟子たちにも向けられます。
寿司の技を究めるだけではなく、客に美味い寿司を提供する。
客に合わせたシャリの準備、 寿司ネタをネタ一つ一つ最適な温度で保存、 なども含め、そのための仕事ぶりもさすがの一言です。

その分、完全予約制であったり、メニューがおまかせのみなど、客に課されるハードルも高いです。

劇中、おまかせはでクラシック音楽にたとえられ三つの楽章に分かれていると語られ、ある日のおまかせが順番に紹介されます。
まさに、突然の寿司テロ。
クラシック音楽とともに、見ただけでうまいと思える寿司の数々がこれでもかと連発されます。

映画を観終わった後は寿司を食べに行きたくて仕方ありませんでした。(その日はおなかいっぱいだったので無理でしたが・・・)

寿司好きには、ぜひ見てほしい一本です。 Amazonのインスタントビデオでレンタルできます。 または、DVD,Bluerayが購入可能。 海外版もあります。

一度は、実際に味わって、その仕事に感動したいので、いつか絶対行ってやるという気持ちを新たにしました。

本題の寿司

ということで、すきやばし次郎は無理でしたが、本日寿司を食べにいきました。 予定を変更して 意気な寿し処阿部 六本木店に行ってきました。
店の入り口

比較的リーズナブルな割に、おいしいお寿司をはじめとした料理を出してくれるお店で、夜遅く(朝の5時)までやっている、深夜でも使いやすいお店です。
本日はせっかくなので特上を頼んでみました。 12貫+(焼き物or刺身)+お椀というメニューとなっています。
本日もおいしくいただくことが出来ましたのでさっそく振り返ってみます。

まずは焼き魚。
焼き魚
そして、寿司その1
20131223-220824.jpg
20131223-220815.jpg
20131223-220807.jpg

寿司その2
20131223-220833.jpg
寿司その3
20131223-220840.jpg
お椀
20131223-220852.jpg

多くは語りません。うにといくらが大嫌いな私ですが、うにが甘くておいしかったし、いくらも変な癖がなく美味しかった。。。
写真を見ればクオリティがわかると思いますが、このレベルのお寿司がなんと5,000円いかないで食べれてしまうなんて、とても幸せだと思いませんか?

というわけで、これからも私は意気な寿し処阿部に足しげく通うのです。

これで、寿司Advent Calendarの記事は終わりとしたいと思います。

あ、弊社では年がら年中寿司を食べても平気な仲間を絶賛募集中です。

Share

[ASP.NET MVC][C#]Controllerで複数のcheckboxの状態を個々のチェック状態のBooleanではなく、チェックされているものだけの配列を引数で受け取りたい

この記事は One ASP.NET Advent Calendar 2013の20日目の記事です。

複数のinput type=”checkbox”の状態をControllerで受け取るときに、個々のcheckboxの状態をBooleanではなく、チェックされているものだけを配列として受け取りたい。

というテーマです。

シナリオは以下の流れ
1.寿司ネタがチェックボックス付きでリストアップ
2.その中から好きな寿司ネタにチェックを入れる
3.「報告」ボタンを押すと、好きな寿司ネタを報告した結果が表示される(実際には表示するだけで何もしない)

というものです。

データは以下のようになっています。

private Dictionary<string, string> SampleData = new Dictionary<string, string>
{
{"sushi1","まぐろ"},
{"sushi2","はまち"},
{"sushi3","金目鯛"},
{"sushi4","中トロ"},
{"sushi5","赤貝"},
{"sushi6","こはだ"},
{"sushi7","うに"},
{"sushi8","いくら"},
{"sushi9","たまご"},
{"sushi10","ネギトロ巻"}
};

実際に動作させると以下のようになることを想定しています。

/Home/Index
選択肢

報告ボタンを置くと以下のページに遷移
/Home/Result
実行結果

Case1:標準helperを利用した場合

ASP.NET MVCに用意されている標準helperを利用した場合、viewは以下のようになります。

index.cshtml

@model Dictionary&lt;string,string&gt;

@using (Html.BeginForm("Result"))
{
<ul>
<ul>@foreach (var item in Model)</ul>
</ul>
&nbsp;
<ul>
<ul>{
	<li>@Html.CheckBox(item.Key)@item.Value</li>
</ul>
</ul>
}

<input type="submit" value="報告" />
}

result.cshtml

@model Dictionary&lt;string,string&gt;

@using (Html.BeginForm("Result"))
{
<ul>
<ul>@foreach (var item in Model)</ul>
</ul>
&nbsp;
<ul>
<ul>{
	<li>@Html.CheckBox(item.Key)@item.Value</li>
</ul>
</ul>
}

<input type="submit" value="報告" />
}

Controller側は以下のようになります。
本来、checkboxのnameで名前解決して以下のようにできますが、

        public ActionResult Result(bool sushi1,bool sushi2,bool sushi3,bool sushi4,bool sushi5,bool sushi6,bool sushi7,bool sushi8,bool sushi9,bool sushi10)

一連のデータを上のように名前解決するのはありえないので以下のようなコードになってしまいます。

        public ActionResult Result()
        {
                    
            var values = Request.Form.AllKeys
                .Where(x => x.StartsWith("sushi"))
                .Select(x => new {Key = x,Value = Boolean.Parse(Request.Form[x].Split(',').First())})
                .ToDictionary(x => x.Key,x => x.Value);
      
            var result = SampleData
                .Where(x => values[x.Key])
                .Select(x => x)
                .ToDictionary(x => x.Key,x => x.Value);

            return View(result);
        }

名前解決されていないので、いちいちFormから持ってくる必要があったり、自分でBooleanにパースする必要があったりします。
また、標準HelperのHtml.CheckBoxはチェックされていない時も、サーバにfalseを返すために
(本来、input type=”checkbox”はチェックされているものだけを返すのが仕様)
input type=”checkbox”とセットでinput type=”hidden” value=”false”を出力するようになっており、チェックが入っているときは、一つ当たり”true,false”という文字列が値として渡されてきますので、”,”で区切って、最初の値を有効とするようにしなくてはいけない。

などとめんどくさい上にイレギュラーに強くなさそうな状態になります。
Html.CheckBoxは、何かのオンオフのチェックなどの時は、便利ですが、こういうチェックボックスをリストにして、どれを選択しているかを判定するのには向いてません。

では、どうするか

Case2:標準helperを利用しない

身も蓋もないのですが、標準Helperを利用しないで、直に書きます。(Helperを新しく作っても良いと思いますが・・・)

この場合、すべてのcheckboxのnameをsushiとしvalueにkeyをあてます。
以下のようになります。

    @model Dictionary<string,string>

    @using (Html.BeginForm("Result","Home"))
    {
        <ul>
            @foreach (var item in Model)
            {
            <li>
                <input type="checkbox" name="sushi" value="@item.Key" /> @item.Value
            </li>
            }
        </ul>
        <input type="submit" value="報告" />
    }

この場合、submitで返されるのはチェックが入っているものだけになります。

そして、Controller側は以下のようにします。
先ほどのview側で指定したsushiというnameで名前解決され、valueが配列で渡されてきます。

        public ActionResult Result(string[] sushi)
        {
            sushi = sushi ?? new string[]{};                               
            
            var result = SampleData
                .Where(x => sushi.Contains(x.Key))
                .Select(x => x)
                .ToDictionary(x => x.Key,x => x.Value);

            return View(result);
        }

標準ヘルパを使っていた時のような、自前で値をパースして、どうこうなどなどの、面倒なことをすることなく、処理できるようになりました。
チェックが入っていない時用のhiddenが作られることもないので、チェックされているものだけが返ってくることが保証されています。

まとめ

・なんらかの判断をユーザに要求するような(○○はスキップするなど・・・)時は標準ヘルパを使ってbool isSkipなどでcontroller側で受け取るのが便利、フォームの複数回答可などの選択肢に使うと、むしろ煩雑
・フォームの複数回答可などのcheckboxはnameを同じにして、Controller側でstring[] sushiなど配列として名前解決された状態で受け取るのが便利

という感じでしょうか。
小ネタも小ネタですいませんが、以上でOne ASP.NET Advent Calender 2013への投稿の閉めとしたいと思います。

Share

[C#][Memo]Magick.Netの画像読み込みをByteArrayで行う時、並列で動かすと例外が発行される件とその対処

世の中にはMagick.NETというImageMagickをラップした便利なライブラリがあり私も利用しています。

利用している中である条件の時だけ例外が発行されるという事象に遭遇しました。
今回は、どういう状況で発生し、それをどうクリアしたのかという話です。

事象

MagickImageインスタンスのコンストラクションをByteArrayで行う時
特に並列で動かして無いとき正常に動作する。(以下サンプルコード)

    using (var image = new MagickImage(ImageByteArray)) //バイト配列を渡しているよ
    {
       //なんらかの処理
    }

しかしながら、並列で動作させる以下のコードだと、

Parallel.ForEach(Enumerable.Range(1, 100), _ =>
{
    using (var image = new MagickImage(ImageByteArray)) //バイト配列を渡しているよ
    {
       //なんらかの処理
    }
}

下記の例外が発行される。
このイメージフォーマットのデコーダが見つからないよ?と言っているんですね。
Parallelで回さなければちゃんと見つけてきてくれるのに・・・。

MagickMissingDelegateErrorException
Magick: no decode delegate for this image format `’ @ error/blob.c/BlobToImage/358

というわけで・・・

解決まで

調べているうちに、以下のサイトにたどりついた

http://magick.codeplex.com/discussions/447999
Unable to load ICO from a Stream or Byte array

パス指定だと読みこめて、Byte Arrayだと同じ例外が発行されるらしい。
状況が違うのと、今回私が対象にしているのは、ICOファイルではないく、PNGファイルなのだが試してみた。

//パスを指定してみる
Parallel.ForEach(Enumerable.Range(1, 100), _ =>
{
    using (var image = new MagickImage(FilePath))
    {
       //なんらかの処理
    }
}

確かに上記コードだと正常に動作する。
しかしながら、バイト配列で読ませたいの。

ということで、上記たどりついたページを見ると、MagickReadSettings を渡してやれよ。と書いてある。
ので、試してみた。

//パスを指定してみる
Parallel.ForEach(Enumerable.Range(1, 100), _ =>
{
   var readSettings = new MagickReadSettings
     {
         Format = MagickFormat.Png
     };

    using (var image = new MagickImage(ImageByteArray,readSettings)) //バイト配列とMagickReadSettingsを渡しているよ
    {
       //なんらかの処理
    }
}

とすると、確かに正常に動作した。
というわけで、結果、ちゃんと読み込み設定をして渡してあげれば、それをもとにdecodeしてくれるようです。

Share