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,488 @@
|
|
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
|
+
# load a raw image file for use on a canvas
|
19
|
+
class Image
|
20
|
+
|
21
|
+
|
22
|
+
BlendModes = {
|
23
|
+
:normal => 'CISourceOverCompositing',
|
24
|
+
:multiply => 'CIMultiplyBlendMode',
|
25
|
+
:screen => 'CIScreenBlendMode',
|
26
|
+
:overlay => 'CIOverlayBlendMode',
|
27
|
+
:darken => 'CIDarkenBlendMode',
|
28
|
+
:lighten => 'CILightenBlendMode',
|
29
|
+
:colordodge => 'CIColorDodgeBlendMode',
|
30
|
+
:colorburn => 'CIColorBurnBlendMode',
|
31
|
+
:softlight => 'CISoftLightBlendMode',
|
32
|
+
:hardlight => 'CIHardLightBlendMode',
|
33
|
+
:difference => 'CIDifferenceBlendMode',
|
34
|
+
:exclusion => 'CIExclusionBlendMode',
|
35
|
+
:hue => 'CIHueBlendMode',
|
36
|
+
:saturation => 'CISaturationBlendMode',
|
37
|
+
:color => 'CIColorBlendMode',
|
38
|
+
:luminosity => 'CILuminosityBlendMode',
|
39
|
+
# following modes not available in CGContext:
|
40
|
+
:maximum => 'CIMaximumCompositing',
|
41
|
+
:minimum => 'CIMinimumCompositing',
|
42
|
+
:add => 'CIAdditionCompositing',
|
43
|
+
:atop => 'CISourceAtopCompositing',
|
44
|
+
:in => 'CISourceInCompositing',
|
45
|
+
:out => 'CISourceOutCompositing',
|
46
|
+
:over => 'CISourceOverCompositing'
|
47
|
+
}
|
48
|
+
BlendModes.default('CISourceOverCompositing')
|
49
|
+
|
50
|
+
attr_reader :cgimage
|
51
|
+
|
52
|
+
# load the image from the given path
|
53
|
+
def initialize(img, verbose=false)
|
54
|
+
self.verbose(verbose)
|
55
|
+
case img
|
56
|
+
when String
|
57
|
+
# if it's the path to an image file, load as a CGImage
|
58
|
+
@path = img
|
59
|
+
File.exists?(@path) or raise "ERROR: file not found: #{@path}"
|
60
|
+
|
61
|
+
nsimage = NSImage.alloc.initWithContentsOfFile(img)
|
62
|
+
nsdata = nsimage.TIFFRepresentation
|
63
|
+
@nsbitmapimage = NSBitmapImageRep.imageRepWithData(nsdata)
|
64
|
+
# cgimagesource = CGImageSourceCreateWithData(nsdata) # argh, doesn't work
|
65
|
+
@ciimage = CIImage.alloc.initWithBitmapImageRep(@nsbitmapimage)
|
66
|
+
when Canvas
|
67
|
+
puts "Image.new with canvas" if @verbose
|
68
|
+
@path = 'canvas'
|
69
|
+
@cgimage = img.cgimage
|
70
|
+
when Image
|
71
|
+
# copy image?
|
72
|
+
else
|
73
|
+
raise "ERROR: image type not recognized: #{img.class}"
|
74
|
+
end
|
75
|
+
# save the original
|
76
|
+
@original = @ciimage.copy
|
77
|
+
puts "Image.new from [#{@path}] at [#{x},#{y}] with #{width}x#{height}" if @verbose
|
78
|
+
self
|
79
|
+
end
|
80
|
+
|
81
|
+
# reload the bitmap image
|
82
|
+
def reset
|
83
|
+
@ciimage = CIImage.alloc.initWithBitmapImageRep(@nsbitmapimage)
|
84
|
+
self
|
85
|
+
end
|
86
|
+
|
87
|
+
# set registration mode to :center or :corner
|
88
|
+
def registration(mode=:center)
|
89
|
+
@registration = mode
|
90
|
+
end
|
91
|
+
|
92
|
+
# print the parameters of the path
|
93
|
+
def to_s
|
94
|
+
"image.to_s: #{@path} at [#{x},#{y}] with #{width}x#{height}"
|
95
|
+
end
|
96
|
+
|
97
|
+
# print drawing functions if verbose is true
|
98
|
+
def verbose(tf=true)
|
99
|
+
@verbose = tf
|
100
|
+
end
|
101
|
+
|
102
|
+
# return the width of the image
|
103
|
+
def width
|
104
|
+
@ciimage ? @ciimage.extent.size.width : CGImageGetWidth(@cgimage)
|
105
|
+
end
|
106
|
+
|
107
|
+
# return the height of the image
|
108
|
+
def height
|
109
|
+
@ciimage ? @ciimage.extent.size.height : CGImageGetHeight(@cgimage)
|
110
|
+
end
|
111
|
+
|
112
|
+
# return the x coordinate of the image's origin
|
113
|
+
def x
|
114
|
+
@ciimage ? @ciimage.extent.origin.x : 0
|
115
|
+
end
|
116
|
+
|
117
|
+
# return the y coordinate of the image's origin
|
118
|
+
def y
|
119
|
+
@ciimage ? @ciimage.extent.origin.y : 0
|
120
|
+
end
|
121
|
+
|
122
|
+
# def translate(x,y)
|
123
|
+
# matrix = CGAffineTransformMakeTranslation(x,y)
|
124
|
+
# @ciimage = @ciimage.imageByApplyingTransform(matrix)
|
125
|
+
# @ciimage.extent
|
126
|
+
# self
|
127
|
+
# end
|
128
|
+
|
129
|
+
# RESIZING/MOVING
|
130
|
+
|
131
|
+
# scale the image by multiplying the width by a multiplier, optionally scaling height using aspect ratio
|
132
|
+
def scale(multiplier, aspect=1.0)
|
133
|
+
puts "image.scale: #{multiplier},#{aspect}" if @verbose
|
134
|
+
filter 'CILanczosScaleTransform', :inputScale => multiplier.to_f, :inputAspectRatio => aspect.to_f
|
135
|
+
self
|
136
|
+
end
|
137
|
+
|
138
|
+
# scale image to fit within a box of w,h using CIAffineTransform (sharper)
|
139
|
+
def fit2(w, h)
|
140
|
+
width_multiplier = w.to_f / width
|
141
|
+
height_multiplier = h.to_f / height
|
142
|
+
multiplier = width_multiplier < height_multiplier ? width_multiplier : height_multiplier
|
143
|
+
puts "image.fit2: #{multiplier}" if @verbose
|
144
|
+
transform = NSAffineTransform.transform
|
145
|
+
transform.scaleBy(multiplier)
|
146
|
+
filter 'CIAffineTransform', :inputTransform => transform
|
147
|
+
self
|
148
|
+
end
|
149
|
+
|
150
|
+
# scale image to fit within a box of w,h using CILanczosScaleTransform
|
151
|
+
def fit(w, h)
|
152
|
+
# http://gigliwood.com/weblog/Cocoa/Core_Image,_part_2.html
|
153
|
+
old_w = self.width.to_f
|
154
|
+
old_h = self.height.to_f
|
155
|
+
old_x = self.x
|
156
|
+
old_y = self.y
|
157
|
+
|
158
|
+
# choose a scaling factor
|
159
|
+
width_multiplier = w.to_f / old_w
|
160
|
+
height_multiplier = h.to_f / old_h
|
161
|
+
multiplier = width_multiplier < height_multiplier ? width_multiplier : height_multiplier
|
162
|
+
|
163
|
+
# crop result to integer pixel dimensions
|
164
|
+
new_width = (self.width * multiplier).truncate
|
165
|
+
new_height = (self.height * multiplier).truncate
|
166
|
+
|
167
|
+
puts "image.fit: old size #{old_w}x#{old_h}, max target #{w}x#{h}, multiplier #{multiplier}, new size #{new_width}x#{new_height}" if @verbose
|
168
|
+
clamp
|
169
|
+
scale(multiplier)
|
170
|
+
crop(old_x, old_y, new_width, new_height)
|
171
|
+
#origin(:bottomleft)
|
172
|
+
self
|
173
|
+
end
|
174
|
+
|
175
|
+
# resize the image to have new dimensions w,h
|
176
|
+
def resize(w, h)
|
177
|
+
oldw = width
|
178
|
+
oldh = height
|
179
|
+
puts "image.resize #{oldw}x#{oldh} => #{w}x#{h}" if @verbose
|
180
|
+
width_ratio = w.to_f / oldw.to_f
|
181
|
+
height_ratio = h.to_f / oldh.to_f
|
182
|
+
aspect = width_ratio / height_ratio # (works when stretching tall, gives aspect = 0.65)
|
183
|
+
scale(height_ratio,aspect)
|
184
|
+
origin(:bottomleft)
|
185
|
+
self
|
186
|
+
end
|
187
|
+
|
188
|
+
# crop the image to a rectangle from x1,y2 with width x height
|
189
|
+
def crop(x=nil,y=nil,w=nil,h=nil)
|
190
|
+
|
191
|
+
# crop to largest square if no parameters were given
|
192
|
+
unless x
|
193
|
+
if (self.width > self.height)
|
194
|
+
side = self.height
|
195
|
+
x = (self.width - side) / 2
|
196
|
+
y = 0
|
197
|
+
else
|
198
|
+
side = self.width
|
199
|
+
y = (self.height - side) / 2
|
200
|
+
x = 0
|
201
|
+
end
|
202
|
+
w = h = side
|
203
|
+
end
|
204
|
+
|
205
|
+
puts "image.crop [#{x},#{y}] with #{w},#{h}" if @verbose
|
206
|
+
#vector = CIVector.vectorWithX_Y_Z_W(x.to_f,y.to_f,w.to_f,h.to_f)
|
207
|
+
vector = CIVector.vectorWithX x.to_f, Y:y.to_f, Z:w.to_f, W:h.to_f
|
208
|
+
filter('CICrop', :inputRectangle => vector)
|
209
|
+
origin(:bottomleft)
|
210
|
+
self
|
211
|
+
end
|
212
|
+
|
213
|
+
# apply an affine transformation using matrix parameters a,b,c,d,tx,ty
|
214
|
+
def transform(a, b, c, d, tx, ty)
|
215
|
+
puts "image.transform #{a},#{b},#{c},#{d},#{tx},#{ty}" if @verbose
|
216
|
+
transform = CGAffineTransformMake(a, b, c, d, tx, ty) # FIXME: needs to be NSAffineTransform?
|
217
|
+
filter 'CIAffineTransform', :inputTransform => transform
|
218
|
+
self
|
219
|
+
end
|
220
|
+
|
221
|
+
# translate image by tx,ty
|
222
|
+
def translate(tx, ty)
|
223
|
+
puts "image.translate #{tx},#{ty}" if @verbose
|
224
|
+
#transform = CGAffineTransformMakeTranslation(tx,ty);
|
225
|
+
transform = NSAffineTransform.transform
|
226
|
+
transform.translateXBy tx, yBy:ty
|
227
|
+
filter 'CIAffineTransform', :inputTransform => transform
|
228
|
+
self
|
229
|
+
end
|
230
|
+
|
231
|
+
# rotate image by degrees
|
232
|
+
def rotate(deg)
|
233
|
+
puts "image.rotate #{deg}" if @verbose
|
234
|
+
#transform = CGAffineTransformMakeRotation(radians(deg));
|
235
|
+
transform = NSAffineTransform.transform
|
236
|
+
transform.rotateByDegrees(-deg)
|
237
|
+
filter 'CIAffineTransform', :inputTransform => transform
|
238
|
+
self
|
239
|
+
end
|
240
|
+
|
241
|
+
# set the origin to the specified location (:center, :bottomleft, etc)
|
242
|
+
def origin(location=:bottomleft)
|
243
|
+
movex, movey = reorient(x, y, width, height, location)
|
244
|
+
translate(movex, movey)
|
245
|
+
end
|
246
|
+
|
247
|
+
|
248
|
+
# FILTERS
|
249
|
+
|
250
|
+
# apply a crystallizing effect with pixel radius 1-100
|
251
|
+
def crystallize(radius=20.0)
|
252
|
+
filter 'CICrystallize', :inputRadius => radius
|
253
|
+
self
|
254
|
+
end
|
255
|
+
|
256
|
+
# apply a gaussian blur with pixel radius 1-100
|
257
|
+
def blur(radius=10.0)
|
258
|
+
filter 'CIGaussianBlur', :inputRadius => inrange(radius, 1.0, 100.0)
|
259
|
+
self
|
260
|
+
end
|
261
|
+
|
262
|
+
# sharpen the image given a radius (0-100) and intensity factor
|
263
|
+
def sharpen(radius=2.50, intensity=0.50)
|
264
|
+
filter 'CIUnsharpMask', :inputRadius => radius, :inputIntensity => intensity
|
265
|
+
self
|
266
|
+
end
|
267
|
+
|
268
|
+
# apply a gaussian blur with pixel radius 1-100
|
269
|
+
def motionblur(radius=10.0, angle=90.0)
|
270
|
+
oldx, oldy, oldw, oldh = [x, y, width, height]
|
271
|
+
clamp
|
272
|
+
filter 'CIMotionBlur', :inputRadius => radius, :inputAngle => radians(angle)
|
273
|
+
crop(oldx, oldy, oldw, oldh)
|
274
|
+
self
|
275
|
+
end
|
276
|
+
|
277
|
+
# rotate pixels around x,y with radius and angle
|
278
|
+
def twirl(x=0, y=0, radius=300, angle=90.0)
|
279
|
+
filter 'CITwirlDistortion', :inputCenter => CIVector.vectorWithX(x, Y:y), :inputRadius => radius, :inputAngle => radians(angle)
|
280
|
+
self
|
281
|
+
end
|
282
|
+
|
283
|
+
# apply a bloom effect
|
284
|
+
def bloom(radius=10, intensity=1.0)
|
285
|
+
filter 'CIBloom', :inputRadius => inrange(radius, 0, 100), :inputIntensity => inrange(intensity, 0.0, 1.0)
|
286
|
+
self
|
287
|
+
end
|
288
|
+
|
289
|
+
# adjust the hue of the image by rotating the color wheel from 0 to 360 degrees
|
290
|
+
def hue(angle=180)
|
291
|
+
filter 'CIHueAdjust', :inputAngle => radians(angle)
|
292
|
+
self
|
293
|
+
end
|
294
|
+
|
295
|
+
# remap colors so they fall within shades of a single color
|
296
|
+
def monochrome(color=Color.gray)
|
297
|
+
filter 'CIColorMonochrome', :inputColor => CIColor.colorWithRed(color.r, green:color.g, blue:color.b, alpha:color.a)
|
298
|
+
self
|
299
|
+
end
|
300
|
+
|
301
|
+
# adjust the reference white point for an image and maps all colors in the source using the new reference
|
302
|
+
def whitepoint(color=Color.white.ish)
|
303
|
+
filter 'CIWhitePointAdjust', :inputColor => CIColor.colorWithRed(color.r, green:color.g, blue:color.b, alpha:color.a)
|
304
|
+
self
|
305
|
+
end
|
306
|
+
|
307
|
+
# reduce colors with a banding effect
|
308
|
+
def posterize(levels=6.0)
|
309
|
+
filter 'CIColorPosterize', :inputLevels => inrange(levels, 1.0, 300.0)
|
310
|
+
self
|
311
|
+
end
|
312
|
+
|
313
|
+
# detect edges
|
314
|
+
def edges(intensity=1.0)
|
315
|
+
filter 'CIEdges', :inputIntensity => inrange(intensity, 0.0,10.0)
|
316
|
+
self
|
317
|
+
end
|
318
|
+
|
319
|
+
# apply woodblock-like effect
|
320
|
+
def edgework(radius=1.0)
|
321
|
+
filter 'CIEdgeWork', :inputRadius => inrange(radius, 0.0,20.0)
|
322
|
+
self
|
323
|
+
end
|
324
|
+
|
325
|
+
# adjust exposure by f-stop
|
326
|
+
def exposure(ev=0.5)
|
327
|
+
filter 'CIExposureAdjust', :inputEV => inrange(ev, -10.0, 10.0)
|
328
|
+
self
|
329
|
+
end
|
330
|
+
|
331
|
+
# adjust saturation
|
332
|
+
def saturation(value=1.5)
|
333
|
+
filter 'CIColorControls', :inputSaturation => value, :inputBrightness => 0.0, :inputContrast => 1.0
|
334
|
+
self
|
335
|
+
end
|
336
|
+
|
337
|
+
# adjust brightness (-1 to 1)
|
338
|
+
def brightness(value=1.1)
|
339
|
+
filter 'CIColorControls', :inputSaturation => 1.0, :inputBrightness => inrange(value, -1.0, 1.0), :inputContrast => 1.0
|
340
|
+
self
|
341
|
+
end
|
342
|
+
|
343
|
+
# adjust contrast (0 to 4)
|
344
|
+
def contrast(value=1.5)
|
345
|
+
#value = inrange(value,0.25,100.0)
|
346
|
+
filter 'CIColorControls', :inputSaturation => 1.0, :inputBrightness => 0.0, :inputContrast => value
|
347
|
+
self
|
348
|
+
end
|
349
|
+
|
350
|
+
# fill with a gradient from color0 to color1 from [x0,y0] to [x1,y1]
|
351
|
+
def gradient(color0, color1, x0 = x / 2, y0 = y, x1 = x / 2, y1 = height)
|
352
|
+
filter 'CILinearGradient',
|
353
|
+
:inputColor0 => color0.rgb,
|
354
|
+
:inputColor1 => color1.rgb,
|
355
|
+
:inputPoint0 => CIVector.vectorWithX(x0, Y:y0),
|
356
|
+
:inputPoint1 => CIVector.vectorWithX(x1, Y:y1)
|
357
|
+
self
|
358
|
+
end
|
359
|
+
|
360
|
+
# use the gray values of the input image as a displacement map (doesn't work with PNG?)
|
361
|
+
def displacement(image, scale=50.0)
|
362
|
+
filter 'CIDisplacementDistortion', :inputDisplacementImage => image.ciimage, :inputScale => inrange(scale, 0.0, 200.0)
|
363
|
+
self
|
364
|
+
end
|
365
|
+
|
366
|
+
# simulate a halftone screen given a center point, angle(0-360), width(1-50), and sharpness(0-1)
|
367
|
+
def dotscreen(dx=0, dy=0, angle=0, width=6, sharpness=0.7)
|
368
|
+
filter 'CIDotScreen',
|
369
|
+
:inputCenter => CIVector.vectorWithX(dx.to_f, Y:dy.to_f),
|
370
|
+
:inputAngle => max(0, min(angle, 360)),
|
371
|
+
:inputWidth => max(1, min(width, 50)),
|
372
|
+
:inputSharpness => max(0, min(sharpness, 1))
|
373
|
+
self
|
374
|
+
end
|
375
|
+
|
376
|
+
# extend pixels at edges to infinity for nicer sharpen/blur effects
|
377
|
+
def clamp
|
378
|
+
puts "image.clamp" if @verbose
|
379
|
+
filter 'CIAffineClamp', :inputTransform => NSAffineTransform.transform
|
380
|
+
self
|
381
|
+
end
|
382
|
+
|
383
|
+
# blend with the given image using mode (:add, etc)
|
384
|
+
def blend(image, mode)
|
385
|
+
case image
|
386
|
+
when String
|
387
|
+
ciimage_background = CIImage.imageWithContentsOfURL(NSURL.fileURLWithPath(image))
|
388
|
+
when Image
|
389
|
+
ciimage_background = image.ciimage
|
390
|
+
else
|
391
|
+
raise "ERROR: Image: type not recognized"
|
392
|
+
end
|
393
|
+
filter BlendModes[mode], :inputBackgroundImage => ciimage_background
|
394
|
+
self
|
395
|
+
end
|
396
|
+
|
397
|
+
# draw this image to the specified context
|
398
|
+
def draw(ctx,x=0,y=0,w=width,h=height)
|
399
|
+
ciimage
|
400
|
+
# imgx = x + self.x
|
401
|
+
# imyy = y + self.y
|
402
|
+
resize(w, h) if w != self.width || h != self.height
|
403
|
+
|
404
|
+
# add the ciimage's own origin coordinates to the target point
|
405
|
+
x = x + self.x
|
406
|
+
y = y + self.y
|
407
|
+
|
408
|
+
puts "image.draw #{x},#{y} #{w}x#{h}" if @verbose
|
409
|
+
cicontext = CIContext.contextWithCGContext ctx, options:nil
|
410
|
+
#cicontext.drawImage_atPoint_fromRect(ciimage, [x,y], CGRectMake(self.x,self.y,w,h))
|
411
|
+
cicontext.drawImage ciimage, atPoint:CGPointMake(x,y), fromRect:CGRectMake(self.x,self.y,w,h)
|
412
|
+
end
|
413
|
+
|
414
|
+
# return the CIImage for this Image object
|
415
|
+
def ciimage
|
416
|
+
@ciimage ||= CIImage.imageWithCGImage(@cgimage)
|
417
|
+
end
|
418
|
+
|
419
|
+
# return an array of n colors chosen randomly from the source image.
|
420
|
+
# if type = :grid, choose average colors from each square in a grid with n squares
|
421
|
+
def colors(n=32, type=:random)
|
422
|
+
ciimage
|
423
|
+
colors = []
|
424
|
+
if (type == :grid) then
|
425
|
+
filtername = 'CIAreaAverage'
|
426
|
+
f = CIFilter.filterWithName(filtername)
|
427
|
+
f.setDefaults
|
428
|
+
f.setValue_forKey(@ciimage, 'inputImage')
|
429
|
+
|
430
|
+
extents = []
|
431
|
+
|
432
|
+
rows = Math::sqrt(n).truncate
|
433
|
+
cols = rows
|
434
|
+
w = self.width
|
435
|
+
h = self.height
|
436
|
+
block_width = w / cols
|
437
|
+
block_height = h / rows
|
438
|
+
rows.times do |row|
|
439
|
+
cols.times do |col|
|
440
|
+
x = col * block_width
|
441
|
+
y = row * block_height
|
442
|
+
extents.push([x, y, block_width, block_height])
|
443
|
+
end
|
444
|
+
end
|
445
|
+
extents.each do |extent|
|
446
|
+
x, y, w, h = extent
|
447
|
+
extent = CIVector.vectorWithX x.to_f, Y:y.to_f, Z:w.to_f, W:h.to_f
|
448
|
+
f.setValue_forKey(extent, 'inputExtent')
|
449
|
+
ciimage = f.valueForKey('outputImage') # CIImageRef
|
450
|
+
nsimg = NSBitmapImageRep.alloc.initWithCIImage(ciimage)
|
451
|
+
nscolor = nsimg.colorAtX 0, y:0 # NSColor
|
452
|
+
#r,g,b,a = nscolor.getRed_green_blue_alpha_()
|
453
|
+
r,b,b,a = [nscolor.redComponent,nscolor.greenComponent,nscolor.blueComponent,nscolor.alphaComponent]
|
454
|
+
colors.push(Color.new(r,g,b,1.0))
|
455
|
+
end
|
456
|
+
elsif (type == :random)
|
457
|
+
nsimg = NSBitmapImageRep.alloc.initWithCIImage(@ciimage)
|
458
|
+
n.times do |i|
|
459
|
+
x = rand(self.width)
|
460
|
+
y = rand(self.height)
|
461
|
+
nscolor = nsimg.colorAtX x, y:y # NSColor
|
462
|
+
#r,g,b,a = nscolor.getRed_green_blue_alpha_()
|
463
|
+
r,g,b,a = [nscolor.redComponent,nscolor.greenComponent,nscolor.blueComponent,nscolor.alphaComponent]
|
464
|
+
colors.push(Color.new(r,g,b,1.0))
|
465
|
+
end
|
466
|
+
end
|
467
|
+
colors
|
468
|
+
end
|
469
|
+
|
470
|
+
private
|
471
|
+
|
472
|
+
# apply the named CoreImage filter using a hash of parameters
|
473
|
+
def filter(filtername, parameters)
|
474
|
+
ciimage
|
475
|
+
f = CIFilter.filterWithName(filtername)
|
476
|
+
f.setDefaults
|
477
|
+
f.setValue @ciimage, forKey:'inputImage'
|
478
|
+
parameters.each do |key,value|
|
479
|
+
f.setValue value, forKey:key
|
480
|
+
end
|
481
|
+
puts "image.filter #{filtername}" if @verbose
|
482
|
+
@ciimage = f.valueForKey('outputImage') # CIImageRef
|
483
|
+
self
|
484
|
+
end
|
485
|
+
|
486
|
+
end
|
487
|
+
|
488
|
+
end
|