spittle 0.9.1 → 0.9.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.
@@ -0,0 +1,20 @@
1
+ Copyright (c) 2009 Colin Harris & Tyler Jennings
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
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README CHANGED
@@ -12,14 +12,14 @@ It takes your PNG's, chews them up and spits out sprites!
12
12
 
13
13
  point bin/spittle at a directory, and watch it sprite away!
14
14
 
15
- INSTALLATION
15
+ INSTALLATION - standalone
16
16
 
17
17
  We're hosted at gemcutter. I believe 'sudo gem install gemcutter' then 'gem tumble'.
18
18
  Then you can run:
19
19
 
20
20
  > sudo gem install spittle
21
21
 
22
- USAGE
22
+ USAGE - standalone
23
23
 
24
24
  > spittle <directory>
25
25
 
@@ -27,25 +27,44 @@ For a full list of options:
27
27
 
28
28
  > spittle -h
29
29
 
30
+ INSTALLATION - Rails plugin
31
+
32
+ >
33
+
34
+ USAGE - Rails plugin
35
+
36
+ Spittle assumes all of your sprites are located in the directory public/images/sprites. This directory should contian sub-directories for each sprite you wish to create. The css class names for an image in a sprite will take the form <directory_name>_<image_nama>. Here is an example:
37
+
38
+ sprites /
39
+ cars /
40
+ ford.png
41
+ chevy.png
42
+ planes /
43
+ boeing.png
44
+ cesna.png
45
+
46
+ Running spittle:generate does all the work. Each sprite directory (cars, planes) will now contain a sprite.png. Spittle will also generate a sprites.css stylesheet in public/stylesheets/ that you should include in your layout. If you wished to use the ford image from the cars sprite you would give the 'cars_ford' class to the desired element in the view. That's it!
47
+
48
+ Check out examples/sprites if you want to see what spittle can do without doing any work.
49
+
30
50
  LIMITATIONS
31
51
 
32
- - only supports images of the same height and color type.
33
- - only supports RGB and RGBA color types
52
+ - only supports RGB and RGBA color types (and they must be the same for all images in a sprite)
34
53
  - does not support color profiles (can cause a slight change in color from the source image)
35
54
 
36
55
  FEATURES
37
56
 
38
- - automatically generates PNG sprites from a set of PNG images
57
+ - automatically generates sprites from a set of PNG images
39
58
  - automatically generates css classes to access images within the sprite
40
- - customize the css template for each sprite
59
+ - customizeble css templates
60
+ - Rails plugin & rake tasks (sprite:generate & sprite:cleanup)
61
+ - Supports varying dimensions in source images
41
62
 
42
63
  ROADMAP - by priority
43
64
 
44
- - Rails plugin & rake tasks
45
- - Support varying height in source images
46
65
  - Use mtime tracking to prevent regeneration of sprites that have not changed
47
- - Support color profiles
48
66
  - Support global css template override
67
+ - Improve image compression
49
68
 
50
69
  AUTHORS
51
70
 
