airsprite 1.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.
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: []