core_image 0.0.2 → 0.0.3

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/.gitignore ADDED
@@ -0,0 +1 @@
1
+ *.gem
data/LICENSE ADDED
@@ -0,0 +1,20 @@
1
+ Copyright (c) 2012 Spencer Rogers
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,47 @@
1
+ ## Core Image Gem ##
2
+ Simple image manipulation using Apple's Core Image technology.
3
+
4
+ ### Installation ###
5
+ `gem install core_image`
6
+
7
+ ### Usage ###
8
+ require 'core_image'
9
+ image = CoreImage.new(location) # more on this below
10
+ image.rotate(90)
11
+ image.scale_to_size(300)
12
+ image.save
13
+
14
+ ### Supports Chaining ###
15
+ image.rotate(90).scale(0.6).save(new_path)
16
+
17
+ ### Extra Bits ###
18
+ image.flip_horizontally
19
+ image.flip_vertically
20
+ image.save # oh no!
21
+ image.revert #=> reverts to the original image
22
+
23
+ ### One More Thing! ###
24
+ image.tint({:red => 0, :green => 0, :blue => 255, :alpha => 1.0})
25
+ image.crop(x, y, w, h) # starts from lower left
26
+ image.color_at(x, y) # => RGB Hash of specified pixel
27
+ image.overlay(another_image) # follows the same instantiation rules
28
+
29
+
30
+ ### Instantiating ###
31
+ You can instantiate a new Core Image object with the following:
32
+ * A path to a file
33
+ * A url to a file
34
+ * An Apple image object: NSImage, CIImage, CGImage
35
+ * Another Core Image object
36
+
37
+ ### File Types ###
38
+ Core Image can open and save the following formats: jpeg, png, tiff, gif, pct, and bmp. It can also open pdfs and save them as one of the previously listed image formats.
39
+
40
+ ### Requirements ###
41
+ Requires Mac OS X to run.
42
+
43
+ ### Tests ###
44
+ Core Image testing is done via the [Riot](http://rubygems.org/gems/riot) gem and can be run by typing `rake` from the root directory of this repository (you must have Riot installed to do so).
45
+
46
+ ### Support ###
47
+ Serveral updates are in the works but feel free to report issues, make comments, voice concerns and I'll get to them when I can! Enjoy!
data/Rakefile ADDED
@@ -0,0 +1,14 @@
1
+ require 'rubygems'
2
+ require 'rake'
3
+ require 'rake/testtask'
4
+
5
+ desc "Run all our tests"
6
+ task :test do
7
+ Rake::TestTask.new do |t|
8
+ t.libs << "test"
9
+ t.pattern = "test/**/*_test.rb"
10
+ t.verbose = false
11
+ end
12
+ end
13
+
14
+ task :default => :test
@@ -0,0 +1,17 @@
1
+ Gem::Specification.new do |spec|
2
+ spec.name = "core_image"
3
+ spec.version = "0.0.3"
4
+ spec.date = "2012-01-31"
5
+ spec.summary = "Manipulate images via Apple's built-in Core Image technology"
6
+ spec.description = "Simple image manipulation using Apple's Core Image technology. Scale, rotate, flip, convert, overlay, and more."
7
+ spec.author = "Spencer Rogers"
8
+ spec.email = "SpencerRogers@gmail.com"
9
+ spec.files = `git ls-files`.split("\n")
10
+ spec.test_files = ['test/core_image_test.rb']
11
+ spec.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
12
+ spec.require_paths = ["lib"]
13
+ spec.add_development_dependency "rubygems"
14
+ spec.add_development_dependency "riot"
15
+ spec.homepage = "https://github.com/serogers/core_image"
16
+ spec.requirements << ["Mac OS X"]
17
+ end
data/lib/core_image.rb CHANGED
@@ -4,6 +4,7 @@
4
4
 
5
5
  class CoreImage
6
6
  require 'osx/cocoa'
7
+ require 'uri'
7
8
 
8
9
  attr_accessor :image_path
9
10
  attr_accessor :original_image
@@ -24,29 +25,31 @@ class CoreImage
24
25
  scale(dimensions[:ratio])
25
26
  end
26
27
 
27
- def rotate(degrees = 0)
28
+ def rotate(degrees = 90)
28
29
  radians = degrees_to_radians(degrees)
29
30
  transform = OSX::CGAffineTransformMakeRotation(radians)
30
31
  self.ciimage = self.ciimage.imageByApplyingTransform(transform)
31
32
  self
32
33
  end
33
34
 
34
- def flip_horizontal
35
+ def flip_horizontally
35
36
  transform = OSX::CGAffineTransformMakeScale(-1.0, 1.0)
36
37
  self.ciimage = self.ciimage.imageByApplyingTransform(transform)
37
38
  self
38
39
  end
39
40
 
40
- def crop(x, y, w, h)
41
- # coordinates start in lower left
41
+ def flip_vertically
42
+ flip_horizontally.rotate(180)
43
+ end
44
+
45
+ def crop(x, y, w, h) # coordinates start in lower left
42
46
  self.ciimage = self.ciimage.imageByCroppingToRect(OSX::CGRectMake(x, y, w, h))
43
47
  self
44
48
  end # crop
45
49
 
46
- def tint(rgb_string)
47
- image_size = size
48
- context = create_ci_context(image_size[:width], image_size[:height])
49
- colored_image = OSX::CIImage.imageWithColor(OSX::CIColor.colorWithString(rgb_string))
50
+ def tint(rgb)
51
+ context = set_context
52
+ colored_image = OSX::CIImage.imageWithColor(OSX::CIColor.colorWithString(rgb_hash_to_string(rgb)))
50
53
  filter = OSX::CIFilter.filterWithName("CIMultiplyCompositing")
51
54
  filter.setValue_forKey(colored_image, "inputImage")
52
55
  filter.setValue_forKey(self.ciimage, "inputBackgroundImage")
@@ -56,10 +59,9 @@ class CoreImage
56
59
  self
57
60
  end
58
61
 
59
- def overlay_image(object)
62
+ def overlay(object)
60
63
  image = open_object(object)
61
- image_size = size
62
- context = create_ci_context(image_size[:width], image_size[:height])
64
+ context = set_context
63
65
  filter = OSX::CIFilter.filterWithName("CISourceOverCompositing")
64
66
  filter.setValue_forKey(image, "inputImage")
65
67
  filter.setValue_forKey(self.ciimage, "inputBackgroundImage")
@@ -69,8 +71,15 @@ class CoreImage
69
71
  self
70
72
  end
71
73
 
72
- def color_at(x, y)
73
- to_bitmap.colorAtX_y(x, y) # returns NSColor
74
+ def color_at(x, y) # coordinates start in upper left
75
+ set_context
76
+ nscolor = to_bitmap.colorAtX_y(x, y)
77
+ rgb = {}
78
+ rgb[:red] = (nscolor.redComponent.to_f * 255.0).to_i
79
+ rgb[:green] = (nscolor.greenComponent.to_f * 255.0).to_i
80
+ rgb[:blue] = (nscolor.blueComponent.to_f * 255.0).to_i
81
+ rgb[:alpha] = nscolor.alphaComponent
82
+ rgb
74
83
  end
75
84
 
76
85
  def to_bitmap
@@ -81,24 +90,31 @@ class CoreImage
81
90
  to_bitmap.CGImage
82
91
  end
83
92
 
93
+ def to_nsimage
94
+ image_size = self.size
95
+ nsimage_rep = OSX::NSCIImageRep.imageRepWithCIImage(self.ciimage)
96
+ nsimage = OSX::NSImage.alloc.initWithSize(OSX::NSMakeSize(image_size[:width], image_size[:height]))
97
+ nsimage.addRepresentation(nsimage_rep)
98
+ nsimage
99
+ end
100
+
84
101
  def size
85
102
  width, height = 0, 0
86
103
 
87
104
  begin # sometimes ciimage.extent throws an error
88
- size = self.ciimage.extent.size
89
- width = size.width
90
- height = size.height
105
+ image_size = self.ciimage.extent.size
106
+ width = image_size.width
107
+ height = image_size.height
91
108
  rescue
92
- # if image.extent fails, use another method for measuring size (by converting to a cgimage)
93
109
  begin
94
110
  cgimage = self.to_cgimage
95
111
  width = OSX::CGImageGetWidth(cgimage)
96
112
  height = OSX::CGImageGetHeight(cgimage)
97
- rescue # if the second image size function fails set zero
113
+ rescue
98
114
  width = 0
99
115
  height = 0
100
- end # begin/rescue
101
- end # begin/rescue
116
+ end
117
+ end
102
118
 
103
119
  {:width => width, :height => height}
104
120
  end
@@ -136,18 +152,30 @@ class CoreImage
136
152
  def open_object(object)
137
153
  case object.class.to_s
138
154
  when "String"
139
- ciimage = File.extname(object).downcase == ".pdf" ? open_from_pdf_path(object) : open_from_path(object)
155
+ open_from_location(object)
140
156
  when "OSX::CIImage"
141
- ciimage = object
157
+ object
158
+ when "OSX::CGImage", "OSX::NSObject"
159
+ OSX::CIImage.imageWithCGImage(object)
160
+ when "OSX::NSImage"
161
+ tiff_data = object.TIFFRepresentation
162
+ OSX::CIImage.imageWithData(tiff_data)
163
+ when "CoreImage"
164
+ object.ciimage
142
165
  end
143
166
  end
144
167
 
145
- def open_from_path(path_to_image)
146
- OSX::CIImage.imageWithContentsOfURL(OSX::NSURL.fileURLWithPath(path_to_image))
147
- end # open
168
+ def open_from_location(string)
169
+ nsurl = valid_url?(string) ? nsurl_from_url(string) : nsurl_from_path(string)
170
+ File.extname(string).downcase == ".pdf" ? open_from_pdf(nsurl) : open_from_image_file(nsurl)
171
+ end
172
+
173
+ def open_from_image_file(nsurl)
174
+ OSX::CIImage.imageWithContentsOfURL(nsurl)
175
+ end
148
176
 
149
- def open_from_pdf_path(pdf_path, scale = 1, dpi = 72.0, preserve_alpha = false)
150
- data = OSX::NSData.dataWithContentsOfURL(OSX::NSURL.fileURLWithPath(pdfPath))
177
+ def open_from_pdf(nsurl, scale = 1, dpi = 72.0, preserve_alpha = false)
178
+ data = OSX::NSData.dataWithContentsOfURL(nsurl)
151
179
  pdf_rep = OSX::NSPDFImageRep.imageRepWithData(data)
152
180
  nsimage = OSX::NSImage.alloc.initWithData(data)
153
181
  nssize = nsimage.size
@@ -174,7 +202,7 @@ class CoreImage
174
202
  cgimage = OSX::CGBitmapContextCreateImage(context.graphicsPort)
175
203
 
176
204
  # return a ciimage
177
- OSX::CIImage.initWithCGImage(cgimage)
205
+ OSX::CIImage.imageWithCGImage(cgimage)
178
206
  end
179
207
 
180
208
  def degrees_to_radians(degrees)
@@ -198,7 +226,11 @@ class CoreImage
198
226
  when ".bmp"
199
227
  OSX::NSBMPFileType
200
228
  end
201
-
229
+ end
230
+
231
+ def set_context
232
+ image_size = size
233
+ create_ci_context(image_size[:width], image_size[:height])
202
234
  end
203
235
 
204
236
  def create_ci_context(width, height)
@@ -206,10 +238,30 @@ class CoreImage
206
238
  end
207
239
 
208
240
  def create_ns_context(width, height)
209
- blank_bitmap = OSX::NSBitmapImageRep.alloc.initWithBitmapDataPlanes_pixelsWide_pixelsHigh_bitsPerSample_samplesPerPixel_hasAlpha_isPlanar_colorSpaceName_bytesPerRow_bitsPerPixel(nil, width, height, 8, 4, true, false, OSX::NSDeviceRGBColorSpace, 0, 0)
241
+ blank_bitmap = OSX::NSBitmapImageRep.alloc.initWithBitmapDataPlanes_pixelsWide_pixelsHigh_bitsPerSample_samplesPerPixel_hasAlpha_isPlanar_colorSpaceName_bytesPerRow_bitsPerPixel(nil, width, height, 8, 4, true, false, OSX::NSCalibratedRGBColorSpace, 0, 0)
210
242
  context = OSX::NSGraphicsContext.graphicsContextWithBitmapImageRep(blank_bitmap)
211
243
  OSX::NSGraphicsContext.setCurrentContext(context)
212
244
  context
213
- end # createContext
245
+ end
214
246
 
247
+ def rgb_hash_to_string(rgb)
248
+ rgb[:alpha] = 1.0 if rgb[:alpha].nil?
249
+ "#{rgb[:red].to_f / 255.0} #{rgb[:green].to_f / 255.0} #{rgb[:blue].to_f / 255.0} #{rgb[:alpha].to_f}"
250
+ end
251
+
252
+ def valid_url?(string)
253
+ uri = URI.parse(string)
254
+ %w( http https ).include?(uri.scheme) and uri.path.nil? == false
255
+ rescue URI::BadURIError
256
+ false
257
+ end
258
+
259
+ def nsurl_from_path(path)
260
+ OSX::NSURL.fileURLWithPath(path)
261
+ end
262
+
263
+ def nsurl_from_url(url)
264
+ OSX::NSURL.URLWithString(url)
265
+ end
266
+
215
267
  end
@@ -0,0 +1,103 @@
1
+ require 'rubygems'
2
+ require 'riot'
3
+ require './lib/core_image.rb'
4
+
5
+ context 'Core Image' do
6
+ setup {CoreImage.new("./test/images/test.png")}
7
+ helper(:solid_blue_rgb) { {:red => 0, :green => 0, :blue => 255, :alpha => 1.0} }
8
+
9
+ context 'opens file' do
10
+ asserts("Opens image file") {topic.ciimage.nil? == false}
11
+ asserts("Opens pdf file") {CoreImage.new("./test/images/test.pdf").ciimage.nil? == false}
12
+ asserts("Opens file from the internet") {CoreImage.new("http://dl.dropbox.com/u/1848018/core_image_test.png").ciimage.nil? == false}
13
+ end
14
+
15
+ context 'opens apple image object' do
16
+ asserts("Opens ciimage") {CoreImage.new(topic.ciimage).ciimage.class.to_s == "OSX::CIImage"}
17
+ asserts("Opens cgimage") {CoreImage.new(topic.to_cgimage).ciimage.class.to_s == "OSX::CIImage"}
18
+ asserts("Opens nsimage") {CoreImage.new(topic.to_nsimage).ciimage.class.to_s == "OSX::CIImage"}
19
+ end
20
+
21
+ context 'opens image from self' do
22
+ asserts("Opens from core_image instantiation") {CoreImage.new(topic).ciimage.class.to_s == "OSX::CIImage"}
23
+ end
24
+
25
+ context '.scale(1.5)' do
26
+ asserts("Image was scaled up by 150%") {topic.scale(1.5).size == {:width => 75, :height => 75}}
27
+ end
28
+
29
+ context '.scale(0.6)' do
30
+ asserts("Image was scaled down by 60%") {topic.scale(0.6).size == {:width => 30, :height => 30}}
31
+ end
32
+
33
+ context '.scale_to_size(300)' do
34
+ asserts("Image was scaled to fit within 500px") {topic.scale_to_size(300).size == {:width => 300, :height => 300}}
35
+ end
36
+
37
+ context '.rotate(90)' do
38
+ setup {topic.rotate(90).color_at(10, 40)}
39
+ asserts("Image was rotated by 90 degrees") {topic == solid_blue_rgb}
40
+ end
41
+
42
+ context '.rotate(-90)' do
43
+ setup {topic.rotate(-90).color_at(40, 10)}
44
+ asserts("Image was rotated by -90 degrees") {topic == solid_blue_rgb}
45
+ end
46
+
47
+ context '.flip_horizontally' do
48
+ setup {topic.flip_horizontally.color_at(40, 10)}
49
+ asserts("Image was flipped on its x-axis") {topic == solid_blue_rgb}
50
+ end
51
+
52
+ context '.flip_vertically' do
53
+ setup {topic.flip_vertically.color_at(10, 40)}
54
+ asserts("Image was flipped on its y-axis") {topic == solid_blue_rgb}
55
+ end
56
+
57
+ context '.crop' do
58
+ setup {topic.crop(0, 0, 25, 25)}
59
+ asserts("Image was cropped to a 25 pixel box starting in lower left") {topic.size == {:width => 25, :height => 25} and topic.color_at(10, 2) == {:red => 0, :green => 128, :blue => 0, :alpha => 1.0}}
60
+ end
61
+
62
+ context '.tint' do
63
+ setup {topic.tint(solid_blue_rgb)}
64
+ asserts("Image was tinted a dark blue") {topic.color_at(40, 10) == solid_blue_rgb}
65
+ end
66
+
67
+ context '.overlay' do
68
+ setup {topic.overlay(CoreImage.new("./test/images/overlay.png")).color_at(40, 10) == solid_blue_rgb}
69
+ end
70
+
71
+ context '.color_at' do
72
+ setup {topic.color_at(10, 10)} # select pixel from blue area
73
+ asserts("Blue equals R:0, G:0, B:255, A:1.0") {topic == solid_blue_rgb}
74
+ end
75
+
76
+ context '.to_bitmap' do
77
+ setup {topic.to_bitmap}
78
+ asserts("Is a bitmap") {topic.class.to_s == "OSX::NSBitmapImageRep"}
79
+ end
80
+
81
+ context '.to_cgimage' do
82
+ setup {topic.to_cgimage}
83
+ asserts("Is a cgimage") {topic.class.to_s == "OSX::NSObject"}
84
+ end
85
+
86
+ context '.to_nsimage' do
87
+ setup {topic.to_nsimage}
88
+ asserts("Is a nsimage") {topic.class.to_s == "OSX::NSImage"}
89
+ end
90
+
91
+ context '.size' do
92
+ asserts("Size equals 50x50") {topic.size == {:width => 50, :height => 50}}
93
+ end
94
+
95
+ context '.size_to_fit_maximum(350)' do
96
+ asserts("Returns 350x350 ratio of 7.0") {topic.size_to_fit_maximum(350) == {:height=>350, :width=>350, :ratio=>7.0}}
97
+ end
98
+
99
+ context '.revert' do
100
+ asserts("Image reverts to original") {topic.ciimage = topic.flip_horizontally.revert.ciimage}
101
+ end
102
+
103
+ end
Binary file
Binary file
Binary file
metadata CHANGED
@@ -1,13 +1,13 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: core_image
3
3
  version: !ruby/object:Gem::Version
4
- hash: 27
4
+ hash: 25
5
5
  prerelease:
6
6
  segments:
7
7
  - 0
8
8
  - 0
9
- - 2
10
- version: 0.0.2
9
+ - 3
10
+ version: 0.0.3
11
11
  platform: ruby
12
12
  authors:
13
13
  - Spencer Rogers
@@ -15,10 +15,37 @@ autorequire:
15
15
  bindir: bin
16
16
  cert_chain: []
17
17
 
18
- date: 2012-01-24 00:00:00 -08:00
18
+ date: 2012-01-31 00:00:00 -08:00
19
19
  default_executable:
20
- dependencies: []
21
-
20
+ dependencies:
21
+ - !ruby/object:Gem::Dependency
22
+ name: rubygems
23
+ prerelease: false
24
+ requirement: &id001 !ruby/object:Gem::Requirement
25
+ none: false
26
+ requirements:
27
+ - - ">="
28
+ - !ruby/object:Gem::Version
29
+ hash: 3
30
+ segments:
31
+ - 0
32
+ version: "0"
33
+ type: :development
34
+ version_requirements: *id001
35
+ - !ruby/object:Gem::Dependency
36
+ name: riot
37
+ prerelease: false
38
+ requirement: &id002 !ruby/object:Gem::Requirement
39
+ none: false
40
+ requirements:
41
+ - - ">="
42
+ - !ruby/object:Gem::Version
43
+ hash: 3
44
+ segments:
45
+ - 0
46
+ version: "0"
47
+ type: :development
48
+ version_requirements: *id002
22
49
  description: Simple image manipulation using Apple's Core Image technology. Scale, rotate, flip, convert, overlay, and more.
23
50
  email: SpencerRogers@gmail.com
24
51
  executables: []
@@ -28,9 +55,18 @@ extensions: []
28
55
  extra_rdoc_files: []
29
56
 
30
57
  files:
58
+ - .gitignore
59
+ - LICENSE
60
+ - README.md
61
+ - Rakefile
62
+ - core_image.gemspec
31
63
  - lib/core_image.rb
64
+ - test/core_image_test.rb
65
+ - test/images/overlay.png
66
+ - test/images/test.pdf
67
+ - test/images/test.png
32
68
  has_rdoc: true
33
- homepage: https://github.com/serogers
69
+ homepage: https://github.com/serogers/core_image
34
70
  licenses: []
35
71
 
36
72
  post_install_message:
@@ -62,6 +98,6 @@ rubyforge_project:
62
98
  rubygems_version: 1.5.3
63
99
  signing_key:
64
100
  specification_version: 3
65
- summary: Manipulate images via Apple's Core Image technology
66
- test_files: []
67
-
101
+ summary: Manipulate images via Apple's built-in Core Image technology
102
+ test_files:
103
+ - test/core_image_test.rb