2017年5月31日水曜日

C# Magick.NETを使ってみる。その2

C# Magick.NETを使ってみる
目次→http://1studying.blogspot.jp/2017/07/c-magicknetmokuzi.html#kuw03


「Magick.NET」の使い方は
https://github.com/dlemstra/Magick.NET/tree/master/Documentation
に載っています

では続きを始めます。


画像の開き方1


「Magick.NET」には、様々な「Magick画像」の開き方が存在します。
ここで改めて整理してみます。

開き方の前に「設定」について先に説明しておきます。
「MagickReadSettings」インスタンスを使えば、「設定」を使って
「Magick画像」を開く事ができます。
ここでは縦横サイズと線を描画した時の太さを先に「設定」しています。
(新規キャンバス作成時、「xc:」と「設定」をセットで使う事が出来ます)
            var myMagickSettings = new ImageMagick.MagickReadSettings();
            //「xc:」が作成するサイズ150×150
            myMagickSettings.Width = 150;
            myMagickSettings.Height = 150;
            myMagickSettings.StrokeWidth = 10;
「myMagickSettings」の「設定」オブジェクトは新規画像を開く際に使用します。
(「MagickReadSettings」クラスは「MagickSettings」クラスを継承しています)
キャンバスを「150*150Pixel」、線の太さを「10Pixel」に「設定」しました。

色々なパターンの画像の開き方の例です。
「myMagickSettings」の「設定」オブジェクトを使い、
新規キャンバスとして「Magick画像」を開く方法も記述してあります。
色々な画像の開き方を確認してみて下さい。
            //「インスタンス時に開く」(背景緑、150*150)
            using (var myMagick = new ImageMagick.MagickImage(new ImageMagick.MagickColor("#00ff00"), 150, 150))
            {
            }
            //「インスタンス時に開く」(画像読み込み)
            using (var myMagick = new ImageMagick.MagickImage("./Lenna.bmp"))
            {
            }
            //「インスタンス時に開く」(背景緑、「設定」)
            using (var myMagick = new ImageMagick.MagickImage("xc:rgb(0,100%,0)", myMagickSettings))
            {
            }

            //「Readで開く」(背景緑、150*150)
            using (var myMagick = new ImageMagick.MagickImage())
            {
                myMagick.Read(new ImageMagick.MagickColor("#00ff00"), 150, 150));
            }
            //「Readで開く」(画像読み込み)
            using (var myMagick = new ImageMagick.MagickImage())
            {
                myMagick.Read("./Lenna.bmp");
            }
            //「Readで開く」(背景緑、「設定」)
            using (var myMagick = new ImageMagick.MagickImage())
            {
                myMagick.Read("xc:rgb(0,100%,0)", myMagickSettings);
                //後から「設定」の部分変更も可能
                myMagick.Settings.StrokeWidth = 5;
            }
ここでは記述していませんが、「ストリーム」や「バイト配列」等から開く事も出来ます。
また、29行目のように、
「myMagick.Settings」を使えば、後から「設定」の変更を行う事も出来ます。
あと、
10行目と27行目で「xc:」を指定していますが、
ここでは「背景色」を意味します。



画像の開き方2(PDFやEPSやAI)


「PDF」や「EPS」や「AI」を「Magick画像」として開く為には、
「Magick.NET」がベクター形式の画像をラスタライズする必要があります。
その為に「Ghostscript」が必要となります。
(「Magick画像」をファイルに保存する際は必要ないみたい。)
「Ghostscript」無しで「PDF」や「EPS」や「AI」を開こうとすると
↓

例外が発生して開けません。

「Ghostscript」のインストール
開けるようにする為に「Ghostscript」をインストールします。
以下のリンク先へ飛びます。「Ghostscript日本語版」
http://www.khotta.org/ghost/index.html
私は2017年5月時点での最新版である、
「 GPL Ghostscript 9.21 (64bit 日本語版)」
「gs921w64full-gpl.exe」
を取得してインストールしました。


「Generate cidfmap for Windows CJK TrueType fonts」は
チェックしたまま「Finish」

「PDF」や「EPS」や「AI」を開く
「AI」ファイルを開き、「RGB、300Dpi、Tiff」で保存してみます。
イラストレーターで使用された効果等によっては必ずしも正しい表示にならない事もあるので、
注意して下さい。(aiファイルは自前で用意して下さい)
            //PDF、EPS、AIのラスタライズ時の設定
            var myMagickSettings = new ImageMagick.MagickReadSettings();
            //ラスタライズ時ICCプロファイル(ICCプロファイル「sRGB.icm」を使用)
            myMagickSettings.ColorSpace = ImageMagick.ColorSpace.sRGB;
            //ラスタライズ時解像度
            myMagickSettings.Density = new ImageMagick.Density(300);
            //ラスタライズ時カラータイプ
            myMagickSettings.ColorType = ImageMagick.ColorType.TrueColor;
            using (var myMagick = new ImageMagick.MagickImage())
            {
                //Gostscriptを使用してsRGBでラスタライズ
                myMagick.Read("test_CMYK_ai.ai", myMagickSettings);
                myMagick.Strip();//解像度変更を反映させる為「EXIF情報」削除
                //解像度を再設定
                myMagick.Density = new ImageMagick.Density(myMagick.Density.X, myMagick.Density.Y);
                //フォーマットをtifに指定
                myMagick.Format = ImageMagick.MagickFormat.Tif;
                myMagick.Write("test_out.tif"); //tifで保存
            }
            MessageBox.Show("処理終了");
「Ghostscript」がインストール済みの為、
問題なく「AI」ファイルを開き、保存する事ができました。


「複数頁PDF」を開く(頁指定も)
「複数頁PDF」を全て単頁「tif」へ変換して保存します。
更に、
「複数頁PDF」を1頁目のみ読み込み「jpg」保存してみます。
(pdfファイルは自前で用意して下さい)
            //PDF、EPS、AIのラスタライズ時の設定
            var myMagickSettings = new ImageMagick.MagickReadSettings();
            //ラスタライズ時ICCプロファイル(ICCプロファイル「sRGB.icm」を使用)
            myMagickSettings.ColorSpace = ImageMagick.ColorSpace.sRGB;
            //ラスタライズ時解像度
            myMagickSettings.Density = new ImageMagick.Density(300);
            //ラスタライズ時カラータイプ
            myMagickSettings.ColorType = ImageMagick.ColorType.TrueColor;
            //「Magick画像」のコレクションを開く
            using (var myMagicks = new ImageMagick.MagickImageCollection())
            {
                //「Gostscript」を使用してsRGBでラスタライズ
                myMagicks.Read("./test_複数頁PDF.pdf", myMagickSettings);
                for (int i = 0; i < myMagicks.Count; i++)
                {
                    //コレクションから「Magick画像」を1頁分取得
                    ImageMagick.MagickImage myMagick = myMagicks[i];
                    myMagick.Strip();//解像度変更を反映させる為「EXIF情報」削除
                    //解像度を再設定
                    myMagick.Density = new ImageMagick.Density(myMagick.Density.X, myMagick.Density.Y);
                    //フォーマットをtifに指定
                    myMagick.Format = ImageMagick.MagickFormat.Tif;
                    //tifで保存
                    myMagick.Write(string.Format(@"./test_out_{0}.tif", i + 1));
                }
            }
            //「複数頁PDF」の頁数が多い場合は、
            //範囲指定を行いラスタライズ化する事もできます。
            myMagickSettings.FrameIndex = 0;//1頁目から読み込み
            myMagickSettings.FrameCount = 1;//1頁分だけ読み込み
            using (var myMagicks = new ImageMagick.MagickImageCollection("./test_複数頁PDF.pdf", myMagickSettings))
            {
                myMagicks[0].Strip();//解像度変更を反映させる為「EXIF情報」削除
                //解像度を再設定
                myMagicks[0].Density = new ImageMagick.Density(myMagicks[0].Density.X, myMagicks[0].Density.Y);
                myMagicks[0].Quality = 75; //品質
                //jpgで保存
                myMagicks[0].Write(@"./test_out_1.jpg");
            }
            MessageBox.Show("処理終了");
コレクションとしてPDFを読み込み、
そこから各頁を「Magick画像」として抽出しています。
コレクションを使わなくてもファイル名の後ろに「[]」で、開きたい頁を直接指定すれば、
PDFの指定ページをいきなり開く事も可能です。
(開く頁の指定は、[0]→1頁目、[1]→2頁目、[2]→3頁目、[3]→4頁目…となります。)
            //PDFの3頁目を「Magick画像として」開く
            using (var myMagick = new ImageMagick.MagickImage("./test_複数頁PDF.pdf[2]"))


