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