css-spriter 0.9.2 → 1.0.1
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/README.rdoc +7 -9
- data/Rakefile +7 -8
- data/VERSION +1 -1
- data/bin/css-spriter +1 -1
- data/css-spriter.gemspec +106 -0
- data/lib/css-spriter.rb +2 -11
- data/lib/css-spriter/directory_processor.rb +82 -78
- data/lib/css-spriter/image.rb +13 -0
- data/lib/css-spriter/mtime_tracker.rb +64 -61
- data/lib/css-spriter/sprite.rb +39 -31
- data/lib/css-spriter/stylesheet_builder.rb +19 -17
- data/spec/builders/image_builder.rb +3 -8
- data/spec/integration_spec.rb +27 -50
- data/spec/lib/css_spriter_image_spec.rb +8 -0
- data/spec/lib/sprite_spec.rb +4 -6
- data/spec/mtime_tracking_spec.rb +33 -19
- data/spec/spec_helper.rb +5 -5
- metadata +50 -40
- data/.gitignore +0 -12
- data/bin/png_info +0 -61
- data/examples/filter_util.rb +0 -17
- data/lib/css-spriter/image_data.rb +0 -128
- data/lib/css-spriter/png/chunk.rb +0 -12
- data/lib/css-spriter/png/file_header.rb +0 -7
- data/lib/css-spriter/png/filters.rb +0 -80
- data/lib/css-spriter/png/idat.rb +0 -26
- data/lib/css-spriter/png/iend.rb +0 -9
- data/lib/css-spriter/png/ihdr.rb +0 -34
- data/lib/css-spriter/png/image.rb +0 -153
- data/lib/css-spriter/png/parser.rb +0 -54
- data/spec/image_data_spec.rb +0 -63
- data/spec/lib/file_header_spec.rb +0 -10
- data/spec/lib/idat_spec.rb +0 -31
- data/spec/lib/ihdr_spec.rb +0 -43
- data/spec/lib/image_spec.rb +0 -42
- data/spec/lib/parser_spec.rb +0 -12
data/lib/css-spriter/png/idat.rb
DELETED
@@ -1,26 +0,0 @@
|
|
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
|
data/lib/css-spriter/png/iend.rb
DELETED
data/lib/css-spriter/png/ihdr.rb
DELETED
@@ -1,34 +0,0 @@
|
|
1
|
-
module PNG
|
2
|
-
class IHDR < Chunk
|
3
|
-
SUPPORTED_COLOR_TYPES = [2,3,6]
|
4
|
-
attr_accessor :width, :height, :depth, :color_type
|
5
|
-
# attr_accessor :compression_method, :filter_method, :interlace_method
|
6
|
-
|
7
|
-
def self.new_from_raw( data )
|
8
|
-
raw = data.unpack("N2C5")
|
9
|
-
|
10
|
-
new( *raw[0..6] )
|
11
|
-
end
|
12
|
-
|
13
|
-
def initialize( width, height, depth=8, color_type=2, compression=0, filter=0, interlace=0 )
|
14
|
-
raise "for now, css-spriter only supports non-interlaced images" unless interlace == 0
|
15
|
-
raise "for now, css-spriter only supports images with a bit depth of 8" unless depth == 8
|
16
|
-
unless SUPPORTED_COLOR_TYPES.include? color_type
|
17
|
-
raise "for now, css-spriter only supports color types #{SUPPORTED_COLOR_TYPES.JOIN(',')} color type was #{color_type}"
|
18
|
-
end
|
19
|
-
@width, @height, @depth, @color_type = width, height, depth, color_type
|
20
|
-
end
|
21
|
-
|
22
|
-
def encode
|
23
|
-
to_a.pack("N2C5")
|
24
|
-
end
|
25
|
-
|
26
|
-
def to_a
|
27
|
-
[@width, @height, @depth, @color_type, 0, 0, 0]
|
28
|
-
end
|
29
|
-
|
30
|
-
def chunk_name
|
31
|
-
"IHDR"
|
32
|
-
end
|
33
|
-
end
|
34
|
-
end
|
@@ -1,153 +0,0 @@
|
|
1
|
-
module PNG
|
2
|
-
class Image
|
3
|
-
#color types
|
4
|
-
RGB = 2
|
5
|
-
RGBA = 6
|
6
|
-
|
7
|
-
class << self
|
8
|
-
def image_data( file_name, options={} )
|
9
|
-
image_options = {:rgba => true}.merge( options )
|
10
|
-
|
11
|
-
png = open(file_name)
|
12
|
-
|
13
|
-
return png.to_image unless image_options[:rgba]
|
14
|
-
png.to_image.to_rgba
|
15
|
-
end
|
16
|
-
|
17
|
-
def open( file_name )
|
18
|
-
name = File.basename( file_name, ".png" )
|
19
|
-
|
20
|
-
File.open(file_name, "r") do |f|
|
21
|
-
ihdr, idat = Parser.go!( f )
|
22
|
-
Image.new( ihdr, idat, name )
|
23
|
-
end
|
24
|
-
end
|
25
|
-
|
26
|
-
def write( file_name, data, options = {} )
|
27
|
-
ihdr = PNG::IHDR.new(data.width, data.height, 8, color_type_of(data.pixel_width))
|
28
|
-
|
29
|
-
Image.new(ihdr, nil, file_name, :rows => data).write( file_name, options)
|
30
|
-
end
|
31
|
-
|
32
|
-
def default_filter_type
|
33
|
-
4 # paeth
|
34
|
-
end
|
35
|
-
|
36
|
-
private # class methods
|
37
|
-
|
38
|
-
def color_type_of(pixel_width)
|
39
|
-
case pixel_width
|
40
|
-
when 3
|
41
|
-
RGB
|
42
|
-
when 4
|
43
|
-
RGBA
|
44
|
-
end
|
45
|
-
end
|
46
|
-
end
|
47
|
-
|
48
|
-
def initialize( ihdr, idat, name, options = {} )
|
49
|
-
@ihdr = ihdr
|
50
|
-
@idat = idat
|
51
|
-
@name = name
|
52
|
-
@rows = options[:rows]
|
53
|
-
end
|
54
|
-
|
55
|
-
attr_reader :name
|
56
|
-
def width; @ihdr.width end
|
57
|
-
def height; @ihdr.height end
|
58
|
-
def depth; @ihdr.depth end
|
59
|
-
def color_type; @ihdr.color_type end
|
60
|
-
def uncompressed; @idat.uncompressed end
|
61
|
-
|
62
|
-
def write(file_name, options={})
|
63
|
-
filter_type = options[:filter_type] || Image.default_filter_type
|
64
|
-
File.open(file_name, 'w') do |f|
|
65
|
-
f.write(generate_png( filter_type ))
|
66
|
-
end
|
67
|
-
end
|
68
|
-
|
69
|
-
# check for RGB or RGBA
|
70
|
-
def pixel_width
|
71
|
-
( color_type == RGB ? 3 : 4)
|
72
|
-
end
|
73
|
-
|
74
|
-
def filter_encoded_rows(filter_type)
|
75
|
-
out = Array.new(height)
|
76
|
-
rows.each_with_index do |row, scanline|
|
77
|
-
last_row = rows.last_scanline(scanline)
|
78
|
-
out[scanline] = encode_row( row, last_row, filter_type, pixel_width)
|
79
|
-
end
|
80
|
-
out
|
81
|
-
end
|
82
|
-
|
83
|
-
def to_image
|
84
|
-
uncompressed = @idat.uncompressed
|
85
|
-
|
86
|
-
#scanline_width - 1 because we're stripping the filter bit
|
87
|
-
n_out = CssSpriter::ImageData.new(:scanline_width => scanline_width - 1,
|
88
|
-
:pixel_width => pixel_width,
|
89
|
-
:name => self.name,
|
90
|
-
:data => Array.new(height))
|
91
|
-
offset = 0
|
92
|
-
height.times do |scanline|
|
93
|
-
end_row = scanline_width + offset
|
94
|
-
row = uncompressed.slice(offset, scanline_width)
|
95
|
-
n_out[scanline] = decode(scanline, row, n_out, pixel_width)
|
96
|
-
offset = end_row
|
97
|
-
end
|
98
|
-
n_out
|
99
|
-
end
|
100
|
-
|
101
|
-
def to_s
|
102
|
-
inspect
|
103
|
-
end
|
104
|
-
|
105
|
-
def inspect
|
106
|
-
"#{@name} (#{height} x #{width}) [color type: #{color_type}, depth: #{depth}]"
|
107
|
-
end
|
108
|
-
|
109
|
-
private
|
110
|
-
|
111
|
-
def scanline_width
|
112
|
-
# + 1 adds filter byte
|
113
|
-
(width * pixel_width) + 1
|
114
|
-
end
|
115
|
-
|
116
|
-
def rows
|
117
|
-
@rows ||= to_image
|
118
|
-
end
|
119
|
-
|
120
|
-
def decode(current, row, data, pixel_width)
|
121
|
-
filter_type = row.shift
|
122
|
-
decode_row(row, data.last_scanline(current), filter_type, pixel_width)
|
123
|
-
end
|
124
|
-
|
125
|
-
def decode_row(row, last_scanline, filter_type, pixel_width)
|
126
|
-
o = Array.new(row.size)
|
127
|
-
row.each_with_index do |byte, i|
|
128
|
-
o[i] = Filters.decode(filter_type, byte, i, o, last_scanline, pixel_width)
|
129
|
-
end
|
130
|
-
o
|
131
|
-
end
|
132
|
-
|
133
|
-
def encode_row( row, last_scanline, filter_type, pixel_width )
|
134
|
-
o = Array.new(row.size)
|
135
|
-
row.each_with_index do |byte, scanline|
|
136
|
-
o[scanline] = Filters.encode( filter_type, byte, scanline, row, last_scanline, pixel_width)
|
137
|
-
end
|
138
|
-
o.unshift( filter_type )
|
139
|
-
end
|
140
|
-
|
141
|
-
def generate_png( filter_type )
|
142
|
-
file_header = PNG::FileHeader.new.encode
|
143
|
-
raw_data = filter_encoded_rows( filter_type )
|
144
|
-
raw_data.flatten!
|
145
|
-
|
146
|
-
ihdr = PNG::IHDR.new( width, height, depth, color_type ).to_chunk
|
147
|
-
idat = PNG::IDAT.new( raw_data ).to_chunk
|
148
|
-
iend = PNG::IEND.new.to_chunk
|
149
|
-
|
150
|
-
file_header + ihdr + idat + iend
|
151
|
-
end
|
152
|
-
end
|
153
|
-
end
|
@@ -1,54 +0,0 @@
|
|
1
|
-
module PNG
|
2
|
-
class Parser
|
3
|
-
def self.go!(file)
|
4
|
-
#TODO: Wanted to remove instance go! and use initialize, didn't work.
|
5
|
-
#Weird
|
6
|
-
Parser.new.go!(file)
|
7
|
-
end
|
8
|
-
def go!( file )
|
9
|
-
check_header( file )
|
10
|
-
|
11
|
-
while(not file.eof?) do
|
12
|
-
parse_chunk(file)
|
13
|
-
end
|
14
|
-
|
15
|
-
[ @ihdr, @idat ]
|
16
|
-
end
|
17
|
-
|
18
|
-
private
|
19
|
-
def check_header( file )
|
20
|
-
header = file.read(8)
|
21
|
-
raise "Invalid PNG file header" unless ( header == FileHeader.new.encode)
|
22
|
-
end
|
23
|
-
|
24
|
-
def parse_chunk(f)
|
25
|
-
len = f.read(4).unpack("N")
|
26
|
-
type = f.read(4)
|
27
|
-
data = f.read(len[0])
|
28
|
-
crc = f.read(4)
|
29
|
-
|
30
|
-
raise "invalid CRC for chunk type #{type}" if crc_invalid?( type, data, crc )
|
31
|
-
|
32
|
-
handle(type, data)
|
33
|
-
end
|
34
|
-
|
35
|
-
def handle(type, data)
|
36
|
-
case(type)
|
37
|
-
when "IHDR"
|
38
|
-
@ihdr = PNG::IHDR.new_from_raw( data )
|
39
|
-
@width, @height, @depth, @color_type = @ihdr.to_a
|
40
|
-
when "IDAT"
|
41
|
-
@idat ||= PNG::IDAT.new
|
42
|
-
@idat << data
|
43
|
-
when "IEND"
|
44
|
-
# NOOP
|
45
|
-
else
|
46
|
-
#puts "Ignoring chunk type #{type}"
|
47
|
-
end
|
48
|
-
end
|
49
|
-
|
50
|
-
def crc_invalid?( type, data, crc )
|
51
|
-
[Zlib.crc32( type + data )] != crc.unpack("N")
|
52
|
-
end
|
53
|
-
end
|
54
|
-
end
|
data/spec/image_data_spec.rb
DELETED
@@ -1,63 +0,0 @@
|
|
1
|
-
require File.expand_path(File.dirname(__FILE__) + '/spec_helper')
|
2
|
-
|
3
|
-
describe CssSpriter::ImageData do
|
4
|
-
before do
|
5
|
-
data = [[1,2,3],
|
6
|
-
[4,5,6]]
|
7
|
-
@id = CssSpriter::ImageData.new(:scanline_width => 3, :pixel_width => 3, :data => data)
|
8
|
-
end
|
9
|
-
|
10
|
-
it "can fill to a specified height" do
|
11
|
-
result = @id.fill_to_height(3)
|
12
|
-
result.should == [[1,2,3], [4,5,6], [0,0,0]]
|
13
|
-
end
|
14
|
-
it "has scanline width and pixel width attributes" do
|
15
|
-
@id.scanline_width.should == 3
|
16
|
-
@id.pixel_width.should == 3
|
17
|
-
end
|
18
|
-
|
19
|
-
it "can give you any arbitrary row in the data set" do
|
20
|
-
@id[0].should == [1,2,3]
|
21
|
-
@id.scanline(0).should == [1,2,3]
|
22
|
-
end
|
23
|
-
|
24
|
-
it "has an empty array by default" do
|
25
|
-
id = CssSpriter::ImageData.new
|
26
|
-
id.empty?.should be_true
|
27
|
-
end
|
28
|
-
|
29
|
-
it "should return nil when asked for an index that doesn't exist" do
|
30
|
-
id = CssSpriter::ImageData.new
|
31
|
-
id[0].should be_nil
|
32
|
-
end
|
33
|
-
|
34
|
-
it "can be assigned a row" do
|
35
|
-
@id[0] = [1,2,3]
|
36
|
-
@id[0].should == [1,2,3]
|
37
|
-
end
|
38
|
-
|
39
|
-
it "behaves like an array" do
|
40
|
-
@id << [1,2,3]
|
41
|
-
@id.last.should == [1,2,3]
|
42
|
-
|
43
|
-
@id.size.should == 3
|
44
|
-
end
|
45
|
-
|
46
|
-
it "will return the last scanline given a current index" do
|
47
|
-
@id.last_scanline(1).should == [1,2,3]
|
48
|
-
end
|
49
|
-
|
50
|
-
describe "RGBA conversion" do
|
51
|
-
it "updates pixel width" do
|
52
|
-
@id.to_rgba.pixel_width.should == 4 # RGBA pixel width
|
53
|
-
end
|
54
|
-
|
55
|
-
it "updates scanline width" do
|
56
|
-
@id.to_rgba.scanline_width.should == 4
|
57
|
-
end
|
58
|
-
|
59
|
-
it "puts in an alpha byte with a default value of 255" do
|
60
|
-
@id.to_rgba[0].should == [1,2,3,255]
|
61
|
-
end
|
62
|
-
end
|
63
|
-
end
|
@@ -1,10 +0,0 @@
|
|
1
|
-
require File.expand_path(File.dirname(__FILE__) + '/../spec_helper')
|
2
|
-
|
3
|
-
describe PNG::FileHeader do
|
4
|
-
# the pnd header provides sanity checks against most common file transfer errrors
|
5
|
-
it "outputs the PNG header" do
|
6
|
-
header = PNG::FileHeader.new.encode
|
7
|
-
header.should == [137, 80, 78, 71, 13, 10, 26, 10].pack("C*")
|
8
|
-
end
|
9
|
-
end
|
10
|
-
|
data/spec/lib/idat_spec.rb
DELETED
@@ -1,31 +0,0 @@
|
|
1
|
-
require File.expand_path(File.dirname(__FILE__) + '/../spec_helper')
|
2
|
-
|
3
|
-
describe PNG::IDAT do
|
4
|
-
before :each do
|
5
|
-
# it's just "Hello, World!" encoded
|
6
|
-
@data = "x\234\363H\315\311\311\327Q\b\317/\312IQ\004\000\037\236\004j"
|
7
|
-
end
|
8
|
-
|
9
|
-
it "accepts compressed data" do
|
10
|
-
@idat = PNG::IDAT.new
|
11
|
-
|
12
|
-
@idat << @data
|
13
|
-
|
14
|
-
@idat.encode.should == @data
|
15
|
-
end
|
16
|
-
|
17
|
-
it "can chunk its self" do
|
18
|
-
@idat = PNG::IDAT.new
|
19
|
-
@idat << @data
|
20
|
-
@chunk = chunk( "IDAT", @idat.encode )
|
21
|
-
|
22
|
-
@idat.to_chunk.should == @chunk
|
23
|
-
end
|
24
|
-
|
25
|
-
it "accepts uncompressed data for it's constructor" do
|
26
|
-
@idat = PNG::IDAT.new( "Hello, World!".unpack("C*") )
|
27
|
-
|
28
|
-
@idat.encode.should == @data
|
29
|
-
end
|
30
|
-
|
31
|
-
end
|
data/spec/lib/ihdr_spec.rb
DELETED
@@ -1,43 +0,0 @@
|
|
1
|
-
require File.expand_path(File.dirname(__FILE__) + '/../spec_helper')
|
2
|
-
|
3
|
-
describe PNG::IHDR do
|
4
|
-
before :each do
|
5
|
-
@width = 40
|
6
|
-
@height = 40
|
7
|
-
@bit_depth = 8
|
8
|
-
@color_type = 2
|
9
|
-
|
10
|
-
@raw = [@width, @height, @bit_depth, @color_type, 0, 0, 0].pack("N2C5")
|
11
|
-
@chunk = chunk( "IHDR", @raw )
|
12
|
-
end
|
13
|
-
|
14
|
-
it "pulls out the width from the ihdr block" do
|
15
|
-
@header = PNG::IHDR.new_from_raw( @raw )
|
16
|
-
@header.width.should == @width
|
17
|
-
end
|
18
|
-
|
19
|
-
it "pulls out the height from the ihdr block" do
|
20
|
-
@header = PNG::IHDR.new_from_raw( @raw )
|
21
|
-
@header.height.should == @height
|
22
|
-
end
|
23
|
-
|
24
|
-
it "pulls out the bit depth from the ihdr block" do
|
25
|
-
@header = PNG::IHDR.new_from_raw( @raw )
|
26
|
-
@header.depth.should == @bit_depth
|
27
|
-
end
|
28
|
-
|
29
|
-
it "pulls out the color type from the ihdr block" do
|
30
|
-
@header = PNG::IHDR.new_from_raw( @raw )
|
31
|
-
@header.color_type.should == @color_type
|
32
|
-
end
|
33
|
-
|
34
|
-
it "encodes it's self properly" do
|
35
|
-
@header = PNG::IHDR.new_from_raw( @raw )
|
36
|
-
@header.encode.should == @raw
|
37
|
-
end
|
38
|
-
|
39
|
-
it "should be able to make a header chunk" do
|
40
|
-
@header = PNG::IHDR.new_from_raw( @raw )
|
41
|
-
@header.to_chunk.should == @chunk
|
42
|
-
end
|
43
|
-
end
|
data/spec/lib/image_spec.rb
DELETED
@@ -1,42 +0,0 @@
|
|
1
|
-
require File.expand_path(File.dirname(__FILE__) + '/../spec_helper')
|
2
|
-
|
3
|
-
describe PNG::Image do
|
4
|
-
before :each do
|
5
|
-
@sprite = Sprite.new
|
6
|
-
|
7
|
-
@builder = ImageBuilder.new
|
8
|
-
|
9
|
-
# 0 is the filter type for the row, then an RGB triplet since builder defaults to color type 2
|
10
|
-
@image1 = @builder.build( :width => 1, :height => 1, :name => "image1", :data => [0,1,2,3] )
|
11
|
-
@image2 = @builder.build( :width => 1, :height => 1, :name => "image2", :data => [0,4,5,6] )
|
12
|
-
end
|
13
|
-
|
14
|
-
it "can merge left" do
|
15
|
-
result = @image1.to_image.merge_left @image2.to_image
|
16
|
-
|
17
|
-
result.should == [[4,5,6,1,2,3]]
|
18
|
-
end
|
19
|
-
|
20
|
-
it "can encode the rows with filter 0" do
|
21
|
-
@image1.filter_encoded_rows(0).should == [[0, 1, 2, 3]]
|
22
|
-
end
|
23
|
-
|
24
|
-
|
25
|
-
it "can encode the rows with filter 1" do
|
26
|
-
image = @builder.build( :width => 2, :height => 1, :name => "image1", :data => [0,1,2,3,4,5,6] )
|
27
|
-
|
28
|
-
# filter byte of 1
|
29
|
-
# first byte of pixel 2 - pixel 1 is 3
|
30
|
-
# second byte of pixel 2 - pixel 1 is 3.. etc
|
31
|
-
image.filter_encoded_rows(1).should == [[1, 1, 2, 3, 3, 3, 3]]
|
32
|
-
end
|
33
|
-
|
34
|
-
it "can encode the rows with filter 2" do
|
35
|
-
image = @builder.build( :width => 2, :height => 2, :name => "image1",
|
36
|
-
:data => [0,1,2,3,4,5,6,
|
37
|
-
0,0,0,0,0,0,0])
|
38
|
-
result = image
|
39
|
-
# filter byte of 2
|
40
|
-
result.filter_encoded_rows(2).should == [[2, 1, 2, 3, 4, 5, 6], [2, 255, 254, 253, 252, 251, 250]]
|
41
|
-
end
|
42
|
-
end
|