「複数頁PDF」への保存
「jpg」を「複数頁PDF」へ保存してみます。
            //「Magick画像」のコレクションを開く
            using (var myMagicks = new ImageMagick.MagickImageCollection())
            {
                myMagicks.Add(new ImageMagick.MagickImage("./test_in_1.jpg"));
                myMagicks.Add(new ImageMagick.MagickImage("./test_in_2.jpg"));
                //「Magick画像」コレクションをそのままPDFへ書き出すと
                //「複数頁PDF」として書き出されます。
                myMagicks.Write("./test_out1.pdf");
            }
            MessageBox.Show("処理終了");


ラスタライズ時のアンチエイリアス設定
            var myMagickSettings = new ImageMagick.MagickReadSettings();
            //ラスタライズ時のアンチエイリアスOFF
            myMagickSettings.StrokeAntiAlias = false;
            myMagickSettings.TextAntiAlias = false;




画像の開き方3(PSDとレイヤー)


フォトショップ側で使用されている効果などによって、
必ずしも正しく表示される訳ではない事に注意して下さい。

PSDを開く(レイヤー含む)
コレクションで開く
            //レイヤー付きPSD画像を
            //「Magick画像」のコレクションで開く
            using (var myMagicks = new ImageMagick.MagickImageCollection("./test_CMYKレイヤー付.psd"))
            {
                for (int i = 0; i < myMagicks.Count; i++)
                {
                    //コレクションから「Magick画像」を1レイヤー分取得
                    ImageMagick.MagickImage myMagick = myMagicks[i];
                    //「背景」にする(アルファを白くします)
                    myMagick.ColorAlpha(ImageMagick.MagickColors.White);
                    //tifで保存
                    myMagick.Write(string.Format(@"./test_out_{0}.tif", i));
                }
            }
            MessageBox.Show("処理終了");
「psd」をコレクションで開いた場合、
・「Magick画像」コレクションの1枚目は、
 「psd」内の「表示レイヤー」全てが統合化されて「Magick画像」化された物、
 が取得できます。
・「Magick画像」コレクションの2枚目以降は、
 「psd」内の「レイヤー」が下から順に「Magick画像」化された物、
 が取得できます。
 (フォルダ内レイヤーの場合も関係なく、下のレイヤーから順に取得)

指定レイヤーを開く
            //レイヤー付きPSD画像の
            //表示レイヤーを統合した「Magick画像」を取得
            using (var myMagick = new ImageMagick.MagickImage("./test_CMYKレイヤー付.psd[0]"))
            {
                //「背景」にする(アルファを白くします)
                myMagick.ColorAlpha(ImageMagick.MagickColors.White);
                //tifで保存
                myMagick.Write(string.Format(@"./test_out_psd.tif", i));
            }
            MessageBox.Show("処理終了");
ファイル名の後ろに「[]」で、開きたいレイヤーを直接指定すれば、
指定レイヤーをいきなり開く事が可能です。
・「[0]」は、
 「psd」内の「表示レイヤー」全てが統合化した「Magick画像」を取得できます。
・「[1]」以降は、
 「psd」内の「レイヤー」を下から数えた指定数値の場所にある「レイヤー」を、
 「Magick画像」として取得します。
 (フォルダ内レイヤーの場合も、関係なく下のレイヤーから順に数えます)


PSDでの保存
・普通のレイヤー画像を保存した場合。
 「L1」という名前の「通常レイヤー」が作られます。
 (PSD内の1つのレイヤーや表示画像をMagick画像」として開いた場合でも、
  PSD保存される画像は、
  「Magick画像」が1つのレイヤー画像として展開された画像となります。)
・統合された画像を保存した場合。
 「L1」と言う名前の「統合画像」になってしまいます。
 通常は「背景」という名前が正しいのですが、
 名前を「背景」に変更する方法が分かりませんでした。
 (tif形式等で保存すれば「背景」という正しい名前になります)



画像の開き方4(-define jpeg)


開く画像が「Jpeg」形式で、なおかつ半分以下のPixelサイズに縮小したい場合のみ、
処理を高速化する方法。
(ImageMagickコマンドの「-define jpeg:size」オプションと同じです。)
            var myMagickSettings = new ImageMagick.MagickReadSettings();
            //「-define jpeg:size」オプション同じ
            //必ずソースとなる「Jpeg画像」より、縮小予定のPixel数がが半分以下である事
            myMagickSettings.SetDefine(ImageMagick.MagickFormat.Jpg, "size", "50x50");
            //「Magick画像」として「Jpeg画像」を開きます
            using (var myMagick = new ImageMagick.MagickImage("./test_RGB.jpg", myMagickSettings))
            {
                //「2分の1」「4分の1」「8分の1」等、
                //指定Pixelの近似値のPixel数で「Magick画像」が開かれます。
                MessageBox.Show(myMagick.Width.ToString() + "\n" + myMagick.Height.ToString());
                //予定通りのPixel数へ縮小します
                myMagick.Resize(new ImageMagick.MagickGeometry(50,50));
                pictureBox1.Image = myMagick.ToBitmap();

                MessageBox.Show(myMagick.Width.ToString() + "\n" + myMagick.Height.ToString());
            }
ソースとなる「Jpeg画像」より低い画素で開く事によって処理を高速化しています。



画像の情報を取得


画像の情報には、「画像情報」と「Exif情報」と「Magick画像」情報があります。
「Exif情報」は付加情報の為、付いていない画像も存在します。
それぞれの情報取得方法を記述します。

「画像情報」取得
            //「画像情報」取得準備
            //(ストリームやバイト配列も引数にできます)
            ImageMagick.MagickImageInfo myMagickInfo = new ImageMagick.MagickImageInfo("./Lenna.bmp");
            //インスタンスから「Read」を使って取得も出来ます。
            //(ストリームやバイト配列も引数にできます)
            var myMagickInfo2 = new ImageMagick.MagickImageInfo();
            myMagickInfo2.Read("./Lenna.bmp");

            //「画像情報」取得
            string msg = "";
            msg += "ファイル名:" + myMagickInfo.FileName + "\n";
            msg += "色空間:" + myMagickInfo.ColorSpace + "\n";
            msg += "横pixel:" + myMagickInfo.Width + "\n";
            msg += "縦pixel:" + myMagickInfo.Height + "\n";
            msg += "画像形式:" + myMagickInfo.Format + "\n";
            msg += "横解像度:" + myMagickInfo.Density.X + "\n";
            msg += "縦解像度:" + myMagickInfo.Density.Y + "\n";
            msg += "解像度の単位:" + myMagickInfo.Density.Units + "\n";
            MessageBox.Show(msg);
解像度の単位は「PixelsPerInch」がよく使われますが、違う場合があります。
ここが「PixelsPerCentimeter」の場合は「解像度」の数値に対して、
「×2.54倍」する必要があります。
↓結果表示

「./Lenna.bmp」は解像度情報が無い為、解像度が「0」と表示されています。
他に、
2値1200dpiのpsd画像を読み込んでみたら、
「色空間:Gray」、
「横解像度:1200」「縦解像度1200」「解像度の単位:PixelPerInch」
と表示されました。


「Exif情報」取得
「Exif情報」を確認する為には「Magick画像」として一度開く必要があります。
Photoshopで作成したpsdファイルは必ず「Exif情報」が付きます。
今回は「./Lenna.psd」にpsdファイルが存在する前提で処理を記述します。
            //「Magick画像」を開く
            using (var myMagick= new ImageMagick.MagickImage("./Lenna.psd"))
            {
                //「Exif情報」取得
                ImageMagick.ExifProfile myMagickExif = myMagick.GetExifProfile();
                //「Exif情報」が存在すれば
                if (myMagickExif != null)
                {
                    string str = "";
                    foreach (var exifValue in myMagickExif.Values)
                    {
                        str += exifValue.Tag + "(";
                        str += exifValue.DataType + "):";
                        str += exifValue.ToString() + "\n";
                    }
                    MessageBox.Show(str); //表示
                }
            }
↓結果表示

72×72dpi、256×256pixelのファイルである事が分かります。



