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