spittle 0.9.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (63) hide show
  1. data/.gitignore +12 -0
  2. data/README +19 -0
  3. data/Rakefile +45 -0
  4. data/VERSION +1 -0
  5. data/bin/spittle +15 -0
  6. data/examples/sprites/README +5 -0
  7. data/examples/sprites/apple/apple.png +0 -0
  8. data/examples/sprites/apple/divider.png +0 -0
  9. data/examples/sprites/apple/downloads.png +0 -0
  10. data/examples/sprites/apple/fragment.css +63 -0
  11. data/examples/sprites/apple/iphone.png +0 -0
  12. data/examples/sprites/apple/itunes.png +0 -0
  13. data/examples/sprites/apple/mac.png +0 -0
  14. data/examples/sprites/apple/search.png +0 -0
  15. data/examples/sprites/apple/sprite.png +0 -0
  16. data/examples/sprites/apple/store.png +0 -0
  17. data/examples/sprites/apple/support.png +0 -0
  18. data/examples/sprites/fragment.css +0 -0
  19. data/examples/sprites/index.html +33 -0
  20. data/examples/sprites/server.rb +10 -0
  21. data/examples/sprites/sprite.css +91 -0
  22. data/examples/sprites/words/fragment.css +28 -0
  23. data/examples/sprites/words/latitude.png +0 -0
  24. data/examples/sprites/words/of.png +0 -0
  25. data/examples/sprites/words/set.png +0 -0
  26. data/examples/sprites/words/specified.png +0 -0
  27. data/examples/sprites/words/sprite.css +24 -0
  28. data/examples/sprites/words/sprite.png +0 -0
  29. data/lib/spittle/chunk.rb +12 -0
  30. data/lib/spittle/directory_spriter.rb +63 -0
  31. data/lib/spittle/file_header.rb +7 -0
  32. data/lib/spittle/filters.rb +64 -0
  33. data/lib/spittle/idat.rb +26 -0
  34. data/lib/spittle/iend.rb +9 -0
  35. data/lib/spittle/ihdr.rb +30 -0
  36. data/lib/spittle/image.rb +114 -0
  37. data/lib/spittle/parser.rb +54 -0
  38. data/lib/spittle/processor.rb +28 -0
  39. data/lib/spittle/sprite.rb +39 -0
  40. data/lib/spittle/stylesheet_builder.rb +22 -0
  41. data/lib/spittle.rb +16 -0
  42. data/spec/builders/image_builder.rb +22 -0
  43. data/spec/css_fragments/deep/style/fragment.css +1 -0
  44. data/spec/css_fragments/some/fragment.css +1 -0
  45. data/spec/expected_output/merge_right_test.png +0 -0
  46. data/spec/expected_output/write_test.png +0 -0
  47. data/spec/images/lightening.png +0 -0
  48. data/spec/integration_spec.rb +134 -0
  49. data/spec/lib/file_header_spec.rb +10 -0
  50. data/spec/lib/idat_spec.rb +30 -0
  51. data/spec/lib/ihdr_spec.rb +43 -0
  52. data/spec/lib/image_spec.rb +19 -0
  53. data/spec/lib/parser_spec.rb +12 -0
  54. data/spec/lib/sprite_spec.rb +36 -0
  55. data/spec/spec.opts +1 -0
  56. data/spec/spec_helper.rb +16 -0
  57. data/spec/sprite_dirs/words/latitude.png +0 -0
  58. data/spec/sprite_dirs/words/of.png +0 -0
  59. data/spec/sprite_dirs/words/set.png +0 -0
  60. data/spec/sprite_dirs/words/specified.png +0 -0
  61. data/spec/tmp/merge_right_test.png +0 -0
  62. data/spec/tmp/write_test.png +0 -0
  63. metadata +137 -0
