miso 0.1.1 → 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (38) hide show
  1. data/Rakefile +1 -1
  2. data/TODO +6 -2
  3. data/VERSION +1 -1
  4. data/lib/miso.rb +9 -0
  5. data/lib/miso/image.rb +8 -0
  6. data/lib/miso/processor.rb +10 -3
  7. data/lib/miso/processor/core_image.rb +83 -8
  8. data/miso.gemspec +11 -26
  9. data/spec/api/factory_spec.rb +1 -1
  10. data/spec/api/image_spec.rb +19 -9
  11. data/spec/api/processor/core_image_spec.rb +29 -0
  12. data/spec/api/processor_spec.rb +10 -2
  13. data/spec/functional/processor/core_image_spec.rb +43 -0
  14. data/spec/functional/processor_spec.rb +14 -0
  15. data/spec/start.rb +12 -0
  16. metadata +8 -24
  17. data/html/classes/Miso.html +0 -133
  18. data/html/classes/Miso/Factory.html +0 -278
  19. data/html/classes/Miso/Image.html +0 -394
  20. data/html/classes/Miso/Processor.html +0 -379
  21. data/html/classes/Miso/Processor/CoreImage.html +0 -246
  22. data/html/classes/Miso/Processor/ImageMagick.html +0 -345
  23. data/html/classes/Miso/Processor/NotImplementedError.html +0 -111
  24. data/html/created.rid +0 -1
  25. data/html/files/LICENSE.html +0 -133
  26. data/html/files/README.html +0 -156
  27. data/html/files/lib/miso/factory_rb.html +0 -101
  28. data/html/files/lib/miso/image_rb.html +0 -101
  29. data/html/files/lib/miso/processor/core_image_rb.html +0 -108
  30. data/html/files/lib/miso/processor/image_magick_rb.html +0 -110
  31. data/html/files/lib/miso/processor_rb.html +0 -101
  32. data/html/files/lib/miso_rb.html +0 -101
  33. data/html/fr_class_index.html +0 -33
  34. data/html/fr_file_index.html +0 -34
  35. data/html/fr_method_index.html +0 -62
  36. data/html/index.html +0 -24
  37. data/html/rdoc-style.css +0 -208
  38. data/pkg/miso-0.1.0.gem +0 -0
data/Rakefile CHANGED
@@ -40,7 +40,7 @@ begin
40
40
  s.email = ["eloy@fngtps.com", "manfred@fngtps.com"]
41
41
  s.authors = ["Eloy Duran", "Manfred Stienstra"]
42
42
  s.summary = s.description = "Miso is a unified API for simple image operations commonly used on the web."
43
- s.files = FileList['**/**'] # tmp until we've patched Jeweler to be able to easily add files to defaults
43
+ s.files -= %w{ .gitignore .kick }
44
44
  s.add_dependency('executioner', '>= 0.2.0')
45
45
  end
46
46
  rescue LoadError
data/TODO CHANGED
@@ -1,2 +1,6 @@
1
- - Add Miso::Image#add(image, 10, 10), which adds another image on top of the image. This should be implemented by the processor as well.
2
- - Add Miso::Image#watermark(image, :southwest, 10, 10), which uses Miso::Image#add to add an image in one of the corners.
1
+ - Add Miso::Image#add(image, 10, 10), which adds another image on top of the
2
+ image. This should be implemented by the processor as well.
3
+ - Add Miso::Image#watermark(image, :southwest, 10, 10), which uses
4
+ Miso::Image#add to add an image in one of the corners.
5
+ - Right now the processors clear the operation buffer after writing, which
6
+ means it's impossible to write out the same operations to multiple files.
data/VERSION CHANGED
@@ -1 +1 @@
1
- 0.1.1
1
+ 0.2.0
data/lib/miso.rb CHANGED
@@ -2,4 +2,13 @@ module Miso
2
2
  autoload :Factory, 'miso/factory'
3
3
  autoload :Image, 'miso/image'
4
4
  autoload :Processor, 'miso/processor'
5
+
6
+ class MisoError < StandardError; end
7
+ class NotImplementedError < MisoError; end
8
+ class UnsupportedFileType < MisoError
9
+ def initialize(path)
10
+ ext = File.extname(path)[1..-1]
11
+ super("Miso does not support file type `#{ext}' of `#{path}'")
12
+ end
13
+ end
5
14
  end
