午後わてんのブログ

ベランダ菜園とWindows用アプリ作成(WPFとC#)

HSVのグラデーションの画像を作成して保存するアプリ作ってみた

以前作ったHSVとRGBの相互変換のdllを使って
HSVのグラデーションの画像を作成して保存するアプリ作ってみた
ダウンロード先
 

動作
修正する2018/03/03以前なので色相360が無彩色になっている
 
 
色相90の画像
イメージ 1
こういう画像を作成するときは
イメージ 2
色相を90
Hueにチェック
横サイズを256
にして保存ボタン
 
 
イメージ 4
彩度をMAXの100固定で
色相と明度の一覧は
イメージ 6
この設定
 
 
イメージ 5
明度をMAXの100固定で
色相と彩度の一覧は
イメージ 7
この設定
 
イメージ 9
保存形式はpngbmptiff
 
 
 
イメージ 8
保存するときのPixelFormatは
Bgr24 普通はこれでOK、24bpp
Pbgra32 32bppで保存したい時用
Gray8 8bitグレースケール用
 
 
 
イメージ 10
色相360は無彩色の設定になっていた
けど
色相360度は0度と同じにした(2018/03/03)
イメージ 19
 
画像の減色でディザリングを試している時に
HSVを使ったグラデーション画像を使いたかったので
以前作ったHSVとRGBのdllを使って作ってみた
このdllは色相360を0と同じ赤じゃなくて無彩色に当てているので
そのせいで今回のアプリでできる画像は正確じゃないかもしれない
イメージ 13
横360、縦100ピクセル
色相は0から360でループしている
これを4倍に拡大して左下と右下の色を確認してみる

f:id:gogowaten:20191212001857p:plain

左下はRGB(255,0,0)の色相は0なのでこれであっている
 
右下は

f:id:gogowaten:20191212001915p:plain

RGB(255,0,4)の色相は359
 
この画像の横ピクセル数は360個
この時に1ピクセルごとに色相1°を割り当てていくと
1ピクセル目は色相0
2ピクセル目は色相1
360ピクセル目は色相359
なので
こうなるように辻褄が合うようにコードを書いたから
右端は色相359
どうなんだろう
 
2018/03/03追記
PSPさんにコメントを頂きまして
HSV.DLLの内容を変更しました、変更内容は
色相360度を無彩色にしていたのを360度=0度に変更
なのでこの記事のアプリの動作も変更されて
イメージ 20
色相360度=0度なので赤
 
グレースケール
イメージ 21
新たに付け足した
アプリのコードは修正無しでグレースケール用のコードを追加しただけ
2018/03/03追記ここまで
 
 
 
デザイン画面

f:id:gogowaten:20191212002055p:plain

ムダに長いw
StyleやSetterとか使えばよかったかも
 
 
C#のコード
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Controls.Primitives;
using System.IO;
using MyHSV;


namespace _2080226_HSVRect
{
    /// <summary>
    /// MainWindow.xaml の相互作用ロジック
    /// </summary>
    public partial class MainWindow : Window
    {
        public MainWindow()
        {
            InitializeComponent();
            ButtonSaveImage.Click += ButtonSaveImage_Click;
            ScrollBarSizeWidth.ValueChanged += ScrollBarSize_ValueChanged;
            ScrollBarSizeHeight.ValueChanged += ScrollBarSize_ValueChanged;

            ScrollBarHue.ValueChanged += ScrollBarSize_ValueChanged;
            ScrollBarSaturation.ValueChanged += ScrollBarSaturation_ValueChanged;
            ScrollBarValue.ValueChanged += ScrollBarValue_ValueChanged;

            ScrollBarHue.MouseWheel += ScrollBarHue_MouseWheel;
            ScrollBarSaturation.MouseWheel += ScrollBarHue_MouseWheel;
            ScrollBarValue.MouseWheel += ScrollBarHue_MouseWheel;

            ScrollBarSizeWidth.MouseWheel += ScrollBarHue_MouseWheel;
            ScrollBarSizeHeight.MouseWheel += ScrollBarHue_MouseWheel;

            TextBoxHue.MouseWheel += TextBoxHue_MouseWheel;
            TextBoxSaturation.MouseWheel += TextBoxHue_MouseWheel;
            TextBoxValue.MouseWheel += TextBoxHue_MouseWheel;

            TextBoxSize.MouseWheel += TextBoxHue_MouseWheel;
            TextBoxSizeHeight.MouseWheel += TextBoxHue_MouseWheel;

            RadioButtonHue.Checked += RadioButtonHue_Checked;
            RadioButtonSaturaion.Checked += RadioButtonSaturaion_Checked;
            RadioButtonValue.Checked += RadioButtonValue_Checked;

            TextBoxHue.GotFocus += TextBlock_GotFocus;
            TextBoxSize.GotFocus += TextBlock_GotFocus;

            ButtonHueAdd30.Click += ButtonHueAdd30_Click;
            ButtonHueSub30.Click += ButtonHueSub30_Click;
            ButtonSaturationAdd.Click += ButtonSaturationAdd_Click;
            ButtonSaturationSub.Click += ButtonSaturationSub_Click;
            ButtonValueAdd.Click += ButtonValueAdd_Click;
            ButtonValueSub.Click += ButtonValueSub_Click;

            ButtonSizeWidth100.Click += ButtonSize100_Click;
            ButtonSizeWidth256.Click += ButtonSize256_Click;
            ButtonSizeWidth360.Click += ButtonSizeWidth360_Click;
            ButtonSizeHeight100.Click += ButtonSizeHeight100_Click;
            ButtonSizeHeight256.Click += ButtonSizeHeight256_Click;
            ButtonSizeHeight360.Click += ButtonSizeHeight360_Click;

            DrawHue();
            ComboBoxInitialize();
        }

        private void TextBoxHue_MouseWheel(object sender, MouseWheelEventArgs e)
        {
            TextBox box = (TextBox)sender;
            Binding binding = BindingOperations.GetBinding(box, TextBox.TextProperty);
            var name = binding.ElementName;
            ScrollBar bar = (ScrollBar)this.FindName(name);
            if (e.Delta > 0)
            {
                bar.Value++;
            }
            else
            {
                bar.Value--;
            }
        }

        private void ScrollBarHue_MouseWheel(object sender, MouseWheelEventArgs e)
        {
            ScrollBar bar = (ScrollBar)sender;
            if (e.Delta > 0)
            {
                bar.Value++;
            }
            else
            {
                bar.Value--;
            }
        }

        private void ComboBoxInitialize()
        {
            ComboBoxSavePixelFormat.Items.Add(PixelFormats.Bgr24);
            ComboBoxSavePixelFormat.Items.Add(PixelFormats.Pbgra32);
            ComboBoxSavePixelFormat.Items.Add(PixelFormats.Gray8);
            ComboBoxSavePixelFormat.Items.Add(PixelFormats.BlackWhite);
            ComboBoxSavePixelFormat.SelectedIndex = 0;
        }

        private void DrawHue()
        {
            if (RadioButtonHue.IsChecked == true)
            {
                MyImage.Source = GetHueRect(ScrollBarHue.Value, (int)ScrollBarSizeWidth.Value);
            }
        }
        private void DrawSaturation()
        {
            if (RadioButtonSaturaion.IsChecked == true)
            {
                MyImage.Source = GetSaturationRect(
                    (int)ScrollBarSizeWidth.Value,
                    (int)ScrollBarSizeHeight.Value,
                    (float)ScrollBarSaturation.Value / 100f
                    );
            }
        }
        private void DrawValue()
        {
            if (RadioButtonValue.IsChecked == true)
            {
                MyImage.Source = GetValueRect(
                    (int)ScrollBarSizeWidth.Value,
                    (int)ScrollBarSizeHeight.Value,
                    (float)(ScrollBarValue.Value / 100f));
            }
        }

       #region イベント


        private void RadioButtonHue_Checked(object sender, RoutedEventArgs e) => DrawHue();

        private void RadioButtonValue_Checked(object sender, RoutedEventArgs e) => DrawValue();

        private void RadioButtonSaturaion_Checked(object sender, RoutedEventArgs e) => DrawSaturation();
        private void ScrollBarValue_ValueChanged(object sender, RoutedPropertyChangedEventArgs<double> e) => DrawValue();

        private void ScrollBarSaturation_ValueChanged(object sender, RoutedPropertyChangedEventArgs<double> e) => DrawSaturation();

        private void ButtonSizeHeight360_Click(object sender, RoutedEventArgs e) => ScrollBarSizeHeight.Value = 360f;

        private void ButtonSizeHeight256_Click(object sender, RoutedEventArgs e) => ScrollBarSizeHeight.Value = 256f;

        private void ButtonSizeHeight100_Click(object sender, RoutedEventArgs e) => ScrollBarSizeHeight.Value = 100f;

        private void ButtonSizeWidth360_Click(object sender, RoutedEventArgs e) => ScrollBarSizeWidth.Value = 360f;

        private void ButtonSize256_Click(object sender, RoutedEventArgs e) => ScrollBarSizeWidth.Value = 256f;

        private void ButtonSize100_Click(object sender, RoutedEventArgs e) => ScrollBarSizeWidth.Value = 100f;
        private void ButtonValueSub_Click(object sender, RoutedEventArgs e) => ScrollBarValue.Value -= 10f;

        private void ButtonValueAdd_Click(object sender, RoutedEventArgs e) => ScrollBarValue.Value += 10f;

        private void ButtonSaturationSub_Click(object sender, RoutedEventArgs e) => ScrollBarSaturation.Value -= 10f;

        private void ButtonSaturationAdd_Click(object sender, RoutedEventArgs e) => ScrollBarSaturation.Value += 10f;
        private void ButtonHueSub30_Click(object sender, RoutedEventArgs e) => ScrollBarHue.Value -= 30f;

        private void ButtonHueAdd30_Click(object sender, RoutedEventArgs e) => ScrollBarHue.Value += 30f;

        private void TextBlock_GotFocus(object sender, RoutedEventArgs e)
        {
            TextBox box = (TextBox)sender;
            this.Dispatcher.InvokeAsync(() => { Task.Delay(10); box.SelectAll(); });
        }

        private void ScrollBarSize_ValueChanged(object sender, RoutedPropertyChangedEventArgs<double> e)
        {
            DrawHue();
            DrawSaturation();
            DrawValue();
        }

        private void ButtonSaveImage_Click(object sender, RoutedEventArgs e)
        {
            SaveImage((BitmapSource)MyImage.Source);
        }
       #endregion


        //彩度を指定、0から1
        private BitmapSource GetSaturationRect(int width, int height, float saturation)
        {
            var wb = new WriteableBitmap(width, height, 96, 96, PixelFormats.Pbgra32, null);
            int h = wb.PixelHeight;
            int w = wb.PixelWidth;
            int stride = wb.BackBufferStride;
            byte[] pixels = new byte[h * stride];
            wb.CopyPixels(pixels, stride, 0);
            int p = 0;
            Color color;
            //高さが1のときは明度を1fいに固定したいので特別
            if (height == 1)
            {
                for (int x = 0; x < width; ++x)
                {
                    p = (x * 4);
                    color = HSV.HSV2Color(
                        (x / (width - 1f)) * 359f,
                        saturation,
                        1f);
                    pixels[p + 3] = 255;
                    pixels[p + 2] = color.R;
                    pixels[p + 1] = color.G;
                    pixels[p + 0] = color.B;
                }
            }
            else
            {
                for (int y = 0; y < height; ++y)
                {
                    for (int x = 0; x < width; ++x)
                    {
                        p = y * stride + (x * 4);
                        color = HSV.HSV2Color(
                            (x / (width - 1f)) * 359f,
                            saturation,
                            y / (double)(height - 1f));
                        pixels[p + 3] = 255;
                        pixels[p + 2] = color.R;
                        pixels[p + 1] = color.G;
                        pixels[p + 0] = color.B;
                    }
                }
            }

            wb.WritePixels(new Int32Rect(0, 0, w, h), pixels, stride, 0);
            return wb;
        }

        //明度を指定、0から1
        private BitmapSource GetValueRect(int width, int height, float value)
        {
            var wb = new WriteableBitmap(width, height, 96, 96, PixelFormats.Pbgra32, null);
            int h = wb.PixelHeight;
            int w = wb.PixelWidth;
            int stride = wb.BackBufferStride;
            byte[] pixels = new byte[h * stride];
            wb.CopyPixels(pixels, stride, 0);
            int p = 0;
            Color color;
            //高さ1のときは彩度を1fに固定したいので特別
            if (height == 1)
            {
                for (int x = 0; x < width; ++x)
                {
                    p = (x * 4);
                    color = HSV.HSV2Color(
                        (x * 359f) / (width - 1f),
                        1f,
                        value);
                    pixels[p + 3] = 255;
                    pixels[p + 2] = color.R;
                    pixels[p + 1] = color.G;
                    pixels[p + 0] = color.B;
                }
            }
            else
            {
                for (int y = 0; y < height; ++y)
                {
                    for (int x = 0; x < width; ++x)
                    {
                        p = y * stride + (x * 4);
                        color = HSV.HSV2Color(
                            (x * 359f) / (width - 1f),                            
                            y / (double)(height - 1f),
                            value);
                        pixels[p + 3] = 255;
                        pixels[p + 2] = color.R;
                        pixels[p + 1] = color.G;
                        pixels[p + 0] = color.B;
                    }
                }
            }
            wb.WritePixels(new Int32Rect(0, 0, w, h), pixels, stride, 0);
            return wb;
        }



        private BitmapSource GetHueRect(double hue, int size)
        {
            var wb = new WriteableBitmap(size, size, 96, 96, PixelFormats.Pbgra32, null);
            int h = wb.PixelHeight;
            int w = wb.PixelWidth;
            int stride = wb.BackBufferStride;
            byte[] pixels = new byte[h * stride];
            wb.CopyPixels(pixels, stride, 0);
            int p = 0;
            Color color;
            for (int y = 0; y < h; ++y)
            {
                for (int x = 0; x < w; ++x)
                {
                    p = y * stride + (x * 4);
                    color = HSV.HSV2Color(hue, (double)x / (size - 1f), (double)y / (size - 1f));
                    pixels[p + 3] = 255;
                    pixels[p + 2] = color.R;
                    pixels[p + 1] = color.G;
                    pixels[p + 0] = color.B;
                }
            }
            wb.WritePixels(new Int32Rect(0, 0, size, size), pixels, stride, 0);
            return wb;
        }



        private void SaveImage(BitmapSource source)
        {
            source = new FormatConvertedBitmap(source, (PixelFormat)ComboBoxSavePixelFormat.SelectedItem, null, 0);
            var saveFileDialog = new Microsoft.Win32.SaveFileDialog();
            saveFileDialog.Filter = "*.png|*.png|*.bmp|*.bmp|*.tiff|*.tiff";
            saveFileDialog.AddExtension = true;
            saveFileDialog.FileName = "huetestrect";
            if (saveFileDialog.ShowDialog() == true)
            {
                BitmapEncoder encoder = new BmpBitmapEncoder();
                if (saveFileDialog.FilterIndex == 1)
                {
                    encoder = new PngBitmapEncoder();
                }
                else if (saveFileDialog.FilterIndex == 2)
                {
                    encoder = new BmpBitmapEncoder();
                }
                else if (saveFileDialog.FilterIndex == 3)
                {
                    encoder = new TiffBitmapEncoder();
                }
                encoder.Frames.Add(BitmapFrame.Create(source));

                using (var fs = new FileStream(saveFileDialog.FileName, FileMode.Create, FileAccess.Write))
                {
                    encoder.Save(fs);
                }
            }
        }
    }
}
太字のところがグラデーション画像を作っているところ
 
 
 
 
20180226forMyBlog/2080226_HSVRect at master · gogowaten/20180226forMyBlog
https://github.com/gogowaten/20180226forMyBlog/tree/master/2080226_HSVRect
アプリの実行ファイルは
binフォルダのReleaseフォルダの2080226_HSVRect.exe
起動するには同じフォルダにあるHSV.dllも必要
 
めんどくさいいい
イメージ 18
HSV.dllをダウンロードしようとすると警告が出る
まだある
実行ファイルとdllを用意してこれを起動しようとすると
イメージ 15
こんなのが出てくる
詳細情報を押すと
 
イメージ 16
実行を押すと
 
イメージ 17
やっと起動できた
2回目以降は普通に起動できるようになった
 
 
関連記事
10日前
WPF、Color(RGB)とHSVを相互変換するdll作ってみた、オブジェクトブラウザ使ってみた ( ソフトウェア ) - 午後わてんのブログ - Yahoo!ブログ
https://blogs.yahoo.co.jp/gogowaten/15380324.html

f:id:gogowaten:20191212002414p:plain