MagickNetでアニメーションGIFを分解してみよう

前回はMagickNetでアニメーションGIFを作りました。

今回は、作ったアニメーションGIFをフレームごとに分解して一つ一つのgifファイルとして保存します。
MagickNetだと、かなり簡単にできるようになっていますので、いきなりサンプルコードを載せます。

普段のMagickImageではなくMagickImageCollectionとしてファイルを読み込ませるだけです。
それだけで各フレームがMagickImageのインスタンスとしてコレクションされているので、1つずつファイルに保存するだけです。

        private static void GIFアニメーションからフレームごとにファイルを分割するよ()
        {
            var path = System.IO.Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.Desktop), "animation.gif");

            //アニメーションGIFをMagickImageCollectionとして読み込む
            using (var imageCollection = new MagickImageCollection(path))
            {
                var frameNo = 1;
                foreach (var image in imageCollection)
                {
                    //コレクション中の要素を保存する
                    var outputPath = System.IO.Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.Desktop), string.Format("frame{0}.gif",frameNo++));
                    image.Write(outputPath);
                }
            }
        }

すると結果として以下のように各フレームが単体のGIFファイルとして出力されます。
このように、簡単に分解することが可能です。

一部だけ載せます。(frame1から4)

frame1
frame2
frame3
frame4

Share

MagickNetでGIFアニメーションを作ろう

前回は9分割した画像を合成してもとに戻すというシナリオで画像合成について説明しました。

今回はGIFアニメーションを作成してみましょう。
シナリオとしては、前回まで使っていた
sample-mini
を9分割し、600ms間隔で各パーツを表示し
最後のパーツが表示されてから600ms後に合成されたものを表示するというGIFアニメーションを作成します。

今回は各フレームをMagickImageであらわし、それを束ねてアニメーションを行うためMagickImageCollectionというクラスを利用します。
MagickImageCollectionに複数のMagickImageを束ねて出力することでGIFアニメーションを作成することができます。

事前に今回3つのポイントについて説明します。

一つめは以下のコンストラクタを使ってまとめて登録することが可能です。

using(var collection in new MagickImageCollection(IEnumerable<MagickImage> images))
{
 //処理
}

追記:初出時、60msと誤記していましたが、600msの間違いでした。既に訂正済みです。

また、600msの間隔はcollectionで指定するのではなく、各フレーム用のMagickImage側で指定します。
以下のプロパティに値を設定します。
単位は1/100secで1です。つまり60と指定すると600msとなります。
前のフレームに対して600ms間隔をあけて表示されます。

MagickNet.AnimationDelay = 60;

最後に各フレーム間の差があるところだけを使うことでファイルを最適化するために以下のメソッドを使います。
MagickImageCollectionに用意されています。

MagickImageCollection.Optimize();

また、今回は分割した結果はファイルではなくバイト配列で持ちます。
上記を踏まえてサンプルコードは以下のようになります。

        private static void GIFアニメーションを作るよ()
        {
            var path = System.IO.Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.Desktop), "sample.jpg");
            byte[] byteArray = null;
            var width = 0;
            var height = 0;
            using (var image = new MagickImage(path))
            {
                //リサイズ他にもスケールする%を渡したりなど、いろいろできるので今回は20%指定で
                image.Resize(0.2);

                //リサイズ後のサイズを取得
                width = image.Width;
                height = image.Height;

                //pngを指定するよ
                image.Format = MagickFormat.Png;

                //バイト配列に出力
                byteArray = image.ToByteArray();
            }

            //今回は分割した部分はバイト配列で持っておきます
            var images = Enumerable.Range(0, 9).Select(i =>
            {
                using (var divImage = new MagickImage(byteArray))
                {
                    var x = width / 3 * (i % 3);
                    var y = height / 3 * (i / 3);

                    divImage.Crop(new MagickGeometry(x, y, width / 3, height / 3));

                    return new { index = i, image = divImage.ToByteArray() };
                }
            }).ToDictionary(x => x.index, x => x.image);

            //MaigckImageを束ねるコレクションに9分割したそれぞれの部分をまとめて登録します
            using(var collection = new MagickImageCollection(
                Enumerable.Range(0, 9).Select(i =>
                {
                    var canvas = new MagickImage(MagickColor.Transparent, width, height);
                    using (var divImage = new MagickImage(images[i]))
                    {
                        var x = width / 3 * (i % 3);
                        var y = height / 3 * (i / 3);
                        //ここで合成しているよ。
                        canvas.Composite(divImage, x, y, CompositeOperator.Over);
                    }
                    //ここで600msずつ遅らせることを指定します
                    canvas.AnimationDelay = 60;
                    return canvas;
                })))
            {
                
                //すべての部分が表示されるコマを作成します
                var canvas = new MagickImage(MagickColor.Transparent, width, height);
                foreach(var i in Enumerable.Range(0, 9))
                {
                    using (var divImage = new MagickImage(images[i]))
                    {
                        var x = width / 3 * (i % 3);
                        var y = height / 3 * (i / 3);
                        //ここで合成しているよ。
                        canvas.Composite(divImage, x, y, CompositeOperator.Over);
                    }
                    //ここでも600ms遅らせることを指定します
                    canvas.AnimationDelay = 60;
                }
                collection.Add(canvas);

                //保存するまえにAnimationGifにする時各コマの差分だけがCropされてフレームになるように最適化を行います。
                collection.Optimize();
               
                var outputPngPath = System.IO.Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.Desktop), "animation.gif");
                collection.Write(outputPngPath);
            }

        }