data/lib/miso/image.rb CHANGED
@@ -23,6 +23,14 @@ module Miso
23
23
  @processor.dimensions
24
24
  end
25
25
 
26
+ def width
27
+ @processor.width
28
+ end
29
+
30
+ def height
31
+ @processor.height
32
+ end
33
+
26
34
  def write(output_file)
27
35
  @processor.write(output_file)
28
36
  self.class.new(output_file, @processor.class)
@@ -3,8 +3,6 @@ module Miso
3
3
  autoload :CoreImage, 'miso/processor/core_image'
4
4
  autoload :ImageMagick, 'miso/processor/image_magick'
5
5
 
6
- class NotImplementedError < StandardError; end
7
-
8
6
  class << self
9
7
  # Sets the default processor class.
10
8
  attr_writer :processor_class
@@ -39,7 +37,8 @@ module Miso
39
37
  attr_reader :input_file
40
38
 
41
39
  def initialize(input_file)
42
- @input_file = input_file
40
+ @input_file = File.expand_path(input_file)
41
+ raise Errno::ENOENT, @input_file unless File.exist?(@input_file)
43
42
  end
44
43
 
45
44
  def crop(width, height)
@@ -57,5 +56,13 @@ module Miso
57
56
  def write(output_file)
58
57
  raise NotImplementedError, "The class `#{self.class.name}' does not implement #write."
59
58
  end
59
+
60
+ def width
61
+ dimensions.first
62
+ end
63
+
64
+ def height
65
+ dimensions.last
66
+ end
60
67
  end
61
68
  end
@@ -1,26 +1,101 @@
1
+ # Based on code from HotCocoa and: http://redartisan.com/2007/12/12/attachment-fu-with-core-image
1
2
  module Miso
2
3
  class Processor
3
4
  class CoreImage < Processor
4
5
  def self.available?
5
- available = $:.any? { |path| File.exist? File.join(path, 'osx/cocoa.rb') }
6
+ $:.any? { |path| File.exist? File.join(path, 'osx/cocoa.rb') }
7
+ end
8
+
9
+ def initialize(input_file)
10
+ super
6
11
  require 'osx/cocoa'
7
- available
12
+ reset!
13
+ end
14
+
15
+ def dimensions
16
+ @dimensions ||= image.extent.size.to_a
8
17
  end
9
18
 
10
19
  def crop(width, height)
11
- # ...
20
+ scale_width, scale_height = scale(width, height)
21
+ multiplier = scale_width > scale_height ? scale_width : scale_height
22
+ transform(multiplier)
23
+
24
+ # find the center and calculate the new bottom right from there
25
+ x = ((buffer_width.to_f - width.to_f) / 2).round.abs
26
+ y = ((buffer_height.to_f - height.to_f) / 2).round.abs
27
+ _crop(x, y, width, height)
12
28
  end
13
29
 
14
30
  def fit(width, height)
15
- # ...
31
+ scale_width, scale_height = scale(width, height)
32
+ multiplier = scale_width < scale_height ? scale_width : scale_height
33
+ new_width = (self.width * multiplier).round
34
+ new_height = (self.height * multiplier).round
35
+
36
+ transform(multiplier)
37
+ _crop(0, 0, new_width, new_height)
16
38
  end
17
39
 
18
- def dimensions
19
- # ...
40
+ def write(output_file)
41
+ bitmap = OSX::NSBitmapImageRep.alloc.initWithCIImage(@buffer)
42
+ blob = bitmap.representationUsingType_properties(detect_file_type(output_file), nil)
43
+ blob.writeToFile_atomically(output_file, false)
44
+ reset!
20
45
  end
21
46
 
