chunky_png 0.9.2 → 0.10.0
Sign up to get free protection for your applications and to get access to all the features.
- data/Gemfile.lock +1 -1
- data/benchmarks/decoding_benchmark.rb +8 -4
- data/benchmarks/encoding_benchmark.rb +3 -1
- data/benchmarks/filesize_benchmark.rb +30 -0
- data/chunky_png.gemspec +4 -4
- data/lib/chunky_png.rb +11 -1
- data/lib/chunky_png/canvas.rb +19 -2
- data/lib/chunky_png/canvas/drawing.rb +2 -2
- data/lib/chunky_png/canvas/operations.rb +1 -1
- data/lib/chunky_png/canvas/png_decoding.rb +85 -94
- data/lib/chunky_png/canvas/png_encoding.rb +120 -132
- data/lib/chunky_png/canvas/stream_importing.rb +2 -2
- data/spec/chunky_png/canvas/png_decoding_spec.rb +27 -29
- data/spec/chunky_png/canvas/png_encoding_spec.rb +103 -22
- data/spec/resources/pixelstream_best_compression.png +0 -0
- data/spec/resources/pixelstream_fast_rgba.png +0 -0
- data/tasks/github-gem.rake +24 -36
- metadata +87 -73
@@ -6,9 +6,9 @@ module ChunkyPNG
|
|
6
6
|
# Overview of the encoding process:
|
7
7
|
#
|
8
8
|
# * The image is split up in scanlines (i.e. rows of pixels);
|
9
|
-
# *
|
10
|
-
# *
|
11
|
-
#
|
9
|
+
# * All pixels are encoded as a pixelstream, based on the color mode.
|
10
|
+
# * All the pixel bytes in the pixelstream are adjusted using a filtering
|
11
|
+
# method if one is specified.
|
12
12
|
# * Compress the resulting string using deflate compression.
|
13
13
|
# * Split compressed data over one or more PNG chunks.
|
14
14
|
# * These chunks should be embedded in a datastream with at least a IHDR and
|
@@ -48,17 +48,17 @@ module ChunkyPNG
|
|
48
48
|
to_datastream(constraints).to_blob
|
49
49
|
end
|
50
50
|
|
51
|
-
|
52
|
-
|
51
|
+
alias_method :to_string, :to_blob
|
52
|
+
alias_method :to_s, :to_blob
|
53
53
|
|
54
54
|
# Converts this Canvas to a datastream, so that it can be saved as a PNG image.
|
55
55
|
# @param [Hash, Symbol] constraints The constraints to use when encoding the canvas.
|
56
56
|
# This can either be a hash with different constraints, or a symbol which acts as a
|
57
57
|
# preset for some constraints. If no constraints are given, ChunkyPNG will decide
|
58
58
|
# for itself how to best create the PNG datastream.
|
59
|
-
# Supported presets are
|
60
|
-
#
|
61
|
-
# smallest possible filesize.
|
59
|
+
# Supported presets are <tt>:fast_rgba</tt> for quickly saving images with transparency,
|
60
|
+
# <tt>:fast_rgb</tt> for quickly saving opaque images, and <tt>:best_compression</tt> to
|
61
|
+
# obtain the smallest possible filesize.
|
62
62
|
# @option constraints [Fixnum] :color_mode The color mode to use. Use one of the
|
63
63
|
# ChunkyPNG::COLOR_* constants.
|
64
64
|
# @option constraints [true, false] :interlace Whether to use interlacing.
|
@@ -68,17 +68,16 @@ module ChunkyPNG
|
|
68
68
|
# @see ChunkyPNG::Canvas::PNGEncoding#determine_png_encoding
|
69
69
|
def to_datastream(constraints = {})
|
70
70
|
encoding = determine_png_encoding(constraints)
|
71
|
-
|
71
|
+
|
72
72
|
ds = Datastream.new
|
73
73
|
ds.header_chunk = Chunk::Header.new(:width => width, :height => height,
|
74
74
|
:color => encoding[:color_mode], :interlace => encoding[:interlace])
|
75
|
-
|
75
|
+
|
76
76
|
if encoding[:color_mode] == ChunkyPNG::COLOR_INDEXED
|
77
77
|
ds.palette_chunk = encoding_palette.to_plte_chunk
|
78
78
|
ds.transparency_chunk = encoding_palette.to_trns_chunk unless encoding_palette.opaque?
|
79
79
|
end
|
80
|
-
|
81
|
-
data = encode_png_pixelstream(encoding[:color_mode], encoding[:interlace], encoding[:compression])
|
80
|
+
data = encode_png_pixelstream(encoding[:color_mode], encoding[:interlace], encoding[:filtering])
|
82
81
|
ds.data_chunks = Chunk::ImageData.split_in_chunks(data, encoding[:compression])
|
83
82
|
ds.end_chunk = Chunk::End.new
|
84
83
|
return ds
|
@@ -96,17 +95,21 @@ module ChunkyPNG
|
|
96
95
|
# Hash or a preset symbol.
|
97
96
|
# @return [Hash] A hash with encoding options for {ChunkyPNG::Canvas::PNGEncoding#to_datastream}
|
98
97
|
def determine_png_encoding(constraints = {})
|
99
|
-
|
98
|
+
|
100
99
|
if constraints == :fast_rgb
|
101
100
|
encoding = { :color_mode => ChunkyPNG::COLOR_TRUECOLOR, :compression => Zlib::BEST_SPEED }
|
102
101
|
elsif constraints == :fast_rgba
|
103
102
|
encoding = { :color_mode => ChunkyPNG::COLOR_TRUECOLOR_ALPHA, :compression => Zlib::BEST_SPEED }
|
104
103
|
elsif constraints == :best_compression
|
105
|
-
encoding = { :compression => Zlib::BEST_COMPRESSION }
|
104
|
+
encoding = { :compression => Zlib::BEST_COMPRESSION, :filtering => ChunkyPNG::FILTER_PAETH }
|
105
|
+
elsif constraints == :good_compression
|
106
|
+
encoding = { :compression => Zlib::BEST_COMPRESSION, :filtering => ChunkyPNG::FILTER_NONE }
|
107
|
+
elsif constraints == :no_compression
|
108
|
+
encoding = { :compression => Zlib::NO_COMPRESSION }
|
106
109
|
else
|
107
110
|
encoding = constraints
|
108
111
|
end
|
109
|
-
|
112
|
+
|
110
113
|
# Do not create a pallete when the encoding is given and does not require a palette.
|
111
114
|
if encoding[:color_mode]
|
112
115
|
if encoding[:color_mode] == ChunkyPNG::COLOR_INDEXED
|
@@ -116,47 +119,52 @@ module ChunkyPNG
|
|
116
119
|
self.encoding_palette = self.palette
|
117
120
|
encoding[:color_mode] ||= encoding_palette.best_colormode
|
118
121
|
end
|
119
|
-
|
122
|
+
|
123
|
+
# Use Zlib's default for compression unless otherwise provided.
|
120
124
|
encoding[:compression] ||= Zlib::DEFAULT_COMPRESSION
|
121
|
-
|
125
|
+
|
122
126
|
encoding[:interlace] = case encoding[:interlace]
|
123
|
-
when nil, false, ChunkyPNG::INTERLACING_NONE
|
124
|
-
when true, ChunkyPNG::INTERLACING_ADAM7
|
127
|
+
when nil, false, ChunkyPNG::INTERLACING_NONE; ChunkyPNG::INTERLACING_NONE
|
128
|
+
when true, ChunkyPNG::INTERLACING_ADAM7; ChunkyPNG::INTERLACING_ADAM7
|
125
129
|
else encoding[:interlace]
|
126
130
|
end
|
127
131
|
|
132
|
+
encoding[:filtering] ||= case encoding[:compression]
|
133
|
+
when Zlib::BEST_COMPRESSION; ChunkyPNG::FILTER_PAETH
|
134
|
+
when Zlib::NO_COMPRESSION..Zlib::BEST_SPEED; ChunkyPNG::FILTER_NONE
|
135
|
+
else ChunkyPNG::FILTER_UP
|
136
|
+
end
|
128
137
|
return encoding
|
129
138
|
end
|
130
|
-
|
139
|
+
|
131
140
|
# Encodes the canvas according to the PNG format specification with a given color
|
132
141
|
# mode, possibly with interlacing.
|
133
142
|
# @param [Integer] color_mode The color mode to use for encoding.
|
134
143
|
# @param [Integer] interlace The interlacing method to use.
|
135
|
-
# @param [Integer] compression The Zlib compression level.
|
136
144
|
# @return [String] The PNG encoded canvas as string.
|
137
|
-
def encode_png_pixelstream(color_mode = ChunkyPNG::COLOR_TRUECOLOR, interlace = ChunkyPNG::INTERLACING_NONE,
|
145
|
+
def encode_png_pixelstream(color_mode = ChunkyPNG::COLOR_TRUECOLOR, interlace = ChunkyPNG::INTERLACING_NONE, filtering = ChunkyPNG::FILTER_NONE)
|
138
146
|
|
139
147
|
if color_mode == ChunkyPNG::COLOR_INDEXED && (encoding_palette.nil? || !encoding_palette.can_encode?)
|
140
148
|
raise ChunkyPNG::ExpectationFailed, "This palette is not suitable for encoding!"
|
141
149
|
end
|
142
150
|
|
143
151
|
case interlace
|
144
|
-
when ChunkyPNG::INTERLACING_NONE then encode_png_image_without_interlacing(color_mode,
|
145
|
-
when ChunkyPNG::INTERLACING_ADAM7 then encode_png_image_with_interlacing(color_mode,
|
152
|
+
when ChunkyPNG::INTERLACING_NONE then encode_png_image_without_interlacing(color_mode, filtering)
|
153
|
+
when ChunkyPNG::INTERLACING_ADAM7 then encode_png_image_with_interlacing(color_mode, filtering)
|
146
154
|
else raise ChunkyPNG::NotSupported, "Unknown interlacing method: #{interlace}!"
|
147
155
|
end
|
148
156
|
end
|
149
157
|
|
150
158
|
# Encodes the canvas according to the PNG format specification with a given color mode.
|
151
159
|
# @param [Integer] color_mode The color mode to use for encoding.
|
152
|
-
# @param [Integer]
|
160
|
+
# @param [Integer] filtering The filtering method to use.
|
153
161
|
# @return [String] The PNG encoded canvas as string.
|
154
|
-
def encode_png_image_without_interlacing(color_mode,
|
162
|
+
def encode_png_image_without_interlacing(color_mode, filtering = ChunkyPNG::FILTER_NONE)
|
155
163
|
stream = ""
|
156
|
-
encode_png_image_pass_to_stream(stream, color_mode,
|
164
|
+
encode_png_image_pass_to_stream(stream, color_mode, filtering)
|
157
165
|
stream
|
158
166
|
end
|
159
|
-
|
167
|
+
|
160
168
|
# Encodes the canvas according to the PNG format specification with a given color
|
161
169
|
# mode and Adam7 interlacing.
|
162
170
|
#
|
@@ -164,141 +172,121 @@ module ChunkyPNG
|
|
164
172
|
# one by one, concatenating the resulting strings.
|
165
173
|
#
|
166
174
|
# @param [Integer] color_mode The color mode to use for encoding.
|
167
|
-
# @param [Integer]
|
175
|
+
# @param [Integer] filtering The filtering method to use.
|
168
176
|
# @return [String] The PNG encoded canvas as string.
|
169
|
-
def encode_png_image_with_interlacing(color_mode,
|
177
|
+
def encode_png_image_with_interlacing(color_mode, filtering = ChunkyPNG::FILTER_NONE)
|
170
178
|
stream = ""
|
171
179
|
0.upto(6) do |pass|
|
172
180
|
subcanvas = self.class.adam7_extract_pass(pass, self)
|
173
181
|
subcanvas.encoding_palette = encoding_palette
|
174
|
-
subcanvas.encode_png_image_pass_to_stream(stream, color_mode,
|
182
|
+
subcanvas.encode_png_image_pass_to_stream(stream, color_mode, filtering)
|
175
183
|
end
|
176
184
|
stream
|
177
185
|
end
|
178
|
-
|
186
|
+
|
179
187
|
# Encodes the canvas to a stream, in a given color mode.
|
180
|
-
# @param [String
|
188
|
+
# @param [String] stream The stream to write to.
|
181
189
|
# @param [Integer] color_mode The color mode to use for encoding.
|
182
|
-
# @param [Integer]
|
183
|
-
def encode_png_image_pass_to_stream(stream, color_mode,
|
190
|
+
# @param [Integer] filtering The filtering method to use.
|
191
|
+
def encode_png_image_pass_to_stream(stream, color_mode, filtering = ChunkyPNG::FILTER_NONE)
|
184
192
|
|
185
|
-
|
186
|
-
|
187
|
-
|
188
|
-
|
189
|
-
|
190
|
-
|
191
|
-
|
192
|
-
|
193
|
-
|
194
|
-
|
195
|
-
#
|
196
|
-
|
197
|
-
pixel_encoder = case color_mode
|
198
|
-
when ChunkyPNG::COLOR_TRUECOLOR then lambda { |color| Color.to_truecolor_bytes(color) }
|
199
|
-
when ChunkyPNG::COLOR_TRUECOLOR_ALPHA then lambda { |color| Color.to_truecolor_alpha_bytes(color) }
|
200
|
-
when ChunkyPNG::COLOR_INDEXED then lambda { |color| [encoding_palette.index(color)] }
|
201
|
-
when ChunkyPNG::COLOR_GRAYSCALE then lambda { |color| Color.to_grayscale_bytes(color) }
|
202
|
-
when ChunkyPNG::COLOR_GRAYSCALE_ALPHA then lambda { |color| Color.to_grayscale_alpha_bytes(color) }
|
203
|
-
else raise ChunkyPNG::NotSupported, "Cannot encode pixels for this mode: #{color_mode}!"
|
204
|
-
end
|
205
|
-
|
206
|
-
previous_bytes = Array.new(pixel_size * width, 0)
|
207
|
-
each_scanline do |line|
|
208
|
-
unencoded_bytes = line.map(&pixel_encoder).flatten
|
209
|
-
stream << encode_png_scanline_paeth(unencoded_bytes, previous_bytes, pixel_size).pack('C*')
|
210
|
-
previous_bytes = unencoded_bytes
|
211
|
-
end
|
193
|
+
start_pos = stream.bytesize
|
194
|
+
pixel_size = Color.bytesize(color_mode)
|
195
|
+
line_width = pixel_size * width
|
196
|
+
|
197
|
+
# Encode the whole image without filtering
|
198
|
+
stream << case color_mode
|
199
|
+
when ChunkyPNG::COLOR_TRUECOLOR; pixels.pack(('x' + ('NX' * width)) * height)
|
200
|
+
when ChunkyPNG::COLOR_TRUECOLOR_ALPHA; pixels.pack("xN#{width}" * height)
|
201
|
+
when ChunkyPNG::COLOR_INDEXED; pixels.map { |p| encoding_palette.index(p) }.pack("xC#{width}" * height)
|
202
|
+
when ChunkyPNG::COLOR_GRAYSCALE; pixels.map { |p| p >> 8 }.pack("xC#{width}" * height)
|
203
|
+
when ChunkyPNG::COLOR_GRAYSCALE_ALPHA; pixels.pack("xn#{width}" * height)
|
204
|
+
else raise ChunkyPNG::NotSupported, "Cannot encode pixels for this mode: #{color_mode}!"
|
212
205
|
end
|
213
|
-
|
214
|
-
|
215
|
-
|
216
|
-
|
217
|
-
|
218
|
-
|
219
|
-
|
220
|
-
|
221
|
-
yield(scanline)
|
206
|
+
|
207
|
+
# Determine the filter method
|
208
|
+
filter_method = case filtering
|
209
|
+
when ChunkyPNG::FILTER_SUB; :encode_png_str_scanline_sub
|
210
|
+
when ChunkyPNG::FILTER_UP; :encode_png_str_scanline_up
|
211
|
+
when ChunkyPNG::FILTER_AVERAGE; :encode_png_str_scanline_average
|
212
|
+
when ChunkyPNG::FILTER_PAETH; :encode_png_str_scanline_paeth
|
213
|
+
else nil
|
222
214
|
end
|
223
|
-
|
224
|
-
|
225
|
-
|
226
|
-
|
227
|
-
|
228
|
-
|
229
|
-
|
230
|
-
|
231
|
-
def encode_png_scanline(filter, bytes, previous_bytes = nil, pixelsize = 3)
|
232
|
-
case filter
|
233
|
-
when ChunkyPNG::FILTER_NONE then encode_png_scanline_none( bytes, previous_bytes, pixelsize)
|
234
|
-
when ChunkyPNG::FILTER_SUB then encode_png_scanline_sub( bytes, previous_bytes, pixelsize)
|
235
|
-
when ChunkyPNG::FILTER_UP then encode_png_scanline_up( bytes, previous_bytes, pixelsize)
|
236
|
-
when ChunkyPNG::FILTER_AVERAGE then encode_png_scanline_average( bytes, previous_bytes, pixelsize)
|
237
|
-
when ChunkyPNG::FILTER_PAETH then encode_png_scanline_paeth( bytes, previous_bytes, pixelsize)
|
238
|
-
else raise ChunkyPNG::NotSupported, "Unknown filter type: #{filter}!"
|
215
|
+
|
216
|
+
# Now, apply filtering if any
|
217
|
+
if filter_method
|
218
|
+
(height - 1).downto(0) do |y|
|
219
|
+
pos = start_pos + y * (line_width + 1)
|
220
|
+
prev_pos = (y == 0) ? nil : pos - (line_width + 1)
|
221
|
+
send(filter_method, stream, pos, prev_pos, line_width, pixel_size)
|
222
|
+
end
|
239
223
|
end
|
240
224
|
end
|
241
225
|
|
242
|
-
# Encodes
|
243
|
-
# @param [
|
244
|
-
# @param [
|
245
|
-
# @param [Integer]
|
246
|
-
#
|
247
|
-
|
248
|
-
|
226
|
+
# Encodes a scanline of a pixelstream without filtering. This is a no-op.
|
227
|
+
# @param [String] stream The pixelstream to work on. This string will be modified.
|
228
|
+
# @param [Integer] pos The starting position of the scanline.
|
229
|
+
# @param [Integer, nil] prev_pos The starting position of the previous scanline. <tt>nil</tt> if
|
230
|
+
# this is the first line.
|
231
|
+
# @param [Integer] line_width The number of bytes in this scanline, without counting the filtering
|
232
|
+
# method byte.
|
233
|
+
# @param [Integer] pixel_size The number of bytes used per pixel.
|
234
|
+
# @return [nil]
|
235
|
+
def encode_png_str_scanline_none(stream, pos, prev_pos, line_width, pixel_size)
|
236
|
+
# noop - this method shouldn't get called at all.
|
249
237
|
end
|
250
238
|
|
251
|
-
# Encodes
|
252
|
-
# @param (see
|
253
|
-
|
254
|
-
|
255
|
-
|
256
|
-
a = (
|
257
|
-
|
239
|
+
# Encodes a scanline of a pixelstream using SUB filtering. This will modify the stream.
|
240
|
+
# @param (see #encode_png_str_scanline_none)
|
241
|
+
# @return [nil]
|
242
|
+
def encode_png_str_scanline_sub(stream, pos, prev_pos, line_width, pixel_size)
|
243
|
+
line_width.downto(1) do |i|
|
244
|
+
a = (i > pixel_size) ? stream.getbyte(pos + i - pixel_size) : 0
|
245
|
+
stream.setbyte(pos + i, (stream.getbyte(pos + i) - a) & 0xff)
|
258
246
|
end
|
259
|
-
|
247
|
+
stream.setbyte(pos, ChunkyPNG::FILTER_SUB)
|
260
248
|
end
|
261
249
|
|
262
|
-
# Encodes
|
263
|
-
# @param (see
|
264
|
-
|
265
|
-
|
266
|
-
|
267
|
-
b =
|
268
|
-
|
250
|
+
# Encodes a scanline of a pixelstream using UP filtering. This will modify the stream.
|
251
|
+
# @param (see #encode_png_str_scanline_none)
|
252
|
+
# @return [nil]
|
253
|
+
def encode_png_str_scanline_up(stream, pos, prev_pos, line_width, pixel_size)
|
254
|
+
line_width.downto(1) do |i|
|
255
|
+
b = prev_pos ? stream.getbyte(prev_pos + i) : 0
|
256
|
+
stream.setbyte(pos + i, (stream.getbyte(pos + i) - b) & 0xff)
|
269
257
|
end
|
270
|
-
|
258
|
+
stream.setbyte(pos, ChunkyPNG::FILTER_UP)
|
271
259
|
end
|
272
|
-
|
273
|
-
# Encodes
|
274
|
-
# @param (see
|
275
|
-
|
276
|
-
|
277
|
-
|
278
|
-
a = (
|
279
|
-
b =
|
280
|
-
|
260
|
+
|
261
|
+
# Encodes a scanline of a pixelstream using AVERAGE filtering. This will modify the stream.
|
262
|
+
# @param (see #encode_png_str_scanline_none)
|
263
|
+
# @return [nil]
|
264
|
+
def encode_png_str_scanline_average(stream, pos, prev_pos, line_width, pixel_size)
|
265
|
+
line_width.downto(1) do |i|
|
266
|
+
a = (i > pixel_size) ? stream.getbyte(pos + i - pixel_size) : 0
|
267
|
+
b = prev_pos ? stream.getbyte(prev_pos + i) : 0
|
268
|
+
stream.setbyte(pos + i, (stream.getbyte(pos + i) - ((a + b) >> 1)) & 0xff)
|
281
269
|
end
|
282
|
-
|
270
|
+
stream.setbyte(pos, ChunkyPNG::FILTER_AVERAGE)
|
283
271
|
end
|
284
|
-
|
285
|
-
# Encodes
|
286
|
-
# @param (see
|
287
|
-
|
288
|
-
|
289
|
-
|
290
|
-
a = (i
|
291
|
-
b =
|
292
|
-
c = (i
|
272
|
+
|
273
|
+
# Encodes a scanline of a pixelstream using PAETH filtering. This will modify the stream.
|
274
|
+
# @param (see #encode_png_str_scanline_none)
|
275
|
+
# @return [nil]
|
276
|
+
def encode_png_str_scanline_paeth(stream, pos, prev_pos, line_width, pixel_size)
|
277
|
+
line_width.downto(1) do |i|
|
278
|
+
a = (i > pixel_size) ? stream.getbyte(pos + i - pixel_size) : 0
|
279
|
+
b = (prev_pos) ? stream.getbyte(prev_pos + i) : 0
|
280
|
+
c = (prev_pos && i > pixel_size) ? stream.getbyte(prev_pos + i - pixel_size) : 0
|
293
281
|
p = a + b - c
|
294
282
|
pa = (p - a).abs
|
295
283
|
pb = (p - b).abs
|
296
284
|
pc = (p - c).abs
|
297
285
|
pr = (pa <= pb && pa <= pc) ? a : (pb <= pc ? b : c)
|
298
|
-
|
286
|
+
stream.setbyte(pos + i, (stream.getbyte(pos + i) - pr) & 0xff)
|
299
287
|
end
|
300
|
-
|
301
|
-
end
|
288
|
+
stream.setbyte(pos, ChunkyPNG::FILTER_PAETH)
|
289
|
+
end
|
302
290
|
end
|
303
291
|
end
|
304
292
|
end
|
@@ -17,7 +17,7 @@ module ChunkyPNG
|
|
17
17
|
# @return [ChunkyPNG::Canvas] The newly constructed canvas instance.
|
18
18
|
def from_rgb_stream(width, height, stream)
|
19
19
|
string = stream.respond_to?(:read) ? stream.read(3 * width * height) : stream.to_s[0, 3 * width * height]
|
20
|
-
string <<
|
20
|
+
string << ChunkyPNG::EXTRA_BYTE # Add a fourth byte to the last RGB triple.
|
21
21
|
unpacker = 'NX' * (width * height)
|
22
22
|
pixels = string.unpack(unpacker).map { |color| color | 0x000000ff }
|
23
23
|
self.new(width, height, pixels)
|
@@ -51,7 +51,7 @@ module ChunkyPNG
|
|
51
51
|
# @param [#read, String] stream The stream to read the pixel data from.
|
52
52
|
# @return [ChunkyPNG::Canvas] The newly constructed canvas instance.
|
53
53
|
def from_bgr_stream(width, height, stream)
|
54
|
-
string =
|
54
|
+
string = ChunkyPNG::EXTRA_BYTE # Add a first byte to the first BGR triple.
|
55
55
|
string << stream.respond_to?(:read) ? stream.read(3 * width * height) : stream.to_s[0, 3 * width * height]
|
56
56
|
pixels = string.unpack("@1" << ('XV' * (width * height))).map { |color| color | 0x000000ff }
|
57
57
|
self.new(width, height, pixels)
|
@@ -6,53 +6,51 @@ describe ChunkyPNG::Canvas::PNGDecoding do
|
|
6
6
|
describe '#decode_png_scanline' do
|
7
7
|
|
8
8
|
it "should decode a line without filtering as is" do
|
9
|
-
|
10
|
-
|
9
|
+
stream = [ChunkyPNG::FILTER_NONE, 255, 255, 255, 255, 255, 255, 255, 255, 255].pack('C*')
|
10
|
+
decode_png_str_scanline(stream, 0, nil, 9, 3)
|
11
|
+
stream.unpack('@1C*').should == [255, 255, 255, 255, 255, 255, 255, 255, 255]
|
11
12
|
end
|
12
13
|
|
13
14
|
it "should decode a line with sub filtering correctly" do
|
14
15
|
# all white pixels
|
15
|
-
|
16
|
-
|
17
|
-
|
16
|
+
stream = [ChunkyPNG::FILTER_SUB, 255, 255, 255, 0, 0, 0, 0, 0, 0].pack('C*')
|
17
|
+
decode_png_str_scanline(stream, 0, nil, 9, 3)
|
18
|
+
stream.unpack('@1C*').should == [255, 255, 255, 255, 255, 255, 255, 255, 255]
|
18
19
|
|
19
20
|
# all black pixels
|
20
|
-
|
21
|
-
|
22
|
-
|
21
|
+
stream = [ChunkyPNG::FILTER_SUB, 0, 0, 0, 0, 0, 0, 0, 0, 0].pack('C*')
|
22
|
+
decode_png_str_scanline(stream, 0, nil, 9, 3)
|
23
|
+
stream.unpack('@1C*').should == [0, 0, 0, 0, 0, 0, 0, 0, 0]
|
23
24
|
|
24
25
|
# various colors
|
25
|
-
|
26
|
-
|
27
|
-
|
26
|
+
stream = [ChunkyPNG::FILTER_SUB, 255, 0, 45, 0, 255, 0, 112, 200, 178].pack('C*')
|
27
|
+
decode_png_str_scanline(stream, 0, nil, 9, 3)
|
28
|
+
stream.unpack('@1C*').should == [255, 0, 45, 255, 255, 45, 111, 199, 223]
|
28
29
|
end
|
29
30
|
|
30
31
|
it "should decode a line with up filtering correctly" do
|
31
|
-
# previous line is all black
|
32
|
-
previous_bytes = [0, 0, 0, 0, 0, 0, 0, 0, 0]
|
33
|
-
bytes = [255, 255, 255, 127, 127, 127, 0, 0, 0]
|
34
|
-
decoded_bytes = decode_png_scanline(ChunkyPNG::FILTER_UP, bytes, previous_bytes)
|
35
|
-
decoded_bytes.should == [255, 255, 255, 127, 127, 127, 0, 0, 0]
|
36
|
-
|
37
32
|
# previous line has various pixels
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
33
|
+
previous = [ChunkyPNG::FILTER_UP, 255, 255, 255, 127, 127, 127, 0, 0, 0]
|
34
|
+
current = [ChunkyPNG::FILTER_UP, 0, 127, 255, 0, 127, 255, 0, 127, 255]
|
35
|
+
stream = (previous + current).pack('C*')
|
36
|
+
decode_png_str_scanline(stream, 10, 0, 9, 3)
|
37
|
+
stream.unpack('@11C9').should == [255, 126, 254, 127, 254, 126, 0, 127, 255]
|
42
38
|
end
|
43
39
|
|
44
40
|
it "should decode a line with average filtering correctly" do
|
45
|
-
previous = [10, 20, 30, 40, 50, 60, 70, 80, 80, 100, 110, 120]
|
46
|
-
current = [
|
47
|
-
|
48
|
-
|
41
|
+
previous = [ChunkyPNG::FILTER_AVERAGE, 10, 20, 30, 40, 50, 60, 70, 80, 80, 100, 110, 120]
|
42
|
+
current = [ChunkyPNG::FILTER_AVERAGE, 0, 0, 10, 23, 15, 13, 23, 63, 38, 60, 253, 53]
|
43
|
+
stream = (previous + current).pack('C*')
|
44
|
+
decode_png_str_scanline(stream, 13, 0, 12, 3)
|
45
|
+
stream.unpack('@14C12').should == [5, 10, 25, 45, 45, 55, 80, 125, 105, 150, 114, 165]
|
49
46
|
end
|
50
47
|
|
51
48
|
it "should decode a line with paeth filtering correctly" do
|
52
|
-
previous = [10, 20, 30, 40, 50, 60, 70, 80, 80, 100, 110, 120]
|
53
|
-
current = [
|
54
|
-
|
55
|
-
|
49
|
+
previous = [ChunkyPNG::FILTER_PAETH, 10, 20, 30, 40, 50, 60, 70, 80, 80, 100, 110, 120]
|
50
|
+
current = [ChunkyPNG::FILTER_PAETH, 0, 0, 10, 20, 10, 0, 0, 40, 10, 20, 190, 0]
|
51
|
+
stream = (previous + current).pack('C*')
|
52
|
+
decode_png_str_scanline(stream, 13, 0, 12, 3)
|
53
|
+
stream.unpack('@14C12').should == [10, 20, 40, 60, 60, 60, 70, 120, 90, 120, 54, 120]
|
56
54
|
end
|
57
55
|
end
|
58
56
|
|