Skip to content
This repository has been archived by the owner on May 6, 2024. It is now read-only.

Commit

Permalink
v1.3.0 (#4)
Browse files Browse the repository at this point in the history
  • Loading branch information
cxmeel authored Apr 25, 2022
1 parent 0c411fe commit a49b240
Show file tree
Hide file tree
Showing 35 changed files with 930 additions and 21 deletions.
4 changes: 3 additions & 1 deletion .github/FUNDING.yml
Original file line number Diff line number Diff line change
@@ -1,2 +1,4 @@
github: csqrl
custom: ["https://csqrl.itch.io/colour-utils"]
custom:
- "https://csqrl.itch.io/colour-utils"
- "https://roblox.com/groups/9536808/csqrl#!/store"
20 changes: 20 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,25 @@
# Changelog

## [1.3.0]

### Added

- Implemented colour space conversions.
- `HSL` (`.fromHSL, .toHSL`).
- `LAB` (`.fromLAB, .toLAB`) _(:test_tube: experimental)_.
- `LCH` (`.fromLCH, .toLCH`) _(:test_tube: experimental)_.
- Saturation methods to either saturate or desaturate a colour.
- Tailwind CSS-style palette generator - Generates 10 swatches, given a base colour, and returns a `TailwindPalette` object (see the docs for more details).

### Changed

- Updated the docs for Hex and Int. The converter methods were previously documented in PascalCase, but they should have been documented in camelCase.
- The `Palette.Monochromatic` method now accepts an optional second parameter, `swatches`, which defaults to `3`. This is to allow for more control over the number of swatches generated.
- **:warning: Warning:** The behaviour of monochromatic has been changed to allow for more control over the number of swatches generated.
- The new behaviour will return `X` amount of swatches, **including** the base colour. The results do not necessarily include a single lighter and darker swatch, and the resulting array is now sorted from darkest to lightest (most vibrant).

###

## [1.2.0]

### Added
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@rbxts/colour-utils",
"version": "1.2.0",
"version": "1.3.0",
"description": "Colour manipulation utility for Roblox",
"homepage": "https://github.com/csqrl/colour-utils",
"main": "src/init.lua",
Expand Down
19 changes: 19 additions & 0 deletions src/Desaturate.lua
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
local Assert = require(script.Parent._Util.Assert)
local Saturate = require(script.Parent.Saturate)

local assertTypeOf = Assert.prepTypeOf("Saturate")

--[=[
@function Desaturate
@within ColourUtils
@param colour Color3 -- The colour to desaturate.
@param coefficient number -- The coefficient to desaturate by [0-1].
@return Color3 -- The desaturated colour.
]=]
return function(colour: Color3, coefficient: number): Color3
assertTypeOf("colour", "Color3", colour)
assertTypeOf("coefficient", "number", coefficient)

return Saturate(colour, -coefficient)
end
35 changes: 35 additions & 0 deletions src/Desaturate.spec.lua
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
local BasicallyIdentical = require(script.Parent._Util.BasicallyIdentical)

return function()
local Desaturate = require(script.Parent.Desaturate)

local TEST_BASE = Color3.fromHex("#00a2ff")
local TEST_RESULT = Color3.fromHex("#80d0ff")
local TEST_AMOUNT = 0.5

it("should desaturate a colour", function()
local colour = Desaturate(TEST_BASE, TEST_AMOUNT)

expect(BasicallyIdentical(TEST_RESULT, colour)).to.equal(true)
end)

it("throws if argument is not a Color3", function()
expect(function()
Desaturate(nil, TEST_AMOUNT)
end).to.throw()

expect(function()
Desaturate(true, TEST_AMOUNT)
end).to.throw()
end)

it("throws if amount is not a number", function()
expect(function()
Desaturate(TEST_BASE, nil)
end).to.throw()

expect(function()
Desaturate(TEST_BASE, true)
end).to.throw()
end)
end
117 changes: 117 additions & 0 deletions src/HSL/init.lua
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
local Assert = require(script.Parent._Util.Assert)

local round = math.round
local floor = math.floor
local clamp = math.clamp
local min = math.min
local max = math.max
local abs = math.abs

--[=[
@interface HSL
@within HSL
.H number
.S number
.L number
]=]
export type HSL = {
H: number,
S: number,
L: number,
}

--[=[
@function toHSL
@within HSL
@param colour Color3 -- The colour to convert.
@return HSL -- The HSL representation of the colour.
]=]
local function ToHSL(colour: Color3): HSL
Assert.typeOf("ToHSL", "colour", "Color3", colour)

local channelMin = min(colour.R, colour.G, colour.B)
local channelMax = max(colour.R, colour.G, colour.B)
local delta = channelMax - channelMin

local hue = 0
local lightness = (channelMax + channelMin) / 2
local saturation = if delta == 0 then 0 else delta / (1 - abs(2 * lightness - 1))

if delta == 0 then
hue = 0
elseif channelMax == colour.R then
hue = ((colour.G - colour.B) / delta) % 6
elseif channelMax == colour.G then
hue = (colour.B - colour.R) / delta + 2
else
hue = (colour.R - colour.G) / delta + 4
end

hue = round(hue * 60)

if hue < 0 then
hue += 360
end

saturation = clamp(abs(round(saturation * 100)), 0, 100)
lightness = clamp(abs(round(lightness * 100)), 0, 100)

return {
H = hue,
S = saturation,
L = lightness,
}
end

--[=[
@function fromHSL
@within HSL
@param hsl HSL -- The HSL colour to convert.
@return Color3 -- The resulting Color3.
]=]
local function FromHSL(hsl: HSL): Color3
Assert.typeOf("FromHSL", "hsl", "table", hsl)

Assert.typeOf("FromHSL", "hsl.H", "number", hsl.H)
Assert.typeOf("FromHSL", "hsl.S", "number", hsl.S)
Assert.typeOf("FromHSL", "hsl.L", "number", hsl.L)

local saturation = hsl.S / 100
local lightness = hsl.L / 100

local c = (1 - abs(2 * lightness - 1)) * saturation
local x = c * (1 - abs((hsl.H / 60) % 2 - 1))
local m = lightness - c / 2

local red, green, blue = 0, 0, 0

if hsl.H >= 0 and hsl.H < 60 then
red, green, blue = c, x, 0
elseif hsl.H >= 60 and hsl.H < 120 then
red, green, blue = x, c, 0
elseif hsl.H >= 120 and hsl.H < 180 then
red, green, blue = 0, c, x
elseif hsl.H >= 180 and hsl.H < 240 then
red, green, blue = 0, x, c
elseif hsl.H >= 240 and hsl.H < 300 then
red, green, blue = x, 0, c
elseif hsl.H >= 300 and hsl.H < 360 then
red, green, blue = c, 0, x
end

red = min(floor((red + m) * 255), 255)
green = min(floor((green + m) * 255), 255)
blue = min(floor((blue + m) * 255), 255)

return Color3.fromRGB(red, green, blue)
end

--[=[
@class HSL
]=]
return {
fromHSL = FromHSL,
toHSL = ToHSL,
}
42 changes: 42 additions & 0 deletions src/HSL/init.spec.lua
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
local BasicallyIdentical = require(script.Parent.Parent._Util.BasicallyIdentical)

return function()
local HSL = require(script.Parent)

local TEST_COLOR3 = Color3.fromHex("#00a2ff")
local TEST_HSL = { H = 202, S = 100, L = 50 }

describe("toHSL", function()
it("throws if argument is not a Color3", function()
expect(function()
HSL.toHSL(nil)
end).to.throw()
end)

it("should convert to HSL", function()
local hsl = HSL.toHSL(TEST_COLOR3)

expect(hsl.H).to.equal(TEST_HSL.H)
expect(hsl.S).to.equal(TEST_HSL.S)
expect(hsl.L).to.equal(TEST_HSL.L)
end)
end)

describe("fromHSL", function()
it("throws if argument is not a HSL table", function()
expect(function()
HSL.fromHSL(nil)
end).to.throw()

expect(function()
HSL.fromHSL({})
end).to.throw()
end)

it("should convert to Color3", function()
local color3 = HSL.fromHSL(TEST_HSL)

expect(BasicallyIdentical(color3, TEST_COLOR3)).to.equal(true)
end)
end)
end
4 changes: 2 additions & 2 deletions src/Hex/init.lua
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ local HEX_EXCLUDE_PATTERN = "[^A-Fa-f0-9]"
local HEX_FORMAT_PATTERN = "%.2x%.2x%.2x"

--[=[
@function FromHex
@function fromHex
@within Hex
:::tip
Expand Down Expand Up @@ -51,7 +51,7 @@ local function FromHex(hex: string): Color3
end

--[=[
@function ToHex
@function toHex
@within Hex
:::note
Expand Down
4 changes: 2 additions & 2 deletions src/Int/init.lua
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ local lshift = bit32.lshift
local band = bit32.band

--[=[
@function FromInt
@function fromInt
@within Int
@param int number -- The integer to convert.
Expand All @@ -25,7 +25,7 @@ local function FromInt(int: number): Color3
end

--[=[
@function ToInt
@function toInt
@within Int
@param colour Color3 -- The colour to convert.
Expand Down
25 changes: 25 additions & 0 deletions src/LAB/Constants.lua
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
--[=[
@interface LAB
@within LAB
.L number
.A number
.B number
]=]
export type LAB = {
L: number,
A: number,
B: number,
}

return {
Kn = 18,

Xn = 0.950470,
Yn = 1,
Zn = 1.088830,

t0 = 0.137931034,
t1 = 0.206896552,
t2 = 0.12841855,
t3 = 0.008856452,
}
43 changes: 43 additions & 0 deletions src/LAB/FromLAB.lua
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
local Assert = require(script.Parent.Parent._Util.Assert)
local isNaN = require(script.Parent.Parent._Util.isNaN)

local CONST = require(script.Parent.Constants)

local function XYZ_RGB(value: number): number
return 255 * (if value <= 0.00304 then 12.92 * value else 1.055 * value ^ (1 / 2.4) - 0.055)
end

local function LAB_XYZ(value: number): number
return if value > CONST.t1 then value ^ 3 else CONST.t2 * (value - CONST.t0)
end

--[=[
@function fromLAB
@within LAB
@param lab LAB -- The colour to convert.
@return Color3 -- The converted colour.
]=]
local function FromLAB(lab: CONST.LAB): Color3
Assert.typeOf("FromLAB", "lab", "table", lab)

Assert.typeOf("FromLAB", "lab.L", "number", lab.L)
Assert.typeOf("FromLAB", "lab.A", "number", lab.A)
Assert.typeOf("FromLAB", "lab.B", "number", lab.B)

local y = (lab.L + 16) / 116
local x = if isNaN(lab.A) then y else y + lab.A / 500
local z = if isNaN(lab.B) then y else y - lab.B / 200

y = CONST.Yn * LAB_XYZ(y)
x = CONST.Xn * LAB_XYZ(x)
z = CONST.Zn * LAB_XYZ(z)

local red = XYZ_RGB(3.2404542 * x - 1.5371385 * y - 0.4985314 * z)
local green = XYZ_RGB(-0.9692660 * x + 1.8760108 * y + 0.0415560 * z)
local blue = XYZ_RGB(0.0556434 * x - 0.2040259 * y + 1.0572252 * z)

return Color3.fromRGB(red, green, blue)
end

return FromLAB
24 changes: 24 additions & 0 deletions src/LAB/FromLAB.spec.lua
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
local BasicallyIdentical = require(script.Parent.Parent._Util.BasicallyIdentical)

return function()
local FromLab = require(script.Parent.FromLAB)

local TEST_COLOR3 = Color3.fromHex("#00a2ff")
local TEST_LAB = { L = 64.21, A = -1.67, B = -55.7 }

it("converts a LAB colour to a Color3", function()
local color3 = FromLab(TEST_LAB)

expect(BasicallyIdentical(color3, TEST_COLOR3)).to.equal(true)
end)

it("throws if argument is not a LAB table", function()
expect(function()
FromLab(nil)
end).to.throw()

expect(function()
FromLab({})
end).to.throw()
end)
end
Loading

0 comments on commit a49b240

Please sign in to comment.