22
- def write(output_file)
23
- # ...
47
+ private
48
+
49
+ def image
50
+ OSX::CIImage.imageWithContentsOfURL(OSX::NSURL.fileURLWithPath(@input_file))
51
+ end
52
+
53
+ def reset!
54
+ @buffer = image
55
+ end
56
+
57
+ def buffer_width
58
+ @buffer.extent.size.width
59
+ end
60
+
61
+ def buffer_height
62
+ @buffer.extent.size.height
63
+ end
64
+
65
+ def apply_filter(name, options)
66
+ filter = OSX::CIFilter.filterWithName(name)
67
+ filter.setDefaults
68
+ options.merge('inputImage' => @buffer).each do |name, value|
69
+ filter.setValue_forKey(value, name)
70
+ end
71
+ @buffer = filter.valueForKey('outputImage')
72
+ end
73
+
74
+ def scale(width, height)
75
+ [width.to_f / buffer_width, height.to_f / buffer_height]
76
+ end
77
+
78
+ def transform(multiplier)
79
+ apply_filter 'CILanczosScaleTransform',
80
+ 'inputScale' => multiplier,
81
+ 'inputAspectRatio' => 1.0
82
+ end
83
+
84
+ def _crop(x, y, width, height)
85
+ apply_filter 'CICrop', 'inputRectangle' => OSX::CIVector.vectorWithX_Y_Z_W(x, y, width, height)
86
+ end
87
+
88
+ def detect_file_type(path)
89
+ case File.extname(path)
90
+ when '.png'
91
+ OSX::NSPNGFileType
92
+ when '.jpg'
93
+ OSX::NSJPEGFileType
94
+ when '.gif'
95
+ OSX::NSGIFFileType
96
+ else
97
+ raise UnsupportedFileType.new(path)
98
+ end
24
99
  end
25
100
  end
26
101
  end
data/miso.gemspec CHANGED
@@ -1,15 +1,15 @@
1
1
  # Generated by jeweler
2
- # DO NOT EDIT THIS FILE
3
- # Instead, edit Jeweler::Tasks in Rakefile, and run `rake gemspec`
2
+ # DO NOT EDIT THIS FILE DIRECTLY
3
+ # Instead, edit Jeweler::Tasks in Rakefile, and run the gemspec command
4
4
  # -*- encoding: utf-8 -*-
5
5
 
6
6
  Gem::Specification.new do |s|
7
7
  s.name = %q{miso}
8
- s.version = "0.1.1"
8
+ s.version = "0.2.0"
9
9
 
10
10
  s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
11
11
  s.authors = ["Eloy Duran", "Manfred Stienstra"]
12
- s.date = %q{2009-10-20}
12
+ s.date = %q{2009-10-21}
13
13
  s.description = %q{Miso is a unified API for simple image operations commonly used on the web.}
14
14
  s.email = ["eloy@fngtps.com", "manfred@fngtps.com"]
15
15
  s.extra_rdoc_files = [
@@ -22,27 +22,6 @@ Gem::Specification.new do |s|
22
22
  "Rakefile",
23
23
  "TODO",
24
24
  "VERSION",
25
- "html/classes/Miso.html",
26
- "html/classes/Miso/Factory.html",
27
- "html/classes/Miso/Image.html",
28
- "html/classes/Miso/Processor.html",
29
- "html/classes/Miso/Processor/CoreImage.html",
30
- "html/classes/Miso/Processor/ImageMagick.html",
31
- "html/classes/Miso/Processor/NotImplementedError.html",
32
- "html/created.rid",
33
- "html/files/LICENSE.html",
34
- "html/files/README.html",
35
- "html/files/lib/miso/factory_rb.html",
36
- "html/files/lib/miso/image_rb.html",
37
- "html/files/lib/miso/processor/core_image_rb.html",
38
- "html/files/lib/miso/processor/image_magick_rb.html",
39
- "html/files/lib/miso/processor_rb.html",
40
- "html/files/lib/miso_rb.html",
41
- "html/fr_class_index.html",
42
- "html/fr_file_index.html",
43
- "html/fr_method_index.html",
44
- "html/index.html",
45
- "html/rdoc-style.css",
46
25
  "lib/miso.rb",
47
26
  "lib/miso/factory.rb",
48
27
  "lib/miso/image.rb",
@@ -50,12 +29,14 @@ Gem::Specification.new do |s|
50
29
  "lib/miso/processor/core_image.rb",
51
30
  "lib/miso/processor/image_magick.rb",
52
31
  "miso.gemspec",
53
- "pkg/miso-0.1.0.gem",
54
32
  "spec/api/factory_spec.rb",
55
33
  "spec/api/image_spec.rb",
34
+ "spec/api/processor/core_image_spec.rb",
56
35
  "spec/api/processor/image_magick_spec.rb",
57
36
  "spec/api/processor_spec.rb",
58
37
  "spec/fixtures/120x100.png",
38
+ "spec/functional/processor/core_image_spec.rb",
39
+ "spec/functional/processor_spec.rb",
59
40
  "spec/start.rb"
60
41
  ]
