akdubya-mapel 0.1.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/README.rdoc +21 -0
- data/Rakefile +62 -0
- data/lib/mapel.rb +188 -0
- data/spec/fixtures/ImageMagick.jpg +0 -0
- data/spec/mapel_spec.rb +63 -0
- data/spec/spec_helper.rb +4 -0
- metadata +60 -0
data/README.rdoc
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
== Mapel: A dead-simple image-rendering DSL
|
2
|
+
|
3
|
+
<tt>Mapel</tt> is a dead-simple, chainable image-rendering DSL for ImageMagick.
|
4
|
+
Still very much an experiment-in-progress, it supports a dozen or so essential
|
5
|
+
commands.
|
6
|
+
|
7
|
+
Example:
|
8
|
+
|
9
|
+
Mapel.render('image.jpg').resize("50%").to('output.jpg').run
|
10
|
+
|
11
|
+
== Fun Fact
|
12
|
+
|
13
|
+
<tt>Mapel</tt> is named after a tortoiseshell cat.
|
14
|
+
|
15
|
+
== Meta
|
16
|
+
|
17
|
+
Written by Aleks Williams (http://github.com/akdubya)
|
18
|
+
|
19
|
+
Released under the MIT License: www.opensource.org/licenses/mit-license.php
|
20
|
+
|
21
|
+
github.com/akdubya/mapel
|
data/Rakefile
ADDED
@@ -0,0 +1,62 @@
|
|
1
|
+
require 'rake'
|
2
|
+
require 'rake/testtask'
|
3
|
+
require 'rake/clean'
|
4
|
+
require 'rake/gempackagetask'
|
5
|
+
require 'rake/rdoctask'
|
6
|
+
require 'fileutils'
|
7
|
+
|
8
|
+
task :default => [:test]
|
9
|
+
task :spec => :test
|
10
|
+
|
11
|
+
name = 'mapel'
|
12
|
+
version = '0.1.3'
|
13
|
+
|
14
|
+
spec = Gem::Specification.new do |s|
|
15
|
+
s.name = name
|
16
|
+
s.version = version
|
17
|
+
s.summary = "A dead-simple image-rendering DSL."
|
18
|
+
s.description = "A dead-simple image-rendering DSL."
|
19
|
+
s.author = "Aleksander Williams"
|
20
|
+
s.email = "alekswilliams@earthlink.net"
|
21
|
+
s.homepage = "http://github.com/akdubya/mapel"
|
22
|
+
s.platform = Gem::Platform::RUBY
|
23
|
+
s.has_rdoc = true
|
24
|
+
s.files = %w(Rakefile README.rdoc) + Dir.glob("{lib,spec}/**/*")
|
25
|
+
s.require_path = "lib"
|
26
|
+
end
|
27
|
+
|
28
|
+
Rake::GemPackageTask.new(spec) do |p|
|
29
|
+
p.need_tar = true if RUBY_PLATFORM !~ /mswin/
|
30
|
+
end
|
31
|
+
|
32
|
+
desc "Install as a system gem"
|
33
|
+
task :install => [ :package ] do
|
34
|
+
sh %{sudo gem install pkg/#{name}-#{version}.gem}
|
35
|
+
end
|
36
|
+
|
37
|
+
desc "Uninstall as a system gem"
|
38
|
+
task :uninstall => [ :clean ] do
|
39
|
+
sh %{sudo gem uninstall #{name}}
|
40
|
+
end
|
41
|
+
|
42
|
+
desc "Create a gemspec file"
|
43
|
+
task :make_spec do
|
44
|
+
File.open("#{name}.gemspec", "w") do |file|
|
45
|
+
file.puts spec.to_ruby
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
Rake::TestTask.new(:test) do |t|
|
50
|
+
t.libs << "spec"
|
51
|
+
t.test_files = FileList['spec/*_spec.rb']
|
52
|
+
t.verbose = true
|
53
|
+
end
|
54
|
+
|
55
|
+
Rake::RDocTask.new do |t|
|
56
|
+
t.rdoc_dir = 'rdoc'
|
57
|
+
t.title = "Mapel: A dead-simple image-rendering DSL."
|
58
|
+
t.options << '--line-numbers' << '--inline-source' << '-A cattr_accessor=object'
|
59
|
+
t.options << '--charset' << 'utf-8'
|
60
|
+
t.rdoc_files.include('README.rdoc')
|
61
|
+
t.rdoc_files.include('lib/mapel.rb')
|
62
|
+
end
|
data/lib/mapel.rb
ADDED
@@ -0,0 +1,188 @@
|
|
1
|
+
def Mapel(source)
|
2
|
+
Mapel.render(source)
|
3
|
+
end
|
4
|
+
|
5
|
+
module Mapel
|
6
|
+
|
7
|
+
# Mapel.info('image.jpg')
|
8
|
+
def self.info(source, engine = :image_magick)
|
9
|
+
Mapel::Engine.const_get(camelize(engine)).info(source)
|
10
|
+
end
|
11
|
+
|
12
|
+
# Mapel.render('image.jpg').resize("50%").to('output.jpg').run
|
13
|
+
def self.render(source, engine = :image_magick)
|
14
|
+
Mapel::Engine.const_get(camelize(engine)).render(source)
|
15
|
+
end
|
16
|
+
|
17
|
+
# Mapel.list
|
18
|
+
def self.list(engine = :image_magick)
|
19
|
+
Mapel::Engine.const_get(camelize(engine)).list
|
20
|
+
end
|
21
|
+
|
22
|
+
class Engine
|
23
|
+
attr_reader :command, :status, :output
|
24
|
+
attr_accessor :commands
|
25
|
+
|
26
|
+
def initialize(source = nil)
|
27
|
+
@source = source
|
28
|
+
@commands = []
|
29
|
+
end
|
30
|
+
|
31
|
+
def success?
|
32
|
+
@status
|
33
|
+
end
|
34
|
+
|
35
|
+
class ImageMagick < Engine
|
36
|
+
def self.info(source)
|
37
|
+
im = new(source)
|
38
|
+
im.commands << 'identify'
|
39
|
+
im.commands << source
|
40
|
+
im.run.to_info_hash
|
41
|
+
end
|
42
|
+
|
43
|
+
def self.render(source = nil)
|
44
|
+
im = new(source)
|
45
|
+
im.commands << 'convert'
|
46
|
+
im.commands << source unless source.nil?
|
47
|
+
im
|
48
|
+
end
|
49
|
+
|
50
|
+
def self.list(type = nil)
|
51
|
+
im = new
|
52
|
+
im.commands << 'convert -list'
|
53
|
+
im.run
|
54
|
+
end
|
55
|
+
|
56
|
+
def crop(*args)
|
57
|
+
@commands << "-crop \"#{Geometry.new(*args).to_s(true)}\""
|
58
|
+
self
|
59
|
+
end
|
60
|
+
|
61
|
+
def gravity(type = :center)
|
62
|
+
@commands << "-gravity #{type}"
|
63
|
+
self
|
64
|
+
end
|
65
|
+
|
66
|
+
def repage
|
67
|
+
@commands << "+repage"
|
68
|
+
self
|
69
|
+
end
|
70
|
+
|
71
|
+
def resize(*args)
|
72
|
+
@commands << "-resize \"#{Geometry.new(*args)}\""
|
73
|
+
self
|
74
|
+
end
|
75
|
+
|
76
|
+
def resize!(*args)
|
77
|
+
@commands << lambda {
|
78
|
+
cmd = self.class.new
|
79
|
+
width, height = Geometry.new(*args).dimensions
|
80
|
+
if @source
|
81
|
+
origin_width, origin_height = Mapel.info(@source)[:dimensions]
|
82
|
+
|
83
|
+
# Crop dimensions should not exceed original dimensions.
|
84
|
+
width = [width, origin_width].min
|
85
|
+
height = [height, origin_height].min
|
86
|
+
|
87
|
+
if width != origin_width || height != origin_height
|
88
|
+
scale = [width/origin_width.to_f, height/origin_height.to_f].max
|
89
|
+
cmd.resize((scale*(origin_width+0.5)), (scale*(origin_height+0.5)))
|
90
|
+
end
|
91
|
+
cmd.crop(width, height).to_preview
|
92
|
+
else
|
93
|
+
"\{crop_resized #{args}\}"
|
94
|
+
end
|
95
|
+
}
|
96
|
+
self
|
97
|
+
end
|
98
|
+
|
99
|
+
def scale(*args)
|
100
|
+
@commands << "-scale \"#{Geometry.new(*args)}\""
|
101
|
+
self
|
102
|
+
end
|
103
|
+
|
104
|
+
def to(path)
|
105
|
+
@commands << path
|
106
|
+
self
|
107
|
+
end
|
108
|
+
|
109
|
+
def undo
|
110
|
+
@commands.pop
|
111
|
+
self
|
112
|
+
end
|
113
|
+
|
114
|
+
def run
|
115
|
+
@output = `#{to_preview}`
|
116
|
+
@status = ($? == 0)
|
117
|
+
self
|
118
|
+
end
|
119
|
+
|
120
|
+
def to_preview
|
121
|
+
@commands.map { |cmd| cmd.respond_to?(:call) ? cmd.call : cmd }.join(' ')
|
122
|
+
end
|
123
|
+
|
124
|
+
def to_info_hash
|
125
|
+
meta = {}
|
126
|
+
meta[:dimensions] = @output.split(' ')[2].split('x').map { |d| d.to_i }
|
127
|
+
meta
|
128
|
+
end
|
129
|
+
end
|
130
|
+
end
|
131
|
+
|
132
|
+
class Geometry
|
133
|
+
attr_accessor :width, :height, :x, :y, :flag
|
134
|
+
|
135
|
+
FLAGS = ['', '%', '<', '>', '!', '@']
|
136
|
+
RFLAGS = {
|
137
|
+
'%' => :percent,
|
138
|
+
'!' => :aspect,
|
139
|
+
'<' => :<,
|
140
|
+
'>' => :>,
|
141
|
+
'@' => :area
|
142
|
+
}
|
143
|
+
|
144
|
+
# Regex parser for geometry strings
|
145
|
+
RE = /\A(\d*)(?:x(\d+)?)?([-+]\d+)?([-+]\d+)?([%!<>@]?)\Z/
|
146
|
+
|
147
|
+
def initialize(*args)
|
148
|
+
if (args.length == 1) && (args.first.kind_of?(String))
|
149
|
+
raise(ArgumentError, "Invalid geometry string") unless m = RE.match(args.first)
|
150
|
+
args = m.to_a[1..5]
|
151
|
+
args[4] = args[4] ? RFLAGS[args[4]] : nil
|
152
|
+
end
|
153
|
+
@width = args[0] ? args[0].to_i.round : 0
|
154
|
+
@height = args[1] ? args[1].to_i.round : 0
|
155
|
+
raise(ArgumentError, "Width must be >= 0") if @width < 0
|
156
|
+
raise(ArgumentError, "Height must be >= 0") if @height < 0
|
157
|
+
@x = args[2] ? args[2].to_i : 0
|
158
|
+
@y = args[3] ? args[3].to_i : 0
|
159
|
+
@flag = (args[4] && RFLAGS.has_value?(args[4])) ? args[4] : nil
|
160
|
+
end
|
161
|
+
|
162
|
+
def dimensions
|
163
|
+
[width, height]
|
164
|
+
end
|
165
|
+
|
166
|
+
# Convert object to a geometry string
|
167
|
+
def to_s(crop = false)
|
168
|
+
str = ''
|
169
|
+
str << "%g" % @width if @width > 0
|
170
|
+
str << 'x' if @height > 0
|
171
|
+
str << "%g" % @height if @height > 0
|
172
|
+
str << "%+d%+d" % [@x, @y] if (@x != 0 || @y != 0 || crop)
|
173
|
+
str << RFLAGS.key(@flag).to_s
|
174
|
+
end
|
175
|
+
end
|
176
|
+
|
177
|
+
# By default, camelize converts strings to UpperCamelCase.
|
178
|
+
#
|
179
|
+
# camelize will also convert '/' to '::' which is useful for converting paths to namespaces
|
180
|
+
#
|
181
|
+
# @example
|
182
|
+
# "active_record".camelize #=> "ActiveRecord"
|
183
|
+
# "active_record/errors".camelize #=> "ActiveRecord::Errors"
|
184
|
+
#
|
185
|
+
def self.camelize(word, *args)
|
186
|
+
word.to_s.gsub(/\/(.?)/) { "::" + $1.upcase }.gsub(/(^|_)(.)/) { $2.upcase }
|
187
|
+
end
|
188
|
+
end
|
Binary file
|
data/spec/mapel_spec.rb
ADDED
@@ -0,0 +1,63 @@
|
|
1
|
+
require File.dirname(__FILE__) + '/spec_helper.rb'
|
2
|
+
|
3
|
+
describe Mapel do
|
4
|
+
before do
|
5
|
+
@input = File.dirname(__FILE__) + '/fixtures'
|
6
|
+
@output = File.dirname(__FILE__) + '/output'
|
7
|
+
@logo = @input + '/ImageMagick.jpg'
|
8
|
+
end
|
9
|
+
|
10
|
+
after do
|
11
|
+
Dir.glob(@output + '/*') { |f| File.delete(f) }
|
12
|
+
end
|
13
|
+
|
14
|
+
it "should respond to #info" do
|
15
|
+
Mapel.respond_to?(:info).should == true
|
16
|
+
end
|
17
|
+
|
18
|
+
it "should respond to #render" do
|
19
|
+
Mapel.respond_to?(:render).should == true
|
20
|
+
end
|
21
|
+
|
22
|
+
it "should support compact rendering syntax" do
|
23
|
+
Mapel(@logo).should.be.kind_of(Mapel::Engine)
|
24
|
+
end
|
25
|
+
|
26
|
+
describe "#info" do
|
27
|
+
it "should return image dimensions" do
|
28
|
+
Mapel.info(@logo)[:dimensions].should == [572, 591]
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
describe "#render" do
|
33
|
+
it "should be able to scale an image" do
|
34
|
+
cmd = Mapel(@logo).scale('50%').to(@output + '/scaled.jpg').run
|
35
|
+
cmd.status.should == true
|
36
|
+
Mapel.info(@output + '/scaled.jpg')[:dimensions].should == [286, 296]
|
37
|
+
end
|
38
|
+
|
39
|
+
it "should be able to crop an image" do
|
40
|
+
cmd = Mapel(@logo).crop('50x50+0+0').to(@output + '/cropped.jpg').run
|
41
|
+
cmd.status.should == true
|
42
|
+
Mapel.info(@output + '/cropped.jpg')[:dimensions].should == [50, 50]
|
43
|
+
end
|
44
|
+
|
45
|
+
it "should be able to resize an image" do
|
46
|
+
cmd = Mapel(@logo).resize('100x').to(@output + '/resized.jpg').run
|
47
|
+
cmd.status.should == true
|
48
|
+
Mapel.info(@output + '/resized.jpg')[:dimensions].should == [100, 103]
|
49
|
+
end
|
50
|
+
|
51
|
+
it "should be able to crop-resize an image" do
|
52
|
+
cmd = Mapel(@logo).gravity(:west).resize!('50x100').to(@output + '/crop_resized.jpg').run
|
53
|
+
cmd.status.should == true
|
54
|
+
Mapel.info(@output + '/crop_resized.jpg')[:dimensions].should == [50, 99]
|
55
|
+
end
|
56
|
+
|
57
|
+
it "should allow arbitrary addition of commands to the queue" do
|
58
|
+
cmd = Mapel(@logo).gravity(:west)
|
59
|
+
cmd.resize(50, 50)
|
60
|
+
cmd.to_preview.should == "convert #{@logo} -gravity west -resize \"50x50\""
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
data/spec/spec_helper.rb
ADDED
metadata
ADDED
@@ -0,0 +1,60 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: akdubya-mapel
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.3
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Aleksander Williams
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
|
12
|
+
date: 2009-03-11 00:00:00 -07:00
|
13
|
+
default_executable:
|
14
|
+
dependencies: []
|
15
|
+
|
16
|
+
description: A dead-simple image-rendering DSL.
|
17
|
+
email: alekswilliams@earthlink.net
|
18
|
+
executables: []
|
19
|
+
|
20
|
+
extensions: []
|
21
|
+
|
22
|
+
extra_rdoc_files: []
|
23
|
+
|
24
|
+
files:
|
25
|
+
- Rakefile
|
26
|
+
- README.rdoc
|
27
|
+
- lib/mapel.rb
|
28
|
+
- spec/fixtures
|
29
|
+
- spec/fixtures/ImageMagick.jpg
|
30
|
+
- spec/spec_helper.rb
|
31
|
+
- spec/output
|
32
|
+
- spec/mapel_spec.rb
|
33
|
+
has_rdoc: true
|
34
|
+
homepage: http://github.com/akdubya/mapel
|
35
|
+
post_install_message:
|
36
|
+
rdoc_options: []
|
37
|
+
|
38
|
+
require_paths:
|
39
|
+
- lib
|
40
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
41
|
+
requirements:
|
42
|
+
- - ">="
|
43
|
+
- !ruby/object:Gem::Version
|
44
|
+
version: "0"
|
45
|
+
version:
|
46
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
47
|
+
requirements:
|
48
|
+
- - ">="
|
49
|
+
- !ruby/object:Gem::Version
|
50
|
+
version: "0"
|
51
|
+
version:
|
52
|
+
requirements: []
|
53
|
+
|
54
|
+
rubyforge_project:
|
55
|
+
rubygems_version: 1.2.0
|
56
|
+
signing_key:
|
57
|
+
specification_version: 2
|
58
|
+
summary: A dead-simple image-rendering DSL.
|
59
|
+
test_files: []
|
60
|
+
|