From 1a5314d0d0cb55ddea621e4d0b963b170c2d9b8b Mon Sep 17 00:00:00 2001 From: Andy Allan Date: Thu, 15 Jan 2026 18:40:00 +0000 Subject: [PATCH 1/7] Start implementing hsluv versions of the color functions --- lib/glug.rb | 1 + lib/glug/hsluv_modifiers.rb | 23 +++++++++++++++++ spec/fixtures/color_functions.glug | 19 ++++++++++++++ spec/fixtures/color_functions.json | 40 ++++++++++++++++++++++++++++++ spec/lib/glug/stylesheet_spec.rb | 9 +++++++ 5 files changed, 92 insertions(+) create mode 100644 lib/glug/hsluv_modifiers.rb create mode 100644 spec/fixtures/color_functions.glug create mode 100644 spec/fixtures/color_functions.json diff --git a/lib/glug.rb b/lib/glug.rb index 113454d..58c8b28 100644 --- a/lib/glug.rb +++ b/lib/glug.rb @@ -10,5 +10,6 @@ module Glug # :nodoc: require_relative 'glug/stylesheet' require_relative 'glug/layer' require_relative 'glug/extensions' +require_relative 'glug/hsluv_modifiers' require_relative 'glug/stylesheet_dsl' require_relative 'glug/layer_dsl' diff --git a/lib/glug/hsluv_modifiers.rb b/lib/glug/hsluv_modifiers.rb new file mode 100644 index 0000000..39e4eaf --- /dev/null +++ b/lib/glug/hsluv_modifiers.rb @@ -0,0 +1,23 @@ +# frozen_string_literal: true + +require 'chroma' +require 'hsluv' + +module Chroma + # Add hsluv versions of the color functions to chroma + class Color + def darkenp(amount = 10) + h, s, l = Hsluv.rgb_to_hsluv(rgb.r / 255.0, rgb.g / 255.0, rgb.b / 255.0) + new_l = [l - amount, 0].max + rgb_values = Hsluv.rgb_prepare(Hsluv.hsluv_to_rgb(h, s, new_l)) + Chroma.paint("rgb(#{rgb_values[0]}, #{rgb_values[1]}, #{rgb_values[2]})") + end + + def lightenp(amount = 10) + h, s, l = Hsluv.rgb_to_hsluv(rgb.r / 255.0, rgb.g / 255.0, rgb.b / 255.0) + new_l = [l + amount, 100].min + rgb_values = Hsluv.rgb_prepare(Hsluv.hsluv_to_rgb(h, s, new_l)) + Chroma.paint("rgb(#{rgb_values[0]}, #{rgb_values[1]}, #{rgb_values[2]})") + end + end +end diff --git a/spec/fixtures/color_functions.glug b/spec/fixtures/color_functions.glug new file mode 100644 index 0000000..ae51520 --- /dev/null +++ b/spec/fixtures/color_functions.glug @@ -0,0 +1,19 @@ +version 8 +name 'My first stylesheet' +source :shortbread, type: 'vector', url: 'https://vector.openstreetmap.org/shortbread_v1/tilejson.json' + +layer(:darken, source: :shortbread) do + line_color 0x998877.chroma(:darken, 20) +end + +layer(:darkenp, source: :shortbread) do + line_color 0x998877.chroma(:darkenp, 20) +end + +layer(:lighten, source: :shortbread) do + line_color 0x998877.chroma(:lighten, 20) +end + +layer(:lightenp, source: :shortbread) do + line_color 0x998877.chroma(:lightenp, 20) +end diff --git a/spec/fixtures/color_functions.json b/spec/fixtures/color_functions.json new file mode 100644 index 0000000..030dff0 --- /dev/null +++ b/spec/fixtures/color_functions.json @@ -0,0 +1,40 @@ +{ + "version":8, + "name":"My first stylesheet", + "sources":{ + "shortbread":{ + "type":"vector", + "url":"https://vector.openstreetmap.org/shortbread_v1/tilejson.json" + } + }, + "layers":[ + { + "paint":{"line-color":"#615549"}, + "source":"shortbread", + "id":"darken", + "source-layer":"darken", + "type":"line" + }, + { + "paint":{"line-color":"#62574b"}, + "source":"shortbread", + "id":"darkenp", + "source-layer":"darkenp", + "type":"line" + }, + { + "paint":{"line-color":"#c5bbb1"}, + "source":"shortbread", + "id":"lighten", + "source-layer":"lighten", + "type":"line" + }, + { + "paint":{"line-color":"#d3bca5"}, + "source":"shortbread", + "id":"lightenp", + "source-layer":"lightenp", + "type":"line" + } + ] +} diff --git a/spec/lib/glug/stylesheet_spec.rb b/spec/lib/glug/stylesheet_spec.rb index f25811e..7c07093 100644 --- a/spec/lib/glug/stylesheet_spec.rb +++ b/spec/lib/glug/stylesheet_spec.rb @@ -113,5 +113,14 @@ output = File.read(File.join(fixture_dir, 'expression_ivars.json')) expect(stylesheet.to_json).to eql(output.strip) end + + it 'handles color functions' do + glug = File.read(File.join(fixture_dir, 'color_functions.glug')) + stylesheet = described_class.new(base_dir: fixture_dir) do + instance_eval(glug) + end + output = File.read(File.join(fixture_dir, 'color_functions.json')) + expect(stylesheet.to_json).to eql(output.strip) + end end end From 323745830307140ab333b436fed9837e4afeba00 Mon Sep 17 00:00:00 2001 From: Andy Allan Date: Thu, 15 Jan 2026 19:01:40 +0000 Subject: [PATCH 2/7] Add saturatep/desaturatep --- lib/glug/hsluv_modifiers.rb | 14 ++++++++++++++ spec/fixtures/color_functions.glug | 16 ++++++++++++++++ spec/fixtures/color_functions.json | 28 ++++++++++++++++++++++++++++ 3 files changed, 58 insertions(+) diff --git a/lib/glug/hsluv_modifiers.rb b/lib/glug/hsluv_modifiers.rb index 39e4eaf..8368c12 100644 --- a/lib/glug/hsluv_modifiers.rb +++ b/lib/glug/hsluv_modifiers.rb @@ -19,5 +19,19 @@ def lightenp(amount = 10) rgb_values = Hsluv.rgb_prepare(Hsluv.hsluv_to_rgb(h, s, new_l)) Chroma.paint("rgb(#{rgb_values[0]}, #{rgb_values[1]}, #{rgb_values[2]})") end + + def saturatep(amount = 10) + h, s, l = Hsluv.rgb_to_hsluv(rgb.r / 255.0, rgb.g / 255.0, rgb.b / 255.0) + new_s = [s + amount, 100].min + rgb_values = Hsluv.rgb_prepare(Hsluv.hsluv_to_rgb(h, new_s, l)) + Chroma.paint("rgb(#{rgb_values[0]}, #{rgb_values[1]}, #{rgb_values[2]})") + end + + def desaturatep(amount = 10) + h, s, l = Hsluv.rgb_to_hsluv(rgb.r / 255.0, rgb.g / 255.0, rgb.b / 255.0) + new_s = [s - amount, 0].max + rgb_values = Hsluv.rgb_prepare(Hsluv.hsluv_to_rgb(h, new_s, l)) + Chroma.paint("rgb(#{rgb_values[0]}, #{rgb_values[1]}, #{rgb_values[2]})") + end end end diff --git a/spec/fixtures/color_functions.glug b/spec/fixtures/color_functions.glug index ae51520..8a67542 100644 --- a/spec/fixtures/color_functions.glug +++ b/spec/fixtures/color_functions.glug @@ -17,3 +17,19 @@ end layer(:lightenp, source: :shortbread) do line_color 0x998877.chroma(:lightenp, 20) end + +layer(:saturate, source: :shortbread) do + line_color 0x998877.chroma(:saturate, 20) +end + +layer(:saturatep, source: :shortbread) do + line_color 0x998877.chroma(:saturatep, 20) +end + +layer(:desaturate, source: :shortbread) do + line_color 0x998877.chroma(:desaturate, 20) +end + +layer(:desaturatep, source: :shortbread) do + line_color 0x998877.chroma(:desaturatep, 20) +end diff --git a/spec/fixtures/color_functions.json b/spec/fixtures/color_functions.json index 030dff0..33ed32e 100644 --- a/spec/fixtures/color_functions.json +++ b/spec/fixtures/color_functions.json @@ -35,6 +35,34 @@ "id":"lightenp", "source-layer":"lightenp", "type":"line" + }, + { + "paint":{"line-color":"#b1885f"}, + "source":"shortbread", + "id":"saturate", + "source-layer":"saturate", + "type":"line" + }, + { + "paint":{"line-color":"#a28665"}, + "source":"shortbread", + "id":"saturatep", + "source-layer":"saturatep", + "type":"line" + }, + { + "paint":{"line-color":"#888888"}, + "source":"shortbread", + "id":"desaturate", + "source-layer":"desaturate", + "type":"line" + }, + { + "paint":{"line-color":"#8e8a87"}, + "source":"shortbread", + "id":"desaturatep", + "source-layer":"desaturatep", + "type":"line" } ] } From 01da68a8064bd5605487848ab8228bdfce8e8d22 Mon Sep 17 00:00:00 2001 From: Andy Allan Date: Fri, 16 Jan 2026 09:26:07 +0000 Subject: [PATCH 3/7] Allow greyscale to work with no parameters, and add greyscalep --- lib/glug/extensions.rb | 7 ++++--- lib/glug/hsluv_modifiers.rb | 7 +++++++ spec/fixtures/color_functions.glug | 8 ++++++++ spec/fixtures/color_functions.json | 14 ++++++++++++++ 4 files changed, 33 insertions(+), 3 deletions(-) diff --git a/lib/glug/extensions.rb b/lib/glug/extensions.rb index b7e0708..bd82e8b 100644 --- a/lib/glug/extensions.rb +++ b/lib/glug/extensions.rb @@ -6,11 +6,12 @@ # Colour methods on Integer class Integer # rubocop:disable Style/StringConcatenation, Naming/MethodParameterName - def chroma_hex(op, p) - ('#' + to_s(16).rjust(6, '0')).paint.send(op, p).to_hex + def chroma_hex(op, p = nil) + color = ('#' + to_s(16).rjust(6, '0')).paint + (p.nil? ? color.send(op) : color.send(op, p)).to_hex end - def chroma(op, p) + def chroma(op, p = nil) chroma_hex(op, p).gsub('#', '0x').to_i(16) end diff --git a/lib/glug/hsluv_modifiers.rb b/lib/glug/hsluv_modifiers.rb index 8368c12..b76fd81 100644 --- a/lib/glug/hsluv_modifiers.rb +++ b/lib/glug/hsluv_modifiers.rb @@ -33,5 +33,12 @@ def desaturatep(amount = 10) rgb_values = Hsluv.rgb_prepare(Hsluv.hsluv_to_rgb(h, new_s, l)) Chroma.paint("rgb(#{rgb_values[0]}, #{rgb_values[1]}, #{rgb_values[2]})") end + + def greyscalep + h, _s, l = Hsluv.rgb_to_hsluv(rgb.r / 255.0, rgb.g / 255.0, rgb.b / 255.0) + rgb_values = Hsluv.rgb_prepare(Hsluv.hsluv_to_rgb(h, 0, l)) + Chroma.paint("rgb(#{rgb_values[0]}, #{rgb_values[1]}, #{rgb_values[2]})") + end + alias grayscalep greyscalep end end diff --git a/spec/fixtures/color_functions.glug b/spec/fixtures/color_functions.glug index 8a67542..f5fc2ba 100644 --- a/spec/fixtures/color_functions.glug +++ b/spec/fixtures/color_functions.glug @@ -33,3 +33,11 @@ end layer(:desaturatep, source: :shortbread) do line_color 0x998877.chroma(:desaturatep, 20) end + +layer(:greyscale, source: :shortbread) do + line_color 0x998877.chroma(:greyscale) +end + +layer(:greyscalep, source: :shortbread) do + line_color 0x998877.chroma(:greyscalep) +end diff --git a/spec/fixtures/color_functions.json b/spec/fixtures/color_functions.json index 33ed32e..8535e45 100644 --- a/spec/fixtures/color_functions.json +++ b/spec/fixtures/color_functions.json @@ -63,6 +63,20 @@ "id":"desaturatep", "source-layer":"desaturatep", "type":"line" + }, + { + "paint":{"line-color":"#888888"}, + "source":"shortbread", + "id":"greyscale", + "source-layer":"greyscale", + "type":"line" + }, + { + "paint":{"line-color":"#8b8b8b"}, + "source":"shortbread", + "id":"greyscalep", + "source-layer":"greyscalep", + "type":"line" } ] } From e7ffc83dca7cde54aa0521474f2e475f8df991f5 Mon Sep 17 00:00:00 2001 From: Andy Allan Date: Fri, 16 Jan 2026 13:59:31 +0000 Subject: [PATCH 4/7] Implement spinp --- lib/glug/hsluv_modifiers.rb | 7 +++++++ spec/fixtures/color_functions.glug | 8 ++++++++ spec/fixtures/color_functions.json | 14 ++++++++++++++ 3 files changed, 29 insertions(+) diff --git a/lib/glug/hsluv_modifiers.rb b/lib/glug/hsluv_modifiers.rb index b76fd81..0db95e5 100644 --- a/lib/glug/hsluv_modifiers.rb +++ b/lib/glug/hsluv_modifiers.rb @@ -40,5 +40,12 @@ def greyscalep Chroma.paint("rgb(#{rgb_values[0]}, #{rgb_values[1]}, #{rgb_values[2]})") end alias grayscalep greyscalep + + def spinp(amount = 0) + h, s, l = Hsluv.rgb_to_hsluv(rgb.r / 255.0, rgb.g / 255.0, rgb.b / 255.0) + new_h = (h + amount) % 360 + rgb_values = Hsluv.rgb_prepare(Hsluv.hsluv_to_rgb(new_h, s, l)) + Chroma.paint("rgb(#{rgb_values[0]}, #{rgb_values[1]}, #{rgb_values[2]})") + end end end diff --git a/spec/fixtures/color_functions.glug b/spec/fixtures/color_functions.glug index f5fc2ba..32a833e 100644 --- a/spec/fixtures/color_functions.glug +++ b/spec/fixtures/color_functions.glug @@ -41,3 +41,11 @@ end layer(:greyscalep, source: :shortbread) do line_color 0x998877.chroma(:greyscalep) end + +layer(:spin, source: :shortbread) do + line_color 0x998877.chroma(:spin, 20) +end + +layer(:spinp, source: :shortbread) do + line_color 0x998877.chroma(:spinp, 20) +end diff --git a/spec/fixtures/color_functions.json b/spec/fixtures/color_functions.json index 8535e45..17ddd60 100644 --- a/spec/fixtures/color_functions.json +++ b/spec/fixtures/color_functions.json @@ -77,6 +77,20 @@ "id":"greyscalep", "source-layer":"greyscalep", "type":"line" + }, + { + "paint":{"line-color":"#999377"}, + "source":"shortbread", + "id":"spin", + "source-layer":"spin", + "type":"line" + }, + { + "paint":{"line-color":"#918b77"}, + "source":"shortbread", + "id":"spinp", + "source-layer":"spinp", + "type":"line" } ] } From c787f0767f0597f7633635e9c424a37425fd775e Mon Sep 17 00:00:00 2001 From: Andy Allan Date: Fri, 16 Jan 2026 14:00:06 +0000 Subject: [PATCH 5/7] Implement mix and mixp --- lib/glug.rb | 1 + lib/glug/chroma_extensions.rb | 19 +++++++++++++++++++ lib/glug/extensions.rb | 12 ++++++------ lib/glug/hsluv_modifiers.rb | 20 ++++++++++++++++++++ spec/fixtures/color_functions.glug | 8 ++++++++ spec/fixtures/color_functions.json | 14 ++++++++++++++ 6 files changed, 68 insertions(+), 6 deletions(-) create mode 100644 lib/glug/chroma_extensions.rb diff --git a/lib/glug.rb b/lib/glug.rb index 58c8b28..54e7fd3 100644 --- a/lib/glug.rb +++ b/lib/glug.rb @@ -11,5 +11,6 @@ module Glug # :nodoc: require_relative 'glug/layer' require_relative 'glug/extensions' require_relative 'glug/hsluv_modifiers' +require_relative 'glug/chroma_extensions' require_relative 'glug/stylesheet_dsl' require_relative 'glug/layer_dsl' diff --git a/lib/glug/chroma_extensions.rb b/lib/glug/chroma_extensions.rb new file mode 100644 index 0000000..0d82dd5 --- /dev/null +++ b/lib/glug/chroma_extensions.rb @@ -0,0 +1,19 @@ +# frozen_string_literal: true + +require 'chroma' +require 'hsluv' + +module Chroma + class Color # :nodoc: + def mix(other, weight = 50) + other = other.paint if other.is_a?(String) + p = weight / 100.0 + + r = (rgb.r * p + other.rgb.r * (1 - p)).round + g = (rgb.g * p + other.rgb.g * (1 - p)).round + b = (rgb.b * p + other.rgb.b * (1 - p)).round + + Chroma.paint("rgb(#{r}, #{g}, #{b})") + end + end +end diff --git a/lib/glug/extensions.rb b/lib/glug/extensions.rb index bd82e8b..4a502e5 100644 --- a/lib/glug/extensions.rb +++ b/lib/glug/extensions.rb @@ -5,20 +5,20 @@ # Colour methods on Integer class Integer - # rubocop:disable Style/StringConcatenation, Naming/MethodParameterName - def chroma_hex(op, p = nil) + # rubocop:disable Style/StringConcatenation + def chroma_hex(op, *args) color = ('#' + to_s(16).rjust(6, '0')).paint - (p.nil? ? color.send(op) : color.send(op, p)).to_hex + color.send(op, *args).to_hex end - def chroma(op, p = nil) - chroma_hex(op, p).gsub('#', '0x').to_i(16) + def chroma(op, *args) + chroma_hex(op, *args).gsub('#', '0x').to_i(16) end def to_hex_color '#' + to_s(16).rjust(6, '0') end - # rubocop:enable Style/StringConcatenation, Naming/MethodParameterName + # rubocop:enable Style/StringConcatenation end # Top-level colour generators diff --git a/lib/glug/hsluv_modifiers.rb b/lib/glug/hsluv_modifiers.rb index 0db95e5..94f948f 100644 --- a/lib/glug/hsluv_modifiers.rb +++ b/lib/glug/hsluv_modifiers.rb @@ -47,5 +47,25 @@ def spinp(amount = 0) rgb_values = Hsluv.rgb_prepare(Hsluv.hsluv_to_rgb(new_h, s, l)) Chroma.paint("rgb(#{rgb_values[0]}, #{rgb_values[1]}, #{rgb_values[2]})") end + + def mixp(other, weight = 50) + other = other.paint if other.is_a?(String) + p = weight / 100.0 + + h1, s1, l1 = Hsluv.rgb_to_hsluv(rgb.r / 255.0, rgb.g / 255.0, rgb.b / 255.0) + h2, s2, l2 = Hsluv.rgb_to_hsluv(other.rgb.r / 255.0, other.rgb.g / 255.0, other.rgb.b / 255.0) + + # Interpolate hue on the shortest path around the color wheel + h_diff = h2 - h1 + h_diff -= 360 if h_diff > 180 + h_diff += 360 if h_diff < -180 + new_h = (h1 + h_diff * (1 - p)) % 360 + + new_s = s1 * p + s2 * (1 - p) + new_l = l1 * p + l2 * (1 - p) + + rgb_values = Hsluv.rgb_prepare(Hsluv.hsluv_to_rgb(new_h, new_s, new_l)) + Chroma.paint("rgb(#{rgb_values[0]}, #{rgb_values[1]}, #{rgb_values[2]})") + end end end diff --git a/spec/fixtures/color_functions.glug b/spec/fixtures/color_functions.glug index 32a833e..8cce2bc 100644 --- a/spec/fixtures/color_functions.glug +++ b/spec/fixtures/color_functions.glug @@ -49,3 +49,11 @@ end layer(:spinp, source: :shortbread) do line_color 0x998877.chroma(:spinp, 20) end + +layer(:mix, source: :shortbread) do + line_color 0x998877.chroma(:mix, '#ffffff', 50) +end + +layer(:mixp, source: :shortbread) do + line_color 0x998877.chroma(:mixp, '#ffffff', 50) +end diff --git a/spec/fixtures/color_functions.json b/spec/fixtures/color_functions.json index 17ddd60..a10ab55 100644 --- a/spec/fixtures/color_functions.json +++ b/spec/fixtures/color_functions.json @@ -91,6 +91,20 @@ "id":"spinp", "source-layer":"spinp", "type":"line" + }, + { + "paint":{"line-color":"#ccc4bb"}, + "source":"shortbread", + "id":"mix", + "source-layer":"mix", + "type":"line" + }, + { + "paint":{"line-color":"#cdc1be"}, + "source":"shortbread", + "id":"mixp", + "source-layer":"mixp", + "type":"line" } ] } From 71d7f4eda24c61cba0f72127218e5462859bf138 Mon Sep 17 00:00:00 2001 From: Andy Allan Date: Fri, 16 Jan 2026 16:15:11 +0000 Subject: [PATCH 6/7] Refactor hsluv colour conversions to use helpers, for clarity --- lib/glug/hsluv_modifiers.rb | 59 +++++++++++++++++++------------------ 1 file changed, 30 insertions(+), 29 deletions(-) diff --git a/lib/glug/hsluv_modifiers.rb b/lib/glug/hsluv_modifiers.rb index 94f948f..5008c66 100644 --- a/lib/glug/hsluv_modifiers.rb +++ b/lib/glug/hsluv_modifiers.rb @@ -7,64 +7,65 @@ module Chroma # Add hsluv versions of the color functions to chroma class Color def darkenp(amount = 10) - h, s, l = Hsluv.rgb_to_hsluv(rgb.r / 255.0, rgb.g / 255.0, rgb.b / 255.0) - new_l = [l - amount, 0].max - rgb_values = Hsluv.rgb_prepare(Hsluv.hsluv_to_rgb(h, s, new_l)) - Chroma.paint("rgb(#{rgb_values[0]}, #{rgb_values[1]}, #{rgb_values[2]})") + h, s, l = to_hsluv + l = [l - amount, 0].max + from_hsluv(h, s, l) end def lightenp(amount = 10) - h, s, l = Hsluv.rgb_to_hsluv(rgb.r / 255.0, rgb.g / 255.0, rgb.b / 255.0) - new_l = [l + amount, 100].min - rgb_values = Hsluv.rgb_prepare(Hsluv.hsluv_to_rgb(h, s, new_l)) - Chroma.paint("rgb(#{rgb_values[0]}, #{rgb_values[1]}, #{rgb_values[2]})") + h, s, l = to_hsluv + l = [l + amount, 100].min + from_hsluv(h, s, l) end def saturatep(amount = 10) - h, s, l = Hsluv.rgb_to_hsluv(rgb.r / 255.0, rgb.g / 255.0, rgb.b / 255.0) - new_s = [s + amount, 100].min - rgb_values = Hsluv.rgb_prepare(Hsluv.hsluv_to_rgb(h, new_s, l)) - Chroma.paint("rgb(#{rgb_values[0]}, #{rgb_values[1]}, #{rgb_values[2]})") + h, s, l = to_hsluv + s = [s + amount, 100].min + from_hsluv(h, s, l) end def desaturatep(amount = 10) - h, s, l = Hsluv.rgb_to_hsluv(rgb.r / 255.0, rgb.g / 255.0, rgb.b / 255.0) - new_s = [s - amount, 0].max - rgb_values = Hsluv.rgb_prepare(Hsluv.hsluv_to_rgb(h, new_s, l)) - Chroma.paint("rgb(#{rgb_values[0]}, #{rgb_values[1]}, #{rgb_values[2]})") + h, s, l = to_hsluv + s = [s - amount, 0].max + from_hsluv(h, s, l) end def greyscalep - h, _s, l = Hsluv.rgb_to_hsluv(rgb.r / 255.0, rgb.g / 255.0, rgb.b / 255.0) - rgb_values = Hsluv.rgb_prepare(Hsluv.hsluv_to_rgb(h, 0, l)) - Chroma.paint("rgb(#{rgb_values[0]}, #{rgb_values[1]}, #{rgb_values[2]})") + h, _s, l = to_hsluv + from_hsluv(h, 0, l) end alias grayscalep greyscalep def spinp(amount = 0) - h, s, l = Hsluv.rgb_to_hsluv(rgb.r / 255.0, rgb.g / 255.0, rgb.b / 255.0) - new_h = (h + amount) % 360 - rgb_values = Hsluv.rgb_prepare(Hsluv.hsluv_to_rgb(new_h, s, l)) - Chroma.paint("rgb(#{rgb_values[0]}, #{rgb_values[1]}, #{rgb_values[2]})") + h, s, l = to_hsluv + h = (h + amount) % 360 + from_hsluv(h, s, l) end def mixp(other, weight = 50) other = other.paint if other.is_a?(String) p = weight / 100.0 - h1, s1, l1 = Hsluv.rgb_to_hsluv(rgb.r / 255.0, rgb.g / 255.0, rgb.b / 255.0) - h2, s2, l2 = Hsluv.rgb_to_hsluv(other.rgb.r / 255.0, other.rgb.g / 255.0, other.rgb.b / 255.0) + h1, s1, l1 = to_hsluv + h2, s2, l2 = other.to_hsluv # Interpolate hue on the shortest path around the color wheel h_diff = h2 - h1 h_diff -= 360 if h_diff > 180 h_diff += 360 if h_diff < -180 - new_h = (h1 + h_diff * (1 - p)) % 360 + h = (h1 + h_diff * (1 - p)) % 360 + s = s1 * p + s2 * (1 - p) + l = l1 * p + l2 * (1 - p) + + from_hsluv(h, s, l) + end - new_s = s1 * p + s2 * (1 - p) - new_l = l1 * p + l2 * (1 - p) + def to_hsluv + Hsluv.rgb_to_hsluv(rgb.r / 255.0, rgb.g / 255.0, rgb.b / 255.0) + end - rgb_values = Hsluv.rgb_prepare(Hsluv.hsluv_to_rgb(new_h, new_s, new_l)) + def from_hsluv(h, s, l) # rubocop:disable Naming/MethodParameterName + rgb_values = Hsluv.rgb_prepare(Hsluv.hsluv_to_rgb(h, s, l)) Chroma.paint("rgb(#{rgb_values[0]}, #{rgb_values[1]}, #{rgb_values[2]})") end end From 8381834af49c25f824163874ff20bfaf040da50a Mon Sep 17 00:00:00 2001 From: Andy Allan Date: Thu, 22 Jan 2026 13:35:19 +0000 Subject: [PATCH 7/7] Pass mutable strings to Chroma Chroma currently expects all strings to be mutable. Calling this from frozen_string_literal files works fine, but not on ruby 2.7 where things were handled differently. This syntax can be removed when either a) Chroma accepts frozen strings or b) ruby 2.7 support is dropped --- lib/glug/chroma_extensions.rb | 2 +- lib/glug/hsluv_modifiers.rb | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/glug/chroma_extensions.rb b/lib/glug/chroma_extensions.rb index 0d82dd5..56905a8 100644 --- a/lib/glug/chroma_extensions.rb +++ b/lib/glug/chroma_extensions.rb @@ -13,7 +13,7 @@ def mix(other, weight = 50) g = (rgb.g * p + other.rgb.g * (1 - p)).round b = (rgb.b * p + other.rgb.b * (1 - p)).round - Chroma.paint("rgb(#{r}, #{g}, #{b})") + Chroma.paint(+"rgb(#{r}, #{g}, #{b})") end end end diff --git a/lib/glug/hsluv_modifiers.rb b/lib/glug/hsluv_modifiers.rb index 5008c66..0bc1683 100644 --- a/lib/glug/hsluv_modifiers.rb +++ b/lib/glug/hsluv_modifiers.rb @@ -66,7 +66,7 @@ def to_hsluv def from_hsluv(h, s, l) # rubocop:disable Naming/MethodParameterName rgb_values = Hsluv.rgb_prepare(Hsluv.hsluv_to_rgb(h, s, l)) - Chroma.paint("rgb(#{rgb_values[0]}, #{rgb_values[1]}, #{rgb_values[2]})") + Chroma.paint(+"rgb(#{rgb_values[0]}, #{rgb_values[1]}, #{rgb_values[2]})") end end end