data/.gitignore ADDED
@@ -0,0 +1,12 @@
1
+ Manifest
2
+ .DS_Store
3
+ pkg/*
4
+ doc/*
5
+ coverage/*
6
+ html/*
7
+ rdoc/*
8
+ examples/pic_data/out.png
9
+ examples/pic_data/sprite_test.png
10
+ examples/pic_data/test.png
11
+ examples/pic_data/animal.png
12
+ *.swp
data/README ADDED
@@ -0,0 +1,19 @@
1
+ [ PNG, PNG, PNG ] (°_°)
2
+ [ PNG, PNG, PNG ] (° )
3
+ [ PNG, PNG, PNG ] )°)
4
+ [ PNG, PNG, )°)
5
+ [ PNG, )°)
6
+ [ )°)
7
+
8
+ \(°_°)/ -> SPRITES!!!
9
+
10
+
11
+ It takes your PNG's, chews them up and spits out sprites!
12
+
13
+ point bin/spittle at a directory, and watch it sprite away!
14
+
15
+
16
+
17
+ currently only supports images of the same height - this will change soon.
18
+
19
+ thx to tjennings for the initial spike and help
data/Rakefile ADDED
@@ -0,0 +1,45 @@
1
+ require 'rubygems'
2
+ require 'rake'
3
+
4
+ begin
5
+ require 'jeweler'
6
+ Jeweler::Tasks.new do |gem|
7
+ gem.name = "spittle"
8
+ gem.summary = %Q{pure ruby PNG}
9
+ gem.description = %Q{pure ruby PNG}
10
+ gem.email = ["qzzzq1@gmail.com", "tyler.jennings@gmail.com"]
11
+ gem.homepage = "http://github.com/aberant/spittle"
12
+ gem.authors = ["aberant", "tjennings"]
13
+ gem.add_development_dependency "rspec" #, ">= 1.2.9"
14
+ # gem is a Gem::Specification... see http://www.rubygems.org/read/chapter/20 for additional settings
15
+ end
16
+ Jeweler::GemcutterTasks.new
17
+ rescue LoadError
18
+ puts "Jeweler (or a dependency) not available. Install it with: sudo gem install jeweler"
19
+ end
20
+
21
+ require 'spec/rake/spectask'
22
+ Spec::Rake::SpecTask.new(:spec) do |spec|
23
+ spec.libs << 'lib' << 'spec'
24
+ spec.spec_files = FileList['spec/**/*_spec.rb']
25
+ end
26
+
27
+ Spec::Rake::SpecTask.new(:rcov) do |spec|
28
+ spec.libs << 'lib' << 'spec'
29
+ spec.pattern = 'spec/**/*_spec.rb'
30
+ spec.rcov = true
31
+ end
32
+
33
+ task :spec #=> :check_dependencies
34
+
35
+ task :default => :spec
36
+
37
+ require 'rake/rdoctask'
38
+ Rake::RDocTask.new do |rdoc|
39
+ version = File.exist?('VERSION') ? File.read('VERSION') : ""
40
+
41
+ rdoc.rdoc_dir = 'rdoc'
42
+ rdoc.title = "spittle #{version}"
43
+ rdoc.rdoc_files.include('README*')
44
+ rdoc.rdoc_files.include('lib/**/*.rb')
45
+ end
data/VERSION ADDED
@@ -0,0 +1 @@
1
+ 0.9.0
data/bin/spittle ADDED
@@ -0,0 +1,15 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require File.dirname(__FILE__) + "/../lib/spittle.rb"
4
+
5
+ dir = ARGV[0]
6
+
7
+ if dir
8
+ Spittle::Processor.new(:source => dir).write
9
+ else
10
+ puts
11
+ puts "spittle! it chews up your PNG's and spits out sprites!"
12
+ puts "useage: spittle DIRECTORY"
13
+ puts "eg. spittle my_images"
14
+ puts
15
+ end
@@ -0,0 +1,5 @@
1
+ The sprites here are pre generated. If you want to see it work yourself delete the the two sprite.png files and all the *.css. Then simply run '../bin/spittle' and it should regenreate the sprites.
2
+
3
+ The server script will start a WEBrick server on port 3000 and serve from this directory. If everything worked out you should see the two sprite samples displayed in index.html.
4
+
5
+ .
Binary file
Binary file
@@ -0,0 +1,63 @@
1
+ .apple_itunes {
2
+ background: transparent url(/apple/sprite.png) -350px 0px no-repeat;
3
+ width:116;
4
+ height:38;
5
+ text-indent:-5000px;
6
+ }
7
+
8
+ .apple_mac {
9
+ background: transparent url(/apple/sprite.png) -466px 0px no-repeat;
10
+ width:116;
11
+ height:38;
12
+ text-indent:-5000px;
13
+ }
14
+
15
+ .apple_apple {
16
+ background: transparent url(/apple/sprite.png) 0px 0px no-repeat;
17
+ width:117;
18
+ height:38;
19
+ text-indent:-5000px;
20
+ }
21
+
22
+ .apple_store {
23
+ background: transparent url(/apple/sprite.png) -744px 0px no-repeat;
24
+ width:116;
25
+ height:38;
26
+ text-indent:-5000px;
27
+ }
28
+
29
+ .apple_search {
30
+ background: transparent url(/apple/sprite.png) -582px 0px no-repeat;
31
+ width:162;
32
+ height:38;
33
+ text-indent:-5000px;
34
+ }
35
+
36
+ .apple_divider {
37
+ background: transparent url(/apple/sprite.png) -117px 0px no-repeat;
38
+ width:1;
39
+ height:38;
40
+ text-indent:-5000px;
41
+ }
42
+
43
+ .apple_support {
44
+ background: transparent url(/apple/sprite.png) -860px 0px no-repeat;
45
+ width:116;
46
+ height:38;
47
+ text-indent:-5000px;
48
+ }
49
+
50
+ .apple_downloads {
51
+ background: transparent url(/apple/sprite.png) -118px 0px no-repeat;
52
+ width:116;
53
+ height:38;
54
+ text-indent:-5000px;
55
+ }
56
+
57
+ .apple_iphone {
58
+ background: transparent url(/apple/sprite.png) -234px 0px no-repeat;
59
+ width:116;
60
+ height:38;
61
+ text-indent:-5000px;
62
+ }
63
+
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
File without changes
@@ -0,0 +1,33 @@
1
+ <html>
2
+ <head>
3
+ <title>A simple sprite test</title>
4
+ <link href="/sprite.css" type="text/css" rel="stylesheet" />
5
+ </head>
6
+ <body>
7
+ <style>
8
+ .float_left {float:left}
9
+ .menu {height: 100px}
10
+ </style>
11
+ Hello. See spittle run:
12
+ <div class="words_of">of</div>
13
+ <div class="words_latitude">latitude</div>
14
+ <div class="words_set">set</div>
15
+
16
+ Some random menu bar
17
+ <div>
18
+ <span class="menu float_left apple_apple">apple</span>
19
+ <span class="menu float_left apple_divider"></span>
20
+ <span class="menu float_left apple_store">store</span>
21
+ <span class="menu float_left apple_divider"></span>
22
+ <span class="menu float_left apple_itunes">ipod</span>
23
+ <span class="menu float_left apple_divider"></span>
24
+ <span class="menu float_left apple_iphone">iphone</span>
25
+ <span class="menu float_left apple_divider"></span>
26
+ <span class="menu float_left apple_downloads">downloads</span>
27
+ <span class="menu float_left apple_divider"></span>
28
+ <span class="menu float_left apple_support">suport</span>
29
+ <span class="menu float_left apple_divider"></span>
30
+ <span class="menu float_left apple_search">search</span>
31
+ </div>
32
+ </body>
33
+ </html>
@@ -0,0 +1,10 @@
1
+ #!/usr/bin/env ruby
2
+ require 'webrick'
3
+
4
+ Socket.do_not_reverse_lookup = true # patch for OS X
5
+ params = { :Port => 3000, :DocumentRoot => '.'}
6
+
7
+ server = WEBrick::HTTPServer.new(params)
8
+ trap("INT") { server.shutdown }
9
+ server.start
10
+
@@ -0,0 +1,91 @@
1
+ .apple_itunes {
2
+ background: transparent url(/apple/sprite.png) -350px 0px no-repeat;
3
+ width:116;
4
+ height:38;
5
+ text-indent:-5000px;
6
+ }
7
+
8
+ .apple_mac {
9
+ background: transparent url(/apple/sprite.png) -466px 0px no-repeat;
10
+ width:116;
11
+ height:38;
12
+ text-indent:-5000px;
13
+ }
14
+
15
+ .apple_apple {
16
+ background: transparent url(/apple/sprite.png) 0px 0px no-repeat;
17
+ width:117;
18
+ height:38;
19
+ text-indent:-5000px;
20
+ }
21
+
22
+ .apple_store {
23
+ background: transparent url(/apple/sprite.png) -744px 0px no-repeat;
24
+ width:116;
25
+ height:38;
26
+ text-indent:-5000px;
27
+ }
28
+
29
+ .apple_search {
30
+ background: transparent url(/apple/sprite.png) -582px 0px no-repeat;
31
+ width:162;
32
+ height:38;
33
+ text-indent:-5000px;
34
+ }
35
+
36
+ .apple_divider {
37
+ background: transparent url(/apple/sprite.png) -117px 0px no-repeat;
38
+ width:1;
39
+ height:38;
40
+ text-indent:-5000px;
41
+ }
42
+
43
+ .apple_support {
44
+ background: transparent url(/apple/sprite.png) -860px 0px no-repeat;
45
+ width:116;
46
+ height:38;
47
+ text-indent:-5000px;
48
+ }
49
+
50
+ .apple_downloads {
51
+ background: transparent url(/apple/sprite.png) -118px 0px no-repeat;
52
+ width:116;
53
+ height:38;
54
+ text-indent:-5000px;
55
+ }
56
+
57
+ .apple_iphone {
58
+ background: transparent url(/apple/sprite.png) -234px 0px no-repeat;
59
+ width:116;
60
+ height:38;
61
+ text-indent:-5000px;
62
+ }
63
+
64
+ .words_set {
65
+ background: transparent url(/words/sprite.png) -85px 0px no-repeat;
66
+ width:26;
67
+ height:21;
68
+ text-indent:-5000px;
69
+ }
70
+
71
+ .words_specified {
72
+ background: transparent url(/words/sprite.png) -111px 0px no-repeat;
73
+ width:70;
74
+ height:21;
75
+ text-indent:-5000px;
76
+ }
77
+
78
+ .words_latitude {
79
+ background: transparent url(/words/sprite.png) 0px 0px no-repeat;
80
+ width:66;
81
+ height:21;
82
+ text-indent:-5000px;
83
+ }
84
+
85
+ .words_of {
86
+ background: transparent url(/words/sprite.png) -66px 0px no-repeat;
87
+ width:19;
88
+ height:21;
89
+ text-indent:-5000px;
90
+ }
91
+
@@ -0,0 +1,28 @@
1
+ .words_set {
2
+ background: transparent url(/words/sprite.png) -85px 0px no-repeat;
3
+ width:26;
4
+ height:21;
5
+ text-indent:-5000px;
6
+ }
7
+
8
+ .words_specified {
9
+ background: transparent url(/words/sprite.png) -111px 0px no-repeat;
10
+ width:70;
11
+ height:21;
12
+ text-indent:-5000px;
13
+ }
14
+
15
+ .words_latitude {
16
+ background: transparent url(/words/sprite.png) 0px 0px no-repeat;
17
+ width:66;
18
+ height:21;
19
+ text-indent:-5000px;
20
+ }
21
+
22
+ .words_of {
23
+ background: transparent url(/words/sprite.png) -66px 0px no-repeat;
24
+ width:19;
25
+ height:21;
26
+ text-indent:-5000px;
27
+ }
28
+
Binary file
Binary file
Binary file
@@ -0,0 +1,24 @@
1
+ .words_of {
2
+ background: transparent url(/words/sprite.png) -66px 0px no-repeat;
3
+ width:19;
4
+ text-indent:-5000px;
5
+ }
6
+
7
+ .words_set {
8
+ background: transparent url(/words/sprite.png) -85px 0px no-repeat;
9
+ width:26;
10
+ text-indent:-5000px;
11
+ }
12
+
13
+ .words_specified {
14
+ background: transparent url(/words/sprite.png) -111px 0px no-repeat;
15
+ width:70;
16
+ text-indent:-5000px;
17
+ }
18
+
19
+ .words_latitude {
20
+ background: transparent url(/words/sprite.png) 0px 0px no-repeat;
21
+ width:66;
22
+ text-indent:-5000px;
23
+ }
24
+
Binary file
@@ -0,0 +1,12 @@
1
+ module PNG
2
+ class Chunk
3
+ def chunk_name
4
+ raise "looks like you havn't subclassed and defined a chunk_name"
5
+ end
6
+
7
+ def to_chunk
8
+ to_check = chunk_name + encode
9
+ [encode.length].pack("N") + to_check + [Zlib.crc32(to_check)].pack("N")
10
+ end
11
+ end
12
+ end
@@ -0,0 +1,63 @@
1
+ class DirectoryProcessor
2
+ def initialize(dir)
3
+ @dir = dir
4
+ files = images
5
+ @sprite = PNG::Sprite.new
6
+ files.each {|f| @sprite.append(PNG::Image.open(f))}
7
+ end
8
+
9
+ def images
10
+ Dir.glob(@dir + "/*.png").reject{|i| i.match /sprite\.png/}
11
+ end
12
+
13
+ def write
14
+ @sprite.write(sprite_file)
15
+ File.open(css_file, 'w') do |f|
16
+ f.write(css)
17
+ end
18
+ end
19
+
20
+ def cleanup
21
+ File.delete(sprite_file) rescue {}
22
+ File.delete(css_file) rescue {}
23
+ end
24
+
25
+ def sprite_file
26
+ @dir + "/sprite.png"
27
+ end
28
+
29
+ def css_file
30
+ @dir + "/fragment.css"
31
+ end
32
+
33
+ def dir_name
34
+ @dir.split('/').last
35
+ end
36
+
37
+ def image_loc
38
+ #TODO: Lame!
39
+ ("/" + @dir + "/sprite.png").gsub("/./", "/")
40
+ end
41
+
42
+ FRAG = <<-EOF
43
+ .<name>_<image_name> {
44
+ background: transparent url(<image_loc>) <offset>px 0px no-repeat;
45
+ width:<width>;
46
+ height:<height>;
47
+ text-indent:-5000px;
48
+ }
49
+
50
+ EOF
51
+
52
+ def css
53
+ @sprite.locations.inject("") do |out, image|
54
+ image_name, properties = image
55
+ out << FRAG.gsub("<name>", dir_name).
56
+ gsub("<image_name>", image_name.to_s).
57
+ gsub("<width>", properties[:width].to_s).
58
+ gsub("<height>", properties[:height].to_s).
59
+ gsub("<offset>", properties[:x].to_s).
60
+ gsub("<image_loc>", image_loc)
61
+ end
62
+ end
63
+ end
@@ -0,0 +1,7 @@
1
+ module PNG
2
+ class FileHeader
3
+ def encode
4
+ [137, 80, 78, 71, 13, 10, 26, 10].pack("C*")
5
+ end
6
+ end
7
+ end
@@ -0,0 +1,64 @@
1
+ module PNG
2
+ class Filters
3
+ class << self
4
+ #TODO: feature envy, this behavior belongs to a row.
5
+ def fetch_pixel(idx, row)
6
+ return 0 if idx < 0
7
+ return row[idx] || 0
8
+ end
9
+
10
+ def call( filter_type, value, index, row, last_row, record_width )
11
+ case filter_type
12
+ when 0
13
+ no_filter( value, index, row, last_row, record_width )
14
+ when 1
15
+ left( value, index, row, last_row, record_width )
16
+ when 2
17
+ up( value, index, row, last_row, record_width )
18
+ when 3
19
+ avg( value, index, row, last_row, record_width )
20
+ when 4
21
+ paeth( value, index, row, last_row, record_width )
22
+ else
23
+ raise "Invalid filter type (#{filter_type})"
24
+ end
25
+ end
26
+
27
+ def no_filter( value, index, row, last_row, record_width )
28
+ value
29
+ end
30
+
31
+ def left( value, index, row, last_row, record_width )
32
+ (value + fetch_pixel(index - record_width, row)) % 256
33
+ end
34
+
35
+ def up( value, index, row, last_row, record_width )
36
+ (value + fetch_pixel(index, last_row)) % 256
37
+ end
38
+
39
+ def avg( value, index, row, last_row, record_width )
40
+ (value + ( (fetch_pixel(index - record_width, row) + fetch_pixel(index, last_row)) / 2 ).floor) % 256
41
+ end
42
+
43
+ def paeth( value, index, row, last_row, record_width )
44
+ a = fetch_pixel(index - record_width, row)
45
+ b = fetch_pixel(index, last_row)
46
+ c = fetch_pixel(index - record_width, last_row)
47
+ p = a + b - c
48
+ pa = (p - a).abs
49
+ pb = (p - b).abs
50
+ pc = (p - c).abs
51
+
52
+ pr = c
53
+ if ( pa <= pb and pa <= pc)
54
+ pr = a
55
+ elsif (pb <= pc)
56
+ pr = b
57
+ end
58
+
59
+ (value + pr) % 256
60
+ end
61
+
62
+ end
63
+ end
64
+ end
@@ -0,0 +1,26 @@
1
+ module PNG
2
+ class IDAT < Chunk
3
+ # I don't like that @compressed contains different values depending on how you're using it
4
+ # maybe we should introduce a builder?
5
+ def initialize( uncompressed="" )
6
+ @compressed = ""
7
+ @compressed += Zlib::Deflate.deflate( uncompressed.pack("C*") ) unless uncompressed == ""
8
+ end
9
+
10
+ def <<( data )
11
+ @compressed << data
12
+ end
13
+
14
+ def encode
15
+ @compressed
16
+ end
17
+
18
+ def uncompressed
19
+ @uncompressed ||= Zlib::Inflate.inflate( @compressed ).unpack("C*")
20
+ end
21
+
22
+ def chunk_name
23
+ "IDAT"
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,9 @@
1
+ module PNG
2
+ class IEND < Chunk
3
+ def encode; "" end
4
+
5
+ def chunk_name
6
+ "IEND"
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,30 @@
1
+ module PNG
2
+ class IHDR < Chunk
3
+ attr_accessor :width, :height, :depth, :color_type
4
+ # attr_accessor :compression_method, :filter_method, :interlace_method
5
+
6
+ def self.new_from_raw( data )
7
+ raw = data.unpack("N2C5")
8
+
9
+ new( *raw[0..3] )
10
+ end
11
+
12
+ def initialize( width, height, depth=8, color_type=2 )
13
+ raise "for now, spittle only supports images with a bit depth of 8" unless depth == 8
14
+ raise "for now, spittle only supports color type 2 or 6" unless color_type == 2 || color_type == 6
15
+ @width, @height, @depth, @color_type = width, height, depth, color_type
16
+ end
17
+
18
+ def encode
19
+ to_a.pack("N2C5")
20
+ end
21
+
22
+ def to_a
23
+ [@width, @height, @depth, @color_type, 0, 0, 0]
24
+ end
25
+
26
+ def chunk_name
27
+ "IHDR"
28
+ end
29
+ end
30
+ end