zpng 0.0.2 → 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
data/Gemfile CHANGED
@@ -8,8 +8,7 @@ gem 'hexdump'
8
8
  # Add dependencies to develop your gem here.
9
9
  # Include everything needed to run rake, tests, features, etc.
10
10
  group :development do
11
- gem "rspec", "~> 2.8.0"
12
- gem "bundler", "~> 1.0.0"
13
- gem "jeweler", "~> 1.6.4"
14
- gem "rcov", ">= 0"
11
+ gem "rspec", ">= 2.8.0"
12
+ gem "bundler", ">= 1.0.0"
13
+ gem "jeweler", "~> 1.8.4"
15
14
  end
@@ -4,29 +4,31 @@ GEM
4
4
  colorize (0.5.8)
5
5
  diff-lcs (1.1.3)
6
6
  git (1.2.5)
7
- hexdump (0.2.1)
8
- jeweler (1.6.4)
7
+ hexdump (0.2.3)
8
+ jeweler (1.8.4)
9
9
  bundler (~> 1.0)
10
10
  git (>= 1.2.5)
11
11
  rake
12
- rake (0.9.2.2)
13
- rcov (0.9.11)
14
- rspec (2.8.0)
15
- rspec-core (~> 2.8.0)
16
- rspec-expectations (~> 2.8.0)
17
- rspec-mocks (~> 2.8.0)
18
- rspec-core (2.8.0)
19
- rspec-expectations (2.8.0)
20
- diff-lcs (~> 1.1.2)
21
- rspec-mocks (2.8.0)
12
+ rdoc
13
+ json (1.7.5)
14
+ rake (10.0.2)
15
+ rdoc (3.12)
16
+ json (~> 1.4)
17
+ rspec (2.12.0)
18
+ rspec-core (~> 2.12.0)
19
+ rspec-expectations (~> 2.12.0)
20
+ rspec-mocks (~> 2.12.0)
21
+ rspec-core (2.12.1)
22
+ rspec-expectations (2.12.0)
23
+ diff-lcs (~> 1.1.3)
24
+ rspec-mocks (2.12.0)
22
25
 
23
26
  PLATFORMS
24
27
  ruby
25
28
 
26
29
  DEPENDENCIES
27
- bundler (~> 1.0.0)
30
+ bundler (>= 1.0.0)
28
31
  colorize
29
32
  hexdump
