miso 0.1.1 → 0.2.0

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.
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