How do I work with colours?

← Getting Started · Ref: Q34

Colour is EK9's built-in type for RGB colour values with hex literal syntax. No imports needed. Uses British spelling 'Colour' consistently.

LITERALS

  #FF0000         Pure red (6-digit RGB)
  #FF186276       With alpha (8-digit ARGB)

OUTPUT FORMATS

  .RGB() returns '#RRGGBB', .RGBA() returns '#RRGGBBAA', .ARGB() returns '#AARRGGBB'.

HSL MANIPULATION

Immutable methods return NEW colours: withLightness(80), withSaturation(90), withHue(200). Access HSL values with .hue(), .saturation(), .lightness(). Control transparency with withOpaque(percentage).

COLOUR ARITHMETIC

Add/subtract to blend/remove RGB components (auto-clamped to 0-255):

  purple <- #FF0000 + #0000FF

BITS INTEROP

  .bits() extracts 32-bit ARGB value. Colour(bitsValue) constructs from Bits.

Comparison: ==, <>, <, >, <=, >=, <=>.

Use 'ek9 -h Colour' to see the full API.

See Q37 for string interpolation. See Q40 for Bits interop.

Example

defines module qa.colour

  defines program
    ColourDemo()
      stdout <- Stdout()

      // === COLOUR LITERALS ===

      // Hex literal with alpha channel: #AARRGGBB (ARGB format)
      opaqueBlue <- #FF186276
      stdout.println(`Opaque blue: ${opaqueBlue}`)

      // Hex literal without alpha: #RRGGBB (RGB format)
      pureRed <- #FF0000
      pureGreen <- #00FF00
      pureBlue <- #0000FF
      stdout.println(`Red: ${pureRed}, Green: ${pureGreen}, Blue: ${pureBlue}`)

      // Unset colour
      unsetColour <- Colour()
      require ~unsetColour?

      // === OUTPUT FORMATS ===

      // Three different hex string formats
      stdout.println(`RGB: ${opaqueBlue.RGB()}`)
      stdout.println(`RGBA: ${opaqueBlue.RGBA()}`)
      stdout.println(`ARGB: ${opaqueBlue.ARGB()}`)

      // === HSL ACCESSORS ===

      // Hue (0-360), Saturation (0-100%), Lightness (0-100%)
      stdout.println(`Hue: ${opaqueBlue.hue()}`)
      stdout.println(`Saturation: ${opaqueBlue.saturation()}`)
      stdout.println(`Lightness: ${opaqueBlue.lightness()}`)

      // === HSL MANIPULATION ===

      // Each with* method returns a NEW colour (immutable)
      lighterBlue <- opaqueBlue.withLightness(80)
      stdout.println(`Lighter: ${lighterBlue}`)

      moreSaturated <- lighterBlue.withSaturation(90)
      stdout.println(`More saturated: ${moreSaturated}`)

      shiftedHue <- opaqueBlue.withHue(200)
      stdout.println(`Shifted hue: ${shiftedHue}`)

      // === ALPHA CHANNEL (TRANSPARENCY) ===

      // withOpaque(percentage) sets opacity: 100=fully opaque, 0=fully transparent
      semiTransparent <- opaqueBlue.withOpaque(50)
      stdout.println(`50% opaque: ${semiTransparent}`)

      mostlyOpaque <- opaqueBlue.withOpaque(80)
      stdout.println(`80% opaque: ${mostlyOpaque}`)

      // === COLOUR ARITHMETIC ===

      // Add colours: blends RGB components (clamped to 0-255)
      purple <- pureRed + pureBlue
      stdout.println(`Red + Blue = Purple: ${purple}`)

      yellow <- pureRed + pureGreen
      stdout.println(`Red + Green = Yellow: ${yellow}`)

      cyan <- pureGreen + pureBlue
      stdout.println(`Green + Blue = Cyan: ${cyan}`)

      // Subtract colours: removes RGB components (clamped to 0)
      backToRed <- purple - pureBlue
      stdout.println(`Purple - Blue = Red: ${backToRed}`)
      require backToRed == pureRed

      backToGreen <- yellow - pureRed
      stdout.println(`Yellow - Red = Green: ${backToGreen}`)
      require backToGreen == pureGreen

      // Subtract specific amounts of a channel
      lessRed <- moreSaturated - #9A0000
      stdout.println(`Less red: ${lessRed}`)

      moreBlue <- lessRed + #00001D
      stdout.println(`More blue: ${moreBlue}`)

      // === BITS INTEROP ===

      // Convert Colour to Bits and back
      colourBits <- opaqueBlue.bits()
      stdout.println(`As bits: ${colourBits}`)

      fromBits <- Colour(colourBits)
      require fromBits == opaqueBlue

      // Build colour from raw bit patterns
      redBits <- 0b11111111111111110000000000000000
      redFromBits <- Colour(redBits)
      stdout.println(`Red from bits: ${redFromBits}`)

      // === PROGRAMMATIC LIGHTENING ===

      // Calculate a percentage lighter
      currentLightness <- opaqueBlue.lightness()
      proposedLightness <- currentLightness * 1.9
      clampedLightness <- proposedLightness <? 100
      percentLighter <- opaqueBlue.withLightness(clampedLightness)
      stdout.println(`90% lighter: ${percentLighter}`)

      // === COMPARISON ===

      require pureRed <> pureBlue
      require #FF0000 == #FF0000
      stdout.println(`Red == Red: ${pureRed == #FF0000}`)

Common mistakes

E50060 — EK9 Colour uses 'withLightness' not 'setLightness'. Methods return new immutable values rather than mutating. Check available methods with 'ek9 -h Colour'. See ek9 -h E50060 for details.

Incorrect:

opaqueBlue.setLightness(80)

Correct:

opaqueBlue.withLightness(80)
Other ways to ask this
  • Does EK9 have a built-in colour type?
  • How do I manipulate RGB colours in EK9?
  • How does HSL colour manipulation work in EK9?
  • How do I blend colours in EK9?

Coming from another language?

Java: java.awt.Color tied to AWT, no literals, no HSL. CSS: #hex but declarative only. Python/Rust/Go/JS: no built-in colour type. EK9: built-in #RRGGBB literals, HSL methods, colour arithmetic with clamping, Bits interop.

Keywords: saturation, start, blend, hsl, bits, rgba, argb, transparent, rgb, colour, color, alpha, intro, hex, first, hue, migrate, opacity, beginner, lightness