Color Library Design - Color Models #202
Replies: 2 comments
-
Typed vs Untyped Color Models. A huge majority of Color Models take the form of three dimensional vectors with bounded, real valued, components. Should a library make use of a type system to distinguish between RGB, Lab*, HSV, XYZ, etc, or should it strive for the simplicity and performance made possible by a common 3D Vector type? Typed Color Models can prevent developers from accidentally treating an instance of RGB as an XYZ and save them a lot of time debugging the subtle errors that come from these mistakes. However, types introduce performance penalties and bloat as every conversion operation may reference different constructors or add arguably extraneous instance variables to the heap. Typed Color Models also make it more difficult for a library to support User Defined Types, or runtime defined types. Untyped color models, using a common type for all, can significantly decrease memory footprint, compute times, and friction for introducing user defined color models at compile time or runtime. However, they ultimately push the responsibility of type consistency onto some kind of contextual mechanism and/or increased developer cognitive load. How would you balance these tradeoffs? Would you prioritize type safety, sheer performance, or extensibility? On that last note, should a color library try to support user defined color types at all; if so, should it support them at compile time, run time, or both? |
Beta Was this translation helpful? Give feedback.
-
Another crucial and commonly addressed aspect of color models involves the conversions between them, e.g.: RGB <-> HSV In each case, a color library must define a high performance function, or composition of functions, and make it accessible in various code contexts, but each design option has consequences: Object Oriented - Methods and Dot Access: rgb1 = RGB(0.75, 0.75, 0.1)
hsv1 = rgb1.to_HSV()
rgb2 = hsv1.to_RGB()
# Syntax variants:
c = rgb1.hsv() # accessor
c = rgb1.asHSV() # explicit 1
c = rgb1.toHSV() # explicit 2
c = rgb1.getHSV() # property/getter Alternatively, a library could take a backwards only, singleton oriented approach: rgb1 = RGB(0.75, 0.75, 0.1)
hsv1 = HSV.from_RGB(rgb1)
rgb2 = RGB.from_HSV(hsv1)
# Syntax variants:
c = RGB(hsv1) # constructor
c = RGB.hsv(hsv1) # factory Further, one could model the color conversion in an undirected way by referencing conversion methods from both class instances and singleton objects. Generally, to reduce redundancy, the singleton can define the conversion function while the class can reference that definition, or vice versa. This to/from syntax might maximize intuitive convenience for users of the library, but at costs:
Procedural Approaches def RGB_HSV(rgb):
def HSV_RGB(hsv):
# Syntax Variants:
def rgb2hsv(rgb):
def RGBtoHSV(rgb):
def RGB2HSV(rgb):
def RGB_to_HSV(rgb): This method promises high performance and supports both typed and untyped color models, but seems error prone for its lack of modularity. Without conforming to any interface, the library maintainer must resort to some external means of keeping track of whether every edge in the conversion graph has a corresponding conversion function. Transformer Classes or Lambdas class ColorTransform:
def __init__(self, conversion):
self.convert = conversion
class RGB2HSV(ColorTransform):
def __init__(self):
conversion = lambda rgb:
# conversion code.
ColorTransform.__init__(self, conversion) This can support limited runtime types as well, through instances of ColorTransform invoked directly with some runtime defined conversion lambda. The main limitation comes from an inability to discern equivalence between two independently instantiated run time defined types. What other broad ways could a color library expose color model conversion functionality? |
Beta Was this translation helpful? Give feedback.
-
With respect to definitions written in Color Library Design - What to Model?, let's explore tradeoffs involved in color model implementation. This post begins the discussion with a kind of type safety concern, but the thread should definitely branch into accessibility, memory footprint, and performance.
Programmers encounter Color Models much more often than any other Color Science concept, so any color library had better maximize performance and accessibility at this level.
Lots of implementations of RGB, RGB24, ARGB32, RGBA32, HSL, HSV, CMY, CMYK, etc already exist in all languages, however each design I've seen only addresses time and space tradeoffs: e.g.
class RGB { float red; float green; float blue; float alpha; }
for convenience vsclass {int rgba;}
for speed and compactness. Whether the color itself comes from sRGB, Apple RGB, or Display P3, libraries do not know and most programmers wouldn't know to ask, even though in applications such as color comparison, the color space that defines the RGB values matters profoundly! Shouldn't a color library protect users from accidentally training the stop sign detector on sRGB images, but then deploys to iPhone apps with cameras that sample in Display P3?How could a library guard inexperienced users from the consequences of cross-color space comparisons?
Some options may include:
Data Approaches:
All of these might protect developers from blundering into color comparisons across color spaces, however they seem prone to increase obtuseness with a proliferation of if statements:
if c1.colorSpace == c2.colorSpace:
and also increase memory footprint. On the other hand, it supports user defined types for Working Spaces, Color Spaces, Gamuts, and Color Models fairly straight forward, but would a programmer tolerate a library that preconditions all functionality on the developer's ability to select the correct working space? Done well, it could educate developers, but done poorly, it will alienate them.Type Approaches:
RGB[Display_P3]
. If so, how does a Working Space (with a structure regular enough to be a class itself) differentiate itself at the type level if it's only meaningful distinction comes through its values? In other words, Working Space A is only different from Working Space B if it has a different Illuminant, chromatic primary, or transfer function. Does it make any sense (in code) to ascribe a unique type to each WorkingSpace?Addressing the issue from the type level eliminates all of the obtuseness and bloat. In most cases involving only one color space, developers can treat the library like any other:
Then, in relatively rare but meaningful cases like the stop sign example:
However, as mentioned, this implies that a type level conception of the Working Space which, mathematically, varies only in data but not structure. It also seems to exclude the possibility of Color Spaces defined at runtime.
How else might a color library capture the relationship between Color Spaces and Color Models?
Beta Was this translation helpful? Give feedback.
All reactions