/* * This program source code file is part of KICAD, a free EDA CAD application. * * Copyright 2012 Torsten Hueter, torstenhtr gmx.de * Copyright 2017-2019 Kicad Developers, see AUTHORS.txt for contributors. * * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License * as published by the Free Software Foundation; either version 2 * of the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, you may find one here: * http://www.gnu.org/licenses/old-licenses/gpl-2.0.html * or you may search the http://www.gnu.org website for the version 2 license, * or you may write to the Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA */ #include #include using namespace KIGFX; COLOR4D::COLOR4D( EDA_COLOR_T aColor ) { if( aColor <= UNSPECIFIED_COLOR || aColor >= NBCOLORS ) { *this = COLOR4D::UNSPECIFIED; return; } r = g_ColorRefs[aColor].m_Red / 255.0; g = g_ColorRefs[aColor].m_Green / 255.0; b = g_ColorRefs[aColor].m_Blue / 255.0; a = 1.0; } #ifdef WX_COMPATIBILITY COLOR4D::COLOR4D( const wxColour& aColor ) { r = aColor.Red() / 255.0; g = aColor.Green() / 255.0; b = aColor.Blue() / 255.0; a = aColor.Alpha() / 255.0; } bool COLOR4D::SetFromWxString( const wxString& aColorString ) { wxColour c; if( c.Set( aColorString ) ) { r = c.Red() / 255.0; g = c.Green() / 255.0; b = c.Blue() / 255.0; a = c.Alpha() / 255.0; return true; } return false; } wxString COLOR4D::ToWxString( long flags ) const { wxColour c = ToColour(); return c.GetAsString( flags ); } wxColour COLOR4D::ToColour() const { using CHAN_T = wxColourBase::ChannelType; const wxColour colour( static_cast( r * 255 + 0.5 ), static_cast( g * 255 + 0.5 ), static_cast( b * 255 + 0.5 ), static_cast( a * 255 + 0.5 ) ); return colour; } COLOR4D COLOR4D::LegacyMix( COLOR4D aColor ) const { COLOR4D candidate; // Blend the two colors (i.e. OR the RGB values) candidate.r = ( (unsigned)( 255.0 * r ) | (unsigned)( 255.0 * aColor.r ) ) / 255.0, candidate.g = ( (unsigned)( 255.0 * g ) | (unsigned)( 255.0 * aColor.g ) ) / 255.0, candidate.b = ( (unsigned)( 255.0 * b ) | (unsigned)( 255.0 * aColor.b ) ) / 255.0, // the alpha channel can be reinitialized // but what is the best value? candidate.a = ( aColor.a + a ) / 2; return candidate; } COLOR4D& COLOR4D::SetToLegacyHighlightColor() { EDA_COLOR_T legacyColor = GetNearestLegacyColor( *this ); EDA_COLOR_T highlightColor = g_ColorRefs[legacyColor].m_LightColor; // The alpha channel is not modified. Only R, G, B values are set, // because legacy color does not know the alpha chanel. r = g_ColorRefs[highlightColor].m_Red / 255.0; g = g_ColorRefs[highlightColor].m_Green / 255.0; b = g_ColorRefs[highlightColor].m_Blue / 255.0; return *this; } COLOR4D& COLOR4D::SetToNearestLegacyColor() { EDA_COLOR_T legacyColor = GetNearestLegacyColor( *this ); // The alpha channel is not modified. Only R, G, B values are set, // because legacy color does not know the alpha chanel. r = g_ColorRefs[legacyColor].m_Red / 255.0; g = g_ColorRefs[legacyColor].m_Green / 255.0; b = g_ColorRefs[legacyColor].m_Blue / 255.0; return *this; } unsigned int COLOR4D::ToU32() const { return ToColour().GetRGB(); } void COLOR4D::FromU32( unsigned int aPackedColor ) { wxColour c; c.SetRGB( aPackedColor ); r = c.Red() / 255.0; g = c.Green() / 255.0; b = c.Blue() / 255.0; a = c.Alpha() / 255.0; } EDA_COLOR_T COLOR4D::GetNearestLegacyColor( const COLOR4D &aColor ) { // Cache layer implemented here, because all callers are using wxColour static std::map< unsigned int, unsigned int > nearestCache; static double hues[NBCOLORS]; static double values[NBCOLORS]; unsigned int colorInt = aColor.ToU32(); auto search = nearestCache.find( colorInt ); if( search != nearestCache.end() ) return static_cast( search->second ); // First use ColorFindNearest to check for exact matches EDA_COLOR_T nearest = ColorFindNearest( aColor.r * 255.0, aColor.g * 255.0, aColor.b * 255.0 ); if( COLOR4D( nearest ) == aColor ) { nearestCache.insert( std::pair< unsigned int, unsigned int >( colorInt, static_cast( nearest ) ) ); return nearest; } // If not, use hue and value to match. // Hue will be NAN for grayscale colors. // The legacy color palette is a grid across hue and value. // We can exploit that to find a good match -- hue is most apparent to the user. // So, first we determine the closest hue match, and then the closest value from that // "grid row" in the legacy palette. double h, s, v; aColor.ToHSV( h, s, v ); double minDist = 360.0; double legacyHue = 0.0; if( std::isnan( h ) ) { legacyHue = NAN; } else { for( EDA_COLOR_T candidate = ::BLACK; candidate < NBCOLORS; candidate = NextColor( candidate ) ) { double ch; if( hues[candidate] == 0.0 && values[candidate] == 0.0 ) { COLOR4D candidate4d( candidate ); double cs, cv; candidate4d.ToHSV( ch, cs, cv ); values[candidate] = cv; // Set the hue to non-zero for black so that we won't do this more than once hues[candidate] = ( cv == 0.0 ) ? 1.0 : ch; } else { ch = hues[candidate]; } if( fabs( ch - h ) < minDist ) { minDist = fabs( ch - h ); legacyHue = ch; } } } // Now we have the desired hue; let's find the nearest value minDist = 1.0; for( EDA_COLOR_T candidate = ::BLACK; candidate < NBCOLORS; candidate = NextColor( candidate ) ) { // If the target hue is NAN, we didn't extract the value for any colors above if( std::isnan( legacyHue ) ) { double ch, cs, cv; COLOR4D candidate4d( candidate ); candidate4d.ToHSV( ch, cs, cv ); values[candidate] = cv; hues[candidate] = ( cv == 0.0 ) ? 1.0 : ch; } if( ( std::isnan( legacyHue ) != std::isnan( hues[candidate] ) ) || hues[candidate] != legacyHue ) continue; if( fabs( values[candidate] - v ) < minDist ) { minDist = fabs( values[candidate] - v ); nearest = candidate; } } nearestCache.insert( std::pair< unsigned int, unsigned int >( colorInt, static_cast( nearest ) ) ); return nearest; } #endif namespace KIGFX { const bool operator==( const COLOR4D& lhs, const COLOR4D& rhs ) { return lhs.a == rhs.a && lhs.r == rhs.r && lhs.g == rhs.g && lhs.b == rhs.b; } const bool operator!=( const COLOR4D& lhs, const COLOR4D& rhs ) { return !( lhs == rhs ); } std::ostream &operator<<( std::ostream &aStream, COLOR4D const &aColor ) { return aStream << aColor.ToWxString( wxC2S_CSS_SYNTAX ); } } void COLOR4D::ToHSL( double& aOutHue, double& aOutSaturation, double& aOutLightness ) const { auto min = std::min( r, std::min( g, b ) ); auto max = std::max( r, std::max( g, b ) ); auto diff = max - min; aOutLightness = ( max + min ) / 2.0; if( aOutLightness >= 1.0 ) aOutSaturation = 0.0; else aOutSaturation = diff / ( 1.0 - std::abs( 2.0 * aOutLightness - 1.0 ) ); double hue; if( diff <= 0.0 ) hue = 0.0; else if( max == r ) hue = ( g - b ) / diff; else if( max == g ) hue = ( b - r ) / diff + 2.0; else hue = ( r - g ) / diff + 4.0; aOutHue = hue > 0.0 ? hue * 60.0 : hue * 60.0 + 360.0; while( aOutHue < 0.0 ) aOutHue += 360.0; } void COLOR4D::FromHSL( double aInHue, double aInSaturation, double aInLightness ) { const auto P = ( 1.0 - std::abs( 2.0 * aInLightness - 1.0 ) ) * aInSaturation; const auto scaled_hue = aInHue / 60.0; const auto Q = P * ( 1.0 - std::abs( std::fmod( scaled_hue, 2.0 ) - 1.0 ) ); r = g = b = aInLightness - P / 2.0; if (scaled_hue < 1.0) { r += P; g += Q; } else if (scaled_hue < 2.0) { r += Q; g += P; } else if (scaled_hue < 3.0) { g += P; b += Q; } else if (scaled_hue < 4.0) { g += Q; b += P; } else if (scaled_hue < 5.0) { r += Q; b += P; } else { r += P; b += Q; } } void COLOR4D::ToHSV( double& aOutHue, double& aOutSaturation, double& aOutValue, bool aAlwaysDefineHue ) const { double min, max, delta; min = r < g ? r : g; min = min < b ? min : b; max = r > g ? r : g; max = max > b ? max : b; aOutValue = max; // value delta = max - min; if( max > 0.0 ) { aOutSaturation = ( delta / max ); } else // for black color (r = g = b = 0 ) saturation is set to 0. { aOutSaturation = 0.0; aOutHue = aAlwaysDefineHue ? 0.0 : NAN; return; } /* Hue in degrees (0...360) is coded according to this table * 0 or 360 : red * 60 : yellow * 120 : green * 180 : cyan * 240 : blue * 300 : magenta */ if( delta != 0.0 ) { if( r >= max ) aOutHue = ( g - b ) / delta; // between yellow & magenta else if( g >= max ) aOutHue = 2.0 + ( b - r ) / delta; // between cyan & yellow else aOutHue = 4.0 + ( r - g ) / delta; // between magenta & cyan aOutHue *= 60.0; // degrees if( aOutHue < 0.0 ) aOutHue += 360.0; } else // delta = 0 means r = g = b. hue is set to 0.0 { aOutHue = aAlwaysDefineHue ? 0.0 : NAN; } } void COLOR4D::FromHSV( double aInH, double aInS, double aInV ) { if( aInS <= 0.0 ) { r = aInV; g = aInV; b = aInV; return; } double hh = aInH; while( hh >= 360.0 ) hh -= 360.0; /* Hue in degrees (0...360) is coded according to this table * 0 or 360 : red * 60 : yellow * 120 : green * 180 : cyan * 240 : blue * 300 : magenta */ hh /= 60.0; int i = (int) hh; double ff = hh - i; double p = aInV * ( 1.0 - aInS ); double q = aInV * ( 1.0 - ( aInS * ff ) ); double t = aInV * ( 1.0 - ( aInS * ( 1.0 - ff ) ) ); switch( i ) { case 0: r = aInV; g = t; b = p; break; case 1: r = q; g = aInV; b = p; break; case 2: r = p; g = aInV; b = t; break; case 3: r = p; g = q; b = aInV; break; case 4: r = t; g = p; b = aInV; break; case 5: default: r = aInV; g = p; b = q; break; } } COLOR4D& COLOR4D::Saturate( double aFactor ) { // One can saturate a color only when r, v, b are not equal if( r == g && r == b ) return *this; double h, s, v; ToHSV( h, s, v, true ); FromHSV( h, aFactor, 1.0 ); return *this; } const COLOR4D COLOR4D::UNSPECIFIED( 0, 0, 0, 0 ); const COLOR4D COLOR4D::WHITE( 1, 1, 1, 1 ); const COLOR4D COLOR4D::BLACK( 0, 0, 0, 1 );