spittle 0.9.1 → 0.9.1.3

Sign up to get free protection for your applications and to get access to all the features.
@@ -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