Begone TranslucentColor!

In this post we will discuss the steps towards merging two seemingly distinct concepts in Pharo into a single class, namely TranslucentColor and Color, given that the latter can suffice for modelling instances of the former. A color instance instance can either opaque or translucent, while preserving it’s rgb component intact.

About the direct references to the class

Currently, there are 9 users of TranslucentColor in the image (version 3.0, update #30375).

  • Color>>alpha: alphaValue
  • Color class>>fromArray: colorDef
  • ColorEditor>>updateColor
  • Paragraph class>>insertionPointColor
  • ThemeSettings>>menuShadowColor
  • ThemeSettings>>secondarySelectionColor
  • ThemeSettings>>selectionColor
  • TranslucentColorTest>>testInitialization

The enumerated methods directly reference the TranslucentColor class, but should use the class method #r:g:b:alpha: instead .

Color>>alpha: anAlpha
alphaValue = 1.0 ifFalse: [^ TranslucentColor basicNew setRgb: rgb alpha: alphaValue]
“I should just say:
^ self class r: self red g: self green b: self blue alpha: anAlpha ”

Modifying the 9 users of TranslucentColor methods in a similar manner, enables us to remove every direct uses of TranslucentColor, but this is not enough for simply discarding the redundant class, we have to refactor Color into modeling both opaque and translucent colors.

About #isTranslucentColor

The method #isTranslucentColor appears to be another direct reference to the unwanted class, although the name is misleading because both implementors (Color and TranslucentColor) specify wether the instance is translucent but not transparent.

TranslucentColor>>isTranslucentColor
“This means: self isTranslucent, but isTransparent not”
^ alpha > 0

We decided to rename #hasTranslucentColor to #isTranslucentButNotTransparent, for having an explicit name that better describes the intention of the method.

About moving up ‘alpha’ to Color

To accomplish our goal of removing the redundant class for modelling translucent colors, we have to move both the instance variable named ‘alpha’ (which specifies the amount of translucency), and the methods that refer to it from TranslucentColor to Color.

The resulting Color should be the following:

Object subclass: #Color
instanceVariableNames: ‘rgb alpha cachedDepth cachedBitPattern’
classVariableNames: ‘BlueShift CachedColormaps C…’
poolDictionaries: ”
category: ‘Graphics-Primitives’

First, we modified all the instance creation methods for creating new instances of opaque and translucent colors. Since we were already cleaning up Color, I decided to rename the initialization methods from #setRed:green:blue and #setRed:green:blue:range, to #initializeRed:green:blue:, compliant with modern Pharo idioms.

Second, we moved up all the queries related to translucency: #isTranslucent, #isTransparent, and #isTranslucentButNotTransparent. Then, we added the ‘alpha’ component to the #hash and #= methods.

To conclude our refactoring to move up the ‘alpha’ instance variable, we had to modify several conversion methods in the Color class, such as #+, #-, #/, and other Form related methods such as #basicPixelValueForDepth:, #pixelValueForDepth:, and #scaledPixelValue32, to make them consider the translucency of the color.

To discard the redundant class, we have to prepare the image by migrating all instances of TranslucentColor to a simple Color instance with the correct alpha component. The following code performs the final removal:

| all |
TranslucentColor compile: ‘migratedColor ^Color r: self red g: self green b: self blue alpha: self alpha’.


all := TranslucentColor allInstances.
all elementsExchangeIdentityWith: (all collect:#migratedColor).


TranslucentColor removeFromSystem .

About performing the refactoring

Similarly to any class belonging to the graphics kernel, modifying Color and removing TranslucentColor is hazardous, because one attempts to change the behavior of color instances used by the very same tools of the IDE who heavily rely on them.

So the best approach is to programatically perform all the changes, and cleanups in a transactional manner, thus I designed a TranslucentColorRemoval class, that:

  1. move alpha from TranslucentColor to Color.
  2. carefully initialises all sub-instances of Color to the correct alpha values.
  3. loads the change-sets including the complete refactoring.
  4. migrates all remaining instances of TranslucentColor to Color using #become.
  5. and finally removes the redundant class!

To load the refactoring, evaluate the following method:

TranslucentColorRemoval>>perform


“TranslucentColorRemoval new perform”


self runBefore .
self import.
self runAfter .
self report.

Code

I’ve submitted the code to Issue 11519 in the Pharo bug tracker
There you can find the change set of the refactoring, and the importer class.

Next

At RMoD, we’ve been working on the rendering, view and morphic layer of the new text model, with the aim of replacing entirely the one in use, based on Paragraph, TextEditor, and TextMorph and friends. In the next post I will introduce the work and present the first demo’s.

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.

Image

Hello!

ferI’m Fernando Olivero, a software developer and research scientist with an interest in dynamic Object-oriented programming languages and User Interfaces.

In this blog I’ll describe the enhancements, cleanups and refactorings to the graphics kernel and development tools of Pharo that will result from my postdoc at the RMoD team, under the guidance of Stéphane Ducasse.

We will track our progress with this TRELLO board.