airsprite 1.0

Sign up to get free protection for your applications and to get access to all the features.
File without changes
@@ -0,0 +1 @@
1
+ *.gem
data/.rspec ADDED
@@ -0,0 +1,2 @@
1
+ --colour
2
+
data/.rvmrc ADDED
@@ -0,0 +1 @@
1
+ rvm use 1.9.2@gems
data/Gemfile ADDED
@@ -0,0 +1,3 @@
1
+ source :rubygems
2
+
3
+ gemspec
@@ -0,0 +1,26 @@
1
+ PATH
2
+ remote: .
3
+ specs:
4
+ bbcoder (0.1.0)
5
+
6
+ GEM
7
+ remote: http://rubygems.org/
8
+ specs:
9
+ diff-lcs (1.1.2)
10
+ rr (1.0.2)
11
+ rspec (2.4.0)
12
+ rspec-core (~> 2.4.0)
13
+ rspec-expectations (~> 2.4.0)
14
+ rspec-mocks (~> 2.4.0)
15
+ rspec-core (2.4.0)
16
+ rspec-expectations (2.4.0)
17
+ diff-lcs (~> 1.1.2)
18
+ rspec-mocks (2.4.0)
19
+
20
+ PLATFORMS
21
+ ruby
22
+
23
+ DEPENDENCIES
24
+ bbcoder!
25
+ rr
26
+ rspec
data/LICENSE ADDED
@@ -0,0 +1,20 @@
1
+ Copyright (c) 2011 John "asceth" Long
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ 'Software'), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
17
+ IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
18
+ CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
19
+ TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
20
+ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
@@ -0,0 +1,55 @@
1
+ Overview
2
+ --------
3
+ airsprite makes single png spritesheets from a directory struture and outputs parseable itx files for the http://www.madewithmarmalade.com (Marmalade SDK).
4
+
5
+
6
+ Give a directory as an argument. Each folder underneath that directory is considered its own spritesheet.
7
+
8
+ Each png file underneath the spritesheet folder is a sprite in of itself (single frame) and constructs a default animation, default frame of that sprite. If a folder is encounted a sprite is created named after the folder.
9
+
10
+ Inside the sprite folder, png files are considered animations with single frames with the name of the png file. A folder is considered an animation called the name of the folder and each png file underneath the animation is a series of frames, sorted alphabetically.
11
+
12
+ Examples:
13
+
14
+ path/sprite.png
15
+ /foo/bar.png
16
+
17
+ * creates a sprite called "sprite" with the "default" animation and a single frame called "idle".
18
+ * creates a sprite called "foo" with the "bar" animation and a single frame called "bar".
19
+
20
+
21
+
22
+ path/cranky/idle/0.png
23
+ idle/1.png
24
+
25
+ path/cranky/run/0.png
26
+ run/1.png
27
+
28
+ * creates a sprite named "cranky" with the "idle" and "run" animations each with two frames called "0" and "1".
29
+
30
+
31
+ Usage
32
+ --------
33
+
34
+ airsprite path/to/dir
35
+
36
+ Install
37
+ -------
38
+
39
+ gem install airsprite
40
+
41
+
42
+ Deps
43
+ -------
44
+
45
+ gem install rmagick
46
+
47
+ * used to create the spritesheet png
48
+
49
+
50
+ Author
51
+ ------
52
+
53
+ Original author: John "asceth" Long
54
+
55
+
@@ -0,0 +1,19 @@
1
+ require "bundler"
2
+ Bundler.setup
3
+
4
+ require "rspec"
5
+ require "rspec/core/rake_task"
6
+
7
+ Rspec::Core::RakeTask.new(:spec)
8
+
9
+ gemspec = eval(File.read(File.join(Dir.pwd, "bbcoder.gemspec")))
10
+
11
+ task :build => "#{gemspec.full_name}.gem"
12
+
13
+ task :test => :spec
14
+
15
+ file "#{gemspec.full_name}.gem" => gemspec.files + ["bbcoder.gemspec"] do
16
+ system "gem build bbcoder.gemspec"
17
+ system "gem install bbcoder-#{BBCoder::VERSION}.gem"
18
+ end
19
+
@@ -0,0 +1,26 @@
1
+ $:.push File.expand_path("../lib", __FILE__)
2
+ require "airsprite/version"
3
+
4
+ Gem::Specification.new do |s|
5
+ s.name = "airsprite"
6
+ s.version = Airsprite::VERSION
7
+ s.platform = Gem::Platform::RUBY
8
+ s.authors = ["John 'asceth' Long"]
9
+ s.email = ["machinist@asceth.com"]
10
+ s.homepage = "http://github.com/asceth/airsprite"
11
+ s.summary = "Marmalade SDK Sprite Sheet generator"
12
+ s.description = "A gem for creating sprite sheets for the Marmalade SDK"
13
+
14
+ s.rubyforge_project = "airsprite"
15
+
16
+ s.files = `git ls-files`.split("\n")
17
+ s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
18
+ s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
19
+ s.require_paths = ["lib"]
20
+
21
+ s.add_dependency 'rmagick'
22
+
23
+ s.add_development_dependency 'rspec'
24
+ s.add_development_dependency 'rr'
25
+ end
26
+
@@ -0,0 +1,8 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ lib = File.expand_path(File.dirname(__FILE__) + "/../lib")
4
+ $LOAD_PATH.unshift(lib) if File.directory?(lib) and (not $LOAD_PATH.include? lib)
5
+
6
+ require "airsprite"
7
+
8
+ Airsprite.run
@@ -0,0 +1,8 @@
1
+ begin
2
+ require 'RMagick'
3
+ rescue
4
+ require 'rmagick'
5
+ end
6
+
7
+ require 'airsprite/base'
8
+ require 'airsprite/config'
@@ -0,0 +1,279 @@
1
+ module Airsprite
2
+ module Base
3
+ def run
4
+ Airsprite::Config.parse_config_file
5
+
6
+ # each directory underneath Airsprite::Config.path is a sheet
7
+ Dir["#{Airsprite::Config.path}/*/"].each do |dir|
8
+ spritesheet = SpriteSheet.new(dir.split('/').last, dir)
9
+ spritesheet.place
10
+ spritesheet.output
11
+ end
12
+ end
13
+
14
+ def twos
15
+ [
16
+ 2,
17
+ 4,
18
+ 8,
19
+ 16,
20
+ 32,
21
+ 64,
22
+ 128,
23
+ 256,
24
+ 512,
25
+ 1024,
26
+ 2048
27
+ ]
28
+ end
29
+ end
30
+ extend Base
31
+ end
32
+
33
+ module Airsprite
34
+ class SpriteSheet
35
+ attr_accessor :name, :path, :sprites
36
+ attr_accessor :surface, :max_side, :width, :height
37
+
38
+ def initialize(name, directory)
39
+ self.name = name
40
+ self.path = directory
41
+ self.sprites = []
42
+
43
+ Dir["#{self.path}*/"].each do |dir|
44
+ self.sprites += [Sprite.new(self, dir.split('/').last, dir)]
45
+ end
46
+
47
+ Dir["#{self.path}*.png"].each do |file|
48
+ self.sprites += [Sprite.new(self, File.basename(file, '.png'), file)]
49
+ end
50
+ end
51
+
52
+ def to_s
53
+ <<-EOS
54
+ SpriteSheet
55
+ {
56
+ name "#{name}"
57
+
58
+ #{sprites.map(&:to_s).join}
59
+ }
60
+ EOS
61
+ end
62
+
63
+ def place
64
+ # biggest first
65
+ frames = sprites.map(&:animations).flatten.map(&:frames).flatten.sort_by(&:area).reverse
66
+
67
+ # figure out lowest possible power of 2
68
+ @surface = frames.map(&:area).inject(0) {|sum, area| sum + area}
69
+ @max_side = [frames.map(&:width), frames.map(&:height)].flatten.sort.last
70
+ @width = 0
71
+ @height = 0
72
+
73
+ STDERR.puts "surface needed: #{@surface}"
74
+ Airsprite.twos.map do |power_of_two|
75
+ # lowest two must be >= biggest sprite size
76
+ next unless power_of_two >= @max_side
77
+
78
+ if (@surface / power_of_two) <= power_of_two
79
+ if (power_of_two * Airsprite.twos[Airsprite.twos.index(power_of_two) - 1]) > @surface
80
+ @width = power_of_two
81
+ @height = Airsprite.twos[Airsprite.twos.index(power_of_two) - 1]
82
+ else
83
+ @width = power_of_two
84
+ @height = power_of_two
85
+ end
86
+ STDERR.puts "surface generated: #{@width * @height}"
87
+ break
88
+ end
89
+ end
90
+
91
+ # set these sprite frames up!
92
+ rects = [Rect.new(@width, @height, 0, 0)]
93
+
94
+ # place frames
95
+ frames.map do |frame|
96
+ # find first square that can contain this sprite
97
+ raise "No rects left to fill" if rects.empty?
98
+
99
+ STDERR.puts "\n\n"
100
+ STDERR.puts frame.to_short_s
101
+ STDERR.puts rects.map(&:to_s).join("\n")
102
+ rects.map do |rect|
103
+ if rect.area >= frame.area && rect.width >= frame.width && rect.height >= frame.height
104
+ frame.x = rect.x
105
+ frame.y = rect.y
106
+
107
+ # delete rect from list
108
+ rects.delete(rect)
109
+ unless rect.height == frame.height && rect.width == frame.width
110
+ if rect.height == frame.height
111
+ rects << Rect.new(rect.width - frame.width, rect.height, rect.x + frame.width, rect.y)
112
+ elsif rect.width == frame.width
113
+ rects << Rect.new(rect.width, rect.height - frame.height, rect.x, rect.y + frame.height)
114
+ else
115
+ rects << Rect.new(frame.width, rect.height - frame.height, rect.x, rect.y + frame.height)
116
+ rects << Rect.new(rect.width - frame.width, rect.height, rect.x + frame.width, rect.y)
117
+ end
118
+ end
119
+ break
120
+ end
121
+ end
122
+ end
123
+ end
124
+
125
+ def output
126
+ frames = sprites.map(&:animations).flatten.map(&:frames).flatten.sort_by(&:area)
127
+
128
+ draw = Magick::Draw.new
129
+ image = Magick::Image.new(@width, @height)
130
+ draw.matte(0, 0, Magick::ResetMethod)
131
+
132
+ frames.map do |frame|
133
+ next if frame.x.nil? || frame.y.nil?
134
+ #draw.composite(frame.data, frame.x, frame.y, Magick::CopyCompositeOp)
135
+ draw.composite(frame.x, frame.y, frame.width, frame.height, frame.data, Magick::CopyCompositeOp)
136
+ #image.store_pixels(frame.x, frame.y, frame.width, frame.height, frame.data.get_pixels(0, 0, frame.width, frame.height))
137
+ end
138
+ STDERR.puts "writing to #{self.path[0..-2]}.itx"
139
+ File.open("#{self.path[0..-2]}.itx", 'w') {|f| f.write(self.to_s)}
140
+ STDERR.puts "writing to #{self.path[0..-2]}.png"
141
+ image.set_channel_depth(Magick::AllChannels, 8)
142
+ draw.draw(image)
143
+ image.write("#{self.path[0..-2]}.png")
144
+ end
145
+ end
146
+
147
+ class Rect
148
+ attr_accessor :width, :height, :x, :y
149
+
150
+ def initialize(width, height, offset_x, offset_y)
151
+ @width = width
152
+ @height = height
153
+ @x = offset_x
154
+ @y = offset_y
155
+ end
156
+
157
+ def area
158
+ width * height
159
+ end
160
+
161
+ def to_s
162
+ "rect: #{width} x #{height} = #{area} (#{x}, #{y})"
163
+ end
164
+ end
165
+
166
+ class Sprite
167
+ attr_accessor :sheet, :name
168
+ attr_accessor :animations
169
+
170
+ def initialize(sheet, name, path)
171
+ @name = name
172
+ @animations = []
173
+
174
+ if File.directory?(path)
175
+ Dir["#{path}*/"].each do |dir|
176
+ @animations += [SpriteAnimation.new(dir.split('/').last, dir)]
177
+ end
178
+ else
179
+ @animations += [SpriteAnimation.new('idle', path)]
180
+ end
181
+
182
+ if @animations.map(&:frames).flatten.map(&:width).uniq.size > 1
183
+ raise "Widths of frames for a sprite must be the same width"
184
+ end
185
+
186
+ if @animations.map(&:frames).flatten.map(&:height).uniq.size > 1
187
+ raise "Widths of frames for a sprite must be the same height"
188
+ end
189
+ end
190
+
191
+ def to_s
192
+ <<-EOS
193
+ Sprite
194
+ {
195
+ name "#{name}"
196
+
197
+ #{animations.map(&:to_s).join}
198
+ }
199
+ EOS
200
+ end
201
+ end
202
+
203
+ class SpriteAnimation
204
+ attr_accessor :name
205
+ attr_accessor :frames
206
+
207
+ def initialize(name, path)
208
+ @name = name
209
+ @frames = []
210
+
211
+ if File.directory?(path)
212
+ Dir["#{path}*.png"].each do |file|
213
+ @frames << SpriteFrame.new(File.basename(file, '.png'), file)
214
+ end
215
+ else
216
+ @frames << SpriteFrame.new(File.basename(path, '.png'), path)
217
+ end
218
+
219
+ @frames.sort_by(&:name).inject(0) do |position, frame|
220
+ frame.position = position
221
+ position + 1
222
+ end
223
+ end
224
+
225
+ def to_s
226
+ <<-EOS
227
+ SpriteAnimation
228
+ {
229
+ name "#{name}"
230
+
231
+ #{frames.sort_by(&:position).map(&:to_s).join}
232
+ }
233
+ EOS
234
+ end
235
+ end
236
+
237
+ class SpriteFrame
238
+ attr_accessor :name, :data, :position, :speed
239
+ attr_accessor :width, :height, :area, :x, :y
240
+
241
+ def initialize(name, path)
242
+ split_name = name.split('-')
243
+
244
+ if split_name.size == 1
245
+ @name = split_name[0]
246
+ @speed = 0
247
+ else
248
+ @name = split_name.shift
249
+ @speed = split_name.join.to_i
250
+ end
251
+
252
+ @data = Magick::Image::read(path).first
253
+ @width = @data.columns.to_i
254
+ @height = @data.rows.to_i
255
+ @area = @width * @height
256
+ end
257
+
258
+ def to_s
259
+ <<-EOS
260
+ SpriteFrame
261
+ {
262
+ name "#{name}"
263
+ speed #{speed}
264
+ position #{position}
265
+ width #{width}
266
+ height #{height}
267
+ area #{area}
268
+ x #{x}
269
+ y #{y}
270
+ }
271
+ EOS
272
+ end
273
+
274
+ def to_short_s
275
+ "frame: #{width} x #{height} = #{area} (#{x}, #{y})"
276
+ end
277
+ end
278
+ end
279
+
@@ -0,0 +1,34 @@
1
+ module Airsprite
2
+ module Config
3
+
4
+ CONFIG_FILENAME="airsprite.yml"
5
+
6
+
7
+ module ModuleMethods
8
+ def path=(value)
9
+ @@path = value
10
+ end
11
+ def path
12
+ @@path
13
+ end
14
+
15
+ def scales=(value)
16
+ @@scales = value
17
+ end
18
+ def scales
19
+ @@scales
20
+ end
21
+
22
+ def parse_config_file
23
+ self.path = Dir.pwd
24
+
25
+ if File.exists?(CONFIG_FILENAME)
26
+ YAML.load(File.read(CONFIG_FILENAME)).each {|(name, value)| send "#{name}=", value }
27
+ else
28
+ self.scales = [1.0]
29
+ end
30
+ end
31
+ end
32
+ extend ModuleMethods
33
+ end
34
+ end
@@ -0,0 +1,3 @@
1
+ class Airsprite
2
+ VERSION = "1.0"
3
+ end
@@ -0,0 +1,46 @@
1
+ require 'spec_helper'
2
+
3
+ describe BBCoder do
4
+
5
+ subject { BBCoder.new("[p]Text and now [b]bolded.[/b][/p]") }
6
+
7
+ context "#configuration" do
8
+ it "should return the same object for multiple calls" do
9
+ BBCoder.configuration.should == BBCoder.configuration
10
+ end
11
+ end
12
+
13
+ context "#buffer" do
14
+ it "should return the same object for multiple calls" do
15
+ subject.buffer.should == subject.buffer
16
+ end
17
+ end
18
+
19
+ context "#configure" do
20
+ it "should fail without a block" do
21
+ lambda { BBCoder.configure }.should raise_error
22
+ end
23
+
24
+ it "should instance_eval the block onto configuration" do
25
+ block = Proc.new { tag :p }
26
+ mock(BBCoder).configuration.stub!.instance_eval(&block)
27
+ BBCoder.configure(&block)
28
+ end
29
+ end
30
+
31
+ context "#initialize" do
32
+ it "should split tags up properly" do
33
+ subject.raw.should == ["[p]", "Text and now ", "[b]", "bolded.", "[/b]", "[/p]"]
34
+ end
35
+ end
36
+
37
+ context "#parse" do
38
+ it "should loop through raw elements and join the buffer" do
39
+ mock(subject).raw.stub!.each {nil}
40
+ mock(subject).buffer.stub!.join {"output"}
41
+
42
+ subject.parse
43
+ end
44
+ end
45
+ end
46
+
@@ -0,0 +1,11 @@
1
+ require 'rubygems'
2
+ require 'bundler'
3
+ Bundler.setup
4
+
5
+ require 'rspec'
6
+ require 'bbcoder'
7
+
8
+ RSpec.configure do |config|
9
+ config.mock_with :rr
10
+ end
11
+
metadata ADDED
@@ -0,0 +1,98 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: airsprite
3
+ version: !ruby/object:Gem::Version
4
+ version: '1.0'
5
+ prerelease:
6
+ platform: ruby
7
+ authors:
8
+ - John 'asceth' Long
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2011-08-07 00:00:00.000000000 -04:00
13
+ default_executable:
14
+ dependencies:
15
+ - !ruby/object:Gem::Dependency
16
+ name: rmagick
17
+ requirement: &17197820 !ruby/object:Gem::Requirement
18
+ none: false
19
+ requirements:
20
+ - - ! '>='
21
+ - !ruby/object:Gem::Version
22
+ version: '0'
23
+ type: :runtime
24
+ prerelease: false
25
+ version_requirements: *17197820
26
+ - !ruby/object:Gem::Dependency
27
+ name: rspec
28
+ requirement: &17197340 !ruby/object:Gem::Requirement
29
+ none: false
30
+ requirements:
31
+ - - ! '>='
32
+ - !ruby/object:Gem::Version
33
+ version: '0'
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: *17197340
37
+ - !ruby/object:Gem::Dependency
38
+ name: rr
39
+ requirement: &17196800 !ruby/object:Gem::Requirement
40
+ none: false
41
+ requirements:
42
+ - - ! '>='
43
+ - !ruby/object:Gem::Version
44
+ version: '0'
45
+ type: :development
46
+ prerelease: false
47
+ version_requirements: *17196800
48
+ description: A gem for creating sprite sheets for the Marmalade SDK
49
+ email:
50
+ - machinist@asceth.com
51
+ executables:
52
+ - airsprite
53
+ extensions: []
54
+ extra_rdoc_files: []
55
+ files:
56
+ - .gemtest
57
+ - .gitignore
58
+ - .rspec
59
+ - .rvmrc
60
+ - Gemfile
61
+ - Gemfile.lock
62
+ - LICENSE
63
+ - README.md
64
+ - Rakefile
65
+ - airsprite.gemspec
66
+ - bin/airsprite
67
+ - lib/airsprite.rb
68
+ - lib/airsprite/base.rb
69
+ - lib/airsprite/config.rb
70
+ - lib/airsprite/version.rb
71
+ - spec/base_spec.rb
72
+ - spec/spec_helper.rb
73
+ has_rdoc: true
74
+ homepage: http://github.com/asceth/airsprite
75
+ licenses: []
76
+ post_install_message:
77
+ rdoc_options: []
78
+ require_paths:
79
+ - lib
80
+ required_ruby_version: !ruby/object:Gem::Requirement
81
+ none: false
82
+ requirements:
83
+ - - ! '>='
84
+ - !ruby/object:Gem::Version
85
+ version: '0'
86
+ required_rubygems_version: !ruby/object:Gem::Requirement
87
+ none: false
88
+ requirements:
89
+ - - ! '>='
90
+ - !ruby/object:Gem::Version
91
+ version: '0'
92
+ requirements: []
93
+ rubyforge_project: airsprite
94
+ rubygems_version: 1.6.2
95
+ signing_key:
96
+ specification_version: 3
97
+ summary: Marmalade SDK Sprite Sheet generator
98
+ test_files: []