カテゴリー別アーカイブ: C#

16進数カラーの変換

ごくまれに16進数カラーを扱わないとならないことがあるのですが、直感的に入力することが難しいのでHSVカラーで編集できるようなツールを作りたいなと思うことがあります。その準備で色を変換する関数を書いてみました。

以下はC#コンソールアプリのコードです。

class Program
{
    static void Main(string[] args)
    {
        // R78, G244, B32のカラーを16進数カラーへ変換します
        int[] rgb = { 78, 244, 32 };

        // 0~1へクランプ
        Vector3 color = rgbToClamp01(rgb);

        // 16進数カラーへ変換
        string hexColor = clamp01ToHex(color);

        // 「4ef420」が返ります
        Console.WriteLine("HEX: #" + hexColor);

        // 今度は逆に「4ef420」を8ビットへ変換します

        // 0~1のRGBへクランプ
        Vector3 color2 = HexToClamp01(hexColor);

        // 8ビットカラーへ変換
        int[] rgb2 = clamp01ToRgb(color2);

        // 「78, 244, 32」が返ります
        Console.WriteLine("RGB: " + rgb2[0] + ", " + rgb2[1] + ", " +  rgb[2]);

        // HSVに変換
        Vector3 hsv = RGB_TO_HSV(color2);
        Console.WriteLine("HSV: " + Math.Round(hsv.X * 360) + ", " + Math.Round(hsv.Y * 100) + ", " + Math.Round(hsv.Z * 100));

        // HSVから16進数カラーへ
        Vector3 color3 = HSV_TO_RGB(hsv);
        string hexColor2 = clamp01ToHex(color3);
        Console.WriteLine("HEX: #" + hexColor2);

        Console.ReadKey();
    }

    // 8ビットから0-1
    static Vector3 rgbToClamp01(int[] rgb)
    {
        return new Vector3(rgb[0] / 255.0f, rgb[1] / 255.0f, rgb[2] / 255.0f);
    }

    // 0-1から8ビットへ
    static int[] clamp01ToRgb(Vector3 color)
    {
        int[] rgb = { (int)(color.X * 255), (int)(color.Y * 255), (int)(color.Z * 255) };
        return rgb;
    }

    // 0-1カラーから16進数カラーへ
    static string clamp01ToHex(Vector3 color)
    {
        string[] hexTable = { "0", "1", "2", "3", "4", "5", "6", "7", "8", "9", "a", "b", "c", "d", "e", "f" };
        Vector3 rgb = color * 255;
        int[] num = new int[6];
        string webColor = "";

        num[0] = (int)Math.Floor(rgb.X / 16);
        num[1] = (int)Math.Floor(rgb.X % 16);
        num[2] = (int)Math.Floor(rgb.Y / 16);
        num[3] = (int)Math.Floor(rgb.Y % 16);
        num[4] = (int)Math.Floor(rgb.Z / 16);
        num[5] = (int)Math.Floor(rgb.Z % 16);

        for(int i = 0; i < 6; i++)
        {
            webColor += hexTable[num[i]];
        }

        return webColor;
    }

    // 16進数カラーから0-1カラーへ
    static Vector3 HexToClamp01(string hexColor)
    {
        string[] hexTable = { "0", "1", "2", "3", "4", "5", "6", "7", "8", "9", "a", "b", "c", "d", "e", "f" };
        hexColor = hexColor.ToLower();
        int[] str = new int[6];

        for(int i = 0; i < 6; i++)
        {
            string num = hexColor.Substring(i, 1);
            for(int j = 0; j < hexTable.Length; j++)
            {
                if(hexTable[j] == num)
                {
                    str[i] = j;
                    break;
                }
            }
        }

        int red = str[0] * 16 +str[1];
        int green = str[2] * 16 + str[3];
        int blue = str[4] * 16 + str[5];

        return new Vector3(red / 255.0f, green / 255.0f, blue / 255.0f);
    }

