読者です 読者をやめる 読者になる 読者になる

SourceChord

C#とXAML好きなプログラマの備忘録。最近はWPF系の話題が中心です。

RGB⇔HSVの変換をするクラス

アプリを作ってて色を扱っていると、RGBではなくHSV表色系で色を扱いたいと思うときが多々あります。


WPFで使うColor構造体はRGBで色を扱うので、HSVで扱うHSVColor構造体を作り、
Color⇔HSVColor構造体を相互に変換できるようにしてみました。


変換の式はこの辺を参考に
http://ja.wikipedia.org/wiki/HSV%E8%89%B2%E7%A9%BA%E9%96%93

特に込み入ったことはせずに、変換式をそのまま書いてます。
ちなみに、Color構造体にToHSV()という拡張メソッドを追加しています。
こうすることで、以下の二つのメソッドでRGB⇔HSVの相互変換をできるようにしてます。
RGB⇒HSV・・・Color.ToHSV()メソッド
HSV⇒RGB・・・HSVColor.ToRGB()メソッド

HSVColor.cs
using System;
using System.Linq;
using System.Windows.Media;

namespace HSVConverter.Common
{
    public struct HSVColor
    {
        public float H { get; set; }
        public float S { get; set; }
        public float V { get; set; }

        public static HSVColor FromHSV(float h, float s, float v)
        {
            return new HSVColor() { H = h, S = s, V = v };
        }

        public override string ToString()
        {
            return string.Format("H:{0}, S:{1}, V:{2}", H, S, V);
        }

        public Color ToRGB()
        {
            int Hi = ((int)(H / 60.0)) % 6;
            float f = H / 60.0f - (int)(H / 60.0);
            float p = V * (1 - S);
            float q = V * (1 - f * S);
            float t = V * (1 - (1 - f) * S);

            switch (Hi)
            {
                case 0:
                    return FromRGB(V, t, p);
                case 1:
                    return FromRGB(q, V, p);
                case 2:
                    return FromRGB(p, V, t);
                case 3:
                    return FromRGB(p, q, V);
                case 4:
                    return FromRGB(t, p, V);
                case 5:
                    return FromRGB(V, p, q);
            }

            // ここには来ない
            throw new InvalidOperationException();
        }

        private Color FromRGB(float fr, float fg, float fb)
        {
            fr *= 255;
            fg *= 255;
            fb *= 255;
            byte r = (byte)((fr < 0) ? 0 : (fr > 255) ? 255 : fr);
            byte g = (byte)((fg < 0) ? 0 : (fg > 255) ? 255 : fg);
            byte b = (byte)((fb < 0) ? 0 : (fb > 255) ? 255 : fb);
            return Color.FromRgb(r, g, b);
        }
    }

    public static class ColorExtension
    {
        public static HSVColor ToHSV(this Color c)
        {
            float r = c.R / 255.0f;
            float g = c.G / 255.0f;
            float b = c.B / 255.0f;

            var list = new float[] { r, g, b };
            var max = list.Max();
            var min = list.Min();

            float h, s, v;
            if (max == min)
                h = 0;
            else if (max == r)
                h = (60 * (g - b) / (max - min) + 360) % 360;
            else if (max == g)
                h = 60 * (b - r) / (max - min) + 120;
            else
                h = 60 * (r - g) / (max - min) + 240;

            if (max == 0)
                s = 0;
            else
                s = (max - min) / max;

            v = max;

            return new HSVColor() { H = h, S = s, V = v };
        }
    }
}

動作チェック

一応、動作確認用に、以下のようなRGB/HSVの変換アプリを作ってみました。

