squeak-smalltalk / squeak-object-memory

Issues and assets related to the Squeak object memory.
https://bugs.squeak.org
MIT License
11 stars 1 forks source link

Color storeOn: can loose information #66

Open nicolas-cellier-aka-nice opened 1 year ago

nicolas-cellier-aka-nice commented 1 year ago

Currently (Squeak 6), red green blue components of Color are stored on 10 bits (2^10 => 1024 combinations)

But colors components are printed (and stored) with 3 decimals floating point notation, which leaves only 1001 possible representations

(0 to: 1000) collect: [:e | e /1000.0].

Therefore, some colors differs but share storeOn: representation:

((1 to: 1023) select: [:r | (Color r: r g: 0 b: 0 range: 1023) printString = ((Color r: r-1 g: 0 b: 0 range: 1023) printString )] )
    -> #(23 67 112 156 201 245 290 334 379 423 468 512 556 601 645 690 734 779 823 868 912 957 1001) 

Some colors are thus not preserved by round-trip conversion:

((0 to: 1023) select: [:r | (Color readFrom: (Color r: r g: 0 b: 0 range: 1023) storeString) ~= ((Color r: r g: 0 b: 0 range: 1023) )] )
     -> #(22 67 111 156 200 245 289 334 378 423 467 511 556 600 645 689 734 778 823 867 912 956 1001) 

Maybe it's not really a problem, because we only display most significant 8 bits of these colors...
Unfortunately, some colors with same printString/storeString also have different pixelValues in Forms of 32bit depth:

((1 to: 1023) select: [:r | (Color r: r g: 0 b: 0 range: 1023) printString = (Color r: r-1 g: 0 b: 0 range: 1023) printString
    and: [(Color r: r g: 0 b: 0 range: 1023) pixelValue32 ~= ((Color r: r-1 g: 0 b: 0 range: 1023) pixelValue32 )]] )
    -> #(112 156 468 512 556 868 912) 

Maybe it's not really a problem, because we only create Colors from

Alas, more annoyingly, some of those internal representation values are used by colors created from 8 bit components:

((0 to: 255) select: [:r | (Color readFrom: (Color r: r g: 0 b: 0 range: 255) storeString) ~= ((Color r: r g: 0 b: 0 range: 255)  )] )
    -> #(39 61 72 183 194 216) 

| c |
c := Color r: 39 g: 0 b: 0 range: 255.
self assert: (Color readFrom: c storeString) = c.

Even worse, two of those byte values will be converted back to different byte when HTML round-tripping:

(Color readFrom: (Color fromString: '#2727D8') storeString) asHTMLColor
    ->  '#2626D9' 

This is because asHTMLColor is truncating the color components (taking 8 most significant bits, discarding 2 least significant) rather than rounding (Color component * 255 / 1023) rounded.