// gColor.cpp #include "stdafx.h" #include "gColor.h" #include "gFilters.h" #include "..\..\Twister.h" double gColor::FromByte(int B) {return B*1/255.0;} int gColor::sgn(double c) {return c<=-1/256.0 ? -1 : (c>=1/256.0);} // to Compare a double value with zero using a tolerance of 1/256.0 int gColor::Compare(double a, double b) const {return sgn(b-a);} gColor::gColor(bool Grey) : R(Twister::GetDouble()), G(Twister::GetDouble()), B(Twister::GetDouble()), A(Twister::GetDouble()) {if(Grey) R=G=B;} gColor::gColor(BYTE R, BYTE G, BYTE B, BYTE A/*=0*/) : R(FromByte(R)), G(FromByte(G)), B(FromByte(B)), A(FromByte(A)) {} // 0-255 gColor::gColor(int R, int G, int B, int A/*=0*/) : R(FromByte(R)), G(FromByte(G)), B(FromByte(B)), A(FromByte(A)) {} // 0-255 gColor::gColor(float R, float G, float B, float A/*=0*/) : R(R), G(G), B(B), A(A) {} // 0-1 gColor::gColor(const double& R, const double& G, const double& B, const double& A/*=0*/) : R(R), G(G), B(B), A(A) {} // 0-1 gColor::gColor(COLORREF Color/*=NO_COLOR*/) {SetCOLORREF(Color);} gColor::gColor(const double& t) {SetFromHue(t);} // t in the Interval [0,1] gColor::~gColor() {} void gColor::SetRGB (BYTE r, BYTE g, BYTE b, BYTE a/*=0*/) {R=FromByte(r); G=FromByte(g); B=FromByte(b); A=FromByte(a);} // 0-255 void gColor::SetRGB (int r, int g, int b, int a/*=0*/) {R=FromByte(r); G=FromByte(g); B=FromByte(b); A=FromByte(a);} // 0-255 void gColor::SetRGB (float r, float g, float b, float a/*=0*/) {R=r; G=g; B=b; A=a;} // 0-1 void gColor::SetRGB (const double& r, const double& g, const double& b, const double& a/*=0*/) {R=r; G=g; B=b; A=a;} // 0-1 void gColor::SetBGR (DWORD Color/*=NO_COLOR*/) {SetBGRA(Color);} void gColor::SetBGRA (DWORD Color/*=NO_COLOR*/) {R=FromByte((Color&0xFF)); G=FromByte(((Color>>=8)&0xFF)); B=FromByte(((Color>>=8)&0xFF)); A=FromByte(((Color>>=8)&0xFF));} void gColor::SetRGB (DWORD Color/*=NO_COLOR*/) {SetRGBA(Color);} void gColor::SetRGBA (DWORD Color/*=NO_COLOR*/) {SetBGRA(Color); double t=R; R=B; B=t;} void gColor::SetCOLORREF(COLORREF Color/*=NO_COLOR*/) {SetBGRA(Color);} // Obvious one for MFC users void gColor::Set (DWORD Color/*=NO_COLOR*/) {SetRGBA(Color);} void gColor::SetLevel(BYTE Level) {R=G=B=FromByte(Level);} void gColor::SetR(BYTE c) {R=FromByte(c);} void gColor::SetG(BYTE c) {G=FromByte(c);} void gColor::SetB(BYTE c) {B=FromByte(c);} void gColor::SetA(BYTE c) {A=FromByte(c);} void gColor::SetR(float c) {R=c;} void gColor::SetG(float c) {G=c;} void gColor::SetB(float c) {B=c;} void gColor::SetA(float c) {A=c;} void gColor::SetR(const double& c) {R=c;} void gColor::SetG(const double& c) {G=c;} void gColor::SetB(const double& c) {B=c;} void gColor::SetA(const double& c) {A=c;} void gColor::SetFromHue(const double& H) { // H is Interval [0,1] if(H<1/3.) {// _ _ R=2-H*6; // Red: | \__/ | G=H*6; // 0 __ 1 B=0; // Green: |/ \__| }else if(H<2/3.) { // 0 __ 1 R=0; // Blue: |__/ \| G=4-H*6; // 0 | | 1 B=H*6-2; // 1/3 2/3 }else{ R=H*6-4; G=0; B=(1-H)*6; } if(R>1) R=1; if(G>1) G=1; if(B>1) B=1; } // Hue, Saturation, Lightness all in the Interval [0,1] void gColor::SetFromHSL(const double& H, const double& S, const double& L) { SetFromHue(H); R=Signed(R)*S+1; G=Signed(G)*S+1; B=Signed(B)*S+1; if(L<0.5) { R*=L; G*=L; B*=L; }else{ R=R*(1-L)+Signed(L); G=G*(1-L)+Signed(L); B=B*(1-L)+Signed(L); } } // Hue, Saturation, Value, all in the Interval [0,1] void gColor::SetFromHSV(const double& H, const double& S, const double& V){ SetFromHue(H); R=V*(1-S*(1-R)); G=V*(1-S*(1-G)); B=V*(1-S*(1-B)); } COLORREF gColor::GetCOLORREF() const {return GetBGR();} // Obvious one for MFC users DWORD gColor::GetRGB () const {return (((GetRb()<<8)|GetGb())<<8)|GetBb();} // True-color bitmap data uses this. DWORD gColor::GetRGBA() const {return (GetAb()<<24)|GetRGB();} DWORD gColor::GetBGR () const {return (((GetBb()<<8)|GetGb())<<8)|GetRb();} // Windows Device Context colors use this. DWORD gColor::GetBGRA() const {return (GetAb()<<24)|GetBGR();} BYTE gColor::GetRb() const {return BYTE(Round(255*R));} // Red [0-255] Round is used to prevent casting from clearing the processor pipeline! (See Global.h) BYTE gColor::GetGb() const {return BYTE(Round(255*G));} // Green [0-255] BYTE gColor::GetBb() const {return BYTE(Round(255*B));} // Blue [0-255] BYTE gColor::GetAb() const {return BYTE(Round(255*A));} // Alpha [0-255] int gColor::GetRi() const {return GetRb();} // Red [0-255] int gColor::GetGi() const {return GetGb();} // Green [0-255] int gColor::GetBi() const {return GetBb();} // Blue [0-255] int gColor::GetAi() const {return GetAb();} // Alpha [0-255] float gColor::GetRf() const {return float(R);} // Red [0-1] float gColor::GetGf() const {return float(G);} // Green [0-1] float gColor::GetBf() const {return float(B);} // Blue [0-1] float gColor::GetAf() const {return float(A);} // Alpha [0-1] double gColor::GetRd() const {return R;} // Red [0-1] double gColor::GetGd() const {return G;} // Green [0-1] double gColor::GetBd() const {return B;} // Blue [0-1] double gColor::GetAd() const {return A;} // Alpha [0-1] // Hue, Saturation, Lightness, all in the Interval [0,1] void gColor::GetHSL(double& H, double& S, double& L) const { double cMin=Min(R,G,B); double cMax=Max(R,G,B); double Delta=cMax-cMin; L=(cMin + cMax)/2; S=0; if((L>0)&&(L<1)) S=Delta/2/(L<0.5 ? L : (1-L)); H=0; if(Delta<=0) return; if((cMax==R)&&(cMax!=G)) H+= (G-B)/Delta; if((cMax==G)&&(cMax!=B)) H+=(2+(B-R)/Delta); if((cMax==B)&&(cMax!=R)) H+=(4+(R-G)/Delta); H/=6; if(H<0) H+=1; } // Hue, Saturation, Value, all in the Interval [0,1] void gColor::GetHSV(double& H, double& S, double& V) const { double cMin=Min(R,G,B); double cMax=Max(R,G,B); double Delta=cMax-cMin; V=cMax; S=(cMax>0 ? Delta/cMax : 0); H=0; if(Delta<=0) return; if((cMax==R)&&(cMax!=G)) H+= (G-B)/Delta; if((cMax==G)&&(cMax!=B)) H+=(2+(B-R)/Delta); if((cMax==B)&&(cMax!=R)) H+=(4+(R-G)/Delta); H/=6; if(H<0) H+=1; } void gColor::ToRGBAByteArray(BYTE (&Array)[4]) const { Array[0]=GetRb(); Array[1]=GetGb(); Array[2]=GetBb(); Array[3]=GetAb(); } void gColor::ToRGBAIntArray(int (&Array)[4]) const { Array[0]=GetRi(); Array[1]=GetGi(); Array[2]=GetBi(); Array[3]=GetAi(); } void gColor::ToRGBAFloatArray(float (&Array)[4]) const { Array[0]=GetRf(); Array[1]=GetGf(); Array[2]=GetBf(); Array[3]=GetAf(); } void gColor::ToRGBADoubleArray(double (&Array)[4]) const { Array[0]=GetRd(); Array[1]=GetGd(); Array[2]=GetBd(); Array[3]=GetAd(); } bool gColor::IsNoColor() const {return sgn(A)<0;} int gColor::Compare(const gColor& c) const { // For Sorting (Ignores Alpha Channel)! int Result=Compare(R, c.R); if(Result) return Result; Result=Compare(G, c.G); if(Result) return Result; return Compare(B, c.B); } bool gColor::operator==(COLORREF c) const {return GetCOLORREF()==c;} // Alpha is ignored! bool gColor::operator!=(COLORREF c) const {return GetCOLORREF()!=c;} // Alpha is ignored! bool gColor::operator==(const gColor& c) const {return Compare(c)==0;} // Alpha is ignored! bool gColor::operator!=(const gColor& c) const {return Compare(c)!=0;} // Alpha is ignored! bool operator==(const gColor& c1, const gColor& c2) {return c1.Compare(c2)==0;} // Alpha is ignored! bool operator!=(const gColor& c1, const gColor& c2) {return c1.Compare(c2)!=0;} // Alpha is ignored! // d is expected to be in the range [0,1]. Alpha is not ignored! gColor& gColor::operator*=(const double& d) {R*=d; G*=d; B*=d; return *this;} // Alpha is ignored! gColor gColor::operator* (const double& d) const {return gColor(R*d,G*d,B*d);} // Alpha is ignored! bool gColor::IsGrey(BYTE Tolerance/*=1/256.0*/) { // Tolerance specifies how close to Gray, 0 asks if R,G and B are identical (unlikely with doubles), 0.25 means that there may be a difference of at most 0.25 between two components return ((fabs(R-G)<=Tolerance) && (fabs(R-B)<=Tolerance) && (fabs(G-B)<=Tolerance)); } BYTE gColor::GetGreyLevel() const {return BYTE(255.0*GetPerceivedLuminance());} // Returns 0 to 1,Standard NTSC weights for eye response double gColor::GetAverageBrightness() const {return (R+G+B)/3.0;} // Avarage Luminance double gColor::GetStandardPerceivedLuminance() const {return 0.2126*R + 0.7152*G + 0.0722*B;} // Photometric/digital ITU-R double gColor::GetPerceivedLuminance() const {return 0.299*R + 0.587*G + 0.114*B;} // Digital CCIR601 (Use to get Greyscale Levels) Standard NTSC weights for eye response double gColor::GetSlowPerceivedLuminance() const {return sqrt(0.241*R*R + 0.691*G*G + 0.068*B*B);} double gColor::GetFastPerceivedLuminance() const {return (R+R+B+G+G+G)/6.0;} void gColor::SetSaturation(const double& t) { ASSERT(t>=0); ASSERT(t<=1); double Delta=GetSlowPerceivedLuminance(); R=Interpolate(t,Delta,R); G=Interpolate(t,Delta,G); B=Interpolate(t,Delta,B); } void gColor::SetLightness(const double& t) { // t is in the Interval [0,1] (Use above Exposure(...) to adjust from raw light levels) ASSERT(t>=0); ASSERT(t<=1); double H,S,L; GetHSL(H,S,L); SetFromHSL(H,S,t); } void gColor::SetValue(const double& t) { // t is in the Interval [0,1] (Use above Exposure(...) to adjust from raw light levels) ASSERT(t>=0); ASSERT(t<=1); double H,S,V; GetHSV(H,S,V); SetFromHSV(H,S,t); } void gColor::SetBrightness(const double& t) { // t is in the Interval [0,1] (Use above Exposure(...) to adjust from raw light levels) ASSERT(t>=0); ASSERT(t<=1); double d=GetAverageBrightness(); if(d==0) {R=G=B=t/3; return;} d=t/d; R*=d; G*=d; B*=d; Normalise(); } void gColor::Normalise() { if(R>1) Distribute(R,G,B); else if(G>1) Distribute(G,R,B); else if(B>1) Distribute(B,R,G); } void gColor::Distribute(double& A, double& B, double& C) const { double xs=(A-1)/2; // Take half the excess... A=1; B+=xs; // ... and share it between the other two: C+=xs; if(B>1) { // See if B overflowed: xs=B-1; // Get the excess... B=1; C+=xs; // ... and put it in C if(C>1) C=1; // Everything has overflowed: make white. }else if(C>1) { // See if C overflowed: xs=C-1; // Get the excess... C=1; B+=xs; // ... and put it in B if(B>1) B=1; // Everything has overflowed: make white. } } //__________________________________ Shaders _____________________________________ // These adjust return a shaded version of the existing colour without changing it gColor gColor::GetPearlColor(const double& x, const double& y) const { // x,y vary over the interval [0,1] double z=sqrt(fabs(1-x*x-y*y)); double s1=2*x+6*z; double s2=4*y+x+6*z; double ss1=(-1.5*x-0.7*y+2.5*z)/3.24; double ss2=(-0.7*x-1.5*y+2.5*z)/3.14; double ss =0.6*(pow(Unsigned(ss1), 37) +pow(Unsigned(ss2), 37)); // Ambient illumination s1=0.40*pow(Unsigned(s1/6.6), 20); // First hilight s2=0.35*pow(Unsigned(s2/8.6), 25); // Second hilight double r=GetRd(); double g=GetGd(); double b=GetBd(); double d=(x-y)/M_SQRT2; d=(0.6*d*d+0.2)*(r+g+b)+0.67*(s1+s2); // Diffuse return gColor(Saturate(d*r+s1*g+s2*b+ss), Saturate(d*g+s1*b+s2*r+ss), Saturate(d*b+s1*r+s2*g+ss)); } gColor gColor::GetGlassColor(const double& x, const double& y) const { // x,y vary over the interval [0,1] double z=sqrt(fabs(1-x*x-y*y)); double s1=2*x+6*z; double s2=4*y+x+6*z; double ss1=(-1.5*x-0.7*y+2.5*z)/3.24; double ss2=(-0.7*x-1.5*y+2.5*z)/3.14; double ss =4*(pow(Unsigned(ss1), 67) +pow(Unsigned(ss2), 67)); // Ambient illumination s1=3.5*pow(Unsigned(s1/7.6), 27); // First hilight s2=0.7*pow(Unsigned(s2/7.6), 11); // Second hilight return gColor(Saturate(GetRd()*(s1+s2)+ss), Saturate(GetGd()*(s1+s2)+ss), Saturate(GetBd()*(s1+s2)+ss)); } //____________________________ Mixers ________________________________________ void gColor::Mix(MixType Type, const double& t, const gColor& C, bool GreyScale/*=false*/) { switch(Type) { // Don't use a function pointer solution: these are likely to get inlined. case BlendMix : MixBlend (t,C,GreyScale); return; case MultiplyMix : MixMultiply (t,C,GreyScale); return; case ScreenMix : MixScreen (t,C,GreyScale); return; case OverlayMix : MixOverlay (t,C,GreyScale); return; case DivideMix : MixDivide (t,C,GreyScale); return; case DifferenceMix: MixDifference(t,C,GreyScale); return; case SubtractMix : MixSubtract (t,C,GreyScale); return; case AddMix : MixAdd (t,C,GreyScale); return; case DarkerMix : MixDarker (t,C,GreyScale); return; case LighterMix : MixLighter (t,C,GreyScale); return; case DodgeMix : MixDodge (t,C,GreyScale); return; case BurnMix : MixBurn (t,C,GreyScale); return; case HueMix : MixHue (t,C ); return; // Hue set from destination case SaturationMix: MixSaturation(t,C ); return; // Blend the Saturations case ColorMix : MixColor (t,C ); return; // Hue and Saturation are taken from the destination case ValueMix : MixValue (t,C ); return; // Blend the Values } } // The resulting color can exceed the normal [0-1] Interval for each component (Add, really does blindly add)! void gColor::MixBlend (const double& t, const gColor& C, bool GreyScale/*=false*/) {double r=1.0-t; R=Blend (C.GetRd(),t,R,r); if(GreyScale) return; G=Blend (C.GetGd(),t,G,r); B=Blend (C.GetBd(),t,B,r);} void gColor::MixMultiply (const double& t, const gColor& C, bool GreyScale/*=false*/) {double r=1.0-t; R=Multiply (C.GetRd(),t,R,r); if(GreyScale) return; G=Multiply (C.GetGd(),t,G,r); B=Multiply (C.GetBd(),t,B,r);} void gColor::MixScreen (const double& t, const gColor& C, bool GreyScale/*=false*/) {double r=1.0-t; R=Screen (C.GetRd(),t,R,r); if(GreyScale) return; G=Screen (C.GetGd(),t,G,r); B=Screen (C.GetBd(),t,B,r);} void gColor::MixOverlay (const double& t, const gColor& C, bool GreyScale/*=false*/) {double r=1.0-t; R=Overlay (C.GetRd(),t,R,r); if(GreyScale) return; G=Overlay (C.GetGd(),t,G,r); B=Overlay (C.GetBd(),t,B,r);} void gColor::MixDivide (const double& t, const gColor& C, bool GreyScale/*=false*/) {double r=1.0-t; R=Divide (C.GetRd(),t,R,r); if(GreyScale) return; G=Divide (C.GetGd(),t,G,r); B=Divide (C.GetBd(),t,B,r);} void gColor::MixDifference(const double& t, const gColor& C, bool GreyScale/*=false*/) {double r=1.0-t; R=Difference(C.GetRd(),t,R,r); if(GreyScale) return; G=Difference(C.GetGd(),t,G,r); B=Difference(C.GetBd(),t,B,r);} void gColor::MixSubtract (const double& t, const gColor& C, bool GreyScale/*=false*/) { R=Subtract (C.GetRd(),t,R ); if(GreyScale) return; G=Subtract (C.GetGd(),t,G ); B=Subtract (C.GetBd(),t,B );} void gColor::MixAdd (const double& t, const gColor& C, bool GreyScale/*=false*/) { R=Add (C.GetRd(),t,R ); if(GreyScale) return; G=Add (C.GetGd(),t,G ); B=Add (C.GetBd(),t,B );} void gColor::MixDarker (const double& t, const gColor& C, bool GreyScale/*=false*/) { R=Darker (C.GetRd(),t,R ); if(GreyScale) return; G=Darker (C.GetGd(),t,G ); B=Darker (C.GetBd(),t,B );} void gColor::MixLighter (const double& t, const gColor& C, bool GreyScale/*=false*/) { R=Lighter (C.GetRd(),t,R ); if(GreyScale) return; G=Lighter (C.GetGd(),t,G ); B=Lighter (C.GetBd(),t,B );} void gColor::MixDodge (const double& t, const gColor& C, bool GreyScale/*=false*/) { R=Dodge (C.GetRd(),t,R ); if(GreyScale) return; G=Dodge (C.GetGd(),t,G ); B=Dodge (C.GetBd(),t,B );} void gColor::MixBurn (const double& t, const gColor& C, bool GreyScale/*=false*/) {double r=1.0-t; R=Burn (C.GetRd(),t,R,r); if(GreyScale) return; G=Burn (C.GetGd(),t,G,r); B=Burn (C.GetBd(),t,B,r);} void gColor::MixHue(const double& t, const gColor& C) { // Hue set from destination double r=1.0-t; double cH,cS,cV; C.GetHSV(cH,cS,cV); if(cS==0) return; double H,S,V; GetHSV(H,S,V); gColor N; N.SetFromHSV(cH,S,V); // Use the destination Hue R=Blend(R, N.GetRd(), r,t); // Could recurse: Mix(MixBlend,t,N,false); G=Blend(G, N.GetGd(), r,t); B=Blend(B, N.GetBd(), r,t); } void gColor::MixSaturation(const double& t, const gColor& C) { // Blend the Saturations double r=1.0-t; double H,S,V; GetHSV(H,S,V); if(S==0) return; double cH,cS,cV; C.GetHSV(cH,cS,cV); gColor N; SetFromHSV(H, Blend(S,cS,r,t), V); // Blend the Saturations } void gColor::MixValue(const double& t, const gColor& C) { // Blend the Values double r=1.0-t; double H,S,V; GetHSV(H,S,V); double cH,cS,cV; C.GetHSV(cH,cS,cV); SetFromHSV(H,S, Blend(V,cV,r,t)); // Blend the Values } void gColor::MixColor(const double& t, const gColor& C) { // Hue and Saturation are taken from the destination double r=1.0-t; double cH,cS,cV; C.GetHSV(cH,cS,cV); if(cS==0) return; double H,S,V; GetHSV(H,S,V); gColor N; N.SetFromHSV(cH,cS,V); // Use the destination Hue and Saturation R=Blend(R, N.GetRd(), r,t); // Could recurse: Mix(MixBlend,t,N,false); G=Blend(G, N.GetGd(), r,t); B=Blend(B, N.GetBd(), r,t); } //____________________________ statics ________________________________________ bool gColor::IsNoColor(DWORD Color) {return Color==NoColor;} DWORD gColor::SwapRedBlue(DWORD Color) { // BGR <-> RGB BYTE* it=reinterpret_cast(&Color); BYTE t=*it; *it=it[2]; it[2]=t; // Compilers and processors optimise for basic swaps return Color; } DWORD gColor::Invert(COLORREF Color) {return Color^0xFFFFFF;} BYTE gColor::GetGreyLevel(COLORREF Color) {return BYTE((19595*GetRValue(Color) + 38470*GetGValue(Color) + 7471*GetBValue(Color))>>16);} // Fast BYTE version of Standard NTSC weights for eye response DWORD gColor::Mix(COLORREF Color1, COLORREF Color2) {return((Color1>>1) & 0x7F7F7F)+((Color2>>1) & 0x7F7F7F);} DWORD gColor::AlphaBlend(COLORREF Paper, COLORREF Paint, BYTE Transparency) { // double a=Transparency/255.0; return RGB(BYTE((a*GetRValue(Paper)+(1-a)*GetRValue(Paint))), BYTE((a*GetGValue(Paper)+(1-a)*GetGValue(Paint))), BYTE((a*GetBValue(Paper)+(1-a)*GetBValue(Paint)))); switch(Transparency) { // Catch values that are easy to optimise: case 0: case 1: return Paint; case 63: case 64: return Mix(Paint,Mix(Paint,Paper)); case 127: case 128: return Mix(Paint,Paper); case 191: case 192: return Mix(Paper,Mix(Paper,Paint)); case 254: case 255: return Paper; default : const BYTE Visibility=~Transparency; return (((((Paint&0x0000FF)*Visibility)+((Paper&0x0000FF)*Transparency))&0x0000FF00) | ((((Paint&0x00FF00)*Visibility)+((Paper&0x00FF00)*Transparency))&0x00FF0000) | ((((Paint&0xFF0000)*Visibility)+((Paper&0xFF0000)*Transparency))&0xFF000000))>>8; } } double gColor::GammaCorrect(const double& Brightness, const double& Gamma) {return pow(Brightness, 1/Gamma);} double gColor::Expose(const double& LightIn, const double& Exposure) {return (1-exp(-LightIn * Exposure));} // Auto-adjust brightness /* Black Body Radiation using the Planck Energy Distribution Equation. const double RWaveLength=700e-9; // Wavelengths of red, green and blue light in metres const double GWaveLength=560e-9; const double BWaveLength=470e-9; */ double gColor::GetLightPower(const double& WaveLength, const double& Temperature) { // WaveLength in metres, Temperature in Kelvin const double h=6.62606896e-34; // Js Planck's Constant const double c=299792458; // m/s Speed Of Light const double k=1.3806504e-23; // J/K Boltzmann Constant return (2*M_PI*h*c*c)/(pow(WaveLength,5)*(exp((h*c)/(k*Temperature*WaveLength))-1)); // J/s/m^2/sr/m } // B is the Brus (new color component) to blend with the screen component, S; t is how much B to use [0-1]; r (Remainder) is 1-t; r is passed in to stop multiple calls recalculating it all the time (these are supposed to be inline, so parameter passing shouldn't matter); // The resulting color can exceed the normal [0-1] Interval for each component (Add, really does blindly add)! double gColor::Blend (const double& B, const double& t, const double& S, const double& r) {return r*S + t*B;} double gColor::Multiply (const double& B, const double& t, const double& S, const double& r) {return S*(r + t*B);} double gColor::Screen (const double& B, const double& t, const double& S, const double& r) {return 1-(r + t*(1-B)) * (1-S);} double gColor::Overlay (const double& B, const double& t, const double& S, const double& r) {return S<0.5 ? S*(r + 2*t*B) : 1-(r + 2*t*(1-B)) * (1-S);} double gColor::Divide (const double& B, const double& t, const double& S, const double& r) {return B==0 ? S : r*S + t*S/B;} double gColor::Difference(const double& B, const double& t, const double& S, const double& r) {return r*S + t*fabs(S-B);} double gColor::Subtract (const double& B, const double& t, const double& S ) {return S - t*B;} double gColor::Add (const double& B, const double& t, const double& S ) {return S + t*B;} double gColor::Darker (const double& B, const double& t, const double& S ) {double tmp=t*B; return (tmpS ? tmp : S);} double gColor::Dodge (const double& B, const double& t, const double& S ) { if(S==0) return 0; double tmp=1-t*B; if(tmp<=0) return 1; if((tmp=S/tmp)>1) return 1; // Note assignment in condition return tmp; } double gColor::Burn(const double& B, const double& t, const double& S, const double& r) { double tmp=r + t*B; if(tmp<=0) return 0; if((tmp=(1-(1-S)/tmp))<0) return 0; // Note assignment in condition if(tmp>1) return 1; return tmp; } //_____________________________ Color Spline __________________________________ gColor gColorSpline::Get(double t, int Count, gColor* Colors, double* Positions/*=0*/, SplineInterpolationType InterpolationType/*=CatmullRomCSI*/) { if(Count==0) return gColor(t,t,t,0.0); // return t as a greyscale level if no Colors defined if(Count==1) return gColor(*Colors); if(t<0.0001) return Colors[0]; if(t>0.9999) return Colors[Count-1]; int b,c; // Index to Colors and Samples. The sample will be interpolated between b and c using a and d (defined later) as control points for splines (a<=b<=c<=d) if(Positions) { c=0; // Find the Color below t: while((cPositions[c])) ++c; if(c==Count) b=--c; // After Last Entry (Not found) else if(c==0) b=c; // Before First Entry else b=c-1; // Normal: position found between entries if((b==c) // Linear Interpolation with undefined ends... && ((InterpolationType==LinearCSI) // ...simply fill to the end with the nearest color: || (InterpolationType==HermiteCSI))) return Colors[b]; double Low =(c==0 ? 0 : Positions[b]); double High=(b==Count-1 ? 1 : Positions[c]); t=(Low==High) ? 0 : Parameterize(t, Low,High); }else{ // Positions==0 Colors (are equally spaced) t*=Count-1; // Find which Color to use as a base b=Floor(t); t-=b; // Now t is a parameter between the correct two Colors if((t<=0.0001) // At one end... && ((InterpolationType!=bSplineCSI))) return Colors[b]; // ...and not a bSpline c=b+1; } double bR=Colors[b].GetRd(); // These just help make the following code shorter: double bG=Colors[b].GetGd(); double bB=Colors[b].GetBd(); double bA=Colors[b].GetAd(); double cR=Colors[c].GetRd(); double cG=Colors[c].GetGd(); double cB=Colors[c].GetBd(); double cA=Colors[c].GetAd(); switch(InterpolationType) { case HermiteCSI: t=gFilters::HermiteCurve(t); // break; fall through: Antialias (ease) sharp changes with a Hermite Curve. case LinearCSI: { double r=1-t; return gColor(gColor::Blend(bR,r,cR,t), gColor::Blend(bG,r,cG,t), gColor::Blend(bB,r,cB,t), gColor::Blend(bA,r,cA,t)); } } int a=b-1; // Reflect the second point back before the first to create a new beginning: double aR=(b ? Colors[a].GetRd() : Interpolate(-1, bR,cR)); double aG=(b ? Colors[a].GetGd() : Interpolate(-1, bG,cG)); double aB=(b ? Colors[a].GetBd() : Interpolate(-1, bB,cB)); double aA=(b ? Colors[a].GetAd() : Interpolate(-1, bA,cA)); int d=c+1; // Reflect the second to last point forward past the last point to create a new end: double dR=(d