    // RGBからHSVへ変換
    // 範囲は0 - 1
    // 360で表すと、0:赤、60:黄、120:緑、180:水色、240:青、300:紫、360:赤
    public static Vector3 RGB_TO_HSV(Vector3 rgb)
    {
        float hue = 0;
        float saturation = 0;
        float value = 0;

        float max = Math.Max(rgb.X, Math.Max(rgb.Y, rgb.Z));
        float min = Math.Min(rgb.X, Math.Min(rgb.Y, rgb.Z));

        // Hue
        if (max == min)
        {
            hue = 0;
        }
        else if (max == rgb.X)
        {
            hue = (rgb.Y - rgb.Z) / (max - min);
        }
        else if (max == rgb.Y)
        {
            hue = (rgb.Z - rgb.X) / (max - min) + 2;
        }
        else if (max == rgb.Z)
        {
            hue = (rgb.X - rgb.Y) / (max - min) + 4;
        }

        hue /= 6.0f;
        if (hue < 0) hue += 1.0f; // Saturation if (max == 0) { saturation = 0; } else { saturation = (max - min) / max; } // Value value = max; return new Vector3(hue, saturation, value); } // HSVからRGBへ変換 public static Vector3 HSV_TO_RGB(Vector3 hsv) { float red = 0; float green = 0; float blue = 0; if (hsv.Y == 0) { red = green = blue = hsv.Z; } else { if (hsv.X >= 1.0f)
                hsv.X -= 1.0f;

            hsv.X *= 6.0f;
            float i = (float)Math.Floor(hsv.X);
            float f = hsv.X - i;    // 小数点以下を得る
            float p = hsv.Z * (1 - hsv.Y);
            float q = hsv.Z * (1 - (hsv.Y * f));
            float t = hsv.Z * (1 - (hsv.Y * (1 - f)));

            if (i < 1)
            {
                red = hsv.Z; green = t; blue = p;
            }
            else if (i < 2)
            {
                red = q; green = hsv.Z; blue = p;
            }
            else if (i < 3)
            {
                red = p; green = hsv.Z; blue = t;
            }
            else if (i < 4)
            {
                red = p; green = q; blue = hsv.Z;
            }
            else if (i < 5)
            {
                red = t; green = p; blue = hsv.Z;
            }
            else
            {
                red = hsv.Z; green = p; blue = q;
            }
        }

        return new Vector3(red, green, blue);
    }
}


関数を使って試作してみたプログラムです。

連番ファイルの操作

abc_01.jpg→abc_001.jpg
と0で埋める桁数を変えたり、

abc_01.jpg→abc_02.jpg
と番号をずらしたりする時のサンプルコード。

            // ディレクトリからファイルパスを取得する
            string dir = @"C:\Users\sample";

            var dirInfo = new DirectoryInfo(dir);

            List filePaths = dirInfo.GetFiles("*", SearchOption.AllDirectories).
                Select(f => f.FullName).
                ToList();

            filePaths.Sort();

            // 連番をプラス方向にシフトする場合はファイル名の重複を避けるために降順にする
            filePaths.Reverse();

            for(int i = 0; i < filePaths.Count; i++)
            {
                // file_001.jpgに1を足してfile_002.jpgにする
                string extension = System.IO.Path.GetExtension(filePaths[i]);
                string fileName = System.IO.Path.GetFileNameWithoutExtension(filePaths[i]);

                // 数字部分を抽出
                string number = Regex.Replace(fileName, @"[^0-9]", "");

                // file_部分を抽出
                string prefix = fileName.Replace(number, "");

                // 桁を調べる
                int digit = number.Length;

                // 文字から数値に変換する
                int num = int.Parse(number);

                // 処理
                digit += 1;
                num += 1;

                // 変換後のファイルパス
                string newFileName = dir + "\\" + prefix + num.ToString().PadLeft(digit, '0') + extension;
                Console.WriteLine(newFileName);

                // ファイル名を変更する
                File.Move(filePaths[i], newFileName);
            }

            Console.ReadKey();

C#:リストクラスをクラス変数でソート

リストクラスをソートするサンプルコード


//昇順でソートする
person = person.OrderBy(f => f.Age).ToList();

//降順
person = person.OrderByDescending(f => f.Age).ToList();

//こんなやり方も
person.Sort(delegate (Person x, Person y) { return x.Age.CompareTo(y.Age); });

Observable Collectionをソート

いちどListにしてソートした後にObservable Collectionに戻す

var listPerson = Person.ToList();
listPerson = listPerson.OrderByDescending(f => f.Age).ToList();
Person.Clear();
Person= new ObservableCollection(listPerson);

C#:Zipファイルと画像の読み書き

System.IO.Compression とSystem.IO.Compression.FileSystem を参照することでZIPファイルの操作を簡単に行うことができます。

 

Zipファイルから画像を読み込んで縮小する

