miso 0.1.1 → 0.2.0
Sign up to get free protection for your applications and to get access to all the features.
- data/Rakefile +1 -1
- data/TODO +6 -2
- data/VERSION +1 -1
- data/lib/miso.rb +9 -0
- data/lib/miso/image.rb +8 -0
- data/lib/miso/processor.rb +10 -3
- data/lib/miso/processor/core_image.rb +83 -8
- data/miso.gemspec +11 -26
- data/spec/api/factory_spec.rb +1 -1
- data/spec/api/image_spec.rb +19 -9
- data/spec/api/processor/core_image_spec.rb +29 -0
- data/spec/api/processor_spec.rb +10 -2
- data/spec/functional/processor/core_image_spec.rb +43 -0
- data/spec/functional/processor_spec.rb +14 -0
- data/spec/start.rb +12 -0
- metadata +8 -24
- data/html/classes/Miso.html +0 -133
- data/html/classes/Miso/Factory.html +0 -278
- data/html/classes/Miso/Image.html +0 -394
- data/html/classes/Miso/Processor.html +0 -379
- data/html/classes/Miso/Processor/CoreImage.html +0 -246
- data/html/classes/Miso/Processor/ImageMagick.html +0 -345
- data/html/classes/Miso/Processor/NotImplementedError.html +0 -111
- data/html/created.rid +0 -1
- data/html/files/LICENSE.html +0 -133
- data/html/files/README.html +0 -156
- data/html/files/lib/miso/factory_rb.html +0 -101
- data/html/files/lib/miso/image_rb.html +0 -101
- data/html/files/lib/miso/processor/core_image_rb.html +0 -108
- data/html/files/lib/miso/processor/image_magick_rb.html +0 -110
- data/html/files/lib/miso/processor_rb.html +0 -101
- data/html/files/lib/miso_rb.html +0 -101
- data/html/fr_class_index.html +0 -33
- data/html/fr_file_index.html +0 -34
- data/html/fr_method_index.html +0 -62
- data/html/index.html +0 -24
- data/html/rdoc-style.css +0 -208
- 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
|
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
|
2
|
-
|
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
|
+
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
data/lib/miso/processor.rb
CHANGED
@@ -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
|
-
|
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
|
-
|
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
|
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
|
-
|
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
|
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.
|
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-
|
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
|
+
|
data/spec/api/factory_spec.rb
CHANGED
data/spec/api/image_spec.rb
CHANGED
@@ -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('
|
16
|
+
image = Miso::Image.new(fixture_file('120x100.png'))
|
17
17
|
|
18
|
-
image.processor.input_file.should == '
|
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('
|
23
|
+
image = Miso::Image.new(fixture_file('120x100.png'), Miso::Processor::ImageMagick)
|
24
24
|
|
25
|
-
image.processor.input_file.should == '
|
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('
|
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('
|
63
|
-
output_image = @image.write('
|
64
|
-
output_image.processor.input_file.should == '
|
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('
|
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
|
data/spec/api/processor_spec.rb
CHANGED
@@ -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('
|
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
|