「Exif情報」から「サムネイル画像」の取得
「Exif情報」を確認する為には「Magick画像」として一度開く必要があります。
Photoshopで作成したjpgファイルの「Exif情報」内に「サムネイル画像」が付きます。
今回は「./Lenna.jpg」にjpgファイルが存在する前提で処理を記述します。
            //「Magick画像」を開く
            using (var myMagick= new ImageMagick.MagickImage("./Lenna.jpg"))
            {
                //「Exif情報」取得
                ImageMagick.ExifProfile myMagickExif = myMagick.GetExifProfile();
                //「Exif情報」が存在すれば
                if (myMagickExif != null)
                {
                    //「サムネイル画像」取得
                    using (var myMagickThumbnail = myMagickExif.CreateThumbnail())
                    {
                        // 「Exif情報」に「サムネイル画像」が存在すれば
                        if (myMagickThumbnail != null)
                        {
                            pictureBox1.Image = myMagickThumbnail.ToBitmap();//表示
                            //myMagickThumbnail.Write("サムネ.jpg");
                        }
                    }
                }
            }


「Magick画像」情報取得
「Magick画像」として開いて、プロパティを通して画像の細かい情報を取得できます。
以下のリンク先に取得例が載せてあります。
「C# Magick.NETを使ってみる。補足 画像情報取得」
http://1studying.blogspot.jp/2017/07/c-magicknet_12.html
メモ:
 「Magick画像」の情報(プロパティ)が取得したいだけの為に、
 一度「Magick画像」としてファイルを開かなくてはならない。
 開くファイルが大きい時「Magick画像」の情報取得に時間が掛かってしまう。
 今のところ、解決方法がみつからない。



その3へ続きます…



「C# Magick.NETを使ってみる。その3」へ続きます…

他



「標準画像/サンプルデータ」は「神奈川工科大学」のサイトを利用しました。
http://www.ess.ic.kanagawa-it.ac.jp/app_images_j.html
「ImageMagick コマンドリファレンス」
http://image-magick.com/


「ImageMagickのメモ」
Imagemagickの「convert」コマンドの様々な使い方が書いてある。
http://sharp-bang.jp/prog/im.html


「ImageMagick」の
「対応画像フォーマット」、「仮想画像」、「内蔵画像」、「内蔵パターン」
http://www.imagemagick.org/script/formats.php


2017年5月27日土曜日

Photoshop上でのカラーモードとICCプロファイル

C# Magick.NETを使ってみる
目次→http://1studying.blogspot.jp/2017/07/c-magicknetmokuzi.html#kuw06

プログラム上から「ICCプロファイル」を扱うにあたって、
「Photoshop」上での「カラーモード」と「ICCプロファイル」の扱いを
確認しておきます。


「カラーモード」と「ICCプロファイル」



カラーモード(カラータイプ)
主な「Photoshop」での「カラーモード」は
「RGB」「CMYK」「グレースケール」「モノクロ2階調」が存在します。
実画像はいずれかの「カラーモード」で色データが構成されている状態です。
プログラム上から見た画像詳細情報での「カラータイプ」が
photshop上での「カラーモード」に近い情報となります。


ICCプロファイル
「ICCプロファイル」は
実画像を(モニタやプリンタや他の「カラーモード」等)何らかの出力機器に出力する際、
実画像と出力機器で再現出来る色が異なるので、
実画像にできる限り近い色で出力させる為に使われる「フィルタ」の様なものです。
この「フィルタ」の事を、
「ICCプロファイル」(色変換テーブル)と呼んでいます。
(入力機器にも「ICCプロファイル」を適用する事がありますが、
ここでは出力機器のみの説明にとどめます。)


「作業用スペース」と「プロファイルの指定」



「作業用スペース」(表示用「ICCプロファイル」指定)
「Photoshop」では各「カラーモード」の実画像をモニタに表示する際、
(再現出来る色が異なる為)色に何らかの「フィルタ」をかけて表示しなければなりません。
「RGB」「CMYK」「グレースケール」それぞれに対応した表示用「フィルタ」が
「Photoshop」の「カラー設定」内「作業用スペース」に設定されています。

↑各「カラーモード」の「ICCプロファイル」(フィルタ)が設定されている。
この設定が「Photoshop」インストール時の標準設定となります。


「プロファイルの指定」(「ICCプロファイル」埋め込み設定)
「Photoshop」の「プロファイルの指定」で
実画像内に作業(表示)用「ICCプロファイル」の埋め込みをどのようにするか、
動作のデフォルト設定ができます。

・「このドキュメントのカラーマネジメントを行わない」
 実画像内に「ICCプロファイル」を埋め込みません。
  画像を保存して開き直した際でも、
  常に「Photoshop」の「作業用スペース」の方で指定された
  「ICCプロファイル」(フィルタ)で表示されます。
・「作業用CMYK:Japann Color 2001 Coated」
 実画像内に「ICCプロファイル」を埋め込みます。
  「Photoshop」の「作業用スペース」で指定されている
  「ICCプロファイル」(フィルタ)を保存時に画像内へ埋め込みます。
  画像を保存して開き直した際は、
  埋め込まれた「ICCプロファイル」(フィルタ)で表示が行われます。 
・「プロファイル:〜」
 実画像内に「ICCプロファイル」を埋め込みます。
  ここで指定した「ICCプロファイル」(フィルタ)を保存時に画像内へ埋め込みます。
  画像を保存して開き直した際は、
  埋め込まれた「ICCプロファイル」(フィルタ)で表示が行われます。 
 


「プロファイル」状態によるタイトルバー表示の違い



「RGB/8」「RGB/8#」「RGB/8*」の違い
「Photoshop」で画像を開いた際のタイトルバーに表示される
「RGB/8」「RGB/8#」「CMYK/8*」の違いについて。
・「RGB/8」
 「作業スペース」と「プロファイルの指定」で、
 「ICCプロファイル」(フィルタ)が同じ場合の表示です。
・「RGB/8#」
 「プロファイルの指定」で
 「このドキュメントのカラーマネジメントを行わない」が
 選択されている場合の表示です。
・「RGB/8*」
 「作業スペース」と「プロファイルの指定」で、
 選択されている「ICCプロファイル」が違う場合の表示です。



「カラーモード」の変換について


「Photoshop」ではversion5以降、
「カラーモード」を変更する時に「ICCプロファイル」という色変換テーブルが使われます。

「カラーモード」の変更は「プロファイル変換」と呼ばれ、
変換元「カラーモード」の「ソースのカラースペースプロファイル」(ICCプロファイル)から
変換先「カラーモード」の「変換先プロファイル」(ICCプロファイル)へ
各「ICCプロファイル」の色変換テーブルに従って、
できる限り近い色の見え方になるように実画像データの色構成が変換されます。
↓分かりにくいので、実際にやってみましょう。

「カラーモード」を変換してみる
実際にRGBからCMYKに変換してみましょう。
RGBのファイルを開きます。
次に、
「プロファイルの指定」で
「このドキュメントのカラーマネジメントを行わない」である事を確認します。
↓

これは、「作業スペース」で設定されている「ICCプロファイル」を
使いますという意味になります。

「作業スペース」を確認してみましょう。
↓

「RGB:sRGB IEC61966-2.1」の「ICCプロファイル」から
「CMYK:Japan Color 2001 Coated」の「ICCプロファイル」へ
実画像の色を変換する事になる予定です。

「カラーモード」を
「RGBカラー」から「CMYKカラー」へ変更します。
(「プロファイル変換」でも同じ処理が行えます。)
↓

「CMYK」に変換されました。

これは、内部で次のような事が行われています。
「RGB」画像変換開始
↓
「RGB」の実画像を
「sRGB IEC61966-2.1」の「ICCプロファイル」(フィルタ)で
実画像の色を変更
((ソースの色空間))
↓
「Labカラー」や「XYZ色空間」へ変換(一度絶対色空間となる値へ変換します)
((絶対色空間))
↓
「CMYK」へ変換後、
「Japan Color 2001 Coated」の「ICCプロファイル」(フィルタ)で
実画像の色を変更
((変換後の色空間))
↓
「CMYK」画像完成


「Photoshop」での実際の「RGB→CMYK」変換手順
「プロファイル変換」を行った際、
色域の狭い色空間への変換を行うと
色域外の色が変換先の色域内(色空間内)に入るように変換される事となります。
「できる限り近い見え方」になるように
プロファイルのテーブルに従って色が変更される事となり、
その結果、狭い色域へと変換された色は変換前の色に戻すことが出来なくなります。

