RGBカラーの色相をシフトする関数を書こうとしています。具体的にはiOSアプリで使用していますが、数学は普遍的です。
次のグラフは、色相に対するR、G、Bの値の変化を示しています。
それを見ると、色をシフトする関数を書くのは比較的簡単であるように思われます、そして私はより計算的に高価になると思います。
これまでに私が持っているのは、どのような作品ですか。純粋な黄色、シアン、またはマゼンタからシフトする場合は、完全に機能しますが、それ以外の場合、いくつかの場所で少しはやくなります。
Color4f ShiftHue(Color4f c, float d) {
if (d==0) {
return c;
}
while (d<0) {
d+=1;
}
d *= 3;
float original[] = {c.red, c.green, c.blue};
float returned[] = {c.red, c.green, c.blue};
// big shifts
for (int i=0; i<3; i++) {
returned[i] = original[(i+((int) d))%3];
}
d -= (float) ((int) d);
original[0] = returned[0];
original[1] = returned[1];
original[2] = returned[2];
float lower = MIN(MIN(c.red, c.green), c.blue);
float upper = MAX(MAX(c.red, c.green), c.blue);
float spread = upper - lower;
float shift = spread * d * 2;
// little shift
for (int i = 0; i < 3; ++i) {
// if middle value
if (original[(i+2)%3]==upper && original[(i+1)%3]==lower) {
returned[i] -= shift;
if (returned[i]<lower) {
returned[(i+1)%3] += lower - returned[i];
returned[i]=lower;
} else
if (returned[i]>upper) {
returned[(i+2)%3] -= returned[i] - upper;
returned[i]=upper;
}
break;
}
}
return Color4fMake(returned[0], returned[1], returned[2], c.alpha);
}
私はあなたがUIColorsでこれを行うことができ、次のようなもので色相をシフトできることを知っています:
CGFloat hue;
CGFloat sat;
CGFloat bri;
[[UIColor colorWithRed:parent.color.red green:parent.color.green blue:parent.color.blue alpha:1] getHue:&hue saturation:&sat brightness:&bri alpha:nil];
hue -= .03;
if (hue<0) {
hue+=1;
}
UIColor *tempColor = [UIColor colorWithHue:hue saturation:sat brightness:bri alpha:1];
const float* components= CGColorGetComponents(tempColor.CGColor);
color = Color4fMake(components[0], components[1], components[2], 1);
ただし、iOS 5でのみ機能し、多数のカラーオブジェクトを割り当ててからRGBからHSBに変換してから戻すまでの間に、それはかなりやり過ぎに思えます。
最終的にはルックアップテーブルを使用したり、アプリケーションで色を事前に計算したりするかもしれませんが、コードを機能させる方法があるかどうか本当に知りたいです。ありがとう!
Editコメントごとに「すべて」が「線形近似可能」に変更されました。
編集2オフセットを追加します。
基本的に、必要な手順は
RBG->HSV->Update hue->RGB
これらは線形行列変換(つまり、連想)で近似できるため、厄介な変換や精度の損失なしに、1つのステップで実行できます。変換行列を互いに複数乗算し、それを使用して色を変換します。
ここにステップバイステップのクイックがあります http://beesbuzz.biz/code/hsv_color_transforms.php
以下はC++コードです(飽和と値の変換が削除されています)。
Color TransformH(
const Color &in, // color to transform
float H
)
{
float U = cos(H*M_PI/180);
float W = sin(H*M_PI/180);
Color ret;
ret.r = (.299+.701*U+.168*W)*in.r
+ (.587-.587*U+.330*W)*in.g
+ (.114-.114*U-.497*W)*in.b;
ret.g = (.299-.299*U-.328*W)*in.r
+ (.587+.413*U+.035*W)*in.g
+ (.114-.114*U+.292*W)*in.b;
ret.b = (.299-.3*U+1.25*W)*in.r
+ (.587-.588*U-1.05*W)*in.g
+ (.114+.886*U-.203*W)*in.b;
return ret;
}
RGB色空間は立方体を表します。この立方体を(0,0,0)から(255,255,255)まで対角軸を中心に回転させて、色相を変更することができます。一部の結果は0〜255の範囲外にあり、クリップする必要があることに注意してください。
やっとこのアルゴリズムをコーディングする機会を得た。これはPython=にありますが、選択した言語に簡単に変換できるはずです。3D回転の式は次のようになります http://en.wikipedia.org/wiki/Rotation_matrix #Rotation_matrix_from_axis_and_angle
編集:以前に投稿したコードを見た場合は無視してください。回転の数式を見つけるのがとても大変だったので、マトリックスベースのソリューションを数式に変換しましたが、マトリックスがずっと最良の形であることに気づきませんでした。軸単位のベクトル値に定数sqrt(1/3)を使用して、行列の計算をまだ簡略化しましたが、これは精神的に参照にはるかに近く、ピクセルごとの計算で単純ですapply
上手。
from math import sqrt,cos,sin,radians
def clamp(v):
if v < 0:
return 0
if v > 255:
return 255
return int(v + 0.5)
class RGBRotate(object):
def __init__(self):
self.matrix = [[1,0,0],[0,1,0],[0,0,1]]
def set_hue_rotation(self, degrees):
cosA = cos(radians(degrees))
sinA = sin(radians(degrees))
self.matrix[0][0] = cosA + (1.0 - cosA) / 3.0
self.matrix[0][1] = 1./3. * (1.0 - cosA) - sqrt(1./3.) * sinA
self.matrix[0][2] = 1./3. * (1.0 - cosA) + sqrt(1./3.) * sinA
self.matrix[1][0] = 1./3. * (1.0 - cosA) + sqrt(1./3.) * sinA
self.matrix[1][1] = cosA + 1./3.*(1.0 - cosA)
self.matrix[1][2] = 1./3. * (1.0 - cosA) - sqrt(1./3.) * sinA
self.matrix[2][0] = 1./3. * (1.0 - cosA) - sqrt(1./3.) * sinA
self.matrix[2][1] = 1./3. * (1.0 - cosA) + sqrt(1./3.) * sinA
self.matrix[2][2] = cosA + 1./3. * (1.0 - cosA)
def apply(self, r, g, b):
rx = r * self.matrix[0][0] + g * self.matrix[0][1] + b * self.matrix[0][2]
gx = r * self.matrix[1][0] + g * self.matrix[1][1] + b * self.matrix[1][2]
bx = r * self.matrix[2][0] + g * self.matrix[2][1] + b * self.matrix[2][2]
return clamp(rx), clamp(gx), clamp(bx)
上記の結果は次のとおりです。
http://www.graficaobscura.com/matrix/index.html で同じアイデアの異なる実装を見つけることができます
私はここで見つけたほとんどの答えに失望しました、いくつかは欠陥があり、基本的には完全に間違っていました。私はこれを理解しようと3時間以上費やしてしまいました。 Mark Ransomの答えは正しいですが、MATLABでも検証されている完全なCソリューションを提供したいと思います。私はこれを徹底的にテストしました、そしてここにCコードがあります:
#include <math.h>
typedef unsigned char BYTE; //define an "integer" that only stores 0-255 value
typedef struct _CRGB //Define a struct to store the 3 color values
{
BYTE r;
BYTE g;
BYTE b;
}CRGB;
BYTE clamp(float v) //define a function to bound and round the input float value to 0-255
{
if (v < 0)
return 0;
if (v > 255)
return 255;
return (BYTE)v;
}
CRGB TransformH(const CRGB &in, const float fHue)
{
CRGB out;
const float cosA = cos(fHue*3.14159265f/180); //convert degrees to radians
const float sinA = sin(fHue*3.14159265f/180); //convert degrees to radians
//calculate the rotation matrix, only depends on Hue
float matrix[3][3] = {{cosA + (1.0f - cosA) / 3.0f, 1.0f/3.0f * (1.0f - cosA) - sqrtf(1.0f/3.0f) * sinA, 1.0f/3.0f * (1.0f - cosA) + sqrtf(1.0f/3.0f) * sinA},
{1.0f/3.0f * (1.0f - cosA) + sqrtf(1.0f/3.0f) * sinA, cosA + 1.0f/3.0f*(1.0f - cosA), 1.0f/3.0f * (1.0f - cosA) - sqrtf(1.0f/3.0f) * sinA},
{1.0f/3.0f * (1.0f - cosA) - sqrtf(1.0f/3.0f) * sinA, 1.0f/3.0f * (1.0f - cosA) + sqrtf(1.0f/3.0f) * sinA, cosA + 1.0f/3.0f * (1.0f - cosA)}};
//Use the rotation matrix to convert the RGB directly
out.r = clamp(in.r*matrix[0][0] + in.g*matrix[0][1] + in.b*matrix[0][2]);
out.g = clamp(in.r*matrix[1][0] + in.g*matrix[1][1] + in.b*matrix[1][2]);
out.b = clamp(in.r*matrix[2][0] + in.g*matrix[2][1] + in.b*matrix[2][2]);
return out;
}
注:回転行列は色相にのみ依存(fHue
)したがって、matrix[3][3]
を計算したら、次のことができます再利用同じ色相変換を受けている画像内のすべてのピクセル!これにより、効率が大幅に向上します。以下は、結果を検証するMATLABコードです。
function out = TransformH(r,g,b,H)
cosA = cos(H * pi/180);
sinA = sin(H * pi/180);
matrix = [cosA + (1-cosA)/3, 1/3 * (1 - cosA) - sqrt(1/3) * sinA, 1/3 * (1 - cosA) + sqrt(1/3) * sinA;
1/3 * (1 - cosA) + sqrt(1/3) * sinA, cosA + 1/3*(1 - cosA), 1/3 * (1 - cosA) - sqrt(1/3) * sinA;
1/3 * (1 - cosA) - sqrt(1/3) * sinA, 1/3 * (1 - cosA) + sqrt(1/3) * sinA, cosA + 1/3 * (1 - cosA)];
in = [r, g, b]';
out = round(matrix*in);
end
以下は、両方のコードで再現可能なサンプル入出力です。
TransformH(86,52,30,210)
ans =
36
43
88
したがって、[86,52,30]
の入力RGBは、[36,43,88]
の色相を使用して210
に変換されました。
基本的に2つのオプションがあります。
2の実装方法はよくわかりませんが、基本的には変換行列を作成し、この行列で画像をフィルター処理する必要があります。ただし、色相のみを変更するのではなく、画像の色を変更します。これが問題なければ、これはオプションである可能性がありますが、そうでない場合は変換を回避できません。
編集
少し調べてみると this であり、私の考えを裏付けています。要約すると、正確な結果が必要な場合は、RGBからHSVへの変換をお勧めします。線形変換によって元のRGB画像を変更することも結果につながりますが、これはむしろ画像に色合いを付けます。違いは次のとおりです。RGBからHSVへの変換は非線形ですが、変換は線形です。
投稿は古く、元の投稿者はiosコードを探していましたが、私はビジュアルベーシックコードの検索を介してここに送信されたので、私のようなすべての人のために、Markのコードをvb .netモジュールに変換しました。
Public Module HueAndTry
Public Function ClampIt(ByVal v As Double) As Integer
Return CInt(Math.Max(0F, Math.Min(v + 0.5, 255.0F)))
End Function
Public Function DegreesToRadians(ByVal degrees As Double) As Double
Return degrees * Math.PI / 180
End Function
Public Function RadiansToDegrees(ByVal radians As Double) As Double
Return radians * 180 / Math.PI
End Function
Public Sub HueConvert(ByRef rgb() As Integer, ByVal degrees As Double)
Dim selfMatrix(,) As Double = {{1, 0, 0}, {0, 1, 0}, {0, 0, 1}}
Dim cosA As Double = Math.Cos(DegreesToRadians(degrees))
Dim sinA As Double = Math.Sin(DegreesToRadians(degrees))
Dim sqrtOneThirdTimesSin As Double = Math.Sqrt(1.0 / 3.0) * sinA
Dim oneThirdTimesOneSubCos As Double = 1.0 / 3.0 * (1.0 - cosA)
selfMatrix(0, 0) = cosA + (1.0 - cosA) / 3.0
selfMatrix(0, 1) = oneThirdTimesOneSubCos - sqrtOneThirdTimesSin
selfMatrix(0, 2) = oneThirdTimesOneSubCos + sqrtOneThirdTimesSin
selfMatrix(1, 0) = selfMatrix(0, 2)
selfMatrix(1, 1) = cosA + oneThirdTimesOneSubCos
selfMatrix(1, 2) = selfMatrix(0, 1)
selfMatrix(2, 0) = selfMatrix(0, 1)
selfMatrix(2, 1) = selfMatrix(0, 2)
selfMatrix(2, 2) = cosA + oneThirdTimesOneSubCos
Dim rx As Double = rgb(0) * selfMatrix(0, 0) + rgb(1) * selfMatrix(0, 1) + rgb(2) * selfMatrix(0, 2)
Dim gx As Double = rgb(0) * selfMatrix(1, 0) + rgb(1) * selfMatrix(1, 1) + rgb(2) * selfMatrix(1, 2)
Dim bx As Double = rgb(0) * selfMatrix(2, 0) + rgb(1) * selfMatrix(2, 1) + rgb(2) * selfMatrix(2, 2)
rgb(0) = ClampIt(rx)
rgb(1) = ClampIt(gx)
rgb(2) = ClampIt(bx)
End Sub
End Module
私は一般的な用語を(長い)変数に入れましたが、それ以外の場合は簡単な変換です-私のニーズにはうまくいきました。
ちなみに、私はマークに優れたコードへの賛成票を残そうとしましたが、それを表示するための十分な票がありませんでした(ヒント、ヒント)。
JavaScriptの実装(VladimirのPHP上記に基づく)
const deg = Math.PI / 180;
function rotateRGBHue(r, g, b, hue) {
const cosA = Math.cos(hue * deg);
const sinA = Math.sin(hue * deg);
const neo = [
cosA + (1 - cosA) / 3,
(1 - cosA) / 3 - Math.sqrt(1 / 3) * sinA,
(1 - cosA) / 3 + Math.sqrt(1 / 3) * sinA,
];
const result = [
r * neo[0] + g * neo[1] + b * neo[2],
r * neo[2] + g * neo[0] + b * neo[1],
r * neo[1] + g * neo[2] + b * neo[0],
];
return result.map(x => uint8(x));
}
function uint8(value) {
return 0 > value ? 0 : (255 < value ? 255 : Math.round(value));
}
HSVへの変換が最も理にかなっているようです。 Sassは驚くべきカラーヘルパーを提供します。 Rubyですが、役に立つかもしれません。
スコット……正確には違う。アルゴはHSL/HSVと同じように機能するようですが、より高速です。また、配列の最初の3つの要素に灰色の係数を単純に乗算すると、輝度が増減します。
例... Rec709のグレースケールにはこれらの値があります[GrayRedFactor_Rec709:R $ 0.212671 GrayGreenFactor_Rec709:R $ 0.715160 GrayBlueFactor_Rec709:R $ 0.072169]
Self.matrix [x] [x]をGreyFactorのコレスポンデントと乗算すると、彩度Exに触れずに輝度が減少します。
def set_hue_rotation(self, degrees):
cosA = cos(radians(degrees))
sinA = sin(radians(degrees))
self.matrix[0][0] = (cosA + (1.0 - cosA) / 3.0) * 0.212671
self.matrix[0][1] = (1./3. * (1.0 - cosA) - sqrt(1./3.) * sinA) * 0.715160
self.matrix[0][2] = (1./3. * (1.0 - cosA) + sqrt(1./3.) * sinA) * 0.072169
self.matrix[1][0] = self.matrix[0][2] <---Not sure, if this is the right code, but i think you got the idea
self.matrix[1][1] = self.matrix[0][0]
self.matrix[1][2] = self.matrix[0][1]
また、その逆も当てはまります。代わりに除算すると、明度が劇的に増加します。
もちろん、このアルゴリズムは、飽和を必要としない限り、HSLの優れた代替品になる可能性があります。
これを試してみてください...色相を1度だけ回転させ(画像の同じ知覚感度を維持しながらアルゴを適切に機能させるだけです)、それらの係数を掛けます。
WebGLバージョン:
vec3 hueShift(vec3 col, float shift){
vec3 m = vec3(cos(shift), -sin(shift) * .57735, 0);
m = vec3(m.xy, -m.y) + (1. - m.x) * .33333;
return mat3(m, m.zxy, m.yzx) * col;
}
上記の(ガンマ補正されていない)色相シフトをパラメーター化されたHLSLピクセルシェーダーとして必要とするすべての人(WPFアプリケーションで一緒に使用し、共有するだけだと思っていました):
sampler2D implicitInput : register(s0);
float factor : register(c0);
float4 main(float2 uv : TEXCOORD) : COLOR
{
float4 color = tex2D(implicitInput, uv);
float h = 360 * factor; //Hue
float s = 1; //Saturation
float v = 1; //Value
float M_PI = 3.14159265359;
float vsu = v * s*cos(h*M_PI / 180);
float vsw = v * s*sin(h*M_PI / 180);
float4 result;
result.r = (.299*v + .701*vsu + .168*vsw)*color.r
+ (.587*v - .587*vsu + .330*vsw)*color.g
+ (.114*v - .114*vsu - .497*vsw)*color.b;
result.g = (.299*v - .299*vsu - .328*vsw)*color.r
+ (.587*v + .413*vsu + .035*vsw)*color.g
+ (.114*v - .114*vsu + .292*vsw)*color.b;
result.b = (.299*v - .300*vsu + 1.25*vsw)*color.r
+ (.587*v - .588*vsu - 1.05*vsw)*color.g
+ (.114*v + .886*vsu - .203*vsw)*color.b;;
result.a = color.a;
return result;
}
Glslで最もコンパクトなバージョン、私はすることができました:
vec3 hs(vec3 c, float s){
vec3 m=vec3(cos(s),s=sin(s)*.5774,-s);
return c*mat3(m+=(1.-m.x)/3.,m.zxy,m.yzx);
}
優れたコードですが、単にself.matrix [2] [0]、self.matrix [2] [1]、self.matrix [2] [1]を使用しない方が高速になる可能性があるのではないでしょうか
したがって、set_hue_rotationは次のように簡単に記述できます。
def set_hue_rotation(self, degrees):
cosA = cos(radians(degrees))
sinA = sin(radians(degrees))
self.matrix[0][0] = cosA + (1.0 - cosA) / 3.0
self.matrix[0][1] = 1./3. * (1.0 - cosA) - sqrt(1./3.) * sinA
self.matrix[0][2] = 1./3. * (1.0 - cosA) + sqrt(1./3.) * sinA
self.matrix[1][0] = self.matrix[0][2] <---Not sure, if this is the right code, but i think you got the idea
self.matrix[1][1] = self.matrix[0][0]
self.matrix[1][2] = self.matrix[0][1]
また、Markのアルゴはより正確な結果を生成します。
たとえば、HSV色空間を使用して色相を180°に回転させると、画像は赤みがかった色調になります。
しかし、マークのアルゴリズムでは、画像は適切に回転しています。たとえば、スキントーン(PSPでは色相= 17、土= 170、L = 160)は適切に青に変わり、PSPでは約144の色相を持ち、画像の他のすべての色は適切に回転します。
アルゴは、次の式で定義される赤、緑、青のアークタンの対数関数であるHueにすぎないので意味があります。
Hue = arctan((logR-logG)/(logR-logG+2*LogB))
PHPの実装:
class Hue
{
public function convert(int $r, int $g, int $b, int $hue)
{
$cosA = cos($hue * pi() / 180);
$sinA = sin($hue * pi() / 180);
$neo = [
$cosA + (1 - $cosA) / 3,
(1 - $cosA) / 3 - sqrt(1 / 3) * $sinA,
(1 - $cosA) / 3 + sqrt(1 / 3) * $sinA,
];
$result = [
$r * $neo[0] + $g * $neo[1] + $b * $neo[2],
$r * $neo[2] + $g * $neo[0] + $b * $neo[1],
$r * $neo[1] + $g * $neo[2] + $b * $neo[0],
];
return array_map([$this, 'crop'], $result);
}
private function crop(float $value)
{
return 0 > $value ? 0 : (255 < $value ? 255 : (int)round($value));
}
}