zpng 0.2.0 → 0.2.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.
@@ -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