「彩度調整 - 酢ろぐ!」では、彩度を求める計算を簡略化していたのですが、NuGet Pakageを作るついでにチェックしていると計算ロジックが微妙かも……と思ってしまったので、ピクセルデータを一旦HSVへ変換した上で彩度を調整し、RGBに戻すように調整しました。
RGBからHSVへの変換、HSVからRGBへの変換方法が書かれているサイトは結構あるのですが、一番分かりやすく変換方法が書かれていたのがWikipediaのHSV色空間でした。この記事を元にRGB to HSV、HSV to RGBを実装してみました。
今日書いたコードは、GitHubにアップしています。⇒ HSV.cs
**RGB to HSV
byte型のRGB値からHSVへ変換します。それぞれHue(色相)、Saturation(彩度)、Value(明度)を保持するプロパティを作成します。
|cs| using System;
namespace Softbuild.Media.Effects
{
///
/// <summary>
/// HSV クラスの新しいインスタンスを初期化します。
/// </summary>
/// <param name="hsv">HSV各要素の値が格納されたdouble配列</param>
public HSV(double[] hsv)
{
Hue = hsv[0];
Saturation = hsv[1];
Value = hsv[2];
}
/// <summary>
/// 色相
/// </summary>
public double Hue { get; set; }
/// <summary>
/// 彩度
/// </summary>
public double Saturation { get; set; }
/// <summary>
/// 明度
/// </summary>
public double Value { get; set; }
||<
HSVクラスのstaticとしてFromRGBメソッドを作成します。
|cs| ///
/// RGB値を元に HSV クラスの新しいインスタンスを初期化します。 /// /// R要素の値 /// G要素の値 /// B要素の値 ///HSVオブジェクト public static HSV FromRGB(double r, double g, double b) { // R、GおよびBが0.0を最小量、1.0を最大値とする0.0から1.0の範囲にある r /= 255; g /= 255; b /= 255;
var max = Math.Max(Math.Max(r, g), b);
var min = Math.Min(Math.Min(r, g), b);
var sub = max - min;
double h = 0, s = 0, v = 0;
// Calculate Hue
if (sub == 0)
{
// MAX = MIN(例・S = 0)のとき、 Hは定義されない。
h = 0;
}
else
{
if (max == r)
{
h = (60 * (g - b) / sub) + 0;
}
else if (max == g)
{
h = (60 * (b - r) / sub) + 120;
}
else if (max == b)
{
h = (60 * (r - g) / sub) + 240;
}
// さらに H += 360 if H < 0
if (h < 0)
{
h += 360;
}
}
// Calculate Saturation
if (max > 0)
{
s = sub / max;
}
// Calculate Value
v = max;
return new HSV(h, s, v);
}
||<
**HSV to RGB
計算結果を格納するRGBクラスを用意します。
|cs| using System;
namespace Softbuild.Media.Effects
{
///
/// <summary>
/// RGB クラスの新しいインスタンスを初期化します。
/// </summary>
/// <param name="r">赤成分</param>
/// <param name="g">緑成分</param>
/// <param name="b">青成分</param>
public RGB(double r, double g, double b)
{
Red = (byte)r;
Green = (byte)g;
Blue = (byte)b;
}
/// <summary>
/// 赤成分
/// </summary>
public byte Red { get; set; }
/// <summary>
/// 緑成分
/// </summary>
public byte Green { get; set; }
/// <summary>
/// 青成分
/// </summary>
public byte Blue { get; set; }
}
} ||<
HSVクラスにToRGBメソッドを追加します。
|cs| ///
/// RGBへ変換する /// ///RGBオブジェクト public RGB ToRGB() { // まず、もしSが0.0と等しいなら、最終的な色は無色もしくは灰色である。 if (Saturation == 0) { return new RGB(Value * 255, Value * 255, Value * 255); }
double r = 0, g = 0, b = 0;
double f = 0;
double p = 0, q = 0, t = 0;
//var h = Math.Min(360.0, Math.Max(0, Hue));
// 角座標系で、Hの範囲は0から360までであるが、その範囲を超えるHは360.0で
// 割った剰余(またはモジュラ演算)でこの範囲に対応させることができる。
// たとえば-30は330と等しく、480は120と等しくなる。
var h = Hue % 360;
var s = Math.Min(1.0, Math.Max(0, Saturation));
var v = Math.Min(1.0, Math.Max(0, Value));
var hi = (int)(h / 60);
f = (h / 60) - hi;
p = v * (1 - s);
q = v * (1 - f * s);
t = v * (1 - (1 - f) * s);
if (hi == 0)
{
r = v; g = t; b = p;
}
else if (hi == 1)
{
r = q; g = v; b = p;
}
else if (hi == 2)
{
r = p; g = v; b = t;
}
else if (hi == 3)
{
r = p; g = q; b = v;
}
else if (hi == 4)
{
r = t; g = p; b = v;
}
else if (hi == 5)
{
r = v; g = p; b = q;
}
return new RGB(r * 255, g * 255, b * 255);
}
||<
**SaturationEffect.cs
RGBクラスとHSVクラスを導入したので、SaturationEffectクラスの実装も変更してみます。
|cs| ///
/// 彩度調整処理をおこなう /// /// ビットマップの幅 /// ビットマップの高さ /// 処理前のピクセルデータ ///処理後のピクセルデータ public byte Effect(int width, int height, byte source) { int pixelCount = width * height; var dest = new byte[source.Length];
for (int i = 0; i < pixelCount; i++)
{
var index = i * 4;
// 処理前のピクセルの各ARGB要素を取得する
double b = source[index + 0];
double g = source[index + 1];
double r = source[index + 2];
double a = source[index + 3];
// 色空間をRGBからHSVへ変換する
var hsv = HSV.FromRGB(r, g, b);
// 彩度の調整をおこなう
hsv.Saturation *= Saturation;
// 色空間をHSVからRGBへ変換する
var rgb = hsv.ToRGB();
int db = rgb.Blue;
int dg = rgb.Green;
int dr = rgb.Red;
// 処理後のバッファへピクセル情報を保存する
dest[index + 0] = (byte)Math.Min(255, Math.Max(0, db));
dest[index + 1] = (byte)Math.Min(255, Math.Max(0, dg));
dest[index + 2] = (byte)Math.Min(255, Math.Max(0, dr));
dest[index + 3] = source[index + 3];
}
return dest;
}
||<