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

phpのpreg_replace_callbackをc#で書くと・・・ #php #csharp #memo

PHPにpreg_replace_callbackという関数があります。

正規表現検索を行い、コールバック関数を使用して置換を行う
http://php.net/manual/ja/function.preg-replace-callback.php

というものです。

これをC#でやるときは、

System.Text.RegularExpressions.Regex.Replace メソッド (String, String, MatchEvaluator)
http://msdn.microsoft.com/ja-jp/library/ht1sxswy.aspx

を利用します。

第1引数が入力文字列で、
第2引数が検索のための正規表現文字列です。
第3引数のMatchEvaluatorデリゲートの中でマッチした部分文字列に対して置換処理を行います。

preg_replace_callbackのマニュアルページの例2にあるサンプルコードをcsで表すとすると以下のようになります。
参考までにPHP側のコードを冒頭にコメントで乗っけています

Regex.Replaceメソッドの使い方のほんの一例です(そもそもreplaceメソッドのオーバーロードの一つでしかない。
ので、詳細は上記MSDNで確認してください。

//  <?php
//// このテキストは 2002 に使われていたものなのですが、
//// これを 2003 年対応の日付に変更したいのです
//$text = "エイプリルフールの日付は 04/01/2002 ですn";  
//$text.= "この前のクリスマスの日付は 12/24/2001 でしたn";  
//// コールバック関数
//function next_year($matches)
//{
//  // 通常は、$matches&#91;0&#93; がマッチした全体を表します。
//  // $matches&#91;1&#93; は、マッチした中で、パターン内の最初の '(...)'
//  // にあてはまる部分を表します。それ以降も同様です。
//  return $matches&#91;1&#93;.($matches&#91;2&#93;+1);
//}
//echo preg_replace_callback(
//            "|(d{2}/d{2}/)(d{4})|",
//            "next_year",
//            $text);

//?>

            var text = @"エイプリルフールの日付は 04/01/2002 です
この前のクリスマスの日付は 12/24/2001 でした";

            
            var replacedText = Regex.Replace(
                 text,
                @"(d{2}/d{2}/)(d{4})",
                //x.Valueおよびx.Groups[0]で部分文字列全体
                //x.Groups[1以降]は上記席表現の()で括られた部分にあたる部分文字列
                 x =>
                 {
                     return x.Groups[1].Value + (Convert.ToInt32(x.Groups[2].Value) + 1);
                 });

            Console.WriteLine(replacedText);

余談ですが、VS2013を使って、debug中にunhandledな例外が発生した時ローカル変数用のウォッチウィンドウに$exceptionという名前で例外情報が表示されるのが地味に良いと思いました。

Share

SelectManyでreturn nullはご法度 #linq #csharp 訂正あり

久しぶりのブログということで、細かいことを少しだけ。

編集:指摘に合わせてサンプルコードを初出に比べて一部修正してあります。
6/2:nullで例外を返すサンプルがnullを返さなくなっていたので修正

LINQに実装されている拡張メソッドの一つにSelectMany
というものがあり作用としては、Selectと同じ作用+配列の平坦化を行なってくれるという活用しどころの多い拡張メソッドの一つです。

具体例としては以下の様なテストコードのような動作をします。
(Stringの配列の配列を平坦化してstringの配列にしてくれます。)
※テストコードはMSTest+Chainning Assertionを利用

        [TestMethod]
        public void TestMethod1()
        {
            var values = new[] { new string[] { "a", "b","c"},
                                 new string[] { "d" }, new string[] { "e" } };

            values.SelectMany(x => x).Is("a","b","c","d","e");               
        }

こっからが本題なのですがそのSelectManyの中のラムダでnullを返すとNullReferenceExceptionが発行されます。

先ほどの例をベースに配列の中に”b”が含まれている時はnullを返す実装例を示します。
※テストコード中では例外が投げられることを期待しているためcatch句に入ったら成功としています。

        [TestMethod]
        public void TestMethod2()
        {
            var values = new[] { new string[] { "a", "b","c"},
                                 new string[] { "d" }, new string[] { "e" } };

            try
            {
                //SelectManyでnullをリターンすると落ちるはず。
                var outputValues = values.SelectMany(x => x.Contains("b") ? null : x).ToArray();
            }
            catch (Exception ex)
            {
                Assert.IsTrue(true);   
            }

        }

上記のような実装をした時、nullがreturnされるタイミングでNullReferenceExceptionが発行されます。

ので、上記のような要件の時は、以下のようにそもそも対象外のものを事前に除外しておくか、

        [TestMethod]
        public void TestMethod3()
        {
            var values = new[] { new string[] { "a", "b","c"},
                                 new string[] { "d" }, new string[] { "e" } };

            values.Where(x => !x.Contains("b")).SelectMany(x => x).Is( "d", "e" );
        }

以下のようにnew string[]{}を返すかする必要があります。

        [TestMethod]
        public void TestMethod4()
        {
            var values = new[] { new string[] { "a", "b","c"},
                                 new string[] { "d" }, new string[] { "e" } };

            values.SelectMany(x => x.Contains("b") ? Enumerable.Empty<string>():x).Is( "d", "e");
        }

まぁどちらかといえば、可能なら事前に除外しておくのが正しいと思います。

Share

クールなスマートフォン向けWebアプリケーションを作るなら「jQuery Mobile」かも?

jQueryは前からかなり便利に利用させていただいている。
jQueryを使うようになってからJavaScriptの見通しがよくなったし、本当に簡単なコードでいろいろできるようになった。
また、JQueryにはいろんなプラグインがあり、それぞれがかなり便利に活用できるようになっている。そんなJQueryプラグインからモバイルアプリケーション用のライブラリであるjQuery Mobileというスマートフォン向けのWebアプリケーションを構築するためのプラグインが登場したようだ。

jQuery Mobileとは、jQueryのプラグインとして利用するモバイルアプリケーション用のライブラリです。そしてその驚異的なまでの簡単さは、これから業務用のモバイルアプリケーションの作り方を一変させてしまう可能性を感じさせます。

引用元: 「jQuery Mobile」の登場で、モバイルアプリケーション開発は大きく変わる - Publickey.

上記のページをざっと読んだ感じだと、ルックアンドフィールもシンプルで統一感があり、触って気持よさそうなデザインとなっている。
jQueryの強力なライブラリにプラグインすることで、あらゆるスマートフォン向けのアプリケーション構築が加速するのでは無いだろうか。

ちょっと、これは導入しないわけにはいかないですね。

Share

.NET Framework 4のSmtpClientで3MBを超える添付ファイルを送信しようとすると落ちる。そしてHotfixがあった。

.NET Framework 4のSmtpClientで3MBを超える添付ファイルを送信しようとするとBase64のEncodingをするところで、IndexOufOfRangeExceptionが発生して落ちる。

なんでかなーと思っていろいろ調べていたら、こんなブログにたどり着く。

ASP.NET 4.0 SMTP throws exception when there is an email attachment over 3MB

そして、Hotfixがここにあるらしい。

http://connect.microsoft.com/VisualStudio/Downloads/DownloadDetails.aspx?DownloadID=30226

まだ、ダウンロードして試していないけど。

KB的には、このページ

FIX 電子メールの添付ファイルが 3 MB より大きい場合は、System.Net.Mail.SmtpClient クラスを使用して、.NET Framework 4. 0 ベースのアプリケーションを使用して電子メール メッセージを送信するときに メールの送信エラー エラー メッセージ

Share
カテゴリー: .NET

NPOIのEXCEL書式設定

NPOIでEXCELの該当のセルに書式設定したとき、想定されたものと違う書式が設定されていて調査した結果わかったこと。

通常、NPOIでEXCELの書式を設定する時、NPOIに用意されている規定の書式じゃない場合CellStyleに

[cs]
GetFormat(書式);
[/cs]

の戻り値を設定することでセルの書式を設定できるんだけど、

与えた書式が規定の書式だった場合、規定の書式が登録されている

値が戻り値に設定されていることが期待される。

しかし、与えた書式が規定の書式だった場合、(規定の書式が登録されている値-1)が

戻り値に設定されていることがわかった。

そのため、規定の書式としては、ひとつずれた書式が設定されていることがわかった。

ここはたぶん、

[cs]
GetBuiltinFormat(書式);
[/cs]

で、規定の書式なのかどうかを調べてから、GetFormatするのが正しそうだ。

GetBuiltinFormatは、正しい値を返してくれる。

Share
カテゴリー: .NET