HSL

The HSL colour model in tc-lib-color: hue as a fraction of a turn, percentage saturation and lightness, and wrapping

Hue, saturation and lightness address the same colours as RGB in cylindrical coordinates. Darkening a colour changes one component instead of three, which is what palette and theme code uses the model for.

Model

ItemValue
Type stringHSL
Enum caseColorModelType::Hsl
Class\Com\Tecnick\Color\Model\Hsl
PDF colour spaceDeviceRGB, by conversion
Notationshsl(), hsla()

Components

ComponentKeyRangeOut of range
Huehue0.0 to 1.0, a fraction of a full turnwrapped
Saturationsaturation0.0 to 1.0clamped
Lightnesslightness0.0 to 1.0clamped
Alphaalpha0.0 to 1.0, default 1.0clamped

The hue is stored as a fraction, not as degrees. The hsl() notation takes degrees and getCssColor() emits degrees; the constructor does not.

Construction

<?php

require_once __DIR__ . '/vendor/autoload.php';

// hue is a fraction of a full turn: 210 / 360
$hsl = new \Com\Tecnick\Color\Model\Hsl([
    'hue' => 210 / 360,
    'saturation' => 0.5,
    'lightness' => 0.4,
]);

echo $hsl->getCssColor(), "\n";
echo $hsl->getRgbHexColor(), "\n";
hsl(210,50%,40%)
#336699

Output

Every accessor, against #336699:

CallResult
getCssColor()hsl(210,50%,40%)
getRgbHexColor()#336699
getRgbaHexColor()#336699ff
getComponentsString()0.200000 0.400000 0.600000
getPdfColor()0.200000 0.400000 0.600000 rg
getPdfColor(true)0.200000 0.400000 0.600000 RG
getJsPdfColor()["RGB",0.200000,0.400000,0.600000]
getArray()['H' => 0.5833333333333334, 'S' => 0.49999999999999994, 'L' => 0.4, 'A' => 1.0]
getNormalizedArray(255)['H' => 210.0, 'S' => 0.49999999999999994, 'L' => 0.4, 'A' => 1.0]
getPDFacArray()[0.19999999999999996, 0.3999999999999998, 0.6000000000000001]

getNormalizedArray(255) ignores its argument here. Hue comes back in degrees and saturation and lightness as fractions, since neither is scaled by a reference maximum. The same call on an Rgb object scales all three channels.

PDF has no HSL colour space, so getPdfColor() and getJsPdfColor() convert and emit DeviceRGB.

Wrapping And Clamping

The hue is an angle, so it wraps: 1.25 turns is the same direction as 0.25. Saturation and lightness are magnitudes, so they clamp.

// hue wraps
foreach ([210 / 360, 570 / 360, -150 / 360, 2.0] as $hue) {
    $hsl = new \Com\Tecnick\Color\Model\Hsl([
        'hue' => $hue,
        'saturation' => 0.5,
        'lightness' => 0.4,
    ]);
    printf("%9.6f  %s\n", $hue, $hsl->getCssColor());
}

// saturation and lightness clamp
echo (new \Com\Tecnick\Color\Model\Hsl([
    'hue' => 0.5,
    'saturation' => 2.0,
    'lightness' => -1.0,
]))->getCssColor(), "\n";
 0.583333  hsl(210,50%,40%)
 1.583333  hsl(210,50%,40%)
-0.416667  hsl(210,50%,40%)
 2.000000  hsl(0,50%,40%)
hsl(180,100%,0%)

Percentages

The CSS specification defines saturation and lightness as percentages, and the parser follows it whether or not the % is written. hsl(210,50,40) is the same colour as hsl(210,50%,40%). Nothing raises, so hsl(210,0.5,0.4) is read as a saturation of half a percent.

See Also