そして結果は以下のようになります。

animation

このように、MagickNetでは少ないコード量でGIFアニメーションを作ることが可能です。

追記:
ちなみに、MagickImage側でAnimationDelayするならCollectionじゃなくても前回までのMagickImage.Compositeでいいのでは?と思うかも(思わないかも)しれませんが、その指定は無視されてただの合成画像になるだけです。

Share

MagickNetで画像を合成してみよう

前回は画像の切り取りかたについて説明しました。

復習

今回は、復習として、いつもの画像を9分割にして保存してみましょう。

リサイズ後の元画像は以下の通り。
sample-mini

サンプルは以下の通りです

        private static void リサイズして9分割()
        {
            var path = System.IO.Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.Desktop), "sample.jpg");
            byte[] byteArray = null;
            var width = 0;
            var height = 0;
            using (var image = new MagickImage(path))
            {
                //リサイズ他にもスケールする%を渡したりなど、いろいろできるので今回は20%指定で
                image.Resize(0.2);

                //リサイズ後のサイズを取得
                width = image.Width;
                height = image.Height;
    
                //pngを指定するよ
                image.Format = MagickFormat.Png;

                //バイト配列に出力
                byteArray = image.ToByteArray();
            }

            foreach (var i in Enumerable.Range(0, 9))
            {

                using (var divImage = new MagickImage(byteArray))
                {
                    var x = width / 3 * (i % 3);
                    var y = height / 3 * (i / 3);
                    //ここで画像を切り取っているよ
                    divImage.Crop(new MagickGeometry(x,y,width / 3,height/3));

                    var outputPngPath = System.IO.Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.Desktop), string.Format("image{0}.png", i));
                    divImage.Write(outputPngPath);
                }
            }
        }

今回一度リサイズしpngにした後の結果をファイルに保存するのではなく、

                //バイト配列に出力
                byteArray = image.ToByteArray();

とすることで、そろぞれを9分割して保存するときに、毎回元ネタの画像を読むのではなく既に読み込んであるバイト配列を以下のように後続のMagickImageで利用することができています。

                using (var divImage = new MagickImage(byteArray))
                {

分割後の結果は以下のようになります。

image0 image1 image2
image3 image4 image5
image6 image7 image8

本題

復習はここまでで、今回の本題合成に入ります。

題材として上の分割した画像を合成して、元の画像に戻します。

合成する方法はいくつかありますが今回紹介するのは以下のメソッドを利用した方法です。
オーバーロードが9種類あるのですが、今回は、以下を使います。
MaigckImageInstanceは合成したい画像。
x,yはオフセット、CompositeOperatorは、どうやって合成するのかを指定するEnumとなっています。

    MagickImage.Composite(MaigckImageInstance,x,y,CompositeOperator);

CompositeOperatorに定義されている合成方法には60種類以上があるのですが、今回は特にほかの合成画像と重ねるわけではないのでOverを利用します。

サンプルコードは以下のようになります。
リサイズして9分割した後に、それを復元します。

        private static void リサイズして9分割した後に復元()
        {
            //リサイズして9分割するコードは上記と同じなので省略

            //復元するためのキャンバスを透過色でリサイズ後のサイズで作成します
            using (var canvas = new MagickImage(MagickColor.Transparent, width, height))
            {
                foreach (var i in Enumerable.Range(0, 9))
                {
                    var readPath = System.IO.Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.Desktop), string.Format("image{0}.png", i));
                    //ここで分割した画像を読んでいます
                    using (var divImage = new MagickImage(readPath))
                    {
                        var x = width / 3 * (i % 3);
                        var y = height / 3 * (i / 3);
                        //ここで合成しているよ。分割した画像とオフセット、合成方法を渡しています
                        canvas.Composite(divImage, x, y, CompositeOperator.Over);
                    }
                }

                var outputPngPath = System.IO.Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.Desktop), "image-merge.png");
                canvas.Write(outputPngPath);
            }
        }

