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.

Advertisements
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.