data/VERSION CHANGED
@@ -1 +1 @@
1
- 0.9.1
1
+ 0.9.1.3
@@ -0,0 +1,61 @@
1
+ #!/usr/bin/env ruby
2
+ require File.join( File.dirname( __FILE__ ), '..', 'lib', 'spittle' )
3
+
4
+ module PNG
5
+ class Parser
6
+ def go!( file )
7
+ check_header( file )
8
+
9
+ while(! file.eof?) do
10
+ parse_chunk(file)
11
+ end
12
+ end
13
+
14
+ private
15
+ def check_header( file )
16
+ header = file.read(8)
17
+ raise "Invalid PNG file header" unless ( header == FileHeader.new.encode)
18
+ end
19
+
20
+ def parse_chunk(f)
21
+ len = f.read(4).unpack("N")
22
+ type = f.read(4)
23
+ data = f.read(len[0])
24
+ crc = f.read(4)
25
+ handle(type, len, data )
26
+ end
27
+
28
+ def handle( type, len, data )
29
+ case type
30
+ when "IHDR"
31
+ attrs = data.unpack("N2C5")
32
+ puts "PNG width: #{attrs[0]}"
33
+ puts "PNG height: #{attrs[1]}"
34
+ puts "PNG depth: #{attrs[2]}"
35
+ puts "PNG color type: #{attrs[3]}"
36
+ puts "PNG compression method: #{attrs[4]}"
37
+ puts "PNG filter method: #{attrs[5]}"
38
+ puts "PNG interlace method: #{attrs[6]}"
39
+ end
40
+
41
+ puts "PNG block #{type}"
42
+ puts "length: #{len}"
43
+ end
44
+ end
45
+
46
+
47
+ class Info
48
+ def self.open( file_name )
49
+ @parser = Parser.new
50
+
51
+ name = File.basename( file_name, ".png" )
52
+
53
+ File.open(file_name, "r") do |f|
54
+ @parser.go!( f )
55
+ end
56
+ end
57
+ end
58
+ end
59
+
60
+
61
+ PNG::Info.open( ARGV[0] )
@@ -1,61 +1,61 @@
1
- .apple_iphone {
2
- background: transparent url(/tacos/apple/sprite.png) -234px 0px no-repeat;
1
+ .apple_mac {
2
+ background: transparent url(/apple/sprite.png) -466px 0px no-repeat;
3
3
  width:116;
4
4
  height:38;
5
5
  text-indent:-5000px;
6
6
  }
7
7
 
8
- .apple_itunes {
9
- background: transparent url(/tacos/apple/sprite.png) -350px 0px no-repeat;
10
- width:116;
8
+ .apple_apple {
9
+ background: transparent url(/apple/sprite.png) 0px 0px no-repeat;
10
+ width:117;
11
11
  height:38;
12
12
  text-indent:-5000px;
13
13
  }
14
14
 
15
- .apple_mac {
16
- background: transparent url(/tacos/apple/sprite.png) -466px 0px no-repeat;
15
+ .apple_support {
16
+ background: transparent url(/apple/sprite.png) -860px 0px no-repeat;
17
17
  width:116;
18
18
  height:38;
19
19
  text-indent:-5000px;
20
20
  }
21
21
 
22
- .apple_apple {
23
- background: transparent url(/tacos/apple/sprite.png) 0px 0px no-repeat;
24
- width:117;
22
+ .apple_divider {
23
+ background: transparent url(/apple/sprite.png) -117px 0px no-repeat;
24
+ width:1;
25
25
  height:38;
26
26
  text-indent:-5000px;
27
27
  }
28
28
 
29
- .apple_support {
30
- background: transparent url(/tacos/apple/sprite.png) -860px 0px no-repeat;
31
- width:116;
29
+ .apple_search {
30
+ background: transparent url(/apple/sprite.png) -582px 0px no-repeat;
31
+ width:162;
32
32
  height:38;
33
33
  text-indent:-5000px;
34
34
  }
35
35
 
36
- .apple_search {
37
- background: transparent url(/tacos/apple/sprite.png) -582px 0px no-repeat;
38
- width:162;
36
+ .apple_downloads {
37
+ background: transparent url(/apple/sprite.png) -118px 0px no-repeat;
38
+ width:116;
39
39
  height:38;
40
40
  text-indent:-5000px;
41
41
  }
42
42
 
43
- .apple_divider {
44
- background: transparent url(/tacos/apple/sprite.png) -117px 0px no-repeat;
45
- width:1;
43
+ .apple_iphone {
44
+ background: transparent url(/apple/sprite.png) -234px 0px no-repeat;
45
+ width:116;
46
46
  height:38;
47
47
  text-indent:-5000px;
48
48
  }
49
49
 
50
50
  .apple_store {
51
- background: transparent url(/tacos/apple/sprite.png) -744px 0px no-repeat;
51
+ background: transparent url(/apple/sprite.png) -744px 0px no-repeat;
52
52
  width:116;
53
53
  height:38;
54
54
  text-indent:-5000px;
55
55
  }
56
56
 
57
- .apple_downloads {
58
- background: transparent url(/tacos/apple/sprite.png) -118px 0px no-repeat;
57
+ .apple_itunes {
58
+ background: transparent url(/apple/sprite.png) -350px 0px no-repeat;
59
59
  width:116;
60
60
  height:38;
61
61
  text-indent:-5000px;
@@ -7,11 +7,14 @@
7
7
  <style>
8
8
  .float_left {float:left}
9
9
  .menu {height: 100px}
10
+ .clear {clear:both}
10
11
  </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>
12
+ <div>
13
+ Hello. See spittle run:
14
+ <div class="words_of">of</div>
15
+ <div class="words_latitude">latitude</div>
16
+ <div class="words_set">set</div>
17
+ </div>
15
18
 
16
19
  Some random menu bar
17
20
  <div>
@@ -28,6 +31,13 @@
28
31
  <span class="menu float_left apple_support">suport</span>
29
32
  <span class="menu float_left apple_divider"></span>
30
33
  <span class="menu float_left apple_search">search</span>
34
+ <div class="clear"/>
35
+ </div>
36
+ OMG Cats!!!! (of varying sizes, in one sprite)
37
+ <div>
38
+ <div class="many_sized_cats_cat-on-keyboard"></div>
39
+ <div class="many_sized_cats_darth_cat"></div>
40
+ <div class="many_sized_cats_music-keyboard-cat"></div>
31
41
  </div>
32
42
  </body>
33
43
  </html>
@@ -0,0 +1,21 @@
1
+ .many_sized_cats_cat-on-keyboard {
2
+ background: transparent url(/many_sized_cats/sprite.png) 0px 0px no-repeat;
3
+ width:64;
4
+ height:48;
5
+ text-indent:-5000px;
6
+ }
7
+
8
+ .many_sized_cats_music-keyboard-cat {
9
+ background: transparent url(/many_sized_cats/sprite.png) -304px 0px no-repeat;
10
+ width:225;
11
+ height:166;
12
+ text-indent:-5000px;
13
+ }
14
+
15
+ .many_sized_cats_darth_cat {
16
+ background: transparent url(/many_sized_cats/sprite.png) -64px 0px no-repeat;
17
+ width:240;
18
+ height:320;
19
+ text-indent:-5000px;
20
+ }
21
+
@@ -1,10 +1,3 @@
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
1
  .apple_mac {
9
2
  background: transparent url(/apple/sprite.png) -466px 0px no-repeat;
10
3
  width:116;
@@ -19,13 +12,20 @@
19
12
  text-indent:-5000px;
20
13
  }
21
14
 
22
- .apple_store {
23
- background: transparent url(/apple/sprite.png) -744px 0px no-repeat;
15
+ .apple_support {
16
+ background: transparent url(/apple/sprite.png) -860px 0px no-repeat;
24
17
  width:116;
25
18
  height:38;
26
19
  text-indent:-5000px;
27
20
  }
28
21
 
22
+ .apple_divider {
23
+ background: transparent url(/apple/sprite.png) -117px 0px no-repeat;
24
+ width:1;
25
+ height:38;
26
+ text-indent:-5000px;
27
+ }
28
+
29
29
  .apple_search {
30
30
  background: transparent url(/apple/sprite.png) -582px 0px no-repeat;
31
31
  width:162;
@@ -33,45 +33,52 @@
33
33
  text-indent:-5000px;
34
34
  }
35
35
 
36
- .apple_divider {
37
- background: transparent url(/apple/sprite.png) -117px 0px no-repeat;
38
- width:1;
36
+ .apple_downloads {
37
+ background: transparent url(/apple/sprite.png) -118px 0px no-repeat;
38
+ width:116;
39
39
  height:38;
40
40
  text-indent:-5000px;
41
41
  }
42
42
 
43
- .apple_support {
44
- background: transparent url(/apple/sprite.png) -860px 0px no-repeat;
43
+ .apple_iphone {
44
+ background: transparent url(/apple/sprite.png) -234px 0px no-repeat;
45
45
  width:116;
46
46
  height:38;
47
47
  text-indent:-5000px;
48
48
  }
49
49
 
50
- .apple_downloads {
51
- background: transparent url(/apple/sprite.png) -118px 0px no-repeat;
50
+ .apple_store {
51
+ background: transparent url(/apple/sprite.png) -744px 0px no-repeat;
52
52
  width:116;
53
53
  height:38;
54
54
  text-indent:-5000px;
55
55
  }
56
56
 
57
- .apple_iphone {
58
- background: transparent url(/apple/sprite.png) -234px 0px no-repeat;
57
+ .apple_itunes {
58
+ background: transparent url(/apple/sprite.png) -350px 0px no-repeat;
59
59
  width:116;
60
60
  height:38;
61
61
  text-indent:-5000px;
62
62
  }
63
63
 
64
- .words_set {
65
- background: transparent url(/words/sprite.png) -85px 0px no-repeat;
66
- width:26;
67
- height:21;
64
+ .many_sized_cats_cat-on-keyboard {
65
+ background: transparent url(/many_sized_cats/sprite.png) 0px 0px no-repeat;
66
+ width:64;
67
+ height:48;
68
68
  text-indent:-5000px;
69
69
  }
70
70
 
71
- .words_specified {
72
- background: transparent url(/words/sprite.png) -111px 0px no-repeat;
73
- width:70;
74
- height:21;
71
+ .many_sized_cats_music-keyboard-cat {
72
+ background: transparent url(/many_sized_cats/sprite.png) -304px 0px no-repeat;
73
+ width:225;
74
+ height:166;
75
+ text-indent:-5000px;
76
+ }
77
+
78
+ .many_sized_cats_darth_cat {
79
+ background: transparent url(/many_sized_cats/sprite.png) -64px 0px no-repeat;
80
+ width:240;
81
+ height:320;
75
82
  text-indent:-5000px;
76
83
  }
77
84
 
@@ -89,3 +96,17 @@
89
96
  text-indent:-5000px;
90
97
  }
91
98
 
99
+ .words_set {
100
+ background: transparent url(/words/sprite.png) -85px 0px no-repeat;
101
+ width:26;
102
+ height:21;
103
+ text-indent:-5000px;
104
+ }
105
+
106
+ .words_specified {
107
+ background: transparent url(/words/sprite.png) -111px 0px no-repeat;
108
+ width:70;
109
+ height:21;
110
+ text-indent:-5000px;
111
+ }
112
+
@@ -1,27 +1,27 @@
1
- .words_set {
2
- background: transparent url(/words/sprite.png) -85px 0px no-repeat;
3
- width:26;
1
+ .words_latitude {
2
+ background: transparent url(/words/sprite.png) 0px 0px no-repeat;
3
+ width:66;
4
4
  height:21;
5
5
  text-indent:-5000px;
6
6
  }
7
7
 
8
- .words_specified {
9
- background: transparent url(/words/sprite.png) -111px 0px no-repeat;
10
- width:70;
8
+ .words_of {
9
+ background: transparent url(/words/sprite.png) -66px 0px no-repeat;
10
+ width:19;
11
11
  height:21;
12
12
  text-indent:-5000px;
13
13
  }
14
14
 
15
- .words_latitude {
16
- background: transparent url(/words/sprite.png) 0px 0px no-repeat;
17
- width:66;
15
+ .words_set {
16
+ background: transparent url(/words/sprite.png) -85px 0px no-repeat;
17
+ width:26;
18
18
  height:21;
19
19
  text-indent:-5000px;
20
20
  }
21
21
 
22
- .words_of {
23
- background: transparent url(/words/sprite.png) -66px 0px no-repeat;
24
- width:19;
22
+ .words_specified {
23
+ background: transparent url(/words/sprite.png) -111px 0px no-repeat;
24
+ width:70;
25
25
  height:21;
26
26
  text-indent:-5000px;
27
27
  }
@@ -1,24 +1,28 @@
1
1
  .words_of {
2
- background: transparent url(/words/sprite.png) -66px 0px no-repeat;
2
+ background: transparent url(/examples/sprites/words/sprite.png) -66px 0px no-repeat;
3
3
  width:19;
4
+ height:21;
4
5
  text-indent:-5000px;
5
6
  }
6
7
 
7
8
  .words_set {
8
- background: transparent url(/words/sprite.png) -85px 0px no-repeat;
9
+ background: transparent url(/examples/sprites/words/sprite.png) -85px 0px no-repeat;
9
10
  width:26;
11
+ height:21;
10
12
  text-indent:-5000px;
11
13
  }
12
14
 
13
15
  .words_specified {
14
- background: transparent url(/words/sprite.png) -111px 0px no-repeat;
16
+ background: transparent url(/examples/sprites/words/sprite.png) -111px 0px no-repeat;
15
17
  width:70;
18
+ height:21;
16
19
  text-indent:-5000px;
17
20
  }
18
21
 
19
22
  .words_latitude {
20
- background: transparent url(/words/sprite.png) 0px 0px no-repeat;
23
+ background: transparent url(/examples/sprites/words/sprite.png) 0px 0px no-repeat;
21
24
  width:66;
25
+ height:21;
22
26
  text-indent:-5000px;
23
27
  }
24
28
 
@@ -1,64 +1,46 @@
1
1
  module PNG
2
2
  class Filters
3
3
  class << self
4
- #TODO: feature envy, this behavior belongs to a row.
5
4
  def fetch_pixel(idx, row)
6
- return 0 if idx < 0
7
- return row[idx] || 0
5
+ idx < 0 ? 0 : row[idx] || 0
8
6
  end
9
7
 
8
+ #filter methods are inlined here for performance
10
9
  def call( filter_type, value, index, row, last_row, record_width )
11
10
  case filter_type
12
11
  when 0
13
- no_filter( value, index, row, last_row, record_width )
12
+ #no filter
13
+ value
14
14
  when 1
15
- left( value, index, row, last_row, record_width )
15
+ #up
16
+ (value + fetch_pixel(index - record_width, row)) % 256
16
17
  when 2
17
- up( value, index, row, last_row, record_width )
18
+ #left
19
+ (value + fetch_pixel(index, last_row)) % 256
18
20
  when 3
19
- avg( value, index, row, last_row, record_width )
21
+ #avg
22
+ (value + ( (fetch_pixel(index - record_width, row) + fetch_pixel(index, last_row)) / 2 ).floor) % 256
20
23
  when 4
21
- paeth( value, index, row, last_row, record_width )
24
+ #paeth
25
+ a = fetch_pixel(index - record_width, row)
26
+ b = fetch_pixel(index, last_row)
27
+ c = fetch_pixel(index - record_width, last_row)
28
+ p = a + b - c
29
+ pa = (p - a).abs
30
+ pb = (p - b).abs
31
+ pc = (p - c).abs
32
+
33
+ pr = c
34
+ if ( pa <= pb and pa <= pc)
35
+ pr = a
36
+ elsif (pb <= pc)
37
+ pr = b
38
+ end
39
+ (value + pr) % 256
22
40
  else
23
41
  raise "Invalid filter type (#{filter_type})"
24
42
  end
25
43
  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
44
  end
63
45
  end
64
46
  end
@@ -1,7 +1,7 @@
1
1
  module PNG
2
2
  class IDAT < Chunk
3
3
  # I don't like that @compressed contains different values depending on how you're using it
4
- # maybe we should introduce a builder?
4
+ # maybe we should introduce a builder?
5
5
  def initialize( uncompressed="" )
6
6
  @compressed = ""
7
7
  @compressed += Zlib::Deflate.deflate( uncompressed.pack("C*") ) unless uncompressed == ""
@@ -10,15 +10,15 @@ module PNG
10
10
  def <<( data )
11
11
  @compressed << data
12
12
  end
13
-
13
+
14
14
  def encode
15
15
  @compressed
16
16
  end
17
-
17
+
18
18
  def uncompressed
19
19
  @uncompressed ||= Zlib::Inflate.inflate( @compressed ).unpack("C*")
20
20
  end
21
-
21
+
22
22
  def chunk_name
23
23
  "IDAT"
24
24
  end
@@ -1,59 +1,81 @@
1
1
  module PNG
2
2
  class Image
3
-
3
+
4
4
  def self.open( file_name )
5
5
  name = File.basename( file_name, ".png" )
6
-
6
+
7
7
  File.open(file_name, "r") do |f|
8
8
  ihdr, idat = Parser.go!( f )
9
9
  Image.new( ihdr, idat, name )
10
10
  end
11
-
11
+
12
12
  end
13
-
13
+
14
14
  def initialize( ihdr, idat, name )
15
15
  @ihdr = ihdr
16
16
  @idat = idat
17
17
  @name = name
18
18
  end
19
-
19
+
20
20
  attr_reader :name
21
21
  def width; @ihdr.width end
22
22
  def height; @ihdr.height end
23
23
  def depth; @ihdr.depth end
24
24
  def color_type; @ihdr.color_type end
25
25
 
26
+ # need better checks, because currently compatible is
27
+ # similar color type, or depth.. maybe it doesn't matter...
26
28
  def compatible?(image)
27
- self.height == image.height
29
+ self.color_type == image.color_type &&
30
+ self.depth == image.depth
28
31
  end
29
-
32
+
30
33
  def write(file_name)
31
34
  File.open(file_name, 'w') do |f|
32
35
  f.write(generate_png)
33
36
  end
34
37
  end
35
38
 
39
+ def fill_to_height( desired_height )
40
+ raise "invalid height" if desired_height < height
41
+ return self if desired_height == height
42
+
43
+ data = @idat.uncompressed
44
+
45
+ empty_row = [0] + [0] * ( width * pixel_width )
46
+
47
+
48
+ ( desired_height - height ).times do
49
+ data = data + empty_row
50
+ end
51
+
52
+ ihdr = IHDR.new( width, desired_height, depth, color_type )
53
+ idat = IDAT.new( data )
54
+
55
+ Image.new( ihdr, idat, name )
56
+ end
57
+
36
58
  def to_s
37
- "#{@name} (#{height} x #{width})"
59
+ "#{@name} (#{height} x #{width}) [color type: #{color_type}, depth: #{depth}]"
38
60
  end
39
-
61
+
40
62
  def merge_left( other )
41
63
  l = other.rows
42
64
  r = self.rows
43
-
65
+
44
66
  data = l.zip r
45
-
67
+
46
68
  #prepend the filter byte 0 = no filter
47
- data.each { |row| row.unshift(0) }
69
+ data.each { |row| row.unshift(0) }
48
70
  data.flatten!
49
-
71
+
50
72
  ihdr = IHDR.new( width + other.width, height, depth, color_type)
51
73
  idat = IDAT.new( data )
52
74
  img_name = "#{name}_#{other.name}"
53
-
75
+
54
76
  Image.new( ihdr, idat, img_name )
55
77
  end
56
-
78
+
57
79
  #color types
58
80
  RGB = 2
59
81
  RGBA = 3
@@ -67,51 +89,52 @@ module PNG
67
89
  # + 1 adds filter byte
68
90
  (width * pixel_width) + 1
69
91
  end
70
-
92
+
71
93
  def rows
72
- out = []
94
+ uncompressed = @idat.uncompressed
95
+ out = Array.new(height)
73
96
  offset = 0
74
-
97
+
75
98
  height.times do |scanline|
76
99
  end_row = scanline_width + offset
77
- row = @idat.uncompressed.slice(offset, scanline_width)
78
- out << decode(scanline, row, out, pixel_width)
100
+ row = uncompressed.slice(offset, scanline_width)
101
+ out[scanline] = decode(scanline, row, out, pixel_width)
79
102
  offset = end_row
80
103
  end
81
104
  out
82
105
  end
83
-
106
+
84
107
  def inspect
85
- "color type: #{color_type}, depth: #{depth}, width: #{width}, height: #{height}"
108
+ "name: #{name}, color type: #{color_type}, depth: #{depth}, width: #{width}, height: #{height}"
86
109
  end
87
110
  private
88
111
 
89
112
  def last_scanline(current, data)
90
113
  (current - 1 < 0 ? [] : data[current - 1])
91
114
  end
92
-
115
+
93
116
  def decode(current, row, data, pixel_width)
94
117
  filter_type = row.shift
95
-
118
+
96
119
  process_row(row, last_scanline(current, data), filter_type, pixel_width)
97
120
  end
98
-
121
+
99
122
  def process_row(row, last_scanline, filter_type, pixel_width)
100
- o = []
123
+ o = Array.new(row.size)
101
124
  row.each_with_index do |e, i|
102
125
  o[i] = Filters.call(filter_type, e, i, o, last_scanline, pixel_width)
103
126
  end
104
127
  o
105
128
  end
106
-
129
+
107
130
  def generate_png
108
131
  file_header = PNG::FileHeader.new.encode
109
132
  raw_data = @idat.uncompressed
110
-
133
+
111
134
  ihdr = PNG::IHDR.new( width, height, depth, color_type ).to_chunk
112
135
  idat = PNG::IDAT.new( raw_data ).to_chunk
113
136
  iend = PNG::IEND.new.to_chunk
114
-
137
+
115
138
  file_header + ihdr + idat + iend
116
139
  end
117
140
  end
@@ -1,24 +1,26 @@
1
1
  module PNG
2
2
  class ImageFormatException < Exception; end
3
3
  class Sprite
4
- attr_reader :images
4
+ attr_reader :images, :max_height
5
5
 
6
6
  def initialize
7
7
  @images = []
8
8
  @locations = {}
9
9
  end
10
-
10
+
11
11
  def append( image )
12
12
  @images.each do |i|
13
13
  unless i.compatible? image
14
- raise ImageFormatException.new("Incompatible image #{i}")
14
+ raise ImageFormatException.new("Image #{i} not compatible with #{image}")
15
15
  end
16
16
  end
17
+
17
18
  @images << image
19
+ @max_height = @images.map{ |i| i.height }.max
18
20
  end
19
-
21
+
20
22
  def locations
21
- @images.inject(0) do |x, image|
23
+ @images.inject(0) do |x, image|
22
24
  @locations[image.name.to_sym] = { :x => -(x),
23
25
  :width => image.width,
24
26
  :height => image.height}
@@ -29,9 +31,9 @@ module PNG
29
31
 
30
32
  def write( output_filename )
31
33
  return if @images.empty?
32
-
34
+ right_sized = @images.map{|i| i.fill_to_height(@max_height)}
33
35
  # head is the last image, then we merge left
34
- head, *tail = @images.reverse
36
+ head, *tail = right_sized.reverse
35
37
  result = tail.inject( head ){ |head, image| head.merge_left( image ) }
36
38
  result.write( output_filename )
37
39
  end
@@ -27,4 +27,5 @@ describe PNG::IDAT do
27
27
 
28
28
  @idat.encode.should == @data
29
29
  end
30
+
30
31
  end
@@ -16,4 +16,10 @@ describe PNG::Image do
16
16
 
17
17
  result.rows.should == [[4,5,6,1,2,3]]
18
18
  end
19
+
20
+ it "can insert empty rows to convert an image to a specific height" do
21
+ result = @image1.fill_to_height(2)
22
+ result.rows.should == [[1, 2, 3],
23
+ [0, 0, 0]]
24
+ end
19
25
  end
@@ -20,17 +20,19 @@ describe PNG::Sprite do
20
20
  @sprite.append( @image1 )
21
21
  @sprite.append( @image2 )
22
22
 
23
- @sprite.locations[@image1.name.to_sym].should == {:x => -( 0 ), :width=> @image1.width, :height => @image1.height }
24
- @sprite.locations[@image2.name.to_sym].should == {:x => -( @image2.width ), :width=> @image2.width, :height => @image2.height }
23
+ @sprite.locations[@image1.name.to_sym].should == {:x => -( 0 ), :width=> @image1.width, :height => @image1.height }
24
+ @sprite.locations[@image2.name.to_sym].should == {:x => -( @image2.width ), :width=> @image2.width, :height => @image2.height }
25
25
  end
26
26
 
27
- it "raises a pretty exception when the images are incompatible" do
28
- taller_image = @builder.build( :width => 50, :height => 60, :name => "image")
29
-
30
- lambda do
31
- @sprite.append taller_image
32
- @sprite.append @image1
33
- end.should raise_error(PNG::ImageFormatException)
27
+ it "knows the height of the tallest image" do
28
+ max_height = 70
29
+
30
+ @image3 = @builder.build( :width => 50, :height => max_height, :name => "image3")
31
+
32
+ @sprite.append( @image1 )
33
+ @sprite.append( @image3 )
34
+
35
+ @sprite.max_height.should == max_height
34
36
  end
35
37
 
36
38
  end
@@ -1,5 +1,6 @@
1
1
  $LOAD_PATH.unshift(File.dirname(__FILE__))
2
2
  $LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
3
+ require 'rubygems'
3
4
  require 'spittle'
4
5
  require 'spec'
5
6
  require 'spec/autorun'
@@ -5,23 +5,24 @@
5
5
 
6
6
  Gem::Specification.new do |s|
7
7
  s.name = %q{spittle}
8
- s.version = "0.9.1"
8
+ s.version = "0.9.1.3"
9
9
 
10
10
  s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
11
11
  s.authors = ["aberant", "tjennings"]
12
- s.date = %q{2009-11-16}
13
- s.default_executable = %q{spittle}
12
+ s.date = %q{2009-11-17}
14
13
  s.description = %q{pure ruby PNG}
15
14
  s.email = ["qzzzq1@gmail.com", "tyler.jennings@gmail.com"]
16
- s.executables = ["spittle"]
15
+ s.executables = ["png_info", "spittle"]
17
16
  s.extra_rdoc_files = [
18
17
  "README"
19
18
  ]
20
19
  s.files = [
21
20
  ".gitignore",
21
+ "MIT-LICENSE",
22
22
  "README",
23
23
  "Rakefile",
24
24
  "VERSION",
25
+ "bin/png_info",
25
26
  "bin/spittle",
26
27
  "examples/sprites/README",
27
28
  "examples/sprites/apple/apple.png",
@@ -37,6 +38,11 @@ Gem::Specification.new do |s|
37
38
  "examples/sprites/apple/support.png",
38
39
  "examples/sprites/fragment.css",
39
40
  "examples/sprites/index.html",
41
+ "examples/sprites/many_sized_cats/cat-on-keyboard.png",
42
+ "examples/sprites/many_sized_cats/darth_cat.png",
43
+ "examples/sprites/many_sized_cats/fragment.css",
44
+ "examples/sprites/many_sized_cats/music-keyboard-cat.png",
45
+ "examples/sprites/many_sized_cats/sprite.png",
40
46
  "examples/sprites/server.rb",
41
47
  "examples/sprites/sprite.css",
42
48
  "examples/sprites/words/fragment.css",
@@ -1,9 +1,25 @@
1
- desc "Generates sprites"
2
- task :sprite do
3
- require File.dirname(__FILE__) + "/../lib/spittle.rb"
1
+ #TODO - make this a rake task instead of a function
2
+ module Spittle
3
+ def self.setup
4
+ require 'fileutils'
5
+ require File.dirname(__FILE__) + "/../lib/spittle.rb"
4
6
 
5
- opts = {:source => RAILS_ROOT + "/public/images/sprites",
6
- :path_prefix => "/images"}
7
+ FileUtils.cd(RAILS_ROOT + "/public/images")
8
+ opts = {:source => "sprites",
9
+ :path_prefix => "/images",
10
+ :css_file => RAILS_ROOT + '/public/stylesheets/sprites.css'}
11
+ end
12
+ end
7
13
 
8
- Spittle::Processor.new(opts).write
9
- end
14
+ namespace :sprite do
15
+ desc "Generates sprite images and stylesheets"
16
+ task :generate do
17
+ opts = Spittle.setup
18
+ Spittle::Processor.new(opts).write
19
+ end
20
+ desc "Removes generated sprite images and stylesheets"
21
+ task :cleanup do
22
+ opts = Spittle.setup
23
+ Spittle::Processor.new(opts).cleanup
24
+ end
25
+ end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: spittle
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.9.1
4
+ version: 0.9.1.3
5
5
  platform: ruby
6
6
  authors:
7
7
  - aberant
@@ -10,8 +10,8 @@ autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
12
 
13
- date: 2009-11-16 00:00:00 -06:00
14
- default_executable: spittle
13
+ date: 2009-11-17 00:00:00 -06:00
14
+ default_executable:
15
15
  dependencies:
16
16
  - !ruby/object:Gem::Dependency
17
17
  name: rspec
@@ -28,6 +28,7 @@ email:
28
28
  - qzzzq1@gmail.com
29
29
  - tyler.jennings@gmail.com
30
30
  executables:
31
+ - png_info
31
32
  - spittle
32
33
  extensions: []
33
34
 
@@ -35,9 +36,11 @@ extra_rdoc_files:
35
36
  - README
36
37
  files:
37
38
  - .gitignore
39
+ - MIT-LICENSE
38
40
  - README
39
41
  - Rakefile
40
42
  - VERSION
43
+ - bin/png_info
41
44
  - bin/spittle
42
45
  - examples/sprites/README
43
46
  - examples/sprites/apple/apple.png
@@ -53,6 +56,11 @@ files:
53
56
  - examples/sprites/apple/support.png
54
57
  - examples/sprites/fragment.css
55
58
  - examples/sprites/index.html
59
+ - examples/sprites/many_sized_cats/cat-on-keyboard.png
60
+ - examples/sprites/many_sized_cats/darth_cat.png
61
+ - examples/sprites/many_sized_cats/fragment.css
62
+ - examples/sprites/many_sized_cats/music-keyboard-cat.png
63
+ - examples/sprites/many_sized_cats/sprite.png
56
64
  - examples/sprites/server.rb
57
65
  - examples/sprites/sprite.css
58
66
  - examples/sprites/words/fragment.css