すると結果は以下のようになります。
image-merge

このように、合成も簡単にできます。
Overのほかにも60種類上の合成方法(オーバーレイしてみたりなど)がありますのでいろいろなCompositeOperatorを与えてみて確認してみるのも面白いと思います。

Share

MagickNetで画像を切り取ってみよう

前回は、画像の回転と画像フォーマットの変更について説明しました。

今回は、画像を切り取ってみようと思います。

リサイズで20%にした画像を中心を軸に縦横半分ずつ残します。

リサイズしただけの画像
sample-mini

画像を切り取るときにはCropというメソッドを利用し、オーバーロードが3種類あります。

                
//重心を規定し、そこから縦横指定したサイズで切り取る
image.Crop(width, height, Gravity.Center);               
//中心を重心とし、そこから縦横指定したサイズで切り取る
image.Crop(width, height);               
//平面領域を指定し、そこを残す(下の例だと開始位置と、そこからのサイズを指定している)
image.Crop(new MagickGeometry(x,y,width,height));

上2つは基本的にはGravityによって指定された場所から切り取る指定方法となっている。

では実際に試してみましょう。
以下にそれぞれのサンプルを示します。
両方共結果はおなじになります。

重心を中心に指定して、そこから元サイズの半分ずつ残すサンプル

        private static void リサイズして中央から縦横半分ずつ切り取ってpngで保存()
        {
            var path = System.IO.Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.Desktop), "sample.jpg");
            using (var image = new MagickImage(path))
            {
                //リサイズ他にもスケールする%を渡したりなど、いろいろできるので今回は20%指定で
                image.Resize(0.2);

                //リサイズ後のサイズを取得
                var width = image.Width;
                var height = image.Height;

                //中心から半分ずつ残して画像を切り取る
                image.Crop(width / 2, height / 2, Gravity.Center);               
                
                //pngを指定するよ
                image.Format = MagickFormat.Png;

                var outputPngPath = System.IO.Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.Desktop), "sample-mini-cropGravity.png");
                image.Write(outputPngPath);

            }
        }

結果
sample-mini-cropGravity

領域を指定して切り取るサンプル

        private static void リサイズしてポジション指定で縦横半分ずつ切り取ってpngで保存()
        {
            var path = System.IO.Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.Desktop), "sample.jpg");
            using (var image = new MagickImage(path))
            {
                //リサイズ他にもスケールする%を渡したりなど、いろいろできるので今回は20%指定で
                image.Resize(0.2);

                //リサイズ後のサイズを取得
                var width = image.Width;
                var height = image.Height;

                //中心から半分ずつ残して画像を切り取る
                image.Crop(new MagickGeometry(width / 4, height / 4, width / 2, height / 2));


                //pngを指定するよ
                image.Format = MagickFormat.Png;

                var outputPngPath = System.IO.Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.Desktop), "sample-mini-cropGeometry.png");
                image.Write(outputPngPath);

            }
        }

結果
sample-mini-cropGeometry

というように、ケースに応じて、指定方法を使い分けて画像を切り取ることができます。

というわけで今回は画像の切り取りについて説明してみました。

Share

MagickNetで画像の回転と画像フォーマットの変更をしよう

前回のMagickNetことはじめに続いて、画像を回転させてみましょう。

あ、あけましておめでとうございます。今年もよろしくお願いします。

前回はリサイズで20%のサイズにしました。
今回はそれに加えて画像を45°傾けてみましょう。

とりあえずjpegのままで保存します。
コードとしては以下のように前回のコードにRotateが追加されただけです。
これに傾けたい度数をdoubleで与えてあげるだけで画像が傾きます。

                image.Rotate(45d);

サンプル:

            var path = System.IO.Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.Desktop), "sample.jpg");
            using (var image = new MagickImage(path))
            {
                //リサイズ他にもスケールする%を渡したりなど、いろいろできる。
                image.Resize(387, 518);
                //ついでに45度傾けてみようか
                image.Rotate(45d);

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

            }

