Right now selected items in PCB have their colors changed by basically lightening the color, probably in an RGB space. This works adequately - but not brilliantly - on a dark background where lighter colors tend to “pop” a little more. For a lighter background - this fails algorithm is sub-optimal, and tends to fail. Pastel colors, when selected, turn into lighter colors that blend into the background and get lost.
Artists tend to think of colors in the HSV color space involving hue, saturation and value. Basically hue is the difference between green and red, saturation is the difference between a bright (not light) blue and more grayed out blue, value is the difference between light blue and dark blue etc.
Ideally for a more sophisticated “selected color” algorithm, in my view, one would want to change all of three of the parameters in the HSV color space when an object was selected. The value shift is the most important and should take into account the background color setting, with a move in the opposite direction of the background value. The saturation should make the color brighter but of course this has a limit if the color is already at maximum saturation. Finally the hue should be changed some amount so blue turns into blue-green or purple etc.
Hopefully all three platforms Windows, Mac and Linux now support some kind of HSV space. I don’t think the details are that important of implementation as the color change is just for flagging people’s attention and not color correcting printing or anything very technical.
The user setting could then choose how dramatically of each component would be altered, but ideally most people would never touch it.
I think that system would work with a wide range of colors and situations without a lot of fuss.
This is all complicated by brightening (used for disambiguation), high-contrast mode, netclass colors, and other factors. None of this is to say that it couldn’t be improved.
FWIW, here’s the current algorithm:
COLOR4D PCB_RENDER_SETTINGS::GetColor( const VIEW_ITEM* aItem, int aLayer ) const
{
const EDA_ITEM* item = dynamic_cast<const EDA_ITEM*>( aItem );
const BOARD_CONNECTED_ITEM* conItem = dynamic_cast<const BOARD_CONNECTED_ITEM*> ( aItem );
int netCode = -1;
int originalLayer = aLayer;
// Marker shadows
if( aLayer == LAYER_MARKER_SHADOWS )
return m_backgroundColor.WithAlpha( 0.6 );
if( IsHoleLayer( aLayer ) && m_isPrinting )
{
// Careful that we don't end up with the same colour for the annular ring and the hole
// when printing in B&W.
const PAD* pad = dynamic_cast<const PAD*>( item );
const PCB_VIA* via = dynamic_cast<const PCB_VIA*>( item );
int holeLayer = aLayer;
int annularRingLayer = UNDEFINED_LAYER;
if( pad && pad->GetAttribute() == PAD_ATTRIB::PTH )
annularRingLayer = LAYER_PADS_TH;
else if( via && via->GetViaType() == VIATYPE::MICROVIA )
annularRingLayer = LAYER_VIA_MICROVIA;
else if( via && via->GetViaType() == VIATYPE::BLIND_BURIED )
annularRingLayer = LAYER_VIA_BBLIND;
else if( via && via->GetViaType() == VIATYPE::THROUGH )
annularRingLayer = LAYER_VIA_THROUGH;
if( annularRingLayer != UNDEFINED_LAYER
&& m_layerColors[ holeLayer ] == m_layerColors[ annularRingLayer ] )
{
aLayer = LAYER_PCB_BACKGROUND;
}
}
// Zones should pull from the copper layer
if( item && ( item->Type() == PCB_ZONE_T || item->Type() == PCB_FP_ZONE_T ) )
{
if( IsZoneLayer( aLayer ) )
aLayer = aLayer - LAYER_ZONE_START;
}
// Hole walls should pull from the copper layer
if( aLayer == LAYER_PAD_HOLEWALLS )
aLayer = LAYER_PADS_TH;
else if( aLayer == LAYER_VIA_HOLEWALLS )
aLayer = LAYER_VIA_THROUGH;
// Normal path: get the layer base color
COLOR4D color = m_layerColors[aLayer];
if( !item )
return m_layerColors[aLayer];
// Selection disambiguation
if( item->IsBrightened() )
return color.Brightened( m_selectFactor ).WithAlpha( 0.8 );
// Normal selection
if( item->IsSelected() )
color = m_layerColorsSel[aLayer];
// Try to obtain the netcode for the item
if( conItem )
netCode = conItem->GetNetCode();
bool highlighted = m_highlightEnabled && m_highlightNetcodes.count( netCode );
bool selected = item->IsSelected();
// Apply net color overrides
if( conItem && m_netColorMode == NET_COLOR_MODE::ALL && IsNetCopperLayer( aLayer ) )
{
COLOR4D netColor = COLOR4D::UNSPECIFIED;
auto ii = m_netColors.find( netCode );
if( ii != m_netColors.end() )
netColor = ii->second;
if( netColor == COLOR4D::UNSPECIFIED )
{
auto jj = m_netclassColors.find( conItem->GetNetClassName() );
if( jj != m_netclassColors.end() )
netColor = jj->second;
}
if( netColor == COLOR4D::UNSPECIFIED )
netColor = color;
if( selected )
{
// Selection brightening overrides highlighting
netColor.Brighten( m_selectFactor );
}
else if( m_highlightEnabled )
{
// Highlight brightens objects on all layers and darkens everything else for contrast
if( highlighted )
netColor.Brighten( m_highlightFactor );
else
netColor.Darken( 1.0 - m_highlightFactor );
}
color = netColor;
}
else if( !selected && m_highlightEnabled )
{
// Single net highlight mode
color = m_highlightNetcodes.count( netCode ) ? m_layerColorsHi[aLayer]
: m_layerColorsDark[aLayer];
}
// Apply high-contrast dimming
if( m_hiContrastEnabled && m_highContrastLayers.size() && !highlighted && !selected )
{
PCB_LAYER_ID primary = GetPrimaryHighContrastLayer();
bool isActive = m_highContrastLayers.count( aLayer );
switch( originalLayer )
{
case LAYER_PADS_TH:
if( !static_cast<const PAD*>( item )->FlashLayer( primary ) )
isActive = false;
break;
case LAYER_VIA_BBLIND:
case LAYER_VIA_MICROVIA:
// Target graphic is active if the via crosses the primary layer
if( static_cast<const PCB_VIA*>( item )->GetLayerSet().test( primary ) == 0 )
isActive = false;
break;
case LAYER_VIA_THROUGH:
if( !static_cast<const PCB_VIA*>( item )->FlashLayer( primary ) )
isActive = false;
break;
case LAYER_PAD_PLATEDHOLES:
case LAYER_PAD_HOLEWALLS:
case LAYER_NON_PLATEDHOLES:
// Pad holes are active is any physical layer is active
if( LSET::PhysicalLayersMask().test( primary ) == 0 )
isActive = false;
break;
case LAYER_VIA_HOLES:
case LAYER_VIA_HOLEWALLS:
if( static_cast<const PCB_VIA*>( item )->GetViaType() == VIATYPE::BLIND_BURIED
|| static_cast<const PCB_VIA*>( item )->GetViaType() == VIATYPE::MICROVIA )
{
// A blind or micro via's hole is active if it crosses the primary layer
if( static_cast<const PCB_VIA*>( item )->GetLayerSet().test( primary ) == 0 )
isActive = false;
}
else
{
// A through via's hole is active if any physical layer is active
if( LSET::PhysicalLayersMask().test( primary ) == 0 )
isActive = false;
}
break;
default:
break;
}
if( !isActive )
{
if( m_contrastModeDisplay == HIGH_CONTRAST_MODE::HIDDEN || IsNetnameLayer( aLayer ) )
color = COLOR4D::CLEAR;
else
color = color.Mix( m_layerColors[LAYER_PCB_BACKGROUND], m_hiContrastFactor );
}
}
// Apply per-type opacity overrides
if( item->Type() == PCB_TRACE_T || item->Type() == PCB_ARC_T )
color.a *= m_trackOpacity;
else if( item->Type() == PCB_VIA_T )
color.a *= m_viaOpacity;
else if( item->Type() == PCB_PAD_T )
color.a *= m_padOpacity;
else if( item->Type() == PCB_ZONE_T || item->Type() == PCB_FP_ZONE_T )
color.a *= m_zoneOpacity;
// No special modificators enabled
return color;
}
I can see it’s pretty complex. It probably is not a simple thing to improve it without breaking anything. I do think working in an HVS space would help to rationalize things but it would likely require a big rewrite of that algorithm.
Probably other things are more important now as most users are just going to use defaults and never look back.
I did tweak the json file for a bit of improved functionality on light background. I don’t think a light theme is that viable for general use without tweaking the algorithm at least some.
Yeah, we’ve got another bug logged against it that if you choose white for any objects it doesn’t work at all…
The downside of just making things lighter I guess. You can’t get lighter than white.
I was thinking through an algorithm and thought that black would have the same problem on a white background,
so I think the answer is to kind of “reflect back” things from the limit. Having hue saturation and value all active would go a long way to solve any problem though.
If you are familiar with the work of Edward Tufte - he talks about limiting the default saturation of colors. Bright colors tend to be distracting in large quantities.
In art school a painting instructor said something like “color makes demands on you”. to the same effect