変換先の色域に収まるようにプロファイル変換が行われる時、
「できる限り近い見え方」になるように
見え方の何を優先させて変換を行うかを「マッチング方式」により指定出来ます。
「マッチング方式」は以下の中から選ぶ事になります。
  知覚的:色が自然に(色の変化が滑らかに)見えるように変換
  彩度:彩度を保つように変換
  相対的な色域を維持:可能なかぎり色を維持
            白点が異なるときは白点が変わらないように調整して変換
  絶対的な色域を維持:可能なかぎり色を維持する
            紙色が違うなど白点や黒点が異なるときも、
            紙色を含めた絶対的な色が維持されるよう変換
            (CMYK画像の印刷イメージを画面上で確認するための設定)

「プロファイル変換」の代表的な例である、
「Photoshop」での「RGB→CMYK変換」では主に
  「知覚的」か「相対的な色域を維持」
の「マッチング方式」を使用します。
  プロファイル「Japan Color 2001 Coated」
  マッチング方式「知覚的」
  黒点の補正「チェック」
が標準的な設定になります。
このように
  「マッチング方式」が「知覚的」
の設定で「RGB→CMYK変換」すると、
  「色は自然(色の変化は滑らか)」ですが「全体の彩度と明度が下がりぎみ」
の結果が得られます。
変換元画像の彩度がそんなに高くなく、CMYK色域外の色が少ない場合は
  「マッチング方式」を「相対的な色域を維持」
で変換した方が良い結果が得られます。
(「日本のPhotoshop」のデフォルトが「知覚的」、
「欧米のPhotoshop」のデフォルトが「相対的な色域を維持」なのだそうです)

以下の法方でCMYK色域外となってしまうRGB画像内のエリアを知る事が出来ます。
  photoshopで「表示>校正設定>作業用 CMYK」を選択
  (「表示>校正設定>カスタム」で「相対的な色域を維持」の色域外チェックも可能)
  「表示>色域外警告」を「チェック」
  (「表示>校正設定>色の校正」により
  RGB画像を保持したままCMYKへ変換後の画像確認も可能です)
これにより色域外の部分がグレー表示されます。
その後CMYKの色域外となるRGBの色を以下の法方で
視認しながら調整を行います。
  「イメージ>色調補正>色相・彩度」や「イメージ>色調補正>自然な彩度」で
  色域外のグレー部分が減るように調整
変換後のイメージに納得が出来る画像となったら、
実際に「RGB→CMYK変換」を行い変換終了となります。



他



以下のサイトを参考にしました。

Photoshop講座:フォトショップの使い方から上級テクニックまで
プロファイルの指定と変換の違い?
http://adobe-photoshop.seesaa.net/article/94446106.html

Photoshopのモード変換とプロファイル変換、カラー設定について
(プロファイル変換の方が綺麗!のその前に、初期設定を見直そう)
https://iwashi.org/archives/3829

2017年5月22日月曜日

BloggerでScribeFireを使用してみる。

Brogger等でブログを書くにあたって、ブログエディターなる物が存在する。
WindowsとMacに両方対応していて、
使いやすい物はないかと探してみたのですが、そもそも選択肢が少ない。

そんな中、これかなと思ったのが「ScribeFire」というソフト。
「Chrome」の拡張機能として入れる事ができるので、
プラットフォームに縛られないで使えそうだ。

ただ、「Blogger」と「ScribeFire」を繋げる為に、
「ScribeFire」側に普段gmailで使用しているユーザーIDとパスワードを
入力しなければならない。これはちょっと怖い。

解決策として、gmailユーザーのサブアカウントを作り、
「Blogger」へそのサブアカウントからのアクセスを許可する事で、
メインのgmailアカウントを「ScribeFire」へ入力しないで使用する事にした。

注意!
結論として、「Blogger」では「ScribeFire」は使用出来ない事が判明した。
残念。


準備



サブアカウントの作成
まず、gmailで新規にサブアカウントを作成しておいて下さい。
(別のブラウザを使うと楽に作成できます。)

Bloggerの設定
サブアカウントからメインアカウントの「Blogger」を
編集できるように設定を行います。

「Blogger」からサブアカウントに向けて「執筆者を招待」を行う。
↓

サブアカウント側のgmailで招待を受け取り、「招待に応じる」をクリック。
↓

サブアカウントで一度「Blogger」へログインして、
投稿用の名前等の設定を行いログアウトします。

メインアカウント側で「Blogger」へ入り、
「設定」の「基本」を表示。
サブアカウントの権限を管理者に設定します。
↓

これでサブアカウントからメインアカウント側の「Blogger」の
編集が可能となりました。

「ScribeFire」のインストール
次に、「Chrome」へ「ScribeFire」を入れます。
「Chrome」のウェブストアで「ScribeFire」を探し
↓

「Choromeに追加」します。
↓

↓

「Chorome」の右上に「ScribeFire」のアイコンが追加されます。
クリックすると「ScribeFire」が起動します。

「ScribeFire」の設定
「ScribeFire」から「Brogger」の編集が出来るように設定します。

メインアカウントで「BloggerのURL」を先に確認しておきます。



メインアカウントで「ScribeFire」を起動し、
「ScribeFire」の「新しいブログを追加」をクリックします。
↓


確認しておいた「BloggerのURL」を入力して、
サブアカウントのユーザーとパスワードを入力したのですが…
↓


「Blogger」へ接続できないみたいです…


ここまで書いておいてなんですが、
色々調べてみた結果
「ScribeFire」から「Blogger」は使えない事が判明しました。
2015年位から「Blogger」側の問題で使用できなくなってしまったようです。
一部で2段階認証が原因のような事が書いてありましたが、
2段階認証を行わなくても使用出来ませんでした。







2017年5月17日水曜日

C# Magick.NETを使ってみる。その1

C# Magick.NETを使ってみる
目次→http://1studying.blogspot.jp/2017/07/c-magicknetmokuzi.html#kuw01

「Magick.NET」は「ImageMagick」のC#版です。

100種類以上の画像フォーマットに対応。
RGBだけで無く、CMYK、グレー、2値画像等が扱えます。
色々な事が出来る分メモリを贅沢に使う為、過度な処理速度は期待しない方がよいでしょう。
RGBメインな画像加工の場合は「OpenCvSharp」の使用も検討してみて下さい。

「Magick.NET」の使い方は
https://github.com/dlemstra/Magick.NET/tree/master/Documentation
に載っています。

バージョン7.0.0.0102以降は「.NETCore」もサポートされていますが、
今のところWindowsのみ対応となっています。

「Magick.NET」は現在、Macからは使用できないようです。
試しにMacで使用してみたのですが、
「DllNotFoundException」のエラーメッセージで蹴られてしまいました。
Macから呼ばれるDLL関数に対応していないようです。
「GitHub」を覗くと、
Macへの将来的な対応をほのめかしているので今後に期待したいです。
https://github.com/dlemstra/Magick.NET/issues


一方、Windowsでは「VisualStudio」から「NuGet」ですぐに使う事ができます。
バージョン6迄と7以降で変更点が多いようです。
不透明度の代わりにアルファを使用するようになっており、
構文の記述に違いがあります。
var6→7への「ImageMagick」の変更点は
https://www.imagemagick.org/script/porting.php
に記載されています。

個人的に嬉しいのは、バージョン7以降でグレースケール画像を使用する際、
今までは「赤、緑、青、アルファ」の4チャンネル消費していたのが、
「グレースケール」1チャンネルの使用に変更されたところです。

私は使った事が無いのですが、
「Magick.NET.Web」というDLLも存在します。
画像処理をXML的な記述でスクリプト化できるようです。
https://github.com/dlemstra/Magick.NET/blob/master/Documentation/Magick.NET.Web.md
に記載されています。

では、「Magick.NET」を使用してみます。


準備


「ファイル→新規作成→プロジェクト」
「テンプレート→VisualC#→Windowsクラシックデスクトップ→Windowsフォームアプリケーション」
を作成します。
↓

私はプロジェクトの名前を「testMagick」としました。

次に、
「ソリューション」を右クリックして「プロパティ」を表示させます。
↓

「構成プロパティ→構成」の「プラットフォーム」が「AnyCPU」である事を
確認しておきます。
(x86やx64を明示しておかなくても対応したMagick.NETのDLLが自動判別されます。)
「NuGet」で「.NET Core」バージョンの「Magick.NET.Core-Q8」等を使う場合、
確認の必要はありません。

今回は「NuGet」で「Magick.NET-Q8-AnyCPU」をインストールします。
(「Q8」は8bit階調0〜255、「Q16」は16bit階調0〜65535が扱えます)
↓


「Form1」のデザイナへ
「button1」、「label1」、「pictureBox1」を配置します。
↓

配置したら、「button1」をダブルクリックして、
「button1」のクリックイベントのコードを表示させておきます。
        private void button1_Click(object sender, EventArgs e)
        {

        }


テスト用の元画像が必要であれば「神奈川工科大学」のサイトから、
カラーの「Lenna.bmp」画像のオリジナルを取得しておきましょう。
http://www.ess.ic.kanagawa-it.ac.jp/app_images_j.html
レナさんは超有名な標準画像ですね。
ここでのオリジナル「横256×縦256pixel、8bit、RGB画像」を使用します。
(本来のオリジナルで「横512×縦480pixel」版が存在しますが、今回は使用しません)

ダウンロードで取得した「Lenna.bmp」画像を、
プロジェクトフォルダの「/bin/Debug」フォルダ内へ入れておいて下さい。


「Magick.NET」を使ってみる


とりあえず使ってみます。
「button1」のクリックイベントのコードを以下の形にします。
        private void button1_Click(object sender, EventArgs e)
        {
            //ピクチャーボックス表示用
            System.Drawing.Bitmap myBitmap;
            //キャンバス画像を作成 100*50pixel
            var myMagick = new ImageMagick.MagickImage( new ImageMagick.MagickColor("#00ff00"), 100, 50 );
            //ピクチャーボックスへ表示する為、Bitmapへ変換
            myBitmap = myMagick.ToBitmap();
            //リソース解放
            myMagick.Dispose();
            //pictureBox1へ表示
            pictureBox1.Image = myBitmap;
        }
「実行」して「ボタン」をクリック
↓

「100×50Pixel」の緑の画像を生成し、
ピクチャーボックスへ表示させる為にBitmapへ変換して表示を行いました。

同じ内容を、
usingを使って書く事も出来ます。(IDisposeインターフェースを継承している)
        private void button1_Click(object sender, EventArgs e)
        {
            //ピクチャーボックス表示用
            System.Drawing.Bitmap myBitmap;
            //キャンバス画像を作成 100*50pixel
            using (var myMagick = new ImageMagick.MagickImage(new ImageMagick.MagickColor("#00ff00"), 100, 50))
            {
                //ピクチャーボックスへ表示する為、Bitmapへ変換
                myBitmap = myMagick.ToBitmap();
            }
            //pictureBox1へ表示
            pictureBox1.Image = myBitmap;
        }
当然、処理結果は先程と同じとなります。
言わずもがなですが、
リソース解放や例外処理の事を考えるとusingを使った方がコードが楽に書けます。




描画系


「ImageMagick」の「magick(旧convert)」コマンドの「-draw」系オプションにあたるのが
「new ImageMagick.Drawables()」メソッドです。
メソッドチェーンでの記述が可能です。

楕円と線の描画
「button1」クリックイベントのコードを以下の形に書き換えて下さい。
        private void button1_Click(object sender, EventArgs e)
        {
            System.Drawing.Bitmap myBitmap;
            //キャンバス画像を作成 150*150pixel
            using (var myMagick = new ImageMagick.MagickImage(new ImageMagick.MagickColor("#00ff00"), 150, 150))
            {
                var myDraw=new ImageMagick.Drawables();
                myDraw.StrokeColor(ImageMagick.MagickColors.Black);
                myDraw.StrokeWidth(10);
                myDraw.FillColor(new ImageMagick.MagickColor("yellow"));
                myDraw.Ellipse(75, 75, 75, 50, 0, 360); //楕円を描く
                //メソッドチェーンで線を描く
                myDraw.StrokeColor(ImageMagick.MagickColors.Blue)
                      .StrokeWidth(10)
                      .Line(50, 50, 100, 100);
                myDraw.Draw(myMagick); //実描画
                myBitmap = myMagick.ToBitmap(); //Bitmapへ変換
            }
            pictureBox1.Image = myBitmap; //pictureBox1へ表示
        }
↓「実行」して「ボタン」をクリック

このように表示されます。

描画手順も「ImageMagick」で使う「magick(旧convert)」コマンドに近いイメージです。
  「-stroke」(線色)オプション→「StrokeColor()」メソッド
  「-strokewidth」(中心からの線幅)オプション→「StrokeWidth()」メソッド
  「-fill」(塗り色)オプション→「FillColor()」メソッド
をそれぞれ使用して「線色、線幅、塗り色」を指定します。

色を何色にするかの指定には以下の記述方法があります。
  「new ImageMagick.MagickColor("#00ff00")」
  「new ImageMagick.MagickColor(0, ImageMagick.Quantum.Max, 0)」
  (「ImageMagick.Quantum.Max」は扱っている階調の最大値が取得できます。
  使用しているDLLが「Q8」だと「255」、「Q16」だと「65535」となります。
  ここでは、「new ImageMagick.MagickColor(0, 255, 0)」と同じになります。)
  「new ImageMagick.MagickColor("green")」
  「ImageMagick.MagickColors.Black」
  「ImageMagick.MagickColors.None」(塗らない)
使用の「色空間」が「RGB」であればこれらの記述方法で記述を行うと良いでしょう。


角丸四角と文字列の描画
「button1」クリックイベントのコードを以下の形に書き換えて下さい。
        private void button1_Click(object sender, EventArgs e)
        {
            System.Drawing.Bitmap myBitmap;
            //キャンバス画像を作成 150*150pixel
            using (var myMagick = new ImageMagick.MagickImage(new ImageMagick.MagickColor("#00ff00"), 150, 150))
            {
                //角丸四角と文字列を描く
                new ImageMagick.Drawables()
                    //角丸四角
                    .StrokeColor(ImageMagick.MagickColors.Black)
                    .StrokeWidth(3)
                    .FillColor(ImageMagick.MagickColors.None) //塗りを行わない
                    .RoundRectangle(10,10,140,140,40,40) //角丸四角描画
                    //文字列
                    .FillColor(ImageMagick.MagickColors.Blue) //塗り青
                    .FontPointSize(60) //文字列の大きさ
                    .Font("Comic Sans") //文字列の書体
                    .TextAlignment(ImageMagick.TextAlignment.Center) //文字列の揃え
                    .Text(75, 75, "Text") //文字列描画
                    .Draw(myMagick); //実描画
                myBitmap = myMagick.ToBitmap(); //Bitmapへ変換
            }
            pictureBox1.Image = myBitmap; //pictureBox1へ表示
        }
↓「実行」して「ボタン」をクリック

文字列を描画する際、手前で設定していた「線色と線幅」がまだ生きている為、
文字列に黒縁が付いた表示となります。



画像を開いて保存


既に準備してある「/bin/Debug」フォルダ内の「Lenna.bmp」を使用します。

「bmp画像を開く」→「50%縮小」→「グレースケール化」→「tif形式で保存」
「button1」クリックイベントのコードを以下の形に書き換えて下さい。
        private void button1_Click(object sender, EventArgs e)
        {
            System.Drawing.Bitmap myBitmap;
            //画像読み込み
            using (var myMagick = new ImageMagick.MagickImage("./Lenna.bmp"))
            {
                myMagick.Resize(new ImageMagick.MagickGeometry("50%")); //縮小
                myMagick.Grayscale(new ImageMagick.PixelIntensityMethod()); //グレー変換
                //出力形式をtifで書き出し
                myMagick.Write("./Lenna.tif");
                myBitmap = myMagick.ToBitmap(); //Bitmapへ変換
                label1.Text=myMagick.Format.ToString(); //フォーマット表示
            }
            pictureBox1.Image = myBitmap; //pictureBox1へ表示
        }
↓「実行」して「ボタン」をクリック

「Lenna.bmp」の同階層に加工された「Lenna.tif」ファイルが書き出されます。
確認してみて下さい。
レナさんが50%縮小され、グレー化された物がTiff画像で書き出されているはずです。

開いた画像形式(Bmp)とは違う画像形式(Tif)で保存を行っても、
所持している画像フォーマットの形式はファイルを開いた時のまま変わりません。
この例でも「ラベル」が「Bmp3」と表示され、ビットマップのままとなっています。
(「Bmp3」はBitmapのバージョン3という意味です)
「 myMagick.Format = ImageMagick.MagickFormat.Png; 」
とすれば所持フォーマットは任意に変更できます。


「Magick画像」と他バイナリ形式との相互変換


ここでは、「Magick画像」(「Magick.NET」の画像)と、
「ストリーム」や「バイト配列」等の相互変換の方法を説明します。

「ストリーム」や「byte[]」を「Magick画像」として開く
「ストリーム」や「byte[]」の取り扱いについてはリンク先で説明しました。
「C# バッキングストアとストリームについて」…
http://1studying.blogspot.jp/2017/05/c.html

では実際に「Magcik.NET」で画像を開いてみましょう。
「button1」クリックイベントのコードを以下の形に書き換えて下さい。
        private async void button1_Click(object sender, EventArgs e)
        {
            var myBitmap = new Bitmap(150, 150); //「Bitmap画像」を作成
            using (Graphics g = Graphics.FromImage(myBitmap))
            {
                g.FillRectangle(Brushes.LawnGreen, g.VisibleClipBounds); //塗りつぶし
                g.DrawLine(new Pen(Color.Black, 5), 20, 20, 130, 130); //線を引く
            }
            //「Bitmap画像」→「byte[]」へ変換
            byte[] byteArray = (byte[])new ImageConverter().ConvertTo(myBitmap, typeof(byte[]));

            //「byte[]」を「Magick.NET」で開く
            using (var myMagick = new ImageMagick.MagickImage(byteArray))
            {
                new ImageMagick.Drawables()
                    .StrokeColor(ImageMagick.MagickColors.Black)
                    .StrokeWidth(5)
                    .Line(20, 75, 130, 75)
                    .Draw(myMagick); //実描画
                pictureBox1.Image = myMagick.ToBitmap(); //pictureBox1へ表示
            }

            await Task.Delay(1000); //非同期で1秒待機

            //「byte[]」→「ストリーム」を生成
            using (var ms = new System.IO.MemoryStream(byteArray))
            {
                //ストリームを「Magick.NET」で開く
                using (var myMagick = new ImageMagick.MagickImage(ms))
                {
                    new ImageMagick.Drawables()
                        .StrokeColor(ImageMagick.MagickColors.Black)
                        .StrokeWidth(5)
                        .Line(20, 130, 130, 20)
                        .Draw(myMagick); //実描画
                    pictureBox1.Image = myMagick.ToBitmap(); //pictureBox1へ表示
                }
            }
↓「実行」して「ボタン」をクリック

↓1秒後

と表示されます。
最初の画像が「byte[]」を「Magick.NET」で開いた物で、
次の画像が「ストリーム」を「Magick.NET」で開いた物です。

「 ImageMagick.MagickImage」クラスは、
コンストラクタ時の引数で「ファイル」、「Bitmap」、「ストリーム」、「byte[]」等、
一通り開けるようです。とても便利。
書き出しは以下のようにします。

「ファイル」や「Bitmap」や「ストリーム」や「byte[]」へ書き出す
「Magick画像」を他の形態へ書き出すには以下の方法があります。
using(var myMagick = new ImageMagick.MagickImage(ファイル読み込み)){
    //「ファイル」へ書き出し
    myMagick.Write(@"./test1.tif");
    //「Bitmap画像」へ書き出し
    Bitmap myBitmap = myMagick.ToBitmap();
    //「ストリーム」へ書き出し
    using (var ms = new System.IO.MemoryStream())
    {
        //(Bmp3はBitmapのバージョン3という意味です)
        myMagick.Write(ms, ImageMagick.MagickFormat.Bmp3);
    }
    //「byte[]」へ書き出し(出力形式をBmp3にして書き出し)
    myMagick.Format = ImageMagick.MagickFormat.Bmp3;
    byte[] byteArray = myMagick.ToByteArray();
}




その2へ続きます…



「C# Magick.NETを使ってみる。その2」へ続きます…
http://1studying.blogspot.com/2017/05/c-magicknet_31.html


他



「標準画像/サンプルデータ」は「神奈川工科大学」のサイトの物を利用しました。
http://www.ess.ic.kanagawa-it.ac.jp/app_images_j.html
「ImageMagick コマンドリファレンス」
http://image-magick.com/

IM使用例→http://www.imagemagick.org/Usage/draw/


2017年5月16日火曜日

C# バッキングストアとストリームについて

C# Magick.NETを使ってみる
目次→http://1studying.blogspot.jp/2017/07/c-magicknetmokuzi.html#kuw02

様々なストレージメディア(バッキングストア)に対してデータの読み書きを行う際、
ストリームを使用します。
(ストリームからバイナリデータをバイト配列で取得、書き出しが行えます。)


バッキングストア(backing store)とは…


「ストリーム」から見たストレージメディア(ファイル、ネットワーク、メモリ等)の事を、
「バッキングストア」と言います。
「ストリーム」はストレージメディアを「バッキングストア」として利用できます。
「バッキングストア」は必ずStreamクラスを実装しているので、
Streamクラスから継承されたメソッドを使用する事により、
プログラムからは同じ操作でバイト配列の読み書きを行えます。



ストリーム(Stream)とは…


「ストリーム」を使用して「バッキングストア」からバイト配列の読み書きを行います。
「ストリーム」には以下の様な物があります。
・「ファイル」のバイト単位読み書き
  →「System.IO.FileStream」クラス
  「ファイルデータ」を「バッキングストア」として使用する「ストリーム」です。
・「ネットワーク」を介したバイトの送受信
  →「System.Net.Sockets.NetworkStream」クラス
  「ネットワーク上データ」を「バッキングストア」として使用する「ストリーム」です。
・「メモリ配列」のバイト単位読み書き
  →「System.IO.MemoryStream」クラス
  「メモリー」を「バッキングストア」として使用する「ストリーム」です。

「System.IO.FileStream」クラスも
「System.Net.Sockets.NetworkStream」クラスも
「System.IO.MemoryStream」クラスも
「System.IO.Stream」クラスを継承している為、同じ使用方法で操作できます。

あと、特殊な「ストリーム」として以下の物があります。
・他の「ストリーム」に「バッファリング」を持たせる
  →「System.IO.BufferedStream」クラス
  主に「NetworkStream」とセットで使用されます。
  (「FileStream」は内部でバッファリングされる為必要無し。
   「MemoryStream」は読み書きが早い為、バッファリングの必要無し。)

・使用する際の注意
「System.IO.Stream」はあくまでバイトの読み書きを行う為の抽象クラスです。
抽象クラスの為、そのまま「new」して使う事はできません。
「Stream stream = new MemoryStream();」
のようにキャストして使用します。
あと注意点として、
テキストの読み書きによく使用される「StreamReader」や「StreamWriter」クラスは、
「Stream」の名前が入っているのですが、バイトの読み書きを行うのではなく、
エンコードを指定した文字の読み書きを行うクラスです。
「System.IO.Stream」を継承していない事に注意して下さい。



Streamクラス…


「System.IO.Stream」クラスについて説明します。
・プロパティ
  「CanRead」→読み取り可能か
  「CanWrite」→書き込み可能か
  「Length」→ストリームの長さをbyte単位で取得
  「CanSeek」→シークが可能かどうか。
         ランダムアクセスも可 true
         シーケンシャルアクセスのみしか出来ない false
  「Position」→ストリームの現在位置を取得、設定する(ポインタ的なやつ)。
・メソッド
  「ReadAsync」→非同期で読み取り。
  「WriteAsync」→非同期で書き込み。
  「Close」→ストリームを閉じ、リソース解放。(使わない。Disposeを使う。)
  「Dispose」→ストリームを閉じ、リソース解放。
        (Closeよりこちらを使うか、usingを使った方が良いです。)
  「Flush」→ストリームにまだメディアに書き出されていないバッファがあれば、書き込み。
       (ストリームを閉じる時にも裏側で呼ばれています)
  「Read」→次の文字を読み取る。
  「ReadByte」→1バイト読み取り。位置が末尾の場合-1を返す。
  「Seek」→ストリームの現在位置を特定の位置へ移動。
  「SetLength」→ストリームの長さをbyte単位で設定。
  「Write」→指定した値をストリームに書き込む。
  「WriteByte」→ストリームの現在位置に1バイト書き込み。
  「CopyToAsync」→ストリームから非同期で読み取り、別のストリームに書き込む。



準備


試しに「ストリーム」を使ってみます。
その為の準備を行います。

「ファイル→新規作成→プロジェクト」
「テンプレート→VisualC#→Windowsクラシックデスクトップ→Windowsフォームアプリケーション」
を作成します。
↓

私はプロジェクトの名前を「testStream」としました。

次に、
「Form1」のデザイナへ
「button1」、「button2」、「pictureBox1」を配置します。
↓



配置したら、「button1」と「button2」をダブルクリックして、
「button1」と「button2」のクリックイベントのコードを表示させておきます。
        private void button1_Click(object sender, EventArgs e)
        {

        }

        private void button2_Click(object sender, EventArgs e)
        {

        }

準備が出来ました。



使ってみる


実際に「ストリーム」を試します。
「System.IO.FileStream」と「System.IO.MemoryStream」を使ってみます。


・「MemoryStream」を使ってみる
150*150pixelの塗りつぶし「Bitmap画像」作成し、
「Bitmap画像」→「MemoryStream」→「byte[]」
へと変換させています。
その後、
「byte[]」→「MemoryStream」→「Bitmap画像」
へと変換を戻し、円を描画して表示させます。

ビットマップ画像を一度、バイト配列としてメモリ領域へ書き出し、
そのメモリ領域のバイト配列をビットマップ画像として読み込み
ビットマップ画像を表示させる。
処理的には遠回りした事を行っています。

「button1」のクリックイベントのコードを以下の形にします。
        private void button1_Click(object sender, EventArgs e)
        {
            var myBitmap = new Bitmap(150, 150);
            using (Graphics g = Graphics.FromImage(myBitmap))
            {
                g.FillRectangle(Brushes.Green, g.VisibleClipBounds); //塗りつぶし
            }

            //Bitmap画像→ストリームに保存
            var ms = new System.IO.MemoryStream(); // 本来はusingを使います
            myBitmap.Save(ms, System.Drawing.Imaging.ImageFormat.Bmp);
            //ストリーム→byte[]へ変換
            byte[] byteArray = ms.ToArray();
            ms.Dispose(); //ストリーム閉じる

            //~~~~〜〜〜〜

            //byte[]→ストリームへ変換
            var ms2 = new System.IO.MemoryStream(byteArray); // 本来はusingを使います
            //ストリーム→Bitmap画像へ変換
            var myBitmap2 = new Bitmap(ms2);
            ms2.Dispose(); //ストリーム閉じる

            using (Graphics g = Graphics.FromImage(myBitmap2))
            {
                g.DrawEllipse(new Pen(Color.Black, 5), 25, 25, 100, 100); //円描画
            }

            pictureBox1.Image = myBitmap2;
        }
↓「実行」して「button1」を押下。

このように表示されます。

今回、
「MemoryStream」を使い、メモリを「ストリーム」として扱いました。
「MemoryStream」を使用し、
「Bitmap画像」から「byte[]」への相互変換をおこないましたが、
大きな画像を扱う場合は「MemoryStream」分のメモリが余計に使用されます。
メモリの使用量が気になる場合は、
この後説明する「「FileStream」をつかってみる」のコード内で使用している
「ImageConverter」クラスの
「ConvertTo()」メソッドと「ConvertFrom()」メソッドを
使用しても良いかもしれません。


・「FileStream」を使ってみる
150*150pixelの塗りつぶし「Bitmap画像」作成し、
「Bitmap画像」→「byte[]」
へと変換後、
「FileStream」を使い「test.bmp」ファイルへ「byte[]」を保存しています。
その後、
「FileStream」を使い「test.bmp」ファイルから「byte[]」を読み込みます。
「byte[]」→「Bitmap画像」
へと変換を戻し、四角を描画して表示させます。
処理的には遠回りした事を行っています。

「button2」のクリックイベントのコードを以下の形にします。
       private async void button2_Click(object sender, EventArgs e)
        {
            var myBitmap = new Bitmap(150, 150);
            using (Graphics g = Graphics.FromImage(myBitmap))
            {
                g.FillRectangle(Brushes.LawnGreen, g.VisibleClipBounds); //塗りつぶし
            }

            //Bitmap画像→byte[]へ変換
            var converter = new ImageConverter();
            byte[] byteArray = (byte[])converter.ConvertTo(myBitmap, typeof(byte[]));

            //ファイルを新規作成(ファイルストリーム生成)
            using (var fs = new System.IO.FileStream(@"./test.bmp", System.IO.FileMode.Create))
            {
                //byte[]→ストリームへ書き込み
                await fs.WriteAsync(byteArray, 0, byteArray.Length);
            }

            //~~~~〜〜〜〜

            byte[] byteArray2;
            //ファイルを開く(ファイルストリーム生成)
            using (var fs = new System.IO.FileStream(@"./test.bmp", System.IO.FileMode.Open))
            {
                byteArray2 = new byte[fs.Length];
                //ストリーム→byte[]へ読み込み
                await fs.ReadAsync(byteArray2, 0, byteArray2.Length);
            }

            //byte[]→Bitmap画像へ変換
            Bitmap myBitmap2 = (Bitmap)converter.ConvertFrom(byteArray2);

            using (Graphics g = Graphics.FromImage(myBitmap2))
            {
                g.DrawRectangle(new Pen(Color.Black, 5), 25, 25, 100, 100); //四角描画
            }

            pictureBox1.Image = myBitmap2;
        }
↓「実行」して「button2」を押下。

このように表示されます。
プロジェクトフォルダの「/bin/Debug」フォルダ内に
緑色で塗りつぶされた画像の「test.bmp」ファイルが作成されます。

今回、
「FileStream」を説明するにあたり、
「byte[]」を経由したいが為にかなり回りくどいやり方をしました。
「FileStream」を使わないのであれば、
//ファイル内容を全てbyte[]へ読み込む
byte[] byteArray = System.IO.File.ReadAllBytes(@"./test.bmp");
//byte[]を全てファイルへ書き込む
System.IO.File.WriteAllBytes(@"./test.bmp", byteArray);
の様に書いた方がスマートです。
また、
通常の「Bitmap画像」ファイルへの読み書きは、
//「Bitmap画像」読み込み
var myBitmap = new Bitmap(@"./test.bmp");
//「Bitmap画像」書き出し
myBitmap.Save(@"./test.bmp");
のようにもっとシンプルに行うのが正しいです。

ファイルを「ストリーム」で「バイナリ」として呼びたい時のみ
            // ファイルストリーム生成
            var fs = new System.IO.FileStream("./test.bmp", System.IO.FileMode.Open); // 本来はusingを使います
            // バッファ用にbyte[]生成
            byte[] byteArray = new byte[fs.Length];
            // byte[](バッファ)へデータを読み込む
            await fs.ReadAsync(byteArray, 0, (int)fs.Length);
            // ファイルストリームを閉じる
            fs.Dispose();
            // byte[](バッファ)からメモリストリーム生成
            var ms = new System.IO.MemoryStream(byteArray); // 本来はusingを使います
            // メモリストリームからBitmap画像生成
            var myBitmap = new Bitmap(ms);
            // メモリストリームを閉じる
            fs.Dispose();
のような形をとります。



メモ
ストリーム→byte[]変換、3パターン
 var ms = new System.IO.MemoryStream();
とした場合…

//パターン1
byte[] byteArray = ms.GetBuffer(); 

//パターン2
byte[] byteArray = new byte[ms.Length];
await ms.ReadAsync(byteArray, 0, byteArray.Length);
か
ms.Read(byteArray, 0, byteArray.Length);

//パターン3
byte[] byteArray = new byte[ms.Length];
await ms.ReadAsync(byteArray, 0, (int)ms.Length);
か
ms.Read(byteArray, 0, (int)ms.Length);

//ストリームはusingするか、閉じ忘れないようにしましょう。
ms.Dispose();
他に「メモリストリーム」はクリップボードの読み書きにもよく使われます。



他



以下のサイトを参考にしています。
http://heppoco-programer-life.dreamlog.jp/archives/1214566.html



2017年5月2日火曜日

C# VisualStudio for MacのXamarin.FormsでMacアプリ作成

「VisualStudio for Mac」の「Xamarin.Forms」でMacアプリが作れたら、
WindowsとUIが共有できるのではないか…と思って調べてみたのですが、
結論から書いてしまうと、不可能みたいです。

「Xamarin.Form」で作れるアプリは
https://developer.xamarin.com/guides/xamarin-forms/getting-started/installation/#Target_Platforms
で調べると、
 Android 4.0.3〜
 iOS 6.1〜
 Windows Phone 8.1〜
 Windows 8.1のタブレットとデスクトップアプリ
 Windows 10のユニバーサルアプリ(UWP)
 Windows Phone 8 Silverlight(非推奨)
のようです。

MacのUIを「VisualStudio」実現するには、
・「Xamarin.Mac」
「C# VisualStudio for Mac で Xamarin.Mac を使ってみる。」
・「Gtk#」
「C# VisualStudio for Mac でGtk#を使ってみた。gtk-sharp」
辺りから選択する事になるのでしょうか。


「Eto.Forms」ってのもあるみたい。
ちょっと調べてみようかな…
あと、
Nevron(NOV).NETクロスプラットフォームUIライブラリ
も気になる…こっちは有料みたい。




C# VisualStudio for Mac で Xamarin.Mac を使ってみる。


「VisualStudio for Mac」を使うと、
「C#」の記述で「Xamarin.Mac」を経由して「Cocoa」を使う事ができます。
XamarinでMacネイティブのプロジェクトを試してみます。


「Xamarin.Mac」から「Cocoa」を使ってみる


「VisualStudio for Mac」で新しいプロジェクトを作成します。
「Mac -> アプリ」の「Cocoa App」を選択
私は「AppName」を「testCocoa」として、
「プロジェクト名」と「ソリューション名」も「testCocoa」にしました。
↓
このように表示されます。

・「AppDelegate.cs」…
  「DidFinishLaunching」メソッドはアプリケーションの開始時に呼ばれます。
  「WillTerminate」メソッドはアプリケーションの終了時に呼ばれます。
・「Entitlements.plist」…
  サンドボックス等セキュリティ範囲の制御。
・「Info.plist」…
  「Xamarin.Mac」のアプリケーション情報。
・「Main.cs」…
  メインエントリポイント。プログラムの開始場所です。
・「Main.storyboard 」…
  「Xcode」の「Interface Builder」を使用してこのファイルを編集し、UIを作ります。(ここでは、最初にUIへボタンとラベルを配置します。)
・「ViewController.cs」…
  「MVCモデル」の「コントローラー」となる部分。
  「storyboard」で作成された「ビュー(Form)」の「コントローラー」を受け持ちます。
  主にこのファイルに
  「ビュー(Form)」からの「イベント通知(ボタンクリック等)」の受け取りと、
  「ビュー(Form)」内の「コントロール(ラベル等)」の操作、参照の
  コードを記述します。
  「ViewDidLoad」メソッドは「View(Form)」がインスタンス化した直後に呼ばれます。
  「RepresentedObject」プロパティは「View(Form)」間での値の受け渡し用に使います。
・「ViewController.Designer.cs」…
  「Xcode」の「Interface Builder」でUI作成時、「VisualStudio for Mac」によって、
  自動更新される。手動で変更してはいけません。
  (「Xcode」側の「ViewController.h」とこのファイルが自動で同期される。)


UIを変更したいので
「Main.storyboard」をダブルクリックして、
「Xcode」の「Interface Builder」で開きます。
↓

開かれた「Xcode」側のプロジェクトに変更を加えると、
「VisualStudio for Mac」側のプロジェクトに同期されます。

一番下のウィンドウ「View Controller」へ「Push Button」の「コントロール」を配置します。
↓

配置した「Push Button」の「コントロール」のプロパティを表示して、
「title」を「開始」に変更します。
↓


続いて、「Label」の「コントロール」を配置します。
↓


配置したらこの画像に従って「下部のアイコン」をクリック。
「Add New Constraints」が表示されます。
「上、右、左」空き量の値を全て「30」と入力します。
設定後、「Add 3 Constraints」を押します。
これで「View(Form)」に対して「Label」の表示位置が左上に固定されます。

コードへ「接続」…
 Macの「Interface Builder」の場合、
 配置した「コントロール」をコードへ「接続」させる必要があります。
 「アウトレッド接続」と「アクション接続」があります。
 ・「Outlets」…
  「コントロール」の「プロパティ」が「アウトレッド接続」のコードを介して、
  公開されます。
  (「コントロール」の「プロパティ」は最初Publicではありません。
  「プロパティ」を参照する為には公開する必要があります。)
 ・「Action」…
  「コントロール」の「イベント」を「アウトレッド接続」のコードを介して、
  「イベントハンドラ」へと割り当てます。
  「コントロール」に対して「クリックイベント」等の「イベント」時に、
  実行するメソッドの割り当て(「アクション接続」)を行います。
  この方法を使うと、複数の「コントロールのイベント」を
  1つのメソッドに対して割り当てる事も可能です。

「コントロール」を「アウトレッド接続」と「アクション接続」させる
コードは「ViewController.h」へ記述されます。
ここでは、
「Label」を「アウトレッド接続」、「Push Button」を「アクション接続」させる
コードを「ViewController.h」内へ記述させます。
「Xcode」側「ViewController.h」への記述は、
「VisualStudio for MAC」側「ViewController.Designer.cs」へ
自動的に同期されます。
手順を書きます。最初は少し戸惑うかもしれません。
まず、
「Main.storyboard」を「Xcode」で以下のような形で表示させておきます。
↓

次に、「Xcode」の右上の「丸が二つ重なったアイコン」をクリック。
コードの編集ウィンドウが開きます。
(「ViewController.h」でないコードが表示された状態になります。)
↓

表示させたいコードをフォルダ階層のリストから選択できます。
ここで、「ViewController.h」のコードを表示させておいて下さい。
これで「コントロール」をコードへ「接続」させる準備ができました。

「Label」を「アウトレッド接続」させるコードを記述させて、
「プロパティ」を公開します。
「Label」を「Control」キーを押しながらドラッグして…
↓
「ViewController.h」の画面の位置の行へドロップします。
(「@interface 〜{}」と「@end」の間の行)
ドロップすると以下の表示になります。
↓
「Conection」が「Outlet」である事を確認後、
「Name」に「Label1」と記入し、「Connect」を押します。
↓

「ViewController.h」に
「Label」を「アウトレッド接続」させるコードが記述され、
「プロパティ」が公開されました。

次に、
「Push Button」を「アクション接続」させるコードが記述させ、
「クリックイベント」をメソッド(イベントハンドラ)へ割り当てます。
「開始ボタン」を「Control」キーを押しながらドラッグして…
↓
「ViewController.h」の画面の位置の行へドロップ。
(「@interface 〜{}」と「@end」の間の行)
↓

「Conection」を「Action」にします。
「Name」に「ClickedButton」と記入し、「Connect」を押します。
↓
「ViewController.h」に
「Push Button」を「アクション接続」させるコードが記述され、
「クリックイベント」がメソッド(イベントハンドラ)へ割り当てられました。

「Xcode」を終了させて下さい。
終了したら「VisualStudio for Mac」を表示させ、
「ViewController.designer.cs」のファイルを確認してみて下さい。
↓

「ViewController.h」と同期され、
「Label1」の「プロパティ」が公開され、
「ClickedButton」メソッドへのイベントハンドラが設定されているのが確認できます。

最後に処理を実装します。
「ViewController.cs」を開いて下さい。
「ViewController」クラス内へ以下のコードを貼り付けます。
  private int ClickedCount = 0;
  partial void ClickedButton(NSObject sender)
  {
   int MyClickedCount = ++ClickedCount;
   //非同期処理
   var task = System.Threading.Tasks.Task.Run(() =>
   {
    for (int i = 0; i < 6; i++)
    {
     //ワーカースレッドからメインスレッドUIの更新
     InvokeOnMainThread(delegate
     {
      Label1.StringValue = MyClickedCount.ToString() + "回目" + i.ToString() + "秒";
     });
     System.Threading.Thread.Sleep(1000);
    }
    InvokeOnMainThread(delegate
    {
     var alert = new NSAlert();
     alert.InformativeText = MyClickedCount.ToString() + "回目非同期処理終了";
     alert.RunModal(); //モーダルダイアログ表示
    });
   });
  }
気をつけたいのは、
イベントハンドラのメソッドが「partial」となっているところです。

これで完成となります。
実行すると、
「1回目0秒→1回目1秒→…→1回目5秒」「1回目非同期処理終了」
の順に表示されます。
「非同期処理」の為、
「開始」ボタンを何回か押すと並列的に処理されます。



他



以下サイトを参考にしました。
https://developer.xamarin.com/guides/mac/getting_started/hello,_mac/
https://github.com/ytabuchi/XamarinHOL

↑Topへ