About Gray, VeryLightGray and VeryVeryLightGray!

We will begin our journey through the graphics kernel of Pharo, by focusing on Color, one of the building blocks of computer graphics.

Let’s start by reading the class definition of a color in Pharo (Version 30356):

Object subclass: #Color
instanceVariableNames: ‘rgb cachedDepth cachedBitPattern’
classVariableNames: ‘Black Blue BlueShift Brown CachedColormaps ColorNames ComponentMask ComponentMax Cyan DarkGray Gray GrayToIndexMap Green GreenShift HalfComponentMask HighLightBitmaps IndexedColors LightBlue LightBrown LightCyan LightGray LightGreen LightMagenta LightOrange LightRed LightYellow Magenta MaskingMap Orange PureBlue PureCyan PureGreen PureMagenta PureRed PureYellow RandomStream Red RedShift TranslucentPatterns Transparent VeryDarkGray VeryLightGray VeryVeryDarkGray VeryVeryLightGray White Yellow’
poolDictionaries: ”
category: ‘Graphics-Primitives’

The Problem

In Color there are too many class Vars referencing named colors, thus we will attempt to reduce the high number of class variables, or at least provide a good reason that explains the complexity of the class.

Color classVarNames size 46

Color defines 32 class variables which reference the set of named colors, used throughout the system, such as Black, Blue, Brown, Cyan, DarkGray, and LightYellow.

The current design is neither flexible nor modular because:

  • to register a named color one must add a class var to Color, and a corresponding accessor.
  • the default set of named colours is fixed in the method #initializeNames.

Besides, the current implementation of registering a named colour is cumbersome, because it uses meta-programming to add the class var and accessor. See the following method in Color which is responsible for initializing and registering the default colors in the image:

Color class>>initializeNames

self
named: #black
put: (Color r: 0 g: 0 b: 0).

self
named: #lightOrange
put: (Color r: 1.0 g: 0.8 b: 0.4).

The Fix

We propose a simpler implementation of named colors, by adding to the Color class a registry of named colors, with both methods for registering a new named color and accessing them. The registry is initialized to the same set of default colours, but easily extendable without resolving to meta-programming.

The fix removes all the class variables representing a named colour, and provides accessors for the default ones for backward compatibility, (i.e.: Color class>>black,etc..), which results in a simpler class definition:

Object subclass: #Color
instanceVariableNames: ‘rgb cachedDepth cachedBitPattern’
classVariableNames: ‘ColorRegistry BlueShift CachedColormaps ComponentMask
ComponentMax GrayToIndexMap GreenShift HalfComponentMask HighLightBitmaps
IndexedColors MaskingMap RandomStream RedShift TranslucentPatterns’
poolDictionaries: ”
category: ‘Graphics-Primitives’

Moreover, the fix introduces an optimisation for answering the name of any registered color, in the form a instance variable in Color. Currently, a registered color name is found by testing an instance for equality against the registered colors (ColorNames class var of Color).

Color>>name
ColorNames do: [:name |
(Color perform: name) = self ifTrue: [^ name]].
^ nil

The Performance Penalty

We measured the performance penalty of an extra message send when asking for a registered color. Currently, retrieving a registered color is achieved by simply accessing a class variable.

Color class>>black ^ Black
Color class>>red ^Red

Instead, our fix involves accessing the registry. See the following convenience methods for accessing the default colors, and the more general method for any registered color.

Color class>>black
^ ColorRegistry at: #black.
Color class>>named:aName
^ ColorRegistry at: aName ifAbsent: nil

I implemented a class that imports the fix, and tests that the new color registry holds identical colors for each named color, before and after loading the changes, and informs the time to run the same benchmark. To load, test and benchmark place the change set with the fix in the same directory as the image, then load the NamedColorBenchmark class and evaluate the following.

| importer |
importer := NamedColorBenchmark new.
importer runBefore.
importer import.
importer runAfter.
importer report.

Conclusion

The benchmark shows that for an extreme case which asks 100000 times for every named color, the performance penalty of a color registry, compared to having numerous class variables referencing every named colors, is 300%.

The latter might appear as a penalty far too great to pay for the sake of a simpler and modular graphics infrastructure, but since the benchmark tests a worst case scenario, having an extra message send to access the registry of named colors, won’t be such a burden for the normal case.

If a certain application can do without a super fast named color accessing, it can implement it’s own cache using as many class variables as needed for the required colors, instead of overcrowding the Color class.

MyColorIntensiveApp>>initializeColorCache
red := Color named: #red.
black := Color named: #black.
MyColorIntensiveApp>>red ^ red
MyColorIntensiveApp>>black ^ black

Code

I’ve submitted the code to Issue 11433 in the Pharo bug tracker
There you can find the change sets of both the fix, and the benchmark.

Next

In the next post, we will challenge the existence of both Color and TranslucentColor in Pharo, with the purpose of unifying both concepts into the same class.

Advertisements