using (ZipArchive archive = ZipFile.OpenRead(zipFilePath))
{
    ZipArchiveEntry entry = archive.GetEntry(imagePath);

    using (Stream stream = entry.Open())
    {
        using (MemoryStream memoryStream = new MemoryStream())
        {
            stream.CopyTo(memoryStream);
            memoryStream.Position = 0;

            BitmapImage bitmap = new BitmapImage();
            bitmap.BeginInit();
            bitmap.CacheOption = BitmapCacheOption.OnLoad;
            bitmap.StreamSource = memoryStream;
            bitmap.DecodePixelHeight = imageHeight;
            bitmap.EndInit();

            return new WriteableBitmap(bitmap);
        }
    }
}

zipFilePathは開くzipファイルのパス、imagePathはZip内における画像のパス。これをbitmapという画像データに割り当てています。ZipのエントリーをStreamで読んで、MemoryStreamに変換した後にBitmapImageで読み込み、拡縮をしています。

 

プログラム内で生成した画像をZipファイルに格納して保存する

using (ZipArchive archive = ZipFile.Open((Directory.GetCurrentDirectory() + "\\" + "test.zip"), ZipArchiveMode.Update))
{
    ZipArchiveEntry entry = archive.CreateEntry("001.jpg");

    using(Stream stream = entry.Open())
    {
        BitmapEncoder encoder = new JpegBitmapEncoder();
        encoder.Frames.Add(BitmapFrame.Create(writeableBitmap));
        encoder.Save(stream);
    }
}

画像データwriteableBitmapをtest.zipというファイルを生成して001.jpgという名前で格納しています。

C#におけるファイルパスや文字列の操作

毎回忘れるのでメモ。

//パスからディレクトリを取得する場合
string dir = System.IO.Path.GetDirectoryName(filePath);

//パスから拡張子を取得する場合
string extension = System.IO.Path.GetExtension(filePath);

//パスからファイル(拡張子なし)を取得する場合
string fileName= System.IO.Path.GetFileNameWithoutExtension(filePath);

//ファイルの存在を確認する
File.Exists(path);

//フォルダの存在を確認する
Directory.Exists(path);

//実行ファイルのディレクトリを取得する
string path = Directory.GetCurrentDirectory();

//数字をn桁の0で埋める
string fileName= index.ToString("000") + ".jpg";

 

ラムダ式

WPFの学習過程でC#のラムダ式を覚えました。これはいいなという使い方を書いてみます。ラムダ式を使えば配列から必要な要素を配列として取得したり、配列同士を比較して新しく配列を作ることが短いコードで容易に出来るようになります。やたらとループ処理をしなくて済みます。

 

string[] supportExts = { ".jpg", ".jpeg", ".bmp", ".png", ".tiff", "gif" }; // 拡張子の配列

var dirInfo = new DirectoryInfo(@"E:\WPF");

var files = dirInfo.GetFiles("*", SearchOption.AllDirectories). // ディレクトリとすべてサブディレクトリのファイルを抽出(List<fileInfo>型)
Where(f => supportExts.Contains(f.Extension)).                  // 特定の拡張子だけを抽出(List<fileInfo>型)
Select(f => f.Name).                                            // fileInfo型からパスのみをリスト(List<string>型)
ToList();

ウインドウズアプリはファイル操作をすることが多いのですが、Listの配列が欲しい時にfileInfoの配列からさらっと抽出できます。Whereはフィルタリング、Selectは変形、変換といった感じです。

 

var imageFiles = dirInfo.GetFiles("*", SearchOption.TopDirectoryOnly).  // ディレクトリのファイル(List<fileInfo>型)
                            Where(f => !f.Name.Contains(".xml")).       // xmlファイルは除外する
                            Select(f => f.Name).                        // ファイル名に(List<string>型)
                            ToList();

!で否定条件での絞り込みもいけます。

 

var deleteFiles = imageFiles.Where(f => !files.Contains(f)).ToList();

二つの配列を比較して片方に含まれないものをリストで取得することもできます。

 

colorsというList<Vector3>型からSolidColorBrushのリストを作るなら

var colorBrush = colors.Select(f => new SolidColorBrush(Color.FromRgb(
        (byte)(f.X * 255),
        (byte)(f.Y * 255),
        (byte)(f.Z * 255)))).ToList();

 

ZipArchiveから拡張子と名前順でソートしたリストを取得する例

string[] supportExts = { ".jpg", ".jpeg", ".bmp", ".png", ".tiff", "gif" }; // 拡張子の配列

ZipArchive archive = ZipFile.OpenRead(path);
List<ZipArchiveEntry> entries = archive.Entries.
    Where(f => supportExts.Contains(System.IO.Path.GetExtension(f.FullName).ToLower())).
    OrderBy(f => f.FullName).
    ToList();