30
- jeweler (~> 1.6.4)
31
- rcov
32
- rspec (~> 2.8.0)
33
+ jeweler (~> 1.8.4)
34
+ rspec (>= 2.8.0)
data/README.md CHANGED
@@ -1,4 +1,4 @@
1
- zpng
1
+ zpng [![Build Status](https://secure.travis-ci.org/zed-0xff/zpng.png)](http://secure.travis-ci.org/zed-0xff/zpng) [![Dependency Status](https://gemnasium.com/zed-0xff/zpng.png)](https://gemnasium.com/zed-0xff/zpng)
2
2
  ======
3
3
 
4
4
 
@@ -15,14 +15,18 @@ Usage
15
15
 
16
16
  # zpng -h
17
17
 
18
- Usage: zpng [options]
18
+ Usage: zpng [options] filename.png
19
19
  -v, --verbose Run verbosely (can be used multiple times)
20
20
  -q, --quiet Silent any warnings (can be used multiple times)
21
- -I, --info General image info
22
21
  -C, --chunks Show file chunks (default)
22
+ -i, --info General image info (default)
23
23
  -A, --ascii Try to display image as ASCII (works best with monochrome images)
24
24
  -S, --scanlines Show scanlines info
25
25
  -P, --palette Show palette
26
+ -E, --extract-chunk ID extract a single chunk
27
+ -U, --unpack-imagedata unpack Image Data (IDAT) chunk(s), output to stdout
28
+ -c, --crop GEOMETRY crop image, {WIDTH}x{HEIGHT}+{X}+{Y},
29
+ puts results on stdout unless --ascii given
26
30
 
27
31
  ### Info
28
32
 
@@ -35,15 +39,15 @@ Usage
35
39
 
36
40
  # zpng --chunks qr_aux_chunks.png
37
41
 
38
- [.] #<ZPNG::Chunk IHDR size= 13, crc=36a28ef4, width=35, height=35, depth=1, color=0, compression=0, filter=0, interlace=0> CRC OK
39
- [.] #<ZPNG::Chunk gAMA size= 4, crc=0bfc6105 > CRC OK
40
- [.] #<ZPNG::Chunk sRGB size= 1, crc=aece1ce9 > CRC OK
41
- [.] #<ZPNG::Chunk cHRM size= 32, crc=9cba513c > CRC OK
42
- [.] #<ZPNG::Chunk pHYs size= 9, crc=46c96b3e > CRC OK
43
- [.] #<ZPNG::Chunk IDAT size= 213, crc=5f3f1ff9 > CRC OK
44
- [.] #<ZPNG::Chunk tEXt size= 37, crc=8d62fd1a > CRC OK
45
- [.] #<ZPNG::Chunk tEXt size= 37, crc=fc3f45a6 > CRC OK
46
- [.] #<ZPNG::Chunk IEND size= 0, crc=ae426082 > CRC OK
42
+ [.] <Chunk #00 IHDR size= 13, crc=36a28ef4, width=35, height=35, depth=1, color=0, compression=0, filter=0, interlace=0, idx=0> CRC OK
43
+ [.] <Chunk #01 gAMA size= 4, crc=0bfc6105 > CRC OK
44
+ [.] <Chunk #02 sRGB size= 1, crc=aece1ce9 > CRC OK
45
+ [.] <Chunk #03 cHRM size= 32, crc=9cba513c > CRC OK
46
+ [.] <Chunk #04 pHYs size= 9, crc=46c96b3e > CRC OK
47
+ [.] <Chunk #05 IDAT size= 213, crc=5f3f1ff9 > CRC OK
48
+ [.] <Chunk #06 tEXt size= 37, crc=8d62fd1a > CRC OK
49
+ [.] <Chunk #07 tEXt size= 37, crc=fc3f45a6 > CRC OK
50
+ [.] <Chunk #08 IEND size= 0, crc=ae426082 > CRC OK
47
51
 
48
52
  ### ASCII
49
53
 
@@ -90,47 +94,47 @@ source image: ![qr_rgb.png](https://github.com/zed-0xff/zpng/raw/master/samples/
90
94
 
91
95
  # zpng --scanlines qr_rgb.png
92
96
 
93
- [#<ZPNG::ScanLine idx=0, bpp=24, BPP=3, offset=1, filter=1>,
94
- #<ZPNG::ScanLine idx=1, bpp=24, BPP=3, offset=107, filter=4>,
95
- #<ZPNG::ScanLine idx=2, bpp=24, BPP=3, offset=213, filter=4>,
96
- #<ZPNG::ScanLine idx=3, bpp=24, BPP=3, offset=319, filter=4>,
97
- #<ZPNG::ScanLine idx=4, bpp=24, BPP=3, offset=425, filter=2>,
98
- #<ZPNG::ScanLine idx=5, bpp=24, BPP=3, offset=531, filter=2>,
99
- #<ZPNG::ScanLine idx=6, bpp=24, BPP=3, offset=637, filter=4>,
100
- #<ZPNG::ScanLine idx=7, bpp=24, BPP=3, offset=743, filter=0>,
101
- #<ZPNG::ScanLine idx=8, bpp=24, BPP=3, offset=849, filter=1>,
102
- #<ZPNG::ScanLine idx=9, bpp=24, BPP=3, offset=955, filter=0>,
103
- #<ZPNG::ScanLine idx=10, bpp=24, BPP=3, offset=1061, filter=0>,
104
- #<ZPNG::ScanLine idx=11, bpp=24, BPP=3, offset=1167, filter=0>,
105
- #<ZPNG::ScanLine idx=12, bpp=24, BPP=3, offset=1273, filter=1>,
106
- #<ZPNG::ScanLine idx=13, bpp=24, BPP=3, offset=1379, filter=2>,
107
- #<ZPNG::ScanLine idx=14, bpp=24, BPP=3, offset=1485, filter=4>,
108
- #<ZPNG::ScanLine idx=15, bpp=24, BPP=3, offset=1591, filter=0>,
109
- #<ZPNG::ScanLine idx=16, bpp=24, BPP=3, offset=1697, filter=4>,
110
- #<ZPNG::ScanLine idx=17, bpp=24, BPP=3, offset=1803, filter=0>,
111
- #<ZPNG::ScanLine idx=18, bpp=24, BPP=3, offset=1909, filter=4>,
112
- #<ZPNG::ScanLine idx=19, bpp=24, BPP=3, offset=2015, filter=4>,
113
- #<ZPNG::ScanLine idx=20, bpp=24, BPP=3, offset=2121, filter=0>,
114
- #<ZPNG::ScanLine idx=21, bpp=24, BPP=3, offset=2227, filter=1>,
115
- #<ZPNG::ScanLine idx=22, bpp=24, BPP=3, offset=2333, filter=2>,
116
- #<ZPNG::ScanLine idx=23, bpp=24, BPP=3, offset=2439, filter=0>,
117
- #<ZPNG::ScanLine idx=24, bpp=24, BPP=3, offset=2545, filter=2>,
118
- #<ZPNG::ScanLine idx=25, bpp=24, BPP=3, offset=2651, filter=1>,
119
- #<ZPNG::ScanLine idx=26, bpp=24, BPP=3, offset=2757, filter=1>,
120
- #<ZPNG::ScanLine idx=27, bpp=24, BPP=3, offset=2863, filter=4>,
121
- #<ZPNG::ScanLine idx=28, bpp=24, BPP=3, offset=2969, filter=4>,
122
- #<ZPNG::ScanLine idx=29, bpp=24, BPP=3, offset=3075, filter=4>,
123
- #<ZPNG::ScanLine idx=30, bpp=24, BPP=3, offset=3181, filter=4>,
124
- #<ZPNG::ScanLine idx=31, bpp=24, BPP=3, offset=3287, filter=2>,
125
- #<ZPNG::ScanLine idx=32, bpp=24, BPP=3, offset=3393, filter=4>,
126
- #<ZPNG::ScanLine idx=33, bpp=24, BPP=3, offset=3499, filter=4>,
127
- #<ZPNG::ScanLine idx=34, bpp=24, BPP=3, offset=3605, filter=1>]
97
+ [#<ZPNG::ScanLine idx=0, bpp=24, offset=1, filter=1>,
98
+ #<ZPNG::ScanLine idx=1, bpp=24, offset=107, filter=4>,
99
+ #<ZPNG::ScanLine idx=2, bpp=24, offset=213, filter=4>,
100
+ #<ZPNG::ScanLine idx=3, bpp=24, offset=319, filter=4>,
101
+ #<ZPNG::ScanLine idx=4, bpp=24, offset=425, filter=2>,
102
+ #<ZPNG::ScanLine idx=5, bpp=24, offset=531, filter=2>,
103
+ #<ZPNG::ScanLine idx=6, bpp=24, offset=637, filter=4>,
104
+ #<ZPNG::ScanLine idx=7, bpp=24, offset=743, filter=0>,
105
+ #<ZPNG::ScanLine idx=8, bpp=24, offset=849, filter=1>,
106
+ #<ZPNG::ScanLine idx=9, bpp=24, offset=955, filter=0>,
107
+ #<ZPNG::ScanLine idx=10, bpp=24, offset=1061, filter=0>,
108
+ #<ZPNG::ScanLine idx=11, bpp=24, offset=1167, filter=0>,
109
+ #<ZPNG::ScanLine idx=12, bpp=24, offset=1273, filter=1>,
110
+ #<ZPNG::ScanLine idx=13, bpp=24, offset=1379, filter=2>,
111
+ #<ZPNG::ScanLine idx=14, bpp=24, offset=1485, filter=4>,
112
+ #<ZPNG::ScanLine idx=15, bpp=24, offset=1591, filter=0>,
113
+ #<ZPNG::ScanLine idx=16, bpp=24, offset=1697, filter=4>,
114
+ #<ZPNG::ScanLine idx=17, bpp=24, offset=1803, filter=0>,
115
+ #<ZPNG::ScanLine idx=18, bpp=24, offset=1909, filter=4>,
116
+ #<ZPNG::ScanLine idx=19, bpp=24, offset=2015, filter=4>,
117
+ #<ZPNG::ScanLine idx=20, bpp=24, offset=2121, filter=0>,
118
+ #<ZPNG::ScanLine idx=21, bpp=24, offset=2227, filter=1>,
119
+ #<ZPNG::ScanLine idx=22, bpp=24, offset=2333, filter=2>,
120
+ #<ZPNG::ScanLine idx=23, bpp=24, offset=2439, filter=0>,
121
+ #<ZPNG::ScanLine idx=24, bpp=24, offset=2545, filter=2>,
122
+ #<ZPNG::ScanLine idx=25, bpp=24, offset=2651, filter=1>,
123
+ #<ZPNG::ScanLine idx=26, bpp=24, offset=2757, filter=1>,
124
+ #<ZPNG::ScanLine idx=27, bpp=24, offset=2863, filter=4>,
125
+ #<ZPNG::ScanLine idx=28, bpp=24, offset=2969, filter=4>,
126
+ #<ZPNG::ScanLine idx=29, bpp=24, offset=3075, filter=4>,
127
+ #<ZPNG::ScanLine idx=30, bpp=24, offset=3181, filter=4>,
128
+ #<ZPNG::ScanLine idx=31, bpp=24, offset=3287, filter=2>,
129
+ #<ZPNG::ScanLine idx=32, bpp=24, offset=3393, filter=4>,
130
+ #<ZPNG::ScanLine idx=33, bpp=24, offset=3499, filter=4>,
131
+ #<ZPNG::ScanLine idx=34, bpp=24, offset=3605, filter=1>]
128
132
 
129
133
  ### Palette
130
134
 
131
135
  # zpng --palette qr_plte_bw.png
132
136
 
133
- #<ZPNG::Chunk PLTE size= 6, crc=55c2d37e >
137
+ <Chunk #02 PLTE size= 6, crc=55c2d37e >
134
138
  00000000 ff ff ff 00 00 00 |......|
135
139
 
136
140
 
@@ -157,6 +161,16 @@ source image: ![qr_rgb.png](https://github.com/zed-0xff/zpng/raw/master/samples/
157
161
  f << img.export
158
162
  end
159
163
 
164
+ ## Create 16x16 transparent PNG
165
+
166
+ #!/usr/bin/env ruby
167
+ require 'zpng'
168
+ include ZPNG
169
+
170
+ img = Image.new :width => 16, :height => 16
171
+ File.open("16x16.png","wb") do |f|
172
+ f << img.export
173
+ end
160
174
 
161
175
  License
162
176
  -------
@@ -1,4 +1,4 @@
1
- zpng
1
+ zpng [![Build Status](https://secure.travis-ci.org/zed-0xff/zpng.png)](http://secure.travis-ci.org/zed-0xff/zpng) [![Dependency Status](https://gemnasium.com/zed-0xff/zpng.png)](https://gemnasium.com/zed-0xff/zpng)
2
2
  ======
3
3
 
4
4
 
@@ -61,6 +61,16 @@ source image: ![qr_rgb.png](https://github.com/zed-0xff/zpng/raw/master/samples/
61
61
  f << img.export
62
62
  end
63
63
 
64
+ ## Create 16x16 transparent PNG
65
+
66
+ #!/usr/bin/env ruby
67
+ require 'zpng'
68
+ include ZPNG
69
+
70
+ img = Image.new :width => 16, :height => 16
71
+ File.open("16x16.png","wb") do |f|
72
+ f << img.export
73
+ end
64
74
 
65
75
  License
66
76
  -------
data/Rakefile CHANGED
@@ -72,3 +72,46 @@ task :readme do
72
72
  Dir.chdir '..'
73
73
  File.open('README.md','w'){ |f| f << result }
74
74
  end
75
+
76
+ desc "generate"
77
+ task :gen do
78
+ $:.unshift("./lib")
79
+ require 'zpng'
80
+ img = ZPNG::Image.new :width => 16, :height => 16, :bpp => 4
81
+ img.save "out.png"
82
+ end
83
+
84
+ Rake::Task[:console].clear
85
+
86
+ # from /usr/local/lib64/ruby/gems/1.9.1/gems/jeweler-1.8.4/lib/jeweler/tasks.rb
87
+ desc "Start IRB with all runtime dependencies loaded"
88
+ task :console, [:script] do |t,args|
89
+ # TODO move to a command
90
+ dirs = ['ext', 'lib'].select { |dir| File.directory?(dir) }
91
+
92
+ original_load_path = $LOAD_PATH
93
+
94
+ cmd = if File.exist?('Gemfile')
95
+ require 'bundler'
96
+ Bundler.setup(:default)
97
+ end
98
+
99
+ # add the project code directories
100
+ $LOAD_PATH.unshift(*dirs)
101
+
102
+ # clear ARGV so IRB is not confused
103
+ ARGV.clear
104
+
105
+ require 'irb'
106
+
107
+ # ZZZ actually added only these 2 lines
108
+ require 'zpng'
109
+ include ZPNG
110
+
111
+ # set the optional script to run
112
+ IRB.conf[:SCRIPT] = args.script
113
+ IRB.start
114
+
115
+ # return the $LOAD_PATH to it's original state
116
+ $LOAD_PATH.reject! { |path| !(original_load_path.include?(path)) }
117
+ end
data/VERSION CHANGED
@@ -1 +1 @@
1
- 0.0.2
1
+ 0.1.0
@@ -18,25 +18,42 @@ module ZPNG
18
18
  end
19
19
  end
20
20
 
21
- def initialize io
22
- @size, @type = io.read(8).unpack('Na4')
23
- @data = io.read(size)
24
- @crc = io.read(4).to_s.unpack('N').first
21
+ def initialize x = {}
22
+ if x.respond_to?(:read)
23
+ # IO
24
+ @size, @type = x.read(8).unpack('Na4')
25
+ @data = x.read(size)
26
+ @crc = x.read(4).to_s.unpack('N').first
27
+ elsif x.respond_to?(:[])
28
+ # Hash
29
+ %w'size type data crc'.each do |k|
30
+ instance_variable_set "@#{k}", x[k.to_sym]
31
+ end
32
+ if !@type && self.class.superclass == ZPNG::Chunk
33
+ # guess @type from self class name, e.g. ZPNG::Chunk::IHDR => "IHDR"
34
+ @type = self.class.to_s.split("::").last
35
+ end
36
+ if !@size && @data
37
+ # guess @size from @data
38
+ @size = @data.size
39
+ end
40
+ end
25
41
  end
26
42
 
27
43
  def export
28
44
  @data = self.export_data # virtual
45
+ @size = @data.size # XXX hmm.. is it always is?
29
46
  @crc = Zlib.crc32(data, Zlib.crc32(type))
30
47
  [@size,@type].pack('Na4') + @data + [@crc].pack('N')
31
48
  end
32
49
 
33
50
  def export_data
34
51
  #STDERR.puts "[!] Chunk::#{type} must realize 'export_data' virtual method".yellow if @size != 0
35
- @data
52
+ @data || ''
36
53
  end
37
54
 
38
55
  def inspect
39
- size = @size ? sprintf("%5d",@size) : sprintf("%5s","???")
56
+ size = @size ? sprintf("%6d",@size) : sprintf("%6s","???")
40
57
  crc = @crc ? sprintf("%08x",@crc) : sprintf("%8s","???")
41
58
  type = @type.to_s.gsub(/[^0-9a-z]/i){ |x| sprintf("\\x%02X",x.ord) }
42
59
  sprintf "<Chunk #%02d %4s size=%s, crc=%s >", idx.to_i, type, size, crc
@@ -54,11 +71,16 @@ module ZPNG
54
71
  COLOR_USED = 2
55
72
  ALPHA_USED = 4
56
73
 
57
- COLOR_GRAYSCALE = 0 # Each pixel is a grayscale sample
58
- COLOR_RGB = 2 # Each pixel is an R,G,B triple.
59
- COLOR_INDEXED = 3 # Each pixel is a palette index; a PLTE chunk must appear.
60
- COLOR_GRAY_ALPHA = 4 # Each pixel is a grayscale sample, followed by an alpha sample.
61
- COLOR_RGBA = 6 # Each pixel is an R,G,B triple, followed by an alpha sample.
74
+ # put constants in the scope of ZPNG module
75
+ # to be able to create new images easily with
76
+ # include ZPNG
77
+ # img = Image.new :width => 16, :height => 16, :color => COLOR_RGB
78
+
79
+ ZPNG::COLOR_GRAYSCALE = 0 # Each pixel is a grayscale sample
80
+ ZPNG::COLOR_RGB = 2 # Each pixel is an R,G,B triple.
81
+ ZPNG::COLOR_INDEXED = 3 # Each pixel is a palette index; a PLTE chunk must appear.
82
+ ZPNG::COLOR_GRAY_ALPHA = 4 # Each pixel is a grayscale sample, followed by an alpha sample.
83
+ ZPNG::COLOR_RGBA = 6 # Each pixel is an R,G,B triple, followed by an alpha sample.
62
84
 
63
85
  SAMPLES_PER_COLOR = {
64
86
  COLOR_GRAYSCALE => 1,
@@ -68,11 +90,67 @@ module ZPNG
68
90
  COLOR_RGBA => 4
69
91
  }
70
92
 
93
+ # http://www.w3.org/TR/PNG/#table111
94
+ ALLOWED_DEPTHS = {
95
+ COLOR_GRAYSCALE => [ 1, 2, 4, 8, 16 ],
96
+ COLOR_RGB => [ 8, 16 ],
97
+ COLOR_INDEXED => [ 1, 2, 4, 8 ],
98
+ COLOR_GRAY_ALPHA => [ 8, 16 ],
99
+ COLOR_RGBA => [ 8, 16 ],
100
+ }
101
+
71
102
  FORMAT = 'NNC5'
72
103
 
73
- def initialize io
104
+ def initialize x
74
105
  super
75
- @width, @height, @depth, @color, @compression, @filter, @interlace = data.unpack(FORMAT)
106
+ vars = %w'width height depth color compression filter interlace' # order is important
107
+ if x.respond_to?(:read)
108
+ # IO
109
+ elsif x.respond_to?(:[])
110
+ # Hash
111
+ vars.each{ |k| instance_variable_set "@#{k}", x[k.to_sym] }
112
+
113
+ raise "[!] width not set" unless @width
114
+ raise "[!] height not set" unless @height
115
+
116
+ # allow easier image creation like
117
+ # img = Image.new :width => 16, :height => 16, :bpp => 4, :color => false
118
+ # img = Image.new :width => 16, :height => 16, :bpp => 1, :color => true
119
+ # img = Image.new :width => 16, :height => 16, :bpp => 32
120
+ if x[:bpp]
121
+ unless [true,false,nil].include?(@color)
122
+ raise "[!] :color must be either 'true' or 'false' when :bpp is set"
123
+ end
124
+ if @depth
125
+ raise "[!] don't use :depth when :bpp is set"
126
+ end
127
+ @color, @depth = case x[:bpp]
128
+ when 1,2,4,8; [ @color ? COLOR_INDEXED : COLOR_GRAYSCALE, x[:bpp] ]
129
+ when 16;
130
+ raise "[!] I don't know how to make COLOR 16 bpp PNG. do you?" if @color
131
+ [ COLOR_GRAY_ALPHA, 8 ]
132
+ when 24; [ COLOR_RGB, 8 ]
133
+ when 32; [ COLOR_RGBA, 8 ]
134
+ else
135
+ raise "[!] unsupported bpp=#{x[:bpp].inspect}"
136
+ end
137
+ end
138
+
139
+ @color ||= COLOR_RGBA
140
+ @depth ||= 8
141
+ @compression ||= 0
142
+ @filter ||= 0
143
+ @interlace ||= 0
144
+
145
+ unless ALLOWED_DEPTHS[@color].include?(@depth)
146
+ raise "[!] invalid color mode (#{@color.inspect}) / bit depth (#{@depth.inspect}) combination"
147
+ end
148
+ end
149
+ if data
150
+ data.unpack(FORMAT).each_with_index do |value,idx|
151
+ instance_variable_set "@#{vars[idx]}", value
152
+ end
153
+ end
76
154
  end
77
155
 
78
156
  def export_data
@@ -109,13 +187,20 @@ module ZPNG
109
187
  end
110
188
 
111
189
  class PLTE < Chunk
190
+ attr_accessor :max_colors
191
+
112
192
  def [] idx
113
193
  rgb = @data[idx*3,3]
114
- rgb && ZPNG::Color.new(*rgb.split('').map(&:ord))
194
+ rgb && ZPNG::Color.new(*rgb.unpack('C3'))
195
+ end
196
+
197
+ def []= idx, color
198
+ @data ||= ''
199
+ @data[idx*3,3] = [color.r, color.g, color.b].pack('C3')
115
200
  end
116
201
 
117
202
  def ncolors
118
- @size/3
203
+ @data.to_s.size/3
119
204
  end
120
205
 
121
206
  def index color
@@ -125,11 +210,23 @@ module ZPNG
125
210
  end
126
211
  nil
127
212
  end
128
- end
129
213
 
130
- class IEND < Chunk
214
+ def add color
215
+ raise "palette full, cannot add color #{ncolors}" if ncolors >= max_colors
216
+ idx = ncolors
217
+ self[idx] = color
218
+ idx
219
+ end
220
+
221
+ def find_or_add color
222
+ index(color) || add(color)
223
+ end
224
+ alias :<< :find_or_add
131
225
  end
132
226
 
227
+ class IDAT < Chunk; end
228
+ class IEND < Chunk; end
229
+
133
230
  class ZTXT < Chunk
134
231
  attr_accessor :keyword, :comp_method, :text
135
232
  def initialize *args