Skip to content

Importing DTCG colours in srgb-linear or with alpha gives the wrong colour #695

Description

@rubenvdlinde

When a W3C design tokens file gives a colour as components, the import writes the wrong colour in two cases. An srgb-linear colour comes out too dark, and any alpha is dropped, so a translucent colour becomes opaque.

Where, at development fd992ea:

  • lib/Service/DesignTokensMapper.php:57 accepts both srgb and srgb-linear as colour spaces.
  • lib/Service/DesignTokensMapper.php:611-633 serializeColorObject() sends both spaces to the same converter when there is no hex.
  • lib/Service/DesignTokensMapper.php:644-652 componentsToHex() scales each component by 255 without the sRGB transfer function, and writes three channels only.
    $colorSpace = strtolower((string)($value['colorSpace'] ?? ''));
    if (in_array($colorSpace, self::SRGB_COLOR_SPACES, true) === false) {
    return ['ok' => false, 'reason' => 'unsupported-color-space', 'detail' => ($value['colorSpace'] ?? 'unknown')];
    }
    if (isset($value['hex']) === true && is_string($value['hex']) === true) {
    $hex = $value['hex'];
    if (str_starts_with($hex, '#') === false) {
    $hex = '#' . $hex;
    }
    return ['ok' => true, 'value' => $hex];
    }
    if (isset($value['components']) === true
    && is_array($value['components']) === true
    && count($value['components']) >= 3
    ) {
    return ['ok' => true, 'value' => $this->componentsToHex(components: array_values($value['components']))];
    }
    return ['ok' => false, 'reason' => 'unsupported-value-shape'];
    }//end serializeColorObject()
    /**
    * Serialize three 0–1 sRGB float components to a `#rrggbb` hex literal.
    *
    * @param array<int, mixed> $components The `[r, g, b]` components (0–1 range, clamped).
    *
    * @return string The `#rrggbb` hex literal.
    *
    * @spec openspec/specs/custom-token-sets/spec.md
    */
    private function componentsToHex(array $components): string {
    $hex = '#';
    foreach ([0, 1, 2] as $index) {
    $channel = max(0.0, min(1.0, (float)$components[$index]));
    $hex .= str_pad(dechex((int)round($channel * 255)), 2, '0', STR_PAD_LEFT);
    }
    return $hex;
    }//end componentsToHex()

Linear light values must go through the sRGB transfer function before scaling. Without it, a linear 0.5 grey becomes #808080 instead of about #bcbcbc. The colour object's alpha is never read. Neither case is reported, so the imported set looks correct and renders differently.

Specified fix: openspec/changes/authoring-dtcg-export (requirement "W3C Design Tokens JSON Import", task 2.2).

Found by the OpenSpec pass on 27 Sep 2026 and re-read at fd992ea on 28 Sep.

Live check: import a token file with {"colorSpace": "srgb-linear", "components": [0.5, 0.5, 0.5], "alpha": 0.5} and read the stored value #808080.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

bugSomething isn't workingtriageAwaiting triage

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions