zpng 0.2.0 → 0.2.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,20 @@
1
+ module ZPNG
2
+ class Metadata < Array
3
+ def initialize img = nil
4
+ return unless img
5
+ img.chunks.each do |c|
6
+ next unless c.is_a?(TextChunk)
7
+ self << [c.keyword, c.text, c.to_hash]
8
+ end
9
+ end
10
+
11
+ def [] *args
12
+ if args.first.is_a?(String)
13
+ each{ |a| return a[1] if a[0] == args.first }
14
+ nil
15
+ else
16
+ super
17
+ end
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,25 @@
1
+ module ZPNG
2
+ class Pixels
3
+ include Enumerable
4
+
5
+ def initialize image
6
+ @image = image
7
+ end
8
+
9
+ def each
10
+ @image.height.times do |y|
11
+ @image.width.times do |x|
12
+ yield @image[x,y]
13
+ end
14
+ end
15
+ end
16
+
17
+ def == other
18
+ self.to_a == other.to_a
19
+ end
20
+
21
+ def uniq
22
+ self.to_a.uniq
23
+ end
24
+ end
25
+ end
@@ -8,6 +8,7 @@ module ZPNG
8
8
  FILTER_PAETH = 4
9
9
 
10
10
  attr_accessor :image, :idx, :filter, :offset, :bpp
11
+ attr_writer :decoded_bytes
11
12
 
12
13
  def initialize image, idx
13
14
  @image,@idx = image,idx
@@ -77,49 +78,63 @@ module ZPNG
77
78
  decode_pixel(x)
78
79
  end
79
80
 
80
- def []= x, newcolor
81
- if image.hdr.palette_used?
82
- color_idx = image.palette.find_or_add(newcolor)
83
- raise "no color #{newcolor.inspect} in palette" unless color_idx
84
- elsif image.grayscale?
85
- color_idx = newcolor.to_grayscale
86
- end
81
+ def []= x, color
82
+ case image.hdr.color
83
+ when COLOR_INDEXED # ALLOWED_DEPTHS: 1, 2, 4, 8
84
+ color_idx = image.palette.find_or_add(color)
85
+ raise "no color #{color.inspect} in palette" unless color_idx
87
86
 
88
- case @bpp
89
- when 1,2,4
90
- pos = x*@bpp/8
91
- b = decoded_bytes[pos].ord
92
87
  mask = 2**@bpp-1
93
88
  shift = 8-(x%(8/@bpp)+1)*@bpp
94
89
  raise "invalid shift #{shift}" if shift < 0 || shift > 7
95
90
 
96
- # printf "[d] %s x=%2d bpp=%d pos=%d mask=%08b shift=%d decoded_bytes=#{decoded_bytes.inspect}\n", self.to_s, x, @bpp, pos, mask, shift
97
-
91
+ pos = x*@bpp/8
92
+ b = decoded_bytes[pos].ord
98
93
  b = (b & (0xff-(mask<<shift))) | ((color_idx & mask) << shift)
99
94
  decoded_bytes[pos] = b.chr
95
+ # TODO: transparency in TRNS
100
96
 
101
- when 8
102
- if image.hdr.palette_used?
103
- decoded_bytes[x] = color_idx.chr
97
+ when COLOR_GRAYSCALE # ALLOWED_DEPTHS: 1, 2, 4, 8, 16
98
+ raw = color.to_depth(@bpp).to_grayscale
99
+ pos = x*@bpp/8
100
+ if @bpp == 16
101
+ decoded_bytes[pos,2] = [raw].pack('n')
104
102
  else
105
- decoded_bytes[x] = ((newcolor.r + newcolor.g + newcolor.b)/3).chr
103
+ mask = 2**@bpp-1
104
+ shift = 8-(x%(8/@bpp)+1)*@bpp
105
+ raise "invalid shift #{shift}" if shift < 0 || shift > 7
106
+ b = decoded_bytes[pos].ord
107
+ b = (b & (0xff-(mask<<shift))) | ((raw & mask) << shift)
108
+ decoded_bytes[pos] = b.chr
106
109
  end
107
- when 16
108
- if image.hdr.palette_used? && image.hdr.alpha_used?
109
- decoded_bytes[x*2] = color_idx.chr
110
- decoded_bytes[x*2+1] = (newcolor.alpha || 0xff).chr
111
- elsif image.grayscale? && image.hdr.alpha_used?
112
- decoded_bytes[x*2] = newcolor.to_grayscale.chr
113
- decoded_bytes[x*2+1] = (newcolor.alpha || 0xff).chr
114
- else
115
- raise "unexpected colormode #{image.hdr.inspect}"
110
+ # TODO: transparency in TRNS
111
+
112
+ when COLOR_RGB # ALLOWED_DEPTHS: 8, 16
113
+ case @bpp
114
+ when 24; decoded_bytes[x*3,3] = color.to_depth(8).to_a.pack('C3')
115
+ when 48; decoded_bytes[x*6,6] = color.to_depth(16).to_a.pack('n3')
116
+ else raise "unexpected bpp #@bpp"
116
117
  end
117
- when 24
118
- decoded_bytes[x*3,3] = [newcolor.r, newcolor.g, newcolor.b].map(&:chr).join
119
- when 32
120
- decoded_bytes[x*4,4] = [newcolor.r, newcolor.g, newcolor.b, newcolor.a].map(&:chr).join
121
- else raise "unsupported bpp #{@bpp}"
122
- end
118
+ # TODO: transparency in TRNS
119
+
120
+ when COLOR_GRAY_ALPHA # ALLOWED_DEPTHS: 8, 16
121
+ case @bpp
122
+ when 16; decoded_bytes[x*2,2] = color.to_depth(8).to_gray_alpha.pack('C2')
123
+ when 32; decoded_bytes[x*4,4] = color.to_depth(16).to_gray_alpha.pack('n2')
124
+ else raise "unexpected bpp #@bpp"
125
+ end
126
+
127
+ when COLOR_RGBA # ALLOWED_DEPTHS: 8, 16
128
+ case @bpp
129
+ when 32; decoded_bytes[x*4,4] = color.to_depth(8).to_a.pack('C4')
130
+ when 64; decoded_bytes[x*8,8] = color.to_depth(16).to_a.pack('n4')
131
+ else raise "unexpected bpp #@bpp"
132
+ end
133
+
134
+ else
135
+ raise "unexpected color mode #{image.hdr.color}"
136
+
137
+ end # case image.hdr.color
123
138
  end
124
139
 
125
140
  def decode_pixel x
@@ -132,56 +147,82 @@ module ZPNG
132
147
  decoded_bytes[x*@bpp/8]
133
148
  end
134
149
 
135
- color, alpha =
136
- case @bpp
137
- when 1,2,4
138
- mask = 2**@bpp-1
139
- shift = 8-(x%(8/@bpp)+1)*@bpp
140
- raise "invalid shift #{shift}" if shift < 0 || shift > 7
141
- [(raw.ord >> shift) & mask, nil]
142
- when 8
143
- [raw.ord, nil]
144
- when 16
145
- if image.alpha_used?
146
- raw.unpack 'C2'
147
- else
148
- # 16-bit grayscale
149
- raw.unpack 'n'
150
- end
151
- when 24
152
- # RGB
153
- return Color.new(*raw.unpack('C3'))
154
- when 32
155
- if image.grayscale? && image.alpha_used?
156
- # 16-bit grayscale + 16-bit alpha
157
- raw.unpack 'n2'
158
- else
159
- # RGBA
160
- return Color.new(*raw.unpack('C4'))
150
+ case image.hdr.color
151
+ when COLOR_INDEXED # ALLOWED_DEPTHS: 1, 2, 4, 8
152
+ mask = 2**@bpp-1
153
+ shift = 8-(x%(8/@bpp)+1)*@bpp
154
+ raise "invalid shift #{shift}" if shift < 0 || shift > 7
155
+ idx = (raw.ord >> shift) & mask
156
+ if image.trns
157
+ # transparency from tRNS chunk
158
+ # For color type 3 (indexed color), the tRNS chunk contains a series of one-byte alpha values,
159
+ # corresponding to entries in the PLTE chunk:
160
+ #
161
+ # Alpha for palette index 0: 1 byte
162
+ # Alpha for palette index 1: 1 byte
163
+ # ...
164
+ #
165
+ color = image.palette[idx].dup
166
+ if color.alpha = image.trns.data[idx]
167
+ # if it's not NULL - convert it from char to int,
168
+ # otherwise it means fully opaque color, as well as NULL alpha in ZPNG::Color
169
+ color.alpha = color.alpha.ord
161
170
  end
162
- when 48
163
- # RGB 16 bits per sample
164
- return Color.new(*raw.unpack('n3'), :depth => 16)
165
- when 64
166
- # RGB 16 bits per sample + 16-bit alpha
167
- return Color.new(*raw.unpack('n4'), :depth => 16, :alpha_depth => 16)
171
+ return color
168
172
  else
169
- raise "unexpected bpp #{@bpp}"
173
+ # no transparency
174
+ return image.palette[idx]
175
+ end
176
+
177
+ when COLOR_GRAYSCALE # ALLOWED_DEPTHS: 1, 2, 4, 8, 16
178
+ c = if @bpp == 16
179
+ raw.unpack('n')[0]
180
+ else
181
+ mask = 2**@bpp-1
182
+ shift = 8-(x%(8/@bpp)+1)*@bpp
183
+ raise "invalid shift #{shift}" if shift < 0 || shift > 7
184
+ (raw.ord >> shift) & mask
185
+ end
186
+
187
+ color = Color.from_grayscale(c, :depth => @bpp) # only in this color mode depth == bpp
188
+ color.alpha = image._alpha_color(color)
189
+ return color
190
+
191
+ when COLOR_RGB # ALLOWED_DEPTHS: 8, 16
192
+ color =
193
+ case @bpp
194
+ when 24 # RGB 8 bits per sample = 24bpp
195
+ Color.new(*raw.unpack('C3'))
196
+ when 48 # RGB 16 bits per sample = 48bpp
197
+ Color.new(*raw.unpack('n3'), :depth => 16)
198
+ else raise "COLOR_RGB unexpected bpp #@bpp"
199
+ end
200
+
201
+ color.alpha = image._alpha_color(color)
202
+ return color
203
+
204
+ when COLOR_GRAY_ALPHA # ALLOWED_DEPTHS: 8, 16
205
+ case @bpp
206
+ when 16 # 8-bit grayscale + 8-bit alpha
207
+ return Color.from_grayscale(*raw.unpack('C2'))
208
+ when 32 # 16-bit grayscale + 16-bit alpha
209
+ return Color.from_grayscale(*raw.unpack('n2'), :depth => 16)
210
+ else raise "COLOR_GRAY_ALPHA unexpected bpp #@bpp"
211
+ end
212
+
213
+ when COLOR_RGBA # ALLOWED_DEPTHS: 8, 16
214
+ case @bpp
215
+ when 32 # RGBA 8-bit/sample
216
+ return Color.new(*raw.unpack('C4'))
217
+ when 64 # RGBA 16-bit/sample
218
+ return Color.new(*raw.unpack('n4'), :depth => 16 )
219
+ else raise "COLOR_RGBA unexpected bpp #@bpp"
170
220
  end
171
221
 
172
- if image.grayscale?
173
- Color.from_grayscale(color,
174
- :alpha => alpha,
175
- :depth => image.hdr.depth,
176
- :alpha_depth => image.alpha_used? ? image.hdr.depth : 0
177
- )
178
- elsif image.palette
179
- color = image.palette[color]
180
- color.alpha = alpha
181
- color
182
222
  else
183
- raise "cannot decode color"
184
- end
223
+ raise "unexpected color mode #{image.hdr.color}"
224
+
225
+ end # case img.hdr.color
185
226
  end
186
227
 
187
228
  def decoded_bytes
@@ -211,6 +252,19 @@ module ZPNG
211
252
  end
212
253
 
213
254
  private
255
+
256
+ def prev_scanline_byte x
257
+ if image.interlaced?
258
+ # When the image is interlaced, each pass of the interlace pattern is
259
+ # treated as an independent image for filtering purposes
260
+ image.adam7.pass_start?(@idx) ? 0 : image.scanlines[@idx-1].decoded_bytes[x].ord
261
+ elsif @idx > 0
262
+ image.scanlines[@idx-1].decoded_bytes[x].ord
263
+ else
264
+ 0
265
+ end
266
+ end
267
+
214
268
  def decode_byte x, b0, bpp1
215
269
  raw = @image.imagedata[@offset+x+1]
216
270
 
@@ -220,27 +274,25 @@ module ZPNG
220
274
  end
221
275
 
222
276
  case @filter
223
- when FILTER_NONE # 0
277
+ when FILTER_NONE # 0
224
278
  raw
225
279
 
226
- when FILTER_SUB # 1
280
+ when FILTER_SUB # 1
227
281
  return raw unless b0
228
282
  ((raw.ord + b0.ord) & 0xff).chr
229
283
 
230
- when FILTER_UP # 2
231
- return raw if @idx == 0
232
- prev = @image.scanlines[@idx-1].decoded_bytes[x]
233
- ((raw.ord + prev.ord) & 0xff).chr
284
+ when FILTER_UP # 2
285
+ ((raw.ord + prev_scanline_byte(x)) & 0xff).chr
234
286
 
235
287
  when FILTER_AVERAGE # 3
236
288
  prev = (b0 && b0.ord) || 0
237
- prior = (@idx > 0) ? @image.scanlines[@idx-1].decoded_bytes[x].ord : 0
289
+ prior = prev_scanline_byte(x)
238
290
  ((raw.ord + (prev + prior)/2) & 0xff).chr
239
291
 
240
- when FILTER_PAETH # 4
292
+ when FILTER_PAETH # 4
241
293
  pa = (b0 && b0.ord) || 0
242
- pb = (@idx > 0) ? @image.scanlines[@idx-1].decoded_bytes[x].ord : 0
243
- pc = (b0 && @idx > 0) ? @image.scanlines[@idx-1].decoded_bytes[x-bpp1].ord : 0
294
+ pb = prev_scanline_byte(x)
295
+ pc = b0 ? prev_scanline_byte(x-bpp1) : 0
244
296
  ((raw.ord + paeth_predictor(pa, pb, pc)) & 0xff).chr
245
297
  else
246
298
  raise "invalid ScanLine filter #{@filter}"
@@ -4,7 +4,15 @@ class String
4
4
  [:black, :red, :green, :yellow, :blue, :magenta, :cyan, :white].each do |color|
5
5
  unless instance_methods.include?(color)
6
6
  define_method color do
7
- self.send(:color, color)
7
+ color(color)
8
+ end
9
+ end
10
+ end
11
+
12
+ [:gray, :grey].each do |color|
13
+ unless instance_methods.include?(color)
14
+ define_method color do
15
+ color(:black).bright
8
16
  end
9
17
  end
10
18
  end
@@ -0,0 +1,75 @@
1
+ module ZPNG
2
+ class TextChunk < Chunk
3
+ attr_accessor :keyword, :text
4
+
5
+ def inspect verbosity = 10
6
+ vars = %w'keyword text language translated_keyword cmethod cflag'
7
+ vars -= %w'text translated_keyword' if verbosity <=0
8
+ super.sub(/ *>$/,'') + ", " +
9
+ vars.map do |var|
10
+ t = instance_variable_get("@#{var}")
11
+ unless t.is_a?(Fixnum)
12
+ t = t.to_s
13
+ t = t[0..20] + "..." if t.size > 20
14
+ end
15
+ if t.nil? || t == ''
16
+ nil
17
+ else
18
+ "#{var.to_s.tr('@','')}=#{t.inspect}"
19
+ end
20
+ end.compact.join(", ") + ">"
21
+ end
22
+
23
+ def to_hash
24
+ { :keyword => keyword, :text => text}
25
+ end
26
+ end
27
+
28
+ class Chunk
29
+ class TEXT < TextChunk
30
+ def initialize *args
31
+ super
32
+ @keyword,@text = data.unpack('Z*a*')
33
+ end
34
+ end
35
+
36
+ class ZTXT < TextChunk
37
+ attr_accessor :cmethod # compression method
38
+ def initialize *args
39
+ super
40
+ @keyword,@cmethod,@text = data.unpack('Z*Ca*')
41
+ # current only @cmethod value is 0 - deflate
42
+ if @text
43
+ @text = Zlib::Inflate.inflate(@text)
44
+ end
45
+ end
46
+ end
47
+
48
+ class ITXT < TextChunk
49
+ attr_accessor :cflag, :cmethod # compression flag & method
50
+ attr_accessor :language, :translated_keyword
51
+ def initialize *args
52
+ super
53
+ # The text, unlike the other strings, is not null-terminated; its length is implied by the chunk length.
54
+ # http://www.libpng.org/pub/png/spec/1.2/PNG-Chunks.html#C.iTXt
55
+ @keyword, @cflag, @cmethod, @language, @translated_keyword, @text = data.unpack('Z*CCZ*Z*a*')
56
+ if @cflag == 1 && @cmethod == 0
57
+ @text = Zlib::Inflate.inflate(@text)
58
+ end
59
+ if @text
60
+ @text.force_encoding('utf-8') rescue nil
61
+ end
62
+ if @translated_keyword
63
+ @translated_keyword.force_encoding('utf-8') rescue nil
64
+ end
65
+ end
66
+
67
+ def to_hash
68
+ super.tap do |h|
69
+ h[:language] = @language if @language || !@language.empty?
70
+ h[:translated_keyword] = @translated_keyword if @translated_keyword || !@translated_keyword.empty?
71
+ end
72
+ end
73
+ end
74
+ end
75
+ end
Binary file
@@ -59,3 +59,27 @@ describe ZPNG::Adam7Decoder do
59
59
  end
60
60
  end
61
61
  end
62
+
63
+ PNGSuite.each("???i*.png") do |fname_i|
64
+ fname_n = File.basename(fname_i)
65
+ fname_n[3] = 'n'
66
+ fname_n = File.join(File.dirname(fname_i), fname_n)
67
+ next unless File.exist?(fname_n)
68
+
69
+ describe fname_i.sub(%r|\A#{Regexp::escape(Dir.getwd)}/?|, '') do
70
+ it "should be pixel-by-pixel identical to " + fname_n.sub(%r|\A#{Regexp::escape(Dir.getwd)}/?|, '') do
71
+ interlaced = ZPNG::Image.load(fname_i)
72
+ normal = ZPNG::Image.load(fname_n)
73
+
74
+ normal.pixels.to_a.should == interlaced.pixels.to_a
75
+
76
+ interlaced.each_pixel do |color,x,y|
77
+ normal[x,y].should == color
78
+ end
79
+
80
+ normal.each_pixel do |color,x,y|
81
+ interlaced[x,y].should == color
82
+ end
83
+ end
84
+ end
85
+ end