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.
- data/MIT-LICENSE +20 -0
- data/README +28 -9
- data/VERSION +1 -1
- data/bin/png_info +61 -0
- data/examples/sprites/apple/fragment.css +22 -22
- data/examples/sprites/index.html +14 -4
- data/examples/sprites/many_sized_cats/cat-on-keyboard.png +0 -0
- data/examples/sprites/many_sized_cats/darth_cat.png +0 -0
- data/examples/sprites/many_sized_cats/fragment.css +21 -0
- data/examples/sprites/many_sized_cats/music-keyboard-cat.png +0 -0
- data/examples/sprites/many_sized_cats/sprite.png +0 -0
- data/examples/sprites/sprite.css +47 -26
- data/examples/sprites/words/fragment.css +12 -12
- data/examples/sprites/words/sprite.css +8 -4
- data/lib/spittle/filters.rb +26 -44
- data/lib/spittle/idat.rb +4 -4
- data/lib/spittle/image.rb +52 -29
- data/lib/spittle/sprite.rb +9 -7
- data/spec/lib/idat_spec.rb +1 -0
- data/spec/lib/image_spec.rb +6 -0
- data/spec/lib/sprite_spec.rb +11 -9
- data/spec/spec_helper.rb +1 -0
- data/spittle.gemspec +10 -4
- data/tasks/spittle_tasks.rake +23 -7
- metadata +11 -3
data/MIT-LICENSE
ADDED
@@ -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
|
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
|
57
|
+
- automatically generates sprites from a set of PNG images
|
39
58
|
- automatically generates css classes to access images within the sprite
|
40
|
-
-
|
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
|
data/bin/png_info
ADDED
@@ -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
|
-
.
|
2
|
-
background: transparent url(/
|
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
|
-
.
|
9
|
-
background: transparent url(/
|
10
|
-
width:
|
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
|
-
.
|
16
|
-
background: transparent url(/
|
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
|
-
.
|
23
|
-
background: transparent url(/
|
24
|
-
width:
|
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
|
-
.
|
30
|
-
background: transparent url(/
|
31
|
-
width:
|
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
|
-
.
|
37
|
-
background: transparent url(/
|
38
|
-
width:
|
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
|
-
.
|
44
|
-
background: transparent url(/
|
45
|
-
width:
|
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(/
|
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
|
-
.
|
58
|
-
background: transparent url(/
|
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;
|
data/examples/sprites/index.html
CHANGED
@@ -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
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
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>
|
Binary file
|
Binary file
|
@@ -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
|
+
|
Binary file
|
Binary file
|
data/examples/sprites/sprite.css
CHANGED
@@ -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
|
-
.
|
23
|
-
background: transparent url(/apple/sprite.png) -
|
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
|
-
.
|
37
|
-
background: transparent url(/apple/sprite.png) -
|
38
|
-
width:
|
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
|
-
.
|
44
|
-
background: transparent url(/apple/sprite.png) -
|
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
|
-
.
|
51
|
-
background: transparent url(/apple/sprite.png) -
|
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
|
-
.
|
58
|
-
background: transparent url(/apple/sprite.png) -
|
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
|
-
.
|
65
|
-
background: transparent url(/
|
66
|
-
width:
|
67
|
-
height:
|
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
|
-
.
|
72
|
-
background: transparent url(/
|
73
|
-
width:
|
74
|
-
height:
|
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
|
-
.
|
2
|
-
background: transparent url(/words/sprite.png)
|
3
|
-
width:
|
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
|
-
.
|
9
|
-
background: transparent url(/words/sprite.png) -
|
10
|
-
width:
|
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
|
-
.
|
16
|
-
background: transparent url(/words/sprite.png)
|
17
|
-
width:
|
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
|
-
.
|
23
|
-
background: transparent url(/words/sprite.png) -
|
24
|
-
width:
|
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
|
|
data/lib/spittle/filters.rb
CHANGED
@@ -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
|
-
|
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
|
-
|
12
|
+
#no filter
|
13
|
+
value
|
14
14
|
when 1
|
15
|
-
|
15
|
+
#up
|
16
|
+
(value + fetch_pixel(index - record_width, row)) % 256
|
16
17
|
when 2
|
17
|
-
|
18
|
+
#left
|
19
|
+
(value + fetch_pixel(index, last_row)) % 256
|
18
20
|
when 3
|
19
|
-
avg
|
21
|
+
#avg
|
22
|
+
(value + ( (fetch_pixel(index - record_width, row) + fetch_pixel(index, last_row)) / 2 ).floor) % 256
|
20
23
|
when 4
|
21
|
-
paeth
|
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
|
data/lib/spittle/idat.rb
CHANGED
@@ -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
|
data/lib/spittle/image.rb
CHANGED
@@ -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.
|
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
|
-
|
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 =
|
78
|
-
out
|
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
|
data/lib/spittle/sprite.rb
CHANGED
@@ -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("
|
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 =
|
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
|
data/spec/lib/idat_spec.rb
CHANGED
data/spec/lib/image_spec.rb
CHANGED
data/spec/lib/sprite_spec.rb
CHANGED
@@ -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 "
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
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
|
data/spec/spec_helper.rb
CHANGED
data/spittle.gemspec
CHANGED
@@ -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-
|
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",
|
data/tasks/spittle_tasks.rake
CHANGED
@@ -1,9 +1,25 @@
|
|
1
|
-
|
2
|
-
|
3
|
-
|
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
|
-
|
6
|
-
|
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
|
-
|
9
|
-
|
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-
|
14
|
-
default_executable:
|
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
|