chunky_png 1.3.11 → 1.4.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +5 -5
- data/.github/workflows/ruby.yml +35 -0
- data/.standard.yml +16 -0
- data/.yardopts +1 -1
- data/CHANGELOG.rdoc +8 -4
- data/CONTRIBUTING.rdoc +17 -8
- data/Gemfile +12 -4
- data/LICENSE +1 -1
- data/README.md +15 -9
- data/Rakefile +5 -3
- data/benchmarks/decoding_benchmark.rb +17 -17
- data/benchmarks/encoding_benchmark.rb +22 -19
- data/benchmarks/filesize_benchmark.rb +6 -6
- data/bin/rake +29 -0
- data/bin/standardrb +29 -0
- data/chunky_png.gemspec +19 -15
- data/docs/.gitignore +3 -0
- data/docs/CNAME +1 -0
- data/docs/_config.yml +9 -0
- data/docs/_posts/2010-01-14-memory-efficiency-when-using-ruby.md +136 -0
- data/docs/_posts/2010-01-17-ode-to-array-pack-and-string-unpack.md +82 -0
- data/docs/_posts/2014-11-07-the-value-of-a-pure-ruby-library.md +61 -0
- data/docs/index.md +88 -0
- data/lib/chunky_png.rb +17 -30
- data/lib/chunky_png/canvas.rb +31 -28
- data/lib/chunky_png/canvas/adam7_interlacing.rb +16 -10
- data/lib/chunky_png/canvas/data_url_exporting.rb +3 -3
- data/lib/chunky_png/canvas/data_url_importing.rb +3 -3
- data/lib/chunky_png/canvas/drawing.rb +30 -43
- data/lib/chunky_png/canvas/masking.rb +14 -14
- data/lib/chunky_png/canvas/operations.rb +28 -24
- data/lib/chunky_png/canvas/png_decoding.rb +39 -33
- data/lib/chunky_png/canvas/png_encoding.rb +111 -103
- data/lib/chunky_png/canvas/resampling.rb +27 -32
- data/lib/chunky_png/canvas/stream_exporting.rb +8 -8
- data/lib/chunky_png/canvas/stream_importing.rb +8 -8
- data/lib/chunky_png/chunk.rb +101 -66
- data/lib/chunky_png/color.rb +218 -212
- data/lib/chunky_png/datastream.rb +22 -28
- data/lib/chunky_png/dimension.rb +18 -11
- data/lib/chunky_png/image.rb +11 -11
- data/lib/chunky_png/palette.rb +13 -14
- data/lib/chunky_png/point.rb +27 -26
- data/lib/chunky_png/rmagick.rb +10 -10
- data/lib/chunky_png/vector.rb +28 -29
- data/lib/chunky_png/version.rb +3 -1
- data/spec/chunky_png/canvas/adam7_interlacing_spec.rb +20 -21
- data/spec/chunky_png/canvas/data_url_exporting_spec.rb +8 -5
- data/spec/chunky_png/canvas/data_url_importing_spec.rb +5 -6
- data/spec/chunky_png/canvas/drawing_spec.rb +46 -38
- data/spec/chunky_png/canvas/masking_spec.rb +15 -16
- data/spec/chunky_png/canvas/operations_spec.rb +68 -67
- data/spec/chunky_png/canvas/png_decoding_spec.rb +37 -38
- data/spec/chunky_png/canvas/png_encoding_spec.rb +59 -50
- data/spec/chunky_png/canvas/resampling_spec.rb +19 -21
- data/spec/chunky_png/canvas/stream_exporting_spec.rb +47 -27
- data/spec/chunky_png/canvas/stream_importing_spec.rb +10 -11
- data/spec/chunky_png/canvas_spec.rb +63 -52
- data/spec/chunky_png/color_spec.rb +115 -114
- data/spec/chunky_png/datastream_spec.rb +49 -51
- data/spec/chunky_png/dimension_spec.rb +10 -10
- data/spec/chunky_png/image_spec.rb +11 -14
- data/spec/chunky_png/point_spec.rb +21 -23
- data/spec/chunky_png/rmagick_spec.rb +7 -8
- data/spec/chunky_png/vector_spec.rb +21 -17
- data/spec/chunky_png_spec.rb +2 -2
- data/spec/png_suite_spec.rb +35 -40
- data/spec/spec_helper.rb +6 -10
- data/tasks/benchmarks.rake +7 -8
- metadata +47 -11
- data/.travis.yml +0 -18
- data/lib/chunky_png/compatibility.rb +0 -15
@@ -1,3 +1,5 @@
|
|
1
|
+
# frozen-string-literal: true
|
2
|
+
|
1
3
|
module ChunkyPNG
|
2
4
|
class Canvas
|
3
5
|
# The ChunkyPNG::Canvas::Operations module defines methods to perform
|
@@ -15,8 +17,8 @@ module ChunkyPNG
|
|
15
17
|
# the current instance intact, use {#grayscale} instead.
|
16
18
|
#
|
17
19
|
# @return [ChunkyPNG::Canvas] Returns itself, converted to grayscale.
|
18
|
-
# @see
|
19
|
-
# @see
|
20
|
+
# @see #grayscale
|
21
|
+
# @see ChunkyPNG::Color#to_grayscale
|
20
22
|
def grayscale!
|
21
23
|
pixels.map! { |pixel| ChunkyPNG::Color.to_grayscale(pixel) }
|
22
24
|
self
|
@@ -29,8 +31,8 @@ module ChunkyPNG
|
|
29
31
|
#
|
30
32
|
# @return [ChunkyPNG::Canvas] A copy of the canvas, converted to
|
31
33
|
# grayscale.
|
32
|
-
# @see
|
33
|
-
# @see
|
34
|
+
# @see #grayscale!
|
35
|
+
# @see ChunkyPNG::Color#to_grayscale
|
34
36
|
def grayscale
|
35
37
|
dup.grayscale!
|
36
38
|
end
|
@@ -56,11 +58,14 @@ module ChunkyPNG
|
|
56
58
|
|
57
59
|
for y in 0...other.height do
|
58
60
|
for x in 0...other.width do
|
59
|
-
set_pixel(
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
61
|
+
set_pixel(
|
62
|
+
x + offset_x,
|
63
|
+
y + offset_y,
|
64
|
+
ChunkyPNG::Color.compose(
|
65
|
+
other.get_pixel(x, y),
|
66
|
+
get_pixel(x + offset_x, y + offset_y)
|
67
|
+
)
|
68
|
+
)
|
64
69
|
end
|
65
70
|
end
|
66
71
|
self
|
@@ -169,17 +174,16 @@ module ChunkyPNG
|
|
169
174
|
# coordinates are bigger then the original image.
|
170
175
|
def crop!(x, y, crop_width, crop_height)
|
171
176
|
if crop_width + x > width
|
172
|
-
raise ChunkyPNG::OutOfBounds,
|
177
|
+
raise ChunkyPNG::OutOfBounds, "Original image width is too small!"
|
173
178
|
end
|
174
179
|
if crop_height + y > height
|
175
|
-
raise ChunkyPNG::OutOfBounds,
|
180
|
+
raise ChunkyPNG::OutOfBounds, "Original image height is too small!"
|
176
181
|
end
|
177
182
|
|
178
183
|
if crop_width == width && x == 0
|
179
184
|
# We only need to crop off the top and/or bottom, so we can take a
|
180
185
|
# shortcut.
|
181
|
-
replace_canvas!(crop_width, crop_height,
|
182
|
-
pixels.slice(y * width, width * crop_height))
|
186
|
+
replace_canvas!(crop_width, crop_height, pixels.slice(y * width, width * crop_height))
|
183
187
|
else
|
184
188
|
new_pixels = []
|
185
189
|
for cy in 0...crop_height do
|
@@ -221,8 +225,8 @@ module ChunkyPNG
|
|
221
225
|
self
|
222
226
|
end
|
223
227
|
|
224
|
-
|
225
|
-
|
228
|
+
alias flip! flip_horizontally!
|
229
|
+
alias flip flip_horizontally
|
226
230
|
|
227
231
|
# Flips the image vertically, leaving the original intact.
|
228
232
|
#
|
@@ -253,8 +257,8 @@ module ChunkyPNG
|
|
253
257
|
self
|
254
258
|
end
|
255
259
|
|
256
|
-
|
257
|
-
|
260
|
+
alias mirror! flip_vertically!
|
261
|
+
alias mirror flip_vertically
|
258
262
|
|
259
263
|
# Returns a new canvas instance that is rotated 90 degrees clockwise.
|
260
264
|
#
|
@@ -278,8 +282,8 @@ module ChunkyPNG
|
|
278
282
|
replace_canvas!(height, width, new_pixels)
|
279
283
|
end
|
280
284
|
|
281
|
-
|
282
|
-
|
285
|
+
alias rotate_clockwise rotate_right
|
286
|
+
alias rotate_clockwise! rotate_right!
|
283
287
|
|
284
288
|
# Returns an image that is rotated 90 degrees counter-clockwise.
|
285
289
|
#
|
@@ -305,8 +309,8 @@ module ChunkyPNG
|
|
305
309
|
replace_canvas!(height, width, new_pixels)
|
306
310
|
end
|
307
311
|
|
308
|
-
|
309
|
-
|
312
|
+
alias rotate_counter_clockwise rotate_left
|
313
|
+
alias rotate_counter_clockwise! rotate_left!
|
310
314
|
|
311
315
|
# Rotates the image 180 degrees.
|
312
316
|
#
|
@@ -389,11 +393,11 @@ module ChunkyPNG
|
|
389
393
|
# applied.
|
390
394
|
# @raise [ChunkyPNG::OutOfBounds] when the other image doesn't fit.
|
391
395
|
def check_size_constraints!(other, offset_x, offset_y)
|
392
|
-
if width
|
393
|
-
raise ChunkyPNG::OutOfBounds,
|
396
|
+
if width < other.width + offset_x
|
397
|
+
raise ChunkyPNG::OutOfBounds, "Background image width is too small!"
|
394
398
|
end
|
395
399
|
if height < other.height + offset_y
|
396
|
-
raise ChunkyPNG::OutOfBounds,
|
400
|
+
raise ChunkyPNG::OutOfBounds, "Background image height is too small!"
|
397
401
|
end
|
398
402
|
end
|
399
403
|
end
|
@@ -1,6 +1,7 @@
|
|
1
|
+
# frozen-string-literal: true
|
2
|
+
|
1
3
|
module ChunkyPNG
|
2
4
|
class Canvas
|
3
|
-
|
4
5
|
# The PNGDecoding contains methods for decoding PNG datastreams to create a
|
5
6
|
# Canvas object. The datastream can be provided as filename, string or IO
|
6
7
|
# stream.
|
@@ -26,9 +27,8 @@ module ChunkyPNG
|
|
26
27
|
# combined to form the original images.
|
27
28
|
#
|
28
29
|
# @see ChunkyPNG::Canvas::PNGEncoding
|
29
|
-
# @see
|
30
|
+
# @see https://www.w3.org/TR/PNG/ The W3C PNG format specification
|
30
31
|
module PNGDecoding
|
31
|
-
|
32
32
|
# Decodes a Canvas from a PNG encoded string.
|
33
33
|
# @param [String] str The string to read from.
|
34
34
|
# @return [ChunkyPNG::Canvas] The canvas decoded from the PNG encoded string.
|
@@ -36,7 +36,7 @@ module ChunkyPNG
|
|
36
36
|
from_datastream(ChunkyPNG::Datastream.from_blob(str))
|
37
37
|
end
|
38
38
|
|
39
|
-
|
39
|
+
alias from_string from_blob
|
40
40
|
|
41
41
|
# Decodes a Canvas from a PNG encoded file.
|
42
42
|
# @param [String] filename The file to read from.
|
@@ -52,7 +52,7 @@ module ChunkyPNG
|
|
52
52
|
from_datastream(ChunkyPNG::Datastream.from_io(io))
|
53
53
|
end
|
54
54
|
|
55
|
-
|
55
|
+
alias from_stream from_io
|
56
56
|
|
57
57
|
# Decodes the Canvas from a PNG datastream instance.
|
58
58
|
# @param [ChunkyPNG::Datastream] ds The datastream to decode.
|
@@ -85,7 +85,7 @@ module ChunkyPNG
|
|
85
85
|
# color mode and interlacing mode.
|
86
86
|
# @param [String] stream The pixelstream to read from.
|
87
87
|
# @param [Integer] width The width of the image.
|
88
|
-
# @param [Integer]
|
88
|
+
# @param [Integer] height The height of the image.
|
89
89
|
# @param [Integer] color_mode The color mode of the encoded pixelstream.
|
90
90
|
# @param [Integer] depth The bit depth of the pixel samples.
|
91
91
|
# @param [Integer] interlace The interlace method of the encoded pixelstream.
|
@@ -96,13 +96,13 @@ module ChunkyPNG
|
|
96
96
|
raise ChunkyPNG::ExpectationFailed, "This palette is not suitable for decoding!" if decoding_palette && !decoding_palette.can_decode?
|
97
97
|
|
98
98
|
image = case interlace
|
99
|
-
when ChunkyPNG::INTERLACING_NONE
|
100
|
-
when ChunkyPNG::INTERLACING_ADAM7
|
99
|
+
when ChunkyPNG::INTERLACING_NONE then decode_png_without_interlacing(stream, width, height, color_mode, depth, decoding_palette)
|
100
|
+
when ChunkyPNG::INTERLACING_ADAM7 then decode_png_with_adam7_interlacing(stream, width, height, color_mode, depth, decoding_palette)
|
101
101
|
else raise ChunkyPNG::NotSupported, "Don't know how the handle interlacing method #{interlace}!"
|
102
102
|
end
|
103
103
|
|
104
104
|
image.pixels.map! { |c| c == transparent_color ? ChunkyPNG::Color::TRANSPARENT : c } if transparent_color
|
105
|
-
|
105
|
+
image
|
106
106
|
end
|
107
107
|
|
108
108
|
protected
|
@@ -147,7 +147,7 @@ module ChunkyPNG
|
|
147
147
|
# the value will be modded by 2 to enforce this.
|
148
148
|
# @return [Integer] The extracted 4bit value (0..15)
|
149
149
|
def decode_png_extract_4bit_value(byte, index)
|
150
|
-
|
150
|
+
index & 0x01 == 0 ? ((byte & 0xf0) >> 4) : (byte & 0x0f)
|
151
151
|
end
|
152
152
|
|
153
153
|
# Extract 2 consecutive bits from a byte.
|
@@ -205,7 +205,6 @@ module ChunkyPNG
|
|
205
205
|
value == 0x01 ? 0xff : 0x00
|
206
206
|
end
|
207
207
|
|
208
|
-
|
209
208
|
# Decodes a scanline of a 1-bit, indexed image into a row of pixels.
|
210
209
|
# @param [String] stream The stream to decode from.
|
211
210
|
# @param [Integer] pos The position in the stream on which the scanline starts (including the filter byte).
|
@@ -259,17 +258,21 @@ module ChunkyPNG
|
|
259
258
|
def decode_png_pixels_from_scanline_truecolor_alpha_16bit(stream, pos, width, _decoding_palette)
|
260
259
|
pixels = []
|
261
260
|
stream.unpack("@#{pos + 1}n#{width * 4}").each_slice(4) do |r, g, b, a|
|
262
|
-
pixels << ChunkyPNG::Color.rgba(
|
263
|
-
|
261
|
+
pixels << ChunkyPNG::Color.rgba(
|
262
|
+
decode_png_resample_16bit_value(r),
|
263
|
+
decode_png_resample_16bit_value(g),
|
264
|
+
decode_png_resample_16bit_value(b),
|
265
|
+
decode_png_resample_16bit_value(a),
|
266
|
+
)
|
264
267
|
end
|
265
|
-
|
268
|
+
pixels
|
266
269
|
end
|
267
270
|
|
268
271
|
# Decodes a scanline of an 8-bit, true color image into a row of pixels.
|
269
272
|
# @params (see #decode_png_pixels_from_scanline_indexed_1bit)
|
270
273
|
# @return (see #decode_png_pixels_from_scanline_indexed_1bit)
|
271
274
|
def decode_png_pixels_from_scanline_truecolor_8bit(stream, pos, width, _decoding_palette)
|
272
|
-
stream.unpack("@#{pos + 1}"
|
275
|
+
stream.unpack("@#{pos + 1}#{"NX" * width}").map { |c| c | 0x000000ff }
|
273
276
|
end
|
274
277
|
|
275
278
|
# Decodes a scanline of a 16-bit, true color image into a row of pixels.
|
@@ -280,7 +283,7 @@ module ChunkyPNG
|
|
280
283
|
stream.unpack("@#{pos + 1}n#{width * 3}").each_slice(3) do |r, g, b|
|
281
284
|
pixels << ChunkyPNG::Color.rgb(decode_png_resample_16bit_value(r), decode_png_resample_16bit_value(g), decode_png_resample_16bit_value(b))
|
282
285
|
end
|
283
|
-
|
286
|
+
pixels
|
284
287
|
end
|
285
288
|
|
286
289
|
# Decodes a scanline of an 8-bit, grayscale image with transparency into a row of pixels.
|
@@ -298,7 +301,7 @@ module ChunkyPNG
|
|
298
301
|
stream.unpack("@#{pos + 1}n#{width * 2}").each_slice(2) do |g, a|
|
299
302
|
pixels << ChunkyPNG::Color.grayscale_alpha(decode_png_resample_16bit_value(g), decode_png_resample_16bit_value(a))
|
300
303
|
end
|
301
|
-
|
304
|
+
pixels
|
302
305
|
end
|
303
306
|
|
304
307
|
# Decodes a scanline of a 1-bit, grayscale image into a row of pixels.
|
@@ -353,12 +356,11 @@ module ChunkyPNG
|
|
353
356
|
# @raise [ChunkyPNG::NotSupported] when the color_mode and/or bit depth is not supported.
|
354
357
|
def decode_png_pixels_from_scanline_method(color_mode, depth)
|
355
358
|
decoder_method = case color_mode
|
356
|
-
when ChunkyPNG::COLOR_TRUECOLOR
|
357
|
-
when ChunkyPNG::COLOR_TRUECOLOR_ALPHA
|
358
|
-
when ChunkyPNG::COLOR_INDEXED
|
359
|
-
when ChunkyPNG::COLOR_GRAYSCALE
|
360
|
-
when ChunkyPNG::COLOR_GRAYSCALE_ALPHA
|
361
|
-
else nil
|
359
|
+
when ChunkyPNG::COLOR_TRUECOLOR then :"decode_png_pixels_from_scanline_truecolor_#{depth}bit"
|
360
|
+
when ChunkyPNG::COLOR_TRUECOLOR_ALPHA then :"decode_png_pixels_from_scanline_truecolor_alpha_#{depth}bit"
|
361
|
+
when ChunkyPNG::COLOR_INDEXED then :"decode_png_pixels_from_scanline_indexed_#{depth}bit"
|
362
|
+
when ChunkyPNG::COLOR_GRAYSCALE then :"decode_png_pixels_from_scanline_grayscale_#{depth}bit"
|
363
|
+
when ChunkyPNG::COLOR_GRAYSCALE_ALPHA then :"decode_png_pixels_from_scanline_grayscale_alpha_#{depth}bit"
|
362
364
|
end
|
363
365
|
|
364
366
|
raise ChunkyPNG::NotSupported, "No decoder found for color mode #{color_mode} and #{depth}-bit depth!" unless respond_to?(decoder_method, true)
|
@@ -379,7 +381,6 @@ module ChunkyPNG
|
|
379
381
|
# @param [ChunkyPNG::Palette] decoding_palette The palette to use to decode colors.
|
380
382
|
# @return (see ChunkyPNG::Canvas::PNGDecoding#decode_png_pixelstream)
|
381
383
|
def decode_png_image_pass(stream, width, height, color_mode, depth, start_pos, decoding_palette)
|
382
|
-
|
383
384
|
pixels = []
|
384
385
|
if width > 0 && height > 0
|
385
386
|
|
@@ -419,11 +420,11 @@ module ChunkyPNG
|
|
419
420
|
# @return [void]
|
420
421
|
def decode_png_str_scanline(stream, pos, prev_pos, line_length, pixel_size)
|
421
422
|
case stream.getbyte(pos)
|
422
|
-
when ChunkyPNG::FILTER_NONE
|
423
|
-
when ChunkyPNG::FILTER_SUB
|
424
|
-
when ChunkyPNG::FILTER_UP
|
425
|
-
when ChunkyPNG::FILTER_AVERAGE
|
426
|
-
when ChunkyPNG::FILTER_PAETH
|
423
|
+
when ChunkyPNG::FILTER_NONE then # rubocop:disable Lint/EmptyWhen # no-op
|
424
|
+
when ChunkyPNG::FILTER_SUB then decode_png_str_scanline_sub(stream, pos, prev_pos, line_length, pixel_size)
|
425
|
+
when ChunkyPNG::FILTER_UP then decode_png_str_scanline_up(stream, pos, prev_pos, line_length, pixel_size)
|
426
|
+
when ChunkyPNG::FILTER_AVERAGE then decode_png_str_scanline_average(stream, pos, prev_pos, line_length, pixel_size)
|
427
|
+
when ChunkyPNG::FILTER_PAETH then decode_png_str_scanline_paeth(stream, pos, prev_pos, line_length, pixel_size)
|
427
428
|
else raise ChunkyPNG::NotSupported, "Unknown filter type: #{stream.getbyte(pos)}!"
|
428
429
|
end
|
429
430
|
end
|
@@ -462,7 +463,7 @@ module ChunkyPNG
|
|
462
463
|
# @return [void]
|
463
464
|
def decode_png_str_scanline_average(stream, pos, prev_pos, line_length, pixel_size)
|
464
465
|
for i in 1..line_length do
|
465
|
-
a =
|
466
|
+
a = i > pixel_size ? stream.getbyte(pos + i - pixel_size) : 0
|
466
467
|
b = prev_pos ? stream.getbyte(prev_pos + i) : 0
|
467
468
|
stream.setbyte(pos + i, (stream.getbyte(pos + i) + ((a + b) >> 1)) & 0xff)
|
468
469
|
end
|
@@ -475,14 +476,19 @@ module ChunkyPNG
|
|
475
476
|
def decode_png_str_scanline_paeth(stream, pos, prev_pos, line_length, pixel_size)
|
476
477
|
for i in 1..line_length do
|
477
478
|
cur_pos = pos + i
|
478
|
-
a =
|
479
|
+
a = i > pixel_size ? stream.getbyte(cur_pos - pixel_size) : 0
|
479
480
|
b = prev_pos ? stream.getbyte(prev_pos + i) : 0
|
480
|
-
c =
|
481
|
+
c = prev_pos && i > pixel_size ? stream.getbyte(prev_pos + i - pixel_size) : 0
|
481
482
|
p = a + b - c
|
482
483
|
pa = (p - a).abs
|
483
484
|
pb = (p - b).abs
|
484
485
|
pc = (p - c).abs
|
485
|
-
pr =
|
486
|
+
pr = if pa <= pb
|
487
|
+
pa <= pc ? a : c
|
488
|
+
else
|
489
|
+
pb <= pc ? b : c
|
490
|
+
end
|
491
|
+
|
486
492
|
stream.setbyte(cur_pos, (stream.getbyte(cur_pos) + pr) & 0xff)
|
487
493
|
end
|
488
494
|
end
|
@@ -1,17 +1,18 @@
|
|
1
|
+
# frozen-string-literal: true
|
2
|
+
|
1
3
|
module ChunkyPNG
|
2
4
|
class Canvas
|
3
|
-
|
4
5
|
# Methods for encoding a Canvas instance into a PNG datastream.
|
5
6
|
#
|
6
7
|
# Overview of the encoding process:
|
7
8
|
#
|
8
9
|
# * The image is split up in scanlines (i.e. rows of pixels);
|
9
10
|
# * 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
|
+
# * All the pixel bytes in the pixelstream are adjusted using a filtering
|
11
12
|
# method if one is specified.
|
12
13
|
# * Compress the resulting string using deflate compression.
|
13
14
|
# * Split compressed data over one or more PNG chunks.
|
14
|
-
# * These chunks should be embedded in a datastream with at least a IHDR and
|
15
|
+
# * These chunks should be embedded in a datastream with at least a IHDR and
|
15
16
|
# IEND chunk and possibly a PLTE chunk.
|
16
17
|
#
|
17
18
|
# For interlaced images, the initial image is first split into 7 subimages.
|
@@ -19,9 +20,8 @@ module ChunkyPNG
|
|
19
20
|
# before the compression step.
|
20
21
|
#
|
21
22
|
# @see ChunkyPNG::Canvas::PNGDecoding
|
22
|
-
# @see
|
23
|
+
# @see https://www.w3.org/TR/PNG/ The W3C PNG format specification
|
23
24
|
module PNGEncoding
|
24
|
-
|
25
25
|
# The palette used for encoding the image.This is only in used for images
|
26
26
|
# that get encoded using indexed colors.
|
27
27
|
# @return [ChunkyPNG::Palette]
|
@@ -40,28 +40,28 @@ module ChunkyPNG
|
|
40
40
|
# @param constraints (see ChunkyPNG::Canvas::PNGEncoding#to_datastream)
|
41
41
|
# @return [void]
|
42
42
|
def save(filename, constraints = {})
|
43
|
-
File.open(filename,
|
43
|
+
File.open(filename, "wb") { |io| write(io, constraints) }
|
44
44
|
end
|
45
|
-
|
45
|
+
|
46
46
|
# Encoded the canvas to a PNG formatted string.
|
47
47
|
# @param constraints (see ChunkyPNG::Canvas::PNGEncoding#to_datastream)
|
48
48
|
# @return [String] The PNG encoded canvas as string.
|
49
49
|
def to_blob(constraints = {})
|
50
50
|
to_datastream(constraints).to_blob
|
51
51
|
end
|
52
|
-
|
53
|
-
|
54
|
-
|
52
|
+
|
53
|
+
alias to_string to_blob
|
54
|
+
alias to_s to_blob
|
55
55
|
|
56
56
|
# Converts this Canvas to a datastream, so that it can be saved as a PNG image.
|
57
57
|
# @param [Hash, Symbol] constraints The constraints to use when encoding the canvas.
|
58
|
-
# This can either be a hash with different constraints, or a symbol which acts as a
|
59
|
-
# preset for some constraints. If no constraints are given, ChunkyPNG will decide
|
60
|
-
# for itself how to best create the PNG datastream.
|
58
|
+
# This can either be a hash with different constraints, or a symbol which acts as a
|
59
|
+
# preset for some constraints. If no constraints are given, ChunkyPNG will decide
|
60
|
+
# for itself how to best create the PNG datastream.
|
61
61
|
# Supported presets are <tt>:fast_rgba</tt> for quickly saving images with transparency,
|
62
62
|
# <tt>:fast_rgb</tt> for quickly saving opaque images, and <tt>:best_compression</tt> to
|
63
63
|
# obtain the smallest possible filesize.
|
64
|
-
# @option constraints [Fixnum] :color_mode The color mode to use. Use one of the
|
64
|
+
# @option constraints [Fixnum] :color_mode The color mode to use. Use one of the
|
65
65
|
# ChunkyPNG::COLOR_* constants.
|
66
66
|
# @option constraints [true, false] :interlace Whether to use interlacing.
|
67
67
|
# @option constraints [Fixnum] :compression The compression level for Zlib. This can be a
|
@@ -75,8 +75,13 @@ module ChunkyPNG
|
|
75
75
|
encoding = determine_png_encoding(constraints)
|
76
76
|
|
77
77
|
ds = Datastream.new
|
78
|
-
ds.header_chunk = Chunk::Header.new(
|
79
|
-
|
78
|
+
ds.header_chunk = Chunk::Header.new(
|
79
|
+
width: width,
|
80
|
+
height: height,
|
81
|
+
color: encoding[:color_mode],
|
82
|
+
depth: encoding[:bit_depth],
|
83
|
+
interlace: encoding[:interlace]
|
84
|
+
)
|
80
85
|
|
81
86
|
if encoding[:color_mode] == ChunkyPNG::COLOR_INDEXED
|
82
87
|
ds.palette_chunk = encoding_palette.to_plte_chunk
|
@@ -85,43 +90,42 @@ module ChunkyPNG
|
|
85
90
|
data = encode_png_pixelstream(encoding[:color_mode], encoding[:bit_depth], encoding[:interlace], encoding[:filtering])
|
86
91
|
ds.data_chunks = Chunk::ImageData.split_in_chunks(data, encoding[:compression])
|
87
92
|
ds.end_chunk = Chunk::End.new
|
88
|
-
|
93
|
+
ds
|
89
94
|
end
|
90
95
|
|
91
96
|
protected
|
92
97
|
|
93
|
-
# Determines the best possible PNG encoding variables for this image, by analyzing
|
98
|
+
# Determines the best possible PNG encoding variables for this image, by analyzing
|
94
99
|
# the colors used for the image.
|
95
100
|
#
|
96
|
-
# You can provide constraints for the encoding variables by passing a hash with
|
101
|
+
# You can provide constraints for the encoding variables by passing a hash with
|
97
102
|
# encoding variables to this method.
|
98
103
|
#
|
99
104
|
# @param [Hash, Symbol] constraints The constraints for the encoding. This can be a
|
100
105
|
# Hash or a preset symbol.
|
101
106
|
# @return [Hash] A hash with encoding options for {ChunkyPNG::Canvas::PNGEncoding#to_datastream}
|
102
107
|
def determine_png_encoding(constraints = {})
|
103
|
-
|
104
108
|
encoding = case constraints
|
105
|
-
when :fast_rgb
|
106
|
-
when :fast_rgba
|
107
|
-
when :best_compression
|
108
|
-
when :good_compression
|
109
|
-
when :no_compression
|
110
|
-
when :black_and_white
|
111
|
-
when Hash
|
109
|
+
when :fast_rgb then {color_mode: ChunkyPNG::COLOR_TRUECOLOR, compression: Zlib::BEST_SPEED}
|
110
|
+
when :fast_rgba then {color_mode: ChunkyPNG::COLOR_TRUECOLOR_ALPHA, compression: Zlib::BEST_SPEED}
|
111
|
+
when :best_compression then {compression: Zlib::BEST_COMPRESSION, filtering: ChunkyPNG::FILTER_PAETH}
|
112
|
+
when :good_compression then {compression: Zlib::BEST_COMPRESSION, filtering: ChunkyPNG::FILTER_NONE}
|
113
|
+
when :no_compression then {compression: Zlib::NO_COMPRESSION}
|
114
|
+
when :black_and_white then {color_mode: ChunkyPNG::COLOR_GRAYSCALE, bit_depth: 1}
|
115
|
+
when Hash then constraints
|
112
116
|
else raise ChunkyPNG::Exception, "Unknown encoding preset: #{constraints.inspect}"
|
113
117
|
end
|
114
118
|
|
115
119
|
# Do not create a palette when the encoding is given and does not require a palette.
|
116
120
|
if encoding[:color_mode]
|
117
121
|
if encoding[:color_mode] == ChunkyPNG::COLOR_INDEXED
|
118
|
-
self.encoding_palette =
|
119
|
-
encoding[:bit_depth] ||=
|
122
|
+
self.encoding_palette = palette
|
123
|
+
encoding[:bit_depth] ||= encoding_palette.determine_bit_depth
|
120
124
|
else
|
121
125
|
encoding[:bit_depth] ||= 8
|
122
126
|
end
|
123
127
|
else
|
124
|
-
self.encoding_palette =
|
128
|
+
self.encoding_palette = palette
|
125
129
|
suggested_color_mode, suggested_bit_depth = encoding_palette.best_color_settings
|
126
130
|
encoding[:color_mode] ||= suggested_color_mode
|
127
131
|
encoding[:bit_depth] ||= suggested_bit_depth
|
@@ -131,35 +135,34 @@ module ChunkyPNG
|
|
131
135
|
encoding[:compression] ||= Zlib::DEFAULT_COMPRESSION
|
132
136
|
|
133
137
|
encoding[:interlace] = case encoding[:interlace]
|
134
|
-
when nil, false
|
135
|
-
when true
|
138
|
+
when nil, false then ChunkyPNG::INTERLACING_NONE
|
139
|
+
when true then ChunkyPNG::INTERLACING_ADAM7
|
136
140
|
else encoding[:interlace]
|
137
141
|
end
|
138
142
|
|
139
143
|
encoding[:filtering] ||= case encoding[:compression]
|
140
|
-
when Zlib::BEST_COMPRESSION
|
141
|
-
when Zlib::NO_COMPRESSION..Zlib::BEST_SPEED
|
144
|
+
when Zlib::BEST_COMPRESSION then ChunkyPNG::FILTER_PAETH
|
145
|
+
when Zlib::NO_COMPRESSION..Zlib::BEST_SPEED then ChunkyPNG::FILTER_NONE
|
142
146
|
else ChunkyPNG::FILTER_UP
|
143
147
|
end
|
144
|
-
|
148
|
+
encoding
|
145
149
|
end
|
146
|
-
|
147
|
-
# Encodes the canvas according to the PNG format specification with a given color
|
150
|
+
|
151
|
+
# Encodes the canvas according to the PNG format specification with a given color
|
148
152
|
# mode, possibly with interlacing.
|
149
153
|
# @param [Integer] color_mode The color mode to use for encoding.
|
150
154
|
# @param [Integer] bit_depth The bit depth of the image.
|
151
155
|
# @param [Integer] interlace The interlacing method to use.
|
152
156
|
# @return [String] The PNG encoded canvas as string.
|
153
157
|
def encode_png_pixelstream(color_mode = ChunkyPNG::COLOR_TRUECOLOR, bit_depth = 8, interlace = ChunkyPNG::INTERLACING_NONE, filtering = ChunkyPNG::FILTER_NONE)
|
154
|
-
|
155
|
-
if color_mode == ChunkyPNG::COLOR_INDEXED
|
158
|
+
if color_mode == ChunkyPNG::COLOR_INDEXED
|
156
159
|
raise ChunkyPNG::ExpectationFailed, "This palette is not suitable for encoding!" if encoding_palette.nil? || !encoding_palette.can_encode?
|
157
160
|
raise ChunkyPNG::ExpectationFailed, "This palette has too many colors!" if encoding_palette.size > (1 << bit_depth)
|
158
161
|
end
|
159
162
|
|
160
163
|
case interlace
|
161
|
-
when ChunkyPNG::INTERLACING_NONE
|
162
|
-
when ChunkyPNG::INTERLACING_ADAM7
|
164
|
+
when ChunkyPNG::INTERLACING_NONE then encode_png_image_without_interlacing(color_mode, bit_depth, filtering)
|
165
|
+
when ChunkyPNG::INTERLACING_ADAM7 then encode_png_image_with_interlacing(color_mode, bit_depth, filtering)
|
163
166
|
else raise ChunkyPNG::NotSupported, "Unknown interlacing method: #{interlace}!"
|
164
167
|
end
|
165
168
|
end
|
@@ -170,12 +173,12 @@ module ChunkyPNG
|
|
170
173
|
# @param [Integer] filtering The filtering method to use.
|
171
174
|
# @return [String] The PNG encoded canvas as string.
|
172
175
|
def encode_png_image_without_interlacing(color_mode, bit_depth = 8, filtering = ChunkyPNG::FILTER_NONE)
|
173
|
-
stream =
|
176
|
+
stream = "".b
|
174
177
|
encode_png_image_pass_to_stream(stream, color_mode, bit_depth, filtering)
|
175
178
|
stream
|
176
179
|
end
|
177
180
|
|
178
|
-
# Encodes the canvas according to the PNG format specification with a given color
|
181
|
+
# Encodes the canvas according to the PNG format specification with a given color
|
179
182
|
# mode and Adam7 interlacing.
|
180
183
|
#
|
181
184
|
# This method will split the original canvas in 7 smaller canvases and encode them
|
@@ -186,7 +189,7 @@ module ChunkyPNG
|
|
186
189
|
# @param [Integer] filtering The filtering method to use.
|
187
190
|
# @return [String] The PNG encoded canvas as string.
|
188
191
|
def encode_png_image_with_interlacing(color_mode, bit_depth = 8, filtering = ChunkyPNG::FILTER_NONE)
|
189
|
-
stream =
|
192
|
+
stream = "".b
|
190
193
|
0.upto(6) do |pass|
|
191
194
|
subcanvas = self.class.adam7_extract_pass(pass, self)
|
192
195
|
subcanvas.encoding_palette = encoding_palette
|
@@ -201,42 +204,42 @@ module ChunkyPNG
|
|
201
204
|
# @param [Integer] bit_depth The bit depth of the image.
|
202
205
|
# @param [Integer] filtering The filtering method to use.
|
203
206
|
def encode_png_image_pass_to_stream(stream, color_mode, bit_depth, filtering)
|
204
|
-
|
205
207
|
start_pos = stream.bytesize
|
206
208
|
pixel_size = Color.pixel_bytesize(color_mode)
|
207
209
|
line_width = Color.scanline_bytesize(color_mode, bit_depth, width)
|
208
|
-
|
210
|
+
|
209
211
|
# Determine the filter method
|
210
212
|
encode_method = encode_png_pixels_to_scanline_method(color_mode, bit_depth)
|
211
213
|
filter_method = case filtering
|
212
|
-
when ChunkyPNG::
|
213
|
-
when ChunkyPNG::
|
214
|
-
when ChunkyPNG::
|
215
|
-
when ChunkyPNG::
|
216
|
-
|
214
|
+
when ChunkyPNG::FILTER_NONE then nil
|
215
|
+
when ChunkyPNG::FILTER_SUB then :encode_png_str_scanline_sub
|
216
|
+
when ChunkyPNG::FILTER_UP then :encode_png_str_scanline_up
|
217
|
+
when ChunkyPNG::FILTER_AVERAGE then :encode_png_str_scanline_average
|
218
|
+
when ChunkyPNG::FILTER_PAETH then :encode_png_str_scanline_paeth
|
219
|
+
else raise ArgumentError, "Filtering method #{filtering} is not supported"
|
217
220
|
end
|
218
|
-
|
221
|
+
|
219
222
|
0.upto(height - 1) do |y|
|
220
223
|
stream << send(encode_method, row(y))
|
221
224
|
end
|
222
|
-
|
225
|
+
|
223
226
|
# Now, apply filtering if any
|
224
227
|
if filter_method
|
225
228
|
(height - 1).downto(0) do |y|
|
226
229
|
pos = start_pos + y * (line_width + 1)
|
227
|
-
prev_pos =
|
230
|
+
prev_pos = y == 0 ? nil : pos - (line_width + 1)
|
228
231
|
send(filter_method, stream, pos, prev_pos, line_width, pixel_size)
|
229
232
|
end
|
230
233
|
end
|
231
234
|
end
|
232
|
-
|
235
|
+
|
233
236
|
# Encodes a line of pixels using 8-bit truecolor mode.
|
234
237
|
# @param [Array<Integer>] pixels A row of pixels of the original image.
|
235
238
|
# @return [String] The encoded scanline as binary string
|
236
239
|
def encode_png_pixels_to_scanline_truecolor_8bit(pixels)
|
237
|
-
pixels.pack(
|
240
|
+
pixels.pack("x" + ("NX" * width))
|
238
241
|
end
|
239
|
-
|
242
|
+
|
240
243
|
# Encodes a line of pixels using 8-bit truecolor alpha mode.
|
241
244
|
# @param [Array<Integer>] pixels A row of pixels of the original image.
|
242
245
|
# @return [String] The encoded scanline as binary string
|
@@ -250,50 +253,54 @@ module ChunkyPNG
|
|
250
253
|
def encode_png_pixels_to_scanline_indexed_1bit(pixels)
|
251
254
|
chars = []
|
252
255
|
pixels.each_slice(8) do |p1, p2, p3, p4, p5, p6, p7, p8|
|
253
|
-
chars << (
|
254
|
-
|
255
|
-
|
256
|
-
|
257
|
-
|
258
|
-
|
259
|
-
|
260
|
-
|
256
|
+
chars << (
|
257
|
+
(encoding_palette.index(p1) << 7) |
|
258
|
+
(encoding_palette.index(p2) << 6) |
|
259
|
+
(encoding_palette.index(p3) << 5) |
|
260
|
+
(encoding_palette.index(p4) << 4) |
|
261
|
+
(encoding_palette.index(p5) << 3) |
|
262
|
+
(encoding_palette.index(p6) << 2) |
|
263
|
+
(encoding_palette.index(p7) << 1) |
|
264
|
+
encoding_palette.index(p8)
|
265
|
+
)
|
261
266
|
end
|
262
|
-
chars.pack(
|
267
|
+
chars.pack("xC*")
|
263
268
|
end
|
264
|
-
|
269
|
+
|
265
270
|
# Encodes a line of pixels using 2-bit indexed mode.
|
266
271
|
# @param [Array<Integer>] pixels A row of pixels of the original image.
|
267
272
|
# @return [String] The encoded scanline as binary string
|
268
273
|
def encode_png_pixels_to_scanline_indexed_2bit(pixels)
|
269
274
|
chars = []
|
270
275
|
pixels.each_slice(4) do |p1, p2, p3, p4|
|
271
|
-
chars << (
|
272
|
-
|
273
|
-
|
274
|
-
|
276
|
+
chars << (
|
277
|
+
(encoding_palette.index(p1) << 6) |
|
278
|
+
(encoding_palette.index(p2) << 4) |
|
279
|
+
(encoding_palette.index(p3) << 2) |
|
280
|
+
encoding_palette.index(p4)
|
281
|
+
)
|
275
282
|
end
|
276
|
-
chars.pack(
|
283
|
+
chars.pack("xC*")
|
277
284
|
end
|
278
|
-
|
285
|
+
|
279
286
|
# Encodes a line of pixels using 4-bit indexed mode.
|
280
287
|
# @param [Array<Integer>] pixels A row of pixels of the original image.
|
281
288
|
# @return [String] The encoded scanline as binary string
|
282
289
|
def encode_png_pixels_to_scanline_indexed_4bit(pixels)
|
283
290
|
chars = []
|
284
291
|
pixels.each_slice(2) do |p1, p2|
|
285
|
-
chars << ((encoding_palette.index(p1) << 4) |
|
292
|
+
chars << ((encoding_palette.index(p1) << 4) | encoding_palette.index(p2))
|
286
293
|
end
|
287
|
-
chars.pack(
|
294
|
+
chars.pack("xC*")
|
288
295
|
end
|
289
|
-
|
296
|
+
|
290
297
|
# Encodes a line of pixels using 8-bit indexed mode.
|
291
298
|
# @param [Array<Integer>] pixels A row of pixels of the original image.
|
292
299
|
# @return [String] The encoded scanline as binary string
|
293
300
|
def encode_png_pixels_to_scanline_indexed_8bit(pixels)
|
294
301
|
pixels.map { |p| encoding_palette.index(p) }.pack("xC#{width}")
|
295
302
|
end
|
296
|
-
|
303
|
+
|
297
304
|
# Encodes a line of pixels using 1-bit grayscale mode.
|
298
305
|
# @param [Array<Integer>] pixels A row of pixels of the original image.
|
299
306
|
# @return [String] The encoded scanline as binary string
|
@@ -309,9 +316,9 @@ module ChunkyPNG
|
|
309
316
|
(p7.nil? ? 0 : (p7 & 0x0000ffff) >> 15 << 1) |
|
310
317
|
(p8.nil? ? 0 : (p8 & 0x0000ffff) >> 15))
|
311
318
|
end
|
312
|
-
chars.pack(
|
319
|
+
chars.pack("xC*")
|
313
320
|
end
|
314
|
-
|
321
|
+
|
315
322
|
# Encodes a line of pixels using 2-bit grayscale mode.
|
316
323
|
# @param [Array<Integer>] pixels A row of pixels of the original image.
|
317
324
|
# @return [String] The encoded scanline as binary string
|
@@ -323,9 +330,9 @@ module ChunkyPNG
|
|
323
330
|
(p3.nil? ? 0 : (p3 & 0x0000ffff) >> 14 << 2) |
|
324
331
|
(p4.nil? ? 0 : (p4 & 0x0000ffff) >> 14))
|
325
332
|
end
|
326
|
-
chars.pack(
|
333
|
+
chars.pack("xC*")
|
327
334
|
end
|
328
|
-
|
335
|
+
|
329
336
|
# Encodes a line of pixels using 2-bit grayscale mode.
|
330
337
|
# @param [Array<Integer>] pixels A row of pixels of the original image.
|
331
338
|
# @return [String] The encoded scanline as binary string
|
@@ -334,9 +341,9 @@ module ChunkyPNG
|
|
334
341
|
pixels.each_slice(2) do |p1, p2|
|
335
342
|
chars << ((p1.nil? ? 0 : ((p1 & 0x0000ffff) >> 12) << 4) | (p2.nil? ? 0 : ((p2 & 0x0000ffff) >> 12)))
|
336
343
|
end
|
337
|
-
chars.pack(
|
344
|
+
chars.pack("xC*")
|
338
345
|
end
|
339
|
-
|
346
|
+
|
340
347
|
# Encodes a line of pixels using 8-bit grayscale mode.
|
341
348
|
# @param [Array<Integer>] pixels A row of pixels of the original image.
|
342
349
|
# @return [String] The encoded scanline as binary string
|
@@ -350,8 +357,7 @@ module ChunkyPNG
|
|
350
357
|
def encode_png_pixels_to_scanline_grayscale_alpha_8bit(pixels)
|
351
358
|
pixels.pack("xn#{width}")
|
352
359
|
end
|
353
|
-
|
354
|
-
|
360
|
+
|
355
361
|
# Returns the method name to use to decode scanlines into pixels.
|
356
362
|
# @param [Integer] color_mode The color mode of the image.
|
357
363
|
# @param [Integer] depth The bit depth of the image.
|
@@ -359,19 +365,16 @@ module ChunkyPNG
|
|
359
365
|
# @raise [ChunkyPNG::NotSupported] when the color_mode and/or bit depth is not supported.
|
360
366
|
def encode_png_pixels_to_scanline_method(color_mode, depth)
|
361
367
|
encoder_method = case color_mode
|
362
|
-
when ChunkyPNG::COLOR_TRUECOLOR
|
363
|
-
when ChunkyPNG::COLOR_TRUECOLOR_ALPHA
|
364
|
-
when ChunkyPNG::COLOR_INDEXED
|
365
|
-
when ChunkyPNG::COLOR_GRAYSCALE
|
366
|
-
when ChunkyPNG::COLOR_GRAYSCALE_ALPHA
|
367
|
-
else nil
|
368
|
+
when ChunkyPNG::COLOR_TRUECOLOR then :"encode_png_pixels_to_scanline_truecolor_#{depth}bit"
|
369
|
+
when ChunkyPNG::COLOR_TRUECOLOR_ALPHA then :"encode_png_pixels_to_scanline_truecolor_alpha_#{depth}bit"
|
370
|
+
when ChunkyPNG::COLOR_INDEXED then :"encode_png_pixels_to_scanline_indexed_#{depth}bit"
|
371
|
+
when ChunkyPNG::COLOR_GRAYSCALE then :"encode_png_pixels_to_scanline_grayscale_#{depth}bit"
|
372
|
+
when ChunkyPNG::COLOR_GRAYSCALE_ALPHA then :"encode_png_pixels_to_scanline_grayscale_alpha_#{depth}bit"
|
368
373
|
end
|
369
|
-
|
374
|
+
|
370
375
|
raise ChunkyPNG::NotSupported, "No encoder found for color mode #{color_mode} and #{depth}-bit depth!" unless respond_to?(encoder_method, true)
|
371
376
|
encoder_method
|
372
377
|
end
|
373
|
-
|
374
|
-
|
375
378
|
|
376
379
|
# Encodes a scanline of a pixelstream without filtering. This is a no-op.
|
377
380
|
# @param [String] stream The pixelstream to work on. This string will be modified.
|
@@ -391,7 +394,7 @@ module ChunkyPNG
|
|
391
394
|
# @return [void]
|
392
395
|
def encode_png_str_scanline_sub(stream, pos, prev_pos, line_width, pixel_size)
|
393
396
|
line_width.downto(1) do |i|
|
394
|
-
a =
|
397
|
+
a = i > pixel_size ? stream.getbyte(pos + i - pixel_size) : 0
|
395
398
|
stream.setbyte(pos + i, (stream.getbyte(pos + i) - a) & 0xff)
|
396
399
|
end
|
397
400
|
stream.setbyte(pos, ChunkyPNG::FILTER_SUB)
|
@@ -407,36 +410,41 @@ module ChunkyPNG
|
|
407
410
|
end
|
408
411
|
stream.setbyte(pos, ChunkyPNG::FILTER_UP)
|
409
412
|
end
|
410
|
-
|
413
|
+
|
411
414
|
# Encodes a scanline of a pixelstream using AVERAGE filtering. This will modify the stream.
|
412
415
|
# @param (see #encode_png_str_scanline_none)
|
413
416
|
# @return [void]
|
414
417
|
def encode_png_str_scanline_average(stream, pos, prev_pos, line_width, pixel_size)
|
415
418
|
line_width.downto(1) do |i|
|
416
|
-
a =
|
419
|
+
a = i > pixel_size ? stream.getbyte(pos + i - pixel_size) : 0
|
417
420
|
b = prev_pos ? stream.getbyte(prev_pos + i) : 0
|
418
421
|
stream.setbyte(pos + i, (stream.getbyte(pos + i) - ((a + b) >> 1)) & 0xff)
|
419
422
|
end
|
420
423
|
stream.setbyte(pos, ChunkyPNG::FILTER_AVERAGE)
|
421
424
|
end
|
422
|
-
|
425
|
+
|
423
426
|
# Encodes a scanline of a pixelstream using PAETH filtering. This will modify the stream.
|
424
427
|
# @param (see #encode_png_str_scanline_none)
|
425
428
|
# @return [void]
|
426
429
|
def encode_png_str_scanline_paeth(stream, pos, prev_pos, line_width, pixel_size)
|
427
430
|
line_width.downto(1) do |i|
|
428
|
-
a =
|
429
|
-
b =
|
430
|
-
c =
|
431
|
+
a = i > pixel_size ? stream.getbyte(pos + i - pixel_size) : 0
|
432
|
+
b = prev_pos ? stream.getbyte(prev_pos + i) : 0
|
433
|
+
c = prev_pos && i > pixel_size ? stream.getbyte(prev_pos + i - pixel_size) : 0
|
431
434
|
p = a + b - c
|
432
435
|
pa = (p - a).abs
|
433
436
|
pb = (p - b).abs
|
434
437
|
pc = (p - c).abs
|
435
|
-
pr =
|
438
|
+
pr = if pa <= pb && pa <= pc
|
439
|
+
a
|
440
|
+
else
|
441
|
+
pb <= pc ? b : c
|
442
|
+
end
|
443
|
+
|
436
444
|
stream.setbyte(pos + i, (stream.getbyte(pos + i) - pr) & 0xff)
|
437
445
|
end
|
438
446
|
stream.setbyte(pos, ChunkyPNG::FILTER_PAETH)
|
439
|
-
end
|
447
|
+
end
|
440
448
|
end
|
441
449
|
end
|
442
450
|
end
|