Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 2 additions & 3 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -9,12 +9,11 @@ on:
jobs:
verify:
runs-on: ubuntu-latest
container: ghcr.io/blueyetisoftware/smartthings-edge-sdk-runtime:16.0.59-51ac4bd

steps:
- name: Checkout
uses: actions/checkout@v3

- name: Run Tests
uses: lunarmodules/busted@v2.3.0
with:
args: spec/
run: busted spec/
1 change: 1 addition & 0 deletions color.lua
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
return require("color.init")
27 changes: 27 additions & 0 deletions color/cct_to_hsv.lua
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
local st_utils = require 'st.utils'
local cct_to_xy = require 'color.cct_to_xy'
local xy_to_hsv = require 'color.xy_to_hsv'

--- Converts correlated color temperature (CCT) to HSV (Hue, Saturation, Value).
---
--- This function converts a correlated color temperature in Kelvin to HSV color space
--- by first converting CCT to CIE 1931 xyY coordinates using the cct_to_xy function,
--- then converting xyY to HSV using the xy_to_hsv function. The resulting HSV represents
--- the color appearance of a blackbody radiator at the given temperature.
---
--- @param cct number correlated color temperature in Kelvin, range [1000,40000]
--- @return number, number, number equivalent hue, saturation, value with each component in range [0,1]
---
--- @raise error if cct is not a number
--- @raise error if cct is outside the valid range [1000,40000]
---
--- @usage
--- local h, s, v = cct_to_hsv(2700) -- Warm white (incandescent bulb)
--- local h, s, v = cct_to_hsv(6500) -- Daylight white (D65)
local function cct_to_hsv(cct)
assert(type(cct) == "number", "cct must be a number")
cct = st_utils.clamp_value(cct, 1000, 40000)
return xy_to_hsv(cct_to_xy(cct))
end

return cct_to_hsv
75 changes: 75 additions & 0 deletions color/cct_to_rgb.lua
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
local st_utils = require 'st.utils'
local from_8bit = require 'color.from_8bit'

--- Converts correlated color temperature (CCT) in Kelvin to RGB color values.
---
--- This function implements Neil Bartlett's 2015 improved approximation algorithm
--- for converting color temperature to RGB values based on blackbody radiation curves.
--- This is a refinement of Tanner Helland's 2012 algorithm, using piecewise linear fits
--- with logarithmic terms to approximate the relationship between temperature and RGB components.
---
--- The coefficients used here are the result of least-squares fitting to match real
--- blackbody chromaticities converted to sRGB. This provides smoother and more accurate
--- results than the original 2012 coefficients, particularly around the 6600K breakpoint.
---
--- @param cct number Color temperature in Kelvin. Valid range: 1000K to 40000K.
--- Values outside this range will be clamped.
--- @return number red Red component in range [0, 1]
--- @return number green Green component in range [0, 1]
--- @return number blue Blue component in range [0, 1]
---
--- @raise error if cct is not a number
---
--- @usage
--- local r, g, b = cct_to_rgb(3000) -- Warm white light
--- local r, g, b = cct_to_rgb(6500) -- Daylight white
---
--- @see https://github.com/neilbartlett/color-temperature
--- @see https://tannerhelland.com/2012/09/18/convert-temperature-rgb-algorithm-code.html
--- @see https://en.wikipedia.org/wiki/Color_temperature
local function cct_to_rgb(cct)
assert(type(cct) == "number", "cct must be a number")
cct = st_utils.clamp_value(cct, 1000, 40000)

local temperature = cct / 100.0 -- scale to match original fitting units (temp in 100-K increments)
local red, green, blue

local fit = function(a, b, c, x)
-- a + b x + c log(x)
return a + b * x + c * math.log(x)
end

-- calculate red

if temperature < 66.0 then -- ~6600 K breakpoint where warmer/cooler blackbody behaviors switch
red = 255
else
red = fit(351.97690566805693, 0.114206453784165, -40.25366309332127, temperature - 55.0)
end

-- calculate green

if temperature < 66.0 then -- ~6600 K breakpoint where warmer/cooler blackbody behaviors switch
green = fit(-155.25485562709179, -0.44596950469579133, 104.49216199393888, temperature - 2)
else
green = fit(325.4494125711974, 0.07943456536662342, -28.0852963507957, temperature - 50)
end

-- calculate blue

if temperature >= 66.0 then -- ~6600 K breakpoint where warmer/cooler blackbody behaviors switch
blue = 255
elseif temperature <= 20.0 then -- below ~2000 K, blue component is negligible (blackbody approximation)
blue = 0
else
blue = fit(-254.76935184120902, 0.8274096064007395, 115.67994401066147, temperature - 10)
end

return from_8bit(
st_utils.clamp_value(red, 0, 255),
st_utils.clamp_value(green, 0, 255),
st_utils.clamp_value(blue, 0, 255)
)
end

return cct_to_rgb
27 changes: 27 additions & 0 deletions color/cct_to_xy.lua
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
local st_utils = require 'st.utils'
local rgb_to_xy = require 'color.rgb_to_xy'
local cct_to_rgb = require 'color.cct_to_rgb'

--- Converts correlated color temperature (CCT) to CIE 1931 xyY color coordinates.
---
--- This function converts a correlated color temperature in Kelvin to CIE 1931 xyY
--- color coordinates by first converting CCT to RGB using the cct_to_rgb function,
--- then converting RGB to xyY using the rgb_to_xy function. The resulting xyY coordinates
--- represent the color appearance of a blackbody radiator at the given temperature.
---
--- @param cct number correlated color temperature in Kelvin, range [1000,40000]
--- @return number, number, number equivalent x, y, Y coordinates where x,y are in [0,1] and Y=1
---
--- @raise error if cct is not a number
--- @raise error if cct is outside the valid range [1000,40000]
---
--- @usage
--- local x, y, Y = cct_to_xy(2700) -- Warm white (incandescent bulb)
--- local x, y, Y = cct_to_xy(6500) -- Daylight white (D65)
local function cct_to_xy(cct)
assert(type(cct) == "number", "cct must be a number")
cct = st_utils.clamp_value(cct, 1000, 40000)
return rgb_to_xy(cct_to_rgb(cct))
end

return cct_to_xy
209 changes: 0 additions & 209 deletions color/color.lua

This file was deleted.

Loading