From the CSSOM specification:
<alphavalue>
If the value is internally represented as an integer between 0 and 255 inclusive (i.e. 8-bit unsigned integer), follow these steps:
- Let alpha be the given integer.
- If there exists an integer between 0 and 100 inclusive that, when multiplied with 2.55 and rounded to the closest integer (rounding up if two values are equally close), equals alpha, let rounded be that integer divided by 100.
- Otherwise, let rounded be alpha divided by 0.255 and rounded to the closest integer (rounding up if two values are equally close), divided by 1000.
- Return the result of serializing rounded as a <number>.
Otherwise, return the result of serializing the given value as a <number>.
Assuming that the color opacity is stored as a binary value:
const alpha = Math.round(49.6 * 2.55) // 126
const integer = Math.round(alpha / 2.55) // 49
const hasInteger = Math.round(integer * 2.55) === alpha // false
const value = hasInteger
? integer / 100
: Math.round(alpha / 0.255) / 1000
// 0.494
Note on floating precision:
Some values will be problematic regarding floating precision, eg. with 50
as opacity, alpha will be rounded to 127
instead of 128
, because in JavaScript (50 * 2.55) === 127.49999999999999
.
A more accurate JavaScript implementation to serialize opacity would be:
const alpha = Math.round((50 * 2.55).toPrecision(15)) // 128
const integer = Math.round(alpha / 2.55) // 50
const hasInteger = Math.round((integer * 2.55).toPrecision(15)) === alpha // true
const value = hasInteger
? integer / 100
: Math.round(alpha / 0.255) / 1000
// 0.5