hotcocoa 0.5
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/History.txt +4 -0
- data/bin/hotcocoa +31 -0
- data/lib/hotcocoa/application_builder.rb +320 -0
- data/lib/hotcocoa/attributed_string.rb +143 -0
- data/lib/hotcocoa/behaviors.rb +7 -0
- data/lib/hotcocoa/data_sources/combo_box_data_source.rb +44 -0
- data/lib/hotcocoa/data_sources/table_data_source.rb +18 -0
- data/lib/hotcocoa/delegate_builder.rb +85 -0
- data/lib/hotcocoa/graphics/canvas.rb +836 -0
- data/lib/hotcocoa/graphics/color.rb +781 -0
- data/lib/hotcocoa/graphics/elements/particle.rb +75 -0
- data/lib/hotcocoa/graphics/elements/rope.rb +99 -0
- data/lib/hotcocoa/graphics/elements/sandpainter.rb +71 -0
- data/lib/hotcocoa/graphics/gradient.rb +63 -0
- data/lib/hotcocoa/graphics/image.rb +488 -0
- data/lib/hotcocoa/graphics/path.rb +325 -0
- data/lib/hotcocoa/graphics/pdf.rb +71 -0
- data/lib/hotcocoa/graphics.rb +161 -0
- data/lib/hotcocoa/kernel_ext.rb +14 -0
- data/lib/hotcocoa/kvo_accessors.rb +48 -0
- data/lib/hotcocoa/layout_view.rb +448 -0
- data/lib/hotcocoa/mapper.rb +227 -0
- data/lib/hotcocoa/mapping_methods.rb +40 -0
- data/lib/hotcocoa/mappings/alert.rb +25 -0
- data/lib/hotcocoa/mappings/application.rb +112 -0
- data/lib/hotcocoa/mappings/array_controller.rb +87 -0
- data/lib/hotcocoa/mappings/box.rb +39 -0
- data/lib/hotcocoa/mappings/button.rb +92 -0
- data/lib/hotcocoa/mappings/collection_view.rb +44 -0
- data/lib/hotcocoa/mappings/color.rb +28 -0
- data/lib/hotcocoa/mappings/column.rb +21 -0
- data/lib/hotcocoa/mappings/combo_box.rb +24 -0
- data/lib/hotcocoa/mappings/control.rb +33 -0
- data/lib/hotcocoa/mappings/font.rb +44 -0
- data/lib/hotcocoa/mappings/gradient.rb +15 -0
- data/lib/hotcocoa/mappings/image.rb +15 -0
- data/lib/hotcocoa/mappings/image_view.rb +43 -0
- data/lib/hotcocoa/mappings/label.rb +25 -0
- data/lib/hotcocoa/mappings/layout_view.rb +9 -0
- data/lib/hotcocoa/mappings/menu.rb +71 -0
- data/lib/hotcocoa/mappings/menu_item.rb +47 -0
- data/lib/hotcocoa/mappings/movie.rb +13 -0
- data/lib/hotcocoa/mappings/movie_view.rb +27 -0
- data/lib/hotcocoa/mappings/notification.rb +17 -0
- data/lib/hotcocoa/mappings/popup.rb +110 -0
- data/lib/hotcocoa/mappings/progress_indicator.rb +68 -0
- data/lib/hotcocoa/mappings/scroll_view.rb +29 -0
- data/lib/hotcocoa/mappings/search_field.rb +9 -0
- data/lib/hotcocoa/mappings/secure_text_field.rb +17 -0
- data/lib/hotcocoa/mappings/segmented_control.rb +97 -0
- data/lib/hotcocoa/mappings/slider.rb +25 -0
- data/lib/hotcocoa/mappings/sort_descriptor.rb +13 -0
- data/lib/hotcocoa/mappings/sound.rb +9 -0
- data/lib/hotcocoa/mappings/speech_synthesizer.rb +25 -0
- data/lib/hotcocoa/mappings/split_view.rb +21 -0
- data/lib/hotcocoa/mappings/status_bar.rb +7 -0
- data/lib/hotcocoa/mappings/status_item.rb +9 -0
- data/lib/hotcocoa/mappings/table_view.rb +110 -0
- data/lib/hotcocoa/mappings/text_field.rb +41 -0
- data/lib/hotcocoa/mappings/text_view.rb +13 -0
- data/lib/hotcocoa/mappings/timer.rb +25 -0
- data/lib/hotcocoa/mappings/toolbar.rb +97 -0
- data/lib/hotcocoa/mappings/toolbar_item.rb +36 -0
- data/lib/hotcocoa/mappings/view.rb +67 -0
- data/lib/hotcocoa/mappings/web_view.rb +22 -0
- data/lib/hotcocoa/mappings/window.rb +118 -0
- data/lib/hotcocoa/mappings/xml_parser.rb +41 -0
- data/lib/hotcocoa/mappings.rb +109 -0
- data/lib/hotcocoa/mvc.rb +175 -0
- data/lib/hotcocoa/notification_listener.rb +62 -0
- data/lib/hotcocoa/object_ext.rb +22 -0
- data/lib/hotcocoa/plist.rb +45 -0
- data/lib/hotcocoa/standard_rake_tasks.rb +17 -0
- data/lib/hotcocoa/template.rb +27 -0
- data/lib/hotcocoa/virtual_file_system.rb +172 -0
- data/lib/hotcocoa.rb +26 -0
- data/template/Rakefile +5 -0
- data/template/config/build.yml +8 -0
- data/template/lib/application.rb +45 -0
- data/template/lib/menu.rb +32 -0
- data/template/resources/HotCocoa.icns +0 -0
- data/test/test_helper.rb +3 -0
- data/test/test_hotcocoa.rb +11 -0
- metadata +137 -0
@@ -0,0 +1,781 @@
|
|
1
|
+
# Ruby Cocoa Graphics is a graphics library providing a simple object-oriented
|
2
|
+
# interface into the power of Mac OS X's Core Graphics and Core Image drawing libraries.
|
3
|
+
# With a few lines of easy-to-read code, you can write scripts to draw simple or complex
|
4
|
+
# shapes, lines, and patterns, process and filter images, create abstract art or visualize
|
5
|
+
# scientific data, and much more.
|
6
|
+
#
|
7
|
+
# Inspiration for this project was derived from Processing and NodeBox. These excellent
|
8
|
+
# graphics programming environments are more full-featured than RCG, but they are implemented
|
9
|
+
# in Java and Python, respectively. RCG was created to offer similar functionality using
|
10
|
+
# the Ruby programming language.
|
11
|
+
#
|
12
|
+
# Author:: James Reynolds (mailto:drtoast@drtoast.com)
|
13
|
+
# Copyright:: Copyright (c) 2008 James Reynolds
|
14
|
+
# License:: Distributes under the same terms as Ruby
|
15
|
+
|
16
|
+
module HotCocoa::Graphics
|
17
|
+
|
18
|
+
# define and manipulate colors in RGBA format
|
19
|
+
class Color
|
20
|
+
|
21
|
+
# License: GPL - includes ports of some code by Tom De Smedt, Frederik De Bleser
|
22
|
+
|
23
|
+
#attr_accessor :r,:g,:b,:a
|
24
|
+
attr_accessor :rgb
|
25
|
+
# def initialize(r=0.0,g=0.0,b=0.0,a=1.0)
|
26
|
+
# @c = CGColorCreate(@colorspace, [r,g,b,a])
|
27
|
+
# end
|
28
|
+
|
29
|
+
# create a new color with the given RGBA values
|
30
|
+
def initialize(r=0.0, g=0.0, b=1.0, a=1.0)
|
31
|
+
@nsColor = NSColor.colorWithDeviceRed r, green:g, blue:b, alpha:a
|
32
|
+
@rgb = @nsColor.colorUsingColorSpaceName NSDeviceRGBColorSpace
|
33
|
+
self
|
34
|
+
end
|
35
|
+
|
36
|
+
COLORNAMES = {
|
37
|
+
"lightpink" => [1.00, 0.71, 0.76],
|
38
|
+
"pink" => [1.00, 0.75, 0.80],
|
39
|
+
"crimson" => [0.86, 0.08, 0.24],
|
40
|
+
"lavenderblush" => [1.00, 0.94, 0.96],
|
41
|
+
"palevioletred" => [0.86, 0.44, 0.58],
|
42
|
+
"hotpink" => [1.00, 0.41, 0.71],
|
43
|
+
"deeppink" => [1.00, 0.08, 0.58],
|
44
|
+
"mediumvioletred" => [0.78, 0.08, 0.52],
|
45
|
+
"orchid" => [0.85, 0.44, 0.84],
|
46
|
+
"thistle" => [0.85, 0.75, 0.85],
|
47
|
+
"plum" => [0.87, 0.63, 0.87],
|
48
|
+
"violet" => [0.93, 0.51, 0.93],
|
49
|
+
"fuchsia" => [1.00, 0.00, 1.00],
|
50
|
+
"darkmagenta" => [0.55, 0.00, 0.55],
|
51
|
+
"purple" => [0.50, 0.00, 0.50],
|
52
|
+
"mediumorchid" => [0.73, 0.33, 0.83],
|
53
|
+
"darkviolet" => [0.58, 0.00, 0.83],
|
54
|
+
"darkorchid" => [0.60, 0.20, 0.80],
|
55
|
+
"indigo" => [0.29, 0.00, 0.51],
|
56
|
+
"blueviolet" => [0.54, 0.17, 0.89],
|
57
|
+
"mediumpurple" => [0.58, 0.44, 0.86],
|
58
|
+
"mediumslateblue" => [0.48, 0.41, 0.93],
|
59
|
+
"slateblue" => [0.42, 0.35, 0.80],
|
60
|
+
"darkslateblue" => [0.28, 0.24, 0.55],
|
61
|
+
"ghostwhite" => [0.97, 0.97, 1.00],
|
62
|
+
"lavender" => [0.90, 0.90, 0.98],
|
63
|
+
"blue" => [0.00, 0.00, 1.00],
|
64
|
+
"mediumblue" => [0.00, 0.00, 0.80],
|
65
|
+
"darkblue" => [0.00, 0.00, 0.55],
|
66
|
+
"navy" => [0.00, 0.00, 0.50],
|
67
|
+
"midnightblue" => [0.10, 0.10, 0.44],
|
68
|
+
"royalblue" => [0.25, 0.41, 0.88],
|
69
|
+
"cornflowerblue" => [0.39, 0.58, 0.93],
|
70
|
+
"lightsteelblue" => [0.69, 0.77, 0.87],
|
71
|
+
"lightslategray" => [0.47, 0.53, 0.60],
|
72
|
+
"slategray" => [0.44, 0.50, 0.56],
|
73
|
+
"dodgerblue" => [0.12, 0.56, 1.00],
|
74
|
+
"aliceblue" => [0.94, 0.97, 1.00],
|
75
|
+
"steelblue" => [0.27, 0.51, 0.71],
|
76
|
+
"lightskyblue" => [0.53, 0.81, 0.98],
|
77
|
+
"skyblue" => [0.53, 0.81, 0.92],
|
78
|
+
"deepskyblue" => [0.00, 0.75, 1.00],
|
79
|
+
"lightblue" => [0.68, 0.85, 0.90],
|
80
|
+
"powderblue" => [0.69, 0.88, 0.90],
|
81
|
+
"cadetblue" => [0.37, 0.62, 0.63],
|
82
|
+
"darkturquoise" => [0.00, 0.81, 0.82],
|
83
|
+
"azure" => [0.94, 1.00, 1.00],
|
84
|
+
"lightcyan" => [0.88, 1.00, 1.00],
|
85
|
+
"paleturquoise" => [0.69, 0.93, 0.93],
|
86
|
+
"aqua" => [0.00, 1.00, 1.00],
|
87
|
+
"darkcyan" => [0.00, 0.55, 0.55],
|
88
|
+
"teal" => [0.00, 0.50, 0.50],
|
89
|
+
"darkslategray" => [0.18, 0.31, 0.31],
|
90
|
+
"mediumturquoise" => [0.28, 0.82, 0.80],
|
91
|
+
"lightseagreen" => [0.13, 0.70, 0.67],
|
92
|
+
"turquoise" => [0.25, 0.88, 0.82],
|
93
|
+
"aquamarine" => [0.50, 1.00, 0.83],
|
94
|
+
"mediumaquamarine" => [0.40, 0.80, 0.67],
|
95
|
+
"mediumspringgreen" => [0.00, 0.98, 0.60],
|
96
|
+
"mintcream" => [0.96, 1.00, 0.98],
|
97
|
+
"springgreen" => [0.00, 1.00, 0.50],
|
98
|
+
"mediumseagreen" => [0.24, 0.70, 0.44],
|
99
|
+
"seagreen" => [0.18, 0.55, 0.34],
|
100
|
+
"honeydew" => [0.94, 1.00, 0.94],
|
101
|
+
"darkseagreen" => [0.56, 0.74, 0.56],
|
102
|
+
"palegreen" => [0.60, 0.98, 0.60],
|
103
|
+
"lightgreen" => [0.56, 0.93, 0.56],
|
104
|
+
"limegreen" => [0.20, 0.80, 0.20],
|
105
|
+
"lime" => [0.00, 1.00, 0.00],
|
106
|
+
"forestgreen" => [0.13, 0.55, 0.13],
|
107
|
+
"green" => [0.00, 0.50, 0.00],
|
108
|
+
"darkgreen" => [0.00, 0.39, 0.00],
|
109
|
+
"lawngreen" => [0.49, 0.99, 0.00],
|
110
|
+
"chartreuse" => [0.50, 1.00, 0.00],
|
111
|
+
"greenyellow" => [0.68, 1.00, 0.18],
|
112
|
+
"darkolivegreen" => [0.33, 0.42, 0.18],
|
113
|
+
"yellowgreen" => [0.60, 0.80, 0.20],
|
114
|
+
"olivedrab" => [0.42, 0.56, 0.14],
|
115
|
+
"ivory" => [1.00, 1.00, 0.94],
|
116
|
+
"beige" => [0.96, 0.96, 0.86],
|
117
|
+
"lightyellow" => [1.00, 1.00, 0.88],
|
118
|
+
"lightgoldenrodyellow" => [0.98, 0.98, 0.82],
|
119
|
+
"yellow" => [1.00, 1.00, 0.00],
|
120
|
+
"olive" => [0.50, 0.50, 0.00],
|
121
|
+
"darkkhaki" => [0.74, 0.72, 0.42],
|
122
|
+
"palegoldenrod" => [0.93, 0.91, 0.67],
|
123
|
+
"lemonchiffon" => [1.00, 0.98, 0.80],
|
124
|
+
"khaki" => [0.94, 0.90, 0.55],
|
125
|
+
"gold" => [1.00, 0.84, 0.00],
|
126
|
+
"cornsilk" => [1.00, 0.97, 0.86],
|
127
|
+
"goldenrod" => [0.85, 0.65, 0.13],
|
128
|
+
"darkgoldenrod" => [0.72, 0.53, 0.04],
|
129
|
+
"floralwhite" => [1.00, 0.98, 0.94],
|
130
|
+
"oldlace" => [0.99, 0.96, 0.90],
|
131
|
+
"wheat" => [0.96, 0.87, 0.07],
|
132
|
+
"orange" => [1.00, 0.65, 0.00],
|
133
|
+
"moccasin" => [1.00, 0.89, 0.71],
|
134
|
+
"papayawhip" => [1.00, 0.94, 0.84],
|
135
|
+
"blanchedalmond" => [1.00, 0.92, 0.80],
|
136
|
+
"navajowhite" => [1.00, 0.87, 0.68],
|
137
|
+
"antiquewhite" => [0.98, 0.92, 0.84],
|
138
|
+
"tan" => [0.82, 0.71, 0.55],
|
139
|
+
"burlywood" => [0.87, 0.72, 0.53],
|
140
|
+
"darkorange" => [1.00, 0.55, 0.00],
|
141
|
+
"bisque" => [1.00, 0.89, 0.77],
|
142
|
+
"linen" => [0.98, 0.94, 0.90],
|
143
|
+
"peru" => [0.80, 0.52, 0.25],
|
144
|
+
"peachpuff" => [1.00, 0.85, 0.73],
|
145
|
+
"sandybrown" => [0.96, 0.64, 0.38],
|
146
|
+
"chocolate" => [0.82, 0.41, 0.12],
|
147
|
+
"saddlebrown" => [0.55, 0.27, 0.07],
|
148
|
+
"seashell" => [1.00, 0.96, 0.93],
|
149
|
+
"sienna" => [0.63, 0.32, 0.18],
|
150
|
+
"lightsalmon" => [1.00, 0.63, 0.48],
|
151
|
+
"coral" => [1.00, 0.50, 0.31],
|
152
|
+
"orangered" => [1.00, 0.27, 0.00],
|
153
|
+
"darksalmon" => [0.91, 0.59, 0.48],
|
154
|
+
"tomato" => [1.00, 0.39, 0.28],
|
155
|
+
"salmon" => [0.98, 0.50, 0.45],
|
156
|
+
"mistyrose" => [1.00, 0.89, 0.88],
|
157
|
+
"lightcoral" => [0.94, 0.50, 0.50],
|
158
|
+
"snow" => [1.00, 0.98, 0.98],
|
159
|
+
"rosybrown" => [0.74, 0.56, 0.56],
|
160
|
+
"indianred" => [0.80, 0.36, 0.36],
|
161
|
+
"red" => [1.00, 0.00, 0.00],
|
162
|
+
"brown" => [0.65, 0.16, 0.16],
|
163
|
+
"firebrick" => [0.70, 0.13, 0.13],
|
164
|
+
"darkred" => [0.55, 0.00, 0.00],
|
165
|
+
"maroon" => [0.50, 0.00, 0.00],
|
166
|
+
"white" => [1.00, 1.00, 1.00],
|
167
|
+
"whitesmoke" => [0.96, 0.96, 0.96],
|
168
|
+
"gainsboro" => [0.86, 0.86, 0.86],
|
169
|
+
"lightgrey" => [0.83, 0.83, 0.83],
|
170
|
+
"silver" => [0.75, 0.75, 0.75],
|
171
|
+
"darkgray" => [0.66, 0.66, 0.66],
|
172
|
+
"gray" => [0.50, 0.50, 0.50],
|
173
|
+
"grey" => [0.50, 0.50, 0.50],
|
174
|
+
"dimgray" => [0.41, 0.41, 0.41],
|
175
|
+
"dimgrey" => [0.41, 0.41, 0.41],
|
176
|
+
"black" => [0.00, 0.00, 0.00],
|
177
|
+
"cyan" => [0.00, 0.68, 0.94],
|
178
|
+
#"transparent" => [0.00, 0.00, 0.00, 0.00],
|
179
|
+
"bark" => [0.25, 0.19, 0.13]
|
180
|
+
}
|
181
|
+
|
182
|
+
RYBWheel = [
|
183
|
+
[ 0, 0], [ 15, 8],
|
184
|
+
[ 30, 17], [ 45, 26],
|
185
|
+
[ 60, 34], [ 75, 41],
|
186
|
+
[ 90, 48], [105, 54],
|
187
|
+
[120, 60], [135, 81],
|
188
|
+
[150, 103], [165, 123],
|
189
|
+
[180, 138], [195, 155],
|
190
|
+
[210, 171], [225, 187],
|
191
|
+
[240, 204], [255, 219],
|
192
|
+
[270, 234], [285, 251],
|
193
|
+
[300, 267], [315, 282],
|
194
|
+
[330, 298], [345, 329],
|
195
|
+
[360, 0 ]
|
196
|
+
]
|
197
|
+
|
198
|
+
COLORNAMES.each_key do |name|
|
199
|
+
(class << self; self; end).define_method(name) do
|
200
|
+
named(name)
|
201
|
+
end
|
202
|
+
end
|
203
|
+
|
204
|
+
# create a color with the specified name
|
205
|
+
def self.named(name)
|
206
|
+
if COLORNAMES[name]
|
207
|
+
r, g, b = COLORNAMES[name]
|
208
|
+
#puts "matched name #{name}"
|
209
|
+
color = Color.new(r, g, b, 1.0)
|
210
|
+
elsif name.match(/^(dark|deep|light|bright)?(.*?)(ish)?$/)
|
211
|
+
#puts "matched #{$1}-#{$2}-#{$3}"
|
212
|
+
value = $1
|
213
|
+
color_name = $2
|
214
|
+
ish = $3
|
215
|
+
analogval = value ? 0 : 0.1
|
216
|
+
r, g, b = COLORNAMES[color_name] || [0.0, 0.0, 0.0]
|
217
|
+
color = Color.new(r, g, b, 1.0)
|
218
|
+
color = c.analog(20, analogval) if ish
|
219
|
+
color.lighten(0.2) if value and value.match(/light|bright/)
|
220
|
+
color.darken(0.2) if value and value.match(/dark|deep/)
|
221
|
+
else
|
222
|
+
color = Color.black
|
223
|
+
end
|
224
|
+
color
|
225
|
+
end
|
226
|
+
|
227
|
+
# return the name of the nearest named color
|
228
|
+
def name
|
229
|
+
nearest, d = ["", 1.0]
|
230
|
+
red = r
|
231
|
+
green = g
|
232
|
+
blue = b
|
233
|
+
for hue in COLORNAMES.keys
|
234
|
+
rdiff = (red - COLORNAMES[hue][0]).abs
|
235
|
+
gdiff = (green - COLORNAMES[hue][1]).abs
|
236
|
+
bdiff = (blue - COLORNAMES[hue][2]).abs
|
237
|
+
totaldiff = rdiff + gdiff + bdiff
|
238
|
+
if (totaldiff < d)
|
239
|
+
nearest, d = [hue, totaldiff]
|
240
|
+
end
|
241
|
+
end
|
242
|
+
nearest
|
243
|
+
end
|
244
|
+
|
245
|
+
# return a copy of this color
|
246
|
+
def copy
|
247
|
+
Color.new(r, g, b, a)
|
248
|
+
end
|
249
|
+
|
250
|
+
# print the color's component values
|
251
|
+
def to_s
|
252
|
+
"color: #{name} (#{r} #{g} #{b} #{a})"
|
253
|
+
end
|
254
|
+
|
255
|
+
# sort the color by brightness in an array
|
256
|
+
def <=> othercolor
|
257
|
+
self.brightness <=> othercolor.brightness || self.hue <=> othercolor.hue
|
258
|
+
end
|
259
|
+
|
260
|
+
# set or retrieve the red component
|
261
|
+
def r(val=nil)
|
262
|
+
if val
|
263
|
+
r, g, b, a = get_rgb
|
264
|
+
set_rgb(val, g, b, a)
|
265
|
+
self
|
266
|
+
else
|
267
|
+
@rgb.redComponent
|
268
|
+
end
|
269
|
+
end
|
270
|
+
|
271
|
+
# set or retrieve the green component
|
272
|
+
def g(val=nil)
|
273
|
+
if val
|
274
|
+
r, g, b, a = get_rgb
|
275
|
+
set_rgb(r, val, b, a)
|
276
|
+
self
|
277
|
+
else
|
278
|
+
@rgb.greenComponent
|
279
|
+
end
|
280
|
+
end
|
281
|
+
|
282
|
+
# set or retrieve the blue component
|
283
|
+
def b(val=nil)
|
284
|
+
if val
|
285
|
+
r, g, b, a = get_rgb
|
286
|
+
set_rgb(r, g, val, a)
|
287
|
+
self
|
288
|
+
else
|
289
|
+
@rgb.blueComponent
|
290
|
+
end
|
291
|
+
end
|
292
|
+
|
293
|
+
# set or retrieve the alpha component
|
294
|
+
def a(val=nil)
|
295
|
+
if val
|
296
|
+
r, g, b, a = get_rgb
|
297
|
+
set_rgb(r, g, b, val)
|
298
|
+
self
|
299
|
+
else
|
300
|
+
@rgb.alphaComponent
|
301
|
+
end
|
302
|
+
end
|
303
|
+
|
304
|
+
# set or retrieve the hue
|
305
|
+
def hue(val=nil)
|
306
|
+
if val
|
307
|
+
h, s, b, a = get_hsb
|
308
|
+
set_hsb(val, s, b, a)
|
309
|
+
self
|
310
|
+
else
|
311
|
+
@rgb.hueComponent
|
312
|
+
end
|
313
|
+
end
|
314
|
+
|
315
|
+
# set or retrieve the saturation
|
316
|
+
def saturation(val=nil)
|
317
|
+
if val
|
318
|
+
h, s, b, a = get_hsb
|
319
|
+
set_hsb(h, val, b, a)
|
320
|
+
self
|
321
|
+
else
|
322
|
+
@rgb.saturationComponent
|
323
|
+
end
|
324
|
+
end
|
325
|
+
|
326
|
+
# set or retrieve the brightness
|
327
|
+
def brightness(val=nil)
|
328
|
+
if val
|
329
|
+
h, s, b, a = get_hsb
|
330
|
+
set_hsb(h, s, val, a)
|
331
|
+
self
|
332
|
+
else
|
333
|
+
@rgb.brightnessComponent
|
334
|
+
end
|
335
|
+
end
|
336
|
+
|
337
|
+
# decrease saturation by the specified amount
|
338
|
+
def desaturate(step=0.1)
|
339
|
+
saturation(saturation - step)
|
340
|
+
self
|
341
|
+
end
|
342
|
+
|
343
|
+
# increase the saturation by the specified amount
|
344
|
+
def saturate(step=0.1)
|
345
|
+
saturation(saturation + step)
|
346
|
+
self
|
347
|
+
end
|
348
|
+
|
349
|
+
# decrease the brightness by the specified amount
|
350
|
+
def darken(step=0.1)
|
351
|
+
brightness(brightness - step)
|
352
|
+
self
|
353
|
+
end
|
354
|
+
|
355
|
+
# increase the brightness by the specified amount
|
356
|
+
def lighten(step=0.1)
|
357
|
+
brightness(brightness + step)
|
358
|
+
self
|
359
|
+
end
|
360
|
+
|
361
|
+
# set the R,G,B,A values
|
362
|
+
def set(r, g, b, a=1.0)
|
363
|
+
set_rgb(r, g, b, a)
|
364
|
+
self
|
365
|
+
end
|
366
|
+
|
367
|
+
# adjust the Red, Green, Blue, Alpha values by the specified amounts
|
368
|
+
def adjust_rgb(r=0.0, g=0.0, b=0.0, a=0.0)
|
369
|
+
r0, g0, b0, a0 = get_rgb
|
370
|
+
set_rgb(r0+r, g0+g, b0+b, a0+a)
|
371
|
+
self
|
372
|
+
end
|
373
|
+
|
374
|
+
# return RGBA values
|
375
|
+
def get_rgb
|
376
|
+
#@rgb.getRed_green_blue_alpha_()
|
377
|
+
[@rgb.redComponent, @rgb.greenComponent, @rgb.blueComponent, @rgb.alphaComponent]
|
378
|
+
end
|
379
|
+
|
380
|
+
# set color using RGBA values
|
381
|
+
def set_rgb(r, g, b, a=1.0)
|
382
|
+
@rgb = NSColor.colorWithDeviceRed r, green:g, blue:b, alpha:a
|
383
|
+
self
|
384
|
+
end
|
385
|
+
|
386
|
+
# return HSBA values
|
387
|
+
def get_hsb
|
388
|
+
#@rgb.getHue_saturation_brightness_alpha_()
|
389
|
+
[@rgb.hueComponent, @rgb.saturationComponent, @rgb.brightnessComponent, @rgb.alphaComponent]
|
390
|
+
end
|
391
|
+
|
392
|
+
# set color using HSBA values
|
393
|
+
def set_hsb(h,s,b,a=1.0)
|
394
|
+
@rgb = NSColor.colorWithDeviceHue h, saturation:s, brightness:b, alpha:a
|
395
|
+
self
|
396
|
+
end
|
397
|
+
|
398
|
+
# adjust Hue, Saturation, Brightness, and Alpha by specified amounts
|
399
|
+
def adjust_hsb(h=0.0, s=0.0, b=0.0, a=0.0)
|
400
|
+
h0, s0, b0, a0 = get_hsb
|
401
|
+
set_hsb(h0+h, s0+s, b0+b, a0+a)
|
402
|
+
self
|
403
|
+
end
|
404
|
+
|
405
|
+
# alter the color by the specified random scaling factor
|
406
|
+
# def ish(angle=10.0,d=0.02)
|
407
|
+
# # r,g,b,a = get_rgb
|
408
|
+
# # r = vary(r, variance)
|
409
|
+
# # g = vary(g, variance)
|
410
|
+
# # b = vary(b, variance)
|
411
|
+
# # a = vary(a, variance)
|
412
|
+
# # set_rgb(r,g,b,a)
|
413
|
+
# analog(angle,d)
|
414
|
+
# self
|
415
|
+
# end
|
416
|
+
|
417
|
+
# create a random color
|
418
|
+
def random
|
419
|
+
set_rgb(rand, rand, rand, 1.0)
|
420
|
+
self
|
421
|
+
end
|
422
|
+
|
423
|
+
# rotate the color on the artistic RYB color wheel (0 to 360 degrees)
|
424
|
+
def rotate_ryb(angle=180)
|
425
|
+
|
426
|
+
# An artistic color wheel has slightly different opposites
|
427
|
+
# (e.g. purple-yellow instead of purple-lime).
|
428
|
+
# It is mathematically incorrect but generally assumed
|
429
|
+
# to provide better complementary colors.
|
430
|
+
#
|
431
|
+
# http://en.wikipedia.org/wiki/RYB_color_model
|
432
|
+
|
433
|
+
h = hue * 360
|
434
|
+
angle = angle % 360.0
|
435
|
+
a = 0
|
436
|
+
|
437
|
+
# Approximation of Itten's RYB color wheel.
|
438
|
+
# In HSB, colors hues range from 0-360.
|
439
|
+
# However, on the artistic color wheel these are not evenly distributed.
|
440
|
+
# The second tuple value contains the actual distribution.
|
441
|
+
|
442
|
+
# Given a hue, find out under what angle it is
|
443
|
+
# located on the artistic color wheel.
|
444
|
+
(RYBWheel.size-1).times do |i|
|
445
|
+
x0,y0 = RYBWheel[i]
|
446
|
+
x1,y1 = RYBWheel[i+1]
|
447
|
+
y1 += 360 if y1 < y0
|
448
|
+
if y0 <= h && h <= y1
|
449
|
+
a = 1.0 * x0 + (x1-x0) * (h-y0) / (y1-y0)
|
450
|
+
break
|
451
|
+
end
|
452
|
+
end
|
453
|
+
|
454
|
+
# And the user-given angle (e.g. complement).
|
455
|
+
a = (a+angle) % 360
|
456
|
+
|
457
|
+
# For the given angle, find out what hue is
|
458
|
+
# located there on the artistic color wheel.
|
459
|
+
(RYBWheel.size-1).times do |i|
|
460
|
+
x0,y0 = RYBWheel[i]
|
461
|
+
x1,y1 = RYBWheel[i+1]
|
462
|
+
y1 += 360 if y1 < y0
|
463
|
+
if x0 <= a && a <= x1
|
464
|
+
h = 1.0 * y0 + (y1-y0) * (a-x0) / (x1-x0)
|
465
|
+
break
|
466
|
+
end
|
467
|
+
end
|
468
|
+
|
469
|
+
h = h % 360
|
470
|
+
set_hsb(h/360, self.saturation, self.brightness, self.a)
|
471
|
+
self
|
472
|
+
end
|
473
|
+
|
474
|
+
# rotate the color on the RGB color wheel (0 to 360 degrees)
|
475
|
+
def rotate_rgb(angle=180)
|
476
|
+
hue = (self.hue + 1.0 * angle / 360) % 1
|
477
|
+
set_hsb(hue, self.saturation, self.brightness, @a)
|
478
|
+
self
|
479
|
+
end
|
480
|
+
|
481
|
+
# return a similar color, varying the hue by angle (0-360) and brightness/saturation by d
|
482
|
+
def analog(angle=20, d=0.5)
|
483
|
+
c = self.copy
|
484
|
+
c.rotate_ryb(angle * (rand*2-1))
|
485
|
+
c.lighten(d * (rand*2-1))
|
486
|
+
c.saturate(d * (rand*2-1))
|
487
|
+
end
|
488
|
+
|
489
|
+
# randomly vary the color within a maximum hue range, saturation range, and brightness range
|
490
|
+
def drift(maxhue=0.1,maxsat=0.3,maxbright=maxsat)
|
491
|
+
# save original values the first time
|
492
|
+
@original_hue ||= self.hue
|
493
|
+
@original_saturation ||= self.saturation
|
494
|
+
@original_brightness ||= self.brightness
|
495
|
+
# get current values
|
496
|
+
current_hue = self.hue
|
497
|
+
current_saturation = self.saturation
|
498
|
+
current_brightness = self.brightness
|
499
|
+
# generate new values
|
500
|
+
randhue = ((rand * maxhue) - maxhue/2.0) + current_hue
|
501
|
+
randhue = inrange(randhue, (@original_hue - maxhue/2.0),(@original_hue + maxhue/2.0))
|
502
|
+
randsat = (rand * maxsat) - maxsat/2.0 + current_saturation
|
503
|
+
randsat = inrange(randsat, @original_saturation - maxsat/2.0,@original_saturation + maxsat/2.0)
|
504
|
+
randbright = (rand * maxbright) - maxbright/2.0 + current_brightness
|
505
|
+
randbright = inrange(randbright, @original_brightness - maxbright/2.0,@original_brightness + maxbright/2.0)
|
506
|
+
# assign new values
|
507
|
+
self.hue(randhue)
|
508
|
+
self.saturation(randsat)
|
509
|
+
self.brightness(randbright)
|
510
|
+
self
|
511
|
+
end
|
512
|
+
|
513
|
+
# convert to the complementary color (the color at opposite on the artistic color wheel)
|
514
|
+
def complement
|
515
|
+
rotate_ryb(180)
|
516
|
+
self
|
517
|
+
end
|
518
|
+
|
519
|
+
# blend with another color (doesn't work?)
|
520
|
+
# def blend(color, pct=0.5)
|
521
|
+
# blended = NSColor.blendedColorWithFraction_ofColor(pct,color.rgb)
|
522
|
+
# @rgb = blended.colorUsingColorSpaceName(NSDeviceRGBColorSpace)
|
523
|
+
# self
|
524
|
+
# end
|
525
|
+
|
526
|
+
# create a new RGBA color
|
527
|
+
def self.rgb(r, g, b, a=1.0)
|
528
|
+
Color.new(r,g,b,a)
|
529
|
+
end
|
530
|
+
|
531
|
+
# create a new HSBA color
|
532
|
+
def self.hsb(h, s, b, a=1.0)
|
533
|
+
Color.new.set_hsb(h,s,b,a)
|
534
|
+
end
|
535
|
+
|
536
|
+
# create a new gray color with the specified darkness
|
537
|
+
def self.gray(pct=0.5)
|
538
|
+
Color.new(pct,pct,pct,1.0)
|
539
|
+
end
|
540
|
+
|
541
|
+
# return a random color
|
542
|
+
def self.random
|
543
|
+
Color.new.random
|
544
|
+
end
|
545
|
+
|
546
|
+
# Returns a list of complementary colors. The complement is the color 180 degrees across the artistic RYB color wheel.
|
547
|
+
# 1) ORIGINAL: the original color
|
548
|
+
# 2) CONTRASTING: same hue as original but much darker or lighter
|
549
|
+
# 3) SOFT SUPPORTING: same hue but lighter and less saturated than the original
|
550
|
+
# 4) CONTRASTING COMPLEMENT: a much brighter or darker version of the complement hue
|
551
|
+
# 5) COMPLEMENT: the hue 180 degrees opposite on the RYB color wheel with same brightness/saturation
|
552
|
+
# 6) LIGHT SUPPORTING COMPLEMENT VARIANT: a lighter less saturated version of the complement hue
|
553
|
+
def complementary
|
554
|
+
colors = []
|
555
|
+
|
556
|
+
# A contrasting color: much darker or lighter than the original.
|
557
|
+
colors.push(self)
|
558
|
+
c = self.copy
|
559
|
+
if self.brightness > 0.4
|
560
|
+
c.brightness(0.1 + c.brightness*0.25)
|
561
|
+
else
|
562
|
+
c.brightness(1.0 - c.brightness*0.25)
|
563
|
+
end
|
564
|
+
colors.push(c)
|
565
|
+
|
566
|
+
# A soft supporting color: lighter and less saturated.
|
567
|
+
c = self.copy
|
568
|
+
c.brightness(0.3 + c.brightness)
|
569
|
+
c.saturation(0.1 + c.saturation*0.3)
|
570
|
+
colors.push(c)
|
571
|
+
|
572
|
+
# A contrasting complement: very dark or very light.
|
573
|
+
c_comp = self.copy.complement
|
574
|
+
c = c_comp.copy
|
575
|
+
if c_comp.brightness > 0.3
|
576
|
+
c.brightness(0.1 + c_comp.brightness*0.25)
|
577
|
+
else
|
578
|
+
c.brightness(1.0 - c.brightness*0.25)
|
579
|
+
end
|
580
|
+
colors.push(c)
|
581
|
+
|
582
|
+
# The complement
|
583
|
+
colors.push(c_comp)
|
584
|
+
|
585
|
+
# and a light supporting variant.
|
586
|
+
c = c_comp.copy
|
587
|
+
c.brightness(0.3 + c.brightness)
|
588
|
+
c.saturation(0.1 + c.saturation*0.25)
|
589
|
+
colors.push(c)
|
590
|
+
|
591
|
+
colors
|
592
|
+
end
|
593
|
+
|
594
|
+
# Returns a list with the split complement of the color.
|
595
|
+
# The split complement are the two colors to the left and right
|
596
|
+
# of the color's complement.
|
597
|
+
def split_complementary(angle=30)
|
598
|
+
colors = []
|
599
|
+
colors.push(self)
|
600
|
+
comp = self.copy.complement
|
601
|
+
colors.push(comp.copy.rotate_ryb(-angle).lighten(0.1))
|
602
|
+
colors.push(comp.copy.rotate_ryb(angle).lighten(0.1))
|
603
|
+
colors
|
604
|
+
end
|
605
|
+
|
606
|
+
# Returns the left half of the split complement.
|
607
|
+
# A list is returned with the same darker and softer colors
|
608
|
+
# as in the complementary list, but using the hue of the
|
609
|
+
# left split complement instead of the complement itself.
|
610
|
+
def left_complement(angle=-30)
|
611
|
+
left = copy.complement.rotate_ryb(angle).lighten(0.1)
|
612
|
+
colors = complementary
|
613
|
+
colors[3].hue(left.hue)
|
614
|
+
colors[4].hue(left.hue)
|
615
|
+
colors[5].hue(left.hue)
|
616
|
+
colors
|
617
|
+
end
|
618
|
+
|
619
|
+
# Returns the right half of the split complement.
|
620
|
+
# A list is returned with the same darker and softer colors
|
621
|
+
# as in the complementary list, but using the hue of the
|
622
|
+
# right split complement instead of the complement itself.
|
623
|
+
def right_complement(angle=30)
|
624
|
+
right = copy.complement.rotate_ryb(angle).lighten(0.1)
|
625
|
+
colors = complementary
|
626
|
+
colors[3].hue(right.hue)
|
627
|
+
colors[4].hue(right.hue)
|
628
|
+
colors[5].hue(right.hue)
|
629
|
+
colors
|
630
|
+
end
|
631
|
+
|
632
|
+
# Returns colors that are next to each other on the wheel.
|
633
|
+
# These yield natural color schemes (like shades of water or sky).
|
634
|
+
# The angle determines how far the colors are apart,
|
635
|
+
# making it bigger will introduce more variation.
|
636
|
+
# The contrast determines the darkness/lightness of
|
637
|
+
# the analogue colors in respect to the given colors.
|
638
|
+
def analogous(angle=10, contrast=0.25)
|
639
|
+
contrast = inrange(contrast, 0.0, 1.0)
|
640
|
+
|
641
|
+
colors = []
|
642
|
+
colors.push(self)
|
643
|
+
|
644
|
+
for i, j in [[1,2.2], [2,1], [-1,-0.5], [-2,1]] do
|
645
|
+
c = copy.rotate_ryb(angle*i)
|
646
|
+
t = 0.44-j*0.1
|
647
|
+
if brightness - contrast*j < t then
|
648
|
+
c.brightness(t)
|
649
|
+
else
|
650
|
+
c.brightness(self.brightness - contrast*j)
|
651
|
+
end
|
652
|
+
c.saturation(c.saturation - 0.05)
|
653
|
+
colors.push(c)
|
654
|
+
end
|
655
|
+
colors
|
656
|
+
end
|
657
|
+
|
658
|
+
# Returns colors in the same hue with varying brightness/saturation.
|
659
|
+
def monochrome
|
660
|
+
|
661
|
+
colors = [self]
|
662
|
+
|
663
|
+
c = copy
|
664
|
+
c.brightness(_wrap(brightness, 0.5, 0.2, 0.3))
|
665
|
+
c.saturation(_wrap(saturation, 0.3, 0.1, 0.3))
|
666
|
+
colors.push(c)
|
667
|
+
|
668
|
+
c = copy
|
669
|
+
c.brightness(_wrap(brightness, 0.2, 0.2, 0.6))
|
670
|
+
colors.push(c)
|
671
|
+
|
672
|
+
c = copy
|
673
|
+
c.brightness(max(0.2, brightness+(1-brightness)*0.2))
|
674
|
+
c.saturation(_wrap(saturation, 0.3, 0.1, 0.3))
|
675
|
+
colors.push(c)
|
676
|
+
|
677
|
+
c = self.copy()
|
678
|
+
c.brightness(_wrap(brightness, 0.5, 0.2, 0.3))
|
679
|
+
colors.push(c)
|
680
|
+
|
681
|
+
colors
|
682
|
+
end
|
683
|
+
|
684
|
+
# Returns a triad of colors.
|
685
|
+
# The triad is made up of this color and two other colors
|
686
|
+
# that together make up an equilateral triangle on
|
687
|
+
# the artistic color wheel.
|
688
|
+
def triad(angle=120)
|
689
|
+
colors = [self]
|
690
|
+
colors.push(copy.rotate_ryb(angle).lighten(0.1))
|
691
|
+
colors.push(copy.rotate_ryb(-angle).lighten(0.1))
|
692
|
+
colors
|
693
|
+
end
|
694
|
+
|
695
|
+
# Returns a tetrad of colors.
|
696
|
+
# The tetrad is made up of this color and three other colors
|
697
|
+
# that together make up a cross on the artistic color wheel.
|
698
|
+
def tetrad(angle=90)
|
699
|
+
|
700
|
+
colors = [self]
|
701
|
+
|
702
|
+
c = copy.rotate_ryb(angle)
|
703
|
+
if brightness < 0.5 then
|
704
|
+
c.brightness(c.brightness + 0.2)
|
705
|
+
else
|
706
|
+
c.brightness(c.brightness - 0.2)
|
707
|
+
end
|
708
|
+
colors.push(c)
|
709
|
+
|
710
|
+
c = copy.rotate_ryb(angle*2)
|
711
|
+
if brightness < 0.5
|
712
|
+
c.brightness(c.brightness + 0.1)
|
713
|
+
else
|
714
|
+
c.brightness(c.brightness - 0.1)
|
715
|
+
end
|
716
|
+
|
717
|
+
colors.push(c)
|
718
|
+
colors.push(copy.rotate_ryb(angle*3).lighten(0.1))
|
719
|
+
colors
|
720
|
+
end
|
721
|
+
|
722
|
+
# Roughly the complement and some far analogs.
|
723
|
+
def compound(flip=false)
|
724
|
+
|
725
|
+
d = (flip ? -1 : 1)
|
726
|
+
|
727
|
+
colors = [self]
|
728
|
+
|
729
|
+
c = copy.rotate_ryb(30*d)
|
730
|
+
c.brightness(_wrap(brightness, 0.25, 0.6, 0.25))
|
731
|
+
colors.push(c)
|
732
|
+
|
733
|
+
c = copy.rotate_ryb(30*d)
|
734
|
+
c.saturation(_wrap(saturation, 0.4, 0.1, 0.4))
|
735
|
+
c.brightness(_wrap(brightness, 0.4, 0.2, 0.4))
|
736
|
+
colors.push(c)
|
737
|
+
|
738
|
+
c = copy.rotate_ryb(160*d)
|
739
|
+
c.saturation(_wrap(saturation, 0.25, 0.1, 0.25))
|
740
|
+
c.brightness(max(0.2, brightness))
|
741
|
+
colors.push(c)
|
742
|
+
|
743
|
+
c = copy.rotate_ryb(150*d)
|
744
|
+
c.saturation(_wrap(saturation, 0.1, 0.8, 0.1))
|
745
|
+
c.brightness(_wrap(brightness, 0.3, 0.6, 0.3))
|
746
|
+
colors.push(c)
|
747
|
+
|
748
|
+
c = copy.rotate_ryb(150*d)
|
749
|
+
c.saturation(_wrap(saturation, 0.1, 0.8, 0.1))
|
750
|
+
c.brightness(_wrap(brightness, 0.4, 0.2, 0.4))
|
751
|
+
#colors.push(c)
|
752
|
+
|
753
|
+
colors
|
754
|
+
end
|
755
|
+
|
756
|
+
# Roughly the complement and some far analogs.
|
757
|
+
def flipped_compound
|
758
|
+
compound(true)
|
759
|
+
end
|
760
|
+
|
761
|
+
private
|
762
|
+
|
763
|
+
# vary a single color component by a multiplier
|
764
|
+
def vary(original, variance)
|
765
|
+
newvalue = original + (rand * variance * (rand > 0.5 ? 1 : -1))
|
766
|
+
newvalue = inrange(newvalue,0.0,1.0)
|
767
|
+
newvalue
|
768
|
+
end
|
769
|
+
|
770
|
+
# wrap within range
|
771
|
+
def _wrap(x, min, threshold, plus)
|
772
|
+
if x - min < threshold
|
773
|
+
x + plus
|
774
|
+
else
|
775
|
+
x - min
|
776
|
+
end
|
777
|
+
end
|
778
|
+
|
779
|
+
end
|
780
|
+
|
781
|
+
end
|