MainWindow.xaml
<Window x:Class="HSVConverter.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:xctk="http://schemas.xceed.com/wpf/xaml/toolkit"
        Title="MainWindow"
        Width="400"
        Height="300">
    <Grid>
        <Grid.RowDefinitions>
            <RowDefinition />
            <RowDefinition />
        </Grid.RowDefinitions>

        <Rectangle x:Name="rctColor"
                   Width="100"
                   Height="100"
                   HorizontalAlignment="Center"
                   VerticalAlignment="Center"
                   Fill="#FFF4F4F5"
                   Stroke="Black" />

        <Grid Grid.Row="1">
            <Grid.ColumnDefinitions>
                <ColumnDefinition />
                <ColumnDefinition Width="Auto" />
                <ColumnDefinition />
            </Grid.ColumnDefinitions>

            <UniformGrid HorizontalAlignment="Center"
                         VerticalAlignment="Top"
                         Columns="2">
                <TextBlock Margin="5"
                           HorizontalAlignment="Right"
                           Text="R:" />
                <xctk:ByteUpDown Width="60"
                                 Height="23"
                                 Margin="5"
                                 HorizontalAlignment="Left"
                                 VerticalAlignment="Top"
                                 Value="{Binding RGBColor.R}" />
                <TextBlock Margin="5"
                           HorizontalAlignment="Right"
                           Text="G:" />
                <xctk:ByteUpDown Width="60"
                                 Height="23"
                                 Margin="5"
                                 HorizontalAlignment="Left"
                                 VerticalAlignment="Top"
                                 Value="{Binding RGBColor.G}" />
                <TextBlock Margin="5"
                           HorizontalAlignment="Right"
                           Text="B:" />
                <xctk:ByteUpDown Width="60"
                                 Height="23"
                                 Margin="5"
                                 HorizontalAlignment="Left"
                                 VerticalAlignment="Top"
                                 Value="{Binding RGBColor.B}" />
            </UniformGrid>

            <Button x:Name="btnRgbToHsv"
                    Grid.Column="1"
                    Width="75"
                    Margin="10"
                    VerticalAlignment="Top"
                    Click="btnRgbToHsv_Click"
                    Content="4"
                    FontFamily="Marlett"
                    FontSize="16" />
            <Button x:Name="btnHsvToRgb"
                    Grid.Column="1"
                    Width="75"
                    Margin="10,50,10,0"
                    VerticalAlignment="Top"
                    Click="btnHsvToRgb_Click"
                    Content="3"
                    FontFamily="Marlett"
                    FontSize="16" />

            <UniformGrid Grid.Column="2"
                         HorizontalAlignment="Center"
                         VerticalAlignment="Top"
                         Columns="2">
                <TextBlock Margin="5"
                           HorizontalAlignment="Right"
                           Text="H:" />
                <xctk:SingleUpDown Width="60"
                                   Height="23"
                                   Margin="5"
                                   HorizontalAlignment="Left"
                                   VerticalAlignment="Top"
                                   Value="{Binding HSVColor.H}" />
                <TextBlock Margin="5"
                           HorizontalAlignment="Right"
                           Text="S:" />
                <xctk:SingleUpDown Width="60"
                                   Height="23"
                                   Margin="5"
                                   HorizontalAlignment="Left"
                                   VerticalAlignment="Top"
                                   Value="{Binding HSVColor.S}" />
                <TextBlock Margin="5"
                           HorizontalAlignment="Right"
                           Text="V:" />
                <xctk:SingleUpDown Width="60"
                                   Height="23"
                                   Margin="5"
                                   HorizontalAlignment="Left"
                                   VerticalAlignment="Top"
                                   Value="{Binding HSVColor.V}" />
            </UniformGrid>

        </Grid>
    </Grid>
</Window>
MainWindow.xaml.cs
using HSVConverter.Common;
using System.Windows;
using System.Windows.Media;

namespace HSVConverter
{
    /// <summary>
    /// MainWindow.xaml の相互作用ロジック
    /// </summary>
    public partial class MainWindow : Window
    {
        /// <summary>
        /// 左側のカラムのRGB値を保持
        /// </summary>
        public Color RGBColor
        {
            get { return (Color)GetValue(RGBColorProperty); }
            set { SetValue(RGBColorProperty, value); }
        }
        // Using a DependencyProperty as the backing store for RGBColor.  This enables animation, styling, binding, etc...
        public static readonly DependencyProperty RGBColorProperty =
            DependencyProperty.Register("RGBColor", typeof(Color), typeof(MainWindow), new PropertyMetadata(Colors.Red));

        /// <summary>
        /// 右側カラムのHSV値を保持
        /// </summary>
        public HSVColor HSVColor
        {
            get { return (HSVColor)GetValue(HSVColorProperty); }
            set { SetValue(HSVColorProperty, value); }
        }
        // Using a DependencyProperty as the backing store for HSVColor.  This enables animation, styling, binding, etc...
        public static readonly DependencyProperty HSVColorProperty =
            DependencyProperty.Register("HSVColor", typeof(HSVColor), typeof(MainWindow), new PropertyMetadata(Colors.Red.ToHSV()));


        public MainWindow()
        {
            InitializeComponent();
            this.DataContext = this;
            rctColor.Fill = new SolidColorBrush(RGBColor);
        }

        private void btnRgbToHsv_Click(object sender, RoutedEventArgs e)
        {
            HSVColor = RGBColor.ToHSV();
            rctColor.Fill = new SolidColorBrush(RGBColor);
        }

        private void btnHsvToRgb_Click(object sender, RoutedEventArgs e)
        {
            RGBColor = HSVColor.ToRGB();
            rctColor.Fill = new SolidColorBrush(RGBColor);
        }
    }
}