でできるのが以下の画像
45°傾けたので、全体のサイズが大きくなり、新しくできた背景の部分は白くなっています。(わかりやすくするためにブログの背景を一部、黒にしています)

sample-mini-rotate45

できれば、新しくできた背景は透過にしたいので、pngに変換して保存してみます。
変換するときは、以下のようにFormatプロパティに変換したいファイルフォーマットを指定するだけです。

                //pngを指定するよ
                image.Format = MagickFormat.Png;

サンプル:

            var path = System.IO.Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.Desktop), "sample.jpg");
            using (var image = new MagickImage(path))
            {
                //リサイズ他にもスケールする%を渡したりなど、いろいろできる。
                image.Resize(387, 518);
                //ついでに45度傾けてみようか
                image.Rotate(45d);

                //pngを指定するよ
                image.Format = MagickFormat.Png;

                var outputPngPath = System.IO.Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.Desktop), "sample-mini-rotate45.png");
                image.Write(outputPngPath);

            }

で以下のようにPNGで保存されています。
が、新しい背景の部分は依然白いままです。
無駄にファイルサイズだけ大きくなった感じですね。

sample-mini-rotate45

ではちゃんと回転後新しい領域が透過になるようにしてみます。
こちらも簡単で、回転等の操作をする前にBackgroundColorプロパティにMagickColor.Transparentを指定するだけです。

                image.BackgroundColor = MagickColor.Transparent;

サンプル:

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

                //背景色を透過に設定しておく
                image.BackgroundColor = MagickColor.Transparent;

                //ついでに45度傾けてみようか
                image.Rotate(45d);

                //pngを指定するよ
                image.Format = MagickFormat.Png;

                var outputPngPath = System.IO.Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.Desktop), "sample-mini-rotate45-transparent.png");
                image.Write(outputPngPath);

            }
sample-mini-rotate45-transparent

次はちゃんと新しい領域が透過された画像になりました。

というわけで、今回は画像の回転と画像フォーマットの変更を合わせて説明してみました。
このように、簡単に使えるのがよいところですね。

Share

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

[C#][Web]Unicode絵文字とキャリア絵文字そしてSystem.Globarization.StringInfo.GetTextElementEnumeratorメソッド

今更感漂うのですが8月のこみゅぷらす勉強会で話した時に使った資料をブログで公開するの忘れていたので公開しておきます。

Unicode絵文字とキャリア絵文字について+で、実際に相互変換はどうしているのかというあたりをザックリと書いてあります。
C#タグがついているのは相互変換をやるのにC#を使っているからです。

[iframe src=”http://www.slideshare.net/slideshow/embed_code/24674603″ width=”427″ height=”356″ frameborder=”0″ marginwidth=”0″ marginheight=”0″ scrolling=”no” style=”border:1px solid #CCC;border-width:1px 1px 0;margin-bottom:5px” allowfullscreen] [/iframe]

結論としては

・この中で使っているStringInfoクラスを使うと、サロゲートペアなども1文字としてカウントして排出してくれるので便利ですねーという話
・結局もっとも融通の聞かないキャリアの絵文字に引きずられるので早くUnicode絵文字だけになってほしいという話

最後に簡単にStringInfoクラスのGetTextElementEnumeratorメソッド便利という一例をサンプルコードでいかに示します。

例によって、Chaining Assertion for MSTestを利用しています。

            //合字を表現する2文字分のUTF32文字コードを表現したバイト列からstring型にする
            var value = System.Text.Encoding.UTF32.GetString(new byte[] { 0x23, 0x00, 0x00, 0x00, 0xE3, 0x20, 0x00, 0x00 });

            //もちろん長さは2文字
            value.Length.Is(2);

            //しかし、System.Globarization.StringInfoクラスのGetTextElementEnumeratorメソッドに食わせると・・・
            var enumerator =  StringInfo.GetTextElementEnumerator(value);

            var list = new List<string>();

            while (enumerator.MoveNext())
            {
                //合字を一つの単位として表現するためstring型
                list.Add(enumerator.Current as string );
            }

            //結果、ちゃんと一つの単位として出力されるので、サロゲートペアや、合字で表現されるような一部のUnicode絵文字の処理などに利用している
            list.Count.Is(1);                

追記:docomoさんも、スマートフォンの絵文字の相互運用で悩むくらいならせめてスマートフォンはキャリア絵文字捨てませんかね #ガラケーという遺産が・・・

Share