61
42
  s.homepage = %q{http://github.com/Fingertips/miso}
@@ -66,8 +47,11 @@ Gem::Specification.new do |s|
66
47
  s.test_files = [
67
48
  "spec/api/factory_spec.rb",
68
49
  "spec/api/image_spec.rb",
50
+ "spec/api/processor/core_image_spec.rb",
69
51
  "spec/api/processor/image_magick_spec.rb",
70
52
  "spec/api/processor_spec.rb",
53
+ "spec/functional/processor/core_image_spec.rb",
54
+ "spec/functional/processor_spec.rb",
71
55
  "spec/start.rb"
72
56
  ]
73
57
 
@@ -84,3 +68,4 @@ Gem::Specification.new do |s|
84
68
  s.add_dependency(%q<executioner>, [">= 0.2.0"])
85
69
  end
86
70
  end
71
+
@@ -6,7 +6,7 @@ describe "A Miso::Factory instance" do
6
6
 
7
7
  Miso::Processor.any_instance.stubs(:write)
8
8
 
9
- @input_file = '/image.png'
9
+ @input_file = fixture_file('120x100.png')
10
10
  @output_file = '/output_image.png'
11
11
  @image = Miso::Image.new(@input_file)
12
12
 
@@ -13,16 +13,16 @@ describe "Miso::Image, concerning initialization" do
13
13
 
14
14
  it "should initialize with only a file argument and use the default processor class" do
15
15
  Miso::Processor.processor_class = Miso::Processor::ImageMagick
16
- image = Miso::Image.new('/image.png')
16
+ image = Miso::Image.new(fixture_file('120x100.png'))
17
17
 
18
- image.processor.input_file.should == '/image.png'
18
+ image.processor.input_file.should == fixture_file('120x100.png')
19
19
  image.processor.class.should == Miso::Processor::ImageMagick
20
20
  end
21
21
 
22
22
  it "should initialize with a file and processor class" do
23
- image = Miso::Image.new('/image.png', Miso::Processor::ImageMagick)
23
+ image = Miso::Image.new(fixture_file('120x100.png'), Miso::Processor::ImageMagick)
24
24
 
25
- image.processor.input_file.should == '/image.png'
25
+ image.processor.input_file.should == fixture_file('120x100.png')
26
26
  image.processor.class.should == Miso::Processor::ImageMagick
27
27
  end
28
28
  end
@@ -32,7 +32,7 @@ describe "An instance of Miso::Image, concerning forwarding calls to the process
32
32
  Miso::Processor.any_instance.stubs(:fit)
33
33
  Miso::Processor.any_instance.stubs(:crop)
34
34
 
35
- @image = Miso::Image.new('/image.png', Miso::Processor)
35
+ @image = Miso::Image.new(fixture_file('120x100.png'), Miso::Processor)
36
36
  end
37
37
 
38
38
  it "should forward #crop to the processor" do
@@ -58,10 +58,20 @@ describe "An instance of Miso::Image, concerning forwarding calls to the process
58
58
  @image.dimensions.should == [123, 456]
59
59
  end
60
60
 
61
+ it "should forward #width and #height to the processor and return the result" do
62
+ @image.processor.expects(:width).returns(123)
63
+ @image.width.should == 123
64
+ end
65
+
66
+ it "should forward #height to the processor and return the result" do
67
+ @image.processor.expects(:height).returns(456)
68
+ @image.height.should == 456
69
+ end
70
+
61
71
  it "should forward #write to the processor and forward its output file to the new instance of Miso::Image" do
62
- @image.processor.expects(:write).with('/output_image.png')
63
- output_image = @image.write('/output_image.png')
64
- output_image.processor.input_file.should == '/output_image.png'
72
+ @image.processor.expects(:write).with(fixture_file('120x100.png'))
73
+ output_image = @image.write(fixture_file('120x100.png'))
74
+ output_image.processor.input_file.should == fixture_file('120x100.png')
65
75
  end
66
76
  end
67
77
 
@@ -70,7 +80,7 @@ describe "An instance of Miso::Image, concerning combined methods" do
70
80
  Miso::Processor.any_instance.stubs(:fit)
71
81
  Miso::Processor.any_instance.stubs(:crop)
72
82
 
73
- @image = Miso::Image.new('/image.png', Miso::Processor)
83
+ @image = Miso::Image.new(fixture_file('120x100.png'), Miso::Processor)
74
84
  end
75
85
 
76
86
  it "should call #fit to scale and preserve aspect ratio, then call #crop" do
@@ -0,0 +1,29 @@
1
+ require File.expand_path('../../../start', __FILE__)
2
+
3
+ begin
4
+ require 'osx/cocoa'
5
+
6
+ describe "An instance of Miso::Processor::CoreImage" do
7
+ before do
8
+ @image_120_x_100 = Miso::Image.new(fixture_file('120x100.png'), Miso::Processor::CoreImage)
9
+ @output_file = temp_file('temp.png')
10
+ end
11
+
12
+ it "should crop to specified dimensions" do
13
+ @image_120_x_100.crop(40, 30).write(@output_file).dimensions.should == [40, 30]
14
+ @image_120_x_100.crop(40, 33).write(@output_file).dimensions.should == [40, 33]
15
+ end
16
+
17
+ it "should fit to specified dimensions, conserving the original aspect ratio" do
18
+ @image_120_x_100.fit(40, 30).write(@output_file).dimensions.should == [36, 30]
19
+ @image_120_x_100.fit(40, 34).write(@output_file).dimensions.should == [40, 33]
20
+ end
21
+
22
+ it "should return its dimensions" do
23
+ @image_120_x_100.dimensions.should == [120, 100]
24
+ end
25
+ end
26
+
27
+ rescue LoadError
28
+ warn "[!] Skipping Miso::Processor::CoreImage API spec."
29
+ end
@@ -35,7 +35,15 @@ describe "Miso::Processor" do
35
35
  }.should.raise
