├── lib ├── color │ ├── palette.rb │ ├── css.rb │ ├── rgb │ │ └── metallic.rb │ ├── yiq.rb │ ├── palette │ │ ├── gimp.rb │ │ ├── monocontrast.rb │ │ └── adobecolor.rb │ ├── grayscale.rb │ ├── hsl.rb │ ├── cmyk.rb │ ├── rgb-colors.rb │ └── rgb.rb └── color.rb ├── Install.txt ├── Manifest.txt ├── test ├── test_all.rb ├── test_css.rb ├── test_yiq.rb ├── test_gimp.rb ├── test_grayscale.rb ├── test_cmyk.rb ├── test_color.rb ├── test_hsl.rb ├── test_monocontrast.rb ├── test_rgb.rb └── test_adobecolor.rb ├── Licence.txt ├── Readme.txt ├── Rakefile ├── History.txt └── setup.rb /lib/color/palette.rb: -------------------------------------------------------------------------------- 1 | #-- 2 | # Color 3 | # Colour management with Ruby 4 | # http://rubyforge.org/projects/color 5 | # Version 1.4.0 6 | # 7 | # Licensed under a MIT-style licence. See Licence.txt in the main 8 | # distribution for full licensing information. 9 | # 10 | # Copyright (c) 2005 - 2007 Austin Ziegler and Matt Lyon 11 | # 12 | # $Id: test_all.rb 55 2007-02-03 23:29:34Z austin $ 13 | #++ 14 | 15 | require 'color' 16 | 17 | module Color::Palette 18 | end 19 | -------------------------------------------------------------------------------- /Install.txt: -------------------------------------------------------------------------------- 1 | == Installing Color 2 | 3 | Color may be installed with: 4 | 5 | % ruby setup.rb 6 | 7 | Alternatively, the RubyGems version of Color may be installed from the usual 8 | sources. 9 | 10 | == Copyright 11 | Color 12 | Colour Management with Ruby 13 | http://rubyforge.org/projects/color 14 | 15 | Licensed under a MIT-style licence. See Licence.txt in the main 16 | distribution for full licensing information. 17 | 18 | Copyright (c) 2005 - 2007 Austin Ziegler and Matt Lyon 19 | 20 | $Id: History.txt 50 2007-02-03 20:26:19Z austin $ 21 | -------------------------------------------------------------------------------- /Manifest.txt: -------------------------------------------------------------------------------- 1 | History.txt 2 | Install.txt 3 | Licence.txt 4 | Manifest.txt 5 | Rakefile 6 | Readme.txt 7 | lib/color.rb 8 | lib/color/cmyk.rb 9 | lib/color/css.rb 10 | lib/color/grayscale.rb 11 | lib/color/hsl.rb 12 | lib/color/palette.rb 13 | lib/color/palette/adobecolor.rb 14 | lib/color/palette/gimp.rb 15 | lib/color/palette/monocontrast.rb 16 | lib/color/rgb-colors.rb 17 | lib/color/rgb.rb 18 | lib/color/rgb/metallic.rb 19 | lib/color/yiq.rb 20 | setup.rb 21 | test/test_adobecolor.rb 22 | test/test_all.rb 23 | test/test_cmyk.rb 24 | test/test_color.rb 25 | test/test_css.rb 26 | test/test_gimp.rb 27 | test/test_grayscale.rb 28 | test/test_hsl.rb 29 | test/test_monocontrast.rb 30 | test/test_rgb.rb 31 | test/test_yiq.rb 32 | -------------------------------------------------------------------------------- /test/test_all.rb: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | #-- 3 | # Color 4 | # Colour management with Ruby 5 | # http://rubyforge.org/projects/color 6 | # Version 1.4.0 7 | # 8 | # Licensed under a MIT-style licence. See Licence.txt in the main 9 | # distribution for full licensing information. 10 | # 11 | # Copyright (c) 2005 - 2007 Austin Ziegler and Matt Lyon 12 | # 13 | # $Id: test_all.rb 55 2007-02-03 23:29:34Z austin $ 14 | #++ 15 | 16 | $LOAD_PATH.unshift("#{File.dirname(__FILE__)}/../lib") if __FILE__ == $0 17 | 18 | $stderr.puts "Checking for test cases:" 19 | 20 | Dir[File.join(File.dirname($0), 'test_*.rb')].each do |testcase| 21 | next if File.basename(testcase) == File.basename(__FILE__) 22 | $stderr.puts "\t#{testcase}" 23 | load testcase 24 | end 25 | $stderr.puts " " 26 | -------------------------------------------------------------------------------- /lib/color/css.rb: -------------------------------------------------------------------------------- 1 | #-- 2 | # Color 3 | # Colour management with Ruby 4 | # http://rubyforge.org/projects/color 5 | # Version 1.4.0 6 | # 7 | # Licensed under a MIT-style licence. See Licence.txt in the main 8 | # distribution for full licensing information. 9 | # 10 | # Copyright (c) 2005 - 2007 Austin Ziegler and Matt Lyon 11 | # 12 | # $Id: test_all.rb 55 2007-02-03 23:29:34Z austin $ 13 | #++ 14 | 15 | require 'color' 16 | 17 | # This namespace contains some CSS colour names. 18 | module Color::CSS 19 | # Returns the RGB colour for name or +nil+ if the name is not valid. 20 | def self.[](name) 21 | @colors[name.to_s.downcase.to_sym] 22 | end 23 | 24 | @colors = {} 25 | Color::RGB.constants.each do |const| 26 | next if const == "PDF_FORMAT_STR" 27 | next if const == "Metallic" 28 | @colors[const.downcase.to_sym] ||= Color::RGB.const_get(const) 29 | end 30 | end 31 | -------------------------------------------------------------------------------- /test/test_css.rb: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | #-- 3 | # Color 4 | # Colour management with Ruby 5 | # http://rubyforge.org/projects/color 6 | # Version 1.4.0 7 | # 8 | # Licensed under a MIT-style licence. See Licence.txt in the main 9 | # distribution for full licensing information. 10 | # 11 | # Copyright (c) 2005 - 2007 Austin Ziegler and Matt Lyon 12 | # 13 | # $Id: test_all.rb 55 2007-02-03 23:29:34Z austin $ 14 | #++ 15 | 16 | $LOAD_PATH.unshift("#{File.dirname(__FILE__)}/../lib") if __FILE__ == $0 17 | require 'test/unit' 18 | require 'color' 19 | require 'color/css' 20 | 21 | module TestColor 22 | class TestCSS < Test::Unit::TestCase 23 | def test_index 24 | assert_equal(Color::RGB::AliceBlue, Color::CSS[:aliceblue]) 25 | assert_equal(Color::RGB::AliceBlue, Color::CSS["AliceBlue"]) 26 | assert_equal(Color::RGB::AliceBlue, Color::CSS["aliceBlue"]) 27 | assert_equal(Color::RGB::AliceBlue, Color::CSS["aliceblue"]) 28 | assert_equal(Color::RGB::AliceBlue, Color::CSS[:AliceBlue]) 29 | end 30 | end 31 | end 32 | -------------------------------------------------------------------------------- /Licence.txt: -------------------------------------------------------------------------------- 1 | Color 2 | Colour management in Ruby 3 | http://rubyforge.org/projects/color/ 4 | 5 | Copyright (c) 2005 - 2007 Austin Ziegler, Matt Lyon, and other contributors 6 | 7 | Permission is hereby granted, free of charge, to any person obtaining a copy of 8 | this software and associated documentation files (the "Software"), to deal in 9 | the Software without restriction, including without limitation the rights to 10 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies 11 | of the Software, and to permit persons to whom the Software is furnished to do 12 | so, subject to the following conditions: 13 | 14 | * The names of its contributors may not be used to endorse or promote 15 | products derived from this software without specific prior written 16 | permission. 17 | 18 | The above copyright notice and this permission notice shall be included in all 19 | copies or substantial portions of the Software. 20 | 21 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 22 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 23 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 24 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 25 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 26 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 27 | SOFTWARE. 28 | 29 | $Id: History.txt 50 2007-02-03 20:26:19Z austin $ 30 | -------------------------------------------------------------------------------- /lib/color/rgb/metallic.rb: -------------------------------------------------------------------------------- 1 | #-- 2 | # Color 3 | # Colour management with Ruby 4 | # http://rubyforge.org/projects/color 5 | # Version 1.4.0 6 | # 7 | # Licensed under a MIT-style licence. See Licence.txt in the main 8 | # distribution for full licensing information. 9 | # 10 | # Copyright (c) 2005 - 2007 Austin Ziegler and Matt Lyon 11 | # 12 | # $Id: test_all.rb 55 2007-02-03 23:29:34Z austin $ 13 | #++ 14 | 15 | # This namespace contains some RGB metallic colours suggested by Jim Freeze. 16 | module Color::RGB::Metallic 17 | Aluminum = Color::RGB.new(0x99, 0x99, 0x99) 18 | CoolCopper = Color::RGB.new(0xd9, 0x87, 0x19) 19 | Copper = Color::RGB.new(0xb8, 0x73, 0x33) 20 | Iron = Color::RGB.new(0x4c, 0x4c, 0x4c) 21 | Lead = Color::RGB.new(0x19, 0x19, 0x19) 22 | Magnesium = Color::RGB.new(0xb3, 0xb3, 0xb3) 23 | Mercury = Color::RGB.new(0xe6, 0xe6, 0xe6) 24 | Nickel = Color::RGB.new(0x80, 0x80, 0x80) 25 | PolySilicon = Color::RGB.new(0x60, 0x00, 0x00) 26 | Poly = PolySilicon 27 | Silver = Color::RGB.new(0xcc, 0xcc, 0xcc) 28 | Steel = Color::RGB.new(0x66, 0x66, 0x66) 29 | Tin = Color::RGB.new(0x7f, 0x7f, 0x7f) 30 | Tungsten = Color::RGB.new(0x33, 0x33, 0x33) 31 | 32 | Aluminum.freeze 33 | CoolCopper.freeze 34 | Copper.freeze 35 | Iron.freeze 36 | Lead.freeze 37 | Magnesium.freeze 38 | Mercury.freeze 39 | Nickel.freeze 40 | PolySilicon.freeze 41 | Silver.freeze 42 | Steel.freeze 43 | Tin.freeze 44 | Tungsten.freeze 45 | end 46 | -------------------------------------------------------------------------------- /Readme.txt: -------------------------------------------------------------------------------- 1 | = Color 2 | Color is a Ruby library to provide basic RGB, CMYK, HSL, and other colourspace 3 | manipulation support to applications that require it. It also provides 152 4 | named RGB colours (184 with spelling variations) that are commonly supported 5 | in HTML, SVG, and X11 applications. A technique for generating monochromatic 6 | contrasting palettes is also included. 7 | 8 | The capabilities of the Color library are limited to pure mathematical 9 | manipulation of the colours based on colour theory without reference to colour 10 | profiles (such as sRGB or Adobe RGB). For most purposes, when working with the 11 | RGB and HSL colours, this won't matter. However, some colour models (like CIE 12 | L*a*b*) are not supported because Color does not yet support colour profiles, 13 | giving no meaningful way to convert colours in absolute colour spaces (like 14 | L*a*b*, XYZ) to non-absolute colour spaces (like RGB). 15 | 16 | Color version 1.4 is the result of a project merge between color.rb 0.1.0 by 17 | Matt Lyon and color-tools 1.3 by Austin Ziegler. Please see History.txt for 18 | details on the changes this merge brings. 19 | 20 | Copyright:: Copyright (c) 2005 - 2007 by Austin Ziegler and Matt Lyon 21 | Version:: 1.4.0 22 | Homepage:: http://rubyforge.org/projects/color/ 23 | Licence:: MIT-Style; see Licence.txt 24 | 25 | Color::Palette was developed based on techniques described by Andy "Malarkey" 26 | Clarke[1], implemented in JavaScript by Steve G. Chipman at SlayerOffice[2] and 27 | by Patrick Fitzgerald of BarelyFitz[3] in PHP. 28 | 29 | [1] http://www.stuffandnonsense.co.uk/archives/creating_colour_palettes.html 30 | [2] http://slayeroffice.com/tools/color_palette/ 31 | [3] http://www.barelyfitz.com/projects/csscolor/ 32 | 33 | $Id: Readme.txt 50 2007-02-03 20:26:19Z austin $ 34 | -------------------------------------------------------------------------------- /lib/color/yiq.rb: -------------------------------------------------------------------------------- 1 | #-- 2 | # Color 3 | # Colour management with Ruby 4 | # http://rubyforge.org/projects/color 5 | # Version 1.4.0 6 | # 7 | # Licensed under a MIT-style licence. See Licence.txt in the main 8 | # distribution for full licensing information. 9 | # 10 | # Copyright (c) 2005 - 2007 Austin Ziegler and Matt Lyon 11 | # 12 | # $Id: test_all.rb 55 2007-02-03 23:29:34Z austin $ 13 | #++ 14 | 15 | # A colour object representing YIQ (NTSC) colour encoding. 16 | class Color::YIQ 17 | # Creates a YIQ colour object from fractional values 0 .. 1. 18 | # 19 | # Color::YIQ.new(0.3, 0.2, 0.1) 20 | def self.from_fraction(y = 0, i = 0, q = 0) 21 | color = Color::YIQ.new 22 | color.y = y 23 | color.i = i 24 | color.q = q 25 | color 26 | end 27 | 28 | # Creates a YIQ colour object from percentages 0 .. 100. 29 | # 30 | # Color::YIQ.new(10, 20, 30) 31 | def initialize(y = 0, i = 0, q = 0) 32 | @y = y / 100.0 33 | @i = i / 100.0 34 | @q = q / 100.0 35 | end 36 | 37 | # Compares the other colour to this one. The other colour will be 38 | # converted to YIQ before comparison, so the comparison between a YIQ 39 | # colour and a non-YIQ colour will be approximate and based on the other 40 | # colour's #to_yiq conversion. If there is no #to_yiq conversion, this 41 | # will raise an exception. This will report that two YIQ values are 42 | # equivalent if all component colours are within COLOR_TOLERANCE of each 43 | # other. 44 | def ==(other) 45 | other = other.to_yiq 46 | other.kind_of?(Color::YIQ) and 47 | ((@y - other.y).abs <= Color::COLOR_TOLERANCE) and 48 | ((@i - other.i).abs <= Color::COLOR_TOLERANCE) and 49 | ((@q - other.q).abs <= Color::COLOR_TOLERANCE) 50 | end 51 | 52 | def to_yiq 53 | self 54 | end 55 | 56 | def brightness 57 | @y 58 | end 59 | def to_grayscale 60 | Color::GrayScale.new(@y) 61 | end 62 | alias to_greyscale to_grayscale 63 | 64 | def y 65 | @y 66 | end 67 | def y=(yy) 68 | @y = Color.normalize(yy) 69 | end 70 | def i 71 | @i 72 | end 73 | def i=(ii) 74 | @i = Color.normalize(ii) 75 | end 76 | def q 77 | @q 78 | end 79 | def q=(qq) 80 | @q = Color.normalize(qq) 81 | end 82 | 83 | def inspect 84 | "YIQ [%.2f%%, %.2f%%, %.2f%%]" % [ @y * 100, @i * 100, @q * 100 ] 85 | end 86 | end 87 | -------------------------------------------------------------------------------- /test/test_yiq.rb: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | #-- 3 | # Color 4 | # Colour management with Ruby 5 | # http://rubyforge.org/projects/color 6 | # Version 1.4.0 7 | # 8 | # Licensed under a MIT-style licence. See Licence.txt in the main 9 | # distribution for full licensing information. 10 | # 11 | # Copyright (c) 2005 - 2007 Austin Ziegler and Matt Lyon 12 | # 13 | # $Id: test_all.rb 55 2007-02-03 23:29:34Z austin $ 14 | #++ 15 | 16 | $LOAD_PATH.unshift("#{File.dirname(__FILE__)}/../lib") if __FILE__ == $0 17 | require 'test/unit' 18 | require 'color' 19 | 20 | module TestColor 21 | class TestYIQ < Test::Unit::TestCase 22 | def setup 23 | @yiq = Color::YIQ.from_fraction(0.1, 0.2, 0.3) 24 | end 25 | 26 | def test_brightness 27 | assert_in_delta(0.1, @yiq.brightness, Color::COLOR_TOLERANCE) 28 | end 29 | 30 | def test_i 31 | assert_in_delta(0.2, @yiq.i, Color::COLOR_TOLERANCE) 32 | assert_in_delta(0.2, @yiq.i, Color::COLOR_TOLERANCE) 33 | assert_nothing_raised { @yiq.i = 0.5 } 34 | assert_in_delta(0.5, @yiq.i, Color::COLOR_TOLERANCE) 35 | assert_nothing_raised { @yiq.i = 5 } 36 | assert_in_delta(1.0, @yiq.i, Color::COLOR_TOLERANCE) 37 | assert_nothing_raised { @yiq.i = -5 } 38 | assert_in_delta(0.0, @yiq.i, Color::COLOR_TOLERANCE) 39 | end 40 | 41 | def test_q 42 | assert_in_delta(0.3, @yiq.q, Color::COLOR_TOLERANCE) 43 | assert_in_delta(0.3, @yiq.q, Color::COLOR_TOLERANCE) 44 | assert_nothing_raised { @yiq.q = 0.5 } 45 | assert_in_delta(0.5, @yiq.q, Color::COLOR_TOLERANCE) 46 | assert_nothing_raised { @yiq.q = 5 } 47 | assert_in_delta(1.0, @yiq.q, Color::COLOR_TOLERANCE) 48 | assert_nothing_raised { @yiq.q = -5 } 49 | assert_in_delta(0.0, @yiq.q, Color::COLOR_TOLERANCE) 50 | end 51 | 52 | def test_to_grayscale 53 | assert_equal(Color::GrayScale.new(0.1), @yiq.to_grayscale) 54 | end 55 | 56 | def test_to_yiq 57 | assert_equal(@yiq, @yiq.to_yiq) 58 | end 59 | 60 | def test_y 61 | assert_in_delta(0.1, @yiq.y, Color::COLOR_TOLERANCE) 62 | assert_in_delta(0.1, @yiq.y, Color::COLOR_TOLERANCE) 63 | assert_nothing_raised { @yiq.y = 0.5 } 64 | assert_in_delta(0.5, @yiq.y, Color::COLOR_TOLERANCE) 65 | assert_nothing_raised { @yiq.y = 5 } 66 | assert_in_delta(1.0, @yiq.y, Color::COLOR_TOLERANCE) 67 | assert_nothing_raised { @yiq.y = -5 } 68 | assert_in_delta(0.0, @yiq.y, Color::COLOR_TOLERANCE) 69 | end 70 | 71 | def test_inspect 72 | assert_equal("YIQ [10.00%, 20.00%, 30.00%]", @yiq.inspect) 73 | end 74 | end 75 | end 76 | -------------------------------------------------------------------------------- /test/test_gimp.rb: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | #-- 3 | # Color 4 | # Colour management with Ruby 5 | # http://rubyforge.org/projects/color 6 | # Version 1.4.0 7 | # 8 | # Licensed under a MIT-style licence. See Licence.txt in the main 9 | # distribution for full licensing information. 10 | # 11 | # Copyright (c) 2005 - 2007 Austin Ziegler and Matt Lyon 12 | # 13 | # $Id: test_all.rb 55 2007-02-03 23:29:34Z austin $ 14 | #++ 15 | 16 | $LOAD_PATH.unshift("#{File.dirname(__FILE__)}/../lib") if __FILE__ == $0 17 | require 'test/unit' 18 | require 'color' 19 | require 'color/palette/gimp' 20 | 21 | module TestColor 22 | module TestPalette 23 | class TestGimp < Test::Unit::TestCase 24 | include Color::Palette 25 | 26 | GIMP_W3C = <<-EOS 27 | GIMP Palette 28 | Name: W3C Named Colors 29 | Columns: 2 30 | # 31 | # ColorZilla W3C Named Colors 32 | # 33 | 255 255 255 White 34 | 255 255 0 Yclow 35 | 255 0 255 Fuchsia 36 | 255 0 0 Red 37 | 192 192 192 Silver 38 | 128 128 128 Gray 39 | 128 128 0 Olive 40 | 128 0 128 Purple 41 | 128 0 0 Maroon 42 | 0 255 255 Aqua 43 | 0 255 0 Lime 44 | 0 128 128 Teal 45 | 0 128 0 Green 46 | 0 0 255 Blue 47 | 0 0 128 Navy 48 | 0 0 0 Black 49 | EOS 50 | 51 | def setup 52 | @filename = "test#{Process.pid}.gimp" 53 | end 54 | 55 | def teardown 56 | require 'fileutils' 57 | FileUtils.rm_f @filename if File.exist? @filename 58 | end 59 | 60 | def test_each 61 | @gimp = Gimp.new(GIMP_W3C) 62 | assert_equal(16, @gimp.instance_variable_get(:@colors).size) 63 | @gimp.each { |c| assert_kind_of(Color::RGB, c) } 64 | end 65 | 66 | def test_each_name 67 | @gimp = Gimp.new(GIMP_W3C) 68 | assert_equal(16, @gimp.instance_variable_get(:@names).size) 69 | 70 | @gimp.each_name { |color_name, color_set| 71 | assert_kind_of(Array, color_set) 72 | color_set.each { |c| 73 | assert_kind_of(Color::RGB, c) 74 | } 75 | } 76 | end 77 | 78 | def test_index 79 | assert_nothing_raised do 80 | File.open(@filename, "wb") do |f| 81 | f.write GIMP_W3C 82 | end 83 | end 84 | assert_nothing_raised { @gimp = Gimp.from_file(@filename) } 85 | assert_equal(Color::RGB::White, @gimp[0]) 86 | assert_equal(Color::RGB::White, @gimp["White"][0]) 87 | assert_equal([Color::RGB::White, Color::RGB::Black], 88 | @gimp.values_at(0, -1)) 89 | assert_equal(16, @gimp.size) 90 | end 91 | 92 | def test_valid_eh 93 | @gimp = Gimp.new(GIMP_W3C) 94 | assert(@gimp.valid?) 95 | end 96 | 97 | def test_name 98 | @gimp = Gimp.new(GIMP_W3C) 99 | assert_equal("W3C Named Colors", @gimp.name) 100 | end 101 | end 102 | end 103 | end 104 | -------------------------------------------------------------------------------- /lib/color/palette/gimp.rb: -------------------------------------------------------------------------------- 1 | #-- 2 | # Color 3 | # Colour management with Ruby 4 | # http://rubyforge.org/projects/color 5 | # Version 1.4.0 6 | # 7 | # Licensed under a MIT-style licence. See Licence.txt in the main 8 | # distribution for full licensing information. 9 | # 10 | # Copyright (c) 2005 - 2007 Austin Ziegler and Matt Lyon 11 | # 12 | # $Id: test_all.rb 55 2007-02-03 23:29:34Z austin $ 13 | #++ 14 | 15 | require 'color/palette' 16 | 17 | # A class that can read a GIMP (GNU Image Manipulation Program) palette file 18 | # and provide a Hash-like interface to the contents. GIMP colour palettes 19 | # are RGB values only. 20 | # 21 | # Because two or more entries in a GIMP palette may have the same name, all 22 | # named entries are returned as an array. 23 | # 24 | # pal = Color::Palette::Gimp.from_file(my_gimp_palette) 25 | # pal[0] => Color::RGB<...> 26 | # pal["white"] => [ Color::RGB<...> ] 27 | # pal["unknown"] => [ Color::RGB<...>, Color::RGB<...>, ... ] 28 | # 29 | # GIMP Palettes are always indexable by insertion order (an integer key). 30 | class Color::Palette::Gimp 31 | include Enumerable 32 | 33 | class << self 34 | # Create a GIMP palette object from the named file. 35 | def from_file(filename) 36 | File.open(filename, "rb") { |io| Color::Palette::Gimp.from_io(io) } 37 | end 38 | 39 | # Create a GIMP palette object from the provided IO. 40 | def from_io(io) 41 | Color::Palette::Gimp.new(io.read) 42 | end 43 | end 44 | 45 | # Create a new GIMP palette from the palette file as a string. 46 | def initialize(palette) 47 | @colors = [] 48 | @names = {} 49 | @valid = false 50 | @name = "(unnamed)" 51 | 52 | palette.split($/).each do |line| 53 | line.chomp! 54 | line.gsub!(/\s*#.*\Z/, '') 55 | 56 | next if line.empty? 57 | 58 | if line =~ /\AGIMP Palette\Z/ 59 | @valid = true 60 | next 61 | end 62 | 63 | info = /(\w+):\s(.*$)/.match(line) 64 | if info 65 | @name = info.captures[1] if info.captures[0] =~ /name/i 66 | next 67 | end 68 | 69 | line.gsub!(/^\s+/, '') 70 | data = line.split(/\s+/, 4) 71 | name = data.pop.strip 72 | data.map! { |el| el.to_i } 73 | 74 | color = Color::RGB.new(*data) 75 | 76 | @colors << color 77 | @names[name] ||= [] 78 | @names[name] << color 79 | end 80 | end 81 | 82 | # Provides the colour or colours at the provided selectors. 83 | def values_at(*selectors) 84 | @colors.values_at(*selectors) 85 | end 86 | 87 | # If a Numeric +key+ is provided, the single colour value at that position 88 | # will be returned. If a String +key+ is provided, the colour set (an 89 | # array) for that colour name will be returned. 90 | def [](key) 91 | if key.kind_of?(Numeric) 92 | @colors[key] 93 | else 94 | @names[key] 95 | end 96 | end 97 | 98 | # Loops through each colour. 99 | def each 100 | @colors.each { |el| yield el } 101 | end 102 | 103 | # Loops through each named colour set. 104 | def each_name #:yields color_name, color_set:# 105 | @names.each { |color_name, color_set| yield color_name, color_set } 106 | end 107 | 108 | # Returns true if this is believed to be a valid GIMP palette. 109 | def valid? 110 | @valid 111 | end 112 | 113 | def size 114 | @colors.size 115 | end 116 | 117 | attr_reader :name 118 | end 119 | -------------------------------------------------------------------------------- /Rakefile: -------------------------------------------------------------------------------- 1 | #! /usr/bin/env rake 2 | #-- 3 | # Color 4 | # Colour Management with Ruby 5 | # http://rubyforge.org/projects/color 6 | # 7 | # Licensed under a MIT-style licence. See Licence.txt in the main 8 | # distribution for full licensing information. 9 | # 10 | # Copyright (c) 2005 - 2007 Austin Ziegler and Matt Lyon 11 | # 12 | # $Id: History.txt 50 2007-02-03 20:26:19Z austin $ 13 | #++ 14 | 15 | require 'rubygems' 16 | require 'hoe' 17 | 18 | $LOAD_PATH.unshift('lib') 19 | 20 | require 'color' 21 | 22 | PKG_NAME = 'color' 23 | PKG_VERSION = Color::COLOR_VERSION 24 | PKG_DIST = "#{PKG_NAME}-#{PKG_VERSION}" 25 | PKG_TAR = "pkg/#{PKG_DIST}.tar.gz" 26 | MANIFEST = File.read("Manifest.txt").split 27 | 28 | Hoe.new PKG_NAME, PKG_VERSION do |p| 29 | p.rubyforge_name = PKG_NAME 30 | # This is a lie becasue I will continue to use Archive::Tar::Minitar. 31 | p.need_tar = false 32 | # need_zip - Should package create a zipfile? [default: false] 33 | 34 | p.author = [ "Austin Ziegler", "Matt Lyon" ] 35 | p.email = %W(austin@rubyforge.org matt@postsomnia.com) 36 | p.url = "http://color.rubyforge.org/" 37 | p.summary = "Colour management with Ruby" 38 | p.changes = p.paragraphs_of("History.txt", 0..1).join("\n\n") 39 | p.description = p.paragraphs_of("Readme.txt", 1..1).join("\n\n") 40 | 41 | p.extra_deps << [ "archive-tar-minitar", "~>0.5.1" ] 42 | 43 | p.clean_globs << "coverage" 44 | 45 | p.spec_extras[:extra_rdoc_files] = MANIFEST.grep(/txt$/) - 46 | ["Manifest.txt"] 47 | end 48 | 49 | desc "Build a Color .tar.gz distribution." 50 | task :tar => [ PKG_TAR ] 51 | file PKG_TAR => [ :test ] do |t| 52 | require 'archive/tar/minitar' 53 | require 'zlib' 54 | files = MANIFEST.map { |f| 55 | fn = File.join(PKG_DIST, f) 56 | tm = File.stat(f).mtime 57 | 58 | if File.directory?(f) 59 | { :name => fn, :mode => 0755, :dir => true, :mtime => tm } 60 | else 61 | mode = if f =~ %r{^bin} 62 | 0755 63 | else 64 | 0644 65 | end 66 | data = File.read(f) 67 | { :name => fn, :mode => mode, :data => data, :size => data.size, 68 | :mtime => tm } 69 | end 70 | } 71 | 72 | begin 73 | unless File.directory?(File.dirname(t.name)) 74 | require 'fileutils' 75 | File.mkdir_p File.dirname(t.name) 76 | end 77 | tf = File.open(t.name, 'wb') 78 | gz = Zlib::GzipWriter.new(tf) 79 | tw = Archive::Tar::Minitar::Writer.new(gz) 80 | 81 | files.each do |entry| 82 | if entry[:dir] 83 | tw.mkdir(entry[:name], entry) 84 | else 85 | tw.add_file_simple(entry[:name], entry) { |os| 86 | os.write(entry[:data]) 87 | } 88 | end 89 | end 90 | ensure 91 | tw.close if tw 92 | gz.close if gz 93 | end 94 | end 95 | task :package => [ PKG_TAR ] 96 | 97 | desc "Build the manifest file from the current set of files." 98 | task :build_manifest do |t| 99 | require 'find' 100 | 101 | paths = [] 102 | Find.find(".") do |path| 103 | next if File.directory?(path) 104 | next if path =~ /\.svn/ 105 | next if path =~ /\.swp$/ 106 | next if path =~ %r{coverage/} 107 | next if path =~ /~$/ 108 | paths << path.sub(%r{^\./}, '') 109 | end 110 | 111 | File.open("Manifest.txt", "w") do |f| 112 | f.puts paths.sort.join("\n") 113 | end 114 | 115 | puts paths.sort.join("\n") 116 | end 117 | -------------------------------------------------------------------------------- /test/test_grayscale.rb: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | #-- 3 | # Color 4 | # Colour management with Ruby 5 | # http://rubyforge.org/projects/color 6 | # Version 1.4.0 7 | # 8 | # Licensed under a MIT-style licence. See Licence.txt in the main 9 | # distribution for full licensing information. 10 | # 11 | # Copyright (c) 2005 - 2007 Austin Ziegler and Matt Lyon 12 | # 13 | # $Id: test_all.rb 55 2007-02-03 23:29:34Z austin $ 14 | #++ 15 | 16 | $LOAD_PATH.unshift("#{File.dirname(__FILE__)}/../lib") if __FILE__ == $0 17 | require 'test/unit' 18 | require 'color' 19 | 20 | module TestColor 21 | class TestGrayScale < Test::Unit::TestCase 22 | def setup 23 | @gs = Color::GrayScale.from_percent(33) 24 | end 25 | 26 | def test_brightness 27 | assert_in_delta(0.33, @gs.brightness, Color::COLOR_TOLERANCE) 28 | end 29 | 30 | def test_darken_by 31 | assert_in_delta(29.7, @gs.darken_by(10).gray, Color::COLOR_TOLERANCE) 32 | end 33 | 34 | def test_g 35 | assert_in_delta(0.33, @gs.g, Color::COLOR_TOLERANCE) 36 | assert_in_delta(33, @gs.grey, Color::COLOR_TOLERANCE) 37 | assert_nothing_raised { @gs.gray = 40 } 38 | assert_in_delta(0.4, @gs.g, Color::COLOR_TOLERANCE) 39 | assert_nothing_raised { @gs.g = 2.0 } 40 | assert_in_delta(100, @gs.gray, Color::COLOR_TOLERANCE) 41 | assert_nothing_raised { @gs.grey = -2.0 } 42 | assert_in_delta(0.0, @gs.g, Color::COLOR_TOLERANCE) 43 | end 44 | 45 | def test_html_css 46 | assert_equal("#545454", @gs.html) 47 | assert_equal("rgb(33.00%, 33.00%, 33.00%)", @gs.css_rgb) 48 | assert_equal("rgba(33.00%, 33.00%, 33.00%, 1.00)", @gs.css_rgba) 49 | end 50 | 51 | def test_lighten_by 52 | assert_in_delta(0.363, @gs.lighten_by(10).g, Color::COLOR_TOLERANCE) 53 | end 54 | 55 | def test_pdf_fill 56 | assert_equal("0.330 g", @gs.pdf_fill) 57 | assert_equal("0.330 G", @gs.pdf_stroke) 58 | end 59 | 60 | def test_to_cmyk 61 | cmyk = nil 62 | assert_nothing_raised { cmyk = @gs.to_cmyk } 63 | assert_kind_of(Color::CMYK, cmyk) 64 | assert_in_delta(0.0, cmyk.c, Color::COLOR_TOLERANCE) 65 | assert_in_delta(0.0, cmyk.m, Color::COLOR_TOLERANCE) 66 | assert_in_delta(0.0, cmyk.y, Color::COLOR_TOLERANCE) 67 | assert_in_delta(0.67, cmyk.k, Color::COLOR_TOLERANCE) 68 | end 69 | 70 | def test_to_grayscale 71 | assert_equal(@gs, @gs.to_grayscale) 72 | assert_equal(@gs, @gs.to_greyscale) 73 | end 74 | 75 | def test_to_hsl 76 | hsl = nil 77 | assert_nothing_raised { hsl = @gs.to_hsl } 78 | assert_kind_of(Color::HSL, hsl) 79 | assert_in_delta(0.0, hsl.h, Color::COLOR_TOLERANCE) 80 | assert_in_delta(0.0, hsl.s, Color::COLOR_TOLERANCE) 81 | assert_in_delta(0.33, hsl.l, Color::COLOR_TOLERANCE) 82 | assert_equal("hsl(0.00, 0.00%, 33.00%)", @gs.css_hsl) 83 | assert_equal("hsla(0.00, 0.00%, 33.00%, 1.00)", @gs.css_hsla) 84 | end 85 | 86 | def test_to_rgb 87 | rgb = nil 88 | assert_nothing_raised { rgb = @gs.to_rgb } 89 | assert_kind_of(Color::RGB, rgb) 90 | assert_in_delta(0.33, rgb.r, Color::COLOR_TOLERANCE) 91 | assert_in_delta(0.33, rgb.g, Color::COLOR_TOLERANCE) 92 | assert_in_delta(0.33, rgb.b, Color::COLOR_TOLERANCE) 93 | end 94 | 95 | def test_to_yiq 96 | yiq = nil 97 | assert_nothing_raised { yiq = @gs.to_yiq } 98 | assert_kind_of(Color::YIQ, yiq) 99 | assert_in_delta(0.33, yiq.y, Color::COLOR_TOLERANCE) 100 | assert_in_delta(0.0, yiq.i, Color::COLOR_TOLERANCE) 101 | assert_in_delta(0.0, yiq.q, Color::COLOR_TOLERANCE) 102 | end 103 | 104 | def test_add 105 | delta = @gs + Color::GrayScale.new(20) 106 | max = @gs + Color::GrayScale.new(80) 107 | 108 | assert_in_delta(1.0, max.g, Color::COLOR_TOLERANCE) 109 | assert_in_delta(0.53, delta.g, Color::COLOR_TOLERANCE) 110 | end 111 | 112 | def test_subtract 113 | delta = @gs - Color::GrayScale.new(20) 114 | max = @gs - Color::GrayScale.new(80) 115 | assert_in_delta(0.0, max.g, Color::COLOR_TOLERANCE) 116 | assert_in_delta(0.13, delta.g, Color::COLOR_TOLERANCE) 117 | end 118 | 119 | def test_inspect 120 | assert_equal("Gray [33.00%]", @gs.inspect) 121 | end 122 | end 123 | end 124 | -------------------------------------------------------------------------------- /History.txt: -------------------------------------------------------------------------------- 1 | == Color 1.4.0 / 2007.02.11 2 | * Merged Austin Ziegler's color-tools library (previously part of the Ruby 3 | PDF Tools project) with Matt Lyon's color library. 4 | - The HSL implementation from the Color class has been merged into 5 | Color::HSL. Color is a module the way it was for color-tools. 6 | - A thin veneer has been written to allow Color::new to return a Color::HSL 7 | instance; Color::HSL supports as many methods as possible that were 8 | previously supported by the Color class. 9 | - Values that were previously rounded by Color are no longer rounded; 10 | fractional values matter. 11 | * Converted to hoe for project management. 12 | * Moved to the next step of deprecating Color::Name values; printing a 13 | warning for each use (see the history for color-tools 1.3.0). 14 | * Print a warning on the access of either VERSION or COLOR_TOOLS_VERSION; the 15 | version constant is now COLOR_VERSION. 16 | * Added humanized versions of accessors (e.g., CMYK colours now have both #cyan 17 | and #c to access the cyan component of the colour; #cyan provides the value 18 | as a percentage). 19 | * Added CSS3 formatters for RGB, RGBA, HSL, and HSLA outputs. Note that the 20 | Color library does not yet have a way of setting alpha opacity, so the 21 | output for RGBA and HSLA are at full alpha opacity (1.0). The values are 22 | output with two decimal places. 23 | * Applied a patch to provide simple arithmetic colour addition and subtraction 24 | to Color::GrayScale and Color::RGB. The patch was contributed by Jeremy 25 | Hinegardner . This patch also provides the ability to 26 | return the maximum RGB value as a grayscale colour. 27 | * Fixed two problems reported by Jean Krohn against 28 | color-tools relating to RGB-to-HSL and HSL-to-RGB conversion. (Color and 29 | color-tools use the same formulas, but the ordering of the calculations is 30 | slightly different with Color and did not suffer from this problem; 31 | color-tools was more sensitive to floating-point values and precision 32 | errors.) 33 | * Fixed an issue with HSL/RGB conversions reported by Adam Johnson 34 | . 35 | * Added an Adobe Color swatch (Photoshop) palette reader, 36 | Color::Palette::AdobeColor (for .aco files only). 37 | 38 | == Color 0.1.0 / 2006.08.05 39 | * Added HSL (degree, percent, percent) interface. 40 | * Removed RGB instance variable; color is managed internally as HSL floating 41 | point. 42 | * Tests! 43 | 44 | == color-tools 1.3.0 45 | * Added new metallic colours suggested by Jim Freeze . These 46 | are in the namespace Color::Metallic. 47 | * Colours that were defined in the Color namespace (e.g., Color::Red, 48 | Color::AliceBlue) are now defined in Color::RGB (e.g., Color::RGB::Red, 49 | Color::RGB::AliceBlue). They are added back to the Color namespace on the 50 | first use of the old colours and a warning is printed. In version 1.4, this 51 | warning will be printed on every use of the old colours. In version 1.5, 52 | the backwards compatible support for colours like Color::Red will be 53 | removed completely. 54 | * Added the Color::CSS module, color/css or Color::CSS that provides a name 55 | lookup of Color::RGB-namespaced constants with Color::CSS[name]. Most of 56 | these colours (which are mirrored from the Color::RGB default colours) are 57 | only "officially" recognised under the CSS3 colour module or SVG. 58 | * Added the Color::HSL colour space and some helper utilities to Color::RGB 59 | for colour manipulation using the HSL value. 60 | * Controlled internal value replacement to be between 0 and 1 for all 61 | colours. 62 | * Updated Color::Palette::Gimp to more meaningfully deal with duplicate named 63 | colours. Named colours now return an array of colours. 64 | * Indicated the plans for some methods and constants out to color-tools 2.0. 65 | * Added unit tests and fixed a number of hidden bugs because of them. 66 | 67 | == color-tools 1.2.0 68 | * Changed installer from a custom-written install.rb to setup.rb 69 | 3.3.1-modified. 70 | * Added Color::GreyScale (or Color::GrayScale). 71 | * Added Color::YIQ. This colour definition is incomplete; it does not have 72 | conversions from YIQ to other colour spaces. 73 | 74 | == color-tools 1.1.0 75 | * Added color/palette/gimp to support the reading and use of GIMP color 76 | palettes. 77 | 78 | == color-tools 1.0.0 79 | * Initial release. 80 | 81 | == Copyright 82 | Color 83 | Colour Management with Ruby 84 | http://rubyforge.org/projects/color 85 | 86 | Licensed under a MIT-style licence. See Licence.txt in the main 87 | distribution for full licensing information. 88 | 89 | Copyright (c) 2005 - 2007 Austin Ziegler and Matt Lyon 90 | 91 | $Id: History.txt 50 2007-02-03 20:26:19Z austin $ 92 | -------------------------------------------------------------------------------- /lib/color.rb: -------------------------------------------------------------------------------- 1 | # :title: Color -- Colour Management with Ruby 2 | # :main: Readme.txt 3 | 4 | #-- 5 | # Color 6 | # Colour management with Ruby 7 | # http://rubyforge.org/projects/color 8 | # Version 1.4.0 9 | # 10 | # Licensed under a MIT-style licence. See Licence.txt in the main 11 | # distribution for full licensing information. 12 | # 13 | # Copyright (c) 2005 - 2007 Austin Ziegler and Matt Lyon 14 | # 15 | # $Id: test_all.rb 55 2007-02-03 23:29:34Z austin $ 16 | #++ 17 | 18 | # = Colour Management with Ruby 19 | module Color 20 | COLOR_VERSION = '1.4.0' 21 | 22 | class RGB; end 23 | class CMYK; end 24 | class GrayScale; end 25 | class YIQ; end 26 | 27 | # The maximum "resolution" for colour math; if any value is less than or 28 | # equal to this value, it is treated as zero. 29 | COLOR_EPSILON = 1e-5 30 | # The tolerance for comparing the components of two colours. In general, 31 | # colours are considered equal if all of their components are within this 32 | # tolerance value of each other. 33 | COLOR_TOLERANCE = 1e-4 34 | 35 | class << self 36 | # Returns +true+ if the value is less than COLOR_EPSILON. 37 | def near_zero?(value) 38 | (value.abs <= COLOR_EPSILON) 39 | end 40 | 41 | # Returns +true+ if the value is within COLOR_EPSILON of zero or less than 42 | # zero. 43 | def near_zero_or_less?(value) 44 | (value < 0.0 or near_zero?(value)) 45 | end 46 | 47 | # Returns +true+ if the value is within COLOR_EPSILON of one. 48 | def near_one?(value) 49 | near_zero?(value - 1.0) 50 | end 51 | 52 | # Returns +true+ if the value is within COLOR_EPSILON of one or more than 53 | # one. 54 | def near_one_or_more?(value) 55 | (value > 1.0 or near_one?(value)) 56 | end 57 | 58 | # Normalizes the value to the range (0.0) .. (1.0). 59 | def normalize(value) 60 | if near_zero_or_less? value 61 | 0.0 62 | elsif near_one_or_more? value 63 | 1.0 64 | else 65 | value 66 | end 67 | end 68 | alias normalize_fractional normalize 69 | 70 | def normalize_to_range(value, range) 71 | range = (range.end..range.begin) if (range.end < range.begin) 72 | 73 | if value <= range.begin 74 | range.begin 75 | elsif value >= range.end 76 | range.end 77 | else 78 | value 79 | end 80 | end 81 | 82 | # Normalize the value to the range (0) .. (255). 83 | def normalize_byte(value) 84 | normalize_to_range(value, 0..255).to_i 85 | end 86 | alias normalize_8bit normalize_byte 87 | 88 | # Normalize the value to the range (0) .. (65535). 89 | def normalize_word(value) 90 | normalize_to_range(value, 0..65535).to_i 91 | end 92 | alias normalize_16bit normalize_word 93 | end 94 | end 95 | 96 | require 'color/rgb' 97 | require 'color/cmyk' 98 | require 'color/grayscale' 99 | require 'color/hsl' 100 | require 'color/yiq' 101 | require 'color/rgb/metallic' 102 | 103 | module Color 104 | def self.const_missing(name) #:nodoc: 105 | case name 106 | when "VERSION", :VERSION, "COLOR_TOOLS_VERSION", :COLOR_TOOLS_VERSION 107 | warn "Color::#{name} has been deprecated. Use Color::COLOR_VERSION instead." 108 | Color::COLOR_VERSION 109 | else 110 | if Color::RGB.const_defined?(name) 111 | warn "Color::#{name} has been deprecated. Use Color::RGB::#{name} instead." 112 | Color::RGB.const_get(name) 113 | else 114 | super 115 | end 116 | end 117 | end 118 | 119 | # Provides a thin veneer over the Color module to make it seem like this 120 | # is Color 0.1.0 (a class) and not Color 1.4.0 (a module). This 121 | # "constructor" will be removed in the future. 122 | # 123 | # mode = :hsl:: +values+ must be an array of [ hue deg, sat %, lum % ]. 124 | # A Color::HSL object will be created. 125 | # mode = :rgb:: +values+ will either be an HTML-style colour string or 126 | # an array of [ red, green, blue ] (range 0 .. 255). A 127 | # Color::RGB object will be created. 128 | # mode = :cmyk:: +values+ must be an array of [ cyan %, magenta %, yellow 129 | # %, black % ]. A Color::CMYK object will be created. 130 | def self.new(values, mode = :rgb) 131 | warn "Color.new has been deprecated. Use Color::#{mode.to_s.upcase}.new instead." 132 | color = case mode 133 | when :hsl 134 | Color::HSL.new(*values) 135 | when :rgb 136 | values = [ values ].flatten 137 | if values.size == 1 138 | Color::RGB.from_html(*values) 139 | else 140 | Color::RGB.new(*values) 141 | end 142 | when :cmyk 143 | Color::CMYK.new(*values) 144 | end 145 | color.to_hsl 146 | end 147 | end 148 | -------------------------------------------------------------------------------- /test/test_cmyk.rb: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | #-- 3 | # Color 4 | # Colour management with Ruby 5 | # http://rubyforge.org/projects/color 6 | # Version 1.4.0 7 | # 8 | # Licensed under a MIT-style licence. See Licence.txt in the main 9 | # distribution for full licensing information. 10 | # 11 | # Copyright (c) 2005 - 2007 Austin Ziegler and Matt Lyon 12 | # 13 | # $Id: test_all.rb 55 2007-02-03 23:29:34Z austin $ 14 | #++ 15 | 16 | $LOAD_PATH.unshift("#{File.dirname(__FILE__)}/../lib") if __FILE__ == $0 17 | require 'test/unit' 18 | require 'color' 19 | 20 | module TestColor 21 | class TestCMYK < Test::Unit::TestCase 22 | def setup 23 | @cmyk = Color::CMYK.new(10, 20, 30, 40) 24 | end 25 | 26 | def test_cyan 27 | assert_in_delta(0.1, @cmyk.c, Color::COLOR_TOLERANCE) 28 | assert_in_delta(10, @cmyk.cyan, Color::COLOR_TOLERANCE) 29 | assert_nothing_raised { @cmyk.cyan = 20 } 30 | assert_in_delta(0.2, @cmyk.c, Color::COLOR_TOLERANCE) 31 | assert_nothing_raised { @cmyk.c = 2.0 } 32 | assert_in_delta(100, @cmyk.cyan, Color::COLOR_TOLERANCE) 33 | assert_nothing_raised { @cmyk.c = -1.0 } 34 | assert_in_delta(0.0, @cmyk.c, Color::COLOR_TOLERANCE) 35 | end 36 | 37 | def test_magenta 38 | assert_in_delta(0.2, @cmyk.m, Color::COLOR_TOLERANCE) 39 | assert_in_delta(20, @cmyk.magenta, Color::COLOR_TOLERANCE) 40 | assert_nothing_raised { @cmyk.magenta = 30 } 41 | assert_in_delta(0.3, @cmyk.m, Color::COLOR_TOLERANCE) 42 | assert_nothing_raised { @cmyk.m = 2.0 } 43 | assert_in_delta(100, @cmyk.magenta, Color::COLOR_TOLERANCE) 44 | assert_nothing_raised { @cmyk.m = -1.0 } 45 | assert_in_delta(0.0, @cmyk.m, Color::COLOR_TOLERANCE) 46 | end 47 | 48 | def test_yellow 49 | assert_in_delta(0.3, @cmyk.y, Color::COLOR_TOLERANCE) 50 | assert_in_delta(30, @cmyk.yellow, Color::COLOR_TOLERANCE) 51 | assert_nothing_raised { @cmyk.yellow = 20 } 52 | assert_in_delta(0.2, @cmyk.y, Color::COLOR_TOLERANCE) 53 | assert_nothing_raised { @cmyk.y = 2.0 } 54 | assert_in_delta(100, @cmyk.yellow, Color::COLOR_TOLERANCE) 55 | assert_nothing_raised { @cmyk.y = -1.0 } 56 | assert_in_delta(0.0, @cmyk.y, Color::COLOR_TOLERANCE) 57 | end 58 | 59 | def test_black 60 | assert_in_delta(0.4, @cmyk.k, Color::COLOR_TOLERANCE) 61 | assert_in_delta(40, @cmyk.black, Color::COLOR_TOLERANCE) 62 | assert_nothing_raised { @cmyk.black = 20 } 63 | assert_in_delta(0.2, @cmyk.k, Color::COLOR_TOLERANCE) 64 | assert_nothing_raised { @cmyk.k = 2.0 } 65 | assert_in_delta(100, @cmyk.black, Color::COLOR_TOLERANCE) 66 | assert_nothing_raised { @cmyk.k = -1.0 } 67 | assert_in_delta(0.0, @cmyk.k, Color::COLOR_TOLERANCE) 68 | end 69 | 70 | def test_pdf 71 | assert_equal("0.100 0.200 0.300 0.400 k", @cmyk.pdf_fill) 72 | assert_equal("0.100 0.200 0.300 0.400 K", @cmyk.pdf_stroke) 73 | end 74 | 75 | def test_to_cmyk 76 | assert(@cmyk.to_cmyk == @cmyk) 77 | end 78 | 79 | def test_to_grayscale 80 | gs = nil 81 | assert_nothing_raised { gs = @cmyk.to_grayscale } 82 | assert_kind_of(Color::GrayScale, gs) 83 | assert_in_delta(0.4185, gs.g, Color::COLOR_TOLERANCE) 84 | assert_kind_of(Color::GreyScale, @cmyk.to_greyscale) 85 | end 86 | 87 | def test_to_hsl 88 | hsl = nil 89 | assert_nothing_raised { hsl = @cmyk.to_hsl } 90 | assert_kind_of(Color::HSL, hsl) 91 | assert_in_delta(0.48, hsl.l, Color::COLOR_TOLERANCE) 92 | assert_in_delta(0.125, hsl.s, Color::COLOR_TOLERANCE) 93 | assert_in_delta(0.08333, hsl.h, Color::COLOR_TOLERANCE) 94 | assert_equal("hsl(30.00, 12.50%, 48.00%)", @cmyk.css_hsl) 95 | assert_equal("hsla(30.00, 12.50%, 48.00%, 1.00)", @cmyk.css_hsla) 96 | end 97 | 98 | def test_to_rgb 99 | rgb = nil 100 | assert_nothing_raised { rgb = @cmyk.to_rgb(true) } 101 | assert_kind_of(Color::RGB, rgb) 102 | assert_in_delta(0.5, rgb.r, Color::COLOR_TOLERANCE) 103 | assert_in_delta(0.4, rgb.g, Color::COLOR_TOLERANCE) 104 | assert_in_delta(0.3, rgb.b, Color::COLOR_TOLERANCE) 105 | 106 | assert_nothing_raised { rgb = @cmyk.to_rgb } 107 | assert_kind_of(Color::RGB, rgb) 108 | assert_in_delta(0.54, rgb.r, Color::COLOR_TOLERANCE) 109 | assert_in_delta(0.48, rgb.g, Color::COLOR_TOLERANCE) 110 | assert_in_delta(0.42, rgb.b, Color::COLOR_TOLERANCE) 111 | 112 | assert_equal("#8a7a6b", @cmyk.html) 113 | assert_equal("rgb(54.00%, 48.00%, 42.00%)", @cmyk.css_rgb) 114 | assert_equal("rgba(54.00%, 48.00%, 42.00%, 1.00)", @cmyk.css_rgba) 115 | end 116 | 117 | def test_inspect 118 | assert_equal("CMYK [10.00%, 20.00%, 30.00%, 40.00%]", @cmyk.inspect) 119 | end 120 | 121 | def test_to_yiq 122 | yiq = nil 123 | assert_nothing_raised { yiq = @cmyk.to_yiq } 124 | assert_kind_of(Color::YIQ, yiq) 125 | assert_in_delta(0.4911, yiq.y, Color::COLOR_TOLERANCE) 126 | assert_in_delta(0.05502, yiq.i, Color::COLOR_TOLERANCE) 127 | assert_in_delta(0.0, yiq.q, Color::COLOR_TOLERANCE) 128 | end 129 | end 130 | end 131 | -------------------------------------------------------------------------------- /test/test_color.rb: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | #-- 3 | # Color 4 | # Colour management with Ruby 5 | # http://rubyforge.org/projects/color 6 | # Version 1.4.0 7 | # 8 | # Licensed under a MIT-style licence. See Licence.txt in the main 9 | # distribution for full licensing information. 10 | # 11 | # Copyright (c) 2005 - 2007 Austin Ziegler and Matt Lyon 12 | # 13 | # $Id: test_all.rb 55 2007-02-03 23:29:34Z austin $ 14 | #++ 15 | 16 | $LOAD_PATH.unshift("#{File.dirname(__FILE__)}/../lib") if __FILE__ == $0 17 | require 'test/unit' 18 | require 'color' 19 | require 'color/css' 20 | 21 | module TestColor 22 | class TestColor < Test::Unit::TestCase 23 | def setup 24 | Kernel.module_eval do 25 | alias old_warn warn 26 | 27 | def warn(message) 28 | $last_warn = message 29 | end 30 | end 31 | end 32 | 33 | def teardown 34 | Kernel.module_eval do 35 | undef warn 36 | alias warn old_warn 37 | undef old_warn 38 | end 39 | end 40 | 41 | def test_const 42 | $last_warn = nil 43 | assert_equal(Color::RGB::AliceBlue, Color::AliceBlue) 44 | assert_equal("Color::AliceBlue has been deprecated. Use Color::RGB::AliceBlue instead.", $last_warn) 45 | 46 | $last_warn = nil # Do this twice to make sure it always happens... 47 | assert(Color::AliceBlue) 48 | assert_equal("Color::AliceBlue has been deprecated. Use Color::RGB::AliceBlue instead.", $last_warn) 49 | 50 | $last_warn = nil 51 | assert_equal(Color::COLOR_VERSION, Color::VERSION) 52 | assert_equal("Color::VERSION has been deprecated. Use Color::COLOR_VERSION instead.", $last_warn) 53 | 54 | $last_warn = nil 55 | assert_equal(Color::COLOR_VERSION, Color::COLOR_TOOLS_VERSION) 56 | assert_equal("Color::COLOR_TOOLS_VERSION has been deprecated. Use Color::COLOR_VERSION instead.", $last_warn) 57 | 58 | $last_warn = nil 59 | assert(Color::COLOR_VERSION) 60 | assert_nil($last_warn) 61 | assert(Color::COLOR_EPSILON) 62 | assert_nil($last_warn) 63 | 64 | assert_raises(NameError) { assert(Color::MISSING_VALUE) } 65 | end 66 | 67 | def test_normalize 68 | (1..10).each do |i| 69 | assert_equal(0.0, Color.normalize(-7 * i)) 70 | assert_equal(0.0, Color.normalize(-7 / i)) 71 | assert_equal(0.0, Color.normalize(0 - i)) 72 | assert_equal(1.0, Color.normalize(255 + i)) 73 | assert_equal(1.0, Color.normalize(256 * i)) 74 | assert_equal(1.0, Color.normalize(65536 / i)) 75 | end 76 | (0..255).each do |i| 77 | assert_in_delta(i / 255.0, Color.normalize(i / 255.0), 78 | 1e-2) 79 | end 80 | end 81 | 82 | def test_normalize_range 83 | assert_equal(0, Color.normalize_8bit(-1)) 84 | assert_equal(0, Color.normalize_8bit(0)) 85 | assert_equal(127, Color.normalize_8bit(127)) 86 | assert_equal(172, Color.normalize_8bit(172)) 87 | assert_equal(255, Color.normalize_8bit(255)) 88 | assert_equal(255, Color.normalize_8bit(256)) 89 | 90 | assert_equal(-100, Color.normalize_to_range(-101, -100..100)) 91 | assert_equal(-100, Color.normalize_to_range(-100.5, -100..100)) 92 | assert_equal(-100, Color.normalize_to_range(-100, -100..100)) 93 | assert_equal(-100, Color.normalize_to_range(-100.0, -100..100)) 94 | assert_equal(-99.5, Color.normalize_to_range(-99.5, -100..100)) 95 | assert_equal(-50, Color.normalize_to_range(-50, -100..100)) 96 | assert_equal(-50.5, Color.normalize_to_range(-50.5, -100..100)) 97 | assert_equal(0, Color.normalize_to_range(0, -100..100)) 98 | assert_equal(50, Color.normalize_to_range(50, -100..100)) 99 | assert_equal(50.5, Color.normalize_to_range(50.5, -100..100)) 100 | assert_equal(99, Color.normalize_to_range(99, -100..100)) 101 | assert_equal(99.5, Color.normalize_to_range(99.5, -100..100)) 102 | assert_equal(100, Color.normalize_to_range(100, -100..100)) 103 | assert_equal(100, Color.normalize_to_range(100.0, -100..100)) 104 | assert_equal(100, Color.normalize_to_range(100.5, -100..100)) 105 | assert_equal(100, Color.normalize_to_range(101, -100..100)) 106 | end 107 | 108 | def test_new 109 | $last_warn = nil 110 | c = Color.new("#fff") 111 | assert_kind_of(Color::HSL, c) 112 | assert_equal(Color::RGB::White.to_hsl, c) 113 | assert_equal("Color.new has been deprecated. Use Color::RGB.new instead.", $last_warn) 114 | 115 | $last_warn = nil 116 | c = Color.new([0, 0, 0]) 117 | assert_kind_of(Color::HSL, c) 118 | assert_equal(Color::RGB::Black.to_hsl, c) 119 | assert_equal("Color.new has been deprecated. Use Color::RGB.new instead.", $last_warn) 120 | 121 | $last_warn = nil 122 | c = Color.new([10, 20, 30], :hsl) 123 | assert_kind_of(Color::HSL, c) 124 | assert_equal(Color::HSL.new(10, 20, 30), c) 125 | assert_equal("Color.new has been deprecated. Use Color::HSL.new instead.", $last_warn) 126 | 127 | $last_warn = nil 128 | c = Color.new([10, 20, 30, 40], :cmyk) 129 | assert_kind_of(Color::HSL, c) 130 | assert_equal(Color::CMYK.new(10, 20, 30, 40).to_hsl, c) 131 | assert_equal("Color.new has been deprecated. Use Color::CMYK.new instead.", $last_warn) 132 | end 133 | end 134 | end 135 | -------------------------------------------------------------------------------- /test/test_hsl.rb: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | #-- 3 | # Color 4 | # Colour management with Ruby 5 | # http://rubyforge.org/projects/color 6 | # Version 1.4.0 7 | # 8 | # Licensed under a MIT-style licence. See Licence.txt in the main 9 | # distribution for full licensing information. 10 | # 11 | # Copyright (c) 2005 - 2007 Austin Ziegler and Matt Lyon 12 | # 13 | # HSL Tests provided by Adam Johnson 14 | # 15 | # $Id: test_all.rb 55 2007-02-03 23:29:34Z austin $ 16 | #++ 17 | 18 | $LOAD_PATH.unshift("#{File.dirname(__FILE__)}/../lib") if __FILE__ == $0 19 | require 'test/unit' 20 | require 'color' 21 | 22 | module TestColor 23 | class TestHSL < Test::Unit::TestCase 24 | def setup 25 | # @hsl = Color::HSL.new(262, 67, 42) 26 | @hsl = Color::HSL.new(145, 20, 30) 27 | # @rgb = Color::RGB.new(88, 35, 179) 28 | end 29 | 30 | def test_rgb_roundtrip_conversion 31 | hsl = Color::HSL.new(262, 67, 42) 32 | c = hsl.to_rgb.to_hsl 33 | assert_in_delta hsl.h, c.h, Color::COLOR_TOLERANCE, "Hue" 34 | assert_in_delta hsl.s, c.s, Color::COLOR_TOLERANCE, "Saturation" 35 | assert_in_delta hsl.l, c.l, Color::COLOR_TOLERANCE, "Luminance" 36 | end 37 | 38 | def test_brightness 39 | assert_in_delta 0.3, @hsl.brightness, Color::COLOR_TOLERANCE 40 | end 41 | 42 | def test_hue 43 | assert_in_delta 0.4027, @hsl.h, Color::COLOR_TOLERANCE 44 | assert_in_delta 145, @hsl.hue, Color::COLOR_TOLERANCE 45 | assert_nothing_raised { @hsl.hue = 33 } 46 | assert_in_delta 0.09167, @hsl.h, Color::COLOR_TOLERANCE 47 | assert_nothing_raised { @hsl.hue = -33 } 48 | assert_in_delta 0.90833, @hsl.h, Color::COLOR_TOLERANCE 49 | assert_nothing_raised { @hsl.h = 3.3 } 50 | assert_in_delta 360, @hsl.hue, Color::COLOR_TOLERANCE 51 | assert_nothing_raised { @hsl.h = -3.3 } 52 | assert_in_delta 0.0, @hsl.h, Color::COLOR_TOLERANCE 53 | assert_nothing_raised { @hsl.hue = 0 } 54 | assert_nothing_raised { @hsl.hue -= 20 } 55 | assert_in_delta 340, @hsl.hue, Color::COLOR_TOLERANCE 56 | assert_nothing_raised { @hsl.hue += 45 } 57 | assert_in_delta 25, @hsl.hue, Color::COLOR_TOLERANCE 58 | end 59 | 60 | def test_saturation 61 | assert_in_delta 0.2, @hsl.s, Color::COLOR_TOLERANCE 62 | assert_in_delta 20, @hsl.saturation, Color::COLOR_TOLERANCE 63 | assert_nothing_raised { @hsl.saturation = 33 } 64 | assert_in_delta 0.33, @hsl.s, Color::COLOR_TOLERANCE 65 | assert_nothing_raised { @hsl.s = 3.3 } 66 | assert_in_delta 100, @hsl.saturation, Color::COLOR_TOLERANCE 67 | assert_nothing_raised { @hsl.s = -3.3 } 68 | assert_in_delta 0.0, @hsl.s, Color::COLOR_TOLERANCE 69 | end 70 | 71 | def test_luminance 72 | assert_in_delta 0.3, @hsl.l, Color::COLOR_TOLERANCE 73 | assert_in_delta 30, @hsl.luminosity, Color::COLOR_TOLERANCE 74 | assert_nothing_raised { @hsl.luminosity = 33 } 75 | assert_in_delta 0.33, @hsl.l, Color::COLOR_TOLERANCE 76 | assert_nothing_raised { @hsl.l = 3.3 } 77 | assert_in_delta 100, @hsl.lightness, Color::COLOR_TOLERANCE 78 | assert_nothing_raised { @hsl.l = -3.3 } 79 | assert_in_delta 0.0, @hsl.l, Color::COLOR_TOLERANCE 80 | end 81 | 82 | def test_html_css 83 | assert_equal "hsl(145.00, 20.00%, 30.00%)", @hsl.css_hsl 84 | assert_equal "hsla(145.00, 20.00%, 30.00%, 1.00)", @hsl.css_hsla 85 | end 86 | 87 | def test_to_cmyk 88 | cmyk = nil 89 | assert_nothing_raised { cmyk = @hsl.to_cmyk } 90 | assert_kind_of Color::CMYK, cmyk 91 | assert_in_delta 0.3223, cmyk.c, Color::COLOR_TOLERANCE 92 | assert_in_delta 0.2023, cmyk.m, Color::COLOR_TOLERANCE 93 | assert_in_delta 0.2723, cmyk.y, Color::COLOR_TOLERANCE 94 | assert_in_delta 0.4377, cmyk.k, Color::COLOR_TOLERANCE 95 | end 96 | 97 | def test_to_grayscale 98 | gs = nil 99 | assert_nothing_raised { gs = @hsl.to_grayscale } 100 | assert_kind_of Color::GreyScale, gs 101 | assert_in_delta 30, gs.gray, Color::COLOR_TOLERANCE 102 | end 103 | 104 | def test_to_rgb 105 | rgb = nil 106 | assert_nothing_raised { rgb = @hsl.to_rgb } 107 | assert_kind_of Color::RGB, rgb 108 | assert_in_delta 0.24, rgb.r, Color::COLOR_TOLERANCE 109 | assert_in_delta 0.36, rgb.g, Color::COLOR_TOLERANCE 110 | assert_in_delta 0.29, rgb.b, Color::COLOR_TOLERANCE 111 | assert_equal "#3d5c4a", @hsl.html 112 | assert_equal "rgb(24.00%, 36.00%, 29.00%)", @hsl.css_rgb 113 | assert_equal "rgba(24.00%, 36.00%, 29.00%, 1.00)", @hsl.css_rgba 114 | # The following tests address a bug reported by Jean Krohn on June 6, 115 | # 2006 and excercise some previously unexercised code in to_rgb. 116 | assert_equal Color::RGB::Black, Color::HSL.new(75, 75, 0) 117 | assert_equal Color::RGB::White, Color::HSL.new(75, 75, 100) 118 | assert_equal Color::RGB::Gray80, Color::HSL.new(75, 0, 80) 119 | 120 | # The following tests a bug reported by Adam Johnson on 29 October 121 | # 2007. 122 | rgb = Color::RGB.from_fraction(0.34496, 0.1386, 0.701399) 123 | c = Color::HSL.new(262, 67, 42).to_rgb 124 | assert_in_delta rgb.r, c.r, Color::COLOR_TOLERANCE, "Red" 125 | assert_in_delta rgb.g, c.g, Color::COLOR_TOLERANCE, "Green" 126 | assert_in_delta rgb.b, c.b, Color::COLOR_TOLERANCE, "Blue" 127 | end 128 | 129 | def test_to_yiq 130 | yiq = nil 131 | assert_nothing_raised { yiq = @hsl.to_yiq } 132 | assert_kind_of Color::YIQ, yiq 133 | assert_in_delta 0.3161, yiq.y, Color::COLOR_TOLERANCE 134 | assert_in_delta 0.0, yiq.i, Color::COLOR_TOLERANCE 135 | assert_in_delta 0.0, yiq.q, Color::COLOR_TOLERANCE 136 | end 137 | 138 | def test_mix_with 139 | red = Color::RGB::Red.to_hsl 140 | yellow = Color::RGB::Yellow.to_hsl 141 | assert_in_delta 0, red.hue, Color::COLOR_TOLERANCE 142 | assert_in_delta 60, yellow.hue, Color::COLOR_TOLERANCE 143 | ry25 = red.mix_with yellow, 0.25 144 | assert_in_delta 15, ry25.hue, Color::COLOR_TOLERANCE 145 | ry50 = red.mix_with yellow, 0.50 146 | assert_in_delta 30, ry50.hue, Color::COLOR_TOLERANCE 147 | ry75 = red.mix_with yellow, 0.75 148 | assert_in_delta 45, ry75.hue, Color::COLOR_TOLERANCE 149 | end 150 | 151 | def test_inspect 152 | assert_equal "HSL [145.00 deg, 20.00%, 30.00%]", @hsl.inspect 153 | end 154 | end 155 | end 156 | -------------------------------------------------------------------------------- /lib/color/grayscale.rb: -------------------------------------------------------------------------------- 1 | #-- 2 | # Color 3 | # Colour management with Ruby 4 | # http://rubyforge.org/projects/color 5 | # Version 1.4.0 6 | # 7 | # Licensed under a MIT-style licence. See Licence.txt in the main 8 | # distribution for full licensing information. 9 | # 10 | # Copyright (c) 2005 - 2007 Austin Ziegler and Matt Lyon 11 | # 12 | # $Id: test_all.rb 55 2007-02-03 23:29:34Z austin $ 13 | #++ 14 | 15 | # A colour object representing shades of grey. Used primarily in PDF 16 | # document creation. 17 | class Color::GrayScale 18 | # The format of a DeviceGrey colour for PDF. In color-tools 2.0 this will 19 | # be removed from this package and added back as a modification by the 20 | # PDF::Writer package. 21 | PDF_FORMAT_STR = "%.3f %s" 22 | 23 | # Creates a greyscale colour object from fractional values 0..1. 24 | # 25 | # Color::GreyScale.from_fraction(0.5) 26 | def self.from_fraction(g = 0) 27 | color = Color::GrayScale.new 28 | color.g = g 29 | color 30 | end 31 | 32 | # Creates a greyscale colour object from percentages 0..100. 33 | # 34 | # Color::GrayScale.from_percent(50) 35 | def self.from_percent(g = 0) 36 | Color::GrayScale.new(g) 37 | end 38 | 39 | # Creates a greyscale colour object from percentages 0..100. 40 | # 41 | # Color::GrayScale.new(50) 42 | def initialize(g = 0) 43 | @g = g / 100.0 44 | end 45 | 46 | # Compares the other colour to this one. The other colour will be 47 | # converted to GreyScale before comparison, so the comparison between a 48 | # GreyScale colour and a non-GreyScale colour will be approximate and 49 | # based on the other colour's #to_greyscale conversion. If there is no 50 | # #to_greyscale conversion, this will raise an exception. This will report 51 | # that two GreyScale values are equivalent if they are within 52 | # COLOR_TOLERANCE of each other. 53 | def ==(other) 54 | other = other.to_grayscale 55 | other.kind_of?(Color::GrayScale) and 56 | ((@g - other.g).abs <= Color::COLOR_TOLERANCE) 57 | end 58 | 59 | # Present the colour as a DeviceGrey fill colour string for PDF. This will 60 | # be removed from the default package in color-tools 2.0. 61 | def pdf_fill 62 | PDF_FORMAT_STR % [ @g, "g" ] 63 | end 64 | 65 | # Present the colour as a DeviceGrey stroke colour string for PDF. This 66 | # will be removed from the default package in color-tools 2.0. 67 | def pdf_stroke 68 | PDF_FORMAT_STR % [ @g, "G" ] 69 | end 70 | 71 | def to_255 72 | [(@g * 255).round, 255].min 73 | end 74 | private :to_255 75 | 76 | # Present the colour as an HTML/CSS colour string. 77 | def html 78 | gs = "%02x" % to_255 79 | "##{gs * 3}" 80 | end 81 | 82 | # Present the colour as an RGB HTML/CSS colour string (e.g., "rgb(0%, 50%, 83 | # 100%)"). 84 | def css_rgb 85 | "rgb(%3.2f%%, %3.2f%%, %3.2f%%)" % [ gray, gray, gray ] 86 | end 87 | 88 | # Present the colour as an RGBA (with alpha) HTML/CSS colour string (e.g., 89 | # "rgb(0%, 50%, 100%, 1)"). 90 | def css_rgba 91 | "rgba(%3.2f%%, %3.2f%%, %3.2f%%, %1.2f)" % [ gray, gray, gray, 1 ] 92 | end 93 | 94 | # Present the colour as an HSL HTML/CSS colour string (e.g., "hsl(180, 95 | # 25%, 35%)"). Note that this will perform a #to_hsl operation. 96 | def css_hsl 97 | to_hsl.css_hsl 98 | end 99 | 100 | # Present the colour as an HSLA (with alpha) HTML/CSS colour string (e.g., 101 | # "hsla(180, 25%, 35%, 1)"). Note that this will perform a #to_hsl 102 | # operation. 103 | def css_hsla 104 | to_hsl.css_hsla 105 | end 106 | 107 | # Convert the greyscale colour to CMYK. 108 | def to_cmyk 109 | k = 1.0 - @g.to_f 110 | Color::CMYK.from_fraction(0, 0, 0, k) 111 | end 112 | 113 | # Convert the greyscale colour to RGB. 114 | def to_rgb(ignored = true) 115 | Color::RGB.from_fraction(g, g, g) 116 | end 117 | 118 | # Reflexive conversion. 119 | def to_grayscale 120 | self 121 | end 122 | alias to_greyscale to_grayscale 123 | 124 | # Lightens the greyscale colour by the stated percent. 125 | def lighten_by(percent) 126 | g = [@g + (@g * (percent / 100.0)), 1.0].min 127 | Color::GrayScale.from_fraction(g) 128 | end 129 | 130 | # Darken the greyscale colour by the stated percent. 131 | def darken_by(percent) 132 | g = [@g - (@g * (percent / 100.0)), 0.0].max 133 | Color::GrayScale.from_fraction(g) 134 | end 135 | 136 | # Returns the YIQ (NTSC) colour encoding of the greyscale value. This is 137 | # an approximation, as the values for I and Q are calculated by treating 138 | # the greyscale value as an RGB value. The Y (intensity or brightness) 139 | # value is the same as the greyscale value. 140 | def to_yiq 141 | y = @g 142 | i = (@g * 0.596) + (@g * -0.275) + (@g * -0.321) 143 | q = (@g * 0.212) + (@g * -0.523) + (@g * 0.311) 144 | Color::YIQ.from_fraction(y, i, q) 145 | end 146 | 147 | # Returns the HSL colour encoding of the greyscale value. 148 | def to_hsl 149 | Color::HSL.from_fraction(0, 0, @g) 150 | end 151 | 152 | # Returns the brightness value for this greyscale value; this is the 153 | # greyscale value itself. 154 | def brightness 155 | @g 156 | end 157 | 158 | # Returns the grayscale value as a percentage of white (100% gray is 159 | # white). 160 | def gray 161 | @g * 100.0 162 | end 163 | alias grey gray 164 | # Returns the grayscale value as a fractional value of white in the range 165 | # 0.0 .. 1.0. 166 | def g 167 | @g 168 | end 169 | # Sets the grayscale value as a percentage of white. 170 | def gray=(gg) 171 | @g = Color.normalize(gg / 100.0) 172 | end 173 | alias grey= gray= ; 174 | # Returns the grayscale value as a fractional value of white in the range 175 | # 0.0 .. 1.0. 176 | def g=(gg) 177 | @g = Color.normalize(gg) 178 | end 179 | 180 | # Adds another colour to the current colour. The other colour will be 181 | # converted to grayscale before addition. This conversion depends upon a 182 | # #to_grayscale method on the other colour. 183 | # 184 | # The addition is done using the grayscale accessor methods to ensure a 185 | # valid colour in the result. 186 | def +(other) 187 | other = other.to_grayscale 188 | ng = self.dup 189 | ng.g += other.g 190 | ng 191 | end 192 | 193 | # Subtracts another colour to the current colour. The other colour will be 194 | # converted to grayscale before subtraction. This conversion depends upon 195 | # a #to_grayscale method on the other colour. 196 | # 197 | # The subtraction is done using the grayscale accessor methods to ensure a 198 | # valid colour in the result. 199 | def -(other) 200 | other = other.to_grayscale 201 | ng = self.dup 202 | ng.g -= other.g 203 | ng 204 | end 205 | 206 | def inspect 207 | "Gray [%.2f%%]" % [ gray ] 208 | end 209 | end 210 | 211 | module Color 212 | # A synonym for Color::GrayScale. 213 | GreyScale = GrayScale 214 | end 215 | -------------------------------------------------------------------------------- /test/test_monocontrast.rb: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | #-- 3 | # Color 4 | # Colour management with Ruby 5 | # http://rubyforge.org/projects/color 6 | # Version 1.4.0 7 | # 8 | # Licensed under a MIT-style licence. See Licence.txt in the main 9 | # distribution for full licensing information. 10 | # 11 | # Copyright (c) 2005 - 2007 Austin Ziegler and Matt Lyon 12 | # 13 | # $Id: test_all.rb 55 2007-02-03 23:29:34Z austin $ 14 | #++ 15 | 16 | $LOAD_PATH.unshift("#{File.dirname(__FILE__)}/../lib") if __FILE__ == $0 17 | require 'test/unit' 18 | require 'color' 19 | require 'color/palette/monocontrast' 20 | 21 | module TestColor 22 | module TestPalette 23 | class TestMonoContrast < Test::Unit::TestCase 24 | include Color::Palette 25 | def setup 26 | @high = Color::RGB.from_html("#c9e3a6") 27 | @low = Color::RGB.from_html("#746b8e") 28 | @mcp1 = MonoContrast.new(@high) 29 | @mcp2 = MonoContrast.new(@low) 30 | end 31 | 32 | def test_background 33 | assert_equal("#141711", @mcp1.background[-5].html) 34 | assert_equal("#32392a", @mcp1.background[-4].html) 35 | assert_equal("#657253", @mcp1.background[-3].html) 36 | assert_equal("#97aa7d", @mcp1.background[-2].html) 37 | assert_equal("#abc18d", @mcp1.background[-1].html) 38 | assert_equal("#c9e3a6", @mcp1.background[ 0].html) 39 | assert_equal("#d1e7b3", @mcp1.background[+1].html) 40 | assert_equal("#d7eabc", @mcp1.background[+2].html) # d7eabd 41 | assert_equal("#e4f1d3", @mcp1.background[+3].html) # e5f2d3 42 | assert_equal("#f2f8e9", @mcp1.background[+4].html) # f1f8e9 43 | assert_equal("#fafcf6", @mcp1.background[+5].html) # fafdf7 44 | 45 | assert_equal("#0c0b0e", @mcp2.background[-5].html) 46 | assert_equal("#1d1b24", @mcp2.background[-4].html) 47 | assert_equal("#3a3647", @mcp2.background[-3].html) 48 | assert_equal("#57506b", @mcp2.background[-2].html) 49 | assert_equal("#635b79", @mcp2.background[-1].html) 50 | assert_equal("#746b8e", @mcp2.background[ 0].html) 51 | assert_equal("#89819f", @mcp2.background[+1].html) 52 | assert_equal("#9790aa", @mcp2.background[+2].html) # 9790ab 53 | assert_equal("#bab5c7", @mcp2.background[+3].html) # bab6c7 54 | assert_equal("#dcdae3", @mcp2.background[+4].html) 55 | assert_equal("#f1f0f4", @mcp2.background[+5].html) # f2f1f4 56 | end 57 | 58 | def test_brightness_diff 59 | bd1 = @mcp1.brightness_diff(@high, @low) 60 | bd2 = @mcp1.brightness_diff(@low, @high) 61 | assert_in_delta(bd1, bd2, Color::COLOR_TOLERANCE) 62 | end 63 | 64 | def test_calculate_foreground 65 | assert_equal("#ffffff", @mcp1.calculate_foreground(@low, @high).html) 66 | assert_equal("#1d1b24", @mcp1.calculate_foreground(@high, @low).html) 67 | end 68 | 69 | def test_color_diff 70 | assert_in_delta(@mcp1.color_diff(@low, @high), 71 | @mcp1.color_diff(@high, @low), 72 | Color::COLOR_TOLERANCE) 73 | end 74 | 75 | def test_foreground 76 | assert_equal("#c9e3a6", @mcp1.foreground[-5].html) 77 | assert_equal("#e4f1d3", @mcp1.foreground[-4].html) # e5f2d3 78 | assert_equal("#ffffff", @mcp1.foreground[-3].html) 79 | assert_equal("#000000", @mcp1.foreground[-2].html) 80 | assert_equal("#000000", @mcp1.foreground[-1].html) 81 | assert_equal("#000000", @mcp1.foreground[ 0].html) 82 | assert_equal("#000000", @mcp1.foreground[+1].html) 83 | assert_equal("#000000", @mcp1.foreground[+2].html) 84 | assert_equal("#32392a", @mcp1.foreground[+3].html) 85 | assert_equal("#32392a", @mcp1.foreground[+4].html) 86 | assert_equal("#32392a", @mcp1.foreground[+5].html) 87 | 88 | assert_equal("#bab5c7", @mcp2.foreground[-5].html) # bab6c7 89 | assert_equal("#dcdae3", @mcp2.foreground[-4].html) 90 | assert_equal("#ffffff", @mcp2.foreground[-3].html) 91 | assert_equal("#ffffff", @mcp2.foreground[-2].html) 92 | assert_equal("#ffffff", @mcp2.foreground[-1].html) 93 | assert_equal("#ffffff", @mcp2.foreground[ 0].html) 94 | assert_equal("#000000", @mcp2.foreground[+1].html) 95 | assert_equal("#000000", @mcp2.foreground[+2].html) 96 | assert_equal("#000000", @mcp2.foreground[+3].html) 97 | assert_equal("#1d1b24", @mcp2.foreground[+4].html) 98 | assert_equal("#3a3647", @mcp2.foreground[+5].html) 99 | end 100 | 101 | def test_minimum_brightness_diff 102 | assert_in_delta(MonoContrast::DEFAULT_MINIMUM_BRIGHTNESS_DIFF, 103 | @mcp1.minimum_brightness_diff, Color::COLOR_TOLERANCE) 104 | end 105 | 106 | def test_minimum_brightness_diff_equals 107 | assert_in_delta(MonoContrast::DEFAULT_MINIMUM_BRIGHTNESS_DIFF, 108 | @mcp1.minimum_brightness_diff, Color::COLOR_TOLERANCE) 109 | mcps = @mcp1.dup 110 | assert_nothing_raised { @mcp1.minimum_brightness_diff = 0.75 } 111 | assert_in_delta(0.75, @mcp1.minimum_brightness_diff, Color::COLOR_TOLERANCE) 112 | assert_not_equal(@mcp1.foreground[-5], mcps.foreground[-5]) 113 | assert_nothing_raised { @mcp1.minimum_brightness_diff = 4.0 } 114 | assert_in_delta(1, @mcp1.minimum_brightness_diff, Color::COLOR_TOLERANCE) 115 | assert_nothing_raised { @mcp1.minimum_brightness_diff = -4.0 } 116 | assert_in_delta(0, @mcp1.minimum_brightness_diff, Color::COLOR_TOLERANCE) 117 | assert_nothing_raised { @mcp1.minimum_brightness_diff = nil } 118 | assert_in_delta(MonoContrast::DEFAULT_MINIMUM_BRIGHTNESS_DIFF, 119 | @mcp1.minimum_brightness_diff, Color::COLOR_TOLERANCE) 120 | end 121 | 122 | def test_minimum_color_diff 123 | assert_in_delta(MonoContrast::DEFAULT_MINIMUM_COLOR_DIFF, 124 | @mcp1.minimum_color_diff, Color::COLOR_TOLERANCE) 125 | end 126 | 127 | def test_minimum_color_diff_equals 128 | assert_in_delta(MonoContrast::DEFAULT_MINIMUM_COLOR_DIFF, 129 | @mcp1.minimum_color_diff, Color::COLOR_TOLERANCE) 130 | mcps = @mcp1.dup 131 | assert_nothing_raised { @mcp1.minimum_color_diff = 0.75 } 132 | assert_in_delta(0.75, @mcp1.minimum_color_diff, Color::COLOR_TOLERANCE) 133 | assert_not_equal(@mcp1.foreground[-5], mcps.foreground[-5]) 134 | assert_nothing_raised { @mcp1.minimum_color_diff = 4.0 } 135 | assert_in_delta(3, @mcp1.minimum_color_diff, Color::COLOR_TOLERANCE) 136 | assert_nothing_raised { @mcp1.minimum_color_diff = -4.0 } 137 | assert_in_delta(0, @mcp1.minimum_color_diff, Color::COLOR_TOLERANCE) 138 | assert_nothing_raised { @mcp1.minimum_color_diff = nil } 139 | assert_in_delta(MonoContrast::DEFAULT_MINIMUM_COLOR_DIFF, 140 | @mcp1.minimum_color_diff, Color::COLOR_TOLERANCE) 141 | end 142 | end 143 | end 144 | end 145 | -------------------------------------------------------------------------------- /lib/color/palette/monocontrast.rb: -------------------------------------------------------------------------------- 1 | #-- 2 | # Color 3 | # Colour management with Ruby 4 | # http://rubyforge.org/projects/color 5 | # Version 1.4.0 6 | # 7 | # Licensed under a MIT-style licence. See Licence.txt in the main 8 | # distribution for full licensing information. 9 | # 10 | # Copyright (c) 2005 - 2007 Austin Ziegler and Matt Lyon 11 | # 12 | # $Id: test_all.rb 55 2007-02-03 23:29:34Z austin $ 13 | #++ 14 | 15 | require 'color/palette' 16 | 17 | # Generates a monochromatic constrasting colour palette for background and 18 | # foreground. What does this mean? 19 | # 20 | # Monochromatic: A single colour is used to generate the base palette, and 21 | # this colour is lightened five times and darkened five times to provide 22 | # eleven distinct colours. 23 | # 24 | # Contrasting: The foreground is also generated as a monochromatic colour 25 | # palette; however, all generated colours are tested to see that they are 26 | # appropriately contrasting to ensure maximum readability of the foreground 27 | # against the background. 28 | class Color::Palette::MonoContrast 29 | # Hash of CSS background colour values. 30 | # 31 | # This is always 11 values: 32 | # 33 | # 0:: The starting colour. 34 | # +1..+5:: Lighter colours. 35 | # -1..-5:: Darker colours. 36 | attr_reader :background 37 | # Hash of CSS foreground colour values. 38 | # 39 | # This is always 11 values: 40 | # 41 | # 0:: The starting colour. 42 | # +1..+5:: Lighter colours. 43 | # -1..-5:: Darker colours. 44 | attr_reader :foreground 45 | 46 | DEFAULT_MINIMUM_BRIGHTNESS_DIFF = (125.0 / 255.0) 47 | 48 | # The minimum brightness difference between the background and the 49 | # foreground, and must be between 0..1. Setting this value will regenerate 50 | # the palette based on the base colours. The default value for this is 125 51 | # / 255.0. If this value is set to +nil+, it will be restored to the 52 | # default. 53 | attr_accessor :minimum_brightness_diff 54 | remove_method :minimum_brightness_diff= ; 55 | def minimum_brightness_diff=(bd) #:nodoc: 56 | if bd.nil? 57 | @minimum_brightness_diff = DEFAULT_MINIMUM_BRIGHTNESS_DIFF 58 | elsif bd > 1.0 59 | @minimum_brightness_diff = 1.0 60 | elsif bd < 0.0 61 | @minimum_brightness_diff = 0.0 62 | else 63 | @minimum_brightness_diff = bd 64 | end 65 | 66 | regenerate(@background[0], @foreground[0]) 67 | end 68 | 69 | DEFAULT_MINIMUM_COLOR_DIFF = (500.0 / 255.0) 70 | 71 | # The minimum colour difference between the background and the foreground, 72 | # and must be between 0..3. Setting this value will regenerate the palette 73 | # based on the base colours. The default value for this is 500 / 255.0. 74 | attr_accessor :minimum_color_diff 75 | remove_method :minimum_color_diff= ; 76 | def minimum_color_diff=(cd) #:noco: 77 | if cd.nil? 78 | @minimum_color_diff = DEFAULT_MINIMUM_COLOR_DIFF 79 | elsif cd > 3.0 80 | @minimum_color_diff = 3.0 81 | elsif cd < 0.0 82 | @minimum_color_diff = 0.0 83 | else 84 | @minimum_color_diff = cd 85 | end 86 | regenerate(@background[0], @foreground[0]) 87 | end 88 | 89 | # Generate the initial palette. 90 | def initialize(background, foreground = nil) 91 | @minimum_brightness_diff = DEFAULT_MINIMUM_BRIGHTNESS_DIFF 92 | @minimum_color_diff = DEFAULT_MINIMUM_COLOR_DIFF 93 | 94 | regenerate(background, foreground) 95 | end 96 | 97 | # Generate the colour palettes. 98 | def regenerate(background, foreground = nil) 99 | foreground ||= background 100 | background = background.to_rgb 101 | foreground = foreground.to_rgb 102 | 103 | @background = {} 104 | @foreground = {} 105 | 106 | @background[-5] = background.darken_by(10) 107 | @background[-4] = background.darken_by(25) 108 | @background[-3] = background.darken_by(50) 109 | @background[-2] = background.darken_by(75) 110 | @background[-1] = background.darken_by(85) 111 | @background[ 0] = background 112 | @background[+1] = background.lighten_by(85) 113 | @background[+2] = background.lighten_by(75) 114 | @background[+3] = background.lighten_by(50) 115 | @background[+4] = background.lighten_by(25) 116 | @background[+5] = background.lighten_by(10) 117 | 118 | @foreground[-5] = calculate_foreground(@background[-5], foreground) 119 | @foreground[-4] = calculate_foreground(@background[-4], foreground) 120 | @foreground[-3] = calculate_foreground(@background[-3], foreground) 121 | @foreground[-2] = calculate_foreground(@background[-2], foreground) 122 | @foreground[-1] = calculate_foreground(@background[-1], foreground) 123 | @foreground[ 0] = calculate_foreground(@background[ 0], foreground) 124 | @foreground[+1] = calculate_foreground(@background[+1], foreground) 125 | @foreground[+2] = calculate_foreground(@background[+2], foreground) 126 | @foreground[+3] = calculate_foreground(@background[+3], foreground) 127 | @foreground[+4] = calculate_foreground(@background[+4], foreground) 128 | @foreground[+5] = calculate_foreground(@background[+5], foreground) 129 | end 130 | 131 | # Given a background colour and a foreground colour, modifies the 132 | # foreground colour so that it will have enough contrast to be seen 133 | # against the background colour. 134 | # 135 | # Uses #mininum_brightness_diff and #minimum_color_diff. 136 | def calculate_foreground(background, foreground) 137 | nfg = nil 138 | # Loop through brighter and darker versions of the foreground color. The 139 | # numbers here represent the amount of foreground color to mix with 140 | # black and white. 141 | [100, 75, 50, 25, 0].each do |percent| 142 | dfg = foreground.darken_by(percent) 143 | lfg = foreground.lighten_by(percent) 144 | 145 | dbd = brightness_diff(background, dfg) 146 | lbd = brightness_diff(background, lfg) 147 | 148 | if lbd > dbd 149 | nfg = lfg 150 | nbd = lbd 151 | else 152 | nfg = dfg 153 | nbd = dbd 154 | end 155 | 156 | ncd = color_diff(background, nfg) 157 | 158 | break if nbd >= @minimum_brightness_diff and ncd >= @minimum_color_diff 159 | end 160 | nfg 161 | end 162 | 163 | # Returns the absolute difference between the brightness levels of two 164 | # colours. This will be a decimal value between 0 and 1. W3C accessibility 165 | # guidelines for colour contrast[http://www.w3.org/TR/AERT#color-contrast] 166 | # suggest that this value be at least approximately 0.49 (125 / 255.0) for 167 | # proper contrast. 168 | def brightness_diff(c1, c2) 169 | (c1.brightness - c2.brightness).abs 170 | end 171 | 172 | # Returns the contrast between to colours, a decimal value between 0 and 173 | # 3. W3C accessibility guidelines for colour 174 | # contrast[http://www.w3.org/TR/AERT#color-contrast] suggest that this 175 | # value be at least approximately 1.96 (500 / 255.0) for proper contrast. 176 | def color_diff(c1, c2) 177 | r = (c1.r - c2.r).abs 178 | g = (c1.g - c2.g).abs 179 | b = (c1.b - c2.b).abs 180 | r + g + b 181 | end 182 | end 183 | -------------------------------------------------------------------------------- /lib/color/hsl.rb: -------------------------------------------------------------------------------- 1 | #-- 2 | # Color 3 | # Colour management with Ruby 4 | # http://rubyforge.org/projects/color 5 | # Version 1.4.0 6 | # 7 | # Licensed under a MIT-style licence. See Licence.txt in the main 8 | # distribution for full licensing information. 9 | # 10 | # Copyright (c) 2005 - 2007 Austin Ziegler and Matt Lyon 11 | # 12 | # $Id: test_all.rb 55 2007-02-03 23:29:34Z austin $ 13 | #++ 14 | 15 | # An HSL colour object. Internally, the hue (#h), saturation (#s), and 16 | # luminosity/lightness (#l) values are dealt with as fractional values in 17 | # the range 0..1. 18 | class Color::HSL 19 | class << self 20 | # Creates an HSL colour object from fractional values 0..1. 21 | def from_fraction(h = 0.0, s = 0.0, l = 0.0) 22 | colour = Color::HSL.new 23 | colour.h = h 24 | colour.s = s 25 | colour.l = l 26 | colour 27 | end 28 | end 29 | 30 | # Compares the other colour to this one. The other colour will be 31 | # converted to HSL before comparison, so the comparison between a HSL 32 | # colour and a non-HSL colour will be approximate and based on the other 33 | # colour's #to_hsl conversion. If there is no #to_hsl conversion, this 34 | # will raise an exception. This will report that two HSL values are 35 | # equivalent if all component values are within Color::COLOR_TOLERANCE of 36 | # each other. 37 | def ==(other) 38 | other = other.to_hsl 39 | other.kind_of?(Color::HSL) and 40 | ((@h - other.h).abs <= Color::COLOR_TOLERANCE) and 41 | ((@s - other.s).abs <= Color::COLOR_TOLERANCE) and 42 | ((@l - other.l).abs <= Color::COLOR_TOLERANCE) 43 | end 44 | 45 | # Creates an HSL colour object from the standard values of degrees and 46 | # percentages (e.g., 145 deg, 30%, 50%). 47 | def initialize(h = 0, s = 0, l = 0) 48 | @h = h / 360.0 49 | @s = s / 100.0 50 | @l = l / 100.0 51 | end 52 | 53 | # Present the colour as an HTML/CSS colour string. 54 | def html 55 | to_rgb.html 56 | end 57 | 58 | # Present the colour as an RGB HTML/CSS colour string (e.g., "rgb(0%, 50%, 59 | # 100%)"). Note that this will perform a #to_rgb operation using the 60 | # default conversion formula. 61 | def css_rgb 62 | to_rgb.css_rgb 63 | end 64 | 65 | # Present the colour as an RGBA (with alpha) HTML/CSS colour string (e.g., 66 | # "rgb(0%, 50%, 100%, 1)"). Note that this will perform a #to_rgb 67 | # operation using the default conversion formula. 68 | def css_rgba 69 | to_rgb.css_rgba 70 | end 71 | 72 | # Present the colour as an HSL HTML/CSS colour string (e.g., "hsl(180, 73 | # 25%, 35%)"). 74 | def css_hsl 75 | "hsl(%3.2f, %3.2f%%, %3.2f%%)" % [ hue, saturation, luminosity ] 76 | end 77 | 78 | # Present the colour as an HSLA (with alpha) HTML/CSS colour string (e.g., 79 | # "hsla(180, 25%, 35%, 1)"). 80 | def css_hsla 81 | "hsla(%3.2f, %3.2f%%, %3.2f%%, %3.2f)" % [ hue, saturation, luminosity, 1 ] 82 | end 83 | 84 | # Converting to HSL as adapted from Foley and Van-Dam from 85 | # http://www.bobpowell.net/RGBHSB.htm. 86 | # 87 | # NOTE: 88 | # * If the colour's luminosity is near zero, the colour is always black. 89 | # * If the colour's luminosity is near one, the colour is always white. 90 | # * If the colour's saturation is near zero, the colour is always a shade 91 | # of grey and is based only on the luminosity of the colour. 92 | # 93 | def to_rgb(ignored = nil) 94 | return Color::RGB.new if Color.near_zero_or_less?(@l) 95 | return Color::RGB.new(0xff, 0xff, 0xff) if Color.near_one_or_more?(@l) 96 | return Color::RGB.from_fraction(@l, @l, @l) if Color.near_zero?(@s) 97 | 98 | # Is the value less than 0.5? 99 | if Color.near_zero_or_less?(@l - 0.5) 100 | tmp2 = @l * (1.0 + @s.to_f) 101 | else 102 | tmp2 = @l + @s - (@l * @s.to_f) 103 | end 104 | tmp1 = 2.0 * @l - tmp2 105 | 106 | tmp3 = [ @h + (1.0 / 3.0), @h, @h - (1.0 / 3.0) ] 107 | 108 | rgb = tmp3.map { |hue| 109 | hue += 1.0 if Color.near_zero_or_less?(hue) 110 | hue -= 1.0 if Color.near_one_or_more?(hue) 111 | 112 | if Color.near_zero_or_less?((6.0 * hue) - 1.0) 113 | tmp1 + ((tmp2 - tmp1) * hue * 6.0) 114 | elsif Color.near_zero_or_less?((2.0 * hue) - 1.0) 115 | tmp2 116 | elsif Color.near_zero_or_less?((3.0 * hue) - 2.0) 117 | tmp1 + (tmp2 - tmp1) * ((2 / 3.0) - hue) * 6.0 118 | else 119 | tmp1 120 | end 121 | } 122 | 123 | Color::RGB.from_fraction(*rgb) 124 | end 125 | 126 | # Converts to RGB then YIQ. 127 | def to_yiq 128 | to_rgb.to_yiq 129 | end 130 | 131 | # Converts to RGB then CMYK. 132 | def to_cmyk 133 | to_rgb.to_cmyk 134 | end 135 | 136 | # Returns the luminosity (#l) of the colour. 137 | def brightness 138 | @l 139 | end 140 | def to_greyscale 141 | Color::GrayScale.from_fraction(@l) 142 | end 143 | alias to_grayscale to_greyscale 144 | 145 | # Returns the hue of the colour in degrees. 146 | def hue 147 | @h * 360.0 148 | end 149 | # Returns the hue of the colour in the range 0.0 .. 1.0. 150 | def h 151 | @h 152 | end 153 | # Sets the hue of the colour in degrees. Colour is perceived as a wheel, 154 | # so values should be set properly even with negative degree values. 155 | def hue=(hh) 156 | hh = hh / 360.0 157 | 158 | hh += 1.0 if hh < 0.0 159 | hh -= 1.0 if hh > 1.0 160 | 161 | @h = Color.normalize(hh) 162 | end 163 | # Sets the hue of the colour in the range 0.0 .. 1.0. 164 | def h=(hh) 165 | @h = Color.normalize(hh) 166 | end 167 | # Returns the percentage of saturation of the colour. 168 | def saturation 169 | @s * 100.0 170 | end 171 | # Returns the saturation of the colour in the range 0.0 .. 1.0. 172 | def s 173 | @s 174 | end 175 | # Sets the percentage of saturation of the colour. 176 | def saturation=(ss) 177 | @s = Color.normalize(ss / 100.0) 178 | end 179 | # Sets the saturation of the colour in the ragne 0.0 .. 1.0. 180 | def s=(ss) 181 | @s = Color.normalize(ss) 182 | end 183 | 184 | # Returns the percentage of luminosity of the colour. 185 | def luminosity 186 | @l * 100.0 187 | end 188 | alias lightness luminosity 189 | # Returns the luminosity of the colour in the range 0.0 .. 1.0. 190 | def l 191 | @l 192 | end 193 | # Sets the percentage of luminosity of the colour. 194 | def luminosity=(ll) 195 | @l = Color.normalize(ll / 100.0) 196 | end 197 | alias lightness= luminosity= ; 198 | # Sets the luminosity of the colour in the ragne 0.0 .. 1.0. 199 | def l=(ll) 200 | @l = Color.normalize(ll) 201 | end 202 | 203 | def to_hsl 204 | self 205 | end 206 | 207 | def inspect 208 | "HSL [%.2f deg, %.2f%%, %.2f%%]" % [ hue, saturation, luminosity ] 209 | end 210 | 211 | # Mix the mask colour (which will be converted to an HSL colour) with the 212 | # current colour at the stated mix percentage as a decimal value. 213 | # 214 | # NOTE:: This differs from Color::RGB#mix_with. 215 | def mix_with(color, mix_percent = 0.5) 216 | color = color.to_hsl 217 | _h = ((color.h - self.h) * mix_percent) + self.h 218 | _s = ((color.s - self.s) * mix_percent) + self.s 219 | _l = ((color.l - self.l) * mix_percent) + self.l 220 | 221 | self.class.from_fraction(_h, _s, _l) 222 | end 223 | end 224 | -------------------------------------------------------------------------------- /lib/color/palette/adobecolor.rb: -------------------------------------------------------------------------------- 1 | #-- 2 | # Color 3 | # Colour management with Ruby 4 | # http://rubyforge.org/projects/color 5 | # Version 1.4.0 6 | # 7 | # Licensed under a MIT-style licence. See Licence.txt in the main 8 | # distribution for full licensing information. 9 | # 10 | # Copyright (c) 2005 - 2007 Austin Ziegler and Matt Lyon 11 | # 12 | # $Id: test_all.rb 55 2007-02-03 23:29:34Z austin $ 13 | #++ 14 | 15 | require 'color/palette' 16 | 17 | # A class that can read an Adobe Color palette file (used for Photoshop 18 | # swatches) and provide a Hash-like interface to the contents. Not all 19 | # colour formats in ACO files are supported. Based largely off the 20 | # information found by Larry Tesler[http://www.nomodes.com/aco.html]. 21 | # 22 | # Not all Adobe Color files have named colours; all named entries are 23 | # returned as an array. 24 | # 25 | # pal = Color::Palette::AdobeColor.from_file(my_aco_palette) 26 | # pal[0] => Color::RGB<...> 27 | # pal["white"] => [ Color::RGB<...> ] 28 | # pal["unknown"] => [ Color::RGB<...>, Color::RGB<...>, ... ] 29 | # 30 | # AdobeColor palettes are always indexable by insertion order (an integer 31 | # key). 32 | # 33 | # Version 2 palettes use UTF-16 colour names. 34 | class Color::Palette::AdobeColor 35 | include Enumerable 36 | 37 | class << self 38 | # Create an AdobeColor palette object from the named file. 39 | def from_file(filename) 40 | File.open(filename, "rb") { |io| Color::Palette::AdobeColor.from_io(io) } 41 | end 42 | 43 | # Create an AdobeColor palette object from the provided IO. 44 | def from_io(io) 45 | Color::Palette::AdobeColor.new(io.read) 46 | end 47 | end 48 | 49 | # Returns statistics about the nature of the colours loaded. 50 | attr_reader :statistics 51 | # Contains the "lost" colours in the palette. These colours could not be 52 | # properly loaded (e.g., L*a*b* is not supported by Color, so it is 53 | # "lost") or are not understood by the algorithms. 54 | attr_reader :lost 55 | 56 | # Use this to convert the unsigned word to the signed word, if necessary. 57 | UwToSw = proc { |n| (n >= (2 ** 16)) ? n - (2 ** 32) : n } #:nodoc: 58 | 59 | # Create a new AdobeColor palette from the palette file as a string. 60 | def initialize(palette) 61 | @colors = [] 62 | @names = {} 63 | @statistics = Hash.new(0) 64 | @lost = [] 65 | @order = [] 66 | @version = nil 67 | 68 | class << palette 69 | def readwords(count = 1) 70 | @offset ||= 0 71 | raise IndexError if @offset >= self.size 72 | val = self[@offset, count * 2] 73 | raise IndexError if val.nil? or val.size < (count * 2) 74 | val = val.unpack("n" * count) 75 | @offset += count * 2 76 | val 77 | end 78 | 79 | def readutf16(count = 1) 80 | @offset ||= 0 81 | raise IndexError if @offset >= self.size 82 | val = self[@offset, count * 2] 83 | raise IndexError if val.nil? or val.size < (count * 2) 84 | @offset += count * 2 85 | val 86 | end 87 | end 88 | 89 | @version, count = palette.readwords 2 90 | 91 | raise "Unknown AdobeColor palette version #@version." unless @version.between?(1, 2) 92 | 93 | count.times do 94 | space, w, x, y, z = palette.readwords 5 95 | name = nil 96 | if @version == 2 97 | raise IndexError unless palette.readwords == [ 0 ] 98 | len = palette.readwords 99 | name = palette.readutf16(len[0] - 1) 100 | raise IndexError unless palette.readwords == [ 0 ] 101 | end 102 | 103 | color = case space 104 | when 0 then # RGB 105 | @statistics[:rgb] += 1 106 | 107 | Color::RGB.new(w / 256, x / 256, y / 256) 108 | when 1 then # HS[BV] -- Convert to RGB 109 | @statistics[:hsb] += 1 110 | 111 | h = w / 65535.0 112 | s = x / 65535.0 113 | v = y / 65535.0 114 | 115 | if defined?(Color::HSB) 116 | Color::HSB.from_fraction(h, s, v) 117 | else 118 | @statistics[:converted] += 1 119 | if Color.near_zero_or_less?(s) 120 | Color::RGB.from_fraction(v, v, v) 121 | else 122 | if Color.near_one_or_more?(h) 123 | vh = 0 124 | else 125 | vh = h * 6.0 126 | end 127 | 128 | vi = vh.floor 129 | v1 = v.to_f * (1 - s.to_f) 130 | v2 = v.to_f * (1 - s.to_f * (vh - vi)) 131 | v3 = v.to_f * (1 - s.to_f * (1 - (vh - vi))) 132 | 133 | case vi 134 | when 0 then Color::RGB.from_fraction(v, v3, v1) 135 | when 1 then Color::RGB.from_fraction(v2, v, v1) 136 | when 2 then Color::RGB.from_fraction(v1, v, v3) 137 | when 3 then Color::RGB.from_fraction(v1, v2, v) 138 | when 4 then Color::RGB.from_fraction(v3, v1, v) 139 | else Color::RGB.from_fraction(v, v1, v2) 140 | end 141 | end 142 | end 143 | when 2 then # CMYK 144 | @statistics[:cmyk] += 1 145 | Color::CMYK.from_percent(100 - (w / 655.35), 146 | 100 - (x / 655.35), 147 | 100 - (y / 655.35), 148 | 100 - (z / 655.35)) 149 | when 7 then # L*a*b* 150 | @statistics[:lab] += 1 151 | 152 | l = [w, 10000].min / 100.0 153 | a = [[-12800, UwToSw[x]].max, 12700].min / 100.0 154 | b = [[-12800, UwToSw[x]].max, 12700].min / 100.0 155 | 156 | if defined? Color::Lab 157 | Color::Lab.new(l, a, b) 158 | else 159 | [ space, w, x, y, z ] 160 | end 161 | when 8 then # Grayscale 162 | @statistics[:gray] += 1 163 | 164 | g = [w, 10000].min / 100.0 165 | Color::GrayScale.new(g) 166 | when 9 then # Wide CMYK 167 | @statistics[:wcmyk] += 1 168 | 169 | c = [w, 10000].min / 100.0 170 | m = [x, 10000].min / 100.0 171 | y = [y, 10000].min / 100.0 172 | k = [z, 10000].min / 100.0 173 | Color::CMYK.from_percent(c, m, y, k) 174 | else 175 | @statistics[space] += 1 176 | [ space, w, x, y, z ] 177 | end 178 | 179 | @order << [ color, name ] 180 | 181 | if color.kind_of? Array 182 | @lost << color 183 | else 184 | @colors << color 185 | 186 | if name 187 | @names[name] ||= [] 188 | @names[name] << color 189 | end 190 | end 191 | end 192 | end 193 | 194 | # Provides the colour or colours at the provided selectors. 195 | def values_at(*selectors) 196 | @colors.values_at(*selectors) 197 | end 198 | 199 | # If a Numeric +key+ is provided, the single colour value at that position 200 | # will be returned. If a String +key+ is provided, the colour set (an 201 | # array) for that colour name will be returned. 202 | def [](key) 203 | if key.kind_of?(Numeric) 204 | @colors[key] 205 | else 206 | @names[key] 207 | end 208 | end 209 | 210 | # Loops through each colour. 211 | def each 212 | @colors.each { |el| yield el } 213 | end 214 | 215 | # Loops through each named colour set. 216 | def each_name #:yields color_name, color_set:# 217 | @names.each { |color_name, color_set| yield color_name, color_set } 218 | end 219 | 220 | def size 221 | @colors.size 222 | end 223 | 224 | attr_reader :version 225 | 226 | def to_aco(version = @version) #:nodoc: 227 | res = "" 228 | 229 | res << [ version, @order.size ].pack("nn") 230 | 231 | @order.each do |cnpair| 232 | color, name = *cnpair 233 | 234 | # Note: HSB and CMYK formats are lost by the conversions performed on 235 | # import. They are turned into RGB and WCMYK, respectively. 236 | 237 | cstr = case color 238 | when Array 239 | color 240 | when Color::RGB 241 | r = [(color.red * 256).round, 65535].min 242 | g = [(color.green * 256).round, 65535].min 243 | b = [(color.blue * 256).round, 65535].min 244 | [ 0, r, g, b, 0 ] 245 | when Color::GrayScale 246 | g = [(color.gray * 100).round, 10000].min 247 | [ 8, g, 0, 0, 0 ] 248 | when Color::CMYK 249 | c = [(color.cyan * 100).round, 10000].min 250 | m = [(color.magenta * 100).round, 10000].min 251 | y = [(color.yellow * 100).round, 10000].min 252 | k = [(color.black * 100).round, 10000].min 253 | [ 9, c, m, y, k ] 254 | end 255 | cstr = cstr.pack("nnnnn") 256 | 257 | nstr = "" 258 | 259 | if version == 2 260 | if (name.size / 2 * 2) == name.size # only where s[0] == byte! 261 | nstr << [ 0, (name.size / 2) + 1 ].pack("nn") 262 | nstr << name 263 | nstr << [ 0 ].pack("n") 264 | else 265 | nstr << [ 0, 1, 0 ].pack("nnn") 266 | end 267 | end 268 | 269 | res << cstr << nstr 270 | end 271 | 272 | res 273 | end 274 | end 275 | -------------------------------------------------------------------------------- /lib/color/cmyk.rb: -------------------------------------------------------------------------------- 1 | #-- 2 | # Color 3 | # Colour management with Ruby 4 | # http://rubyforge.org/projects/color 5 | # Version 1.4.0 6 | # 7 | # Licensed under a MIT-style licence. See Licence.txt in the main 8 | # distribution for full licensing information. 9 | # 10 | # Copyright (c) 2005 - 2007 Austin Ziegler and Matt Lyon 11 | # 12 | # $Id: test_all.rb 55 2007-02-03 23:29:34Z austin $ 13 | #++ 14 | 15 | # An CMYK colour object. CMYK (cyan, magenta, yellow, and black) colours are 16 | # based on additive percentages of ink. A CMYK colour of (0.3, 0, 0.8, 0.3) 17 | # would be mixed from 30% cyan, 0% magenta, 80% yellow, and 30% black. 18 | # Primarily used in four-colour printing processes. 19 | class Color::CMYK 20 | # The format of a DeviceCMYK colour for PDF. In color-tools 2.0 this will 21 | # be removed from this package and added back as a modification by the 22 | # PDF::Writer package. 23 | PDF_FORMAT_STR = "%.3f %.3f %.3f %.3f %s" 24 | 25 | # Compares the other colour to this one. The other colour will be 26 | # converted to CMYK before comparison, so the comparison between a CMYK 27 | # colour and a non-CMYK colour will be approximate and based on the other 28 | # colour's #to_cmyk conversion. If there is no #to_cmyk conversion, this 29 | # will raise an exception. This will report that two CMYK colours are 30 | # equivalent if all component values are within COLOR_TOLERANCE of each 31 | # other. 32 | def ==(other) 33 | other = other.to_cmyk 34 | other.kind_of?(Color::CMYK) and 35 | ((@c - other.c).abs <= Color::COLOR_TOLERANCE) and 36 | ((@m - other.m).abs <= Color::COLOR_TOLERANCE) and 37 | ((@y - other.y).abs <= Color::COLOR_TOLERANCE) and 38 | ((@k - other.k).abs <= Color::COLOR_TOLERANCE) 39 | end 40 | 41 | # Creates a CMYK colour object from fractional values 0..1. 42 | # 43 | # Color::CMYK.from_fraction(0.3, 0, 0.8, 0.3) 44 | def self.from_fraction(c = 0, m = 0, y = 0, k = 0) 45 | colour = Color::CMYK.new 46 | colour.c = c 47 | colour.m = m 48 | colour.y = y 49 | colour.k = k 50 | colour 51 | end 52 | 53 | # Creates a CMYK colour object from percentages. Internally, the colour is 54 | # managed as fractional values 0..1. 55 | # 56 | # Color::CMYK.new(30, 0, 80, 30) 57 | def self.from_percent(c = 0, m = 0, y = 0, k = 0) 58 | Color::CMYK.new(c, m, y, k) 59 | end 60 | 61 | # Creates a CMYK colour object from percentages. Internally, the colour is 62 | # managed as fractional values 0..1. 63 | # 64 | # Color::CMYK.new(30, 0, 80, 30) 65 | def initialize(c = 0, m = 0, y = 0, k = 0) 66 | @c = c / 100.0 67 | @m = m / 100.0 68 | @y = y / 100.0 69 | @k = k / 100.0 70 | end 71 | 72 | # Present the colour as a DeviceCMYK fill colour string for PDF. This will 73 | # be removed from the default package in color-tools 2.0. 74 | def pdf_fill 75 | PDF_FORMAT_STR % [ @c, @m, @y, @k, "k" ] 76 | end 77 | 78 | # Present the colour as a DeviceCMYK stroke colour string for PDF. This 79 | # will be removed from the default package in color-tools 2.0. 80 | def pdf_stroke 81 | PDF_FORMAT_STR % [ @c, @m, @y, @k, "K" ] 82 | end 83 | 84 | # Present the colour as an RGB HTML/CSS colour string (e.g., "#aabbcc"). 85 | # Note that this will perform a #to_rgb operation using the default 86 | # conversion formula. 87 | def html 88 | to_rgb.html 89 | end 90 | 91 | # Present the colour as an RGB HTML/CSS colour string (e.g., "rgb(0%, 50%, 92 | # 100%)"). Note that this will perform a #to_rgb operation using the 93 | # default conversion formula. 94 | def css_rgb 95 | to_rgb.css_rgb 96 | end 97 | 98 | # Present the colour as an RGBA (with alpha) HTML/CSS colour string (e.g., 99 | # "rgb(0%, 50%, 100%, 1)"). Note that this will perform a #to_rgb 100 | # operation using the default conversion formula. 101 | def css_rgba 102 | to_rgb.css_rgba 103 | end 104 | 105 | # Present the colour as an HSL HTML/CSS colour string (e.g., "hsl(180, 106 | # 25%, 35%)"). Note that this will perform a #to_hsl operation using the 107 | # default conversion formula. 108 | def css_hsl 109 | to_hsl.css_hsl 110 | end 111 | 112 | # Present the colour as an HSLA (with alpha) HTML/CSS colour string (e.g., 113 | # "hsla(180, 25%, 35%, 1)"). Note that this will perform a #to_hsl 114 | # operation using the default conversion formula. 115 | def css_hsla 116 | to_hsl.css_hsla 117 | end 118 | 119 | # Converts the CMYK colour to RGB. Most colour experts strongly suggest 120 | # that this is not a good idea (some even suggesting that it's a very bad 121 | # idea). CMYK represents additive percentages of inks on white paper, 122 | # whereas RGB represents mixed colour intensities on a black screen. 123 | # 124 | # However, the colour conversion can be done, and there are two different 125 | # methods for the conversion that provide slightly different results. 126 | # Adobe PDF conversions are done with the first form. 127 | # 128 | # # Adobe PDF Display Formula 129 | # r = 1.0 - min(1.0, c + k) 130 | # g = 1.0 - min(1.0, m + k) 131 | # b = 1.0 - min(1.0, y + k) 132 | # 133 | # # Other 134 | # r = 1.0 - (c * (1.0 - k) + k) 135 | # g = 1.0 - (m * (1.0 - k) + k) 136 | # b = 1.0 - (y * (1.0 - k) + k) 137 | # 138 | # If we have a CMYK colour of [33% 66% 83% 25%], the first method will 139 | # give an approximate RGB colour of (107, 23, 0) or #6b1700. The second 140 | # method will give an approximate RGB colour of (128, 65, 33) or #804121. 141 | # Which is correct? Although the colours may seem to be drastically 142 | # different in the RGB colour space, they are very similar colours, 143 | # differing mostly in intensity. The first is a darker, slightly redder 144 | # brown; the second is a lighter brown. 145 | # 146 | # Because of this subtlety, both methods are now offered for conversion. 147 | # The Adobe method is not used by default; to enable it, pass +true+ to 148 | # #to_rgb. 149 | # 150 | # Future versions of Color may offer other conversion mechanisms that 151 | # offer greater colour fidelity, including recognition of ICC colour 152 | # profiles. 153 | def to_rgb(use_adobe_method = false) 154 | if use_adobe_method 155 | r = 1.0 - [1.0, @c + @k].min 156 | g = 1.0 - [1.0, @m + @k].min 157 | b = 1.0 - [1.0, @y + @k].min 158 | else 159 | r = 1.0 - (@c.to_f * (1.0 - @k.to_f) + @k.to_f) 160 | g = 1.0 - (@m.to_f * (1.0 - @k.to_f) + @k.to_f) 161 | b = 1.0 - (@y.to_f * (1.0 - @k.to_f) + @k.to_f) 162 | end 163 | Color::RGB.from_fraction(r, g, b) 164 | end 165 | 166 | # Converts the CMYK colour to a single greyscale value. There are 167 | # undoubtedly multiple methods for this conversion, but only a minor 168 | # variant of the Adobe conversion method will be used: 169 | # 170 | # g = 1.0 - min(1.0, 0.299 * c + 0.587 * m + 0.114 * y + k) 171 | # 172 | # This treats the CMY values similarly to YIQ (NTSC) values and then adds 173 | # the level of black. This is a variant of the Adobe version because it 174 | # uses the more precise YIQ (NTSC) conversion values for Y (intensity) 175 | # rather than the approximates provided by Adobe (0.3, 0.59, and 0.11). 176 | def to_grayscale 177 | c = 0.299 * @c.to_f 178 | m = 0.587 * @m.to_f 179 | y = 0.114 * @y.to_f 180 | g = 1.0 - [1.0, c + m + y + @k].min 181 | Color::GrayScale.from_fraction(g) 182 | end 183 | alias to_greyscale to_grayscale 184 | 185 | def to_cmyk 186 | self 187 | end 188 | 189 | def inspect 190 | "CMYK [%.2f%%, %.2f%%, %.2f%%, %.2f%%]" % [ cyan, magenta, yellow, black ] 191 | end 192 | 193 | # Converts to RGB then YIQ. 194 | def to_yiq 195 | to_rgb.to_yiq 196 | end 197 | 198 | # Converts to RGB then HSL. 199 | def to_hsl 200 | to_rgb.to_hsl 201 | end 202 | 203 | # Returns the cyan (C) component of the CMYK colour as a percentage value. 204 | def cyan 205 | @c * 100.0 206 | end 207 | # Returns the cyan (C) component of the CMYK colour as a value in the 208 | # range 0.0 .. 1.0. 209 | def c 210 | @c 211 | end 212 | # Sets the cyan (C) component of the CMYK colour as a percentage value. 213 | def cyan=(cc) 214 | @c = Color.normalize(cc / 100.0) 215 | end 216 | # Sets the cyan (C) component of the CMYK colour as a value in the range 217 | # 0.0 .. 1.0. 218 | def c=(cc) 219 | @c = Color.normalize(cc) 220 | end 221 | 222 | # Returns the magenta (M) component of the CMYK colour as a percentage 223 | # value. 224 | def magenta 225 | @m * 100.0 226 | end 227 | # Returns the magenta (M) component of the CMYK colour as a value in the 228 | # range 0.0 .. 1.0. 229 | def m 230 | @m 231 | end 232 | # Sets the magenta (M) component of the CMYK colour as a percentage value. 233 | def magenta=(mm) 234 | @m = Color.normalize(mm / 100.0) 235 | end 236 | # Sets the magenta (M) component of the CMYK colour as a value in the 237 | # range 0.0 .. 1.0. 238 | def m=(mm) 239 | @m = Color.normalize(mm) 240 | end 241 | 242 | # Returns the yellow (Y) component of the CMYK colour as a percentage 243 | # value. 244 | def yellow 245 | @y * 100.0 246 | end 247 | # Returns the yellow (Y) component of the CMYK colour as a value in the 248 | # range 0.0 .. 1.0. 249 | def y 250 | @y 251 | end 252 | # Sets the yellow (Y) component of the CMYK colour as a percentage value. 253 | def yellow=(yy) 254 | @y = Color.normalize(yy / 100.0) 255 | end 256 | # Sets the yellow (Y) component of the CMYK colour as a value in the range 257 | # 0.0 .. 1.0. 258 | def y=(kk) 259 | @y = Color.normalize(kk) 260 | end 261 | 262 | # Returns the black (K) component of the CMYK colour as a percentage 263 | # value. 264 | def black 265 | @k * 100.0 266 | end 267 | # Returns the black (K) component of the CMYK colour as a value in the 268 | # range 0.0 .. 1.0. 269 | def k 270 | @k 271 | end 272 | # Sets the black (K) component of the CMYK colour as a percentage value. 273 | def black=(kk) 274 | @k = Color.normalize(kk / 100.0) 275 | end 276 | # Sets the black (K) component of the CMYK colour as a value in the range 277 | # 0.0 .. 1.0. 278 | def k=(kk) 279 | @k = Color.normalize(kk) 280 | end 281 | end 282 | -------------------------------------------------------------------------------- /lib/color/rgb-colors.rb: -------------------------------------------------------------------------------- 1 | #-- 2 | # Color 3 | # Colour management with Ruby 4 | # http://rubyforge.org/projects/color 5 | # Version 1.4.0 6 | # 7 | # Licensed under a MIT-style licence. See Licence.txt in the main 8 | # distribution for full licensing information. 9 | # 10 | # Copyright (c) 2005 - 2007 Austin Ziegler and Matt Lyon 11 | # 12 | # $Id: test_all.rb 55 2007-02-03 23:29:34Z austin $ 13 | #++ 14 | 15 | class Color::RGB 16 | AliceBlue = Color::RGB.new(0xf0, 0xf8, 0xff) 17 | AntiqueWhite = Color::RGB.new(0xfa, 0xeb, 0xd7) 18 | Aqua = Color::RGB.new(0x00, 0xff, 0xff) 19 | Aquamarine = Color::RGB.new(0x7f, 0xff, 0xd4) 20 | Azure = Color::RGB.new(0xf0, 0xff, 0xff) 21 | Beige = Color::RGB.new(0xf5, 0xf5, 0xdc) 22 | Bisque = Color::RGB.new(0xff, 0xe4, 0xc4) 23 | Black = Color::RGB.new(0, 0, 0) 24 | BlanchedAlmond = Color::RGB.new(0xff, 0xeb, 0xcd) 25 | Blue = Color::RGB.new(0x00, 0x00, 0xff) 26 | BlueViolet = Color::RGB.new(0x8a, 0x2b, 0xe2) 27 | Brown = Color::RGB.new(0xa5, 0x2a, 0x2a) 28 | BurlyWood = Color::RGB.new(0xde, 0xb8, 0x87) 29 | Burlywood = BurlyWood 30 | CadetBlue = Color::RGB.new(0x5f, 0x9e, 0xa0) 31 | Carnation = Color::RGB.new(0xff, 0x5e, 0xd0) 32 | Cayenne = Color::RGB.new(0x8d, 0x00, 0x00) 33 | Chartreuse = Color::RGB.new(0x7f, 0xff, 0x00) 34 | Chocolate = Color::RGB.new(0xd2, 0x69, 0x1e) 35 | Coral = Color::RGB.new(0xff, 0x7f, 0x50) 36 | CornflowerBlue = Color::RGB.new(0x64, 0x95, 0xed) 37 | Cornsilk = Color::RGB.new(0xff, 0xf8, 0xdc) 38 | Crimson = Color::RGB.new(0xdc, 0x14, 0x3c) 39 | Cyan = Color::RGB.new(0x00, 0xff, 0xff) 40 | DarkBlue = Color::RGB.new(0x00, 0x00, 0x8b) 41 | DarkCyan = Color::RGB.new(0x00, 0x8b, 0x8b) 42 | DarkGoldenRod = Color::RGB.new(0xb8, 0x86, 0x0b) 43 | DarkGoldenrod = DarkGoldenRod 44 | DarkGray = Color::RGB.new(0xa9, 0xa9, 0xa9) 45 | DarkGreen = Color::RGB.new(0x00, 0x64, 0x00) 46 | DarkGrey = DarkGray 47 | DarkKhaki = Color::RGB.new(0xbd, 0xb7, 0x6b) 48 | DarkMagenta = Color::RGB.new(0x8b, 0x00, 0x8b) 49 | DarkOliveGreen = Color::RGB.new(0x55, 0x6b, 0x2f) 50 | DarkOrange = Color::RGB.new(0xff, 0x8c, 0x00) 51 | DarkOrchid = Color::RGB.new(0x99, 0x32, 0xcc) 52 | DarkRed = Color::RGB.new(0x8b, 0x00, 0x00) 53 | DarkSalmon = Color::RGB.new(0xe9, 0x96, 0x7a) 54 | DarkSeaGreen = Color::RGB.new(0x8f, 0xbc, 0x8f) 55 | DarkSlateBlue = Color::RGB.new(0x48, 0x3d, 0x8b) 56 | DarkSlateGray = Color::RGB.new(0x2f, 0x4f, 0x4f) 57 | DarkSlateGrey = DarkSlateGray 58 | DarkTurquoise = Color::RGB.new(0x00, 0xce, 0xd1) 59 | DarkViolet = Color::RGB.new(0x94, 0x00, 0xd3) 60 | DarkoliveGreen = DarkOliveGreen 61 | Darkorange = Color::RGB.new(0xff, 0x8c, 0x00) 62 | Darksalmon = DarkSalmon 63 | DeepPink = Color::RGB.new(0xff, 0x14, 0x93) 64 | DeepSkyBlue = Color::RGB.new(0x00, 0xbf, 0xbf) 65 | DimGray = Color::RGB.new(0x69, 0x69, 0x69) 66 | DimGrey = DimGray 67 | DodgerBlue = Color::RGB.new(0x1e, 0x90, 0xff) 68 | Feldspar = Color::RGB.new(0xd1, 0x92, 0x75) 69 | FireBrick = Color::RGB.new(0xb2, 0x22, 0x22) 70 | Firebrick = FireBrick 71 | FloralWhite = Color::RGB.new(0xff, 0xfa, 0xf0) 72 | ForestGreen = Color::RGB.new(0x22, 0x8b, 0x22) 73 | Fuchsia = Color::RGB.new(0xff, 0x00, 0xff) 74 | Gainsboro = Color::RGB.new(0xdc, 0xdc, 0xdc) 75 | GhostWhite = Color::RGB.new(0xf8, 0xf8, 0xff) 76 | Gold = Color::RGB.new(0xff, 0xd7, 0x00) 77 | GoldenRod = Color::RGB.new(0xda, 0xa5, 0x20) 78 | Goldenrod = GoldenRod 79 | Gray = Color::RGB.new(0x80, 0x80, 0x80) 80 | Gray10 = Color::RGB.from_percentage(10, 10, 10) 81 | Gray20 = Color::RGB.from_percentage(20, 20, 20) 82 | Gray30 = Color::RGB.from_percentage(30, 30, 30) 83 | Gray40 = Color::RGB.from_percentage(40, 40, 40) 84 | Gray50 = Color::RGB.from_percentage(50, 50, 50) 85 | Gray60 = Color::RGB.from_percentage(60, 60, 60) 86 | Gray70 = Color::RGB.from_percentage(70, 70, 70) 87 | Gray80 = Color::RGB.from_percentage(80, 80, 80) 88 | Gray90 = Color::RGB.from_percentage(90, 90, 90) 89 | Green = Color::RGB.new(0x00, 0x80, 0x00) 90 | GreenYellow = Color::RGB.new(0xad, 0xff, 0x2f) 91 | Grey = Gray 92 | Grey10 = Gray10 93 | Grey20 = Gray20 94 | Grey30 = Gray30 95 | Grey40 = Gray40 96 | Grey50 = Gray50 97 | Grey60 = Gray60 98 | Grey70 = Gray70 99 | Grey80 = Gray80 100 | Grey90 = Gray90 101 | HoneyDew = Color::RGB.new(0xf0, 0xff, 0xf0) 102 | Honeydew = HoneyDew 103 | HotPink = Color::RGB.new(0xff, 0x69, 0xb4) 104 | IndianRed = Color::RGB.new(0xcd, 0x5c, 0x5c) 105 | Indigo = Color::RGB.new(0x4b, 0x00, 0x82) 106 | Ivory = Color::RGB.new(0xff, 0xff, 0xf0) 107 | Khaki = Color::RGB.new(0xf0, 0xe6, 0x8c) 108 | Lavender = Color::RGB.new(0xe6, 0xe6, 0xfa) 109 | LavenderBlush = Color::RGB.new(0xff, 0xf0, 0xf5) 110 | LawnGreen = Color::RGB.new(0x7c, 0xfc, 0x00) 111 | LemonChiffon = Color::RGB.new(0xff, 0xfa, 0xcd) 112 | LightBlue = Color::RGB.new(0xad, 0xd8, 0xe6) 113 | LightCoral = Color::RGB.new(0xf0, 0x80, 0x80) 114 | LightCyan = Color::RGB.new(0xe0, 0xff, 0xff) 115 | LightGoldenRodYellow = Color::RGB.new(0xfa, 0xfa, 0xd2) 116 | LightGoldenrodYellow = LightGoldenRodYellow 117 | LightGray = Color::RGB.new(0xd3, 0xd3, 0xd3) 118 | LightGreen = Color::RGB.new(0x90, 0xee, 0x90) 119 | LightGrey = LightGray 120 | LightPink = Color::RGB.new(0xff, 0xb6, 0xc1) 121 | LightSalmon = Color::RGB.new(0xff, 0xa0, 0x7a) 122 | LightSeaGreen = Color::RGB.new(0x20, 0xb2, 0xaa) 123 | LightSkyBlue = Color::RGB.new(0x87, 0xce, 0xfa) 124 | LightSlateBlue = Color::RGB.new(0x84, 0x70, 0xff) 125 | LightSlateGray = Color::RGB.new(0x77, 0x88, 0x99) 126 | LightSlateGrey = LightSlateGray 127 | LightSteelBlue = Color::RGB.new(0xb0, 0xc4, 0xde) 128 | LightYellow = Color::RGB.new(0xff, 0xff, 0xe0) 129 | Lightsalmon = LightSalmon 130 | LightsteelBlue = LightSteelBlue 131 | Lime = Color::RGB.new(0x00, 0xff, 0x00) 132 | LimeGreen = Color::RGB.new(0x32, 0xcd, 0x32) 133 | Linen = Color::RGB.new(0xfa, 0xf0, 0xe6) 134 | Magenta = Color::RGB.new(0xff, 0x00, 0xff) 135 | Maroon = Color::RGB.new(0x80, 0x00, 0x00) 136 | MediumAquaMarine = Color::RGB.new(0x66, 0xcd, 0xaa) 137 | MediumAquamarine = MediumAquaMarine 138 | MediumBlue = Color::RGB.new(0x00, 0x00, 0xcd) 139 | MediumOrchid = Color::RGB.new(0xba, 0x55, 0xd3) 140 | MediumPurple = Color::RGB.new(0x93, 0x70, 0xdb) 141 | MediumSeaGreen = Color::RGB.new(0x3c, 0xb3, 0x71) 142 | MediumSlateBlue = Color::RGB.new(0x7b, 0x68, 0xee) 143 | MediumSpringGreen = Color::RGB.new(0x00, 0xfa, 0x9a) 144 | MediumTurquoise = Color::RGB.new(0x48, 0xd1, 0xcc) 145 | MediumVioletRed = Color::RGB.new(0xc7, 0x15, 0x85) 146 | MidnightBlue = Color::RGB.new(0x19, 0x19, 0x70) 147 | MintCream = Color::RGB.new(0xf5, 0xff, 0xfa) 148 | MistyRose = Color::RGB.new(0xff, 0xe4, 0xe1) 149 | Moccasin = Color::RGB.new(0xff, 0xe4, 0xb5) 150 | NavajoWhite = Color::RGB.new(0xff, 0xde, 0xad) 151 | Navy = Color::RGB.new(0x00, 0x00, 0x80) 152 | OldLace = Color::RGB.new(0xfd, 0xf5, 0xe6) 153 | Olive = Color::RGB.new(0x80, 0x80, 0x00) 154 | OliveDrab = Color::RGB.new(0x6b, 0x8e, 0x23) 155 | Olivedrab = OliveDrab 156 | Orange = Color::RGB.new(0xff, 0xa5, 0x00) 157 | OrangeRed = Color::RGB.new(0xff, 0x45, 0x00) 158 | Orchid = Color::RGB.new(0xda, 0x70, 0xd6) 159 | PaleGoldenRod = Color::RGB.new(0xee, 0xe8, 0xaa) 160 | PaleGoldenrod = PaleGoldenRod 161 | PaleGreen = Color::RGB.new(0x98, 0xfb, 0x98) 162 | PaleTurquoise = Color::RGB.new(0xaf, 0xee, 0xee) 163 | PaleVioletRed = Color::RGB.new(0xdb, 0x70, 0x93) 164 | PapayaWhip = Color::RGB.new(0xff, 0xef, 0xd5) 165 | PeachPuff = Color::RGB.new(0xff, 0xda, 0xb9) 166 | Peachpuff = PeachPuff 167 | Peru = Color::RGB.new(0xcd, 0x85, 0x3f) 168 | Pink = Color::RGB.new(0xff, 0xc0, 0xcb) 169 | Plum = Color::RGB.new(0xdd, 0xa0, 0xdd) 170 | PowderBlue = Color::RGB.new(0xb0, 0xe0, 0xe6) 171 | Purple = Color::RGB.new(0x80, 0x00, 0x80) 172 | Red = Color::RGB.new(0xff, 0x00, 0x00) 173 | RosyBrown = Color::RGB.new(0xbc, 0x8f, 0x8f) 174 | RoyalBlue = Color::RGB.new(0x41, 0x69, 0xe1) 175 | SaddleBrown = Color::RGB.new(0x8b, 0x45, 0x13) 176 | Salmon = Color::RGB.new(0xfa, 0x80, 0x72) 177 | SandyBrown = Color::RGB.new(0xf4, 0xa4, 0x60) 178 | SeaGreen = Color::RGB.new(0x2e, 0x8b, 0x57) 179 | SeaShell = Color::RGB.new(0xff, 0xf5, 0xee) 180 | Seashell = SeaShell 181 | Sienna = Color::RGB.new(0xa0, 0x52, 0x2d) 182 | Silver = Color::RGB.new(0xc0, 0xc0, 0xc0) 183 | SkyBlue = Color::RGB.new(0x87, 0xce, 0xeb) 184 | SlateBlue = Color::RGB.new(0x6a, 0x5a, 0xcd) 185 | SlateGray = Color::RGB.new(0x70, 0x80, 0x90) 186 | SlateGrey = SlateGray 187 | Snow = Color::RGB.new(0xff, 0xfa, 0xfa) 188 | SpringGreen = Color::RGB.new(0x00, 0xff, 0x7f) 189 | SteelBlue = Color::RGB.new(0x46, 0x82, 0xb4) 190 | Tan = Color::RGB.new(0xd2, 0xb4, 0x8c) 191 | Teal = Color::RGB.new(0x00, 0x80, 0x80) 192 | Thistle = Color::RGB.new(0xd8, 0xbf, 0xd8) 193 | Tomato = Color::RGB.new(0xff, 0x63, 0x47) 194 | Turquoise = Color::RGB.new(0x40, 0xe0, 0xd0) 195 | Violet = Color::RGB.new(0xee, 0x82, 0xee) 196 | VioletRed = Color::RGB.new(0xd0, 0x20, 0x90) 197 | Wheat = Color::RGB.new(0xf5, 0xde, 0xb3) 198 | White = Color::RGB.new(0xff, 0xff, 0xff) 199 | WhiteSmoke = Color::RGB.new(0xf5, 0xf5, 0xf5) 200 | Yellow = Color::RGB.new(0xff, 0xff, 0x00) 201 | YellowGreen = Color::RGB.new(0x9a, 0xcd, 0x32) 202 | 203 | AliceBlue.freeze 204 | AntiqueWhite.freeze 205 | Aqua.freeze 206 | Aquamarine.freeze 207 | Azure.freeze 208 | Beige.freeze 209 | Bisque.freeze 210 | Black.freeze 211 | BlanchedAlmond.freeze 212 | Blue.freeze 213 | BlueViolet.freeze 214 | Brown.freeze 215 | Burlywood.freeze 216 | CadetBlue.freeze 217 | Cayenne.freeze 218 | Carnation.freeze 219 | Chartreuse.freeze 220 | Chocolate.freeze 221 | Coral.freeze 222 | CornflowerBlue.freeze 223 | Cornsilk.freeze 224 | Crimson.freeze 225 | Cyan.freeze 226 | DarkBlue.freeze 227 | DarkCyan.freeze 228 | DarkGoldenrod.freeze 229 | DarkGray.freeze 230 | DarkGreen.freeze 231 | DarkKhaki.freeze 232 | DarkMagenta.freeze 233 | DarkoliveGreen.freeze 234 | Darkorange.freeze 235 | DarkOrchid.freeze 236 | DarkRed.freeze 237 | Darksalmon.freeze 238 | DarkSeaGreen.freeze 239 | DarkSlateBlue.freeze 240 | DarkSlateGray.freeze 241 | DarkTurquoise.freeze 242 | DarkViolet.freeze 243 | DeepPink.freeze 244 | DeepSkyBlue.freeze 245 | DimGray.freeze 246 | DodgerBlue.freeze 247 | Feldspar.freeze 248 | Firebrick.freeze 249 | FloralWhite.freeze 250 | ForestGreen.freeze 251 | Fuchsia.freeze 252 | Gainsboro.freeze 253 | GhostWhite.freeze 254 | Gold.freeze 255 | Goldenrod.freeze 256 | Gray.freeze 257 | Green.freeze 258 | GreenYellow.freeze 259 | Honeydew.freeze 260 | HotPink.freeze 261 | IndianRed.freeze 262 | Indigo.freeze 263 | Ivory.freeze 264 | Khaki.freeze 265 | Lavender.freeze 266 | LavenderBlush.freeze 267 | LawnGreen.freeze 268 | LemonChiffon.freeze 269 | LightBlue.freeze 270 | LightCoral.freeze 271 | LightCyan.freeze 272 | LightGoldenrodYellow.freeze 273 | LightGray.freeze 274 | LightGreen.freeze 275 | LightPink.freeze 276 | Lightsalmon.freeze 277 | LightSeaGreen.freeze 278 | LightSkyBlue.freeze 279 | LightSlateBlue.freeze 280 | LightSlateGray.freeze 281 | LightsteelBlue.freeze 282 | LightYellow.freeze 283 | Lime.freeze 284 | LimeGreen.freeze 285 | Linen.freeze 286 | Magenta.freeze 287 | Maroon.freeze 288 | MediumAquamarine.freeze 289 | MediumBlue.freeze 290 | MediumOrchid.freeze 291 | MediumPurple.freeze 292 | MediumSeaGreen.freeze 293 | MediumSlateBlue.freeze 294 | MediumSpringGreen.freeze 295 | MediumTurquoise.freeze 296 | MediumVioletRed.freeze 297 | MidnightBlue.freeze 298 | MintCream.freeze 299 | MistyRose.freeze 300 | Moccasin.freeze 301 | NavajoWhite.freeze 302 | Navy.freeze 303 | OldLace.freeze 304 | Olive.freeze 305 | Olivedrab.freeze 306 | Orange.freeze 307 | OrangeRed.freeze 308 | Orchid.freeze 309 | PaleGoldenrod.freeze 310 | PaleGreen.freeze 311 | PaleTurquoise.freeze 312 | PaleVioletRed.freeze 313 | PapayaWhip.freeze 314 | Peachpuff.freeze 315 | Peru.freeze 316 | Pink.freeze 317 | Plum.freeze 318 | PowderBlue.freeze 319 | Purple.freeze 320 | Red.freeze 321 | RosyBrown.freeze 322 | RoyalBlue.freeze 323 | SaddleBrown.freeze 324 | Salmon.freeze 325 | SandyBrown.freeze 326 | SeaGreen.freeze 327 | Seashell.freeze 328 | Sienna.freeze 329 | Silver.freeze 330 | SkyBlue.freeze 331 | SlateBlue.freeze 332 | SlateGray.freeze 333 | Snow.freeze 334 | SpringGreen.freeze 335 | SteelBlue.freeze 336 | Tan.freeze 337 | Teal.freeze 338 | Thistle.freeze 339 | Tomato.freeze 340 | Turquoise.freeze 341 | Violet.freeze 342 | VioletRed.freeze 343 | Wheat.freeze 344 | White.freeze 345 | WhiteSmoke.freeze 346 | Yellow.freeze 347 | YellowGreen.freeze 348 | Gray10.freeze 349 | Gray20.freeze 350 | Gray30.freeze 351 | Gray40.freeze 352 | Gray50.freeze 353 | Gray60.freeze 354 | Gray70.freeze 355 | Gray80.freeze 356 | Gray90.freeze 357 | end 358 | -------------------------------------------------------------------------------- /lib/color/rgb.rb: -------------------------------------------------------------------------------- 1 | #-- 2 | # Color 3 | # Colour management with Ruby 4 | # http://rubyforge.org/projects/color 5 | # Version 1.4.0 6 | # 7 | # Licensed under a MIT-style licence. See Licence.txt in the main 8 | # distribution for full licensing information. 9 | # 10 | # Copyright (c) 2005 - 2007 Austin Ziegler and Matt Lyon 11 | # 12 | # $Id: test_all.rb 55 2007-02-03 23:29:34Z austin $ 13 | #++ 14 | 15 | # An RGB colour object. 16 | class Color::RGB 17 | # The format of a DeviceRGB colour for PDF. In color-tools 2.0 this will 18 | # be removed from this package and added back as a modification by the 19 | # PDF::Writer package. 20 | PDF_FORMAT_STR = "%.3f %.3f %.3f %s" 21 | 22 | class << self 23 | # Creates an RGB colour object from percentages 0..100. 24 | # 25 | # Color::RGB.from_percentage(10, 20 30) 26 | def from_percentage(r = 0, g = 0, b = 0) 27 | from_fraction(r / 100.0, g / 100.0, b / 100.0) 28 | end 29 | 30 | # Creates an RGB colour object from fractional values 0..1. 31 | # 32 | # Color::RGB.from_fraction(.3, .2, .1) 33 | def from_fraction(r = 0.0, g = 0.0, b = 0.0) 34 | colour = Color::RGB.new 35 | colour.r = r 36 | colour.g = g 37 | colour.b = b 38 | colour 39 | end 40 | 41 | # Creates an RGB colour object from an HTML colour descriptor (e.g., 42 | # "fed" or "#cabbed;". 43 | # 44 | # Color::RGB.from_html("fed") 45 | # Color::RGB.from_html("#fed") 46 | # Color::RGB.from_html("#cabbed") 47 | # Color::RGB.from_html("cabbed") 48 | def from_html(html_colour) 49 | html_colour = html_colour.gsub(%r{[#;]}, '') 50 | case html_colour.size 51 | when 3 52 | colours = html_colour.scan(%r{[0-9A-Fa-f]}).map { |el| (el * 2).to_i(16) } 53 | when 6 54 | colours = html_colour.scan(%r<[0-9A-Fa-f]{2}>).map { |el| el.to_i(16) } 55 | else 56 | raise ArgumentError 57 | end 58 | 59 | Color::RGB.new(*colours) 60 | end 61 | end 62 | 63 | # Compares the other colour to this one. The other colour will be 64 | # converted to RGB before comparison, so the comparison between a RGB 65 | # colour and a non-RGB colour will be approximate and based on the other 66 | # colour's default #to_rgb conversion. If there is no #to_rgb conversion, 67 | # this will raise an exception. This will report that two RGB colours are 68 | # equivalent if all component values are within COLOR_TOLERANCE of each 69 | # other. 70 | def ==(other) 71 | other = other.to_rgb 72 | other.kind_of?(Color::RGB) and 73 | ((@r - other.r).abs <= Color::COLOR_TOLERANCE) and 74 | ((@g - other.g).abs <= Color::COLOR_TOLERANCE) and 75 | ((@b - other.b).abs <= Color::COLOR_TOLERANCE) 76 | end 77 | 78 | # Creates an RGB colour object from the standard range 0..255. 79 | # 80 | # Color::RGB.new(32, 64, 128) 81 | # Color::RGB.new(0x20, 0x40, 0x80) 82 | def initialize(r = 0, g = 0, b = 0) 83 | @r = r / 255.0 84 | @g = g / 255.0 85 | @b = b / 255.0 86 | end 87 | 88 | # Present the colour as a DeviceRGB fill colour string for PDF. This will 89 | # be removed from the default package in color-tools 2.0. 90 | def pdf_fill 91 | PDF_FORMAT_STR % [ @r, @g, @b, "rg" ] 92 | end 93 | 94 | # Present the colour as a DeviceRGB stroke colour string for PDF. This 95 | # will be removed from the default package in color-tools 2.0. 96 | def pdf_stroke 97 | PDF_FORMAT_STR % [ @r, @g, @b, "RG" ] 98 | end 99 | 100 | # Present the colour as an HTML/CSS colour string. 101 | def html 102 | r = (@r * 255).round 103 | r = 255 if r > 255 104 | 105 | g = (@g * 255).round 106 | g = 255 if g > 255 107 | 108 | b = (@b * 255).round 109 | b = 255 if b > 255 110 | 111 | "#%02x%02x%02x" % [ r, g, b ] 112 | end 113 | 114 | # Present the colour as an RGB HTML/CSS colour string (e.g., "rgb(0%, 50%, 115 | # 100%)"). Note that this will perform a #to_rgb operation using the 116 | # default conversion formula. 117 | def css_rgb 118 | "rgb(%3.2f%%, %3.2f%%, %3.2f%%)" % [ red_p, green_p, blue_p ] 119 | end 120 | 121 | # Present the colour as an RGBA (with alpha) HTML/CSS colour string (e.g., 122 | # "rgb(0%, 50%, 100%, 1)"). Note that this will perform a #to_rgb 123 | # operation using the default conversion formula. 124 | def css_rgba 125 | "rgba(%3.2f%%, %3.2f%%, %3.2f%%, %3.2f)" % [ red_p, green_p, blue_p, 1 ] 126 | end 127 | 128 | # Present the colour as an HSL HTML/CSS colour string (e.g., "hsl(180, 129 | # 25%, 35%)"). Note that this will perform a #to_hsl operation using the 130 | # default conversion formula. 131 | def css_hsl 132 | to_hsl.css_hsl 133 | end 134 | 135 | # Present the colour as an HSLA (with alpha) HTML/CSS colour string (e.g., 136 | # "hsla(180, 25%, 35%, 1)"). Note that this will perform a #to_hsl 137 | # operation using the default conversion formula. 138 | def css_hsla 139 | to_hsl.css_hsla 140 | end 141 | 142 | # Converts the RGB colour to CMYK. Most colour experts strongly suggest 143 | # that this is not a good idea (some even suggesting that it's a very bad 144 | # idea). CMYK represents additive percentages of inks on white paper, 145 | # whereas RGB represents mixed colour intensities on a black screen. 146 | # 147 | # However, the colour conversion can be done. The basic method is 148 | # multi-step: 149 | # 150 | # 1. Convert the R, G, and B components to C, M, and Y components. 151 | # c = 1.0 - r 152 | # m = 1.0 - g 153 | # y = 1.0 - b 154 | # 2. Compute the minimum amount of black (K) required to smooth the colour 155 | # in inks. 156 | # k = min(c, m, y) 157 | # 3. Perform undercolour removal on the C, M, and Y components of the 158 | # colours because less of each colour is needed for each bit of black. 159 | # Also, regenerate the black (K) based on the undercolour removal so 160 | # that the colour is more accurately represented in ink. 161 | # c = min(1.0, max(0.0, c - UCR(k))) 162 | # m = min(1.0, max(0.0, m - UCR(k))) 163 | # y = min(1.0, max(0.0, y - UCR(k))) 164 | # k = min(1.0, max(0.0, BG(k))) 165 | # 166 | # The undercolour removal function and the black generation functions 167 | # return a value based on the brightness of the RGB colour. 168 | def to_cmyk 169 | c = 1.0 - @r.to_f 170 | m = 1.0 - @g.to_f 171 | y = 1.0 - @b.to_f 172 | 173 | k = [c, m, y].min 174 | k = k - (k * brightness) 175 | 176 | c = [1.0, [0.0, c - k].max].min 177 | m = [1.0, [0.0, m - k].max].min 178 | y = [1.0, [0.0, y - k].max].min 179 | k = [1.0, [0.0, k].max].min 180 | 181 | Color::CMYK.from_fraction(c, m, y, k) 182 | end 183 | 184 | def to_rgb(ignored = nil) 185 | self 186 | end 187 | 188 | # Returns the YIQ (NTSC) colour encoding of the RGB value. 189 | def to_yiq 190 | y = (@r * 0.299) + (@g * 0.587) + (@b * 0.114) 191 | i = (@r * 0.596) + (@g * -0.275) + (@b * -0.321) 192 | q = (@r * 0.212) + (@g * -0.523) + (@b * 0.311) 193 | Color::YIQ.from_fraction(y, i, q) 194 | end 195 | 196 | # Returns the HSL colour encoding of the RGB value. The conversions here 197 | # are based on forumlas from http://www.easyrgb.com/math.php and 198 | # elsewhere. 199 | def to_hsl 200 | min = [ @r, @g, @b ].min 201 | max = [ @r, @g, @b ].max 202 | delta = (max - min).to_f 203 | 204 | lum = (max + min) / 2.0 205 | 206 | if Color.near_zero?(delta) # close to 0.0, so it's a grey 207 | hue = 0 208 | sat = 0 209 | else 210 | if Color.near_zero_or_less?(lum - 0.5) 211 | sat = delta / (max + min).to_f 212 | else 213 | sat = delta / (2 - max - min).to_f 214 | end 215 | 216 | # This is based on the conversion algorithm from 217 | # http://en.wikipedia.org/wiki/HSV_color_space#Conversion_from_RGB_to_HSL_or_HSV 218 | # Contributed by Adam Johnson 219 | sixth = 1 / 6.0 220 | if @r == max # Color.near_zero_or_less?(@r - max) 221 | hue = (sixth * ((@g - @b) / delta)) 222 | hue += 1.0 if @g < @b 223 | elsif @g == max # Color.near_zero_or_less(@g - max) 224 | hue = (sixth * ((@b - @r) / delta)) + (1.0 / 3.0) 225 | elsif @b == max # Color.near_zero_or_less?(@b - max) 226 | hue = (sixth * ((@r - @g) / delta)) + (2.0 / 3.0) 227 | end 228 | 229 | hue += 1 if hue < 0 230 | hue -= 1 if hue > 1 231 | end 232 | Color::HSL.from_fraction(hue, sat, lum) 233 | end 234 | 235 | # Mix the RGB hue with White so that the RGB hue is the specified 236 | # percentage of the resulting colour. Strictly speaking, this isn't a 237 | # darken_by operation. 238 | def lighten_by(percent) 239 | mix_with(White, percent) 240 | end 241 | 242 | # Mix the RGB hue with Black so that the RGB hue is the specified 243 | # percentage of the resulting colour. Strictly speaking, this isn't a 244 | # darken_by operation. 245 | def darken_by(percent) 246 | mix_with(Black, percent) 247 | end 248 | 249 | # Mix the mask colour (which must be an RGB object) with the current 250 | # colour at the stated opacity percentage (0..100). 251 | def mix_with(mask, opacity) 252 | opacity /= 100.0 253 | rgb = self.dup 254 | 255 | rgb.r = (@r * opacity) + (mask.r * (1 - opacity)) 256 | rgb.g = (@g * opacity) + (mask.g * (1 - opacity)) 257 | rgb.b = (@b * opacity) + (mask.b * (1 - opacity)) 258 | 259 | rgb 260 | end 261 | 262 | # Returns the brightness value for a colour, a number between 0..1. Based 263 | # on the Y value of YIQ encoding, representing luminosity, or perceived 264 | # brightness. 265 | # 266 | # This may be modified in a future version of color-tools to use the 267 | # luminosity value of HSL. 268 | def brightness 269 | to_yiq.y 270 | end 271 | # Convert to grayscale. 272 | def to_grayscale 273 | Color::GrayScale.from_fraction(to_hsl.l) 274 | end 275 | alias to_greyscale to_grayscale 276 | 277 | # Returns a new colour with the brightness adjusted by the specified 278 | # percentage. Negative percentages will darken the colour; positive 279 | # percentages will brighten the colour. 280 | # 281 | # Color::RGB::DarkBlue.adjust_brightness(10) 282 | # Color::RGB::DarkBlue.adjust_brightness(-10) 283 | def adjust_brightness(percent) 284 | percent /= 100.0 285 | percent += 1.0 286 | percent = [ percent, 2.0 ].min 287 | percent = [ 0.0, percent ].max 288 | 289 | hsl = to_hsl 290 | hsl.l *= percent 291 | hsl.to_rgb 292 | end 293 | 294 | # Returns a new colour with the saturation adjusted by the specified 295 | # percentage. Negative percentages will reduce the saturation; positive 296 | # percentages will increase the saturation. 297 | # 298 | # Color::RGB::DarkBlue.adjust_saturation(10) 299 | # Color::RGB::DarkBlue.adjust_saturation(-10) 300 | def adjust_saturation(percent) 301 | percent /= 100.0 302 | percent += 1.0 303 | percent = [ percent, 2.0 ].min 304 | percent = [ 0.0, percent ].max 305 | 306 | hsl = to_hsl 307 | hsl.s *= percent 308 | hsl.to_rgb 309 | end 310 | 311 | # Returns a new colour with the hue adjusted by the specified percentage. 312 | # Negative percentages will reduce the hue; positive percentages will 313 | # increase the hue. 314 | # 315 | # Color::RGB::DarkBlue.adjust_hue(10) 316 | # Color::RGB::DarkBlue.adjust_hue(-10) 317 | def adjust_hue(percent) 318 | percent /= 100.0 319 | percent += 1.0 320 | percent = [ percent, 2.0 ].min 321 | percent = [ 0.0, percent ].max 322 | 323 | hsl = to_hsl 324 | hsl.h *= percent 325 | hsl.to_rgb 326 | end 327 | 328 | # Returns the red component of the colour in the normal 0 .. 255 range. 329 | def red 330 | @r * 255.0 331 | end 332 | # Returns the red component of the colour as a percentage. 333 | def red_p 334 | @r * 100.0 335 | end 336 | # Returns the red component of the colour as a fraction in the range 0.0 337 | # .. 1.0. 338 | def r 339 | @r 340 | end 341 | # Sets the red component of the colour in the normal 0 .. 255 range. 342 | def red=(rr) 343 | @r = Color.normalize(rr / 255.0) 344 | end 345 | # Sets the red component of the colour as a percentage. 346 | def red_p=(rr) 347 | @r = Color.normalize(rr / 100.0) 348 | end 349 | # Sets the red component of the colour as a fraction in the range 0.0 .. 350 | # 1.0. 351 | def r=(rr) 352 | @r = Color.normalize(rr) 353 | end 354 | 355 | # Returns the green component of the colour in the normal 0 .. 255 range. 356 | def green 357 | @g * 255.0 358 | end 359 | # Returns the green component of the colour as a percentage. 360 | def green_p 361 | @g * 100.0 362 | end 363 | # Returns the green component of the colour as a fraction in the range 0.0 364 | # .. 1.0. 365 | def g 366 | @g 367 | end 368 | # Sets the green component of the colour in the normal 0 .. 255 range. 369 | def green=(gg) 370 | @g = Color.normalize(gg / 255.0) 371 | end 372 | # Sets the green component of the colour as a percentage. 373 | def green_p=(gg) 374 | @g = Color.normalize(gg / 100.0) 375 | end 376 | # Sets the green component of the colour as a fraction in the range 0.0 .. 377 | # 1.0. 378 | def g=(gg) 379 | @g = Color.normalize(gg) 380 | end 381 | 382 | # Returns the blue component of the colour in the normal 0 .. 255 range. 383 | def blue 384 | @b * 255.0 385 | end 386 | # Returns the blue component of the colour as a percentage. 387 | def blue_p 388 | @b * 100.0 389 | end 390 | # Returns the blue component of the colour as a fraction in the range 0.0 391 | # .. 1.0. 392 | def b 393 | @b 394 | end 395 | # Sets the blue component of the colour in the normal 0 .. 255 range. 396 | def blue=(bb) 397 | @b = Color.normalize(bb / 255.0) 398 | end 399 | # Sets the blue component of the colour as a percentage. 400 | def blue_p=(bb) 401 | @b = Color.normalize(bb / 100.0) 402 | end 403 | # Sets the blue component of the colour as a fraction in the range 0.0 .. 404 | # 1.0. 405 | def b=(bb) 406 | @b = Color.normalize(bb) 407 | end 408 | 409 | # Adds another colour to the current colour. The other colour will be 410 | # converted to RGB before addition. This conversion depends upon a #to_rgb 411 | # method on the other colour. 412 | # 413 | # The addition is done using the RGB Accessor methods to ensure a valid 414 | # colour in the result. 415 | def +(other) 416 | other = other.to_rgb 417 | rgb = self.dup 418 | 419 | rgb.r += other.r 420 | rgb.g += other.g 421 | rgb.b += other.b 422 | 423 | rgb 424 | end 425 | 426 | # Subtracts another colour to the current colour. The other colour will be 427 | # converted to RGB before subtraction. This conversion depends upon a 428 | # #to_rgb method on the other colour. 429 | # 430 | # The subtraction is done using the RGB Accessor methods to ensure a valid 431 | # colour in the result. 432 | def -(other) 433 | other = other.to_rgb 434 | rgb = self.dup 435 | 436 | rgb.r -= other.r 437 | rgb.g -= other.g 438 | rgb.b -= other.b 439 | 440 | rgb 441 | end 442 | 443 | # Retrieve the maxmum RGB value from the current colour as a GrayScale 444 | # colour 445 | def max_rgb_as_grayscale 446 | Color::GrayScale.from_fraction([@r, @g, @b].max) 447 | end 448 | alias max_rgb_as_greyscale max_rgb_as_grayscale 449 | 450 | def inspect 451 | "RGB [#{html}]" 452 | end 453 | end 454 | 455 | require 'color/rgb-colors' 456 | -------------------------------------------------------------------------------- /test/test_rgb.rb: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | #-- 3 | # Color 4 | # Colour management with Ruby 5 | # http://rubyforge.org/projects/color 6 | # Version 1.4.0 7 | # 8 | # Licensed under a MIT-style licence. See Licence.txt in the main 9 | # distribution for full licensing information. 10 | # 11 | # Copyright (c) 2005 - 2007 Austin Ziegler and Matt Lyon 12 | # 13 | # $Id: test_all.rb 55 2007-02-03 23:29:34Z austin $ 14 | #++ 15 | 16 | $LOAD_PATH.unshift("#{File.dirname(__FILE__)}/../lib") if __FILE__ == $0 17 | require 'test/unit' 18 | require 'color' 19 | 20 | module TestColor 21 | class TestRGB < Test::Unit::TestCase 22 | def test_adjust_brightness 23 | assert_equal("#1a1aff", Color::RGB::Blue.adjust_brightness(10).html) 24 | assert_equal("#0000e6", Color::RGB::Blue.adjust_brightness(-10).html) 25 | end 26 | 27 | def test_adjust_hue 28 | assert_equal("#6600ff", Color::RGB::Blue.adjust_hue(10).html) 29 | assert_equal("#0066ff", Color::RGB::Blue.adjust_hue(-10).html) 30 | end 31 | 32 | def test_adjust_saturation 33 | assert_equal("#ef9374", 34 | Color::RGB::DarkSalmon.adjust_saturation(10).html) 35 | assert_equal("#e39980", 36 | Color::RGB::DarkSalmon.adjust_saturation(-10).html) 37 | end 38 | 39 | def test_red 40 | red = Color::RGB::Red.dup 41 | assert_in_delta(1.0, red.r, Color::COLOR_TOLERANCE) 42 | assert_in_delta(100, red.red_p, Color::COLOR_TOLERANCE) 43 | assert_in_delta(255, red.red, Color::COLOR_TOLERANCE) 44 | assert_in_delta(1.0, red.r, Color::COLOR_TOLERANCE) 45 | assert_nothing_raised { red.red_p = 33 } 46 | assert_in_delta(0.33, red.r, Color::COLOR_TOLERANCE) 47 | assert_nothing_raised { red.red = 330 } 48 | assert_in_delta(1.0, red.r, Color::COLOR_TOLERANCE) 49 | assert_nothing_raised { red.r = -3.3 } 50 | assert_in_delta(0.0, red.r, Color::COLOR_TOLERANCE) 51 | end 52 | 53 | def test_green 54 | lime = Color::RGB::Lime.dup 55 | assert_in_delta(1.0, lime.g, Color::COLOR_TOLERANCE) 56 | assert_in_delta(100, lime.green_p, Color::COLOR_TOLERANCE) 57 | assert_in_delta(255, lime.green, Color::COLOR_TOLERANCE) 58 | assert_nothing_raised { lime.green_p = 33 } 59 | assert_in_delta(0.33, lime.g, Color::COLOR_TOLERANCE) 60 | assert_nothing_raised { lime.green = 330 } 61 | assert_in_delta(1.0, lime.g, Color::COLOR_TOLERANCE) 62 | assert_nothing_raised { lime.g = -3.3 } 63 | assert_in_delta(0.0, lime.g, Color::COLOR_TOLERANCE) 64 | end 65 | 66 | def test_blue 67 | blue = Color::RGB::Blue.dup 68 | assert_in_delta(1.0, blue.b, Color::COLOR_TOLERANCE) 69 | assert_in_delta(255, blue.blue, Color::COLOR_TOLERANCE) 70 | assert_in_delta(100, blue.blue_p, Color::COLOR_TOLERANCE) 71 | assert_nothing_raised { blue.blue_p = 33 } 72 | assert_in_delta(0.33, blue.b, Color::COLOR_TOLERANCE) 73 | assert_nothing_raised { blue.blue = 330 } 74 | assert_in_delta(1.0, blue.b, Color::COLOR_TOLERANCE) 75 | assert_nothing_raised { blue.b = -3.3 } 76 | assert_in_delta(0.0, blue.b, Color::COLOR_TOLERANCE) 77 | end 78 | 79 | def test_brightness 80 | assert_in_delta(0.0, Color::RGB::Black.brightness, Color::COLOR_TOLERANCE) 81 | assert_in_delta(0.5, Color::RGB::Grey50.brightness, Color::COLOR_TOLERANCE) 82 | assert_in_delta(1.0, Color::RGB::White.brightness, Color::COLOR_TOLERANCE) 83 | end 84 | 85 | def test_darken_by 86 | assert_in_delta(0.5, Color::RGB::Blue.darken_by(50).b, 87 | Color::COLOR_TOLERANCE) 88 | end 89 | 90 | def test_html 91 | assert_equal("#000000", Color::RGB::Black.html) 92 | assert_equal(Color::RGB::Black, Color::RGB.from_html("#000000")) 93 | assert_equal("#0000ff", Color::RGB::Blue.html) 94 | assert_equal("#00ff00", Color::RGB::Lime.html) 95 | assert_equal("#ff0000", Color::RGB::Red.html) 96 | assert_equal("#ffffff", Color::RGB::White.html) 97 | 98 | assert_equal("rgb(0.00%, 0.00%, 0.00%)", Color::RGB::Black.css_rgb) 99 | assert_equal("rgb(0.00%, 0.00%, 100.00%)", Color::RGB::Blue.css_rgb) 100 | assert_equal("rgb(0.00%, 100.00%, 0.00%)", Color::RGB::Lime.css_rgb) 101 | assert_equal("rgb(100.00%, 0.00%, 0.00%)", Color::RGB::Red.css_rgb) 102 | assert_equal("rgb(100.00%, 100.00%, 100.00%)", Color::RGB::White.css_rgb) 103 | 104 | assert_equal("rgba(0.00%, 0.00%, 0.00%, 1.00)", Color::RGB::Black.css_rgba) 105 | assert_equal("rgba(0.00%, 0.00%, 100.00%, 1.00)", Color::RGB::Blue.css_rgba) 106 | assert_equal("rgba(0.00%, 100.00%, 0.00%, 1.00)", Color::RGB::Lime.css_rgba) 107 | assert_equal("rgba(100.00%, 0.00%, 0.00%, 1.00)", Color::RGB::Red.css_rgba) 108 | assert_equal("rgba(100.00%, 100.00%, 100.00%, 1.00)", 109 | Color::RGB::White.css_rgba) 110 | end 111 | 112 | def test_lighten_by 113 | assert_in_delta(1.0, Color::RGB::Blue.lighten_by(50).b, 114 | Color::COLOR_TOLERANCE) 115 | assert_in_delta(0.5, Color::RGB::Blue.lighten_by(50).r, 116 | Color::COLOR_TOLERANCE) 117 | assert_in_delta(0.5, Color::RGB::Blue.lighten_by(50).g, 118 | Color::COLOR_TOLERANCE) 119 | end 120 | 121 | def test_mix_with 122 | assert_in_delta(0.5, Color::RGB::Red.mix_with(Color::RGB::Blue, 50).r, 123 | Color::COLOR_TOLERANCE) 124 | assert_in_delta(0.0, Color::RGB::Red.mix_with(Color::RGB::Blue, 50).g, 125 | Color::COLOR_TOLERANCE) 126 | assert_in_delta(0.5, Color::RGB::Red.mix_with(Color::RGB::Blue, 50).b, 127 | Color::COLOR_TOLERANCE) 128 | assert_in_delta(0.5, Color::RGB::Blue.mix_with(Color::RGB::Red, 50).r, 129 | Color::COLOR_TOLERANCE) 130 | assert_in_delta(0.0, Color::RGB::Blue.mix_with(Color::RGB::Red, 50).g, 131 | Color::COLOR_TOLERANCE) 132 | assert_in_delta(0.5, Color::RGB::Blue.mix_with(Color::RGB::Red, 50).b, 133 | Color::COLOR_TOLERANCE) 134 | end 135 | 136 | def test_pdf_fill 137 | assert_equal("0.000 0.000 0.000 rg", Color::RGB::Black.pdf_fill) 138 | assert_equal("0.000 0.000 1.000 rg", Color::RGB::Blue.pdf_fill) 139 | assert_equal("0.000 1.000 0.000 rg", Color::RGB::Lime.pdf_fill) 140 | assert_equal("1.000 0.000 0.000 rg", Color::RGB::Red.pdf_fill) 141 | assert_equal("1.000 1.000 1.000 rg", Color::RGB::White.pdf_fill) 142 | assert_equal("0.000 0.000 0.000 RG", Color::RGB::Black.pdf_stroke) 143 | assert_equal("0.000 0.000 1.000 RG", Color::RGB::Blue.pdf_stroke) 144 | assert_equal("0.000 1.000 0.000 RG", Color::RGB::Lime.pdf_stroke) 145 | assert_equal("1.000 0.000 0.000 RG", Color::RGB::Red.pdf_stroke) 146 | assert_equal("1.000 1.000 1.000 RG", Color::RGB::White.pdf_stroke) 147 | end 148 | 149 | def test_to_cmyk 150 | assert_kind_of(Color::CMYK, Color::RGB::Black.to_cmyk) 151 | assert_equal(Color::CMYK.new(0, 0, 0, 100), Color::RGB::Black.to_cmyk) 152 | assert_equal(Color::CMYK.new(0, 0, 100, 0), 153 | Color::RGB::Yellow.to_cmyk) 154 | assert_equal(Color::CMYK.new(100, 0, 0, 0), Color::RGB::Cyan.to_cmyk) 155 | assert_equal(Color::CMYK.new(0, 100, 0, 0), 156 | Color::RGB::Magenta.to_cmyk) 157 | assert_equal(Color::CMYK.new(0, 100, 100, 0), Color::RGB::Red.to_cmyk) 158 | assert_equal(Color::CMYK.new(100, 0, 100, 0), 159 | Color::RGB::Lime.to_cmyk) 160 | assert_equal(Color::CMYK.new(100, 100, 0, 0), 161 | Color::RGB::Blue.to_cmyk) 162 | assert_equal(Color::CMYK.new(10.32, 60.52, 10.32, 39.47), 163 | Color::RGB::Purple.to_cmyk) 164 | assert_equal(Color::CMYK.new(10.90, 59.13, 59.13, 24.39), 165 | Color::RGB::Brown.to_cmyk) 166 | assert_equal(Color::CMYK.new(0, 63.14, 18.43, 0), 167 | Color::RGB::Carnation.to_cmyk) 168 | assert_equal(Color::CMYK.new(7.39, 62.69, 62.69, 37.32), 169 | Color::RGB::Cayenne.to_cmyk) 170 | end 171 | 172 | def test_to_grayscale 173 | assert_kind_of(Color::GrayScale, Color::RGB::Black.to_grayscale) 174 | assert_equal(Color::GrayScale.from_fraction(0), 175 | Color::RGB::Black.to_grayscale) 176 | assert_equal(Color::GrayScale.from_fraction(0.5), 177 | Color::RGB::Yellow.to_grayscale) 178 | assert_equal(Color::GrayScale.from_fraction(0.5), 179 | Color::RGB::Cyan.to_grayscale) 180 | assert_equal(Color::GrayScale.from_fraction(0.5), 181 | Color::RGB::Magenta.to_grayscale) 182 | assert_equal(Color::GrayScale.from_fraction(0.5), 183 | Color::RGB::Red.to_grayscale) 184 | assert_equal(Color::GrayScale.from_fraction(0.5), 185 | Color::RGB::Lime.to_grayscale) 186 | assert_equal(Color::GrayScale.from_fraction(0.5), 187 | Color::RGB::Blue.to_grayscale) 188 | assert_equal(Color::GrayScale.from_fraction(0.2510), 189 | Color::RGB::Purple.to_grayscale) 190 | assert_equal(Color::GrayScale.new(40.58), 191 | Color::RGB::Brown.to_grayscale) 192 | assert_equal(Color::GrayScale.new(68.43), 193 | Color::RGB::Carnation.to_grayscale) 194 | assert_equal(Color::GrayScale.new(27.65), 195 | Color::RGB::Cayenne.to_grayscale) 196 | end 197 | 198 | def test_to_hsl 199 | assert_kind_of(Color::HSL, Color::RGB::Black.to_hsl) 200 | assert_equal(Color::HSL.new, Color::RGB::Black.to_hsl) 201 | assert_equal(Color::HSL.new(60, 100, 50), Color::RGB::Yellow.to_hsl) 202 | assert_equal(Color::HSL.new(180, 100, 50), Color::RGB::Cyan.to_hsl) 203 | assert_equal(Color::HSL.new(300, 100, 50), Color::RGB::Magenta.to_hsl) 204 | assert_equal(Color::HSL.new(0, 100, 50), Color::RGB::Red.to_hsl) 205 | assert_equal(Color::HSL.new(120, 100, 50), Color::RGB::Lime.to_hsl) 206 | assert_equal(Color::HSL.new(240, 100, 50), Color::RGB::Blue.to_hsl) 207 | assert_equal(Color::HSL.new(300, 100, 25.10), 208 | Color::RGB::Purple.to_hsl) 209 | assert_equal(Color::HSL.new(0, 59.42, 40.59), 210 | Color::RGB::Brown.to_hsl) 211 | assert_equal(Color::HSL.new(317.5, 100, 68.43), 212 | Color::RGB::Carnation.to_hsl) 213 | assert_equal(Color::HSL.new(0, 100, 27.64), 214 | Color::RGB::Cayenne.to_hsl) 215 | 216 | assert_equal("hsl(0.00, 0.00%, 0.00%)", Color::RGB::Black.css_hsl) 217 | assert_equal("hsl(60.00, 100.00%, 50.00%)", 218 | Color::RGB::Yellow.css_hsl) 219 | assert_equal("hsl(180.00, 100.00%, 50.00%)", Color::RGB::Cyan.css_hsl) 220 | assert_equal("hsl(300.00, 100.00%, 50.00%)", 221 | Color::RGB::Magenta.css_hsl) 222 | assert_equal("hsl(0.00, 100.00%, 50.00%)", Color::RGB::Red.css_hsl) 223 | assert_equal("hsl(120.00, 100.00%, 50.00%)", Color::RGB::Lime.css_hsl) 224 | assert_equal("hsl(240.00, 100.00%, 50.00%)", Color::RGB::Blue.css_hsl) 225 | assert_equal("hsl(300.00, 100.00%, 25.10%)", 226 | Color::RGB::Purple.css_hsl) 227 | assert_equal("hsl(0.00, 59.42%, 40.59%)", Color::RGB::Brown.css_hsl) 228 | assert_equal("hsl(317.52, 100.00%, 68.43%)", 229 | Color::RGB::Carnation.css_hsl) 230 | assert_equal("hsl(0.00, 100.00%, 27.65%)", Color::RGB::Cayenne.css_hsl) 231 | 232 | assert_equal("hsla(0.00, 0.00%, 0.00%, 1.00)", 233 | Color::RGB::Black.css_hsla) 234 | assert_equal("hsla(60.00, 100.00%, 50.00%, 1.00)", 235 | Color::RGB::Yellow.css_hsla) 236 | assert_equal("hsla(180.00, 100.00%, 50.00%, 1.00)", 237 | Color::RGB::Cyan.css_hsla) 238 | assert_equal("hsla(300.00, 100.00%, 50.00%, 1.00)", 239 | Color::RGB::Magenta.css_hsla) 240 | assert_equal("hsla(0.00, 100.00%, 50.00%, 1.00)", 241 | Color::RGB::Red.css_hsla) 242 | assert_equal("hsla(120.00, 100.00%, 50.00%, 1.00)", 243 | Color::RGB::Lime.css_hsla) 244 | assert_equal("hsla(240.00, 100.00%, 50.00%, 1.00)", 245 | Color::RGB::Blue.css_hsla) 246 | assert_equal("hsla(300.00, 100.00%, 25.10%, 1.00)", 247 | Color::RGB::Purple.css_hsla) 248 | assert_equal("hsla(0.00, 59.42%, 40.59%, 1.00)", 249 | Color::RGB::Brown.css_hsla) 250 | assert_equal("hsla(317.52, 100.00%, 68.43%, 1.00)", 251 | Color::RGB::Carnation.css_hsla) 252 | assert_equal("hsla(0.00, 100.00%, 27.65%, 1.00)", 253 | Color::RGB::Cayenne.css_hsla) 254 | 255 | # The following tests a bug reported by Jean Krohn on 10 June 2006 256 | # where HSL conversion was not quite correct, resulting in a bad 257 | # round-trip. 258 | assert_equal("#008800", Color::RGB.from_html("#008800").to_hsl.html) 259 | assert_not_equal("#002288", Color::RGB.from_html("#008800").to_hsl.html) 260 | 261 | # The following tests a bug reported by Adam Johnson on 29 October 262 | # 2007. 263 | hsl = Color::HSL.new(262, 67, 42) 264 | c = Color::RGB.from_fraction(0.34496, 0.1386, 0.701399).to_hsl 265 | assert_in_delta hsl.h, c.h, Color::COLOR_TOLERANCE, "Hue" 266 | assert_in_delta hsl.s, c.s, Color::COLOR_TOLERANCE, "Saturation" 267 | assert_in_delta hsl.l, c.l, Color::COLOR_TOLERANCE, "Luminance" 268 | end 269 | 270 | def test_to_rgb 271 | assert_equal(Color::RGB::Black, Color::RGB::Black.to_rgb) 272 | end 273 | 274 | def test_to_yiq 275 | assert_kind_of(Color::YIQ, Color::RGB::Black.to_yiq) 276 | assert_equal(Color::YIQ.new, Color::RGB::Black.to_yiq) 277 | assert_equal(Color::YIQ.new(88.6, 32.1, 0), Color::RGB::Yellow.to_yiq) 278 | assert_equal(Color::YIQ.new(70.1, 0, 0), Color::RGB::Cyan.to_yiq) 279 | assert_equal(Color::YIQ.new(41.3, 27.5, 52.3), 280 | Color::RGB::Magenta.to_yiq) 281 | assert_equal(Color::YIQ.new(29.9, 59.6, 21.2), Color::RGB::Red.to_yiq) 282 | assert_equal(Color::YIQ.new(58.7, 0, 0), Color::RGB::Lime.to_yiq) 283 | assert_equal(Color::YIQ.new(11.4, 0, 31.1), Color::RGB::Blue.to_yiq) 284 | assert_equal(Color::YIQ.new(20.73, 13.80, 26.25), 285 | Color::RGB::Purple.to_yiq) 286 | assert_equal(Color::YIQ.new(30.89, 28.75, 10.23), 287 | Color::RGB::Brown.to_yiq) 288 | assert_equal(Color::YIQ.new(60.84, 23.28, 27.29), 289 | Color::RGB::Carnation.to_yiq) 290 | assert_equal(Color::YIQ.new(16.53, 32.96, 11.72), 291 | Color::RGB::Cayenne.to_yiq) 292 | end 293 | 294 | def test_add 295 | assert_nothing_raised { Color::RGB::Cyan + Color::RGB::Yellow } 296 | white = Color::RGB::Cyan + Color::RGB::Yellow 297 | assert_not_nil(white) 298 | assert_equal(Color::RGB::White, white) 299 | 300 | c1 = Color::RGB.new(0x80, 0x80, 0x00) 301 | c2 = Color::RGB.new(0x45, 0x20, 0xf0) 302 | cr = Color::RGB.new(0xc5, 0xa0, 0xf0) 303 | 304 | assert_equal(cr, c1 + c2) 305 | end 306 | 307 | def test_subtract 308 | black = Color::RGB::LightCoral - Color::RGB::Honeydew 309 | assert_equal(Color::RGB::Black, black) 310 | 311 | c1 = Color::RGB.new(0x85, 0x80, 0x00) 312 | c2 = Color::RGB.new(0x40, 0x20, 0xf0) 313 | cr = Color::RGB.new(0x45, 0x60, 0x00) 314 | 315 | assert_equal(cr, c1 - c2) 316 | end 317 | 318 | def test_mean_grayscale 319 | c1 = Color::RGB.new(0x85, 0x80, 0x00) 320 | c1_max = assert_nothing_raised { c1.max_rgb_as_greyscale } 321 | c1_max = c1.max_rgb_as_greyscale 322 | c1_result = Color::GrayScale.from_fraction(0x85 / 255.0) 323 | 324 | assert_equal(c1_result, c1_max) 325 | end 326 | 327 | def test_from_html 328 | assert_equal("RGB [#333333]", Color::RGB.from_html("#333").inspect) 329 | assert_equal("RGB [#333333]", Color::RGB.from_html("333").inspect) 330 | assert_equal("RGB [#555555]", Color::RGB.from_html("#555555").inspect) 331 | assert_equal("RGB [#555555]", Color::RGB.from_html("555555").inspect) 332 | assert_raises(ArgumentError) { Color::RGB.from_html("#5555555") } 333 | assert_raises(ArgumentError) { Color::RGB.from_html("5555555") } 334 | assert_raises(ArgumentError) { Color::RGB.from_html("#55555") } 335 | assert_raises(ArgumentError) { Color::RGB.from_html("55555") } 336 | end 337 | 338 | def test_inspect 339 | assert_equal("RGB [#000000]", Color::RGB::Black.inspect) 340 | assert_equal("RGB [#0000ff]", Color::RGB::Blue.inspect) 341 | assert_equal("RGB [#00ff00]", Color::RGB::Lime.inspect) 342 | assert_equal("RGB [#ff0000]", Color::RGB::Red.inspect) 343 | assert_equal("RGB [#ffffff]", Color::RGB::White.inspect) 344 | end 345 | end 346 | end 347 | -------------------------------------------------------------------------------- /test/test_adobecolor.rb: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | #-- 3 | # Color 4 | # Colour management with Ruby 5 | # http://rubyforge.org/projects/color 6 | # Version 1.4.0 7 | # 8 | # Licensed under a MIT-style licence. See Licence.txt in the main 9 | # distribution for full licensing information. 10 | # 11 | # Copyright (c) 2005 - 2007 Austin Ziegler and Matt Lyon 12 | # 13 | # $Id: test_all.rb 55 2007-02-03 23:29:34Z austin $ 14 | #++ 15 | 16 | $LOAD_PATH.unshift("#{File.dirname(__FILE__)}/../lib") if __FILE__ == $0 17 | require 'test/unit' 18 | require 'color' 19 | require 'color/palette/adobecolor' 20 | 21 | module TestColor 22 | module TestPalette 23 | class TestAdobeColor < Test::Unit::TestCase 24 | include Color::Palette 25 | 26 | # This is based on the Visibone Anglo-Centric Color Code List; this is 27 | # an Adobe Color swatch version 1 (RGB colours only). 28 | VISIBONE_V1 = <<-EOS 29 | AAEA2AAA/wD/AP8AAAAAAMwAzADMAAAAAACZAJkAmQAAAAAAZgBmAGYAAAAA 30 | ADMAMwAzAAAAAAAAAAAAAAAAAAAA/wAAAAAAAAAAAP8AMwAzAAAAAADMAAAA 31 | AAAAAAAA/wBmAGYAAAAAAMwAMwAzAAAAAACZAAAAAAAAAAAA/wCZAJkAAAAA 32 | AMwAZgBmAAAAAACZADMAMwAAAAAAZgAAAAAAAAAAAP8AzADMAAAAAADMAJkA 33 | mQAAAAAAmQBmAGYAAAAAAGYAMwAzAAAAAAAzAAAAAAAAAAAA/wAzAAAAAAAA 34 | AP8AZgAzAAAAAADMADMAAAAAAAAA/wCZAGYAAAAAAMwAZgAzAAAAAACZADMA 35 | AAAAAAAA/wBmAAAAAAAAAP8AmQAzAAAAAADMAGYAAAAAAAAA/wDMAJkAAAAA 36 | AMwAmQBmAAAAAACZAGYAMwAAAAAAZgAzAAAAAAAAAP8AmQAAAAAAAAD/AMwA 37 | ZgAAAAAAzACZADMAAAAAAJkAZgAAAAAAAADMAJkAAAAAAAAA/wDMADMAAAAA 38 | AP8AzAAAAAAAAAD/AP8AAAAAAAAA/wD/ADMAAAAAAMwAzAAAAAAAAAD/AP8A 39 | ZgAAAAAAzADMADMAAAAAAJkAmQAAAAAAAAD/AP8AmQAAAAAAzADMAGYAAAAA 40 | AJkAmQAzAAAAAABmAGYAAAAAAAAA/wD/AMwAAAAAAMwAzACZAAAAAACZAJkA 41 | ZgAAAAAAZgBmADMAAAAAADMAMwAAAAAAAADMAP8AAAAAAAAAzAD/ADMAAAAA 42 | AJkAzAAAAAAAAADMAP8AZgAAAAAAmQDMADMAAAAAAGYAmQAAAAAAAACZAP8A 43 | AAAAAAAAmQD/ADMAAAAAAGYAzAAAAAAAAADMAP8AmQAAAAAAmQDMAGYAAAAA 44 | AGYAmQAzAAAAAAAzAGYAAAAAAAAAZgD/AAAAAAAAAJkA/wBmAAAAAABmAMwA 45 | MwAAAAAAMwCZAAAAAAAAAGYA/wAzAAAAAAAzAMwAAAAAAAAAMwD/AAAAAAAA 46 | AAAA/wAAAAAAAAAzAP8AMwAAAAAAAADMAAAAAAAAAGYA/wBmAAAAAAAzAMwA 47 | MwAAAAAAAACZAAAAAAAAAJkA/wCZAAAAAABmAMwAZgAAAAAAMwCZADMAAAAA 48 | AAAAZgAAAAAAAADMAP8AzAAAAAAAmQDMAJkAAAAAAGYAmQBmAAAAAAAzAGYA 49 | MwAAAAAAAAAzAAAAAAAAAAAA/wAzAAAAAAAzAP8AZgAAAAAAAADMADMAAAAA 50 | AGYA/wCZAAAAAAAzAMwAZgAAAAAAAACZADMAAAAAAAAA/wBmAAAAAAAzAP8A 51 | mQAAAAAAAADMAGYAAAAAAJkA/wDMAAAAAABmAMwAmQAAAAAAMwCZAGYAAAAA 52 | AAAAZgAzAAAAAAAAAP8AmQAAAAAAZgD/AMwAAAAAADMAzACZAAAAAAAAAJkA 53 | ZgAAAAAAMwD/AMwAAAAAAAAAzACZAAAAAAAAAP8AzAAAAAAAAAD/AP8AAAAA 54 | ADMA/wD/AAAAAAAAAMwAzAAAAAAAZgD/AP8AAAAAADMAzADMAAAAAAAAAJkA 55 | mQAAAAAAmQD/AP8AAAAAAGYAzADMAAAAAAAzAJkAmQAAAAAAAABmAGYAAAAA 56 | AMwA/wD/AAAAAACZAMwAzAAAAAAAZgCZAJkAAAAAADMAZgBmAAAAAAAAADMA 57 | MwAAAAAAAADMAP8AAAAAADMAzAD/AAAAAAAAAJkAzAAAAAAAZgDMAP8AAAAA 58 | ADMAmQDMAAAAAAAAAGYAmQAAAAAAAACZAP8AAAAAADMAmQD/AAAAAAAAAGYA 59 | zAAAAAAAmQDMAP8AAAAAAGYAmQDMAAAAAAAzAGYAmQAAAAAAAAAzAGYAAAAA 60 | AAAAZgD/AAAAAABmAJkA/wAAAAAAMwBmAMwAAAAAAAAAMwCZAAAAAAAzAGYA 61 | /wAAAAAAAAAzAMwAAAAAAAAAMwD/AAAAAAAAAAAA/wAAAAAAMwAzAP8AAAAA 62 | AAAAAADMAAAAAABmAGYA/wAAAAAAMwAzAMwAAAAAAAAAAACZAAAAAACZAJkA 63 | /wAAAAAAZgBmAMwAAAAAADMAMwCZAAAAAAAAAAAAZgAAAAAAzADMAP8AAAAA 64 | AJkAmQDMAAAAAABmAGYAmQAAAAAAMwAzAGYAAAAAAAAAAAAzAAAAAAAzAAAA 65 | /wAAAAAAZgAzAP8AAAAAADMAAADMAAAAAACZAGYA/wAAAAAAZgAzAMwAAAAA 66 | ADMAAACZAAAAAABmAAAA/wAAAAAAmQAzAP8AAAAAAGYAAADMAAAAAADMAJkA 67 | /wAAAAAAmQBmAMwAAAAAAGYAMwCZAAAAAAAzAAAAZgAAAAAAmQAAAP8AAAAA 68 | AMwAZgD/AAAAAACZADMAzAAAAAAAZgAAAJkAAAAAAMwAMwD/AAAAAACZAAAA 69 | zAAAAAAAzAAAAP8AAAAAAP8AAAD/AAAAAAD/ADMA/wAAAAAAzAAAAMwAAAAA 70 | AP8AZgD/AAAAAADMADMAzAAAAAAAmQAAAJkAAAAAAP8AmQD/AAAAAADMAGYA 71 | zAAAAAAAmQAzAJkAAAAAAGYAAABmAAAAAAD/AMwA/wAAAAAAzACZAMwAAAAA 72 | AJkAZgCZAAAAAABmADMAZgAAAAAAMwAAADMAAAAAAP8AAADMAAAAAAD/ADMA 73 | zAAAAAAAzAAAAJkAAAAAAP8AZgDMAAAAAADMADMAmQAAAAAAmQAAAGYAAAAA 74 | AP8AAACZAAAAAAD/ADMAmQAAAAAAzAAAAGYAAAAAAP8AmQDMAAAAAADMAGYA 75 | mQAAAAAAmQAzAGYAAAAAAGYAAAAzAAAAAAD/AAAAZgAAAAAA/wBmAJkAAAAA 76 | AMwAMwBmAAAAAACZAAAAMwAAAAAA/wAzAGYAAAAAAMwAAAAzAAAAAAD/AAAA 77 | MwAAAA== 78 | EOS 79 | 80 | # This is based on the Visibone Anglo-Centric Color Code List; this is 81 | # an Adobe Color swatch version 2 (with names). 82 | VISIBONE_V2 = <<-EOS 83 | AAIA2AAA/wD/AP8AAAAAAAAGAFcAaABpAHQAZQAAAADMAMwAzAAAAAAAAAoA 84 | UABhAGwAZQAgAEcAcgBhAHkAAAAAmQCZAJkAAAAAAAALAEwAaQBnAGgAdAAg 85 | AEcAcgBhAHkAAAAAZgBmAGYAAAAAAAAKAEQAYQByAGsAIABHAHIAYQB5AAAA 86 | ADMAMwAzAAAAAAAADQBPAGIAcwBjAHUAcgBlACAARwByAGEAeQAAAAAAAAAA 87 | AAAAAAAAAAYAQgBsAGEAYwBrAAAAAP8AAAAAAAAAAAAABABSAGUAZAAAAAD/ 88 | ADMAMwAAAAAAAA8ATABpAGcAaAB0ACAASABhAHIAZAAgAFIAZQBkAAAAAMwA 89 | AAAAAAAAAAAADgBEAGEAcgBrACAASABhAHIAZAAgAFIAZQBkAAAAAP8AZgBm 90 | AAAAAAAAEABMAGkAZwBoAHQAIABGAGEAZABlAGQAIABSAGUAZAAAAADMADMA 91 | MwAAAAAAABEATQBlAGQAaQB1AG0AIABGAGEAZABlAGQAIABSAGUAZAAAAACZ 92 | AAAAAAAAAAAAAA8ARABhAHIAawAgAEYAYQBkAGUAZAAgAFIAZQBkAAAAAP8A 93 | mQCZAAAAAAAADgBQAGEAbABlACAARAB1AGwAbAAgAFIAZQBkAAAAAMwAZgBm 94 | AAAAAAAADwBMAGkAZwBoAHQAIABEAHUAbABsACAAUgBlAGQAAAAAmQAzADMA 95 | AAAAAAAOAEQAYQByAGsAIABEAHUAbABsACAAUgBlAGQAAAAAZgAAAAAAAAAA 96 | AAARAE8AYgBzAGMAdQByAGUAIABEAHUAbABsACAAUgBlAGQAAAAA/wDMAMwA 97 | AAAAAAAOAFAAYQBsAGUAIABXAGUAYQBrACAAUgBlAGQAAAAAzACZAJkAAAAA 98 | AAAPAEwAaQBnAGgAdAAgAFcAZQBhAGsAIABSAGUAZAAAAACZAGYAZgAAAAAA 99 | ABAATQBlAGQAaQB1AG0AIABXAGUAYQBrACAAUgBlAGQAAAAAZgAzADMAAAAA 100 | AAAOAEQAYQByAGsAIABXAGUAYQBrACAAUgBlAGQAAAAAMwAAAAAAAAAAAAAR 101 | AE8AYgBzAGMAdQByAGUAIABXAGUAYQBrACAAUgBlAGQAAAAA/wAzAAAAAAAA 102 | AAAPAFIAZQBkAC0AUgBlAGQALQBPAHIAYQBuAGcAZQAAAAD/AGYAMwAAAAAA 103 | ABEATABpAGcAaAB0ACAAUgBlAGQALQBPAHIAYQBuAGcAZQAAAADMADMAAAAA 104 | AAAAABAARABhAHIAawAgAFIAZQBkAC0ATwByAGEAbgBnAGUAAAAA/wCZAGYA 105 | AAAAAAARAEwAaQBnAGgAdAAgAE8AcgBhAG4AZwBlAC0AUgBlAGQAAAAAzABm 106 | ADMAAAAAAAASAE0AZQBkAGkAdQBtACAATwByAGEAbgBnAGUALQBSAGUAZAAA 107 | AACZADMAAAAAAAAAABAARABhAHIAawAgAE8AcgBhAG4AZwBlAC0AUgBlAGQA 108 | AAAA/wBmAAAAAAAAAAASAE8AcgBhAG4AZwBlAC0ATwByAGEAbgBnAGUALQBS 109 | AGUAZAAAAAD/AJkAMwAAAAAAABIATABpAGcAaAB0ACAASABhAHIAZAAgAE8A 110 | cgBhAG4AZwBlAAAAAMwAZgAAAAAAAAAAEQBEAGEAcgBrACAASABhAHIAZAAg 111 | AE8AcgBhAG4AZwBlAAAAAP8AzACZAAAAAAAAEQBQAGEAbABlACAARAB1AGwA 112 | bAAgAE8AcgBhAG4AZwBlAAAAAMwAmQBmAAAAAAAAEgBMAGkAZwBoAHQAIABE 113 | AHUAbABsACAATwByAGEAbgBnAGUAAAAAmQBmADMAAAAAAAARAEQAYQByAGsA 114 | IABEAHUAbABsACAATwByAGEAbgBnAGUAAAAAZgAzAAAAAAAAAAAUAE8AYgBz 115 | AGMAdQByAGUAIABEAHUAbABsACAATwByAGEAbgBnAGUAAAAA/wCZAAAAAAAA 116 | AAAVAE8AcgBhAG4AZwBlAC0ATwByAGEAbgBnAGUALQBZAGUAbABsAG8AdwAA 117 | AAD/AMwAZgAAAAAAABQATABpAGcAaAB0ACAATwByAGEAbgBnAGUALQBZAGUA 118 | bABsAG8AdwAAAADMAJkAMwAAAAAAABUATQBlAGQAaQB1AG0AIABPAHIAYQBu 119 | AGcAZQAtAFkAZQBsAGwAbwB3AAAAAJkAZgAAAAAAAAAAEwBEAGEAcgBrACAA 120 | TwByAGEAbgBnAGUALQBZAGUAbABsAG8AdwAAAADMAJkAAAAAAAAAABMARABh 121 | AHIAawAgAFkAZQBsAGwAbwB3AC0ATwByAGEAbgBnAGUAAAAA/wDMADMAAAAA 122 | AAAUAEwAaQBnAGgAdAAgAFkAZQBsAGwAbwB3AC0ATwByAGEAbgBnAGUAAAAA 123 | /wDMAAAAAAAAAAAVAFkAZQBsAGwAbwB3AC0AWQBlAGwAbABvAHcALQBPAHIA 124 | YQBuAGcAZQAAAAD/AP8AAAAAAAAAAAcAWQBlAGwAbABvAHcAAAAA/wD/ADMA 125 | AAAAAAASAEwAaQBnAGgAdAAgAEgAYQByAGQAIABZAGUAbABsAG8AdwAAAADM 126 | AMwAAAAAAAAAABEARABhAHIAawAgAEgAYQByAGQAIABZAGUAbABsAG8AdwAA 127 | AAD/AP8AZgAAAAAAABMATABpAGcAaAB0ACAARgBhAGQAZQBkACAAWQBlAGwA 128 | bABvAHcAAAAAzADMADMAAAAAAAAUAE0AZQBkAGkAdQBtACAARgBhAGQAZQBk 129 | ACAAWQBlAGwAbABvAHcAAAAAmQCZAAAAAAAAAAASAEQAYQByAGsAIABGAGEA 130 | ZABlAGQAIABZAGUAbABsAG8AdwAAAAD/AP8AmQAAAAAAABEAUABhAGwAZQAg 131 | AEQAdQBsAGwAIABZAGUAbABsAG8AdwAAAADMAMwAZgAAAAAAABIATABpAGcA 132 | aAB0ACAARAB1AGwAbAAgAFkAZQBsAGwAbwB3AAAAAJkAmQAzAAAAAAAAEQBE 133 | AGEAcgBrACAARAB1AGwAbAAgAFkAZQBsAGwAbwB3AAAAAGYAZgAAAAAAAAAA 134 | FABPAGIAcwBjAHUAcgBlACAARAB1AGwAbAAgAFkAZQBsAGwAbwB3AAAAAP8A 135 | /wDMAAAAAAAAEQBQAGEAbABlACAAVwBlAGEAawAgAFkAZQBsAGwAbwB3AAAA 136 | AMwAzACZAAAAAAAAEgBMAGkAZwBoAHQAIABXAGUAYQBrACAAWQBlAGwAbABv 137 | AHcAAAAAmQCZAGYAAAAAAAATAE0AZQBkAGkAdQBtACAAVwBlAGEAawAgAFkA 138 | ZQBsAGwAbwB3AAAAAGYAZgAzAAAAAAAAEQBEAGEAcgBrACAAVwBlAGEAawAg 139 | AFkAZQBsAGwAbwB3AAAAADMAMwAAAAAAAAAAFABPAGIAcwBjAHUAcgBlACAA 140 | VwBlAGEAawAgAFkAZQBsAGwAbwB3AAAAAMwA/wAAAAAAAAAAFQBZAGUAbABs 141 | AG8AdwAtAFkAZQBsAGwAbwB3AC0AUwBwAHIAaQBuAGcAAAAAzAD/ADMAAAAA 142 | AAAUAEwAaQBnAGgAdAAgAFkAZQBsAGwAbwB3AC0AUwBwAHIAaQBuAGcAAAAA 143 | mQDMAAAAAAAAAAATAEQAYQByAGsAIABZAGUAbABsAG8AdwAtAFMAcAByAGkA 144 | bgBnAAAAAMwA/wBmAAAAAAAAFABMAGkAZwBoAHQAIABTAHAAcgBpAG4AZwAt 145 | AFkAZQBsAGwAbwB3AAAAAJkAzAAzAAAAAAAAFQBNAGUAZABpAHUAbQAgAFMA 146 | cAByAGkAbgBnAC0AWQBlAGwAbABvAHcAAAAAZgCZAAAAAAAAAAATAEQAYQBy 147 | AGsAIABTAHAAcgBpAG4AZwAtAFkAZQBsAGwAbwB3AAAAAJkA/wAAAAAAAAAA 148 | FQBTAHAAcgBpAG4AZwAtAFMAcAByAGkAbgBnAC0AWQBlAGwAbABvAHcAAAAA 149 | mQD/ADMAAAAAAAASAEwAaQBnAGgAdAAgAEgAYQByAGQAIABTAHAAcgBpAG4A 150 | ZwAAAABmAMwAAAAAAAAAABEARABhAHIAawAgAEgAYQByAGQAIABTAHAAcgBp 151 | AG4AZwAAAADMAP8AmQAAAAAAABEAUABhAGwAZQAgAEQAdQBsAGwAIABTAHAA 152 | cgBpAG4AZwAAAACZAMwAZgAAAAAAABIATABpAGcAaAB0ACAARAB1AGwAbAAg 153 | AFMAcAByAGkAbgBnAAAAAGYAmQAzAAAAAAAAEQBEAGEAcgBrACAARAB1AGwA 154 | bAAgAFMAcAByAGkAbgBnAAAAADMAZgAAAAAAAAAAFABPAGIAcwBjAHUAcgBl 155 | ACAARAB1AGwAbAAgAFMAcAByAGkAbgBnAAAAAGYA/wAAAAAAAAAAFABTAHAA 156 | cgBpAG4AZwAtAFMAcAByAGkAbgBnAC0ARwByAGUAZQBuAAAAAJkA/wBmAAAA 157 | AAAAEwBMAGkAZwBoAHQAIABTAHAAcgBpAG4AZwAtAEcAcgBlAGUAbgAAAABm 158 | AMwAMwAAAAAAABQATQBlAGQAaQB1AG0AIABTAHAAcgBpAG4AZwAtAEcAcgBl 159 | AGUAbgAAAAAzAJkAAAAAAAAAABIARABhAHIAawAgAFMAcAByAGkAbgBnAC0A 160 | RwByAGUAZQBuAAAAAGYA/wAzAAAAAAAAEwBMAGkAZwBoAHQAIABHAHIAZQBl 161 | AG4ALQBTAHAAcgBpAG4AZwAAAAAzAMwAAAAAAAAAABIARABhAHIAawAgAEcA 162 | cgBlAGUAbgAtAFMAcAByAGkAbgBnAAAAADMA/wAAAAAAAAAAEwBHAHIAZQBl 163 | AG4ALQBHAHIAZQBlAG4ALQBTAHAAcgBpAG4AZwAAAAAAAP8AAAAAAAAAAAYA 164 | RwByAGUAZQBuAAAAADMA/wAzAAAAAAAAEQBMAGkAZwBoAHQAIABIAGEAcgBk 165 | ACAARwByAGUAZQBuAAAAAAAAzAAAAAAAAAAAEABEAGEAcgBrACAASABhAHIA 166 | ZAAgAEcAcgBlAGUAbgAAAABmAP8AZgAAAAAAABIATABpAGcAaAB0ACAARgBh 167 | AGQAZQBkACAARwByAGUAZQBuAAAAADMAzAAzAAAAAAAAEwBNAGUAZABpAHUA 168 | bQAgAEYAYQBkAGUAZAAgAEcAcgBlAGUAbgAAAAAAAJkAAAAAAAAAABEARABh 169 | AHIAawAgAEYAYQBkAGUAZAAgAEcAcgBlAGUAbgAAAACZAP8AmQAAAAAAABAA 170 | UABhAGwAZQAgAEQAdQBsAGwAIABHAHIAZQBlAG4AAAAAZgDMAGYAAAAAAAAR 171 | AEwAaQBnAGgAdAAgAEQAdQBsAGwAIABHAHIAZQBlAG4AAAAAMwCZADMAAAAA 172 | AAAQAEQAYQByAGsAIABEAHUAbABsACAARwByAGUAZQBuAAAAAAAAZgAAAAAA 173 | AAAAEwBPAGIAcwBjAHUAcgBlACAARAB1AGwAbAAgAEcAcgBlAGUAbgAAAADM 174 | AP8AzAAAAAAAABAAUABhAGwAZQAgAFcAZQBhAGsAIABHAHIAZQBlAG4AAAAA 175 | mQDMAJkAAAAAAAARAEwAaQBnAGgAdAAgAFcAZQBhAGsAIABHAHIAZQBlAG4A 176 | AAAAZgCZAGYAAAAAAAASAE0AZQBkAGkAdQBtACAAVwBlAGEAawAgAEcAcgBl 177 | AGUAbgAAAAAzAGYAMwAAAAAAABAARABhAHIAawAgAFcAZQBhAGsAIABHAHIA 178 | ZQBlAG4AAAAAAAAzAAAAAAAAAAATAE8AYgBzAGMAdQByAGUAIABXAGUAYQBr 179 | ACAARwByAGUAZQBuAAAAAAAA/wAzAAAAAAAAEQBHAHIAZQBlAG4ALQBHAHIA 180 | ZQBlAG4ALQBUAGUAYQBsAAAAADMA/wBmAAAAAAAAEQBMAGkAZwBoAHQAIABH 181 | AHIAZQBlAG4ALQBUAGUAYQBsAAAAAAAAzAAzAAAAAAAAEABEAGEAcgBrACAA 182 | RwByAGUAZQBuAC0AVABlAGEAbAAAAABmAP8AmQAAAAAAABEATABpAGcAaAB0 183 | ACAAVABlAGEAbAAtAEcAcgBlAGUAbgAAAAAzAMwAZgAAAAAAABIATQBlAGQA 184 | aQB1AG0AIABUAGUAYQBsAC0ARwByAGUAZQBuAAAAAAAAmQAzAAAAAAAAEABE 185 | AGEAcgBrACAAVABlAGEAbAAtAEcAcgBlAGUAbgAAAAAAAP8AZgAAAAAAABAA 186 | VABlAGEAbAAtAFQAZQBhAGwALQBHAHIAZQBlAG4AAAAAMwD/AJkAAAAAAAAQ 187 | AEwAaQBnAGgAdAAgAEgAYQByAGQAIABUAGUAYQBsAAAAAAAAzABmAAAAAAAA 188 | DwBEAGEAcgBrACAASABhAHIAZAAgAFQAZQBhAGwAAAAAmQD/AMwAAAAAAAAP 189 | AFAAYQBsAGUAIABEAHUAbABsACAAVABlAGEAbAAAAABmAMwAmQAAAAAAABAA 190 | TABpAGcAaAB0ACAARAB1AGwAbAAgAFQAZQBhAGwAAAAAMwCZAGYAAAAAAAAP 191 | AEQAYQByAGsAIABEAHUAbABsACAAVABlAGEAbAAAAAAAAGYAMwAAAAAAABIA 192 | TwBiAHMAYwB1AHIAZQAgAEQAdQBsAGwAIABUAGUAYQBsAAAAAAAA/wCZAAAA 193 | AAAADwBUAGUAYQBsAC0AVABlAGEAbAAtAEMAeQBhAG4AAAAAZgD/AMwAAAAA 194 | AAAQAEwAaQBnAGgAdAAgAFQAZQBhAGwALQBDAHkAYQBuAAAAADMAzACZAAAA 195 | AAAAEQBNAGUAZABpAHUAbQAgAFQAZQBhAGwALQBDAHkAYQBuAAAAAAAAmQBm 196 | AAAAAAAADwBEAGEAcgBrACAAVABlAGEAbAAtAEMAeQBhAG4AAAAAMwD/AMwA 197 | AAAAAAAQAEwAaQBnAGgAdAAgAEMAeQBhAG4ALQBUAGUAYQBsAAAAAAAAzACZ 198 | AAAAAAAADwBEAGEAcgBrACAAQwB5AGEAbgAtAFQAZQBhAGwAAAAAAAD/AMwA 199 | AAAAAAAPAEMAeQBhAG4ALQBDAHkAYQBuAC0AVABlAGEAbAAAAAAAAP8A/wAA 200 | AAAAAAUAQwB5AGEAbgAAAAAzAP8A/wAAAAAAABAATABpAGcAaAB0ACAASABh 201 | AHIAZAAgAEMAeQBhAG4AAAAAAADMAMwAAAAAAAAPAEQAYQByAGsAIABIAGEA 202 | cgBkACAAQwB5AGEAbgAAAABmAP8A/wAAAAAAABEATABpAGcAaAB0ACAARgBh 203 | AGQAZQBkACAAQwB5AGEAbgAAAAAzAMwAzAAAAAAAABIATQBlAGQAaQB1AG0A 204 | IABGAGEAZABlAGQAIABDAHkAYQBuAAAAAAAAmQCZAAAAAAAAEABEAGEAcgBr 205 | ACAARgBhAGQAZQBkACAAQwB5AGEAbgAAAACZAP8A/wAAAAAAAA8AUABhAGwA 206 | ZQAgAEQAdQBsAGwAIABDAHkAYQBuAAAAAGYAzADMAAAAAAAAEABMAGkAZwBo 207 | AHQAIABEAHUAbABsACAAQwB5AGEAbgAAAAAzAJkAmQAAAAAAAA8ARABhAHIA 208 | awAgAEQAdQBsAGwAIABDAHkAYQBuAAAAAAAAZgBmAAAAAAAAEgBPAGIAcwBj 209 | AHUAcgBlACAARAB1AGwAbAAgAEMAeQBhAG4AAAAAzAD/AP8AAAAAAAAPAFAA 210 | YQBsAGUAIABXAGUAYQBrACAAQwB5AGEAbgAAAACZAMwAzAAAAAAAABAATABp 211 | AGcAaAB0ACAAVwBlAGEAawAgAEMAeQBhAG4AAAAAZgCZAJkAAAAAAAARAE0A 212 | ZQBkAGkAdQBtACAAVwBlAGEAawAgAEMAeQBhAG4AAAAAMwBmAGYAAAAAAAAP 213 | AEQAYQByAGsAIABXAGUAYQBrACAAQwB5AGEAbgAAAAAAADMAMwAAAAAAABIA 214 | TwBiAHMAYwB1AHIAZQAgAFcAZQBhAGsAIABDAHkAYQBuAAAAAAAAzAD/AAAA 215 | AAAAEABDAHkAYQBuAC0AQwB5AGEAbgAtAEEAegB1AHIAZQAAAAAzAMwA/wAA 216 | AAAAABEATABpAGcAaAB0ACAAQwB5AGEAbgAtAEEAegB1AHIAZQAAAAAAAJkA 217 | zAAAAAAAABAARABhAHIAawAgAEMAeQBhAG4ALQBBAHoAdQByAGUAAAAAZgDM 218 | AP8AAAAAAAARAEwAaQBnAGgAdAAgAEEAegB1AHIAZQAtAEMAeQBhAG4AAAAA 219 | MwCZAMwAAAAAAAASAE0AZQBkAGkAdQBtACAAQQB6AHUAcgBlAC0AQwB5AGEA 220 | bgAAAAAAAGYAmQAAAAAAABAARABhAHIAawAgAEEAegB1AHIAZQAtAEMAeQBh 221 | AG4AAAAAAACZAP8AAAAAAAARAEEAegB1AHIAZQAtAEEAegB1AHIAZQAtAEMA 222 | eQBhAG4AAAAAMwCZAP8AAAAAAAARAEwAaQBnAGgAdAAgAEgAYQByAGQAIABB 223 | AHoAdQByAGUAAAAAAABmAMwAAAAAAAAQAEQAYQByAGsAIABIAGEAcgBkACAA 224 | QQB6AHUAcgBlAAAAAJkAzAD/AAAAAAAAEABQAGEAbABlACAARAB1AGwAbAAg 225 | AEEAegB1AHIAZQAAAABmAJkAzAAAAAAAABEATABpAGcAaAB0ACAARAB1AGwA 226 | bAAgAEEAegB1AHIAZQAAAAAzAGYAmQAAAAAAABAARABhAHIAawAgAEQAdQBs 227 | AGwAIABBAHoAdQByAGUAAAAAAAAzAGYAAAAAAAATAE8AYgBzAGMAdQByAGUA 228 | IABEAHUAbABsACAAQQB6AHUAcgBlAAAAAAAAZgD/AAAAAAAAEQBBAHoAdQBy 229 | AGUALQBBAHoAdQByAGUALQBCAGwAdQBlAAAAAGYAmQD/AAAAAAAAEQBMAGkA 230 | ZwBoAHQAIABBAHoAdQByAGUALQBCAGwAdQBlAAAAADMAZgDMAAAAAAAAEgBN 231 | AGUAZABpAHUAbQAgAEEAegB1AHIAZQAtAEIAbAB1AGUAAAAAAAAzAJkAAAAA 232 | AAAQAEQAYQByAGsAIABBAHoAdQByAGUALQBCAGwAdQBlAAAAADMAZgD/AAAA 233 | AAAAEQBMAGkAZwBoAHQAIABCAGwAdQBlAC0AQQB6AHUAcgBlAAAAAAAAMwDM 234 | AAAAAAAAEABEAGEAcgBrACAAQgBsAHUAZQAtAEEAegB1AHIAZQAAAAAAADMA 235 | /wAAAAAAABAAQgBsAHUAZQAtAEIAbAB1AGUALQBBAHoAdQByAGUAAAAAAAAA 236 | AP8AAAAAAAAFAEIAbAB1AGUAAAAAMwAzAP8AAAAAAAAQAEwAaQBnAGgAdAAg 237 | AEgAYQByAGQAIABCAGwAdQBlAAAAAAAAAADMAAAAAAAADwBEAGEAcgBrACAA 238 | SABhAHIAZAAgAEIAbAB1AGUAAAAAZgBmAP8AAAAAAAARAEwAaQBnAGgAdAAg 239 | AEYAYQBkAGUAZAAgAEIAbAB1AGUAAAAAMwAzAMwAAAAAAAASAE0AZQBkAGkA 240 | dQBtACAARgBhAGQAZQBkACAAQgBsAHUAZQAAAAAAAAAAmQAAAAAAABAARABh 241 | AHIAawAgAEYAYQBkAGUAZAAgAEIAbAB1AGUAAAAAmQCZAP8AAAAAAAAPAFAA 242 | YQBsAGUAIABEAHUAbABsACAAQgBsAHUAZQAAAABmAGYAzAAAAAAAABAATABp 243 | AGcAaAB0ACAARAB1AGwAbAAgAEIAbAB1AGUAAAAAMwAzAJkAAAAAAAAPAEQA 244 | YQByAGsAIABEAHUAbABsACAAQgBsAHUAZQAAAAAAAAAAZgAAAAAAABIATwBi 245 | AHMAYwB1AHIAZQAgAEQAdQBsAGwAIABCAGwAdQBlAAAAAMwAzAD/AAAAAAAA 246 | DwBQAGEAbABlACAAVwBlAGEAawAgAEIAbAB1AGUAAAAAmQCZAMwAAAAAAAAQ 247 | AEwAaQBnAGgAdAAgAFcAZQBhAGsAIABCAGwAdQBlAAAAAGYAZgCZAAAAAAAA 248 | EQBNAGUAZABpAHUAbQAgAFcAZQBhAGsAIABCAGwAdQBlAAAAADMAMwBmAAAA 249 | AAAADwBEAGEAcgBrACAAVwBlAGEAawAgAEIAbAB1AGUAAAAAAAAAADMAAAAA 250 | AAASAE8AYgBzAGMAdQByAGUAIABXAGUAYQBrACAAQgBsAHUAZQAAAAAzAAAA 251 | /wAAAAAAABEAQgBsAHUAZQAtAEIAbAB1AGUALQBWAGkAbwBsAGUAdAAAAABm 252 | ADMA/wAAAAAAABIATABpAGcAaAB0ACAAQgBsAHUAZQAtAFYAaQBvAGwAZQB0 253 | AAAAADMAAADMAAAAAAAAEQBEAGEAcgBrACAAQgBsAHUAZQAtAFYAaQBvAGwA 254 | ZQB0AAAAAJkAZgD/AAAAAAAAEgBMAGkAZwBoAHQAIABWAGkAbwBsAGUAdAAt 255 | AEIAbAB1AGUAAAAAZgAzAMwAAAAAAAATAE0AZQBkAGkAdQBtACAAVgBpAG8A 256 | bABlAHQALQBCAGwAdQBlAAAAADMAAACZAAAAAAAAEQBEAGEAcgBrACAAVgBp 257 | AG8AbABlAHQALQBCAGwAdQBlAAAAAGYAAAD/AAAAAAAAEwBWAGkAbwBsAGUA 258 | dAAtAFYAaQBvAGwAZQB0AC0AQgBsAHUAZQAAAACZADMA/wAAAAAAABIATABp 259 | AGcAaAB0ACAASABhAHIAZAAgAFYAaQBvAGwAZQB0AAAAAGYAAADMAAAAAAAA 260 | EQBEAGEAcgBrACAASABhAHIAZAAgAFYAaQBvAGwAZQB0AAAAAMwAmQD/AAAA 261 | AAAAEQBQAGEAbABlACAARAB1AGwAbAAgAFYAaQBvAGwAZQB0AAAAAJkAZgDM 262 | AAAAAAAAEgBMAGkAZwBoAHQAIABEAHUAbABsACAAVgBpAG8AbABlAHQAAAAA 263 | ZgAzAJkAAAAAAAARAEQAYQByAGsAIABEAHUAbABsACAAVgBpAG8AbABlAHQA 264 | AAAAMwAAAGYAAAAAAAAUAE8AYgBzAGMAdQByAGUAIABEAHUAbABsACAAVgBp 265 | AG8AbABlAHQAAAAAmQAAAP8AAAAAAAAWAFYAaQBvAGwAZQB0AC0AVgBpAG8A 266 | bABlAHQALQBNAGEAZwBlAG4AdABhAAAAAMwAZgD/AAAAAAAAFQBMAGkAZwBo 267 | AHQAIABWAGkAbwBsAGUAdAAtAE0AYQBnAGUAbgB0AGEAAAAAmQAzAMwAAAAA 268 | AAAWAE0AZQBkAGkAdQBtACAAVgBpAG8AbABlAHQALQBNAGEAZwBlAG4AdABh 269 | AAAAAGYAAACZAAAAAAAAFABEAGEAcgBrACAAVgBpAG8AbABlAHQALQBNAGEA 270 | ZwBlAG4AdABhAAAAAMwAMwD/AAAAAAAAFQBMAGkAZwBoAHQAIABNAGEAZwBl 271 | AG4AdABhAC0AVgBpAG8AbABlAHQAAAAAmQAAAMwAAAAAAAAUAEQAYQByAGsA 272 | IABNAGEAZwBlAG4AdABhAC0AVgBpAG8AbABlAHQAAAAAzAAAAP8AAAAAAAAX 273 | AE0AYQBnAGUAbgB0AGEALQBNAGEAZwBlAG4AdABhAC0AVgBpAG8AbABlAHQA 274 | AAAA/wAAAP8AAAAAAAAIAE0AYQBnAGUAbgB0AGEAAAAA/wAzAP8AAAAAAAAT 275 | AEwAaQBnAGgAdAAgAEgAYQByAGQAIABNAGEAZwBlAG4AdABhAAAAAMwAAADM 276 | AAAAAAAAEgBEAGEAcgBrACAASABhAHIAZAAgAE0AYQBnAGUAbgB0AGEAAAAA 277 | /wBmAP8AAAAAAAAUAEwAaQBnAGgAdAAgAEYAYQBkAGUAZAAgAE0AYQBnAGUA 278 | bgB0AGEAAAAAzAAzAMwAAAAAAAAVAE0AZQBkAGkAdQBtACAARgBhAGQAZQBk 279 | ACAATQBhAGcAZQBuAHQAYQAAAACZAAAAmQAAAAAAABMARABhAHIAawAgAEYA 280 | YQBkAGUAZAAgAE0AYQBnAGUAbgB0AGEAAAAA/wCZAP8AAAAAAAASAFAAYQBs 281 | AGUAIABEAHUAbABsACAATQBhAGcAZQBuAHQAYQAAAADMAGYAzAAAAAAAABMA 282 | TABpAGcAaAB0ACAARAB1AGwAbAAgAE0AYQBnAGUAbgB0AGEAAAAAmQAzAJkA 283 | AAAAAAASAEQAYQByAGsAIABEAHUAbABsACAATQBhAGcAZQBuAHQAYQAAAABm 284 | AAAAZgAAAAAAABUATwBiAHMAYwB1AHIAZQAgAEQAdQBsAGwAIABNAGEAZwBl 285 | AG4AdABhAAAAAP8AzAD/AAAAAAAAEgBQAGEAbABlACAAVwBlAGEAawAgAE0A 286 | YQBnAGUAbgB0AGEAAAAAzACZAMwAAAAAAAATAEwAaQBnAGgAdAAgAFcAZQBh 287 | AGsAIABNAGEAZwBlAG4AdABhAAAAAJkAZgCZAAAAAAAAFABNAGUAZABpAHUA 288 | bQAgAFcAZQBhAGsAIABNAGEAZwBlAG4AdABhAAAAAGYAMwBmAAAAAAAAEgBE 289 | AGEAcgBrACAAVwBlAGEAawAgAE0AYQBnAGUAbgB0AGEAAAAAMwAAADMAAAAA 290 | AAAVAE8AYgBzAGMAdQByAGUAIABXAGUAYQBrACAATQBhAGcAZQBuAHQAYQAA 291 | AAD/AAAAzAAAAAAAABUATQBhAGcAZQBuAHQAYQAtAE0AYQBnAGUAbgB0AGEA 292 | LQBQAGkAbgBrAAAAAP8AMwDMAAAAAAAAEwBMAGkAZwBoAHQAIABNAGEAZwBl 293 | AG4AdABhAC0AUABpAG4AawAAAADMAAAAmQAAAAAAABIARABhAHIAawAgAE0A 294 | YQBnAGUAbgB0AGEALQBQAGkAbgBrAAAAAP8AZgDMAAAAAAAAEwBMAGkAZwBo 295 | AHQAIABQAGkAbgBrAC0ATQBhAGcAZQBuAHQAYQAAAADMADMAmQAAAAAAABQA 296 | TQBlAGQAaQB1AG0AIABQAGkAbgBrAC0ATQBhAGcAZQBuAHQAYQAAAACZAAAA 297 | ZgAAAAAAABIARABhAHIAawAgAFAAaQBuAGsALQBNAGEAZwBlAG4AdABhAAAA 298 | AP8AAACZAAAAAAAAEgBQAGkAbgBrAC0AUABpAG4AawAtAE0AYQBnAGUAbgB0 299 | AGEAAAAA/wAzAJkAAAAAAAAQAEwAaQBnAGgAdAAgAEgAYQByAGQAIABQAGkA 300 | bgBrAAAAAMwAAABmAAAAAAAADwBEAGEAcgBrACAASABhAHIAZAAgAFAAaQBu 301 | AGsAAAAA/wCZAMwAAAAAAAAPAFAAYQBsAGUAIABEAHUAbABsACAAUABpAG4A 302 | awAAAADMAGYAmQAAAAAAABAATABpAGcAaAB0ACAARAB1AGwAbAAgAFAAaQBu 303 | AGsAAAAAmQAzAGYAAAAAAAAPAEQAYQByAGsAIABEAHUAbABsACAAUABpAG4A 304 | awAAAABmAAAAMwAAAAAAABIATwBiAHMAYwB1AHIAZQAgAEQAdQBsAGwAIABQ 305 | AGkAbgBrAAAAAP8AAABmAAAAAAAADgBQAGkAbgBrAC0AUABpAG4AawAtAFIA 306 | ZQBkAAAAAP8AZgCZAAAAAAAADwBMAGkAZwBoAHQAIABQAGkAbgBrAC0AUgBl 307 | AGQAAAAAzAAzAGYAAAAAAAAQAE0AZQBkAGkAdQBtACAAUABpAG4AawAtAFIA 308 | ZQBkAAAAAJkAAAAzAAAAAAAADgBEAGEAcgBrACAAUABpAG4AawAtAFIAZQBk 309 | AAAAAP8AMwBmAAAAAAAADwBMAGkAZwBoAHQAIABSAGUAZAAtAFAAaQBuAGsA 310 | AAAAzAAAADMAAAAAAAAOAEQAYQByAGsAIABSAGUAZAAtAFAAaQBuAGsAAAAA 311 | /wAAADMAAAAAAAANAFIAZQBkAC0AUgBlAGQALQBQAGkAbgBrAAA= 312 | EOS 313 | 314 | EXERCISE = <<-EOS 315 | AAIABwAAHgA8AFoAAAAAAAAEAFIARwBCAAAAAQKPGZn//wAAAAAABABIAFMA 316 | QgAAAAIMzH//GZn//wAAAAUAQwBNAFkASwAAAAcbWOiQG1gAAAAAAAQATABB 317 | AEIAAAAIFXwAAAAAAAAAAAAFAEcAcgBhAHkAAAAJCcQNrBGUBdwAAAAGAFcA 318 | QwBNAFkASwAAAA0JxA2sEZQF3AAAAAsAVQBOAEsATgBPAFcATgAgADEAMwAA 319 | EOS 320 | 321 | # http://www.visibone.com/swatches/VisiBone.aco 322 | # http://www.visibone.com/swatches/VisiBone2.aco 323 | # http://www.visibone.com/swatches/VisiBone2_km.psppalette 324 | # http://www.visibone.com/swatches/VisiBone2_km.pal 325 | # http://www.visibone.com/swatches/VisiBone2_vaccc.ai 326 | # http://www.visibone.com/swatches/VisiBone.gimp 327 | # http://www.visibone.com/swatches/VisiBone.act 328 | 329 | def setup 330 | @filename = "test#{Process.pid}.aco" 331 | end 332 | 333 | def teardown 334 | require 'fileutils' 335 | FileUtils.rm_f @filename if File.exist? @filename 336 | end 337 | 338 | def test_version1 339 | v1 = VISIBONE_V1.unpack("m*")[0] 340 | assert_nothing_raised { @aco = AdobeColor.new(v1) } 341 | assert_equal(216, @aco.size) 342 | assert_equal(1, @aco.version) 343 | assert_equal({:rgb => 216}, @aco.statistics) 344 | assert_equal(Color::RGB::White, @aco[0]) 345 | assert_equal("#ff0033", @aco[-1].html) 346 | assert_equal(v1, @aco.to_aco) 347 | end 348 | 349 | def test_version2 350 | v2 = VISIBONE_V2.unpack("m*")[0] 351 | @aco = AdobeColor.new(v2) 352 | assert_equal(216, @aco.size) 353 | assert_equal(2, @aco.version) 354 | assert_equal({:rgb => 216}, @aco.statistics) 355 | assert_equal(Color::RGB::White, @aco[0]) 356 | assert_equal(Color::RGB::White, 357 | @aco["\000W\000h\000i\000t\000e"][0]) 358 | assert_equal("#ff0033", @aco[-1].html) 359 | assert_equal("#ff0033", 360 | @aco["\000R\000e\000d\000-\000R\000e\000d\000-\000P\000i\000n\000k"][0].html) 361 | assert_equal(v2, @aco.to_aco) 362 | end 363 | 364 | def test_bogus 365 | o = VISIBONE_V2.unpack("m*")[0] 366 | v = o.dup 367 | v[0, 2] = [ 322 ].pack("n") # break the version 368 | assert_raises(RuntimeError) { AdobeColor.new(v) } 369 | v = o.dup 370 | v[2, 2] = [ 217 ].pack("n") # break the colour count 371 | assert_raises(IndexError) { @aco = AdobeColor.new(v) } 372 | v = o.dup 373 | v[14, 2] = [ 99 ].pack("n") # break the NULL before the name 374 | assert_raises(IndexError) { @aco = AdobeColor.new(v) } 375 | v = o.dup 376 | v[16, 2] = [ 18 ].pack("n") # break the length of the name 377 | assert_raises(IndexError) { @aco = AdobeColor.new(v) } 378 | v = o.dup 379 | v[28, 2] = [ 99 ].pack("n") # break the trailing null of the name 380 | end 381 | 382 | def test_exercise 383 | assert_nothing_raised do 384 | File.open(@filename, "wb") do |f| 385 | f.write EXERCISE.unpack("m*")[0] 386 | end 387 | end 388 | assert_nothing_raised { @aco = AdobeColor.from_file(@filename) } 389 | assert_equal(5, @aco.size) 390 | assert_equal(7, @aco.instance_variable_get(:@order).size) 391 | end 392 | 393 | def test_each 394 | v1 = VISIBONE_V1.unpack("m*")[0] 395 | @aco = AdobeColor.new(v1) 396 | @aco.each { |c| assert_kind_of(Color::RGB, c) } 397 | end 398 | 399 | def test_each_name 400 | v2 = VISIBONE_V2.unpack("m*")[0] 401 | assert_nothing_raised { @aco = AdobeColor.new(v2) } 402 | assert_equal(216, @aco.size) 403 | assert_equal(2, @aco.version) 404 | @aco.each_name do |n, s| 405 | assert_equal(0, n[0]) if RUBY_VERSION < "1.9" 406 | assert_equal(1, s.size) 407 | assert_kind_of(Color::RGB, s[0]) 408 | end 409 | end 410 | 411 | def test_values_at 412 | v2 = VISIBONE_V2.unpack("m*")[0] 413 | assert_nothing_raised { @aco = AdobeColor.new(v2) } 414 | assert_equal(216, @aco.size) 415 | assert_equal(2, @aco.version) 416 | assert_equal([Color::RGB::White, Color::RGB.from_html("#ff0033")], 417 | @aco.values_at(0, -1)) 418 | end 419 | end 420 | end 421 | end 422 | -------------------------------------------------------------------------------- /setup.rb: -------------------------------------------------------------------------------- 1 | # 2 | # setup.rb 3 | # 4 | # Copyright (c) 2000-2005 Minero Aoki 5 | # 6 | # This program is free software. 7 | # You can distribute/modify this program under the terms of 8 | # the GNU LGPL, Lesser General Public License version 2.1. 9 | # 10 | 11 | unless Enumerable.method_defined?(:map) # Ruby 1.4.6 12 | module Enumerable 13 | alias map collect 14 | end 15 | end 16 | 17 | unless File.respond_to?(:read) # Ruby 1.6 18 | def File.read(fname) 19 | open(fname) {|f| 20 | return f.read 21 | } 22 | end 23 | end 24 | 25 | unless Errno.const_defined?(:ENOTEMPTY) # Windows? 26 | module Errno 27 | class ENOTEMPTY 28 | # We do not raise this exception, implementation is not needed. 29 | end 30 | end 31 | end 32 | 33 | def File.binread(fname) 34 | open(fname, 'rb') {|f| 35 | return f.read 36 | } 37 | end 38 | 39 | # for corrupted Windows' stat(2) 40 | def File.dir?(path) 41 | File.directory?((path[-1,1] == '/') ? path : path + '/') 42 | end 43 | 44 | 45 | class ConfigTable 46 | 47 | include Enumerable 48 | 49 | def initialize(rbconfig) 50 | @rbconfig = rbconfig 51 | @items = [] 52 | @table = {} 53 | # options 54 | @install_prefix = nil 55 | @config_opt = nil 56 | @verbose = true 57 | @no_harm = false 58 | end 59 | 60 | attr_accessor :install_prefix 61 | attr_accessor :config_opt 62 | 63 | attr_writer :verbose 64 | 65 | def verbose? 66 | @verbose 67 | end 68 | 69 | attr_writer :no_harm 70 | 71 | def no_harm? 72 | @no_harm 73 | end 74 | 75 | def [](key) 76 | lookup(key).resolve(self) 77 | end 78 | 79 | def []=(key, val) 80 | lookup(key).set val 81 | end 82 | 83 | def names 84 | @items.map {|i| i.name } 85 | end 86 | 87 | def each(&block) 88 | @items.each(&block) 89 | end 90 | 91 | def key?(name) 92 | @table.key?(name) 93 | end 94 | 95 | def lookup(name) 96 | @table[name] or setup_rb_error "no such config item: #{name}" 97 | end 98 | 99 | def add(item) 100 | @items.push item 101 | @table[item.name] = item 102 | end 103 | 104 | def remove(name) 105 | item = lookup(name) 106 | @items.delete_if {|i| i.name == name } 107 | @table.delete_if {|name, i| i.name == name } 108 | item 109 | end 110 | 111 | def load_script(path, inst = nil) 112 | if File.file?(path) 113 | MetaConfigEnvironment.new(self, inst).instance_eval File.read(path), path 114 | end 115 | end 116 | 117 | def savefile 118 | '.config' 119 | end 120 | 121 | def load_savefile 122 | begin 123 | File.foreach(savefile()) do |line| 124 | k, v = *line.split(/=/, 2) 125 | self[k] = v.strip 126 | end 127 | rescue Errno::ENOENT 128 | setup_rb_error $!.message + "\n#{File.basename($0)} config first" 129 | end 130 | end 131 | 132 | def save 133 | @items.each {|i| i.value } 134 | File.open(savefile(), 'w') {|f| 135 | @items.each do |i| 136 | f.printf "%s=%s\n", i.name, i.value if i.value? and i.value 137 | end 138 | } 139 | end 140 | 141 | def load_standard_entries 142 | standard_entries(@rbconfig).each do |ent| 143 | add ent 144 | end 145 | end 146 | 147 | def standard_entries(rbconfig) 148 | c = rbconfig 149 | 150 | rubypath = File.join(c['bindir'], c['ruby_install_name'] + c['EXEEXT']) 151 | 152 | major = c['MAJOR'].to_i 153 | minor = c['MINOR'].to_i 154 | teeny = c['TEENY'].to_i 155 | version = "#{major}.#{minor}" 156 | 157 | # ruby ver. >= 1.4.4? 158 | newpath_p = ((major >= 2) or 159 | ((major == 1) and 160 | ((minor >= 5) or 161 | ((minor == 4) and (teeny >= 4))))) 162 | 163 | if c['rubylibdir'] 164 | # V > 1.6.3 165 | libruby = "#{c['prefix']}/lib/ruby" 166 | librubyver = c['rubylibdir'] 167 | librubyverarch = c['archdir'] 168 | siteruby = c['sitedir'] 169 | siterubyver = c['sitelibdir'] 170 | siterubyverarch = c['sitearchdir'] 171 | elsif newpath_p 172 | # 1.4.4 <= V <= 1.6.3 173 | libruby = "#{c['prefix']}/lib/ruby" 174 | librubyver = "#{c['prefix']}/lib/ruby/#{version}" 175 | librubyverarch = "#{c['prefix']}/lib/ruby/#{version}/#{c['arch']}" 176 | siteruby = c['sitedir'] 177 | siterubyver = "$siteruby/#{version}" 178 | siterubyverarch = "$siterubyver/#{c['arch']}" 179 | else 180 | # V < 1.4.4 181 | libruby = "#{c['prefix']}/lib/ruby" 182 | librubyver = "#{c['prefix']}/lib/ruby/#{version}" 183 | librubyverarch = "#{c['prefix']}/lib/ruby/#{version}/#{c['arch']}" 184 | siteruby = "#{c['prefix']}/lib/ruby/#{version}/site_ruby" 185 | siterubyver = siteruby 186 | siterubyverarch = "$siterubyver/#{c['arch']}" 187 | end 188 | parameterize = lambda {|path| 189 | path.sub(/\A#{Regexp.quote(c['prefix'])}/, '$prefix') 190 | } 191 | 192 | if arg = c['configure_args'].split.detect {|arg| /--with-make-prog=/ =~ arg } 193 | makeprog = arg.sub(/'/, '').split(/=/, 2)[1] 194 | else 195 | makeprog = 'make' 196 | end 197 | 198 | [ 199 | ExecItem.new('installdirs', 'std/site/home', 200 | 'std: install under libruby; site: install under site_ruby; home: install under $HOME')\ 201 | {|val, table| 202 | case val 203 | when 'std' 204 | table['rbdir'] = '$librubyver' 205 | table['sodir'] = '$librubyverarch' 206 | when 'site' 207 | table['rbdir'] = '$siterubyver' 208 | table['sodir'] = '$siterubyverarch' 209 | when 'home' 210 | setup_rb_error '$HOME was not set' unless ENV['HOME'] 211 | table['prefix'] = ENV['HOME'] 212 | table['rbdir'] = '$libdir/ruby' 213 | table['sodir'] = '$libdir/ruby' 214 | end 215 | }, 216 | PathItem.new('prefix', 'path', c['prefix'], 217 | 'path prefix of target environment'), 218 | PathItem.new('bindir', 'path', parameterize.call(c['bindir']), 219 | 'the directory for commands'), 220 | PathItem.new('libdir', 'path', parameterize.call(c['libdir']), 221 | 'the directory for libraries'), 222 | PathItem.new('datadir', 'path', parameterize.call(c['datadir']), 223 | 'the directory for shared data'), 224 | PathItem.new('mandir', 'path', parameterize.call(c['mandir']), 225 | 'the directory for man pages'), 226 | PathItem.new('sysconfdir', 'path', parameterize.call(c['sysconfdir']), 227 | 'the directory for system configuration files'), 228 | PathItem.new('localstatedir', 'path', parameterize.call(c['localstatedir']), 229 | 'the directory for local state data'), 230 | PathItem.new('libruby', 'path', libruby, 231 | 'the directory for ruby libraries'), 232 | PathItem.new('librubyver', 'path', librubyver, 233 | 'the directory for standard ruby libraries'), 234 | PathItem.new('librubyverarch', 'path', librubyverarch, 235 | 'the directory for standard ruby extensions'), 236 | PathItem.new('siteruby', 'path', siteruby, 237 | 'the directory for version-independent aux ruby libraries'), 238 | PathItem.new('siterubyver', 'path', siterubyver, 239 | 'the directory for aux ruby libraries'), 240 | PathItem.new('siterubyverarch', 'path', siterubyverarch, 241 | 'the directory for aux ruby binaries'), 242 | PathItem.new('rbdir', 'path', '$siterubyver', 243 | 'the directory for ruby scripts'), 244 | PathItem.new('sodir', 'path', '$siterubyverarch', 245 | 'the directory for ruby extentions'), 246 | PathItem.new('rubypath', 'path', rubypath, 247 | 'the path to set to #! line'), 248 | ProgramItem.new('rubyprog', 'name', rubypath, 249 | 'the ruby program using for installation'), 250 | ProgramItem.new('makeprog', 'name', makeprog, 251 | 'the make program to compile ruby extentions'), 252 | SelectItem.new('shebang', 'all/ruby/never', 'ruby', 253 | 'shebang line (#!) editing mode'), 254 | BoolItem.new('without-ext', 'yes/no', 'no', 255 | 'does not compile/install ruby extentions') 256 | ] 257 | end 258 | private :standard_entries 259 | 260 | def load_multipackage_entries 261 | multipackage_entries().each do |ent| 262 | add ent 263 | end 264 | end 265 | 266 | def multipackage_entries 267 | [ 268 | PackageSelectionItem.new('with', 'name,name...', '', 'ALL', 269 | 'package names that you want to install'), 270 | PackageSelectionItem.new('without', 'name,name...', '', 'NONE', 271 | 'package names that you do not want to install') 272 | ] 273 | end 274 | private :multipackage_entries 275 | 276 | ALIASES = { 277 | 'std-ruby' => 'librubyver', 278 | 'stdruby' => 'librubyver', 279 | 'rubylibdir' => 'librubyver', 280 | 'archdir' => 'librubyverarch', 281 | 'site-ruby-common' => 'siteruby', # For backward compatibility 282 | 'site-ruby' => 'siterubyver', # For backward compatibility 283 | 'bin-dir' => 'bindir', 284 | 'bin-dir' => 'bindir', 285 | 'rb-dir' => 'rbdir', 286 | 'so-dir' => 'sodir', 287 | 'data-dir' => 'datadir', 288 | 'ruby-path' => 'rubypath', 289 | 'ruby-prog' => 'rubyprog', 290 | 'ruby' => 'rubyprog', 291 | 'make-prog' => 'makeprog', 292 | 'make' => 'makeprog' 293 | } 294 | 295 | def fixup 296 | ALIASES.each do |ali, name| 297 | @table[ali] = @table[name] 298 | end 299 | @items.freeze 300 | @table.freeze 301 | @options_re = /\A--(#{@table.keys.join('|')})(?:=(.*))?\z/ 302 | end 303 | 304 | def parse_opt(opt) 305 | m = @options_re.match(opt) or setup_rb_error "config: unknown option #{opt}" 306 | m.to_a[1,2] 307 | end 308 | 309 | def dllext 310 | @rbconfig['DLEXT'] 311 | end 312 | 313 | def value_config?(name) 314 | lookup(name).value? 315 | end 316 | 317 | class Item 318 | def initialize(name, template, default, desc) 319 | @name = name.freeze 320 | @template = template 321 | @value = default 322 | @default = default 323 | @description = desc 324 | end 325 | 326 | attr_reader :name 327 | attr_reader :description 328 | 329 | attr_accessor :default 330 | alias help_default default 331 | 332 | def help_opt 333 | "--#{@name}=#{@template}" 334 | end 335 | 336 | def value? 337 | true 338 | end 339 | 340 | def value 341 | @value 342 | end 343 | 344 | def resolve(table) 345 | @value.gsub(%r<\$([^/]+)>) { table[$1] } 346 | end 347 | 348 | def set(val) 349 | @value = check(val) 350 | end 351 | 352 | private 353 | 354 | def check(val) 355 | setup_rb_error "config: --#{name} requires argument" unless val 356 | val 357 | end 358 | end 359 | 360 | class BoolItem < Item 361 | def config_type 362 | 'bool' 363 | end 364 | 365 | def help_opt 366 | "--#{@name}" 367 | end 368 | 369 | private 370 | 371 | def check(val) 372 | return 'yes' unless val 373 | case val 374 | when /\Ay(es)?\z/i, /\At(rue)?\z/i then 'yes' 375 | when /\An(o)?\z/i, /\Af(alse)\z/i then 'no' 376 | else 377 | setup_rb_error "config: --#{@name} accepts only yes/no for argument" 378 | end 379 | end 380 | end 381 | 382 | class PathItem < Item 383 | def config_type 384 | 'path' 385 | end 386 | 387 | private 388 | 389 | def check(path) 390 | setup_rb_error "config: --#{@name} requires argument" unless path 391 | path[0,1] == '$' ? path : File.expand_path(path) 392 | end 393 | end 394 | 395 | class ProgramItem < Item 396 | def config_type 397 | 'program' 398 | end 399 | end 400 | 401 | class SelectItem < Item 402 | def initialize(name, selection, default, desc) 403 | super 404 | @ok = selection.split('/') 405 | end 406 | 407 | def config_type 408 | 'select' 409 | end 410 | 411 | private 412 | 413 | def check(val) 414 | unless @ok.include?(val.strip) 415 | setup_rb_error "config: use --#{@name}=#{@template} (#{val})" 416 | end 417 | val.strip 418 | end 419 | end 420 | 421 | class ExecItem < Item 422 | def initialize(name, selection, desc, &block) 423 | super name, selection, nil, desc 424 | @ok = selection.split('/') 425 | @action = block 426 | end 427 | 428 | def config_type 429 | 'exec' 430 | end 431 | 432 | def value? 433 | false 434 | end 435 | 436 | def resolve(table) 437 | setup_rb_error "$#{name()} wrongly used as option value" 438 | end 439 | 440 | undef set 441 | 442 | def evaluate(val, table) 443 | v = val.strip.downcase 444 | unless @ok.include?(v) 445 | setup_rb_error "invalid option --#{@name}=#{val} (use #{@template})" 446 | end 447 | @action.call v, table 448 | end 449 | end 450 | 451 | class PackageSelectionItem < Item 452 | def initialize(name, template, default, help_default, desc) 453 | super name, template, default, desc 454 | @help_default = help_default 455 | end 456 | 457 | attr_reader :help_default 458 | 459 | def config_type 460 | 'package' 461 | end 462 | 463 | private 464 | 465 | def check(val) 466 | unless File.dir?("packages/#{val}") 467 | setup_rb_error "config: no such package: #{val}" 468 | end 469 | val 470 | end 471 | end 472 | 473 | class MetaConfigEnvironment 474 | def initialize(config, installer) 475 | @config = config 476 | @installer = installer 477 | end 478 | 479 | def config_names 480 | @config.names 481 | end 482 | 483 | def config?(name) 484 | @config.key?(name) 485 | end 486 | 487 | def bool_config?(name) 488 | @config.lookup(name).config_type == 'bool' 489 | end 490 | 491 | def path_config?(name) 492 | @config.lookup(name).config_type == 'path' 493 | end 494 | 495 | def value_config?(name) 496 | @config.lookup(name).config_type != 'exec' 497 | end 498 | 499 | def add_config(item) 500 | @config.add item 501 | end 502 | 503 | def add_bool_config(name, default, desc) 504 | @config.add BoolItem.new(name, 'yes/no', default ? 'yes' : 'no', desc) 505 | end 506 | 507 | def add_path_config(name, default, desc) 508 | @config.add PathItem.new(name, 'path', default, desc) 509 | end 510 | 511 | def set_config_default(name, default) 512 | @config.lookup(name).default = default 513 | end 514 | 515 | def remove_config(name) 516 | @config.remove(name) 517 | end 518 | 519 | # For only multipackage 520 | def packages 521 | raise '[setup.rb fatal] multi-package metaconfig API packages() called for single-package; contact application package vendor' unless @installer 522 | @installer.packages 523 | end 524 | 525 | # For only multipackage 526 | def declare_packages(list) 527 | raise '[setup.rb fatal] multi-package metaconfig API declare_packages() called for single-package; contact application package vendor' unless @installer 528 | @installer.packages = list 529 | end 530 | end 531 | 532 | end # class ConfigTable 533 | 534 | 535 | # This module requires: #verbose?, #no_harm? 536 | module FileOperations 537 | 538 | def mkdir_p(dirname, prefix = nil) 539 | dirname = prefix + File.expand_path(dirname) if prefix 540 | $stderr.puts "mkdir -p #{dirname}" if verbose? 541 | return if no_harm? 542 | 543 | # Does not check '/', it's too abnormal. 544 | dirs = File.expand_path(dirname).split(%r<(?=/)>) 545 | if /\A[a-z]:\z/i =~ dirs[0] 546 | disk = dirs.shift 547 | dirs[0] = disk + dirs[0] 548 | end 549 | dirs.each_index do |idx| 550 | path = dirs[0..idx].join('') 551 | Dir.mkdir path unless File.dir?(path) 552 | end 553 | end 554 | 555 | def rm_f(path) 556 | $stderr.puts "rm -f #{path}" if verbose? 557 | return if no_harm? 558 | force_remove_file path 559 | end 560 | 561 | def rm_rf(path) 562 | $stderr.puts "rm -rf #{path}" if verbose? 563 | return if no_harm? 564 | remove_tree path 565 | end 566 | 567 | def remove_tree(path) 568 | if File.symlink?(path) 569 | remove_file path 570 | elsif File.dir?(path) 571 | remove_tree0 path 572 | else 573 | force_remove_file path 574 | end 575 | end 576 | 577 | def remove_tree0(path) 578 | Dir.foreach(path) do |ent| 579 | next if ent == '.' 580 | next if ent == '..' 581 | entpath = "#{path}/#{ent}" 582 | if File.symlink?(entpath) 583 | remove_file entpath 584 | elsif File.dir?(entpath) 585 | remove_tree0 entpath 586 | else 587 | force_remove_file entpath 588 | end 589 | end 590 | begin 591 | Dir.rmdir path 592 | rescue Errno::ENOTEMPTY 593 | # directory may not be empty 594 | end 595 | end 596 | 597 | def move_file(src, dest) 598 | force_remove_file dest 599 | begin 600 | File.rename src, dest 601 | rescue 602 | File.open(dest, 'wb') {|f| 603 | f.write File.binread(src) 604 | } 605 | File.chmod File.stat(src).mode, dest 606 | File.unlink src 607 | end 608 | end 609 | 610 | def force_remove_file(path) 611 | begin 612 | remove_file path 613 | rescue 614 | end 615 | end 616 | 617 | def remove_file(path) 618 | File.chmod 0777, path 619 | File.unlink path 620 | end 621 | 622 | def install(from, dest, mode, prefix = nil) 623 | $stderr.puts "install #{from} #{dest}" if verbose? 624 | return if no_harm? 625 | 626 | realdest = prefix ? prefix + File.expand_path(dest) : dest 627 | realdest = File.join(realdest, File.basename(from)) if File.dir?(realdest) 628 | str = File.binread(from) 629 | if diff?(str, realdest) 630 | verbose_off { 631 | rm_f realdest if File.exist?(realdest) 632 | } 633 | File.open(realdest, 'wb') {|f| 634 | f.write str 635 | } 636 | File.chmod mode, realdest 637 | 638 | File.open("#{objdir_root()}/InstalledFiles", 'a') {|f| 639 | if prefix 640 | f.puts realdest.sub(prefix, '') 641 | else 642 | f.puts realdest 643 | end 644 | } 645 | end 646 | end 647 | 648 | def diff?(new_content, path) 649 | return true unless File.exist?(path) 650 | new_content != File.binread(path) 651 | end 652 | 653 | def command(*args) 654 | $stderr.puts args.join(' ') if verbose? 655 | system(*args) or raise RuntimeError, 656 | "system(#{args.map{|a| a.inspect }.join(' ')}) failed" 657 | end 658 | 659 | def ruby(*args) 660 | command config('rubyprog'), *args 661 | end 662 | 663 | def make(task = nil) 664 | command(*[config('makeprog'), task].compact) 665 | end 666 | 667 | def extdir?(dir) 668 | File.exist?("#{dir}/MANIFEST") or File.exist?("#{dir}/extconf.rb") 669 | end 670 | 671 | def files_of(dir) 672 | Dir.open(dir) {|d| 673 | return d.select {|ent| File.file?("#{dir}/#{ent}") } 674 | } 675 | end 676 | 677 | DIR_REJECT = %w( . .. CVS SCCS RCS CVS.adm .svn ) 678 | 679 | def directories_of(dir) 680 | Dir.open(dir) {|d| 681 | return d.select {|ent| File.dir?("#{dir}/#{ent}") } - DIR_REJECT 682 | } 683 | end 684 | 685 | end 686 | 687 | 688 | # This module requires: #srcdir_root, #objdir_root, #relpath 689 | module HookScriptAPI 690 | 691 | def get_config(key) 692 | @config[key] 693 | end 694 | 695 | alias config get_config 696 | 697 | # obsolete: use metaconfig to change configuration 698 | def set_config(key, val) 699 | @config[key] = val 700 | end 701 | 702 | # 703 | # srcdir/objdir (works only in the package directory) 704 | # 705 | 706 | def curr_srcdir 707 | "#{srcdir_root()}/#{relpath()}" 708 | end 709 | 710 | def curr_objdir 711 | "#{objdir_root()}/#{relpath()}" 712 | end 713 | 714 | def srcfile(path) 715 | "#{curr_srcdir()}/#{path}" 716 | end 717 | 718 | def srcexist?(path) 719 | File.exist?(srcfile(path)) 720 | end 721 | 722 | def srcdirectory?(path) 723 | File.dir?(srcfile(path)) 724 | end 725 | 726 | def srcfile?(path) 727 | File.file?(srcfile(path)) 728 | end 729 | 730 | def srcentries(path = '.') 731 | Dir.open("#{curr_srcdir()}/#{path}") {|d| 732 | return d.to_a - %w(. ..) 733 | } 734 | end 735 | 736 | def srcfiles(path = '.') 737 | srcentries(path).select {|fname| 738 | File.file?(File.join(curr_srcdir(), path, fname)) 739 | } 740 | end 741 | 742 | def srcdirectories(path = '.') 743 | srcentries(path).select {|fname| 744 | File.dir?(File.join(curr_srcdir(), path, fname)) 745 | } 746 | end 747 | 748 | end 749 | 750 | 751 | class ToplevelInstaller 752 | 753 | Version = '3.4.1' 754 | Copyright = 'Copyright (c) 2000-2005 Minero Aoki' 755 | 756 | TASKS = [ 757 | [ 'all', 'do config, setup, then install' ], 758 | [ 'config', 'saves your configurations' ], 759 | [ 'show', 'shows current configuration' ], 760 | [ 'setup', 'compiles ruby extentions and others' ], 761 | [ 'install', 'installs files' ], 762 | [ 'test', 'run all tests in test/' ], 763 | [ 'clean', "does `make clean' for each extention" ], 764 | [ 'distclean',"does `make distclean' for each extention" ] 765 | ] 766 | 767 | def ToplevelInstaller.invoke 768 | config = ConfigTable.new(load_rbconfig()) 769 | config.load_standard_entries 770 | config.load_multipackage_entries if multipackage? 771 | config.fixup 772 | klass = (multipackage?() ? ToplevelInstallerMulti : ToplevelInstaller) 773 | klass.new(File.dirname($0), config).invoke 774 | end 775 | 776 | def ToplevelInstaller.multipackage? 777 | File.dir?(File.dirname($0) + '/packages') 778 | end 779 | 780 | def ToplevelInstaller.load_rbconfig 781 | if arg = ARGV.detect {|arg| /\A--rbconfig=/ =~ arg } 782 | ARGV.delete(arg) 783 | load File.expand_path(arg.split(/=/, 2)[1]) 784 | $".push 'rbconfig.rb' 785 | else 786 | require 'rbconfig' 787 | end 788 | ::Config::CONFIG 789 | end 790 | 791 | def initialize(ardir_root, config) 792 | @ardir = File.expand_path(ardir_root) 793 | @config = config 794 | # cache 795 | @valid_task_re = nil 796 | end 797 | 798 | def config(key) 799 | @config[key] 800 | end 801 | 802 | def inspect 803 | "#<#{self.class} #{__id__()}>" 804 | end 805 | 806 | def invoke 807 | run_metaconfigs 808 | case task = parsearg_global() 809 | when nil, 'all' 810 | parsearg_config 811 | init_installers 812 | exec_config 813 | exec_setup 814 | exec_install 815 | else 816 | case task 817 | when 'config', 'test' 818 | ; 819 | when 'clean', 'distclean' 820 | @config.load_savefile if File.exist?(@config.savefile) 821 | else 822 | @config.load_savefile 823 | end 824 | __send__ "parsearg_#{task}" 825 | init_installers 826 | __send__ "exec_#{task}" 827 | end 828 | end 829 | 830 | def run_metaconfigs 831 | @config.load_script "#{@ardir}/metaconfig" 832 | end 833 | 834 | def init_installers 835 | @installer = Installer.new(@config, @ardir, File.expand_path('.')) 836 | end 837 | 838 | # 839 | # Hook Script API bases 840 | # 841 | 842 | def srcdir_root 843 | @ardir 844 | end 845 | 846 | def objdir_root 847 | '.' 848 | end 849 | 850 | def relpath 851 | '.' 852 | end 853 | 854 | # 855 | # Option Parsing 856 | # 857 | 858 | def parsearg_global 859 | while arg = ARGV.shift 860 | case arg 861 | when /\A\w+\z/ 862 | setup_rb_error "invalid task: #{arg}" unless valid_task?(arg) 863 | return arg 864 | when '-q', '--quiet' 865 | @config.verbose = false 866 | when '--verbose' 867 | @config.verbose = true 868 | when '--help' 869 | print_usage $stdout 870 | exit 0 871 | when '--version' 872 | puts "#{File.basename($0)} version #{Version}" 873 | exit 0 874 | when '--copyright' 875 | puts Copyright 876 | exit 0 877 | else 878 | setup_rb_error "unknown global option '#{arg}'" 879 | end 880 | end 881 | nil 882 | end 883 | 884 | def valid_task?(t) 885 | valid_task_re() =~ t 886 | end 887 | 888 | def valid_task_re 889 | @valid_task_re ||= /\A(?:#{TASKS.map {|task,desc| task }.join('|')})\z/ 890 | end 891 | 892 | def parsearg_no_options 893 | unless ARGV.empty? 894 | task = caller(0).first.slice(%r<`parsearg_(\w+)'>, 1) 895 | setup_rb_error "#{task}: unknown options: #{ARGV.join(' ')}" 896 | end 897 | end 898 | 899 | alias parsearg_show parsearg_no_options 900 | alias parsearg_setup parsearg_no_options 901 | alias parsearg_test parsearg_no_options 902 | alias parsearg_clean parsearg_no_options 903 | alias parsearg_distclean parsearg_no_options 904 | 905 | def parsearg_config 906 | evalopt = [] 907 | set = [] 908 | @config.config_opt = [] 909 | while i = ARGV.shift 910 | if /\A--?\z/ =~ i 911 | @config.config_opt = ARGV.dup 912 | break 913 | end 914 | name, value = *@config.parse_opt(i) 915 | if @config.value_config?(name) 916 | @config[name] = value 917 | else 918 | evalopt.push [name, value] 919 | end 920 | set.push name 921 | end 922 | evalopt.each do |name, value| 923 | @config.lookup(name).evaluate value, @config 924 | end 925 | # Check if configuration is valid 926 | set.each do |n| 927 | @config[n] if @config.value_config?(n) 928 | end 929 | end 930 | 931 | def parsearg_install 932 | @config.no_harm = false 933 | @config.install_prefix = '' 934 | while a = ARGV.shift 935 | case a 936 | when '--no-harm' 937 | @config.no_harm = true 938 | when /\A--prefix=/ 939 | path = a.split(/=/, 2)[1] 940 | path = File.expand_path(path) unless path[0,1] == '/' 941 | @config.install_prefix = path 942 | else 943 | setup_rb_error "install: unknown option #{a}" 944 | end 945 | end 946 | end 947 | 948 | def print_usage(out) 949 | out.puts 'Typical Installation Procedure:' 950 | out.puts " $ ruby #{File.basename $0} config" 951 | out.puts " $ ruby #{File.basename $0} setup" 952 | out.puts " # ruby #{File.basename $0} install (may require root privilege)" 953 | out.puts 954 | out.puts 'Detailed Usage:' 955 | out.puts " ruby #{File.basename $0} " 956 | out.puts " ruby #{File.basename $0} [] []" 957 | 958 | fmt = " %-24s %s\n" 959 | out.puts 960 | out.puts 'Global options:' 961 | out.printf fmt, '-q,--quiet', 'suppress message outputs' 962 | out.printf fmt, ' --verbose', 'output messages verbosely' 963 | out.printf fmt, ' --help', 'print this message' 964 | out.printf fmt, ' --version', 'print version and quit' 965 | out.printf fmt, ' --copyright', 'print copyright and quit' 966 | out.puts 967 | out.puts 'Tasks:' 968 | TASKS.each do |name, desc| 969 | out.printf fmt, name, desc 970 | end 971 | 972 | fmt = " %-24s %s [%s]\n" 973 | out.puts 974 | out.puts 'Options for CONFIG or ALL:' 975 | @config.each do |item| 976 | out.printf fmt, item.help_opt, item.description, item.help_default 977 | end 978 | out.printf fmt, '--rbconfig=path', 'rbconfig.rb to load',"running ruby's" 979 | out.puts 980 | out.puts 'Options for INSTALL:' 981 | out.printf fmt, '--no-harm', 'only display what to do if given', 'off' 982 | out.printf fmt, '--prefix=path', 'install path prefix', '' 983 | out.puts 984 | end 985 | 986 | # 987 | # Task Handlers 988 | # 989 | 990 | def exec_config 991 | @installer.exec_config 992 | @config.save # must be final 993 | end 994 | 995 | def exec_setup 996 | @installer.exec_setup 997 | end 998 | 999 | def exec_install 1000 | @installer.exec_install 1001 | end 1002 | 1003 | def exec_test 1004 | @installer.exec_test 1005 | end 1006 | 1007 | def exec_show 1008 | @config.each do |i| 1009 | printf "%-20s %s\n", i.name, i.value if i.value? 1010 | end 1011 | end 1012 | 1013 | def exec_clean 1014 | @installer.exec_clean 1015 | end 1016 | 1017 | def exec_distclean 1018 | @installer.exec_distclean 1019 | end 1020 | 1021 | end # class ToplevelInstaller 1022 | 1023 | 1024 | class ToplevelInstallerMulti < ToplevelInstaller 1025 | 1026 | include FileOperations 1027 | 1028 | def initialize(ardir_root, config) 1029 | super 1030 | @packages = directories_of("#{@ardir}/packages") 1031 | raise 'no package exists' if @packages.empty? 1032 | @root_installer = Installer.new(@config, @ardir, File.expand_path('.')) 1033 | end 1034 | 1035 | def run_metaconfigs 1036 | @config.load_script "#{@ardir}/metaconfig", self 1037 | @packages.each do |name| 1038 | @config.load_script "#{@ardir}/packages/#{name}/metaconfig" 1039 | end 1040 | end 1041 | 1042 | attr_reader :packages 1043 | 1044 | def packages=(list) 1045 | raise 'package list is empty' if list.empty? 1046 | list.each do |name| 1047 | raise "directory packages/#{name} does not exist"\ 1048 | unless File.dir?("#{@ardir}/packages/#{name}") 1049 | end 1050 | @packages = list 1051 | end 1052 | 1053 | def init_installers 1054 | @installers = {} 1055 | @packages.each do |pack| 1056 | @installers[pack] = Installer.new(@config, 1057 | "#{@ardir}/packages/#{pack}", 1058 | "packages/#{pack}") 1059 | end 1060 | with = extract_selection(config('with')) 1061 | without = extract_selection(config('without')) 1062 | @selected = @installers.keys.select {|name| 1063 | (with.empty? or with.include?(name)) \ 1064 | and not without.include?(name) 1065 | } 1066 | end 1067 | 1068 | def extract_selection(list) 1069 | a = list.split(/,/) 1070 | a.each do |name| 1071 | setup_rb_error "no such package: #{name}" unless @installers.key?(name) 1072 | end 1073 | a 1074 | end 1075 | 1076 | def print_usage(f) 1077 | super 1078 | f.puts 'Inluded packages:' 1079 | f.puts ' ' + @packages.sort.join(' ') 1080 | f.puts 1081 | end 1082 | 1083 | # 1084 | # Task Handlers 1085 | # 1086 | 1087 | def exec_config 1088 | run_hook 'pre-config' 1089 | each_selected_installers {|inst| inst.exec_config } 1090 | run_hook 'post-config' 1091 | @config.save # must be final 1092 | end 1093 | 1094 | def exec_setup 1095 | run_hook 'pre-setup' 1096 | each_selected_installers {|inst| inst.exec_setup } 1097 | run_hook 'post-setup' 1098 | end 1099 | 1100 | def exec_install 1101 | run_hook 'pre-install' 1102 | each_selected_installers {|inst| inst.exec_install } 1103 | run_hook 'post-install' 1104 | end 1105 | 1106 | def exec_test 1107 | run_hook 'pre-test' 1108 | each_selected_installers {|inst| inst.exec_test } 1109 | run_hook 'post-test' 1110 | end 1111 | 1112 | def exec_clean 1113 | rm_f @config.savefile 1114 | run_hook 'pre-clean' 1115 | each_selected_installers {|inst| inst.exec_clean } 1116 | run_hook 'post-clean' 1117 | end 1118 | 1119 | def exec_distclean 1120 | rm_f @config.savefile 1121 | run_hook 'pre-distclean' 1122 | each_selected_installers {|inst| inst.exec_distclean } 1123 | run_hook 'post-distclean' 1124 | end 1125 | 1126 | # 1127 | # lib 1128 | # 1129 | 1130 | def each_selected_installers 1131 | Dir.mkdir 'packages' unless File.dir?('packages') 1132 | @selected.each do |pack| 1133 | $stderr.puts "Processing the package `#{pack}' ..." if verbose? 1134 | Dir.mkdir "packages/#{pack}" unless File.dir?("packages/#{pack}") 1135 | Dir.chdir "packages/#{pack}" 1136 | yield @installers[pack] 1137 | Dir.chdir '../..' 1138 | end 1139 | end 1140 | 1141 | def run_hook(id) 1142 | @root_installer.run_hook id 1143 | end 1144 | 1145 | # module FileOperations requires this 1146 | def verbose? 1147 | @config.verbose? 1148 | end 1149 | 1150 | # module FileOperations requires this 1151 | def no_harm? 1152 | @config.no_harm? 1153 | end 1154 | 1155 | end # class ToplevelInstallerMulti 1156 | 1157 | 1158 | class Installer 1159 | 1160 | FILETYPES = %w( bin lib ext data conf man ) 1161 | 1162 | include FileOperations 1163 | include HookScriptAPI 1164 | 1165 | def initialize(config, srcroot, objroot) 1166 | @config = config 1167 | @srcdir = File.expand_path(srcroot) 1168 | @objdir = File.expand_path(objroot) 1169 | @currdir = '.' 1170 | end 1171 | 1172 | def inspect 1173 | "#<#{self.class} #{File.basename(@srcdir)}>" 1174 | end 1175 | 1176 | def noop(rel) 1177 | end 1178 | 1179 | # 1180 | # Hook Script API base methods 1181 | # 1182 | 1183 | def srcdir_root 1184 | @srcdir 1185 | end 1186 | 1187 | def objdir_root 1188 | @objdir 1189 | end 1190 | 1191 | def relpath 1192 | @currdir 1193 | end 1194 | 1195 | # 1196 | # Config Access 1197 | # 1198 | 1199 | # module FileOperations requires this 1200 | def verbose? 1201 | @config.verbose? 1202 | end 1203 | 1204 | # module FileOperations requires this 1205 | def no_harm? 1206 | @config.no_harm? 1207 | end 1208 | 1209 | def verbose_off 1210 | begin 1211 | save, @config.verbose = @config.verbose?, false 1212 | yield 1213 | ensure 1214 | @config.verbose = save 1215 | end 1216 | end 1217 | 1218 | # 1219 | # TASK config 1220 | # 1221 | 1222 | def exec_config 1223 | exec_task_traverse 'config' 1224 | end 1225 | 1226 | alias config_dir_bin noop 1227 | alias config_dir_lib noop 1228 | 1229 | def config_dir_ext(rel) 1230 | extconf if extdir?(curr_srcdir()) 1231 | end 1232 | 1233 | alias config_dir_data noop 1234 | alias config_dir_conf noop 1235 | alias config_dir_man noop 1236 | 1237 | def extconf 1238 | ruby "#{curr_srcdir()}/extconf.rb", *@config.config_opt 1239 | end 1240 | 1241 | # 1242 | # TASK setup 1243 | # 1244 | 1245 | def exec_setup 1246 | exec_task_traverse 'setup' 1247 | end 1248 | 1249 | def setup_dir_bin(rel) 1250 | files_of(curr_srcdir()).each do |fname| 1251 | update_shebang_line "#{curr_srcdir()}/#{fname}" 1252 | end 1253 | end 1254 | 1255 | alias setup_dir_lib noop 1256 | 1257 | def setup_dir_ext(rel) 1258 | make if extdir?(curr_srcdir()) 1259 | end 1260 | 1261 | alias setup_dir_data noop 1262 | alias setup_dir_conf noop 1263 | alias setup_dir_man noop 1264 | 1265 | def update_shebang_line(path) 1266 | return if no_harm? 1267 | return if config('shebang') == 'never' 1268 | old = Shebang.load(path) 1269 | if old 1270 | $stderr.puts "warning: #{path}: Shebang line includes too many args. It is not portable and your program may not work." if old.args.size > 1 1271 | new = new_shebang(old) 1272 | return if new.to_s == old.to_s 1273 | else 1274 | return unless config('shebang') == 'all' 1275 | new = Shebang.new(config('rubypath')) 1276 | end 1277 | $stderr.puts "updating shebang: #{File.basename(path)}" if verbose? 1278 | open_atomic_writer(path) {|output| 1279 | File.open(path, 'rb') {|f| 1280 | f.gets if old # discard 1281 | output.puts new.to_s 1282 | output.print f.read 1283 | } 1284 | } 1285 | end 1286 | 1287 | def new_shebang(old) 1288 | if /\Aruby/ =~ File.basename(old.cmd) 1289 | Shebang.new(config('rubypath'), old.args) 1290 | elsif File.basename(old.cmd) == 'env' and old.args.first == 'ruby' 1291 | Shebang.new(config('rubypath'), old.args[1..-1]) 1292 | else 1293 | return old unless config('shebang') == 'all' 1294 | Shebang.new(config('rubypath')) 1295 | end 1296 | end 1297 | 1298 | def open_atomic_writer(path, &block) 1299 | tmpfile = File.basename(path) + '.tmp' 1300 | begin 1301 | File.open(tmpfile, 'wb', &block) 1302 | File.rename tmpfile, File.basename(path) 1303 | ensure 1304 | File.unlink tmpfile if File.exist?(tmpfile) 1305 | end 1306 | end 1307 | 1308 | class Shebang 1309 | def Shebang.load(path) 1310 | line = nil 1311 | File.open(path) {|f| 1312 | line = f.gets 1313 | } 1314 | return nil unless /\A#!/ =~ line 1315 | parse(line) 1316 | end 1317 | 1318 | def Shebang.parse(line) 1319 | cmd, *args = *line.strip.sub(/\A\#!/, '').split(' ') 1320 | new(cmd, args) 1321 | end 1322 | 1323 | def initialize(cmd, args = []) 1324 | @cmd = cmd 1325 | @args = args 1326 | end 1327 | 1328 | attr_reader :cmd 1329 | attr_reader :args 1330 | 1331 | def to_s 1332 | "#! #{@cmd}" + (@args.empty? ? '' : " #{@args.join(' ')}") 1333 | end 1334 | end 1335 | 1336 | # 1337 | # TASK install 1338 | # 1339 | 1340 | def exec_install 1341 | rm_f 'InstalledFiles' 1342 | exec_task_traverse 'install' 1343 | end 1344 | 1345 | def install_dir_bin(rel) 1346 | install_files targetfiles(), "#{config('bindir')}/#{rel}", 0755 1347 | end 1348 | 1349 | def install_dir_lib(rel) 1350 | install_files libfiles(), "#{config('rbdir')}/#{rel}", 0644 1351 | end 1352 | 1353 | def install_dir_ext(rel) 1354 | return unless extdir?(curr_srcdir()) 1355 | install_files rubyextentions('.'), 1356 | "#{config('sodir')}/#{File.dirname(rel)}", 1357 | 0555 1358 | end 1359 | 1360 | def install_dir_data(rel) 1361 | install_files targetfiles(), "#{config('datadir')}/#{rel}", 0644 1362 | end 1363 | 1364 | def install_dir_conf(rel) 1365 | # FIXME: should not remove current config files 1366 | # (rename previous file to .old/.org) 1367 | install_files targetfiles(), "#{config('sysconfdir')}/#{rel}", 0644 1368 | end 1369 | 1370 | def install_dir_man(rel) 1371 | install_files targetfiles(), "#{config('mandir')}/#{rel}", 0644 1372 | end 1373 | 1374 | def install_files(list, dest, mode) 1375 | mkdir_p dest, @config.install_prefix 1376 | list.each do |fname| 1377 | install fname, dest, mode, @config.install_prefix 1378 | end 1379 | end 1380 | 1381 | def libfiles 1382 | glob_reject(%w(*.y *.output), targetfiles()) 1383 | end 1384 | 1385 | def rubyextentions(dir) 1386 | ents = glob_select("*.#{@config.dllext}", targetfiles()) 1387 | if ents.empty? 1388 | setup_rb_error "no ruby extention exists: 'ruby #{$0} setup' first" 1389 | end 1390 | ents 1391 | end 1392 | 1393 | def targetfiles 1394 | mapdir(existfiles() - hookfiles()) 1395 | end 1396 | 1397 | def mapdir(ents) 1398 | ents.map {|ent| 1399 | if File.exist?(ent) 1400 | then ent # objdir 1401 | else "#{curr_srcdir()}/#{ent}" # srcdir 1402 | end 1403 | } 1404 | end 1405 | 1406 | # picked up many entries from cvs-1.11.1/src/ignore.c 1407 | JUNK_FILES = %w( 1408 | core RCSLOG tags TAGS .make.state 1409 | .nse_depinfo #* .#* cvslog.* ,* .del-* *.olb 1410 | *~ *.old *.bak *.BAK *.orig *.rej _$* *$ 1411 | 1412 | *.org *.in .* 1413 | ) 1414 | 1415 | def existfiles 1416 | glob_reject(JUNK_FILES, (files_of(curr_srcdir()) | files_of('.'))) 1417 | end 1418 | 1419 | def hookfiles 1420 | %w( pre-%s post-%s pre-%s.rb post-%s.rb ).map {|fmt| 1421 | %w( config setup install clean ).map {|t| sprintf(fmt, t) } 1422 | }.flatten 1423 | end 1424 | 1425 | def glob_select(pat, ents) 1426 | re = globs2re([pat]) 1427 | ents.select {|ent| re =~ ent } 1428 | end 1429 | 1430 | def glob_reject(pats, ents) 1431 | re = globs2re(pats) 1432 | ents.reject {|ent| re =~ ent } 1433 | end 1434 | 1435 | GLOB2REGEX = { 1436 | '.' => '\.', 1437 | '$' => '\$', 1438 | '#' => '\#', 1439 | '*' => '.*' 1440 | } 1441 | 1442 | def globs2re(pats) 1443 | /\A(?:#{ 1444 | pats.map {|pat| pat.gsub(/[\.\$\#\*]/) {|ch| GLOB2REGEX[ch] } }.join('|') 1445 | })\z/ 1446 | end 1447 | 1448 | # 1449 | # TASK test 1450 | # 1451 | 1452 | TESTDIR = 'test' 1453 | 1454 | def exec_test 1455 | unless File.directory?('test') 1456 | $stderr.puts 'no test in this package' if verbose? 1457 | return 1458 | end 1459 | $stderr.puts 'Running tests...' if verbose? 1460 | begin 1461 | require 'test/unit' 1462 | rescue LoadError 1463 | setup_rb_error 'test/unit cannot loaded. You need Ruby 1.8 or later to invoke this task.' 1464 | end 1465 | runner = Test::Unit::AutoRunner.new(true) 1466 | runner.to_run << TESTDIR 1467 | runner.run 1468 | end 1469 | 1470 | # 1471 | # TASK clean 1472 | # 1473 | 1474 | def exec_clean 1475 | exec_task_traverse 'clean' 1476 | rm_f @config.savefile 1477 | rm_f 'InstalledFiles' 1478 | end 1479 | 1480 | alias clean_dir_bin noop 1481 | alias clean_dir_lib noop 1482 | alias clean_dir_data noop 1483 | alias clean_dir_conf noop 1484 | alias clean_dir_man noop 1485 | 1486 | def clean_dir_ext(rel) 1487 | return unless extdir?(curr_srcdir()) 1488 | make 'clean' if File.file?('Makefile') 1489 | end 1490 | 1491 | # 1492 | # TASK distclean 1493 | # 1494 | 1495 | def exec_distclean 1496 | exec_task_traverse 'distclean' 1497 | rm_f @config.savefile 1498 | rm_f 'InstalledFiles' 1499 | end 1500 | 1501 | alias distclean_dir_bin noop 1502 | alias distclean_dir_lib noop 1503 | 1504 | def distclean_dir_ext(rel) 1505 | return unless extdir?(curr_srcdir()) 1506 | make 'distclean' if File.file?('Makefile') 1507 | end 1508 | 1509 | alias distclean_dir_data noop 1510 | alias distclean_dir_conf noop 1511 | alias distclean_dir_man noop 1512 | 1513 | # 1514 | # Traversing 1515 | # 1516 | 1517 | def exec_task_traverse(task) 1518 | run_hook "pre-#{task}" 1519 | FILETYPES.each do |type| 1520 | if type == 'ext' and config('without-ext') == 'yes' 1521 | $stderr.puts 'skipping ext/* by user option' if verbose? 1522 | next 1523 | end 1524 | traverse task, type, "#{task}_dir_#{type}" 1525 | end 1526 | run_hook "post-#{task}" 1527 | end 1528 | 1529 | def traverse(task, rel, mid) 1530 | dive_into(rel) { 1531 | run_hook "pre-#{task}" 1532 | __send__ mid, rel.sub(%r[\A.*?(?:/|\z)], '') 1533 | directories_of(curr_srcdir()).each do |d| 1534 | traverse task, "#{rel}/#{d}", mid 1535 | end 1536 | run_hook "post-#{task}" 1537 | } 1538 | end 1539 | 1540 | def dive_into(rel) 1541 | return unless File.dir?("#{@srcdir}/#{rel}") 1542 | 1543 | dir = File.basename(rel) 1544 | Dir.mkdir dir unless File.dir?(dir) 1545 | prevdir = Dir.pwd 1546 | Dir.chdir dir 1547 | $stderr.puts '---> ' + rel if verbose? 1548 | @currdir = rel 1549 | yield 1550 | Dir.chdir prevdir 1551 | $stderr.puts '<--- ' + rel if verbose? 1552 | @currdir = File.dirname(rel) 1553 | end 1554 | 1555 | def run_hook(id) 1556 | path = [ "#{curr_srcdir()}/#{id}", 1557 | "#{curr_srcdir()}/#{id}.rb" ].detect {|cand| File.file?(cand) } 1558 | return unless path 1559 | begin 1560 | instance_eval File.read(path), path, 1 1561 | rescue 1562 | raise if $DEBUG 1563 | setup_rb_error "hook #{path} failed:\n" + $!.message 1564 | end 1565 | end 1566 | 1567 | end # class Installer 1568 | 1569 | 1570 | class SetupError < StandardError; end 1571 | 1572 | def setup_rb_error(msg) 1573 | raise SetupError, msg 1574 | end 1575 | 1576 | if $0 == __FILE__ 1577 | begin 1578 | ToplevelInstaller.invoke 1579 | rescue SetupError 1580 | raise if $DEBUG 1581 | $stderr.puts $!.message 1582 | $stderr.puts "Try 'ruby #{$0} --help' for detailed usage." 1583 | exit 1 1584 | end 1585 | end 1586 | --------------------------------------------------------------------------------