ripta-color-tools 1.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
data/lib/color/css.rb ADDED
@@ -0,0 +1,27 @@
1
+ #--
2
+ # Colour management with Ruby.
3
+ #
4
+ # Copyright 2005 Austin Ziegler
5
+ # http://rubyforge.org/ruby-pdf/
6
+ #
7
+ # Licensed under a MIT-style licence.
8
+ #
9
+ # $Id$
10
+ #++
11
+
12
+ # This namespace contains some CSS colour names.
13
+ module Color::CSS
14
+
15
+ # Returns the RGB colour for name or +nil+ if the name is not valid.
16
+ def self.[](name)
17
+ @colors[name.to_s.downcase.to_sym]
18
+ end
19
+
20
+ @colors = {}
21
+ Color::RGB.constants.each do |const|
22
+ next if const == "PDF_FORMAT_STR"
23
+ next if const == "Metallic"
24
+ @colors[const.downcase.to_sym] ||= Color::RGB.const_get(const)
25
+ end
26
+
27
+ end
@@ -0,0 +1,135 @@
1
+ #--
2
+ # Colour management with Ruby.
3
+ #
4
+ # Copyright 2005 Austin Ziegler
5
+ # http://rubyforge.org/ruby-pdf/
6
+ #
7
+ # Licensed under a MIT-style licence.
8
+ #
9
+ # $Id$
10
+ #++
11
+
12
+ # A colour object representing shades of grey. Used primarily in PDF
13
+ # document creation.
14
+ class Color::GrayScale
15
+ # The format of a DeviceGrey colour for PDF. In color-tools 2.0 this
16
+ # will be removed from this package and added back as a modification by
17
+ # the PDF::Writer package.
18
+ PDF_FORMAT_STR = "%.3f %s"
19
+
20
+ # Creates a greyscale colour object from fractional values 0..1.
21
+ #
22
+ # Color::GreyScale.from_fraction(0.5)
23
+ def self.from_fraction(g = 0)
24
+ color = Color::GrayScale.new
25
+ color.g = g
26
+ color
27
+ end
28
+
29
+ # Creates a greyscale colour object from percentages 0..100.
30
+ #
31
+ # Color::GrayScale.new(50)
32
+ def initialize(g = 0)
33
+ @g = g / 100.0
34
+ end
35
+
36
+ # Compares the other colour to this one. The other colour will be
37
+ # converted to GreyScale before comparison, so the comparison between a
38
+ # GreyScale colour and a non-GreyScale colour will be approximate and
39
+ # based on the other colour's #to_greyscale conversion. If there is no
40
+ # #to_greyscale conversion, this will raise an exception. This will
41
+ # report that two GreyScale values are equivalent if they are within
42
+ # 1e-4 (0.0001) of each other.
43
+ def ==(other)
44
+ other = other.to_grayscale
45
+ other.kind_of?(Color::GrayScale) and
46
+ ((@g - other.g).abs <= 1e-4)
47
+ end
48
+
49
+ # Present the colour as a DeviceGrey fill colour string for PDF. This
50
+ # will be removed from the default package in color-tools 2.0.
51
+ def pdf_fill
52
+ PDF_FORMAT_STR % [ @g, "g" ]
53
+ end
54
+
55
+ # Present the colour as a DeviceGrey stroke colour string for PDF. This
56
+ # will be removed from the default package in color-tools 2.0.
57
+ def pdf_stroke
58
+ PDF_FORMAT_STR % [ @g, "G" ]
59
+ end
60
+
61
+ def to_255
62
+ [(@g * 255).round, 255].min
63
+ end
64
+ private :to_255
65
+
66
+ # Present the colour as an HTML/CSS colour string.
67
+ def html
68
+ gs = "%02x" % to_255
69
+ "##{gs * 3}"
70
+ end
71
+
72
+ # Convert the greyscale colour to CMYK.
73
+ def to_cmyk
74
+ k = 1.0 - @g.to_f
75
+ Color::CMYK.from_fraction(0, 0, 0, k)
76
+ end
77
+
78
+ # Convert the greyscale colour to RGB.
79
+ def to_rgb(ignored = true)
80
+ g = to_255
81
+ Color::RGB.new(g, g, g)
82
+ end
83
+
84
+ def to_grayscale
85
+ self
86
+ end
87
+ alias to_greyscale to_grayscale
88
+
89
+ # Lightens the greyscale colour by the stated percent.
90
+ def lighten_by(percent)
91
+ g = [@g + (@g * (percent / 100.0)), 1.0].min
92
+ Color::GrayScale.from_fraction(g)
93
+ end
94
+
95
+ # Darken the RGB hue by the stated percent.
96
+ def darken_by(percent)
97
+ g = [@g - (@g * (percent / 100.0)), 0.0].max
98
+ Color::GrayScale.from_fraction(g)
99
+ end
100
+
101
+ # Returns the YIQ (NTSC) colour encoding of the greyscale value. This
102
+ # is an approximation, as the values for I and Q are calculated by
103
+ # treating the greyscale value as an RGB value. The Y (intensity or
104
+ # brightness) value is the same as the greyscale value.
105
+ def to_yiq
106
+ y = @g
107
+ i = (@g * 0.596) + (@g * -0.275) + (@g * -0.321)
108
+ q = (@g * 0.212) + (@g * -0.523) + (@g * 0.311)
109
+ Color::YIQ.from_fraction(y, i, q)
110
+ end
111
+
112
+ # Returns the HSL colour encoding of the greyscale value.
113
+ def to_hsl
114
+ Color::HSL.from_fraction(0, 0, @g)
115
+ end
116
+
117
+ # Returns the brightness value for this greyscale value; this is the
118
+ # greyscale value.
119
+ def brightness
120
+ @g
121
+ end
122
+
123
+ attr_reader :g
124
+
125
+ def g=(gg) #:nodoc:
126
+ gg = 1.0 if gg > 1
127
+ gg = 0.0 if gg < 0
128
+ @g = gg
129
+ end
130
+ end
131
+
132
+ module Color
133
+ # A synonym for Color::GrayScale.
134
+ GreyScale = GrayScale
135
+ end
data/lib/color/hsl.rb ADDED
@@ -0,0 +1,134 @@
1
+ #--
2
+ # Colour management with Ruby.
3
+ #
4
+ # Copyright 2005 Austin Ziegler
5
+ # http://rubyforge.org/ruby-pdf/
6
+ #
7
+ # Licensed under a MIT-style licence.
8
+ #
9
+ # $Id$
10
+ #++
11
+
12
+ # An HSL colour object. Internally, the hue (#h), saturation (#s), and
13
+ # luminosity (#l) values are dealt with as fractional values in the range
14
+ # 0..1.
15
+ class Color::HSL
16
+ class << self
17
+ # Creates an HSL colour object from fractional values 0..1.
18
+ def from_fraction(h = 0.0, s = 0.0, l = 0.0)
19
+ colour = Color::HSL.new
20
+ colour.h = h
21
+ colour.s = s
22
+ colour.l = l
23
+ colour
24
+ end
25
+ end
26
+
27
+ # Compares the other colour to this one. The other colour will be
28
+ # converted to HSL before comparison, so the comparison between a HSL
29
+ # colour and a non-HSL colour will be approximate and based on the other
30
+ # colour's #to_hsl conversion. If there is no #to_hsl conversion, this
31
+ # will raise an exception. This will report that two HSL values are
32
+ # equivalent if all component values are within 1e-4 (0.0001) of each
33
+ # other.
34
+ def ==(other)
35
+ other = other.to_hsl
36
+ other.kind_of?(Color::HSL) and
37
+ ((@h - other.h).abs <= 1e-4) and
38
+ ((@s - other.s).abs <= 1e-4) and
39
+ ((@l - other.l).abs <= 1e-4)
40
+ end
41
+
42
+ # Creates an HSL colour object from the standard values of degrees and
43
+ # percentages (e.g., 145�, 30%, 50%).
44
+ def initialize(h = 0, s = 0, l = 0)
45
+ @h = h / 360.0
46
+ @s = s / 100.0
47
+ @l = l / 100.0
48
+ end
49
+
50
+ # Present the colour as an HTML/CSS colour string.
51
+ def html
52
+ to_rgb.html
53
+ end
54
+
55
+ # Converting to HSL as adapted from Foley and Van-Dam from
56
+ # http://www.bobpowell.net/RGBHSB.htm.
57
+ def to_rgb(ignored = nil)
58
+ # If luminosity is zero, the colour is always black.
59
+ return Color::RGB.new if @l == 0
60
+ # If luminosity is one, the colour is always white.
61
+ return Color::RGB.new(0xff, 0xff, 0xff) if @l == 1
62
+ # If saturation is zero, the colour is always a greyscale colour.
63
+ return Color::RGB.new(@l, @l, @l) if @s <= 1e-5
64
+
65
+ if (@l - 0.5) < 1e-5
66
+ tmp2 = @l * (1.0 + @s.to_f)
67
+ else
68
+ tmp2 = @l + @s - (@l * @s.to_f)
69
+ end
70
+ tmp1 = 2.0 * @l - tmp2
71
+
72
+ t3 = [ @h + 1.0 / 3.0, @h, @h - 1.0 / 3.0 ]
73
+ t3 = t3.map { |tmp3|
74
+ tmp3 += 1.0 if tmp3 < 1e-5
75
+ tmp3 -= 1.0 if (tmp3 - 1.0) > 1e-5
76
+ tmp3
77
+ }
78
+
79
+ rgb = t3.map do |tmp3|
80
+ if ((6.0 * tmp3) - 1.0) < 1e-5
81
+ tmp1 + ((tmp2 - tmp1) * tmp3 * 6.0)
82
+ elsif ((2.0 * tmp3) - 1.0) < 1e-5
83
+ tmp2
84
+ elsif ((3.0 * tmp3) - 2.0) < 1e-5
85
+ tmp1 + (tmp2 - tmp1) * ((2 / 3.0) - tmp3) * 6.0
86
+ else
87
+ tmp1
88
+ end
89
+ end
90
+
91
+ Color::RGB.from_fraction(*rgb)
92
+ end
93
+
94
+ # Converts to RGB then YIQ.
95
+ def to_yiq
96
+ to_rgb.to_yiq
97
+ end
98
+
99
+ # Converts to RGB then CMYK.
100
+ def to_cmyk
101
+ to_rgb.to_cmyk
102
+ end
103
+
104
+ # Returns the luminosity (#l) of the colour.
105
+ def brightness
106
+ @l
107
+ end
108
+
109
+ def to_greyscale
110
+ Color::GrayScale.from_fraction(@l)
111
+ end
112
+
113
+ alias to_grayscale to_greyscale
114
+
115
+ attr_reader :h, :s, :l
116
+
117
+ def h=(hh) #:nodoc:
118
+ hh = 1.0 if hh > 1
119
+ hh = 0.0 if hh < 0
120
+ @h = hh
121
+ end
122
+
123
+ def s=(ss) #:nodoc:
124
+ ss = 1.0 if ss > 1
125
+ ss = 0.0 if ss < 0
126
+ @s = ss
127
+ end
128
+
129
+ def l=(ll) #:nodoc:
130
+ ll = 1.0 if ll > 1
131
+ ll = 0.0 if ll < 0
132
+ @l = ll
133
+ end
134
+ end
@@ -0,0 +1,18 @@
1
+ #--
2
+ # Colour management with Ruby.
3
+ #
4
+ # Copyright 2005 Austin Ziegler
5
+ # http://rubyforge.org/ruby-pdf/
6
+ #
7
+ # Licensed under a MIT-style licence.
8
+ #
9
+ # $Id$
10
+ #++
11
+ module Color
12
+ module Palette
13
+
14
+ autoload :Gimp, "color/palette/gimp"
15
+ autoload :MonoContrast, "color/palette/monocontrast"
16
+
17
+ end
18
+ end
@@ -0,0 +1,105 @@
1
+ #--
2
+ # Colour management with Ruby.
3
+ #
4
+ # Copyright 2005 Austin Ziegler
5
+ # http://rubyforge.org/ruby-pdf/
6
+ #
7
+ # Licensed under a MIT-style licence.
8
+ #
9
+ # $Id$
10
+ #++
11
+
12
+ # A class that can read a GIMP (GNU Image Manipulation Program) palette
13
+ # file and provide a Hash-like interface to the contents. GIMP colour
14
+ # palettes are RGB values only.
15
+ #
16
+ # Because two or more entries in a GIMP palette may have the same name,
17
+ # all named entries are returned as an array.
18
+ #
19
+ # pal = Color::Palette::Gimp.from_file(my_gimp_palette)
20
+ # pal[0] => Color::RGB<...>
21
+ # pal["white"] => [ Color::RGB<...> ]
22
+ # pal["unknown"] => [ Color::RGB<...>, Color::RGB<...>, ... ]
23
+ #
24
+ # GIMP Palettes are always indexable by insertion order (an integer key).
25
+ class Color::Palette::Gimp
26
+ include Enumerable
27
+
28
+ class << self
29
+ # Create a GIMP palette object from the named file.
30
+ def from_file(filename)
31
+ File.open(filename, "rb") { |io| Color::Palette::Gimp.from_io(io) }
32
+ end
33
+
34
+ # Create a GIMP palette object from the provided IO.
35
+ def from_io(io)
36
+ Color::Palette::Gimp.new(io.read)
37
+ end
38
+ end
39
+
40
+ # Create a new GIMP palette.
41
+ def initialize(palette)
42
+ @colors = []
43
+ @names = {}
44
+ @valid = false
45
+ @name = "(unnamed)"
46
+
47
+ index = 0
48
+
49
+ palette.split($/).each do |line|
50
+ line.chomp!
51
+ line.gsub!(/\s*#.*\Z/, '')
52
+
53
+ next if line.empty?
54
+
55
+ if line =~ /\AGIMP Palette\Z/
56
+ @valid = true
57
+ next
58
+ end
59
+
60
+ info = /(\w+):\s(.*$)/.match(line)
61
+ if info
62
+ @name = info.captures[1] if info.captures[0] =~ /name/i
63
+ next
64
+ end
65
+
66
+ line.gsub!(/^\s+/, '')
67
+ data = line.split(/\s+/, 4)
68
+ name = data.pop.strip
69
+ data.map! { |el| el.to_i }
70
+
71
+ color = Color::RGB.new(*data)
72
+
73
+ @colors[index] = color
74
+ @names[name] ||= []
75
+ @names[name] << color
76
+
77
+ index += 1
78
+ end
79
+ end
80
+
81
+ def [](key)
82
+ if key.kind_of?(Numeric)
83
+ @colors[key]
84
+ else
85
+ @names[key]
86
+ end
87
+ end
88
+
89
+ # Loops through each colour.
90
+ def each
91
+ @colors.each { |el| yield el }
92
+ end
93
+
94
+ # Loops through each named colour set.
95
+ def each_name #:yields color_name, color_set:#
96
+ @names.each { |color_name, color_set| yield color_name, color_set }
97
+ end
98
+
99
+ # Returns true if this is believed to be a valid GIMP palette.
100
+ def valid?
101
+ @valid
102
+ end
103
+
104
+ attr_reader :name
105
+ end
@@ -0,0 +1,178 @@
1
+ #--
2
+ # Colour management with Ruby.
3
+ #
4
+ # Copyright 2005 Austin Ziegler
5
+ # http://rubyforge.org/ruby-pdf/
6
+ #
7
+ # Licensed under a MIT-style licence.
8
+ #
9
+ # $Id$
10
+ #++
11
+
12
+ # Generates a monochromatic constrasting colour palette for background and
13
+ # foreground. What does this mean?
14
+ #
15
+ # Monochromatic: A single colour is used to generate the base palette, and
16
+ # this colour is lightened five times and darkened five times to provide
17
+ # eleven distinct colours.
18
+ #
19
+ # Contrasting: The foreground is also generated as a monochromatic colour
20
+ # palettte; however, all generated colours are tested to see that they are
21
+ # appropriately contrasting to ensure maximum readability of the
22
+ # foreground against the background.
23
+ class Color::Palette::MonoContrast
24
+ # Hash of CSS background colour values.
25
+ #
26
+ # This is always 11 values:
27
+ #
28
+ # 0:: The starting colour.
29
+ # +1..+5:: Lighter colours.
30
+ # -1..-5:: Darker colours.
31
+ attr_reader :background
32
+ # Hash of CSS foreground colour values.
33
+ #
34
+ # This is always 11 values:
35
+ #
36
+ # 0:: The starting colour.
37
+ # +1..+5:: Lighter colours.
38
+ # -1..-5:: Darker colours.
39
+ attr_reader :foreground
40
+
41
+ DEFAULT_MINIMUM_BRIGHTNESS_DIFF = (125.0 / 255.0)
42
+
43
+ attr_reader :minimum_brightness_diff
44
+
45
+ # The minimum brightness difference between the background and the
46
+ # foreground, and must be between 0..1. Setting this value will
47
+ # regenerate the palette based on the base colours. The default value
48
+ # for this is 125 / 255.0. If this value is set to +nil+, it will be
49
+ # restored to the default.
50
+ def minimum_brightness_diff=(bd) #:nodoc:
51
+ if bd.nil?
52
+ @minimum_brightness_diff = DEFAULT_MINIMUM_BRIGHTNESS_DIFF
53
+ elsif bd > 1.0
54
+ @minimum_brightness_diff = 1.0
55
+ elsif bd < 0.0
56
+ @minimum_brightness_diff = 0.0
57
+ else
58
+ @minimum_brightness_diff = bd
59
+ end
60
+
61
+ regenerate(@background[0], @foreground[0])
62
+ end
63
+
64
+ DEFAULT_MINIMUM_COLOR_DIFF = (500.0 / 255.0)
65
+
66
+ attr_reader :minimum_color_diff
67
+
68
+ # The minimum colour difference between the background and the
69
+ # foreground, and must be between 0..3. Setting this value will
70
+ # regenerate the palette based on the base colours. The default value
71
+ # for this is 500 / 255.0.
72
+ def minimum_color_diff=(cd) #:noco:
73
+ if cd.nil?
74
+ @minimum_color_diff = DEFAULT_MINIMUM_COLOR_DIFF
75
+ elsif cd > 3.0
76
+ @minimum_color_diff = 3.0
77
+ elsif cd < 0.0
78
+ @minimum_color_diff = 0.0
79
+ else
80
+ @minimum_color_diff = cd
81
+ end
82
+ regenerate(@background[0], @foreground[0])
83
+ end
84
+
85
+ # Generate the initial palette.
86
+ def initialize(background, foreground = nil)
87
+ @minimum_brightness_diff = DEFAULT_MINIMUM_BRIGHTNESS_DIFF
88
+ @minimum_color_diff = DEFAULT_MINIMUM_COLOR_DIFF
89
+
90
+ regenerate(background, foreground)
91
+ end
92
+
93
+ # Generate the colour palettes.
94
+ def regenerate(background, foreground = nil)
95
+ foreground ||= background
96
+ background = background.to_rgb
97
+ foreground = foreground.to_rgb
98
+
99
+ @background = {}
100
+ @foreground = {}
101
+
102
+ @background[-5] = background.darken_by(10)
103
+ @background[-4] = background.darken_by(25)
104
+ @background[-3] = background.darken_by(50)
105
+ @background[-2] = background.darken_by(75)
106
+ @background[-1] = background.darken_by(85)
107
+ @background[ 0] = background
108
+ @background[+1] = background.lighten_by(85)
109
+ @background[+2] = background.lighten_by(75)
110
+ @background[+3] = background.lighten_by(50)
111
+ @background[+4] = background.lighten_by(25)
112
+ @background[+5] = background.lighten_by(10)
113
+
114
+ @foreground[-5] = calculate_foreground(@background[-5], foreground)
115
+ @foreground[-4] = calculate_foreground(@background[-4], foreground)
116
+ @foreground[-3] = calculate_foreground(@background[-3], foreground)
117
+ @foreground[-2] = calculate_foreground(@background[-2], foreground)
118
+ @foreground[-1] = calculate_foreground(@background[-1], foreground)
119
+ @foreground[ 0] = calculate_foreground(@background[ 0], foreground)
120
+ @foreground[+1] = calculate_foreground(@background[+1], foreground)
121
+ @foreground[+2] = calculate_foreground(@background[+2], foreground)
122
+ @foreground[+3] = calculate_foreground(@background[+3], foreground)
123
+ @foreground[+4] = calculate_foreground(@background[+4], foreground)
124
+ @foreground[+5] = calculate_foreground(@background[+5], foreground)
125
+ end
126
+
127
+ # Given a background colour and a foreground colour, modifies the
128
+ # foreground colour so that it will have enough contrast to be seen
129
+ # against the background colour.
130
+ #
131
+ # Uses #mininum_brightness_diff and #minimum_color_diff.
132
+ def calculate_foreground(background, foreground)
133
+ nfg = nil
134
+ # Loop through brighter and darker versions of the foreground color.
135
+ # The numbers here represent the amount of foreground color to mix
136
+ # with black and white.
137
+ [100, 75, 50, 25, 0].each do |percent|
138
+ dfg = foreground.darken_by(percent)
139
+ lfg = foreground.lighten_by(percent)
140
+
141
+ dbd = brightness_diff(background, dfg)
142
+ lbd = brightness_diff(background, lfg)
143
+
144
+ if lbd > dbd
145
+ nfg = lfg
146
+ nbd = lbd
147
+ else
148
+ nfg = dfg
149
+ nbd = dbd
150
+ end
151
+
152
+ ncd = color_diff(background, nfg)
153
+
154
+ break if nbd >= @minimum_brightness_diff and ncd >= @minimum_color_diff
155
+ end
156
+ nfg
157
+ end
158
+
159
+ # Returns the absolute difference between the brightness levels of two
160
+ # colours. This will be a decimal value between 0 and 1. W3C
161
+ # accessibility guidelines for colour
162
+ # contrast[http://www.w3.org/TR/AERT#color-contrast] suggest that this
163
+ # value be at least approximately 0.49 (125 / 255.0) for proper contrast.
164
+ def brightness_diff(c1, c2)
165
+ (c1.brightness - c2.brightness).abs
166
+ end
167
+
168
+ # Returns the contrast between to colours, a decimal value between 0 and
169
+ # 3. W3C accessibility guidelines for colour
170
+ # contrast[http://www.w3.org/TR/AERT#color-contrast] suggest that this
171
+ # value be at least approximately 1.96 (500 / 255.0) for proper contrast.
172
+ def color_diff(c1, c2)
173
+ r = (c1.r - c2.r).abs
174
+ g = (c1.g - c2.g).abs
175
+ b = (c1.b - c2.b).abs
176
+ r + g + b
177
+ end
178
+ end