ripta-color-tools 1.4.0

Sign up to get free protection for your applications and to get access to all the features.
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