color-2.1.1/0000755000004100000410000000000015050641176012666 5ustar www-datawww-datacolor-2.1.1/Manifest.txt0000644000004100000410000000074415050641176015202 0ustar www-datawww-dataCHANGELOG.md CODE_OF_CONDUCT.md CONTRIBUTING.md CONTRIBUTORS.md LICENCE.md Manifest.txt README.md Rakefile SECURITY.md lib/color.rb lib/color/cielab.rb lib/color/cmyk.rb lib/color/grayscale.rb lib/color/hsl.rb lib/color/rgb.rb lib/color/rgb/colors.rb lib/color/version.rb lib/color/xyz.rb lib/color/yiq.rb licences/dco.txt test/fixtures/cielab.json test/minitest_helper.rb test/test_cmyk.rb test/test_color.rb test/test_grayscale.rb test/test_hsl.rb test/test_rgb.rb test/test_yiq.rb color-2.1.1/color.gemspec0000644000004100000410000001274715050641176015364 0ustar www-datawww-data######################################################### # This file has been automatically generated by gem2tgz # ######################################################### # -*- encoding: utf-8 -*- # stub: color 2.1.1 ruby lib Gem::Specification.new do |s| s.name = "color".freeze s.version = "2.1.1" s.required_rubygems_version = Gem::Requirement.new(">= 0".freeze) if s.respond_to? :required_rubygems_version= s.metadata = { "bug_tracker_uri" => "https://github.com/halostatue/color/issues", "changelog_uri" => "https://github.com/halostatue/color/blob/main/CHANGELOG.md", "rubygems_mfa_required" => "true", "source_code_uri" => "https://github.com/halostatue/color" } if s.respond_to? :metadata= s.require_paths = ["lib".freeze] s.authors = ["Austin Ziegler".freeze, "Matt Lyon".freeze] s.date = "1980-01-02" s.description = "Color is a Ruby library to provide RGB, CMYK, HSL, and other color space\nmanipulation support to applications that require it. It provides optional named\nRGB colors that are commonly supported in HTML, SVG, and X11 applications.\n\nThe Color library performs purely mathematical manipulation of the colors based\non color theory without reference to device color profiles (such as sRGB or\nAdobe RGB). For most purposes, when working with RGB and HSL color spaces, this\nwon't matter. Absolute color spaces (like CIE LAB and CIE XYZ) cannot be\nreliably converted to relative color spaces (like RGB) without color profiles.\nWhen necessary for conversions, Color provides D65 and D50 reference white\nvalues in Color::XYZ.\n\nColor 2.1 fixes a Color::XYZ bug where the values were improperly clamped and\nadds more Color::XYZ white points for standard illuminants. It builds on the\nColor 2.0 major release, dropping support for all versions of Ruby prior to 3.2\nas well as removing or renaming a number of features. The main breaking changes\nare:\n\n- Color classes are immutable Data objects; they are no longer mutable.\n- RGB named colors are no longer loaded on gem startup, but must be required\n explicitly (this is _not_ done via `autoload` because there are more than 100\n named colors with spelling variations) with `require \"color/rgb/colors\"`.\n- Color palettes have been removed.\n- `Color::CSS` and `Color::CSS#[]` have been removed.".freeze s.email = ["halostatue@gmail.com".freeze, "matt@postsomnia.com".freeze] s.extra_rdoc_files = ["CHANGELOG.md".freeze, "CODE_OF_CONDUCT.md".freeze, "CONTRIBUTING.md".freeze, "CONTRIBUTORS.md".freeze, "LICENCE.md".freeze, "Manifest.txt".freeze, "README.md".freeze, "SECURITY.md".freeze, "licences/dco.txt".freeze] s.files = ["CHANGELOG.md".freeze, "CODE_OF_CONDUCT.md".freeze, "CONTRIBUTING.md".freeze, "CONTRIBUTORS.md".freeze, "LICENCE.md".freeze, "Manifest.txt".freeze, "README.md".freeze, "Rakefile".freeze, "SECURITY.md".freeze, "lib/color.rb".freeze, "lib/color/cielab.rb".freeze, "lib/color/cmyk.rb".freeze, "lib/color/grayscale.rb".freeze, "lib/color/hsl.rb".freeze, "lib/color/rgb.rb".freeze, "lib/color/rgb/colors.rb".freeze, "lib/color/version.rb".freeze, "lib/color/xyz.rb".freeze, "lib/color/yiq.rb".freeze, "licences/dco.txt".freeze, "test/fixtures/cielab.json".freeze, "test/minitest_helper.rb".freeze, "test/test_cmyk.rb".freeze, "test/test_color.rb".freeze, "test/test_grayscale.rb".freeze, "test/test_hsl.rb".freeze, "test/test_rgb.rb".freeze, "test/test_yiq.rb".freeze] s.homepage = "https://github.com/halostatue/color".freeze s.licenses = ["MIT".freeze] s.rdoc_options = ["--main".freeze, "README.md".freeze] s.required_ruby_version = Gem::Requirement.new(">= 3.2".freeze) s.rubygems_version = "3.3.15".freeze s.summary = "Color is a Ruby library to provide RGB, CMYK, HSL, and other color space manipulation support to applications that require it".freeze if s.respond_to? :specification_version then s.specification_version = 4 end if s.respond_to? :add_runtime_dependency then s.add_development_dependency(%q.freeze, ["~> 4.0"]) s.add_development_dependency(%q.freeze, ["~> 1.6"]) s.add_development_dependency(%q.freeze, ["~> 2.1", ">= 2.1.1"]) s.add_development_dependency(%q.freeze, [">= 0.0"]) s.add_development_dependency(%q.freeze, ["~> 5.8"]) s.add_development_dependency(%q.freeze, ["~> 1.0"]) s.add_development_dependency(%q.freeze, ["~> 1.1"]) s.add_development_dependency(%q.freeze, ["~> 0.0"]) s.add_development_dependency(%q.freeze, [">= 10.0", "< 14"]) s.add_development_dependency(%q.freeze, [">= 0.0", "< 7"]) s.add_development_dependency(%q.freeze, ["~> 0.22"]) s.add_development_dependency(%q.freeze, ["~> 0.8"]) s.add_development_dependency(%q.freeze, ["~> 1.0"]) else s.add_dependency(%q.freeze, ["~> 4.0"]) s.add_dependency(%q.freeze, ["~> 1.6"]) s.add_dependency(%q.freeze, ["~> 2.1", ">= 2.1.1"]) s.add_dependency(%q.freeze, [">= 0.0"]) s.add_dependency(%q.freeze, ["~> 5.8"]) s.add_dependency(%q.freeze, ["~> 1.0"]) s.add_dependency(%q.freeze, ["~> 1.1"]) s.add_dependency(%q.freeze, ["~> 0.0"]) s.add_dependency(%q.freeze, [">= 10.0", "< 14"]) s.add_dependency(%q.freeze, [">= 0.0", "< 7"]) s.add_dependency(%q.freeze, ["~> 0.22"]) s.add_dependency(%q.freeze, ["~> 0.8"]) s.add_dependency(%q.freeze, ["~> 1.0"]) end end color-2.1.1/CONTRIBUTING.md0000644000004100000410000000664415050641176015131 0ustar www-datawww-data# Contributing Contribution to color is encouraged: bug reports, feature requests, or code contributions. There are a few DOs and DON'Ts that should be followed. ## DO - Keep the coding style that already exists for any updated Ruby code (support or otherwise). I use [Standard Ruby][standardrb] for linting and formatting. - Use thoughtfully-named topic branches for contributions. Rebase your commits into logical chunks as necessary. - Use [quality commit messages][qcm]. - Add your name or GitHub handle to `CONTRIBUTORS.md` and a record in the `CHANGELOG.md` as a separate commit from your main change. (Follow the style in the `CHANGELOG.md` and provide a link to your PR.) - Add or update tests as appropriate for your change. The test suite is written with [minitest][minitest]. - Add or update documentation as appropriate for your change. The documentation is RDoc; color does not use extensions that may be present in alternative documentation generators. ## DO NOT - Modify `VERSION` in `lib/color/version.rb`. When your patch is accepted and a release is made, the version will be updated at that point. - Modify `color.gemspec`; it is a generated file. (You _may_ use `rake gemspec` to regenerate it if your change involves metadata related to gem itself). - Modify the `Gemfile`. ## LLM-Generated Contribution Policy Color is a library full of complex math and subtle decisions (some of them possibly even wrong). It is extremely important that any issues or pull requests be well understood by the submitter and that, especially for pull requests, the developer can attest to the [Developer Certificate of Origin][dco] for each pull request (see [LICENCE](LICENCE.md)). If LLM assistance is used in writing pull requests, this must be documented in the commit message and pull request. If there is evidence of LLM assistance without such declaration, the pull request **will be declined**. Any contribution (bug, feature request, or pull request) that uses unreviewed LLM output will be rejected. ## Test Dependencies color uses Ryan Davis's [Hoe][Hoe] to manage the release process, and it adds a number of rake tasks. You will mostly be interested in `rake`, which runs the tests the same way that `rake test` will do. To assist with the installation of the development dependencies for mime-types, I have provided the simplest possible Gemfile pointing to the (generated) `color.gemspec` file. This will permit you to do `bundle install` to get the development dependencies. You can run tests with code coverage analysis by running `rake coverage`. ## Workflow Here's the most direct way to get your work merged into the project: - Fork the project. - Clone down your fork (`git clone git://github.com//color.git`). - Create a topic branch to contain your change (`git checkout -b my_awesome_feature`). - Hack away, add tests. Not necessarily in that order. - Make sure everything still passes by running `rake`. - If necessary, rebase your commits into logical chunks, without errors. - Push the branch up (`git push origin my_awesome_feature`). - Create a pull request against halostatue/color and describe what your change does and the why you think it should be merged. [dco]: licences/dco.txt [hoe]: https://github.com/seattlerb/hoe [minitest]: https://github.com/seattlerb/minitest [qcm]: http://tbaggery.com/2008/04/19/a-note-about-git-commit-messages.html [standardrb]: https://github.com/standardrb/standard color-2.1.1/SECURITY.md0000644000004100000410000000271315050641176014462 0ustar www-datawww-data# color Security ## LLM-Generated Security Report Policy Absolutely no security reports will be accepted that have been generated by LLM agents. ## Supported Versions Security reports are accepted for the most recent major release and the previous version for a limited time after the initial major release version. After a major release, the previous version will receive full support for three months and security support for an additional three months (for a total of six months). Because color 1.x supports a wide range of Ruby versions that are themselves end of life, security reports will only be accepted when they can be demonstrated on Ruby 3.2 or higher. > | Version | Release Date | Support Ends | Security Support Ends | > | ------- | ------------ | -------------- | --------------------- | > | 1.x | 2015-10-26 | 2.x + 3 months | 2.x + 6 months | > | 2.x | 2025-MM-DD | - | - | ## Reporting a Vulnerability By preference, use the [Tidelift security contact][tidelift]. Tidelift will coordinate the fix and disclosure. Alternatively, Send an email to [color@halostatue.ca][email] with the text `Color` in the subject. Emails sent to this address should be encrypted using [age][age] with the following public key: ``` age1fc6ngxmn02m62fej5cl30lrvwmxn4k3q2atqu53aatekmnqfwumqj4g93w ``` [tidelift]: https://tidelift.com/security [email]: mailto:color@halostatue.ca [age]: https://github.com/FiloSottile/age color-2.1.1/lib/0000755000004100000410000000000015050641176013434 5ustar www-datawww-datacolor-2.1.1/lib/color.rb0000644000004100000410000001773415050641176015113 0ustar www-datawww-data# frozen_string_literal: true # # \Color -- \Color Math in Ruby # # - **code**: [github.com/halostatue/color](https://github.com/halostatue/color) # - **issues**: [github.com/halostatue/color/issues](https://github.com/halostatue/color/issues) # - **changelog**: [CHANGELOG](rdoc-ref:CHANGELOG.md) # # \Color is a Ruby library to provide RGB, CMYK, HSL, and other color space manipulation # support to applications that require it. It provides optional named RGB colors that are # commonly supported in HTML, SVG, and X11 applications. # # The \Color library performs purely mathematical manipulation of the colors based on # color theory without reference to device color profiles (such as sRGB or Adobe RGB). For # most purposes, when working with RGB and HSL color spaces, this won't matter. Absolute # color spaces (like CIE LAB and CIE XYZ) cannot be reliably converted to relative color # spaces (like RGB) without color profiles. When necessary for conversions, \Color # provides \D65 and \D50 reference white values in Color::XYZ. # # Color 2.1 fixes a Color::XYZ bug where the values were improperly clamped and adds more # Color::XYZ white points for standard illuminants. It builds on the Color 2.0 major # release, dropping support for all versions of Ruby prior to 3.2 as well as removing or # renaming a number of features. The main breaking changes are: # # - Color classes are immutable Data objects; they are no longer mutable. # - RGB named colors are no longer loaded on gem startup, but must be required explicitly # (this is _not_ done via `autoload` because there are more than 100 named colors with # spelling variations) with `require "color/rgb/colors"`. # - Color palettes have been removed. # - `Color::CSS` and `Color::CSS#[]` have been removed. module Color ## # The maximum "resolution" for color math; if any value is less than or equal to this # value, it is treated as zero. EPSILON = 1e-5 ## # The tolerance for comparing the components of two colors. In general, colors are # considered equal if all of their components are within this tolerance value of each # other. TOLERANCE = 1e-4 # :stopdoc: CIELAB = Data.define(:l, :a, :b) CMYK = Data.define(:c, :m, :y, :k) Grayscale = Data.define(:g) HSL = Data.define(:h, :s, :l) RGB = Data.define(:r, :g, :b, :names) XYZ = Data.define(:x, :y, :z) YIQ = Data.define(:y, :i, :q) # :startdoc: ## # It is useful to know the number of components in some cases. Since most colors are # defined with three components, we define a constant value here. Color classes that # require more or less should override this. # # We _could_ define this as `members.count`, but this would require a special case # for Color::RGB _regardless_ because there's an additional member for RGB colors # (names). def components = 3 # :nodoc: ## # Compares the `other` color to this one. The `other` color will be coerced to the same # type as the current color. Such converted color comparisons will always be more # approximate than non-converted comparisons. # # All values are compared as floating-point values, so two colors will be reported # equivalent if all component values are within +TOLERANCE+ of each other. def ==(other) other.is_a?(Color) && to_internal.zip(coerce(other).to_internal).all? { near?(_1, _2) } end ## # Apply the provided block to each color component in turn, returning a new color # instance. def map(&block) = self.class.from_internal(*to_internal.map(&block)) ## # Apply the provided block to the color component pairs in turn, returning a new color # instance. def map_with(other, &block) = self.class.from_internal(*zip(other).map(&block)) ## # Zip the color component pairs together. def zip(other) = to_internal.zip(coerce(other).to_internal) ## # Multiplies each component value by the scaling factor or factors, returning a new # color object with the scaled values. # # If a single scaling factor is provided, it is applied to all components: # # ```ruby # rgb = Color::RGB::Wheat # => RGB [#f5deb3] # rgb.scale(0.75) # => RGB [#b8a786] # ``` # # If more than one scaling factor is provided, there must be exactly one factor for each # color component of the color object or an `ArgumentError` will be raised. # # ```ruby # rgb = Color::RGB::Wheat # => RGB [#f5deb3] # # 0xf5 * 0 == 0x00, 0xde * 0.5 == 0x6f, 0xb3 * 2 == 0x166 (clamped to 0xff) # rgb.scale(0, 0.5, 2) # => RGB [#006fff] # # rgb.scale(1, 2) # => Invalid scaling factors [1, 2] for Color::RGB (ArgumentError) # ``` def scale(*factors) if factors.size == 1 factor = factors.first map { _1 * factor } elsif factors.size != components raise ArgumentError, "Invalid scaling factors #{factors.inspect} for #{self.class}" else new_components = to_internal.zip(factors).map { _1 * _2 } self.class.from_internal(*new_components) end end ## def css_value(value, format = nil) # :nodoc: if value.nil? "none" elsif near_zero?(value) "0" else suffix = case format in :percent "%" in :degrees "deg" else "" end "%3.2f%s" % [value, suffix] end end private ## def from_internal(...) = self.class.from_internal(...) ## # Returns `true` if the value is less than EPSILON. def near_zero?(value) = (value.abs <= Color::EPSILON) # :nodoc: ## # Returns `true` if the value is within EPSILON of zero or less than zero. def near_zero_or_less?(value) = (value < 0.0 or near_zero?(value)) # :nodoc: ## # Returns +true+ if the value is within EPSILON of one. def near_one?(value) = near_zero?(value - 1.0) # :nodoc: ## # Returns +true+ if the value is within EPSILON of one or more than one. def near_one_or_more?(value) = (value > 1.0 or near_one?(value)) # :nodoc: ## # Returns +true+ if the two values provided are near each other. def near?(x, y) = (x - y).abs <= Color::TOLERANCE # :nodoc: ## def to_degrees(radians) # :nodoc: if radians < 0 (Math::PI + radians % -Math::PI) * (180 / Math::PI) + 180 else (radians % Math::PI) * (180 / Math::PI) end end ## def to_radians(degrees) # :nodoc: degrees = ((degrees % 360) + 360) % 360 if degrees >= 180 Math::PI * (degrees - 360) / 180.0 else Math::PI * degrees / 180.0 end end ## # Normalizes the value to the range (0.0) .. (1.0). module_function def normalize(value, range = 0.0..1.0) # :nodoc: value = value.clamp(range) if near?(value, range.begin) range.begin elsif near?(value, range.end) range.end else value end end ## # Translates a value from range `from` to range `to`. Both ranges must be closed. # As 0.0 .. 1.0 is a common internal range, it is the default for `from`. # # This is based on the formula: # # [a, b] ← from ← [from.begin, from.end] # [c, d] ← to ← [to.begin, to.end] # # y = (((x - a) * (d - c)) / (b - a)) + c # # The value is clamped to the values of `to`. module_function def translate_range(x, to:, from: 0.0..1.0) # :nodoc: a, b = [from.begin, from.end] c, d = [to.begin, to.end] y = (((x - a) * (d - c)) / (b - a)) + c y.clamp(to) end ## # Normalizes the value to the specified range. def normalize_to_range(value, range) # :nodoc: range = (range.end..range.begin) if range.end < range.begin if value <= range.begin range.begin elsif value >= range.end range.end else value end end ## # Normalize the value to the range (0) .. (255). def normalize_byte(value) = normalize_to_range(value, 0..255).to_i # :nodoc: ## # Normalize the value to the range (0) .. (65535). def normalize_word(value) = normalize_to_range(value, 0..65535).to_i # :nodoc: end require "color/cmyk" require "color/grayscale" require "color/hsl" require "color/cielab" require "color/rgb" require "color/xyz" require "color/yiq" require "color/version" color-2.1.1/lib/color/0000755000004100000410000000000015050641176014552 5ustar www-datawww-datacolor-2.1.1/lib/color/grayscale.rb0000644000004100000410000000764015050641176017060 0ustar www-datawww-data# frozen_string_literal: true ## # \Grayscale is a color object representing shades of gray as a ratio of black to white, # where 0% (0.0) gray is black and 100% (1.0) gray is white. # # \Grayscale colors are immutable Data class instances. Array deconstruction is `[gray]` # and hash deconstruction is `{g:, gray:}`. See #g, #gray. class Color::Grayscale include Color ## # :attr_reader: brightness # Returns the grayscale value as a proportion of white (0.0 .. 1.0). ## # :attr_reader: g # Returns the grayscale value as a proportion of white (0.0 .. 1.0). ## # :attr_reader: gray # Returns the grayscale value as a percentage of white (0.0 .. 100.0). ## # Creates a grayscale color object from a percentage value (0.0 .. 100.0). # # ```ruby # Color::Grayscale.from_percentage(50) # => Grayscale [0.50%] # Color::Grayscale.from_values(50) # => Grayscale [0.50%] # ``` # # :call-seq: # from_percentage(g) # from_percentage(g:) # from_values(g) # from_values(g:) def self.from_percentage(*args, **kwargs) g = case [args, kwargs] in [[g], {}] g in [[], {g:}] g else new(*args, **kwargs) end new(g: g / 100.0) end class << self alias_method :from_values, :from_percentage alias_method :from_fraction, :new alias_method :from_internal, :from_fraction # :nodoc: end ## # Creates a grayscale color object from a fractional value (0.0 .. 1.0). # # ```ruby # Color::Grayscale.from_fraction(0.5) # Color::Grayscale.new(0.5) # Color::Grayscale[g: 0.5] # ``` def initialize(g:) super(g: normalize(g)) end ## # Coerces the other Color object to grayscale. def coerce(other) = other.to_grayscale ## # Convert \Grayscale to Color::CMYK. def to_cmyk(...) = Color::CMYK.from_fraction(0, 0, 0, 1.0 - g.to_f) ## # Convert \Grayscale to Color::RGB. def to_rgb(...) = Color::RGB.from_fraction(g, g, g) ## def to_grayscale(...) = self ## # Convert \Grayscale to Color::YIQ. # # This approximates the actual value, as I and Q are calculated by treating the # grayscale value as a RGB value. The Y (intensity or brightness) value is the same as # the grayscale value. def to_yiq(...) y = g i = (g * 0.596) + (g * -0.275) + (g * -0.321) q = (g * 0.212) + (g * -0.523) + (g * 0.311) Color::YIQ.from_fraction(y, i, q) end ## # Converts \Grayscale to Color::HSL. def to_hsl(...) = Color::HSL.from_fraction(0, 0, g) ## # Converts \Grayscale to Color::CIELAB via Color::RGB. def to_lab(...) = to_rgb(...).to_lab(...) ## # Present the color as an HTML/CSS color string (e.g., `#dddddd`). def html "##{("%02x" % translate_range(g, to: 0.0..255.0)) * 3}" end ## # Present the color as a CSS `rgb` color with optional `alpha`. # # ```ruby # Color::Grayscale[0.5].css # => rgb(50.00% 50.00% 50.00%) # Color::Grayscale[0.5].css(alpha: 0.75) # => rgb(50.00% 50.00% 50.00% / 0.75) # ``` def css(alpha: nil) params = ([css_value(gray, :percent)] * 3).join(" ") params = "#{params} / #{css_value(alpha)}" if alpha "rgb(#{params})" end ## # Lightens the grayscale color by the stated percent. def lighten_by(percent) = Color::Grayscale.from_fraction([g + (g * (percent / 100.0)), 1.0].min) ## # Darken the grayscale color by the stated percent. def darken_by(percent) = Color::Grayscale.from_fraction([g - (g * (percent / 100.0)), 0.0].max) ## alias_method :brightness, :g ## def gray = g * 100.0 ## def inspect = "Grayscale [%.2f%%]" % [gray] # :nodoc: ## def pretty_print(q) # :nodoc: q.text "Grayscale" q.breakable q.group 2, "[", "]" do q.text "%.2f%%" % gray end end ## def to_a = [gray] # :nodoc: ## alias_method :deconstruct, :to_a ## def deconstruct_keys(_keys) = {g:, gray:} ## def to_internal = [g] # :nodoc: ## def components = 1 # :nodoc: end color-2.1.1/lib/color/yiq.rb0000644000004100000410000000570715050641176015712 0ustar www-datawww-data# frozen_string_literal: true # A \Color object representing YIQ (NTSC) color encoding, where Y is the luma # (brightness) value, and I (orange-blue) and Q (purple-green) are chrominance. # # All values are clamped between 0 and 1 inclusive. # # More more details, see [YIQ][wikiyiq]. # # [wikiyiq]: https://en.wikipedia.org/wiki/YIQ # # \YIQ colors are immutable Data class instances. Array deconstruction is `[y, i, q]` and # hash deconstruction is `{y:, i:, q:}` (see #y, #i, #q). # # \YIQ is only partially implemented: other \Color objects can only be converted _to_ # \YIQ, but it has few conversion functions for converting _from_ \YIQ. class Color::YIQ include Color ## # :attr_reader: y # The `y` (luma) attribute of this \YIQ color expressed as a value 0..1. ## # :attr_reader: i # The `i` (orange-blue chrominance) attribute of this \YIQ color expressed as a value # 0..1. ## # :attr_reader: q # The `q` (purple-green chrominance) attribute of this \YIQ color expressed as a value # 0..1. ## # Creates a YIQ color object from percentage values 0 .. 1. # # ```ruby # Color::YIQ.from_percentage(30, 20, 10) # => YIQ [30% 20% 10%] # Color::YIQ.from_percentage(y: 30, i: 20, q: 10) # => YIQ [30% 20% 10%] # Color::YIQ.from_values(30, 20, 10) # => YIQ [30% 20% 10%] # Color::YIQ.from_values(y: 30, i: 20, q: 10) # => YIQ [30% 20% 10%] # ``` def self.from_percentage(*args, **kwargs) y, i, q = case [args, kwargs] in [[_, _, _], {}] args in [[], {y:, i:, q:}] [y, i, q] else new(*args, **kwargs) end new(y: y / 100.0, i: i / 100.0, q: q / 100.0) end class << self alias_method :from_values, :from_percentage alias_method :from_fraction, :new # :nodoc: alias_method :from_internal, :new end ## # Creates a YIQ color object from fractional values 0 .. 1. # # ```ruby # Color::YIQ.from_fraction(0.3, 0.2, 0.1) # => YIQ [30% 20% 10%] # Color::YIQ.new(0.3, 0.2, 0.1) # => YIQ [30% 20% 10%] # Color::YIQ[y: 0.3, i: 0.2, q: 0.1] # => YIQ [30% 20% 10%] # ``` def initialize(y:, i:, q:) # :nodoc: super(y: normalize(y), i: normalize(i), q: normalize(q)) end ## # Coerces the other Color object into \YIQ. def coerce(other) = other.to_yiq ## def to_yiq = self ## # Convert \YIQ to Color::Grayscale using the luma (#y) value. def to_grayscale = Color::Grayscale.from_fraction(y) ## alias_method :brightness, :y def inspect = "YIQ [%.2f%% %.2f%% %.2f%%]" % [y * 100, i * 100, q * 100] # :nodoc: def pretty_print(q) # :nodoc: q.text "YIQ" q.breakable q.group 2, "[", "]" do q.text "%.2f%%" % y q.fill_breakable q.text "%.2f%%" % i q.fill_breakable q.text "%.2f%%" % q end end alias_method :to_a, :deconstruct # :nodoc: alias_method :to_internal, :deconstruct # :nodoc: def deconstruct_keys(_keys) = {y:, i:, q:} # :nodoc: end color-2.1.1/lib/color/xyz.rb0000644000004100000410000002243215050641176015734 0ustar www-datawww-data# frozen_string_literal: true ## # A \Color object for the CIE 1931 \XYZ color space derived from the original CIE \RGB # color space as linear transformation functions x̅(λ), y̅(λ), and z̅(λ) that describe the # device-independent \CIE standard observer. It underpins most other CIE color systems # (such as \CIELAB), but is directly used mostly for color instrument readings and color # space transformations particularly in color profiles. # # The \XYZ color space ranges describe the mixture of wavelengths of light required to # stimulate cone cells in the human eye, as well as the luminance (brightness) required. # The `Y` component describes the luminance while the `X` and `Z` components describe two # axes of chromaticity. Definitionally, the minimum value for any \XYZ color component is # 0. # # As \XYZ describes imaginary colors, the color gamut is usually expressed in relation to # a reference white of an illuminant (frequently often D65 or D50) and expressed as the # `xyY` color space, computed as: # # ``` # x = X / (X + Y + Z) # y = Y / (X + Y + Z) # Y = Y # ``` # # The range of `Y` values is conventionally clamped to 0..100, whereas the `X` and `Z` # values must be no lower than 0 and on the same scale. # # For more details, see [CIE XYZ color space][ciexyz]. # # [ciexyz]: https://en.wikipedia.org/wiki/CIE_1931_color_space#CIE_XYZ_color_space # # \XYZ colors are immutable Data class instances. Array deconstruction is `[x * 100, # y * 100, z * 100]` and hash deconstruction is `{x:, y:, z:}` (see #x, #y, #z). class Color::XYZ include Color ## # :attr_reader: x # The X attribute of this \XYZ color object expressed as a value scaled to #y. ## # :attr_reader: y # The Y attribute of this \XYZ color object expressed as a value 0..1. ## # :attr_reader: z # The Z attribute of this \XYZ color object expressed as a value scaled to #y. ## # Creates a \XYZ color representation from native values. `y` must be between 0 and 100 # and `x` and `z` values must be scaled to `y`. # # ```ruby # Color::XYZ.from_values(95.047, 100.00, 108.883) # Color::XYZ.from_values(x: 95.047, y: 100.00, z: 108.883) # ``` # # call-seq: # Color::XYZ.from_values(x, y, z) # Color::XYZ.from_values(x:, y:, z:) def self.from_values(*args, **kwargs) x, y, z = case [args, kwargs] in [[_, _, _], {}] args in [[], {x:, y:, z:}] [x, y, z] else new(*args, **kwargs) end new(x: x / 100.0, y: y / 100.0, z: z / 100.0) end class << self alias_method :from_fraction, :new alias_method :from_internal, :new # :nodoc: end ## # Creates a \XYZ color representation from native values. The `y` value must be between # 0 and 1 and `x` and `z` must be fractional valiues greater than or equal to 0. # # ```ruby # Color::XYZ.from_fraction(0.95047, 1.0, 1.0883) # Color::XYZ.new(0.95047, 1.0, 1.08883) # Color::XYZ[x: 0.95047, y: 1.0, z: 1.08883] # ``` def initialize(x:, y:, z:) # The X and Z values in the XYZ color model are technically unbounded. With Y scaled # to 1.0, we will clamp X to 0.0..2.2 and Z to 0.0..2.8. super( x: normalize(x, 0.0..2.2), y: normalize(y), z: normalize(z, 0.0..2.8) ) end # :stopdoc: # NOTE: This should be using Rational instead of floating point values, # otherwise there will be discontinuities. # http://www.brucelindbloom.com/LContinuity.html # :startdoc: E = 216r/24389r # :nodoc: K = 24389r/27r # :nodoc: EK = E * K # :nodoc: ## # White points for standard illuminants at 2° (CIE 1931). WP2 = { A: new(1.09849161234507, 1.0, 0.355798257454902), B: new(0.9909274480248, 1.0, 0.853132732288615), C: new(0.980705971659919, 1.0, 1.18224949392713), D50: new(0.964211994421199, 1.0, 0.825188284518828), D55: new(0.956797052643698, 1.0, 0.921480586017327), D65: new(0.950430051970945, 1.0, 1.08880649180926), D75: new(0.949722089884072, 1.0, 1.22639352072415), D93: new(0.953014035205816, 1.0, 1.41274275520851), E: new(1, 1.0, 1.0000300003), F1: new(0.92833634773327, 1.0, 1.03664719660806), F2: new(0.991446614618029, 1.0, 0.673159423379253), F3: new(1.03753487192493, 1.0, 0.49860512300279), F4: new(1.0914726375561, 1.0, 0.388132609288601), F5: new(0.908719701138108, 1.0, 0.987228866815325), F6: new(0.973091283635895, 1.0, 0.601905497618128), F7: new(0.950171560440895, 1.0, 1.08629642000425), F8: new(0.96412543554007, 1.0, 0.823331010452962), F9: new(1.00364797081623, 1.0, 0.678683511708377), F10: new(0.961735119213027, 1.0, 0.817123325737787), F11: new(1.00898894280487, 1.0, 0.642616604353936), F12: new(1.08046289656537, 1.0, 0.392275166291635), "FL3.0": new(1.09273493677163, 1.0, 0.3868088271758), "FL3.1": new(1.01981788966256, 1.0, 0.658275307980718), "FL3.2": new(0.916836289619075, 1.0, 0.990985751671998), "FL3.3": new(1.09547365817462, 1.0, 0.377937175364828), "FL3.4": new(1.02096949891068, 1.0, 0.702342047930283), "FL3.5": new(0.968888888888889, 1.0, 0.808888888888889), "FL3.6": new(1.08380716934487, 1.0, 0.388380716934487), "FL3.7": new(0.996868475991649, 1.0, 0.612734864300626), "FL3.8": new(0.974380395433027, 1.0, 0.810359231411863), "FL3.9": new(0.970505617977528, 1.0, 0.838483146067416), "FL3.10": new(0.944962143273151, 1.0, 0.967093768200349), "FL3.11": new(1.08422095615556, 1.0, 0.392865989596235), "FL3.12": new(1.02846401718582, 1.0, 0.656820622986037), "FL3.13": new(0.955112219451372, 1.0, 0.815738431698531), "FL3.14": new(0.951034063260341, 1.0, 1.09032846715328), HP1: new(1.28433734939759, 1.0, 0.125301204819277), HP2: new(1.14911014911015, 1.0, 0.255892255892256), HP3: new(1.05570552147239, 1.0, 0.398282208588957), HP4: new(1.00395048722676, 1.0, 0.62970766394522), HP5: new(1.01696741179639, 1.0, 0.676272555884729), "LED-B1": new(1.11819519372241, 1.0, 0.3339872486513), "LED-B2": new(1.08599202392822, 1.0, 0.406530408773679), "LED-B3": new(1.0088638195004, 1.0, 0.677142089712597), "LED-B4": new(0.977155910908053, 1.0, 0.87835522558538), "LED-B5": new(0.963535228677379, 1.0, 1.12669962917182), "LED-BH1": new(1.10034431874078, 1.0, 0.359075258239056), "LED-RGB1": new(1.08216575635241, 1.0, 0.292567086202802), "LED-V1": new(1.12462908011869, 1.0, 0.348170128585559), "LED-V2": new(1.00158940397351, 1.0, 0.647417218543046), ID50: new(0.952803997779012, 1.0, 0.823431426985008), ID65: new(0.939522225582099, 1.0, 1.08436649531297) }.freeze ## # The D50 standard illuminant white point at 2° (CIE 1931). D50 = WP2[:D50] ## # The D65 standard illuminant white point at 2° (CIE 1931). D65 = WP2[:D65] ## # Coerces the other Color object into \XYZ. def coerce(other) = other.to_xyz ## def to_xyz(...) = self ## # Converts \XYZ to Color::CMYK via Color::RGB. # # See #to_rgb and Color::RGB#to_cmyk. def to_cmyk(...) = to_rgb(...).to_cmyk(...) ## # Converts \XYZ to Color::Grayscale using the #y value def to_grayscale(...) = Color::Grayscale.from_fraction(y) ## # Converts \XYZ to Color::HSL via Color::RGB. # # See #to_rgb and Color::RGB#to_hsl. def to_hsl(...) = to_rgb(...).to_hsl(...) ## # Converts \XYZ to Color::YIQ via Color::RGB. # # See #to_rgb and Color::RGB#to_yiq. def to_yiq(...) = to_rgb(...).to_yiq(...) ## # Converts \XYZ to Color::CIELAB. # # :call-seq: # to_lab(white: Color::XYZ::D65) def to_lab(*args, **kwargs) ref = kwargs[:white] || args.first || Color::XYZ::D65 # Calculate the ratio of the XYZ values to the reference white. # http://www.brucelindbloom.com/index.html?Equations.html rel = scale(1.0 / ref.x, 1.0 / ref.y, 1.0 / ref.z) # And now transform # http://en.wikipedia.org/wiki/Lab_color_space#Forward_transformation # There is a brief explanation there as far as the nature of the calculations, # as well as a much nicer looking modeling of the algebra. f = rel.map { |t| if t > E t**(1.0 / 3) else # t <= E ((K * t) + 16) / 116.0 # The 4/29 here is for when t = 0 (black). 4/29 * 116 = 16, and 16 - # 16 = 0, which is the correct value for L* with black. # ((1.0/3)*((29.0/6)**2) * t) + (4.0/29) end } Color::CIELAB.from_values( (116 * f.y) - 16, 500 * (f.x - f.y), 200 * (f.y - f.z) ) end ## # Converts \XYZ to Color::RGB. # # This always assumes an sRGB target color space and a D65 white point. def to_rgb(...) # sRGB companding from linear values linear = [ x * 3.2406255 + y * -1.5372080 + z * -0.4986286, x * -0.9689307 + y * 1.8757561 + z * 0.0415175, x * 0.0557101 + y * -0.2040211 + z * 1.0569959 ].map { if _1.abs <= 0.0031308 _1 * 12.92 else 1.055 * (_1**(1 / 2.4)) - 0.055 end } Color::RGB.from_fraction(*linear) end def deconstruct = [x * 100.0, y * 100.0, z * 100.0] # :nodoc: alias_method :to_a, :deconstruct # :nodoc: def to_internal = [x, y, z] # :nodoc: def inspect = "XYZ [#{x} #{y} #{z}]" # :nodoc: def pretty_print(q) # :nodoc: q.text "XYZ" q.breakable q.group 2, "[", "]" do q.text "%.4f" % x q.fill_breakable q.text "%.4f" % y q.fill_breakable q.text "%.4f" % z end end end color-2.1.1/lib/color/cielab.rb0000644000004100000410000002625015050641176016323 0ustar www-datawww-data# frozen_string_literal: true ## # A \Color object for the \CIELAB color space (also known as L\*a\*\b*). Color is # expressed in a three-dimensional, device-independent "standard observer" model, often # in relation to a "reference white" color, usually Color::XYZ::D65 (most purposes) or # Color::XYZ::D50 (printing). # # `L*` is the perceptual lightness, bounded to values between 0 (black) and 100 (white). # `a*` is the range of green (negative) / red (positive) and `b*` is the range of blue # (negative) / yellow (positive). # # The `a*` and `b*` ranges are _technically_ unbounded but \Color clamps them to the # values `-128..127`. # # For more information, see [CIELAB](https://en.wikipedia.org/wiki/CIELAB_color_space). # # \CIELAB colors are immutable Data class instances. Array deconstruction is `[l, a, b]` # and hash deconstruction is `{l:, a:, b:}` (see #l, #a, #b). class Color::CIELAB include Color ## # Standard weights applied for perceptual differences using the ΔE*94 algorithm. DE94_WEIGHTS = { graphic_arts: {k_1: 0.045, k_2: 0.015, k_l: 1.0}.freeze, textiles: {k_1: 0.048, k_2: 0.014, k_l: 2.0}.freeze }.freeze RANGES = {L: 0.0..100.0, ab: -128.0..127.0}.freeze # :nodoc: private_constant :RANGES ## # :attr_reader: l # The `L*` attribute of this \CIELAB color object expressed as a value 0..100. ## # :attr_reader: a # The `a*` attribute of this \CIELAB color object expressed as a value -128..127. ## # :attr_reader: b # The `b*` attribute of this \CIELAB color object expressed as a value -128..127. ## # Creates a \CIELAB color representation from percentage values. # # `l` must be between 0% and 100%; `a` and `b` must be between -100% and 100% and will # be transposed to the native value -128..127. # # ```ruby # Color::CIELAB.from_percentage(10, -30, 30) # => CIELAB [10.0000 -38.7500 37.7500] # ``` # # :call-seq: # from_percentage(l, a, b) # from_percentage(l:, a:, b:) def self.from_percentage(*args, **kwargs) l, a, b = case [args, kwargs] in [[_, _, _], {}] args in [[], {l:, a:, b:}] [l, a, b] else new(*args, **kwargs) end new( l: l, a: Color.translate_range(a, from: -100.0..100.0, to: RANGES[:ab]), b: Color.translate_range(b, from: -100.0..100.0, to: RANGES[:ab]) ) end class << self alias_method :from_values, :new alias_method :from_internal, :new # :nodoc: end ## # Creates a \CIELAB color representation from `L*a*b*` native values. The `l` value # must be between 0 and 100 and the `a` and `b` values must be between -128 and 127. # # ```ruby # Color::CIELAB.new(10, 35, -35) # => CIELAB [10.00 35.00 -35.00] # Color::CIELAB.from_values(10, 35, -35) # => CIELAB [10.00 35.00 -35.00] # Color::CIELAB[l: 10, a: 35, b: -35] # => CIELAB [10.00 35.00 -35.00] # ``` def initialize(l:, a:, b:) super( l: normalize(l, RANGES[:L]), a: normalize(a, RANGES[:ab]), b: normalize(b, RANGES[:ab]) ) end ## # Coerces the other Color object into \CIELAB. def coerce(other) = other.to_lab ## # Converts \CIELAB to Color::CMYK via Color::RGB. # # See #to_rgb and Color::RGB#to_cmyk. def to_cmyk(...) = to_rgb(...).to_cmyk(...) ## # Converts \CIELAB to Color::Grayscale via Color::RGB. # # See #to_rgb and Color::RGB#to_grayscale. def to_grayscale(...) = to_rgb(...).to_grayscale(...) ## def to_lab(...) = self ## # Converts \CIELAB to Color::HSL via Color::RGB. # # See #to_rgb and Color::RGB#to_hsl. def to_hsl(...) = to_rgb(...).to_hsl(...) ## # Converts \CIELAB to Color::RGB via Color::XYZ. # # See #to_xyz and Color::XYZ#to_rgb. def to_rgb(...) = to_xyz(...).to_rgb(...) ## # Converts \CIELAB to Color::XYZ based on a reference white. # # Accepts a single keyword parameter, `white`, indicating the reference white used for # conversion scaling. If none is provided, Color::XYZ::D65 is used. # # :call-seq: # to_xyz(white: Color::XYZ::D65) def to_xyz(*args, **kwargs) fy = (l + 16.0) / 116 fz = fy - b / 200.0 fx = a / 500.0 + fy xr = ((fx3 = fx**3) > Color::XYZ::E) ? fx3 : (116.0 * fx - 16) / Color::XYZ::K yr = (l > Color::XYZ::EK) ? ((l + 16.0) / 116)**3 : l zr = ((fz3 = fz**3) > Color::XYZ::E) ? fz3 : (116.0 * fz - 16) / Color::XYZ::K ref = kwargs[:white] || args.first ref = Color::XYZ::D65 unless ref.is_a?(Color::XYZ) ref.scale(xr, yr, zr) end ## # Render the CSS `lab()` function for this \CIELAB object, adding an `alpha` if # provided. def css(alpha: nil, **) params = [css_value(l, :percent), css_value(a), css_value(b)].join(" ") params = "#{params} / #{css_value(alpha)}" if alpha "lab(#{params})" end ## # Implements the \CIELAB ΔE* 2000 perceptual color distance metric with more reliable # results over \CIELAB ΔE* 1994. # # See [CIEDE2000][ciede2000] for precise details on the mathematical formulas. The # implementation here is based on Sharma, Wu, and Dala in [CIEDE2000.xls][ciede2000xls], # published as supplementary materials for their paper "The CIEDE2000 Color-Difference # Formula: Implementation Notes, Supplementary Test Data, and Mathematical # Observations,", G. Sharma, W. Wu, E. N. Dalal, Color Research and Application, vol. # 30. No. 1, pp. 21-30, February 2005. # # Do not override the `klch` parameter unless you _really_ know what you're doing. # # See also # # [ciede2000]: https://en.wikipedia.org/wiki/Color_difference#CIEDE2000 # [ciede2000xls]: http://www.ece.rochester.edu/~gsharma/ciede2000/dataNprograms/CIEDE2000.xls def delta_e2000(other, klch: {L: 1.0, C: 1.0, H: 1.0}) other = coerce(other) klch => L: k_l, C: k_c, H: k_h self => l: l_star_1, a: a_star_1, b: b_star_1 other => l: l_star_2, a: a_star_2, b: b_star_2 v_25_pow_7 = 25**7 c_star_1 = Math.sqrt(a_star_1**2 + b_star_1**2) c_star_2 = Math.sqrt(a_star_2**2 + b_star_2**2) c_mean = ((c_star_1 + c_star_2) / 2.0) c_mean_pow_7 = c_mean**7 c_mean_g = (0.5 * (1.0 - Math.sqrt(c_mean_pow_7 / (c_mean_pow_7 + v_25_pow_7)))) a_1_prime = ((1.0 + c_mean_g) * a_star_1) a_2_prime = ((1.0 + c_mean_g) * a_star_2) c_1_prime = Math.sqrt(a_1_prime**2 + b_star_1**2) c_2_prime = Math.sqrt(a_2_prime**2 + b_star_2**2) h_1_prime = if a_1_prime + b_star_1 == 0 0 else (to_degrees(Math.atan2(b_star_1, a_1_prime)) % 360.0) end h_2_prime = if a_2_prime + b_star_2 == 0 0 else (to_degrees(Math.atan2(b_star_2, a_2_prime)) % 360.0) end delta_lower_h_prime = if h_2_prime - h_1_prime < -180 h_2_prime + 360 - h_1_prime elsif h_2_prime - h_1_prime > 180 h_2_prime - h_1_prime - 360.0 else h_2_prime - h_1_prime end delta_upper_l_prime = l_star_2 - l_star_1 delta_upper_c_prime = c_2_prime - c_1_prime delta_upper_h_prime = ( 2.0 * Math.sqrt(c_1_prime * c_2_prime) * Math.sin(to_radians(delta_lower_h_prime / 2.0)) ) l_prime_mean = ((l_star_1 + l_star_2) / 2.0) c_prime_mean = ((c_1_prime + c_2_prime) / 2.0) h_prime_mean = if c_1_prime * c_2_prime == 0 h_1_prime + h_2_prime elsif (h_2_prime - h_1_prime).abs <= 180 ((h_1_prime + h_2_prime) / 2.0) elsif h_2_prime + h_1_prime <= 360 ((h_1_prime + h_2_prime) / 2.0 + 180.0) else ((h_1_prime + h_2_prime) / 2.0 - 180.0) end l_prime_mean50sq = ((l_prime_mean - 50)**2) upper_s_l = (1 + (0.015 * l_prime_mean50sq / Math.sqrt(20 + l_prime_mean50sq))) upper_s_c = (1 + 0.045 * c_prime_mean) upper_t = ( 1 - 0.17 * Math.cos(to_radians(h_prime_mean - 30)) + 0.24 * Math.cos(to_radians(2 * h_prime_mean)) + 0.32 * Math.cos(to_radians(3 * h_prime_mean + 6)) - 0.2 * Math.cos(to_radians(4 * h_prime_mean - 63)) ) upper_s_h = (1 + 0.015 * c_prime_mean * upper_t) delta_theta = (30 * Math.exp(-1 * ((h_prime_mean - 275) / 25.0)**2)) upper_r_c = (2 * Math.sqrt(c_prime_mean**7 / (c_prime_mean**7 + v_25_pow_7))) upper_r_t = (-Math.sin(to_radians(2 * delta_theta)) * upper_r_c) delta_l_prime_div_kl_div_sl = (delta_upper_l_prime / upper_s_l / k_l.to_f) delta_c_prime_div_kc_div_sc = (delta_upper_c_prime / upper_s_c / k_c.to_f) delta_h_prime_div_kh_div_sh = (delta_upper_h_prime / upper_s_h / k_h.to_f) Math.sqrt( delta_l_prime_div_kl_div_sl**2 + delta_c_prime_div_kc_div_sc**2 + delta_h_prime_div_kh_div_sh**2 + upper_r_t * delta_c_prime_div_kc_div_sc * delta_h_prime_div_kh_div_sh ) end ## # Implements the \CIELAB ΔE* 1994 perceptual color distance metric. This version is an # improvement over previous versions, but it does not handle perceptual discontinuities # as well as \CIELAB ΔE* 2000. This is implemented because some functions still require # the 1994 algorithm for proper operation. # # See [CIE94][cie94] for precise details on the mathematical formulas. # # Different weights for `k_l`, `k_1`, and `k_2` may be applied via the `weight` keyword # parameter. This may be provided either as a Hash with `k_l`, `k_1`, and `k_2` values # or as a key to DE94_WEIGHTS. The default weight is `:graphic_arts`. # # See also . # # [cie94]: https://en.wikipedia.org/wiki/Color_difference#CIE94 def delta_e94(other, weight: :graphic_arts) weight = DE94_WEIGHTS[weight] if DE94_WEIGHTS.key?(weight) raise ArgumentError, "Unsupported weight #{weight.inspect}." unless weight.is_a?(Hash) weight => k_1:, k_2:, k_l: # Under some circumstances in real computers, the computed value of ΔH could be an # imaginary number (it's a square root value), so instead of √(((ΔL/(kL*sL))²) + # ((ΔC/(kC*sC))²) + ((ΔH/(kH*sH))²)), we have implemented the final computation as # √(((ΔL/(kL*sL))²) + ((ΔC/(kC*sC))²) + (ΔH2/(kH*sH)²)) and not performing the square # root when computing ΔH2. k_c = k_h = 1.0 other = coerce(other) self => l: l_1, a: a_1, b: b_1 other => l: l_2, a: a_2, b: b_2 delta_a = a_1 - a_2 delta_b = b_1 - b_2 cab_1 = Math.sqrt((a_1**2) + (b_1**2)) cab_2 = Math.sqrt((a_2**2) + (b_2**2)) delta_upper_l = l_1 - l_2 delta_upper_c = cab_1 - cab_2 delta_h2 = (delta_a**2) + (delta_b**2) - (delta_upper_c**2) s_upper_l = 1.0 s_upper_c = 1 + k_1 * cab_1 s_upper_h = 1 + k_2 * cab_1 composite_upper_l = (delta_upper_l / (k_l * s_upper_l))**2 composite_upper_c = (delta_upper_c / (k_c * s_upper_c))**2 composite_upper_h = delta_h2 / ((k_h * s_upper_h)**2) Math.sqrt(composite_upper_l + composite_upper_c + composite_upper_h) end ## alias_method :to_a, :deconstruct ## alias_method :to_internal, :deconstruct # :nodoc: ## def inspect = "CIELAB [%.4f %.4f %.4f]" % [l, a, b] # :nodoc: ## def pretty_print(q) # :nodoc: q.text "CIELAB" q.breakable q.group 2, "[", "]" do q.text "%.4f" % l q.fill_breakable q.text "%.4f" % a q.fill_breakable q.text "%.4f" % b end end end color-2.1.1/lib/color/rgb.rb0000644000004100000410000004657315050641176015670 0ustar www-datawww-data# frozen_string_literal: true # The \RGB color model is an additive color model where the primary colors (red, green, # and blue) of light are added to produce millions of colors. \RGB rendering is # device-dependent and without color management, the same "red" color will render # differently. # # This class does not implement color management and is not \RGB colorspace aware; that is, # unless otherwise noted, it does not assume that the \RGB represented is sRGB or Adobe # \RGB (opRGB). # # \RGB colors are immutable Data class instances. Array deconstruction is `[red, green, # blue]` and hash deconstruction is `{r:, red:, g:, green:, b:, blue}`. See #r, #red, #g, # #green, #b, #blue. class Color::RGB include Color ## # :attr_reader: r # Returns the red component of the color in the range 0.0..1.0. ## # :attr_reader: red # Returns the red component of the color in the normal 0..255 range. ## # :attr_reader: red_p # Returns the red component of the color as a percentage (0.0 .. 100.0). # ## # :attr_reader: g # Returns the green component of the color in the range 0.0..1.0. ## # :attr_reader: green # Returns the green component of the color in the normal 0 .. 255 range. ## # :attr_reader: green_p # Returns the green component of the color as a percentage (0.0 .. 100.0). ## # :attr_reader: b # Returns the blue component of the color in the range 0.0..1.0. ## # :attr_reader: blue # Returns the blue component of the color in the normal 0 .. 255 range. ## # :attr_reader: blue_p # Returns the blue component of the color as a percentage (0.0 .. 100.0). ## # Creates a \RGB color object from fractional values (0.0 .. 1.0). # # ```ruby # Color::RGB.from_fraction(0.3, 0.2, 0.1) # => RGB [#4d331a] # Color::RGB.new(0.3, 0.2, 0.1) # => RGB [#4d331a] # Color::RGB[r: 0.3, g: 0.2, b: 0.1] # => RGB [#4d331a] # ``` def initialize(r:, g:, b:, names: nil) super(r: normalize(r), g: normalize(g), b: normalize(b), names: names) end Black000 = new(r: 0x00, g: 0x00, b: 0x00) # :nodoc: WhiteFFF = new(r: 0xff, g: 0xff, b: 0xff) # :nodoc: ## # :attr_reader: name # The primary name for this \RGB color. ## # :attr_reader: names # The names for this \RGB color. ## def name = names&.first # :nodoc: ## # Coerces the other Color object into \RGB. def coerce(other) = other.to_rgb ## # Converts the \RGB color to Color::CMYK. # # Most color experts strongly suggest that this is not a good idea (some suggesting that # it's a very bad idea). CMYK represents additive percentages of inks on white paper, # whereas \RGB represents mixed color intensities on an unlit (black) screen. # # 1. Convert the R, G, and B components to C, M, and Y components. # # c = 1.0 - r # m = 1.0 - g # y = 1.0 - b # # 2. Compute the minimum amount of black (K) required to smooth the color in inks. # # k = min(c, m, y) # # 3. Perform undercolor removal on the C, M, and Y components of the colors because less # of each color is needed for each bit of black. Also, regenerate the black (K) based # on the undercolor removal so that the color is more accurately represented in ink. # # c = min(1.0, max(0.0, c - UCR(k))) # m = min(1.0, max(0.0, m - UCR(k))) # y = min(1.0, max(0.0, y - UCR(k))) # k = min(1.0, max(0.0, BG(k))) # # The undercolor removal function and the black generation functions return a value # based on the brightness of the \RGB color. def to_cmyk(...) c = 1.0 - r.to_f m = 1.0 - g.to_f y = 1.0 - b.to_f k = [c, m, y].min k -= (k * brightness) c = normalize(c - k) m = normalize(m - k) y = normalize(y - k) k = normalize(k) Color::CMYK.from_fraction(c, m, y, k) end ## def to_rgb(...) = self ## # Convert \RGB to Color::Grayscale via Color::HSL (for the luminance value). def to_grayscale(...) = Color::Grayscale.from_fraction(to_hsl.l) ## # Converts \RGB to Color::YIQ. def to_yiq(...) y = (r * 0.299) + (g * 0.587) + (b * 0.114) i = (r * 0.596) + (g * -0.275) + (b * -0.321) q = (r * 0.212) + (g * -0.523) + (b * 0.311) Color::YIQ.from_fraction(y, i, q) end ## # Converts \RGB to Color::HSL. # # The conversion here is based on formulas from http://www.easyrgb.com/math.php and # elsewhere. def to_hsl(...) min, max = [r, g, b].minmax delta = (max - min).to_f l = (max + min) / 2.0 if near_zero?(delta) # close to 0.0, so it's a gray h = 0 s = 0 else s = if near_zero_or_less?(l - 0.5) delta / (max + min).to_f else delta / (2 - max - min).to_f end # This is based on the conversion algorithm from # http://en.wikipedia.org/wiki/HSV_color_space#Conversion_from_RGB_to_HSL_or_HSV # Contributed by Adam Johnson sixth = 1 / 6.0 if r == max # near_zero_or_less?(r - max) h = (sixth * ((g - b) / delta)) h += 1.0 if g < b elsif g == max # near_zero_or_less(g - max) h = (sixth * ((b - r) / delta)) + (1.0 / 3.0) elsif b == max # near_zero_or_less?(b - max) h = (sixth * ((r - g) / delta)) + (2.0 / 3.0) end h += 1 if h < 0 h -= 1 if h > 1 end Color::HSL.from_fraction(h, s, l) end ## # Converts \RGB to Color::XYZ using the D65 reference white. This is based on conversion # formulas presented by Bruce Lindbloom, in particular [RGB to XYZ][rgbxyz]. # # [rgbxyz]: http://www.brucelindbloom.com/index.html?Eqn_RGB_to_XYZ.html # # The conversion is performed assuming the \RGB value is in the sRGB color space. No # other \RGB color spaces are currently supported. # # :call-seq: # to_xyz(color_space: :srgb) def to_xyz(*args, **kwargs) color_space = kwargs[:color_space] || args.first || :sRGB case color_space.to_s.downcase when "srgb" # Inverse sRGB companding. Linearizes RGB channels with respect to energy. rr, gg, bb = [r, g, b].map { if _1 > 0.04045 (((_1 + 0.055) / 1.055)**2.4) else (_1 / 12.92) end * 100.0 } # Convert using the RGB/XYZ matrix at: # http://www.brucelindbloom.com/index.html?Eqn_RGB_XYZ_Matrix.html#WSMatrices Color::XYZ.from_values( rr * 0.4124564 + gg * 0.3575761 + bb * 0.1804375, rr * 0.2126729 + gg * 0.7151522 + bb * 0.0721750, rr * 0.0193339 + gg * 0.1191920 + bb * 0.9503041 ) else raise ArgumentError, "Unsupported color space #{color_space}." end end ## # Converts \RGB to Color::CIELAB via Color::XYZ. # # Based on the [XYZ to CIELAB][xyztolab] formula presented by Bruce Lindbloom. # # [xyztolab]: http://www.brucelindbloom.com/index.html?Eqn_XYZ_to_Lab.html # # The conversion is performed assuming the \RGB value is in the sRGB color space. No # other \RGB color spaces are currently supported. By default, uses the D65 reference # white for the conversion. # # :call-seq: # to_lab(color_space: :sRGB, white: Color::XYZ::D65) def to_lab(...) = to_xyz(...).to_lab(...) ## # Present the color as an HTML/CSS \RGB hex triplet (+ccddee+). def hex "%02x%02x%02x" % [red, green, blue].map(&:round) end ## # Present the color as an HTML/CSS color string (+#ccddee+). def html "##{hex}" end ## # Present the color as an CSS `rgb` function with optional `alpha`. # # ```ruby # rgb = Color::RGB.from_percentage(0, 50, 100) # rgb.css # => rgb(0 50.00% 100.00%) # rgb.css(alpha: 0.5) # => rgb(0 50.00% 100.00% / 0.50) # ``` def css(alpha: nil) params = [css_value(red_p, :percent), css_value(green_p, :percent), css_value(blue_p, :percent)].join(" ") params = "#{params} / #{css_value(alpha)}" if alpha "rgb(#{params})" end ## # Computes the ΔE* 2000 difference via Color::CIELAB. See Color::CIELAB#delta_e2000. def delta_e2000(other) = to_lab.delta_e2000(coerce(other).to_lab) ## # Mix the \RGB hue with white so that the \RGB hue is the specified percentage of the # resulting color. # # Strictly speaking, this isn't a `lighten_by` operation, but it mostly works. def lighten_by(percent) = mix_with(Color::RGB::WhiteFFF, percent) ## # Mix the \RGB hue with black so that the \RGB hue is the specified percentage of the # resulting color. # # Strictly speaking, this isn't a `darken_by` operation, but it mostly works. def darken_by(percent) = mix_with(Color::RGB::Black000, percent) ## # Mix the mask color with the current color at the stated opacity percentage (0..100). def mix_with(mask, opacity) opacity = normalize(opacity / 100.0) mask = coerce(mask) with( r: (r * opacity) + (mask.r * (1 - opacity)), g: (g * opacity) + (mask.g * (1 - opacity)), b: (b * opacity) + (mask.b * (1 - opacity)) ) end ## # Returns the brightness value for a color, a number between 0..1. # # Based on the Y value of Color::YIQ encoding, representing luminosity, or perceived # brightness. def brightness = to_yiq.y ## # Returns a new \RGB color with the brightness adjusted by the specified percentage via # Color::HSL. Negative percentages will darken the color; positive percentages will # brighten the color. # # ```ruby # dark_blue = Color::RGB::DarkBlue # => RGB [#00008b] # dark_blue.adjust_brightness(10) # => RGB [#000099] # dark_blue.adjust_brightness(-10) # => RGB [#00007d] # ``` def adjust_brightness(percent) hsl = to_hsl hsl.with(l: hsl.l * percent_adjustment(percent)).to_rgb end ## # Returns a new \RGB color with the saturation adjusted by the specified percentage via # Color::HSL. Negative percentages will reduce the saturation; positive percentages will # increase the saturation. # # ```ruby # dark_blue = Color::RGB::DarkBlue # => RGB [#00008b] # dark_blue.adjust_saturation(10) # => RGB [#00008b] # dark_blue.adjust_saturation(-10) # => RGB [#070784] # ``` def adjust_saturation(percent) hsl = to_hsl hsl.with(s: hsl.s * percent_adjustment(percent)).to_rgb end ## # Returns a new \RGB color with the hue adjusted by the specified percentage via # Color::HSL. Negative percentages will reduce the hue; positive percentages will # increase the hue. # # ```ruby # dark_blue = Color::RGB::DarkBlue # => RGB [#00008b] # dark_blue.adjust_hue(10) # => RGB [#38008b] # dark_blue.adjust_hue(-10) # => RGB [#00388b] # ``` def adjust_hue(percent) hsl = to_hsl hsl.with(h: hsl.h * percent_adjustment(percent)).to_rgb end ## # Determines the closest match to this color from a list of provided colors or `nil` if # `color_list` is empty or no color is found within the `threshold_distance`. # # The default search uses the CIE ΔE* 1994 algorithm (CIE94) to find near matches based # on the perceived visual differences between the colors. The default value for # `algorithm` is `:delta_e94`. # # `threshold_distance` is used to determine the minimum color distance permitted. Uses # the CIE ΔE* 1994 algorithm (CIE94) to find near matches based on perceived visual # color. The default value (1000.0) is an arbitrarily large number. The values `:jnd` # and `:just_noticeable` may be passed as the `threshold_distance` to use the value # `2.3`. # # All ΔE* formulae were designed to use 1.0 as a "just noticeable difference" (JND), # but CIE ΔE*ab 1976 defined JND as 2.3. # # :call-seq: # closest_match(color_list, algorithm: :delta_e94, threshold_distance: 1000.0) def closest_match(color_list, *args, **kwargs) color_list = [color_list].flatten(1) return nil if color_list.empty? algorithm = kwargs[:algorithm] || args.first || :delta_e94 threshold_distance = kwargs[:threshold_distance] || args[1] || 1000.0 threshold_distance = case threshold_distance when :jnd, :just_noticeable 2.3 else threshold_distance.to_f end closest_distance = threshold_distance best_match = nil color_list.each do |c| distance = contrast(c, algorithm) if distance < closest_distance closest_distance = distance best_match = c end end best_match end ## # The Delta E (CIE94) algorithm http://en.wikipedia.org/wiki/Color_difference#CIE94 # # There is a newer version, CIEDE2000, that uses slightly more complicated math, but # addresses "the perceptual uniformity issue" left lingering by the CIE94 algorithm. # # Since our source is treated as sRGB, we use the "graphic arts" presets for k_L, k_1, # and k_2 # # The calculations go through LCH(ab). (?) # # See also http://www.brucelindbloom.com/index.html?Eqn_DeltaE_CIE94.html def delta_e94(...) = to_lab.delta_e94(...) ## def red = normalize(r * 255.0, 0.0..255.0) # :nodoc: ## def red_p = normalize(r * 100.0, 0.0..100.0) # :nodoc: ## def green = normalize(g * 255.0, 0.0..255.0) # :nodoc: ## def green_p = normalize(g * 100.0, 0.0..100.0) # :nodoc: ## def blue = normalize(b * 255.0, 0.0..255.0) # :nodoc: ## def blue_p = normalize(b * 100.0, 0.0..100.0) # :nodoc: ## # Return a Grayscale color object created from the largest of the `r`, `g`, and `b` # values. def max_rgb_as_grayscale = Color::Grayscale.from_fraction([r, g, b].max) ## def inspect = "RGB [#{html}]" # :nodoc: ## def pretty_print(q) # :nodoc: q.text "RGB" q.breakable q.group 2, "[", "]" do q.text html end end ## def to_a = [red, green, blue] # :nodoc: ## alias_method :deconstruct, :to_a # :nodoc: ## def deconstruct_keys(_keys) = {r:, g:, b:, red:, green:, blue:} # :nodoc: ## def to_internal = [r, g, b] # :nodoc: ## # Outputs how much contrast this color has with another RGB color. # # The `delta_e94` algorithm uses ΔE*94 for contrast calculations and the `delta_e2000` # algorithm uses ΔE*2000. # # The `naive` algorithm treats the foreground and background colors as the same. # Any result over about 0.22 should have a high likelihood of being legible, but the # larger the difference, the more contrast. Otherwise, to be safe go with something # > 0.3. # # :call-seq: # contrast(other, algorithm: :naive) # contrast(other, algorithm: :delta_e94) # contrast(other, algorithm: :delta_e2000) def contrast(other, *args, **kwargs) other = coerce(other) algorithm = kwargs[:algorithm] || args.first || :naive case algorithm when :delta_e94 delta_e94(other) when :delta_e2000 delta_e2000(other) when :naive # The following numbers have been set with some care. ((diff_brightness(other) * 0.65) + (diff_hue(other) * 0.20) + (diff_luminosity(other) * 0.15)) else raise ARgumentError, "Unknown algorithm #{algorithm.inspect}" end end private ## def percent_adjustment(percent) # :nodoc: percent /= 100.0 percent += 1.0 percent = [percent, 2.0].min [0.0, percent].max end ## # Provides the luminosity difference between two rbg vals def diff_luminosity(other) # :nodoc: l1 = (0.2126 * other.r**2.2) + (0.7152 * other.b**2.2) + (0.0722 * other.g**2.2) l2 = (0.2126 * r**2.2) + (0.7152 * b**2.2) + (0.0722 * g**2.2) (([l1, l2].max + 0.05) / ([l1, l2].min + 0.05) - 1) / 20.0 end ## # Provides the brightness difference. def diff_brightness(other) # :nodoc: br1 = (299 * other.r + 587 * other.g + 114 * other.b) br2 = (299 * r + 587 * g + 114 * b) (br1 - br2).abs / 1000.0 end ## # Provides the euclidean distance between the two color values def diff_euclidean(other) ((((other.r - r)**2) + ((other.g - g)**2) + ((other.b - b)**2))**0.5) / 1.7320508075688772 end ## # Difference in the two colors' hue def diff_hue(other) # :nodoc: ((r - other.r).abs + (g - other.g).abs + (b - other.b).abs) / 3 end end class << Color::RGB ## # Creates a RGB color object from percentage values (0.0 .. 100.0). # # ```ruby # Color::RGB.from_percentage(10, 20, 30) # ``` def from_percentage(*args, **kwargs) r, g, b, names = case [args, kwargs] in [[r, g, b], {}] [r, g, b, nil] in [[_, _, _, _], {}] args in [[], {r:, g:, b:}] [r, g, b, nil] in [[], {r:, g:, b:, names:}] [r, g, b, names] else new(*args, **kwargs) end new(r: r / 100.0, g: g / 100.0, b: b / 100.0, names: names) end # Creates a RGB color object from the standard three byte range (0 .. 255). # # ```ruby # Color::RGB.from_values(32, 64, 128) # Color::RGB.from_values(0x20, 0x40, 0x80) # ``` def from_values(*args, **kwargs) r, g, b, names = case [args, kwargs] in [[r, g, b], {}] [r, g, b, nil] in [[_, _, _, _], {}] args in [[], {r:, g:, b:}] [r, g, b, nil] in [[], {r:, g:, b:, names:}] [r, g, b, names] else new(*args, **kwargs) end new(r: r / 255.0, g: g / 255.0, b: b / 255.0, names: names) end ## alias_method :from_fraction, :new ## alias_method :from_internal, :new # :nodoc: ## # Creates a RGB color object from an HTML color descriptor (e.g., `"fed"` or # `"#cabbed;"`. # # ```ruby # Color::RGB.from_html("fed") # Color::RGB.from_html("#fed") # Color::RGB.from_html("#cabbed") # Color::RGB.from_html("cabbed") # ``` def from_html(html_color) h = html_color.scan(/\h/i) r, g, b = case h.size when 3 h.map { |v| (v * 2).to_i(16) } when 6 h.each_slice(2).map { |v| v.join.to_i(16) } else raise ArgumentError, "Not a supported HTML color type." end from_values(r, g, b) end ## # Find or create a color by an HTML hex code. This differs from the #from_html method # in that if the color code matches a named color, the existing color will be # returned. # # ```ruby # Color::RGB.by_hex('ff0000').name # => 'red' # Color::RGB.by_hex('ff0001').name # => nil # ``` # # An exception will be raised if the value provided is not found or cannot be # interpreted as a valid hex colour. def by_hex(hex) = __by_hex.fetch(html_hexify(hex)) { from_html(hex) } ## # Return a color as identified by the color name. def by_name(name, &block) = __by_name.fetch(name.to_s.downcase, &block) ## # Return a color as identified by the color name, or by hex. def by_css(name_or_hex, &block) = by_name(name_or_hex) { by_hex(name_or_hex, &block) } ## # Extract named or hex colors from the provided text. def extract_colors(text, mode = :both) require "color/rgb/colors" text = text.downcase regex = case mode when :name Regexp.union(__by_name.keys) when :hex Regexp.union(__by_hex.keys) when :both Regexp.union(__by_hex.keys + __by_name.keys) else raise ArgumentError, "Unknown mode #{mode}" end text.scan(regex).map { |match| case mode when :name by_name(match) when :hex by_hex(match) when :both by_css(match) end } end private ## def __by_hex # :nodoc: require "color/rgb/colors" @__by_hex end ## def __by_name # :nodoc: require "color/rgb/colors" @__by_name end ## def html_hexify(hex) # :nodoc: h = hex.to_s.downcase.scan(/\h/) case h.size when 3 h.map { |v| (v * 2) }.join when 6 h.join else raise ArgumentError, "Not a supported HTML color type." end end end color-2.1.1/lib/color/rgb/0000755000004100000410000000000015050641176015324 5ustar www-datawww-datacolor-2.1.1/lib/color/rgb/colors.rb0000644000004100000410000002662515050641176017165 0ustar www-datawww-data# frozen_string_literal: true class Color::RGB # :stopdoc: class << self def __create_named_colors(mod, *colors) @__by_hex ||= {} @__by_name ||= {} colors.each do |color| color => {rgb:, names:} raise ArgumentError, "Names cannot be empty" if names.nil? || names.empty? used = names - mod.constants.map(&:to_sym) if used.length < names.length raise ArgumentError, "#{names.join(", ")} already defined in #{mod}" end rgb = rgb.with(names: Array(names).flatten.compact.map { _1.to_s.downcase }.sort.uniq) names.each { mod.const_set(_1, rgb) } rgb.names.each { @__by_name[_1] = @__by_name[_1.to_s] = rgb } lower = rgb.name.downcase @__by_name[lower] = @__by_name[lower.to_s] = rgb @__by_hex[rgb.hex] = rgb end end end __create_named_colors( self, {rgb: from_values(0xf0, 0xf8, 0xff), names: [:AliceBlue]}, {rgb: from_values(0xfa, 0xeb, 0xd7), names: [:AntiqueWhite]}, {rgb: from_values(0x00, 0xff, 0xff), names: [:Aqua]}, {rgb: from_values(0x7f, 0xff, 0xd4), names: [:Aquamarine]}, {rgb: from_values(0xf0, 0xff, 0xff), names: [:Azure]}, {rgb: from_values(0xf5, 0xf5, 0xdc), names: [:Beige]}, {rgb: from_values(0xff, 0xe4, 0xc4), names: [:Bisque]}, {rgb: from_values(0x00, 0x00, 0x00), names: [:Black]}, {rgb: from_values(0xff, 0xeb, 0xcd), names: [:BlanchedAlmond]}, {rgb: from_values(0x00, 0x00, 0xff), names: [:Blue]}, {rgb: from_values(0x8a, 0x2b, 0xe2), names: [:BlueViolet]}, {rgb: from_values(0xa5, 0x2a, 0x2a), names: [:Brown]}, {rgb: from_values(0xde, 0xb8, 0x87), names: [:BurlyWood, :Burlywood]}, {rgb: from_values(0x5f, 0x9e, 0xa0), names: [:CadetBlue]}, {rgb: from_values(0xff, 0x5e, 0xd0), names: [:Carnation]}, {rgb: from_values(0x8d, 0x00, 0x00), names: [:Cayenne]}, {rgb: from_values(0x7f, 0xff, 0x00), names: [:Chartreuse]}, {rgb: from_values(0xd2, 0x69, 0x1e), names: [:Chocolate]}, {rgb: from_values(0xff, 0x7f, 0x50), names: [:Coral]}, {rgb: from_values(0x64, 0x95, 0xed), names: [:CornflowerBlue]}, {rgb: from_values(0xff, 0xf8, 0xdc), names: [:Cornsilk]}, {rgb: from_values(0xdc, 0x14, 0x3c), names: [:Crimson]}, {rgb: from_values(0x00, 0xff, 0xff), names: [:Cyan]}, {rgb: from_values(0x00, 0x00, 0x8b), names: [:DarkBlue]}, {rgb: from_values(0x00, 0x8b, 0x8b), names: [:DarkCyan]}, {rgb: from_values(0xb8, 0x86, 0x0b), names: [:DarkGoldenRod, :DarkGoldenrod]}, {rgb: from_values(0xa9, 0xa9, 0xa9), names: [:DarkGray, :DarkGrey]}, {rgb: from_values(0x00, 0x64, 0x00), names: [:DarkGreen]}, {rgb: from_values(0xbd, 0xb7, 0x6b), names: [:DarkKhaki]}, {rgb: from_values(0x8b, 0x00, 0x8b), names: [:DarkMagenta]}, {rgb: from_values(0x55, 0x6b, 0x2f), names: [:DarkOliveGreen, :DarkoliveGreen]}, {rgb: from_values(0xff, 0x8c, 0x00), names: [:DarkOrange, :Darkorange]}, {rgb: from_values(0x99, 0x32, 0xcc), names: [:DarkOrchid]}, {rgb: from_values(0x8b, 0x00, 0x00), names: [:DarkRed]}, {rgb: from_values(0xe9, 0x96, 0x7a), names: [:DarkSalmon, :Darksalmon]}, {rgb: from_values(0x8f, 0xbc, 0x8f), names: [:DarkSeaGreen]}, {rgb: from_values(0x48, 0x3d, 0x8b), names: [:DarkSlateBlue]}, {rgb: from_values(0x2f, 0x4f, 0x4f), names: [:DarkSlateGray, :DarkSlateGrey]}, {rgb: from_values(0x00, 0xce, 0xd1), names: [:DarkTurquoise]}, {rgb: from_values(0x94, 0x00, 0xd3), names: [:DarkViolet]}, {rgb: from_values(0xff, 0x14, 0x93), names: [:DeepPink]}, {rgb: from_values(0x00, 0xbf, 0xff), names: [:DeepSkyBlue]}, {rgb: from_values(0x69, 0x69, 0x69), names: [:DimGray, :DimGrey]}, {rgb: from_values(0x1e, 0x90, 0xff), names: [:DodgerBlue]}, {rgb: from_values(0xd1, 0x92, 0x75), names: [:Feldspar]}, {rgb: from_values(0xb2, 0x22, 0x22), names: [:FireBrick, :Firebrick]}, {rgb: from_values(0xff, 0xfa, 0xf0), names: [:FloralWhite]}, {rgb: from_values(0x22, 0x8b, 0x22), names: [:ForestGreen]}, {rgb: from_values(0xff, 0x00, 0xff), names: [:Fuchsia]}, {rgb: from_values(0xdc, 0xdc, 0xdc), names: [:Gainsboro]}, {rgb: from_values(0xf8, 0xf8, 0xff), names: [:GhostWhite]}, {rgb: from_values(0xff, 0xd7, 0x00), names: [:Gold]}, {rgb: from_values(0xda, 0xa5, 0x20), names: [:GoldenRod, :Goldenrod]}, {rgb: from_values(0x80, 0x80, 0x80), names: [:Gray, :Grey]}, {rgb: from_fraction(0.05, 0.05, 0.05), names: [:Gray05, :Gray5, :Grey05, :Grey5]}, *(10..95).step(5).map { v = _1 / 100.0 {rgb: from_fraction(v, v, v), names: [:"Gray#{_1}", :"Grey#{_1}"]} }, {rgb: from_values(0x00, 0x80, 0x00), names: [:Green]}, {rgb: from_values(0xad, 0xff, 0x2f), names: [:GreenYellow]}, {rgb: from_values(0xf0, 0xff, 0xf0), names: [:HoneyDew, :Honeydew]}, {rgb: from_values(0xff, 0x69, 0xb4), names: [:HotPink]}, {rgb: from_values(0xcd, 0x5c, 0x5c), names: [:IndianRed]}, {rgb: from_values(0x4b, 0x00, 0x82), names: [:Indigo]}, {rgb: from_values(0xff, 0xff, 0xf0), names: [:Ivory]}, {rgb: from_values(0xf0, 0xe6, 0x8c), names: [:Khaki]}, {rgb: from_values(0xe6, 0xe6, 0xfa), names: [:Lavender]}, {rgb: from_values(0xff, 0xf0, 0xf5), names: [:LavenderBlush]}, {rgb: from_values(0x7c, 0xfc, 0x00), names: [:LawnGreen]}, {rgb: from_values(0xff, 0xfa, 0xcd), names: [:LemonChiffon]}, {rgb: from_values(0xad, 0xd8, 0xe6), names: [:LightBlue]}, {rgb: from_values(0xf0, 0x80, 0x80), names: [:LightCoral]}, {rgb: from_values(0xe0, 0xff, 0xff), names: [:LightCyan]}, {rgb: from_values(0xfa, 0xfa, 0xd2), names: [:LightGoldenRodYellow, :LightGoldenrodYellow]}, {rgb: from_values(0xd3, 0xd3, 0xd3), names: [:LightGray, :LightGrey]}, {rgb: from_values(0x90, 0xee, 0x90), names: [:LightGreen]}, {rgb: from_values(0xff, 0xb6, 0xc1), names: [:LightPink]}, {rgb: from_values(0xff, 0xa0, 0x7a), names: [:LightSalmon, :Lightsalmon]}, {rgb: from_values(0x20, 0xb2, 0xaa), names: [:LightSeaGreen]}, {rgb: from_values(0x87, 0xce, 0xfa), names: [:LightSkyBlue]}, {rgb: from_values(0x84, 0x70, 0xff), names: [:LightSlateBlue]}, {rgb: from_values(0x77, 0x88, 0x99), names: [:LightSlateGray, :LightSlateGrey]}, {rgb: from_values(0xb0, 0xc4, 0xde), names: [:LightSteelBlue, :LightsteelBlue]}, {rgb: from_values(0xff, 0xff, 0xe0), names: [:LightYellow]}, {rgb: from_values(0x00, 0xff, 0x00), names: [:Lime]}, {rgb: from_values(0x32, 0xcd, 0x32), names: [:LimeGreen]}, {rgb: from_values(0xfa, 0xf0, 0xe6), names: [:Linen]}, {rgb: from_values(0xff, 0x00, 0xff), names: [:Magenta]}, {rgb: from_values(0x80, 0x00, 0x00), names: [:Maroon]}, {rgb: from_values(0x66, 0xcd, 0xaa), names: [:MediumAquaMarine, :MediumAquamarine]}, {rgb: from_values(0x00, 0x00, 0xcd), names: [:MediumBlue]}, {rgb: from_values(0xba, 0x55, 0xd3), names: [:MediumOrchid]}, {rgb: from_values(0x93, 0x70, 0xdb), names: [:MediumPurple]}, {rgb: from_values(0x3c, 0xb3, 0x71), names: [:MediumSeaGreen]}, {rgb: from_values(0x7b, 0x68, 0xee), names: [:MediumSlateBlue]}, {rgb: from_values(0x00, 0xfa, 0x9a), names: [:MediumSpringGreen]}, {rgb: from_values(0x48, 0xd1, 0xcc), names: [:MediumTurquoise]}, {rgb: from_values(0xc7, 0x15, 0x85), names: [:MediumVioletRed]}, {rgb: from_values(0x19, 0x19, 0x70), names: [:MidnightBlue]}, {rgb: from_values(0xf5, 0xff, 0xfa), names: [:MintCream]}, {rgb: from_values(0xff, 0xe4, 0xe1), names: [:MistyRose]}, {rgb: from_values(0xff, 0xe4, 0xb5), names: [:Moccasin]}, {rgb: from_values(0xff, 0xde, 0xad), names: [:NavajoWhite]}, {rgb: from_values(0x00, 0x00, 0x80), names: [:Navy]}, {rgb: from_values(0xfd, 0xf5, 0xe6), names: [:OldLace]}, {rgb: from_values(0x80, 0x80, 0x00), names: [:Olive]}, {rgb: from_values(0x6b, 0x8e, 0x23), names: [:OliveDrab, :Olivedrab]}, {rgb: from_values(0xff, 0xa5, 0x00), names: [:Orange]}, {rgb: from_values(0xff, 0x45, 0x00), names: [:OrangeRed]}, {rgb: from_values(0xda, 0x70, 0xd6), names: [:Orchid]}, {rgb: from_values(0xee, 0xe8, 0xaa), names: [:PaleGoldenRod, :PaleGoldenrod]}, {rgb: from_values(0x98, 0xfb, 0x98), names: [:PaleGreen]}, {rgb: from_values(0xaf, 0xee, 0xee), names: [:PaleTurquoise]}, {rgb: from_values(0xdb, 0x70, 0x93), names: [:PaleVioletRed]}, {rgb: from_values(0xff, 0xef, 0xd5), names: [:PapayaWhip]}, {rgb: from_values(0xff, 0xda, 0xb9), names: [:PeachPuff, :Peachpuff]}, {rgb: from_values(0xcd, 0x85, 0x3f), names: [:Peru]}, {rgb: from_values(0xff, 0xc0, 0xcb), names: [:Pink]}, {rgb: from_values(0xdd, 0xa0, 0xdd), names: [:Plum]}, {rgb: from_values(0xb0, 0xe0, 0xe6), names: [:PowderBlue]}, {rgb: from_values(0x80, 0x00, 0x80), names: [:Purple]}, {rgb: from_values(0x66, 0x33, 0x99), names: [:RebeccaPurple]}, {rgb: from_values(0xff, 0x00, 0x00), names: [:Red]}, {rgb: from_values(0xbc, 0x8f, 0x8f), names: [:RosyBrown]}, {rgb: from_values(0x41, 0x69, 0xe1), names: [:RoyalBlue]}, {rgb: from_values(0x8b, 0x45, 0x13), names: [:SaddleBrown]}, {rgb: from_values(0xfa, 0x80, 0x72), names: [:Salmon]}, {rgb: from_values(0xf4, 0xa4, 0x60), names: [:SandyBrown]}, {rgb: from_values(0x2e, 0x8b, 0x57), names: [:SeaGreen]}, {rgb: from_values(0xff, 0xf5, 0xee), names: [:SeaShell, :Seashell]}, {rgb: from_values(0xa0, 0x52, 0x2d), names: [:Sienna]}, {rgb: from_values(0xc0, 0xc0, 0xc0), names: [:Silver]}, {rgb: from_values(0x87, 0xce, 0xeb), names: [:SkyBlue]}, {rgb: from_values(0x6a, 0x5a, 0xcd), names: [:SlateBlue]}, {rgb: from_values(0x70, 0x80, 0x90), names: [:SlateGray, :SlateGrey]}, {rgb: from_values(0xff, 0xfa, 0xfa), names: [:Snow]}, {rgb: from_values(0x00, 0xff, 0x7f), names: [:SpringGreen]}, {rgb: from_values(0x46, 0x82, 0xb4), names: [:SteelBlue]}, {rgb: from_values(0xd2, 0xb4, 0x8c), names: [:Tan]}, {rgb: from_values(0x00, 0x80, 0x80), names: [:Teal]}, {rgb: from_values(0xd8, 0xbf, 0xd8), names: [:Thistle]}, {rgb: from_values(0xff, 0x63, 0x47), names: [:Tomato]}, {rgb: from_values(0x40, 0xe0, 0xd0), names: [:Turquoise]}, {rgb: from_values(0xee, 0x82, 0xee), names: [:Violet]}, {rgb: from_values(0xd0, 0x20, 0x90), names: [:VioletRed]}, {rgb: from_values(0xf5, 0xde, 0xb3), names: [:Wheat]}, {rgb: from_values(0xff, 0xff, 0xff), names: [:White]}, {rgb: from_values(0xf5, 0xf5, 0xf5), names: [:WhiteSmoke]}, {rgb: from_values(0xff, 0xff, 0x00), names: [:Yellow]}, {rgb: from_values(0x9a, 0xcd, 0x32), names: [:YellowGreen]} ) # :stopdoc: # This namespace contains some RGB metallic colors suggested by Jim # Freeze. # :startdoc: module Metallic; end __create_named_colors( Metallic, {rgb: from_values(0x99, 0x99, 0x99), names: [:Aluminum]}, {rgb: from_values(0xd9, 0x87, 0x19), names: [:CoolCopper]}, {rgb: from_values(0xb8, 0x73, 0x33), names: [:Copper]}, {rgb: from_values(0x4c, 0x4c, 0x4c), names: [:Iron]}, {rgb: from_values(0x19, 0x19, 0x19), names: [:Lead]}, {rgb: from_values(0xb3, 0xb3, 0xb3), names: [:Magnesium]}, {rgb: from_values(0xe6, 0xe6, 0xe6), names: [:Mercury]}, {rgb: from_values(0x80, 0x80, 0x80), names: [:Nickel]}, {rgb: from_values(0x60, 0x00, 0x00), names: [:PolySilicon, :Poly]}, {rgb: from_values(0xcc, 0xcc, 0xcc), names: [:Silver]}, {rgb: from_values(0x66, 0x66, 0x66), names: [:Steel]}, {rgb: from_values(0x7f, 0x7f, 0x7f), names: [:Tin]}, {rgb: from_values(0x33, 0x33, 0x33), names: [:Tungsten]} ) class << self undef :__create_named_colors end # :startdoc: end color-2.1.1/lib/color/hsl.rb0000644000004100000410000001657015050641176015676 0ustar www-datawww-data# frozen_string_literal: true ## # The \HSL color model is a cylindrical-coordinate representation of the sRGB color model, # standing for hue (measured in degrees on the cylinder), saturation (measured in # percentage), and lightness (measured in percentage). # # \HSL colors are immutable Data class instances. Array deconstruction is `[hue, # saturation, luminosity]` and hash deconstruction is `{h:, hue:, s:, saturation:, l:, # luminosity:}`. See #h, #hue, #s, #saturation, #l, #luminosity. class Color::HSL include Color ## # :attr_reader: h # Returns the hue of the color in the range 0.0..1.0. ## # :attr_reader: hue # Returns the hue of the color in degrees (0.0..360.0). ## # :attr_reader: s # Returns the saturation of the color in the range 0.0..1.0. ## # :attr_reader: saturation # Returns the percentage of saturation of the color (0.0..100.0). ## # :attr_reader: brightness # Returns the luminosity (#l) of the color. ## # :attr_reader: l # Returns the luminosity of the color in the range 0.0..1.0. ## # :attr_reader: luminosity # Returns the percentage of luminosity of the color. ## # Creates a \HSL color object from degrees (0.0 .. 360.0) and percentage values # (0.0 .. 100.0). # # ```ruby # Color::HSL.from_values(145, 30, 50) # => HSL [145deg 30% 50%] # Color::HSL.from_values(h: 145, s: 30, l: 50) # => HSL [145deg 30% 50%] # ``` # # :call-seq: # from_values(h, s, l) # from_values(h:, s:, l:) def self.from_values(*args, **kwargs) h, s, l = case [args, kwargs] in [[_, _, _], {}] args in [[], {h:, s:, l:}] [h, s, l] else new(*args, **kwargs) end new(h: h / 360.0, s: s / 100.0, l: l / 100.0) end class << self alias_method :from_fraction, :new alias_method :from_internal, :new # :nodoc: end ## # Creates a \HSL color object from fractional values (0.0 .. 1.0). # # ```ruby # Color::HSL.from_fraction(0.3, 0.3, 0.5) # => HSL [108deg 30% 50%] # Color::HSL.new(h: 0.3, s: 0.3, l: 0.5) # => HSL [108deg 30% 50%] # Color::HSL[0.3, 0.3, 0.5] # => HSL [108deg 30% 50%] # ``` def initialize(h:, s:, l:) super(h: normalize(h), s: normalize(s), l: normalize(l)) end ## # Coerces the other Color object into \HSL. def coerce(other) = other.to_hsl ## # Converts from \HSL to Color::RGB. # # As with all color conversions, this is an approximation. The code here is adapted from # fvd and van Dam, originally found at [1] (implemented similarly at [2]). This # simplifies the calculations with the following assumptions: # # - Luminance values <= 0 always translate to a black Color::RGB value. # - Luminance values >= 1 always translate to a white Color::RGB value. # - Saturation values <= 0 always translate to a shade of gray using luminance as # a percentage of gray. # # [1] http://bobpowell.net/RGBHSB.aspx # [2] http://support.microsoft.com/kb/29240 def to_rgb(...) if near_zero_or_less?(l) Color::RGB::Black000 elsif near_one_or_more?(l) Color::RGB::WhiteFFF elsif near_zero?(s) Color::RGB.from_fraction(l, l, l) else Color::RGB.from_fraction(*compute_fvd_rgb) end end ## # Converts from \HSL to Color::YIQ via Color::RGB. def to_yiq(...) = to_rgb(...).to_yiq(...) ## # Converts from \HSL to Color::CMYK via Color::RGB. def to_cmyk(...) = to_rgb(...).to_cmyk(...) ## # Converts from \HSL to Color::Grayscale. # # Luminance is treated as the Grayscale ratio. def to_grayscale(...) = Color::Grayscale.from_fraction(l) ## # Converts from \HSL to Color::CIELAB via Color::RGB. def to_lab(...) = to_rgb(...).to_lab(...) ## # Converts from \HSL to Color::XYZ via Color::RGB. def to_xyz(...) = to_rgb(...).to_xyz(...) ## def to_hsl(...) = self ## # Present the color as a CSS `hsl` function with optional `alpha`. # # ```ruby # hsl = Color::HSL.from_values(145, 30, 50) # hsl.css # => hsl(145deg 30% 50%) # hsl.css(alpha: 0.5) # => hsl(145deg 30% 50% / 0.50) # ``` def css(alpha: nil) params = [ css_value(hue, :degrees), css_value(saturation, :percent), css_value(luminosity, :percent) ].join(" ") params = "#{params} / #{css_value(alpha)}" if alpha "hsl(#{params})" end ## def brightness = l # :nodoc: ## def hue = h * 360.0 # :nodoc: ## def saturation = s * 100.0 # :nodoc: ## def luminosity = l * 100.0 # :nodoc: ## alias_method :lightness, :luminosity ## def inspect = "HSL [%.2fdeg %.2f%% %.2f%%]" % [hue, saturation, luminosity] # :nodoc: ## def pretty_print(q) # :nodoc: q.text "HSL" q.breakable q.group 2, "[", "]" do q.text "%.2fdeg" % hue q.fill_breakable q.text "%.2f%%" % saturation q.fill_breakable q.text "%.2f%%" % luminosity end end ## # Mix the mask color (which will be converted to a \HSL color) with the current color # at the stated fractional mix ratio (0.0..1.0). # # This implementation differs from Color::RGB#mix_with. # # :call-seq: # mix_with(mask, mix_ratio: 0.5) def mix_with(mask, *args, **kwargs) mix_ratio = normalize(kwargs[:mix_ratio] || args.first || 0.5) map_with(mask) { ((_2 - _1) * mix_ratio) + _1 } end ## def to_a = [hue, saturation, luminosity] # :nodoc: ## alias_method :deconstruct, :to_a ## def deconstruct_keys(_keys) = {h:, s:, l:, hue:, saturation:, luminance:} # :nodoc: ## def to_internal = [h, s, l] # :nodoc: private ## # This algorithm calculates based on a mixture of the saturation and luminance, and then # takes the RGB values from the hue + 1/3, hue, and hue - 1/3 positions in a circular # representation of color divided into four parts (confusing, I know, but it's the way # that it works). See #hue_to_rgb for more information. def compute_fvd_rgb # :nodoc: t1, t2 = fvd_mix_sat_lum [h + (1 / 3.0), h, h - (1 / 3.0)].map { |v| hue_to_rgb(rotate_hue(v), t1, t2) } end ## # Mix saturation and luminance for use in hue_to_rgb. The base value is different # depending on whether luminance is <= 50% or > 50%. def fvd_mix_sat_lum # :nodoc: t = if near_zero_or_less?(l - 0.5) l * (1.0 + s.to_f) else l + s - (l * s.to_f) end [2.0 * l - t, t] end ## # In \HSL, hues are referenced as degrees in a color circle. The flow itself is endless; # therefore, we can rotate around. The only thing our implementation restricts is that # you should not be > 1.0. def rotate_hue(h) # :nodoc: h += 1.0 if near_zero_or_less?(h) h -= 1.0 if near_one_or_more?(h) h end ## # We calculate the interaction of the saturation/luminance mix (calculated earlier) # based on the position of the hue in the circular color space divided into quadrants. # Our hue range is [0, 1), not [0, 360º). # # - The first quadrant covers the first 60º [0, 60º]. # - The second quadrant covers the next 120º (60º, 180º]. # - The third quadrant covers the next 60º (180º, 240º]. # - The fourth quadrant covers the final 120º (240º, 360º). def hue_to_rgb(h, t1, t2) # :nodoc: if near_zero_or_less?((6.0 * h) - 1.0) t1 + ((t2 - t1) * h * 6.0) elsif near_zero_or_less?((2.0 * h) - 1.0) t2 elsif near_zero_or_less?((3.0 * h) - 2.0) t1 + (t2 - t1) * ((2 / 3.0) - h) * 6.0 else t1 end end end color-2.1.1/lib/color/version.rb0000644000004100000410000000011615050641176016562 0ustar www-datawww-data# frozen_string_literal: true module Color VERSION = "2.1.1" # :nodoc: end color-2.1.1/lib/color/cmyk.rb0000644000004100000410000002334615050641176016052 0ustar www-datawww-data# frozen_string_literal: true ## # The \CMYK color model is a subtractive color model based on additive percentages of # colored inks: cyan, magenta, yellow, and key (most often black). # # \CMYK [30% 0% 80% 30%] would be mixed from 30% cyan, 0% magenta, 80% yellow, and 30% # black. # # \CMYK colors are immutable Data class instances. Array deconstruction is `[cyan, # magenta, yellow, key]` and hash deconstruction is `{c:, cyan:, m:, magenta:, y:, yellow: # k:, key:}`. See #c, #cyan, #m, #magenta, #y, #yellow, #k, #key. class Color::CMYK include Color ## # :attr_reader: c # Returns the cyan (`C`) component as a value 0.0 .. 1.0. ## # :attr_reader: cyan # Returns the cyan (`C`) component as a percentage value (0.0 .. 100.0). ## # :attr_reader: m # Returns the magenta (`M`) component as a value 0.0 .. 1.0. ## # :attr_reader: magenta # Returns the magenta (`M`) component as a percentage value (0.0 .. 100.0). ## # :attr_reader: y # Returns the yellow (`Y`) component as a value 0.0 .. 1.0. ## # :attr_reader: yellow # Returns the yellow (`Y`) component as a percentage value (0.0 .. 100.0). ## # :attr_reader: k # Returns the key or black (`K`) component as a value 0.0 .. 1.0. ## # :attr_reader: b # Returns the key or black (`K`) component as a value 0.0 .. 1.0. ## # :attr_reader: key # Returns the key or black (`K`) component as a percentage value (0.0 .. 100.0). ## # :attr_reader: black # Returns the key or black (`K`) component as a percentage value (0.0 .. 100.0). ## # Creates a CMYK color object from percentage values (0.0 .. 100.0). # # ```ruby # Color::CMYK.from_percentage(30, 0, 80, 30) # => CMYK [30.00% 0.00% 80.00% 30.00%] # Color::CMYK.from_values(30, 0, 80, 30) # => CMYK [30.00% 0.00% 80.00% 30.00%] # ``` # # :call-seq: # from_percentage(c, m, y, k) # from_percentage(c:, m:, y:, k:) # from_values(c, m, y, k) # from_values(c:, m:, y:, k:) def self.from_percentage(*args, **kwargs) c, m, y, k = case [args, kwargs] in [[_, _, _, _], {}] args in [[], {c:, m:, y:, k:}] [c, m, y, k] else new(*args, **kwargs) end new(c: c / 100.0, m: m / 100.0, y: y / 100.0, k: k / 100.0) end class << self alias_method :from_values, :from_percentage alias_method :from_fraction, :new alias_method :from_internal, :new # :nodoc: end ## # Creates a CMYK color object from fractional values (0.0 .. 1.0). # # ```ruby # Color::CMYK.from_fraction(0.3, 0, 0.8, 0.3) # => CMYK [30.00% 0.00% 80.00% 30.00%] # Color::CMYK.new(0.3, 0, 0.8, 0.3) # => CMYK [30.00% 0.00% 80.00% 30.00%] # Color::CMYK[c: 0.3, m: 0, y: 0.8, k: 0.3] # => CMYK [30.00% 0.00% 80.00% 30.00%] # ``` def initialize(c:, m:, y:, k:) super(c: normalize(c), m: normalize(m), y: normalize(y), k: normalize(k)) end ## # Output a CSS representation of the CMYK color using `device-cmyk()`. # # If an `alpha` value is provided, it will be included in the output. # # A `fallback` may be provided or included automatically depending on the value # provided, which may be `true`, `false`, a Color object, or a Hash with `:color` and/or # `:alpha` keys. The default value is `true`. # # When `fallback` is: # # - `true`: this CMYK color will be converted to RGB and this will be provided as the # fallback color. If an `alpha` value is provided, it will be used for the fallback. # - `false`: no fallback color will be included. # - a Color object will be used to produce the `fallback` value. # - a Hash will be checked for `:color` and/or `:alpha` keys: # - if `:color` is present, it will be used for the fallback color; if not present, # the CMYK color will be converted to RGB. # - if `:alpha` is present, it will be used for the fallback color; if not present, # the fallback color will be presented _without_ alpha. # # Examples: # # ```ruby # cmyk = Color::CMYK.from_percentage(30, 0, 80, 30) # cmyk.css # # => device-cmyk(30.00% 0 80.00% 30.00%, rgb(49.00% 70.00% 14.00%)) # # cmyk.css(alpha: 0.5) # # => device-cmyk(30.00% 0 80.00% 30.00% / 0.50, rgb(49.00% 70.00% 14.00% / 0.50)) # # cmyk.css(fallback: false) # # => device-cmyk(30.00% 0 80.00% 30.00%) # # cmyk.css(alpha: 0.5, fallback: false) # # => device-cmyk(30.00% 0 80.00% 30.00% / 0.50) # # cmyk.css(fallback: Color::RGB::Blue) # # => device-cmyk(30.00% 0 80.00% 30.00%, rgb(0.00% 0.00% 100.00%)) # # cmyk.css(alpha: 0.5, fallback: Color::RGB::Blue) # # => device-cmyk(30.00% 0 80.00% 30.00% / 0.50, rgb(0.00% 0.00% 100.00% / 0.50)) # # cmyk.css(alpha: 0.5, fallback: { color: Color::RGB::Blue }) # # => device-cmyk(30.00% 0 80.00% 30.00% / 0.50, rgb(0.00% 0.00% 100.00%)) # # cmyk.css(alpha: 0.5, fallback: { color: Color::RGB::Blue, alpha: 0.3 }) # # => device-cmyk(30.00% 0 80.00% 30.00% / 0.50, rgb(0.00% 0.00% 100.00% / 0.30)) # # cmyk.css(alpha: 0.5, fallback: { alpha: 0.3 }) # # => device-cmyk(30.00% 0 80.00% 30.00% / 0.50, rgb(49.00% 70.00% 14.00% / 0.30)) # ``` def css(alpha: nil, fallback: true) if fallback.is_a?(Color) device_cmyk(alpha, fallback, alpha) elsif fallback.is_a?(Hash) device_cmyk(alpha, fallback.fetch(:color) { to_rgb }, fallback[:alpha]) elsif fallback == true device_cmyk(alpha, to_rgb, alpha) else device_cmyk(alpha, nil, nil) end end ## # Coerces the other Color object into CMYK. def coerce(other) = other.to_cmyk ## def to_cmyk(...) = self ## # Converts CMYK to Color::Grayscale. # # There are multiple methods for grayscale conversion, but this implements a variant of # the Adobe PDF conversion method with higher precision: # # ``` # g = 1.0 - min(1.0, 0.299 * c + 0.587 * m + 0.114 * y + k) # ``` # # The default Adobe conversion uses lower precision conversion constants (0.3, 0.59, and # 0.11) instead of the more precise NTSC/YIQ values. def to_grayscale(...) gc = 0.299 * c gm = 0.587 * m gy = 0.114 * y g = 1.0 - [1.0, gc + gm + gy + k].min Color::Grayscale.from_fraction(g) end ## # Converts CMYK to Color::YIQ via Color::RGB. def to_yiq(...) = to_rgb(...).to_yiq(...) ## # Converts CMYK to Color::RGB. # # Most color experts strongly suggest that this is not a good idea (some suggesting that # it's a very bad idea). CMYK represents additive percentages of inks on white paper, # whereas RGB represents mixed color intensities on an unlit (black) screen. # # The color conversion can be done and there are two different methods (standard and # Adobe PDF) that provide slightly different results. Using CMYK [33% 66% 83% 25%], the # standard method provides an approximate RGB color of (128, 65, 33) or #804121. The # Adobe PDF method provides an approximate RGB color of (107, 23, 0) or #6b1700. # # Which is correct? The colors may seem to be drastically different in the RGB color # space, they differ mostly in intensity. The Adobe PDF conversion is a darker, slightly # redder brown; the standard conversion is a lighter brown. Because of this subtlety, # both methods are offered for conversion. The Adobe PDF method is not used by default; # to use it, pass `rgb_method: :adobe` to #to_rgb. # # # Adobe PDF CMYK -> RGB Conversion # r = 1.0 - min(1.0, c + k) # g = 1.0 - min(1.0, m + k) # b = 1.0 - min(1.0, y + k) # # # Standard CMYK -> RGB Conversion # r = 1.0 - (c * (1.0 - k) + k) # g = 1.0 - (m * (1.0 - k) + k) # b = 1.0 - (y * (1.0 - k) + k) # # :call-seq: # to_rgb(rgb_method: :standard) def to_rgb(*args, **kwargs) values = if kwargs[:rgb_method] == :adobe || args.first == :adobe adobe_cmyk_rgb else standard_cmyk_rgb end Color::RGB.from_fraction(*values) end ## # Converts CMYK to Color::HSL via Color::RGB. def to_hsl(...) = to_rgb(...).to_hsl(...) ## # Converts CMYK to Color::CIELAB via Color::RGB. def to_lab(...) = to_rgb(...).to_lab(...) ## # Converts CMYK to Color::XYZ via Color::RGB. def to_xyz(...) = to_rgb(...).to_xyz(...) ## def inspect = "CMYK [%.2f%% %.2f%% %.2f%% %.2f%%]" % [cyan, magenta, yellow, key] # :nodoc: ## def pretty_print(q) # :nodoc: q.text "CMYK" q.breakable q.group 2, "[", "]" do q.text ".2f%%" % cyan q.fill_breakable q.text ".2f%%" % magenta q.fill_breakable q.text ".2f%%" % yellow q.fill_breakable q.text ".2f%%" % key q.fill_breakable end end ## def cyan = c * 100.0 # :nodoc: ## def magenta = m * 100.0 # :nodoc: ## def yellow = y * 100.0 # :nodoc: ## alias_method :b, :k # :nodoc: ## def key = k * 100.0 # :nodoc: ## alias_method :black, :key # :nodoc: ## def to_a = [cyan, magenta, yellow, key] # :nodoc: ## alias_method :deconstruct, :to_a # :nodoc: ## def deconstruct_keys(_keys) = {c:, m:, y:, k:, cyan:, magenta:, yellow:, key:} # :nodoc: ## def to_internal = [c, m, y, k] # :nodoc: ## def components = 4 # :nodoc: private ## # Implements the Adobe PDF conversion of CMYK to RGB. def adobe_cmyk_rgb = [c, m, y].map { 1.0 - [1.0, _1 + k].min } # :nodoc: ## # Implements the standard conversion of CMYK to RGB. def standard_cmyk_rgb = [c, m, y].map { 1.0 - (_1 * (1.0 - k) + k) } # :nodoc: ## def device_cmyk(alpha, fallback, fallback_alpha) # :nodoc: params = [ css_value(cyan, :percent), css_value(magenta, :percent), css_value(yellow, :percent), css_value(key, :percent) ].join(" ") params = "#{params} / #{css_value(alpha)}" if alpha fallback = fallback&.css(alpha: fallback_alpha) if fallback "device-cmyk(#{params}, #{fallback})" else "device-cmyk(#{params})" end end end color-2.1.1/test/0000755000004100000410000000000015050641176013645 5ustar www-datawww-datacolor-2.1.1/test/minitest_helper.rb0000644000004100000410000000070015050641176017362 0ustar www-datawww-data# frozen_string_literal: true require "color" require "color/rgb/colors" gem "minitest" require "minitest/autorun" require "minitest/focus" if ENV["STRICT"] $VERBOSE = true Warning[:deprecated] = true require "minitest/error_on_warning" end module Minitest::ColorExtensions def assert_in_tolerance(expected, actual, msg = nil) assert_in_delta expected, actual, Color::TOLERANCE, msg end Minitest::Test.send(:include, self) end color-2.1.1/test/test_color.rb0000644000004100000410000000567315050641176016362 0ustar www-datawww-data# frozen_string_literal: true require "color" require "minitest_helper" module TestColor class TestColor < Minitest::Test def setup @subject = Object.new.extend(Color) end # def test_equivalent # assert Color.equivalent?(Color::RGB::Red, Color::HSL.from_values(0, 100, 50)) # refute Color.equivalent?(Color::RGB::Red, nil) # refute Color.equivalent?(nil, Color::RGB::Red) # end def test_normalize normalize = @subject.method(:normalize) (1..10).each do |i| assert_equal(0.0, normalize.call(-7 * i)) assert_equal(0.0, normalize.call(-7 / i)) assert_equal(0.0, normalize.call(0 - i)) assert_equal(1.0, normalize.call(255 + i)) assert_equal(1.0, normalize.call(256 * i)) assert_equal(1.0, normalize.call(65536 / i)) end (0..255).each do |i| assert_in_delta(i / 255.0, normalize.call(i / 255.0), 1e-2) end end def test_normalize_byte normalize_byte = @subject.method(:normalize_byte) assert_equal(0, normalize_byte.call(-1)) assert_equal(0, normalize_byte.call(0)) assert_equal(127, normalize_byte.call(127)) assert_equal(172, normalize_byte.call(172)) assert_equal(255, normalize_byte.call(255)) assert_equal(255, normalize_byte.call(256)) end def test_normalize_word normalize_word = @subject.method(:normalize_word) assert_equal(0, normalize_word.call(-1)) assert_equal(0, normalize_word.call(0)) assert_equal(127, normalize_word.call(127)) assert_equal(172, normalize_word.call(172)) assert_equal(255, normalize_word.call(255)) assert_equal(256, normalize_word.call(256)) assert_equal(65535, normalize_word.call(65535)) assert_equal(65535, normalize_word.call(66536)) end def test_normalize_range normalize_to_range = @subject.method(:normalize_to_range) assert_equal(-100, normalize_to_range.call(-101, -100..100)) assert_equal(-100, normalize_to_range.call(-100.5, -100..100)) assert_equal(-100, normalize_to_range.call(-100, -100..100)) assert_equal(-100, normalize_to_range.call(-100.0, -100..100)) assert_equal(-99.5, normalize_to_range.call(-99.5, -100..100)) assert_equal(-50, normalize_to_range.call(-50, -100..100)) assert_equal(-50.5, normalize_to_range.call(-50.5, -100..100)) assert_equal(0, normalize_to_range.call(0, -100..100)) assert_equal(50, normalize_to_range.call(50, -100..100)) assert_equal(50.5, normalize_to_range.call(50.5, -100..100)) assert_equal(99, normalize_to_range.call(99, -100..100)) assert_equal(99.5, normalize_to_range.call(99.5, -100..100)) assert_equal(100, normalize_to_range.call(100, -100..100)) assert_equal(100, normalize_to_range.call(100.0, -100..100)) assert_equal(100, normalize_to_range.call(100.5, -100..100)) assert_equal(100, normalize_to_range.call(101, -100..100)) end end end color-2.1.1/test/fixtures/0000755000004100000410000000000015050641176015516 5ustar www-datawww-datacolor-2.1.1/test/fixtures/cielab.json0000644000004100000410000001525615050641176017641 0ustar www-datawww-data[ { "c1": { "L": "50.0000", "a": "2.6772", "b": "-79.7751" }, "c2": { "L": "50.0000", "a": "0.0000", "b": "-82.7485" }, "∂E2000": "2.0425" }, { "c1": { "L": "50.0000", "a": "3.1571", "b": "-77.2803" }, "c2": { "L": "50.0000", "a": "0.0000", "b": "-82.7485" }, "∂E2000": "2.8615" }, { "c1": { "L": "50.0000", "a": "2.8361", "b": "-74.0200" }, "c2": { "L": "50.0000", "a": "0.0000", "b": "-82.7485" }, "∂E2000": "3.4412" }, { "c1": { "L": "50.0000", "a": "-1.3802", "b": "-84.2814" }, "c2": { "L": "50.0000", "a": "0.0000", "b": "-82.7485" }, "∂E2000": "1.0000" }, { "c1": { "L": "50.0000", "a": "-1.1848", "b": "-84.8006" }, "c2": { "L": "50.0000", "a": "0.0000", "b": "-82.7485" }, "∂E2000": "1.0000" }, { "c1": { "L": "50.0000", "a": "-0.9009", "b": "-85.5211" }, "c2": { "L": "50.0000", "a": "0.0000", "b": "-82.7485" }, "∂E2000": "1.0000" }, { "c1": { "L": "50.0000", "a": "0.0000", "b": "0.0000" }, "c2": { "L": "50.0000", "a": "-1.0000", "b": "2.0000" }, "∂E2000": "2.3669" }, { "c1": { "L": "50.0000", "a": "-1.0000", "b": "2.0000" }, "c2": { "L": "50.0000", "a": "0.0000", "b": "0.0000" }, "∂E2000": "2.3669" }, { "c1": { "L": "50.0000", "a": "2.4900", "b": "-0.0010" }, "c2": { "L": "50.0000", "a": "-2.4900", "b": "0.0009" }, "∂E2000": "7.1792" }, { "c1": { "L": "50.0000", "a": "2.4900", "b": "-0.0010" }, "c2": { "L": "50.0000", "a": "-2.4900", "b": "0.0010" }, "∂E2000": "7.1792" }, { "c1": { "L": "50.0000", "a": "2.4900", "b": "-0.0010" }, "c2": { "L": "50.0000", "a": "-2.4900", "b": "0.0011" }, "∂E2000": "7.2195" }, { "c1": { "L": "50.0000", "a": "2.4900", "b": "-0.0010" }, "c2": { "L": "50.0000", "a": "-2.4900", "b": "0.0012" }, "∂E2000": "7.2195" }, { "c1": { "L": "50.0000", "a": "-0.0010", "b": "2.4900" }, "c2": { "L": "50.0000", "a": "0.0009", "b": "-2.4900" }, "∂E2000": "4.8045" }, { "c1": { "L": "50.0000", "a": "-0.0010", "b": "2.4900" }, "c2": { "L": "50.0000", "a": "0.0010", "b": "-2.4900" }, "∂E2000": "4.8045" }, { "c1": { "L": "50.0000", "a": "-0.0010", "b": "2.4900" }, "c2": { "L": "50.0000", "a": "0.0011", "b": "-2.4900" }, "∂E2000": "4.7461" }, { "c1": { "L": "50.0000", "a": "2.5000", "b": "0.0000" }, "c2": { "L": "50.0000", "a": "0.0000", "b": "-2.5000" }, "∂E2000": "4.3065" }, { "c1": { "L": "50.0000", "a": "2.5000", "b": "0.0000" }, "c2": { "L": "73.0000", "a": "25.0000", "b": "-18.0000" }, "∂E2000": "27.1492" }, { "c1": { "L": "50.0000", "a": "2.5000", "b": "0.0000" }, "c2": { "L": "61.0000", "a": "-5.0000", "b": "29.0000" }, "∂E2000": "22.8977" }, { "c1": { "L": "50.0000", "a": "2.5000", "b": "0.0000" }, "c2": { "L": "56.0000", "a": "-27.0000", "b": "-3.0000" }, "∂E2000": "31.9030" }, { "c1": { "L": "50.0000", "a": "2.5000", "b": "0.0000" }, "c2": { "L": "58.0000", "a": "24.0000", "b": "15.0000" }, "∂E2000": "19.4535" }, { "c1": { "L": "50.0000", "a": "2.5000", "b": "0.0000" }, "c2": { "L": "50.0000", "a": "3.1736", "b": "0.5854" }, "∂E2000": "1.0000" }, { "c1": { "L": "50.0000", "a": "2.5000", "b": "0.0000" }, "c2": { "L": "50.0000", "a": "3.2972", "b": "0.0000" }, "∂E2000": "1.0000" }, { "c1": { "L": "50.0000", "a": "2.5000", "b": "0.0000" }, "c2": { "L": "50.0000", "a": "1.8634", "b": "0.5757" }, "∂E2000": "1.0000" }, { "c1": { "L": "50.0000", "a": "2.5000", "b": "0.0000" }, "c2": { "L": "50.0000", "a": "3.2592", "b": "0.3350" }, "∂E2000": "1.0000" }, { "c1": { "L": "60.2574", "a": "-34.0099", "b": "36.2677" }, "c2": { "L": "60.4626", "a": "-34.1751", "b": "39.4387" }, "∂E2000": "1.2644" }, { "c1": { "L": "63.0109", "a": "-31.0961", "b": "-5.8663" }, "c2": { "L": "62.8187", "a": "-29.7946", "b": "-4.0864" }, "∂E2000": "1.2630" }, { "c1": { "L": "61.2901", "a": "3.7196", "b": "-5.3901" }, "c2": { "L": "61.4292", "a": "2.2480", "b": "-4.9620" }, "∂E2000": "1.8731" }, { "c1": { "L": "35.0831", "a": "-44.1164", "b": "3.7933" }, "c2": { "L": "35.0232", "a": "-40.0716", "b": "1.5901" }, "∂E2000": "1.8645" }, { "c1": { "L": "22.7233", "a": "20.0904", "b": "-46.6940" }, "c2": { "L": "23.0331", "a": "14.9730", "b": "-42.5619" }, "∂E2000": "2.0373" }, { "c1": { "L": "36.4612", "a": "47.8580", "b": "18.3852" }, "c2": { "L": "36.2715", "a": "50.5065", "b": "21.2231" }, "∂E2000": "1.4146" }, { "c1": { "L": "90.8027", "a": "-2.0831", "b": "1.4410" }, "c2": { "L": "91.1528", "a": "-1.6435", "b": "0.0447" }, "∂E2000": "1.4441" }, { "c1": { "L": "90.9257", "a": "-0.5406", "b": "-0.9208" }, "c2": { "L": "88.6381", "a": "-0.8985", "b": "-0.7239" }, "∂E2000": "1.5381" }, { "c1": { "L": "6.7747", "a": "-0.2908", "b": "-2.4247" }, "c2": { "L": "5.8714", "a": "-0.0985", "b": "-2.2286" }, "∂E2000": "0.6377" }, { "c1": { "L": "2.0776", "a": "0.0795", "b": "-1.1350" }, "c2": { "L": "0.9033", "a": "-0.0636", "b": "-0.5514" }, "∂E2000": "0.9082" } ] color-2.1.1/test/test_yiq.rb0000644000004100000410000000154215050641176016035 0ustar www-datawww-data# frozen_string_literal: true require "color" require "minitest_helper" module TestColor class TestYIQ < Minitest::Test def setup @yiq = Color::YIQ.from_fraction(0.1, 0.2, 0.3) end def test_brightness assert_in_tolerance(0.1, @yiq.brightness) end def test_i assert_in_tolerance(0.2, @yiq.i) assert_in_tolerance(0.2, @yiq.i) end def test_q assert_in_tolerance(0.3, @yiq.q) assert_in_tolerance(0.3, @yiq.q) end def test_to_grayscale assert_equal(Color::Grayscale.from_fraction(0.1), @yiq.to_grayscale) end def test_to_yiq assert_equal(@yiq, @yiq.to_yiq) end def test_y assert_in_tolerance(0.1, @yiq.y) assert_in_tolerance(0.1, @yiq.y) end def test_inspect assert_equal("YIQ [10.00% 20.00% 30.00%]", @yiq.inspect) end end end color-2.1.1/test/test_rgb.rb0000644000004100000410000003603015050641176016005 0ustar www-datawww-data# frozen_string_literal: true require "color" require "json" require "minitest_helper" module TestColor class TestRGB < Minitest::Test def test_adjust_brightness assert_equal("#1a1aff", Color::RGB::Blue.adjust_brightness(10).html) assert_equal("#0000e6", Color::RGB::Blue.adjust_brightness(-10).html) end def test_adjust_hue assert_equal("#6600ff", Color::RGB::Blue.adjust_hue(10).html) assert_equal("#0066ff", Color::RGB::Blue.adjust_hue(-10).html) end def test_adjust_saturation assert_equal("#ef9374", Color::RGB::DarkSalmon.adjust_saturation(10).html) assert_equal("#e39980", Color::RGB::DarkSalmon.adjust_saturation(-10).html) end def test_red red = Color::RGB::Red assert_in_tolerance(1.0, red.r) assert_in_tolerance(100, red.red_p) assert_in_tolerance(255, red.red) assert_in_tolerance(1.0, red.r) end def test_green lime = Color::RGB::Lime assert_in_tolerance(1.0, lime.g) assert_in_tolerance(100, lime.green_p) assert_in_tolerance(255, lime.green) end def test_blue blue = Color::RGB::Blue assert_in_tolerance(1.0, blue.b) assert_in_tolerance(255, blue.blue) assert_in_tolerance(100, blue.blue_p) end def test_brightness assert_in_tolerance(0.0, Color::RGB::Black.brightness) assert_in_tolerance(0.5, Color::RGB::Grey50.brightness) assert_in_tolerance(1.0, Color::RGB::White.brightness) end def test_darken_by assert_in_tolerance(0.5, Color::RGB::Blue.darken_by(50).b) end def test_html assert_equal("#000000", Color::RGB::Black.html) assert_equal(Color::RGB::Black, Color::RGB.from_html("#000000")) assert_equal("#0000ff", Color::RGB::Blue.html) assert_equal("#00ff00", Color::RGB::Lime.html) assert_equal("#ff0000", Color::RGB::Red.html) assert_equal("#ffffff", Color::RGB::White.html) end def test_css assert_equal("rgb(0 0 0)", Color::RGB::Black.css) assert_equal("rgb(0 0 100.00%)", Color::RGB::Blue.css) assert_equal("rgb(0 100.00% 0)", Color::RGB::Lime.css) assert_equal("rgb(100.00% 0 0)", Color::RGB::Red.css) assert_equal("rgb(100.00% 100.00% 100.00%)", Color::RGB::White.css) assert_equal("rgb(0 0 0 / 1.00)", Color::RGB::Black.css(alpha: 1)) assert_equal("rgb(0 0 100.00% / 1.00)", Color::RGB::Blue.css(alpha: 1)) assert_equal("rgb(0 100.00% 0 / 1.00)", Color::RGB::Lime.css(alpha: 1)) assert_equal("rgb(100.00% 0 0 / 1.00)", Color::RGB::Red.css(alpha: 1)) assert_equal("rgb(100.00% 100.00% 100.00% / 1.00)", Color::RGB::White.css(alpha: 1)) assert_equal("rgb(100.00% 0 0 / 0.50)", Color::RGB::Red.css(alpha: 0.5)) end def test_lighten_by assert_in_tolerance(1.0, Color::RGB::Blue.lighten_by(50).b) assert_in_tolerance(0.5, Color::RGB::Blue.lighten_by(50).r) assert_in_tolerance(0.5, Color::RGB::Blue.lighten_by(50).g) end def test_mix_with assert_in_tolerance(0.5, Color::RGB::Red.mix_with(Color::RGB::Blue, 50).r) assert_in_tolerance(0.0, Color::RGB::Red.mix_with(Color::RGB::Blue, 50).g) assert_in_tolerance(0.5, Color::RGB::Red.mix_with(Color::RGB::Blue, 50).b) assert_in_tolerance(0.5, Color::RGB::Blue.mix_with(Color::RGB::Red, 50).r) assert_in_tolerance(0.0, Color::RGB::Blue.mix_with(Color::RGB::Red, 50).g) assert_in_tolerance(0.5, Color::RGB::Blue.mix_with(Color::RGB::Red, 50).b) end def test_to_cmyk assert_kind_of(Color::CMYK, Color::RGB::Black.to_cmyk) assert_equal(Color::CMYK.from_percentage(0, 0, 0, 100), Color::RGB::Black.to_cmyk) assert_equal(Color::CMYK.from_percentage(0, 0, 100, 0), Color::RGB::Yellow.to_cmyk) assert_equal(Color::CMYK.from_percentage(100, 0, 0, 0), Color::RGB::Cyan.to_cmyk) assert_equal(Color::CMYK.from_percentage(0, 100, 0, 0), Color::RGB::Magenta.to_cmyk) assert_equal(Color::CMYK.from_percentage(0, 100, 100, 0), Color::RGB::Red.to_cmyk) assert_equal(Color::CMYK.from_percentage(100, 0, 100, 0), Color::RGB::Lime.to_cmyk) assert_equal(Color::CMYK.from_percentage(100, 100, 0, 0), Color::RGB::Blue.to_cmyk) assert_equal(Color::CMYK.from_percentage(10.32, 60.52, 10.32, 39.47), Color::RGB::Purple.to_cmyk) assert_equal(Color::CMYK.from_percentage(10.90, 59.13, 59.13, 24.39), Color::RGB::Brown.to_cmyk) assert_equal(Color::CMYK.from_percentage(0, 63.14, 18.43, 0), Color::RGB::Carnation.to_cmyk) assert_equal(Color::CMYK.from_percentage(7.39, 62.69, 62.69, 37.32), Color::RGB::Cayenne.to_cmyk) end def test_to_grayscale assert_kind_of(Color::Grayscale, Color::RGB::Black.to_grayscale) assert_equal(Color::Grayscale.from_fraction(0), Color::RGB::Black.to_grayscale) assert_equal(Color::Grayscale.from_fraction(0.5), Color::RGB::Yellow.to_grayscale) assert_equal(Color::Grayscale.from_fraction(0.5), Color::RGB::Cyan.to_grayscale) assert_equal(Color::Grayscale.from_fraction(0.5), Color::RGB::Magenta.to_grayscale) assert_equal(Color::Grayscale.from_fraction(0.5), Color::RGB::Red.to_grayscale) assert_equal(Color::Grayscale.from_fraction(0.5), Color::RGB::Lime.to_grayscale) assert_equal(Color::Grayscale.from_fraction(0.5), Color::RGB::Blue.to_grayscale) assert_equal(Color::Grayscale.from_fraction(0.2510), Color::RGB::Purple.to_grayscale) assert_equal(Color::Grayscale.from_percentage(40.58), Color::RGB::Brown.to_grayscale) assert_equal(Color::Grayscale.from_percentage(68.43), Color::RGB::Carnation.to_grayscale) assert_equal(Color::Grayscale.from_percentage(27.65), Color::RGB::Cayenne.to_grayscale) end def test_to_hsl assert_kind_of(Color::HSL, Color::RGB::Black.to_hsl) assert_equal(Color::HSL.from_values(0, 0, 0), Color::RGB::Black.to_hsl) assert_equal(Color::HSL.from_values(60, 100, 50), Color::RGB::Yellow.to_hsl) assert_equal(Color::HSL.from_values(180, 100, 50), Color::RGB::Cyan.to_hsl) assert_equal(Color::HSL.from_values(300, 100, 50), Color::RGB::Magenta.to_hsl) assert_equal(Color::HSL.from_values(0, 100, 50), Color::RGB::Red.to_hsl) assert_equal(Color::HSL.from_values(120, 100, 50), Color::RGB::Lime.to_hsl) assert_equal(Color::HSL.from_values(240, 100, 50), Color::RGB::Blue.to_hsl) assert_equal(Color::HSL.from_values(300, 100, 25.10), Color::RGB::Purple.to_hsl) assert_equal(Color::HSL.from_values(0, 59.42, 40.59), Color::RGB::Brown.to_hsl) assert_equal(Color::HSL.from_values(317.5, 100, 68.43), Color::RGB::Carnation.to_hsl) assert_equal(Color::HSL.from_values(0, 100, 27.64), Color::RGB::Cayenne.to_hsl) # The following tests a bug reported by Jean Krohn on 10 June 2006 where HSL # conversion was not quite correct, resulting in a bad round-trip. assert_equal("RGB [#008800]", Color::RGB.from_html("#008800").to_hsl.to_rgb.inspect) refute_equal("RGB [#002288]", Color::RGB.from_html("#008800").to_hsl.to_rgb.inspect) # The following tests a bug reported by Adam Johnson on 29 October # 2010. hsl = Color::HSL.from_values(262, 67, 42) c = Color::RGB.from_fraction(0.34496, 0.1386, 0.701399).to_hsl assert_in_tolerance hsl.h, c.h, "Hue" assert_in_tolerance hsl.s, c.s, "Saturation" assert_in_tolerance hsl.l, c.l, "Luminance" end def test_to_rgb assert_same(Color::RGB::Black, Color::RGB::Black.to_rgb) end def test_to_yiq assert_kind_of(Color::YIQ, Color::RGB::Black.to_yiq) assert_equal(Color::YIQ.from_values(0, 0, 0), Color::RGB::Black.to_yiq) assert_equal(Color::YIQ.from_values(88.6, 32.1, 0), Color::RGB::Yellow.to_yiq) assert_equal(Color::YIQ.from_values(70.1, 0, 0), Color::RGB::Cyan.to_yiq) assert_equal(Color::YIQ.from_values(41.3, 27.5, 52.3), Color::RGB::Magenta.to_yiq) assert_equal(Color::YIQ.from_values(29.9, 59.6, 21.2), Color::RGB::Red.to_yiq) assert_equal(Color::YIQ.from_values(58.7, 0, 0), Color::RGB::Lime.to_yiq) assert_equal(Color::YIQ.from_values(11.4, 0, 31.1), Color::RGB::Blue.to_yiq) assert_equal(Color::YIQ.from_values(20.73, 13.80, 26.25), Color::RGB::Purple.to_yiq) assert_equal(Color::YIQ.from_values(30.89, 28.75, 10.23), Color::RGB::Brown.to_yiq) assert_equal(Color::YIQ.from_values(60.84, 23.28, 27.29), Color::RGB::Carnation.to_yiq) assert_equal(Color::YIQ.from_values(16.53, 32.96, 11.72), Color::RGB::Cayenne.to_yiq) end def test_to_lab # Luminosity can be an absolute. assert_in_tolerance(0.0, Color::RGB::Black.to_lab.l) assert_in_tolerance(100.0, Color::RGB::White.to_lab.l) # It's not really possible to have absolute # numbers here because of how L*a*b* works, but # negative/positive comparisons work assert(Color::RGB::Green.to_lab.a < 0) assert(Color::RGB::Magenta.to_lab.a > 0) assert(Color::RGB::Blue.to_lab.b < 0) assert(Color::RGB::Yellow.to_lab.b > 0) end def test_closest_match # It should match Blue to Indigo (very simple match) match_from = [Color::RGB::Red, Color::RGB::Green, Color::RGB::Blue] assert_equal(Color::RGB::Blue, Color::RGB::Indigo.closest_match(match_from)) # But fails if using the :just_noticeable difference. assert_nil(Color::RGB::Indigo.closest_match(match_from, threshold_distance: :just_noticeable)) # Crimson & Firebrick are visually closer than DarkRed and Firebrick # (more precise match) match_from += [Color::RGB::DarkRed, Color::RGB::Crimson] assert_equal(Color::RGB::Crimson, Color::RGB::Firebrick.closest_match(match_from)) # Specifying a threshold low enough will cause even that match to fail, though. assert_nil(Color::RGB::Firebrick.closest_match(match_from, threshold_distance: 8.0)) # If the match_from list is an empty array, it also returns nil assert_nil(Color::RGB::Red.closest_match([])) # RGB::Green is 0,128,0, so we'll pick something VERY close to it, visually jnd_green = Color::RGB.from_values(3, 132, 3) assert_equal(Color::RGB::Green, jnd_green.closest_match(match_from, threshold_distance: :jnd)) # And then something that's just barely out of the tolerance range diff_green = Color::RGB.from_values(9, 142, 9) assert_nil(diff_green.closest_match(match_from, threshold_distance: :jnd)) end def test_mean_grayscale c1 = Color::RGB.from_values(0x85, 0x80, 0x00) c1.max_rgb_as_grayscale c1_max = c1.max_rgb_as_grayscale c1_result = Color::Grayscale.from_fraction(0x85 / 255.0) assert_equal(c1_result, c1_max) end def test_from_html assert_equal("RGB [#333333]", Color::RGB.from_html("#333").inspect) assert_equal("RGB [#333333]", Color::RGB.from_html("333").inspect) assert_equal("RGB [#555555]", Color::RGB.from_html("#555555").inspect) assert_equal("RGB [#555555]", Color::RGB.from_html("555555").inspect) assert_raises(ArgumentError) { Color::RGB.from_html("#5555555") } assert_raises(ArgumentError) { Color::RGB.from_html("5555555") } assert_raises(ArgumentError) { Color::RGB.from_html("#55555") } assert_raises(ArgumentError) { Color::RGB.from_html("55555") } end def test_by_hex assert_same(Color::RGB::Cyan, Color::RGB.by_hex("#0ff")) assert_same(Color::RGB::Cyan, Color::RGB.by_hex("#00ffff")) assert_equal("RGB [#333333]", Color::RGB.by_hex("#333").inspect) assert_equal("RGB [#333333]", Color::RGB.by_hex("333").inspect) assert_raises(ArgumentError) { Color::RGB.by_hex("5555555") } assert_raises(ArgumentError) { Color::RGB.by_hex("#55555") } end def test_by_name assert_same(Color::RGB::Cyan, Color::RGB.by_name("cyan")) fetch_error = if RUBY_VERSION < "1.9" IndexError else KeyError end assert_raises(fetch_error) { Color::RGB.by_name("cyanide") } assert_equal(:boom, Color::RGB.by_name("cyanide") { :boom }) end def test_by_css assert_same(Color::RGB::Cyan, Color::RGB.by_css("#0ff")) assert_same(Color::RGB::Cyan, Color::RGB.by_css("cyan")) assert_raises(ArgumentError) { Color::RGB.by_css("cyanide") } end def test_extract_colors assert_equal([Color::RGB::BlanchedAlmond, Color::RGB::Cyan], Color::RGB.extract_colors("BlanchedAlmond is a nice shade, but #00ffff is not.")) end def test_inspect assert_equal("RGB [#000000]", Color::RGB::Black.inspect) assert_equal("RGB [#0000ff]", Color::RGB::Blue.inspect) assert_equal("RGB [#00ff00]", Color::RGB::Lime.inspect) assert_equal("RGB [#ff0000]", Color::RGB::Red.inspect) assert_equal("RGB [#ffffff]", Color::RGB::White.inspect) end def test_delta_e2000 # test data: # http://www.ece.rochester.edu/~gsharma/ciede2000/ # http://www.ece.rochester.edu/~gsharma/ciede2000/dataNprograms/CIEDE2000.xls # this will also test to_degrees and to_radians by association test_data = JSON.parse(File.read("test/fixtures/cielab.json")).map.with_index { |e, i| { i: i, c1: Color::CIELAB.from_values(e["c1"]["L"].to_f, e["c1"]["a"].to_f, e["c1"]["b"].to_f), c2: Color::CIELAB.from_values(e["c2"]["L"].to_f, e["c2"]["a"].to_f, e["c2"]["b"].to_f), correct_delta: e["∂E2000"].to_f } } test_data.each do |e| e => c1:, c2:, correct_delta: e2000_c1_c2 = c1.delta_e2000(c2) e2000_c2_c1 = c2.delta_e2000(c1) assert_in_tolerance e2000_c1_c2, e2000_c2_c1 assert_in_tolerance e2000_c1_c2, correct_delta end end def test_contrast data = [ [[171, 215, 103], [195, 108, 197], 0.18117], [[223, 133, 234], [64, 160, 101], 0.229530], [[7, 30, 49], [37, 225, 31], 0.377786], [[65, 119, 130], [70, 63, 212], 0.10323], [[211, 77, 232], [5, 113, 139], 0.233503], [[166, 185, 41], [87, 193, 137], 0.07275], [[1, 120, 37], [195, 70, 33], 0.1474640], [[99, 206, 21], [228, 204, 155], 0.22611], [[15, 18, 41], [90, 202, 208], 0.552434] ] data.each do |(c1, c2, value)| c1 = Color::RGB.from_values(c1[0], c1[1], c1[2]) c2 = Color::RGB.from_values(c2[0], c2[1], c2[2]) assert_in_delta(0.0001, c1.contrast(c2), value) assert_equal(c1.contrast(c2), c2.contrast(c1)) end end def test_fix_45_invalid_rgb_to_lab assert_equal( Color::CIELAB[56.2562, 88.1033, -18.8203], Color::RGB.by_hex("#ff00aa").to_lab ) assert_equal("#ff00aa", Color::RGB.by_hex("#ff00aa").to_lab.to_rgb.html) end # # An RGB color round-tripped through CIELAB should still have more or less the same # # RGB values, but this doesn't really work because the color math here is slightly # # wrong. # def test_to_lab_automated # 10.times do |t| # c1 = Color::RGB.from_values(rand(256), rand(256), rand(256)) # l1 = c1.to_lab # c2 = l1.to_rgb # # assert_in_tolerance(c1.r, c2.r) # assert_in_tolerance(c1.g, c2.g) # assert_in_tolerance(c1.b, c2.b) # end # end end end color-2.1.1/test/test_hsl.rb0000644000004100000410000000747215050641176016031 0ustar www-datawww-data# frozen_string_literal: true require "color" require "minitest_helper" module TestColor class TestHSL < Minitest::Test def setup @hsl = Color::HSL.from_values(145, 20, 30) end def test_rgb_roundtrip_conversion hsl = Color::HSL.from_values(262, 67, 42) c = hsl.to_rgb.to_hsl assert_in_tolerance hsl.h, c.h, "Hue" assert_in_tolerance hsl.s, c.s, "Saturation" assert_in_tolerance hsl.l, c.l, "Luminance" end def test_brightness assert_in_tolerance 0.3, @hsl.brightness end def test_hue assert_in_tolerance 0.4027, @hsl.h assert_in_tolerance 145, @hsl.hue # @hsl.hue = 33 # assert_in_tolerance 0.09167, @hsl.h # @hsl.hue = -33 # assert_in_tolerance 0.90833, @hsl.h # @hsl.h = 3.3 # assert_in_tolerance 360, @hsl.hue # @hsl.h = -3.3 # assert_in_tolerance 0.0, @hsl.h # @hsl.hue = 0 # @hsl.hue -= 20 # assert_in_tolerance 340, @hsl.hue # @hsl.hue += 45 # assert_in_tolerance 25, @hsl.hue end def test_saturation assert_in_tolerance 0.2, @hsl.s assert_in_tolerance 20, @hsl.saturation # @hsl.saturation = 33 # assert_in_tolerance 0.33, @hsl.s # @hsl.s = 3.3 # assert_in_tolerance 100, @hsl.saturation # @hsl.s = -3.3 # assert_in_tolerance 0.0, @hsl.s end def test_luminance assert_in_tolerance 0.3, @hsl.l assert_in_tolerance 30, @hsl.luminosity # @hsl.luminosity = 33 # assert_in_tolerance 0.33, @hsl.l # @hsl.l = 3.3 # assert_in_tolerance 100, @hsl.lightness # @hsl.l = -3.3 # assert_in_tolerance 0.0, @hsl.l end def test_css assert_equal "hsl(145.00deg 20.00% 30.00%)", @hsl.css assert_equal "hsl(145.00deg 20.00% 30.00% / 1.00)", @hsl.css(alpha: 1) end def test_to_cmyk cmyk = @hsl.to_cmyk assert_kind_of Color::CMYK, cmyk assert_in_tolerance 0.3223, cmyk.c assert_in_tolerance 0.2023, cmyk.m assert_in_tolerance 0.2723, cmyk.y assert_in_tolerance 0.4377, cmyk.k end def test_to_grayscale gs = @hsl.to_grayscale assert_kind_of Color::Grayscale, gs assert_in_tolerance 30, gs.gray end def test_to_rgb rgb = @hsl.to_rgb assert_kind_of Color::RGB, rgb assert_in_tolerance 0.24, rgb.r assert_in_tolerance 0.36, rgb.g assert_in_tolerance 0.29, rgb.b # The following tests address a bug reported by Jean Krohn on June 6, # 2006 and exercise some previously unexercised code in to_rgb. assert_equal Color::RGB::Black, Color::HSL.from_values(75, 75, 0) assert_equal Color::RGB::White, Color::HSL.from_values(75, 75, 100) assert_equal Color::RGB::Gray80, Color::HSL.from_values(75, 0, 80) # The following tests a bug reported by Adam Johnson on 29 October # 2010. rgb = Color::RGB.from_fraction(0.34496, 0.1386, 0.701399) c = Color::HSL.from_values(262, 67, 42).to_rgb assert_in_tolerance rgb.r, c.r, "Red" assert_in_tolerance rgb.g, c.g, "Green" assert_in_tolerance rgb.b, c.b, "Blue" end def test_to_yiq yiq = @hsl.to_yiq assert_kind_of Color::YIQ, yiq assert_in_tolerance 0.3161, yiq.y assert_in_tolerance 0.0, yiq.i assert_in_tolerance 0.0, yiq.q end def test_mix_with red = Color::RGB::Red.to_hsl yellow = Color::RGB::Yellow.to_hsl assert_in_tolerance 0, red.hue assert_in_tolerance 60, yellow.hue ry25 = red.mix_with yellow, 0.25 assert_in_tolerance 15, ry25.hue ry50 = red.mix_with yellow, 0.50 assert_in_tolerance 30, ry50.hue ry75 = red.mix_with yellow, 0.75 assert_in_tolerance 45, ry75.hue end def test_inspect assert_equal "HSL [145.00deg 20.00% 30.00%]", @hsl.inspect end end end color-2.1.1/test/test_grayscale.rb0000644000004100000410000000407115050641176017205 0ustar www-datawww-data# frozen_string_literal: true require "color" require "minitest_helper" module TestColor class TestGrayscale < Minitest::Test def setup @gs = Color::Grayscale.from_percentage(33) end def test_brightness assert_in_tolerance(0.33, @gs.brightness) end def test_darken_by assert_in_tolerance(29.7, @gs.darken_by(10).gray) end def test_g assert_in_tolerance(0.33, @gs.g) assert_in_tolerance(33, @gs.gray) # @gs.gray = 40 # assert_in_tolerance(0.4, @gs.g) # @gs.g = 2.0 # assert_in_tolerance(100, @gs.gray) # @gs.gray = -2.0 # assert_in_tolerance(0.0, @gs.g) end def test_css assert_equal("#545454", @gs.html) assert_equal("rgb(33.00% 33.00% 33.00%)", @gs.css) assert_equal("rgb(33.00% 33.00% 33.00% / 1.00)", @gs.css(alpha: 1)) assert_equal("rgb(33.00% 33.00% 33.00% / 0.20)", @gs.css(alpha: 0.2)) end def test_lighten_by assert_in_tolerance(0.363, @gs.lighten_by(10).g) end def test_to_cmyk cmyk = @gs.to_cmyk assert_kind_of(Color::CMYK, cmyk) assert_in_tolerance(0.0, cmyk.c) assert_in_tolerance(0.0, cmyk.m) assert_in_tolerance(0.0, cmyk.y) assert_in_tolerance(0.67, cmyk.k) end def test_to_grayscale assert_equal(@gs, @gs.to_grayscale) assert_equal(@gs, @gs.to_grayscale) end def test_to_hsl hsl = @gs.to_hsl assert_kind_of(Color::HSL, hsl) assert_in_tolerance(0.0, hsl.h) assert_in_tolerance(0.0, hsl.s) assert_in_tolerance(0.33, hsl.l) end def test_to_rgb rgb = @gs.to_rgb assert_kind_of(Color::RGB, rgb) assert_in_tolerance(0.33, rgb.r) assert_in_tolerance(0.33, rgb.g) assert_in_tolerance(0.33, rgb.b) end def test_to_yiq yiq = @gs.to_yiq assert_kind_of(Color::YIQ, yiq) assert_in_tolerance(0.33, yiq.y) assert_in_tolerance(0.0, yiq.i) assert_in_tolerance(0.0, yiq.q) end def test_inspect assert_equal("Grayscale [33.00%]", @gs.inspect) end end end color-2.1.1/test/test_cmyk.rb0000644000004100000410000000612215050641176016175 0ustar www-datawww-data# frozen_string_literal: true require "color" require "minitest_helper" module TestColor class TestCMYK < Minitest::Test def setup @cmyk = Color::CMYK.from_percentage(10, 20, 30, 40) end def test_cyan assert_in_tolerance(0.1, @cmyk.c) assert_in_tolerance(10, @cmyk.cyan) assert_in_tolerance(0.0, Color::CMYK[-1.0, 20, 30, 40].cyan) end def test_magenta assert_in_tolerance(0.2, @cmyk.m) assert_in_tolerance(20, @cmyk.magenta) end def test_yellow assert_in_tolerance(0.3, @cmyk.y) assert_in_tolerance(30, @cmyk.yellow) end def test_black assert_in_tolerance(0.4, @cmyk.k) assert_in_tolerance(40, @cmyk.black) end def test_to_cmyk assert(@cmyk.to_cmyk == @cmyk) end def test_to_grayscale gs = @cmyk.to_grayscale assert_kind_of(Color::Grayscale, gs) assert_in_tolerance(0.4185, gs.g) assert_kind_of(Color::Grayscale, @cmyk.to_grayscale) end # def test_to_hsl # hsl = @cmyk.to_hsl # assert_kind_of(Color::HSL, hsl) # assert_in_tolerance(0.48, hsl.l) # assert_in_tolerance(0.125, hsl.s) # assert_in_tolerance(0.08333, hsl.h) # end def test_to_rgb rgb = @cmyk.to_rgb(rgb_method: :adobe) assert_kind_of(Color::RGB, rgb) assert_in_tolerance(0.5, rgb.r) assert_in_tolerance(0.4, rgb.g) assert_in_tolerance(0.3, rgb.b) rgb = @cmyk.to_rgb assert_kind_of(Color::RGB, rgb) assert_in_tolerance(0.54, rgb.r) assert_in_tolerance(0.48, rgb.g) assert_in_tolerance(0.42, rgb.b) end def test_inspect assert_equal("CMYK [10.00% 20.00% 30.00% 40.00%]", @cmyk.inspect) end def test_css assert_equal("device-cmyk(10.00% 20.00% 30.00% 40.00%, rgb(54.00% 48.00% 42.00%))", @cmyk.css) assert_equal("device-cmyk(10.00% 20.00% 30.00% 40.00% / 0.50, rgb(54.00% 48.00% 42.00% / 0.50))", @cmyk.css(alpha: 0.5)) assert_equal("device-cmyk(10.00% 20.00% 30.00% 40.00%)", @cmyk.css(fallback: false)) assert_equal("device-cmyk(10.00% 20.00% 30.00% 40.00% / 0.50)", @cmyk.css(alpha: 0.5, fallback: false)) assert_equal("device-cmyk(10.00% 20.00% 30.00% 40.00%, rgb(0 0 100.00%))", @cmyk.css(fallback: Color::RGB::Blue)) assert_equal("device-cmyk(10.00% 20.00% 30.00% 40.00% / 0.50, rgb(0 0 100.00% / 0.50))", @cmyk.css(alpha: 0.5, fallback: Color::RGB::Blue)) assert_equal("device-cmyk(10.00% 20.00% 30.00% 40.00% / 0.50, rgb(0 0 100.00%))", @cmyk.css(alpha: 0.5, fallback: {color: Color::RGB::Blue})) assert_equal("device-cmyk(10.00% 20.00% 30.00% 40.00% / 0.50, rgb(0 0 100.00% / 0.30))", @cmyk.css(alpha: 0.5, fallback: {color: Color::RGB::Blue, alpha: 0.3})) assert_equal("device-cmyk(10.00% 20.00% 30.00% 40.00% / 0.50, rgb(54.00% 48.00% 42.00% / 0.30))", @cmyk.css(alpha: 0.5, fallback: {alpha: 0.3})) end # def test_to_yiq # yiq = @cmyk.to_yiq # assert_kind_of(Color::YIQ, yiq) # assert_in_tolerance(0.4911, yiq.y) # assert_in_tolerance(0.05502, yiq.i) # assert_in_tolerance(0.0, yiq.q) # end end end color-2.1.1/Rakefile0000644000004100000410000000433615050641176014341 0ustar www-datawww-datarequire "rubygems" require "hoe" require "rake/clean" require "rdoc/task" require "minitest" require "minitest/test_task" Hoe.plugin :halostatue Hoe.plugin :rubygems Hoe.plugins.delete :debug Hoe.plugins.delete :newb Hoe.plugins.delete :publish Hoe.plugins.delete :signing Hoe.plugins.delete :test hoe = Hoe.spec "color" do developer("Austin Ziegler", "halostatue@gmail.com") developer("Matt Lyon", "matt@postsomnia.com") self.trusted_release = ENV["rubygems_release_gem"] == "true" require_ruby_version ">= 3.2" license "MIT" spec_extras[:metadata] = ->(val) { val["rubygems_mfa_required"] = "true" } extra_dev_deps << ["hoe", "~> 4.0"] extra_dev_deps << ["hoe-halostatue", "~> 2.1", ">= 2.1.1"] extra_dev_deps << ["hoe-git", "~> 1.6"] extra_dev_deps << ["minitest", "~> 5.8"] extra_dev_deps << ["minitest-autotest", "~> 1.0"] extra_dev_deps << ["minitest-focus", "~> 1.1"] extra_dev_deps << ["minitest-moar", "~> 0.0"] extra_dev_deps << ["rake", ">= 10.0", "< 14"] extra_dev_deps << ["rdoc", ">= 0.0", "< 7"] extra_dev_deps << ["standard", "~> 1.0"] extra_dev_deps << ["json", ">= 0.0"] extra_dev_deps << ["simplecov", "~> 0.22"] extra_dev_deps << ["simplecov-lcov", "~> 0.8"] end Minitest::TestTask.create :test Minitest::TestTask.create :coverage do |t| formatters = <<-RUBY.split($/).join(" ") SimpleCov::Formatter::MultiFormatter.new([ SimpleCov::Formatter::HTMLFormatter, SimpleCov::Formatter::LcovFormatter, SimpleCov::Formatter::SimpleFormatter ]) RUBY t.test_prelude = <<-RUBY.split($/).join("; ") require "simplecov" require "simplecov-lcov" SimpleCov::Formatter::LcovFormatter.config do |config| config.report_with_single_file = true config.lcov_file_name = "lcov.info" end SimpleCov.start "test_frameworks" do enable_coverage :branch primary_coverage :branch formatter #{formatters} end RUBY end task default: :test task :version do require "color/version" puts Color::VERSION end RDoc::Task.new do _1.title = "Color -- Color Math with Ruby" _1.main = "lib/color.rb" _1.rdoc_dir = "doc" _1.rdoc_files = hoe.spec.require_paths - ["Manifest.txt"] + hoe.spec.extra_rdoc_files _1.markup = "markdown" end task docs: :rerdoc color-2.1.1/CODE_OF_CONDUCT.md0000644000004100000410000001222615050641176015470 0ustar www-datawww-data# Contributor Covenant Code of Conduct ## Our Pledge We as members, contributors, and leaders pledge to make participation in our community a harassment-free experience for everyone, regardless of age, body size, visible or invisible disability, ethnicity, sex characteristics, gender identity and expression, level of experience, education, socio-economic status, nationality, personal appearance, race, caste, color, religion, or sexual identity and orientation. We pledge to act and interact in ways that contribute to an open, welcoming, diverse, inclusive, and healthy community. ## Our Standards Examples of behavior that contributes to a positive environment for our community include: - Demonstrating empathy and kindness toward other people - Being respectful of differing opinions, viewpoints, and experiences - Giving and gracefully accepting constructive feedback - Accepting responsibility and apologizing to those affected by our mistakes, and learning from the experience - Focusing on what is best not just for us as individuals, but for the overall community Examples of unacceptable behavior include: - The use of sexualized language or imagery, and sexual attention or advances of any kind - Trolling, insulting or derogatory comments, and personal or political attacks - Public or private harassment - Publishing others' private information, such as a physical or email address, without their explicit permission - Other conduct which could reasonably be considered inappropriate in a professional setting ## Enforcement Responsibilities Community leaders are responsible for clarifying and enforcing our standards of acceptable behavior and will take appropriate and fair corrective action in response to any behavior that they deem inappropriate, threatening, offensive, or harmful. Community leaders have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct, and will communicate reasons for moderation decisions when appropriate. ## Scope This Code of Conduct applies within all community spaces, and also applies when an individual is officially representing the community in public spaces. Examples of representing our community include using an official email address, posting via an official social media account, or acting as an appointed representative at an online or offline event. ## Enforcement Instances of abusive, harassing, or otherwise unacceptable behavior may be reported to the community leaders responsible for enforcement at [INSERT CONTACT METHOD]. All complaints will be reviewed and investigated promptly and fairly. All community leaders are obligated to respect the privacy and security of the reporter of any incident. ## Enforcement Guidelines Community leaders will follow these Community Impact Guidelines in determining the consequences for any action they deem in violation of this Code of Conduct: ### 1. Correction **Community Impact**: Use of inappropriate language or other behavior deemed unprofessional or unwelcome in the community. **Consequence**: A private, written warning from community leaders, providing clarity around the nature of the violation and an explanation of why the behavior was inappropriate. A public apology may be requested. ### 2. Warning **Community Impact**: A violation through a single incident or series of actions. **Consequence**: A warning with consequences for continued behavior. No interaction with the people involved, including unsolicited interaction with those enforcing the Code of Conduct, for a specified period of time. This includes avoiding interactions in community spaces as well as external channels like social media. Violating these terms may lead to a temporary or permanent ban. ### 3. Temporary Ban **Community Impact**: A serious violation of community standards, including sustained inappropriate behavior. **Consequence**: A temporary ban from any sort of interaction or public communication with the community for a specified period of time. No public or private interaction with the people involved, including unsolicited interaction with those enforcing the Code of Conduct, is allowed during this period. Violating these terms may lead to a permanent ban. ### 4. Permanent Ban **Community Impact**: Demonstrating a pattern of violation of community standards, including sustained inappropriate behavior, harassment of an individual, or aggression toward or disparagement of classes of individuals. **Consequence**: A permanent ban from any sort of public interaction within the community. ## Attribution This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 2.1, available at . Community Impact Guidelines were inspired by [Mozilla's code of conduct enforcement ladder][Mozilla CoC]. For answers to common questions about this code of conduct, see the FAQ at . Translations are available at . [homepage]: https://www.contributor-covenant.org [Mozilla CoC]: https://github.com/mozilla/diversity color-2.1.1/CONTRIBUTORS.md0000644000004100000410000000044015050641176015143 0ustar www-datawww-data# Contributors - Austin Ziegler created color-tools. - Matt Lyons created color. - Dave Heitzman (contrast comparison, CIELAB color support) - Thomas Sawyer - Aaron Hill (CIE94 color matching) - Luke Bennellick - @stiff (CIELAB color support) - @r-plus - Matthew Draper - Charles Nutter color-2.1.1/licences/0000755000004100000410000000000015050641176014453 5ustar www-datawww-datacolor-2.1.1/licences/dco.txt0000644000004100000410000000252615050641176015766 0ustar www-datawww-dataDeveloper Certificate of Origin Version 1.1 Copyright (C) 2004, 2006 The Linux Foundation and its contributors. Everyone is permitted to copy and distribute verbatim copies of this license document, but changing it is not allowed. Developer's Certificate of Origin 1.1 By making a contribution to this project, I certify that: (a) The contribution was created in whole or in part by me and I have the right to submit it under the open source license indicated in the file; or (b) The contribution is based upon previous work that, to the best of my knowledge, is covered under an appropriate open source license and I have the right under that license to submit that work with modifications, whether created in whole or in part by me, under the same open source license (unless I am permitted to submit under a different license), as indicated in the file; or (c) The contribution was provided directly to me by some other person who certified (a), (b) or (c) and I have not modified it. (d) I understand and agree that this project and the contribution are public and that a record of the contribution (including all personal information I submit with it, including my sign-off) is maintained indefinitely and may be redistributed consistent with this project or the open source license(s) involved. color-2.1.1/README.md0000644000004100000410000000466415050641176014157 0ustar www-datawww-data# Color -- Color Math in Ruby - code :: - issues :: - changelog :: - continuous integration :: [![Build Status](https://github.com/halostatue/color/actions/workflows/ci.yml/badge.svg)][ci-workflow] - test coverage :: [![Coverage](https://coveralls.io/repos/halostatue/color/badge.svg?branch=main&service=github)][coveralls] ## Description Color is a Ruby library to provide RGB, CMYK, HSL, and other color space manipulation support to applications that require it. It provides optional named RGB colors that are commonly supported in HTML, SVG, and X11 applications. The Color library performs purely mathematical manipulation of the colors based on color theory without reference to device color profiles (such as sRGB or Adobe RGB). For most purposes, when working with RGB and HSL color spaces, this won't matter. Absolute color spaces (like CIE LAB and CIE XYZ) cannot be reliably converted to relative color spaces (like RGB) without color profiles. When necessary for conversions, Color provides D65 and D50 reference white values in Color::XYZ. Color 2.1 fixes a Color::XYZ bug where the values were improperly clamped and adds more Color::XYZ white points for standard illuminants. It builds on the Color 2.0 major release, dropping support for all versions of Ruby prior to 3.2 as well as removing or renaming a number of features. The main breaking changes are: - Color classes are immutable Data objects; they are no longer mutable. - RGB named colors are no longer loaded on gem startup, but must be required explicitly (this is _not_ done via `autoload` because there are more than 100 named colors with spelling variations) with `require "color/rgb/colors"`. - Color palettes have been removed. - `Color::CSS` and `Color::CSS#[]` have been removed. ## Example Suppose you want to make a given RGB color a little lighter. Adjusting the RGB color curves will change the hue and saturation will also change. Instead, use the CIE LAB color space keeping the `color` components intact, altering only the lightness component: ```ruby c = Color::RGB.from_values(r, g, b).to_lab if (t = c.l / 50.0) < 1 c.l = 50 * ((1.0 - t) * Math.sqrt(t) + t**2) end c.to_rgb ``` [ci-workflow]: https://github.com/halostatue/color/actions/workflows/ci.yml [coveralls]: https://coveralls.io/github/halostatue/color?branch=main color-2.1.1/CHANGELOG.md0000644000004100000410000003324315050641176014504 0ustar www-datawww-data# Changelog ## 2.1.1 / 2025-08-08 Color 2.1.1 fixes a bug where `Color::RGB::Black` and `Color::RGB::White` are no longer defined automatically because they are part of `color/rgb/colors`. Internally, this defines `Color::RGB::Black000` and `Color::RGB::WhiteFFF`. ## 2.1.0 / 2025-07-20 Color 2.1.0 fixes a computation bug where CIE XYZ values were improperly clamped and adds more Color::XYZ white points for standard illuminants. - Fixes a bug where standard illuminant white points were improperly clamped and was seen in `Color::RGB#to_lab` since CIELAB conversions must go through the XYZ color model. Even though we were using the D65 white point, the Z value was being clamped to 1.0 instead of the correct value of ≅1.08. Reported by @r-plus in [#45][issue-45] and fixed in [#45][pr-46]. The resulting Color::LAB values are not _exactly_ the same values under Color 1.8, but they are within fractional differences deemed acceptable. - Added more white points for standard illuminants in the Color::XYZ::WP2 constant. The values here were derived from the [White points of standard illuminants][wp-std-illuminant] using the `xyY` to `XYZ` conversion formula where `X = (x * Y) / y` and `Z = ((1 - x - y) * Y) / y`. Only the values for CIE 1931 2° were computed. The values for Color::XYZ::D50 and Color::XYZ::D65 were replaced with these computed values. ## 2.0.1 / 2025-07-05 Color 2.0.1 is a minor documentation update. ## 2.0.0 / 2025-07-05 Color 2.0.0 is a major release of the Color library. ### 💣 Breaking Changes Color 2.0 contains breaking changes. Functionality previously deprecated has been removed, but other functionality has been changed or removed as part of this release without prior warning. - The minimum supported version of Ruby is 3.2. - Color classes are now immutable implementations of Data objects (first introduced in Ruby 3.2). This will restrict Color 2 from running on versions of JRuby before JRuby 10. - The constants `Color::COLOR_VERSION` and `Color::COLOR_TOOLS_VERSION` have been removed; there is only `Color::VERSION`. This reverses a planned deprecation decision made more than ten years ago that no longer makes sense. - All named color classes at `Color` have been removed as planned. - `Color::RGB::BeccaPurple` has been removed as an alias for `Color::RGB::RebeccaPurple`. - The pseudo-constructor `Color.new` has been removed. - Color class constructors no longer yield the constructed color if a block is passed. - Renamed `Color::COLOR_EPSILON` and `Color::COLOR_TOLERANCE` to `Color::EPSILON` and `Color::TOLERANCE`. These aren't private constants because they need to be accessed throughout Color, but they are _internal_ constants that should not be used outside of the Color library or functions exposed therein. - PDF format functions `#pdf_fill` and `#pdf_stroke` have been removed from `Color::CMYK`, `Color::Grayscale`, and `Color::RGB`. The supporting internal constants `Color::::PDF_FORMAT_STR` have also been removed. - Palette processing classes, `Color::Palette::AdobeColor`, `Color::Palette::Gimp`, and `Color::Palette::MonoContrast` have been removed. Persons interested in using these are encouraged to extract them from [Color 1.8][color-1.8] and adapt them to use Color 2.0 APIs. - CSS methods (`#css_rgb`, `#css_rgba`, `#css_hsl`, `#css_hsla`) have been replaced with `#css` on color classes that have CSS representations. The output of `#css` differs (Color 1.8 used the legacy CSS color formats; Color 2.0 uses modern CSS color formats). - `Color::GrayScale` has been renamed to `Color::Grayscale`. The alias constant `Color::GreyScale` has been removed. - The `#html` method has been removed from all color classes except Color::RGB. - Named RGB colors are no longer defined automatically, but must be loaded explicitly by requiring `color/rgb/colors`. This resolves [#30][issue-30]. The use of `Color::RGB#extract_colors`, `Color::RGB.by_hex`, `Color::RGB.by_name`, or `Color::RGB.by_css` will require `color/rgb/colors` automatically as they require the presence of the named colors. - `Color:CSS#[]` has been removed, as has the containing namespace. It has always been a shallow wrapper around `Color::RGB.by_name`. ### 🚀 New Features - `Color::CIELAB` and `Color::XYZ` namespaces have been added. Separate implementations were submitted by David Heitzman and @stiff (in [#8][pr-8] and [#11][pr-11]), but I have reworked the code substantially. These implementations were originally as `Color::LAB` and include a new contrast calculation using the ΔE\*00 algorithm. ### Internal - Updated project structure for how I manage Ruby libraries in 2025. This includes increased release security (MFA is required for all releases, automated releases are enabled), full GitHub Actions, Dependabot, Standard Ruby, and more. - Charles Nutter re-added JRuby support in CI. [#36][pr-36] ### Governance Color 2.0 and later requires that all contributions be signed-off attesting that the developer created the change and has the appropriate permissions or ownership to contribute it to this project under the licence terms. ## 1.8 / 2015-10-26 - Add an optional `alpha` parameter to all `#css` calls. Thanks to Luke Bennellick (@bennell) and Alexander Popov (@AlexWayfer) for independently implemented submissions. Merged from [#15][pull-15]. - Improve constant detection to prevent incorrectly identified name collisions with various other libraries such as Azure deployment tools. Based on work by Matthew Draper (@matthewd) in [#24][pull-24]. - Prevent `Color.equivalent?` comparisons from using non-Color types for comparison. Fix provided by Benjamin Guest (@bguest) in [#18][pull-18]. - This project now has a [Code of Conduct](CODE_OF_CONDUCT.md). ## 1.7.1 / 2014-06-12 - Renamed `Color::RGB::BeccaPurple` to `Color::RGB::RebeccaPurple` as stipulated by Eric Meyer. For purposes of backwards compatibility, the previous name is still permitted, but its use is strongly discouraged, and it will be removed in the Color 2.0 release. ## 1.7 / 2014-06-12 - Added `Color::RGB::BeccaPurple` (#663399) in honour of Rebecca Meyer, the daughter of Eric Meyer, who passed away on the 7 June 2014. Her favourite color was purple. `#663399becca` - Changed the homepage in the gem to point to GitHub instead of RubyForge, which has been shut down. Fixes [#10][issue-10], reported by @voxik. ## 1.6 / 2014-05-19 - Aaron Hill (@armahillo) implemented the CIE Delta E 94 method by which an RGB color can be asked for the closest matching color from a list of provided colors. Fixes [#5][issue-5]. - To implement `#closest_match` and `#delta_e94`, conversion methods for sRGB to XYZ and XYZ to L\*a\*b\* space were implemented. These should be considered experimental. - Ensured that the gem manifest was up-to-date. Fixes [#4][issue-4] reported by @boutil. Thanks! - Fixed problems with Travis builds. Note that Ruby 1.9.2 is no longer tested. Rubinius remains in a 'failure-tolerated' mode. - Color 1.6 is, barring security patches, the last release of Color that will support Ruby 1.8. ## 1.5.1 / 2014-01-28 - color 1.5 was a yanked release. - Added new methods to `Color::RGB` to make it so that the default defined colors can be looked up by hex, name, or both. - Added a method to `Color::RGB` to extract colors from text by hex, name, or both. - Added new common methods for color names. Converted colors do not retain names. - Restructured color comparisons to use protocols instead of custom implementations. This makes it easier to implement new color classes. To make this work, color classes should `include` Color only need to implement `#coerce(other)`, `#to_a`, and supported conversion methods (e.g., `#to_rgb`). - Added @daveheitzman's initial implementation of a RGB contrast method as an extension file: `require 'color/rgb/contrast'`. This method and the value it returns should be considered experimental; it requires further examination to ensure that the results produced are consistent with the contrast comparisons used in `Color::Palette::MonoContrast`. - Reducing duplicated code. - Moved `lib/color/rgb-colors.rb` to `lib/color/rgb/colors.rb`. - Improved the way that named colors are specified internally. - Fixed bugs with Ruby 1.8.7 that may have been introduced in color 1.4.2. - Added simplecov for test coverage analysis. - Modernized Travis CI support. ## 1.4.2 / 2013-06-30 - Modernized Hoe installation of Color, removing some dependencies. - Switched to Minitest. - Turned on Travis CI. - Started using Code Climate. - Small code formatting cleanup that touched pretty much every file. ## 1.4.1 / 2010-02-03 - Imported to GitHub. - Converted to Hoe 2.5 spec format. ## 1.4.0 / 2007-02-11 - Merged Austin Ziegler's color-tools library (previously part of the Ruby PDF Tools project) with Matt Lyon's color library. - The HSL implementation from the Color class has been merged into `Color::HSL`. Color is a module the way it was for color-tools. - A thin veneer has been written to allow Color::new to return a `Color::HSL` instance; `Color::HSL` supports as many methods as possible that were previously supported by the Color class. - Values that were previously rounded by Color are no longer rounded; fractional values matter. - Converted to hoe for project management. - Moved to the next step of deprecating `Color::` values; printing a warning for each use (see the history for color-tools 1.3.0). - Print a warning on the access of either `VERSION` or `COLOR_TOOLS_VERSION`; the version constant is now `COLOR_VERSION`. - Added humanized versions of accessors (e.g., CMYK colors now have both #cyan and #c to access the cyan component of the color; #cyan provides the value as a percentage). - Added CSS3 formatters for RGB, RGBA, HSL, and HSLA outputs. Note that the Color library does not yet have a way of setting alpha opacity, so the output for RGBA and HSLA are at full alpha opacity (1.0). The values are output with two decimal places. - Applied a patch to provide simple arithmetic color addition and subtraction to `Color::GrayScale` and `Color::RGB`. The patch was contributed by Jeremy Hinegardner. This patch also provides the ability to return the maximum RGB value as a grayscale color. - Fixed two problems reported by Jean Krohn against color-tools relating to RGB-to-HSL and HSL-to-RGB conversion. (Color and color-tools use the same formulas, but the ordering of the calculations is slightly different with Color and did not suffer from this problem; color-tools was more sensitive to floating-point values and precision errors.) - Fixed an issue with HSL/RGB conversions reported by Adam Johnson. - Added an Adobe Color swatch (Photoshop) palette reader, `Color::Palette::AdobeColor` (for `.aco` files only). ## Color 0.1.0 / 2006-08-05 - Added HSL (degree, percent, percent) interface. - Removed RGB instance variable; color is managed internally as HSL floating point. - Tests! ## color-tools 1.3.0 - Added new metallic colors suggested by Jim Freeze. These are in the namespace `Color::Metallic`. - Colours that were defined in the Color namespace (e.g., `Color::Red`, `Color::AliceBlue`) are now defined in Color::RGB (e.g., `Color::RGB::Red`, `Color::RGB::AliceBlue`). They are added back to the Color namespace on the first use of the old colors and a warning is printed. In version 1.4, this warning will be printed on every use of the old colors. In version 1.5, the backwards compatible support for colors like Color::Red will be removed completely. - Added the `Color::CSS` module that provides a name lookup of `Color::RGB`-namespace constants with `Color::CSS[name]`. Most of these colors (which are mirrored from the `Color::RGB` default colors) are only "officially" recognised under the CSS3 color module or SVG. - Added the `Color::HSL` color space and some helper utilities to `Color::RGB` for color manipulation using the HSL value. - Controlled internal value replacement to be between 0 and 1 for all colors. - Updated `Color::Palette::Gimp` to more meaningfully deal with duplicate named colors. Named colors now return an array of colors. - Indicated the plans for some methods and constants out to color-tools 2.0. - Added unit tests and fixed a number of hidden bugs because of them. ## color-tools 1.2.0 - Changed installer from a custom-written install.rb to setup.rb 3.3.1-modified. - Added `Color::GreyScale` (or `Color::GrayScale`). - Added `Color::YIQ`. This color definition is incomplete; it does not have conversions from YIQ to other color spaces. ## color-tools 1.1.0 - Added `color/palette/gimp` to support the reading and use of GIMP color palettes. ## color-tools 1.0.0 - Initial release. [color-1.8]: https://github.com/halostatue/color/tree/v1.8 [css-color]: https://developer.mozilla.org/en-US/docs/Web/CSS/color_value/color [css-device-cmyk]: https://developer.mozilla.org/en-US/docs/Web/CSS/color_value/device-cmyk [issue-10]: https://github.com/halostatue/color/issues/10 [issue-30]: https://github.com/halostatue/color/issues/30 [issue-45]: https://github.com/halostatue/color/issues/45 [pr-11]: https://github.com/halostatue/color/pull/11 [pr-36]: https://github.com/halostatue/color/pull/36 [pr-46]: https://github.com/halostatue/pull/46 [pr-8]: https://github.com/halostatue/color/pulls/8 [wp-std-illuminant]: https://en.wikipedia.org/wiki/Standard_illuminant#White_points_of_standard_illuminants color-2.1.1/LICENCE.md0000644000004100000410000000404715050641176014257 0ustar www-datawww-data# Licence - SPDX-License-Identifier: [MIT][mit] - Copyright 2005-2025 Austin Ziegler, Matt Lyon, and other contributors. The software in this repository is made available under the MIT license. ## MIT License Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: - The names of its contributors may not be used to endorse or promote products derived from this software without specific prior written permission. The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ## Developer Certificate of Origin All contributors **must** certify they are willing and able to provide their contributions under the terms of this project's licences with the certification of the [Developer Certificate of Origin (Version 1.1)](licences/dco.txt). Such certification is provided by ensuring that a `Signed-off-by` [commit trailer][trailer] is present on every commit: Signed-off-by: FirstName LastName The `Signed-off-by` trailer can be automatically added by git with the `-s` or `--signoff` option on `git commit`: ```sh git commit --signoff ``` [mit]: https://spdx.org/licenses/MIT.html [trailer]: https://git-scm.com/docs/git-interpret-trailers