chunky_png 0.9.2 → 0.10.0
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/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
|
|