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