Skip to content

[flutter_svg] HSL to RGB conversion incorrect due to percentage parsing #185833

Description

@fondoger

What package does this bug report belong to?

flutter_svg

Description

There's a bug in the SVG color parsing code that incorrectly scales percentage components that contain decimal points. When an HSL/HSLA component string contains a decimal (for example "76.2745098039%"), the code multiplies the parsed value by 2.55 (parseDouble(raw) * 2.55) and rounds it. That conversion is appropriate for RGB percentage inputs (to convert percent → 0–255), but it is incorrect for HSL saturation and lightness components. HSL percent values should be interpreted in the 0–100 range (or normalized to 0.0–1.0), not converted to 0–255. This leads to incorrect color conversions.

Offending code location

The problematic logic is in parser.dart around this line:
https://github.com/flutter/packages/blob/c3360ac8ae748c270cabe3a2f867b4ac9d6d36b1/packages/vector_graphics_compiler/lib/src/svg/parser.dart#L1436

Specifically, the branch that does parseDouble(rawColor)! * 2.55 is applied to any numeric token that contains a decimal point, regardless of whether the token is part of an rgb(...) or hsl(...) input.

Reproduction

Input:

  • color string: "hsl(270, 100%, 76.2745098039%)"

Observed (current) result:

  • Converted color: "#efdeff"

Expected result:

  • Converted color: "#c286ff"
Image

Root cause

When a percentage token contains a decimal point, the parser multiplies the parsed value by 2.55 and rounds it. For HSL this turns, e.g., 76.2745098039 → ~194 (instead of using ~76.2745 as a percent), so subsequent HSL→RGB math is performed with wrong saturation/ lightness values.

Suggested fix

  • Do not multiply decimal percentage tokens by 2.55 when parsing HSL/HSLA inputs.
  • Distinguish the parsing behavior for rgb()/rgba() versus hsl()/hsla():
    • For rgb()/rgba(), percent components may be converted to 0–255 via *2.55 as currently done.
    • For hsl()/hsla(), percent components should be parsed as floats in the 0–100 range (or directly normalized to 0.0–1.0). Remove the *2.55 scaling for HSL parsing.
  • Concretely, modify the parsing branch so that decimal percentages are returned as floats when the current color function is hsl/hsla, while retaining the *2.55 behavior for rgb/rgba percent inputs.

Minimal pseudocode patch suggestion

Before (problematic):

if (rawColor.contains('.')) {
  return (parseDouble(rawColor)! * 2.55).round();
}

After (fix — conceptually):

if (rawColor.contains('.')) {
  if (parsingRgbPercentage) {
    // rgb percentage -> 0..255
    return (parseDouble(rawColor)! * 2.55).round();
  } else {
    // hsl percentage -> keep as percentage float (0..100) to be normalized later
    return parseDouble(rawColor)!;
  }
}

Notes

  • The project already has shared parsing logic intended to handle a variety of color formats; the fix should be careful to preserve existing rgb percentage behavior.
  • I verified locally that parsing HSL percentage decimals as floats (0–100) and normalizing to 0.0–1.0 before the HSL→RGB math produces the expected result for the example ("hsl(270, 100%, 76.2745098039%)" → "#c286ff").

Offer

I can open a PR with a concrete patch and unit tests covering:

  • hsl with integer percentages
  • hsl with decimal percentages
  • rgb with percentage components (ensuring existing behavior is preserved)
  • hsla / rgba alpha parsing

Please let me know if you prefer the fix to be implemented by splitting the parsing flow for rgb/hsl or by adding a flag/parameter to the existing token parser.

Metadata

Metadata

Assignees

No one assigned

    Labels

    engineflutter/engine related. See also e: labels.p: flutter_svgThe Flutter SVG drawing packagespackageflutter/packages repository. See also p: labels.team-engineOwned by Engine team

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions