Last active
March 24, 2024 01:03
-
-
Save tobiashm/243103 to your computer and use it in GitHub Desktop.
contrast-color for SASS
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Gem::Specification.new do |spec| | |
spec.name = 'contrast-color' | |
spec.version = '0.1.1' | |
spec.platform = Gem::Platform::RUBY | |
spec.author = 'Tobias H. Michaelsen' | |
spec.email = '[email protected]' | |
spec.summary = 'Generate contrast colors in SASS' | |
spec.description = 'A simple extension to SASS that alows you to calculate colors that conforms to WAI and WCAG20 contrast requirements.' | |
spec.homepage = 'https://gist.github.com/243103' | |
spec.license = 'MIT' | |
spec.files = ['contrast-color.rb'] | |
spec.test_file = 'test.rb' | |
spec.require_path = '.' | |
spec.add_dependency('sass') | |
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
require 'sass' | |
module ContrastColor | |
BRIGHTNESS_COEFS = [0.299, 0.587, 0.114] | |
LUMINANCE_COEFS = [0.2126, 0.7152, 0.0722] | |
module Color | |
def diff(other) | |
# W3C | |
Utils.sum(Utils.abs(rgb, other.rgb)) | |
end | |
def diff_alt(other) | |
# 3D - Sqrt(dr^2+dg^2+db^2) | |
Math.sqrt(Utils.sum(Utils.sq(Utils.abs(rgb, other.rgb)))) | |
end | |
def brightness | |
# W3C; Rec. 601 luma | |
Utils.sum(Utils.mul(rgb, BRIGHTNESS_COEFS)) | |
end | |
def brightness_alt | |
# http://alienryderflex.com/hsp.html | |
Math.sqrt(Utils.sum(Utils.mul(Utils.sq(rgb), [0.241, 0.691, 0.068]))) | |
end | |
def luminance | |
# http://www.w3.org/TR/WCAG20/#relativeluminancedef | |
norm_rgb = rgb.map { |value| value.to_f / 255 } | |
Utils.sum(Utils.mul(norm_rgb.map { |v| v <= 0.03928 ? v/12.92 : ((v+0.055)/1.055) ** 2.4 }, LUMINANCE_COEFS)) | |
end | |
end | |
module Functions | |
def contrast_color(color, seed_color = nil) | |
seed_color ||= color | |
assert_type color, :Color | |
assert_type seed_color, :Color | |
direction = color.brightness > 127 ? darken_method : lighten_method | |
new_color = seed_color | |
percentage = 0.0 | |
until conform(new_color, color) or percentage > 100.0 do | |
amount = Sass::Script::Number.new percentage, ['%'] | |
new_color = self.send direction, seed_color, amount | |
percentage += 0.1 | |
end | |
new_color | |
end | |
# http://www.w3.org/WAI/ER/WD-AERT/#color-contrast | |
MIN_BRIGHT_DIFF = 125 | |
MIN_COLOR_DIFF = 500 | |
def conform(color1, color2, wcag20_level = :aa) | |
wcag20_level = :aa unless /^aaa?(?:_large)?$/ === wcag20_level.to_s | |
bright_diff = (color1.brightness - color2.brightness).abs | |
color_diff = color1.diff(color2) | |
bright_diff >= MIN_BRIGHT_DIFF && color_diff >= MIN_COLOR_DIFF && send("wcag20_conform_#{wcag20_level}".to_sym, color1, color2) | |
end | |
# http://www.w3.org/TR/WCAG20/#visual-audio-contrast-contrast | |
MIN_CONTRAST_RATE_AA = 4.5 | |
MIN_CONTRAST_RATE_AA_LARGE_TEXT = 3 | |
MIN_CONTRAST_RATE_AAA = 7 | |
MIN_CONTRAST_RATE_AAA_LARGE_TEXT = 4.5 | |
def wcag20_conform_aa(color1, color2) | |
contrast_ratio(color1, color2) >= MIN_CONTRAST_RATE_AA | |
end | |
alias :wcag20_conform :wcag20_conform_aa | |
def wcag20_conform_aa_large(color1, color2) | |
contrast_ratio(color1, color2) >= MIN_CONTRAST_RATE_AA_LARGE_TEXT | |
end | |
def wcag20_conform_aaa(color1, color2) | |
contrast_ratio(color1, color2) >= MIN_CONTRAST_RATE_AAA | |
end | |
def wcag20_conform_aaa_large(color1, color2) | |
contrast_ratio(color1, color2) >= MIN_CONTRAST_RATE_AAA_LARGE_TEXT | |
end | |
def contrast_ratio(color1, color2) | |
assert_type color1, :Color | |
assert_type color2, :Color | |
l1, l2 = color1.luminance, color2.luminance | |
l2, l1 = l1, l2 if l2 > l1 | |
(l1 + 0.05) / (l2 + 0.05) | |
end | |
private | |
def lighten_method | |
respond_to?(:add_brightness) ? :add_brightness : :lighten | |
end | |
def darken_method | |
respond_to?(:detract_brightness) ? :detract_brightness : :darken | |
end | |
end | |
module Utils | |
def self.abs(array, other) | |
array.zip(other).map { |x, y| (x.to_f - y.to_f).abs } | |
end | |
def self.mul(array, other) | |
array.zip(other).map { |x, y| x.to_f * y.to_f } | |
end | |
def self.sum(array) | |
array.inject(0) { |sum, value| sum + value.to_f } | |
end | |
def self.sq(array) | |
array.map { |e| e ** 2 } | |
end | |
end | |
end | |
module Sass::Script | |
class Color | |
include ContrastColor::Color | |
end | |
module Functions | |
include ContrastColor::Functions | |
end | |
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
require 'minitest/autorun' | |
require './contrast-color' | |
describe ContrastColor do | |
it "calculates brightness" do | |
white = Sass::Script::Color.new [255, 255, 255] | |
white.brightness.must_be_within_delta 255.0 | |
grey = Sass::Script::Color.new [0xe5, 0xe5, 0xe5] | |
grey.brightness.must_be_within_delta 0.9*255, 1.0 | |
end | |
it 'should generate colors with enough contrast' do | |
data = File.read(__FILE__).split('__END__').last | |
css = Sass::Engine.new(data, :syntax => :scss).to_css | |
rules = css.gsub(/\s+/, ' ').gsub(/\} a/, '}\na').split('\n') | |
puts rules | |
rules.each { |rule| rule.must_match(/a \{ color: (#?\w+); background: \1; \}/) } | |
end | |
end | |
__END__ | |
a { | |
color: contrast_color(white, #ff0000); | |
background: #ee0000; | |
} | |
a { | |
color: contrast_color(rgb(127,127,127)); | |
background: white; | |
} | |
a { | |
color: contrast_color(rgb(128,128,128)); | |
background: black; | |
} | |
a { | |
color: contrast_color(white); | |
background: #585858; | |
} | |
a { | |
color: contrast_color(black); | |
background: #a7a7a7; | |
} | |
a { | |
color: contrast_color(red); | |
background: white; | |
} | |
a { | |
color: contrast_color(green); | |
background: #c3ffc3; | |
} | |
a { | |
color: contrast_color(blue); | |
background: #fafaff; | |
} | |
a { | |
color: contrast_color(#123456); | |
background: #c3dbf2; | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
This is now part of the sass-extras gem https://github.com/tobiashm/sass-extras