36
36
  end
37
37
 
38
- it "should initialize with an input file" do
39
- Miso::Processor.new('/image.png').input_file.should == '/image.png'
38
+ it "should initialize with an input file path" do
39
+ processor = Miso::Processor.new(fixture_file('120x100.png'))
40
+ processor.input_file.should == fixture_file('120x100.png')
41
+ end
42
+
43
+ it "should returns the width and height" do
44
+ processor = Miso::Processor.new(fixture_file('120x100.png'))
45
+ processor.stubs(:dimensions).returns([120, 100])
46
+ processor.width.should == 120
47
+ processor.height.should == 100
40
48
  end
41
49
  end
@@ -0,0 +1,43 @@
1
+ require File.expand_path('../../../start', __FILE__)
2
+
3
+ begin
4
+ require 'osx/cocoa'
5
+
6
+ describe "Miso::Processor::CoreImage" do
7
+ it "should check the load paths to see if RubyCocoa is available" do
8
+ Miso::Processor::CoreImage.should.be.available
9
+ with_load_path '/tmp', '~/' do
10
+ Miso::Processor::CoreImage.should.not.be.available
11
+ end
12
+ end
13
+
14
+ it "should require osx/cocoa on initialization" do
15
+ Miso::Processor::CoreImage.any_instance.expects(:require).with('osx/cocoa')
16
+ Miso::Processor::CoreImage.new(fixture_file('120x100.png'))
17
+ end
18
+ end
19
+
20
+ describe "An instance of Miso::Processor::CoreImage" do
21
+ it "should write the output file with the type inflected from the extension" do
22
+ { 'png' => 'PNG', 'jpg' => 'JPEG', 'gif' => 'GIF' }.each do |ext, type|
23
+ Miso::Image.crop(input, output(ext), 100, 100, processor)
24
+ file_info(output(ext)).should.include type
25
+ end
26
+ end
27
+
28
+ it "should raise if it can't inflect the type from the extension" do
29
+ lambda {
30
+ Miso::Image.crop(input, output('foo'), 100, 100, processor)
31
+ }.should.raise Miso::UnsupportedFileType
32
+ end
33
+
34
+ private
35
+
36
+ def processor; Miso::Processor::CoreImage end
37
+ def input; fixture_file('120x100.png') end
38
+ def output(ext); temp_file("temp.#{ext}") end
39
+ end
40
+
41
+ rescue LoadError
42
+ warn "[!] Skipping Miso::Processor::CoreImage functional spec."
43
+ end
@@ -0,0 +1,14 @@
1
+ require File.expand_path('../../start', __FILE__)
2
+
3
+ describe "Miso::Processor" do
4
+ it "should expand and verify the input file path" do
5
+ raised = false
6
+ begin
7
+ Miso::Processor.new('~/image.png')
8
+ rescue Errno::ENOENT => e
9
+ raised = true
10
+ e.message.should.include File.expand_path('~/image.png')
11
+ end
12
+ raised.should.be true
13
+ end
14
+ end