chunky_png 0.0.5 → 0.5.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/README.rdoc +20 -10
- data/chunky_png.gemspec +18 -6
- data/lib/chunky_png.rb +11 -28
- data/lib/chunky_png/canvas.rb +186 -0
- data/lib/chunky_png/canvas/adam7_interlacing.rb +53 -0
- data/lib/chunky_png/canvas/drawing.rb +8 -0
- data/lib/chunky_png/{pixel_matrix → canvas}/operations.rb +12 -12
- data/lib/chunky_png/canvas/png_decoding.rb +145 -0
- data/lib/chunky_png/canvas/png_encoding.rb +182 -0
- data/lib/chunky_png/chunk.rb +101 -23
- data/lib/chunky_png/color.rb +307 -0
- data/lib/chunky_png/datastream.rb +143 -45
- data/lib/chunky_png/image.rb +31 -30
- data/lib/chunky_png/palette.rb +49 -47
- data/lib/chunky_png/rmagick.rb +43 -0
- data/spec/chunky_png/canvas/adam7_interlacing_spec.rb +108 -0
- data/spec/chunky_png/canvas/png_decoding_spec.rb +81 -0
- data/spec/chunky_png/canvas/png_encoding_spec.rb +70 -0
- data/spec/chunky_png/canvas_spec.rb +71 -0
- data/spec/chunky_png/color_spec.rb +104 -0
- data/spec/chunky_png/datastream_spec.rb +32 -0
- data/spec/chunky_png/image_spec.rb +25 -0
- data/spec/chunky_png/rmagick_spec.rb +21 -0
- data/spec/{integration/image_spec.rb → chunky_png_spec.rb} +14 -8
- data/spec/resources/composited.png +0 -0
- data/spec/resources/cropped.png +0 -0
- data/spec/resources/damaged_chunk.png +0 -0
- data/spec/resources/damaged_signature.png +13 -0
- data/spec/resources/pixelstream.rgba +67 -0
- data/spec/resources/replaced.png +0 -0
- data/spec/resources/text_chunk.png +0 -0
- data/spec/resources/ztxt_chunk.png +0 -0
- data/spec/spec_helper.rb +8 -5
- data/tasks/github-gem.rake +1 -1
- metadata +37 -18
- data/lib/chunky_png/pixel.rb +0 -272
- data/lib/chunky_png/pixel_matrix.rb +0 -136
- data/lib/chunky_png/pixel_matrix/decoding.rb +0 -159
- data/lib/chunky_png/pixel_matrix/encoding.rb +0 -89
- data/spec/unit/decoding_spec.rb +0 -83
- data/spec/unit/encoding_spec.rb +0 -27
- data/spec/unit/pixel_matrix_spec.rb +0 -93
- data/spec/unit/pixel_spec.rb +0 -47
data/lib/chunky_png/image.rb
CHANGED
@@ -1,43 +1,44 @@
|
|
1
1
|
module ChunkyPNG
|
2
|
-
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
end
|
9
|
-
|
10
|
-
def self.from_pixel_matrix(matrix)
|
11
|
-
self.new(matrix.width, matrix.height, matrix.pixels)
|
12
|
-
end
|
2
|
+
|
3
|
+
# Image class
|
4
|
+
#
|
5
|
+
class Image < Canvas
|
6
|
+
|
7
|
+
METADATA_COMPRESSION_TRESHOLD = 300
|
13
8
|
|
14
|
-
|
15
|
-
pixel_matrix.width
|
16
|
-
end
|
9
|
+
attr_accessor :metadata
|
17
10
|
|
18
|
-
def height
|
19
|
-
|
11
|
+
def initialize(width, height, initial = ChunkyPNG::Color::TRANSPARENT, metadata = {})
|
12
|
+
super(width, height, initial)
|
13
|
+
@metadata = metadata
|
14
|
+
@metadata_compression_treshhold = 300
|
20
15
|
end
|
21
16
|
|
22
|
-
def
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
def []=(x, y, pixel)
|
27
|
-
pixel_matrix[x,y] = pixel
|
17
|
+
def initialize_copy(other)
|
18
|
+
super(other)
|
19
|
+
@metdata = other.metadata
|
28
20
|
end
|
29
21
|
|
30
|
-
def
|
31
|
-
|
22
|
+
def metadata_chunks
|
23
|
+
metadata.map do |key, value|
|
24
|
+
if value.length >= METADATA_COMPRESSION_TRESHOLD
|
25
|
+
ChunkyPNG::Chunk::CompressedText.new(key, value)
|
26
|
+
else
|
27
|
+
ChunkyPNG::Chunk::Text.new(key, value)
|
28
|
+
end
|
29
|
+
end
|
32
30
|
end
|
33
31
|
|
34
|
-
def
|
35
|
-
|
36
|
-
|
32
|
+
def to_datastream(constraints = {})
|
33
|
+
ds = super(constraints)
|
34
|
+
ds.other_chunks += metadata_chunks
|
35
|
+
return ds
|
37
36
|
end
|
38
37
|
|
39
|
-
def
|
40
|
-
|
38
|
+
def self.from_datastream(ds)
|
39
|
+
image = super(ds)
|
40
|
+
image.metadata = ds.metadata
|
41
|
+
return image
|
41
42
|
end
|
42
43
|
end
|
43
|
-
end
|
44
|
+
end
|
data/lib/chunky_png/palette.rb
CHANGED
@@ -2,90 +2,92 @@ module ChunkyPNG
|
|
2
2
|
|
3
3
|
# A palette describes the set of colors that is being used for an image.
|
4
4
|
#
|
5
|
-
# A PNG image can contain an explicit palette which defines the colors of
|
6
|
-
# but can also use an implicit palette, e.g. all truecolor
|
5
|
+
# A PNG image can contain an explicit palette which defines the colors of
|
6
|
+
# that image, but can also use an implicit palette, e.g. all truecolor
|
7
|
+
# colors or all grayscale colors.
|
7
8
|
#
|
8
|
-
# This palette supports decoding colors from a palette if an explicit
|
9
|
-
# in a PNG datastream, and it supports encoding colors
|
9
|
+
# This palette supports decoding colors from a palette if an explicit
|
10
|
+
# palette is provided in a PNG datastream, and it supports encoding colors
|
11
|
+
# to an explicit palette (stores as PLTE & tRNS chunks in a PNG file).
|
10
12
|
#
|
11
|
-
# @see ChunkyPNG::
|
13
|
+
# @see ChunkyPNG::Color
|
12
14
|
class Palette < SortedSet
|
13
|
-
|
15
|
+
|
14
16
|
# Builds a new palette given a set (Enumerable instance) of colors.
|
15
17
|
#
|
16
|
-
# @param [Enumerbale<
|
18
|
+
# @param [Enumerbale<CFixnum>] enum The set of colors to include in this palette.
|
17
19
|
# This Enumerbale can contains duplicates.
|
18
|
-
# @param [Array] decoding_map An array of colors in the exact order at which
|
20
|
+
# @param [Array] decoding_map An array of colors in the exact order at which
|
19
21
|
# they appeared in the palette chunk, so that this array can be used for decoding.
|
20
22
|
def initialize(enum, decoding_map = nil)
|
21
23
|
super(enum)
|
22
24
|
@decoding_map = decoding_map if decoding_map
|
23
25
|
end
|
24
|
-
|
25
|
-
# Builds a palette instance from a PLTE chunk and optionally a tRNS chunk
|
26
|
+
|
27
|
+
# Builds a palette instance from a PLTE chunk and optionally a tRNS chunk
|
26
28
|
# from a PNG datastream.
|
27
29
|
#
|
28
30
|
# This method will cerate a palette that is suitable for decoding an image.
|
29
31
|
#
|
30
32
|
# @param [ChunkyPNG::Chunk::Palette] The palette chunk to load from
|
31
33
|
# @param [ChunkyPNG::Chunk::Transparency, nil] The optional transparency chunk.
|
32
|
-
# @return [ChunkyPNG::Palette] The loaded palette instance.
|
34
|
+
# @return [ChunkyPNG::Palette] The loaded palette instance.
|
33
35
|
# @see ChunkyPNG::Palette#can_decode?
|
34
36
|
def self.from_chunks(palette_chunk, transparency_chunk = nil)
|
35
37
|
return nil if palette_chunk.nil?
|
36
|
-
|
38
|
+
|
37
39
|
decoding_map = []
|
38
40
|
index = 0
|
39
|
-
|
41
|
+
|
40
42
|
palatte_bytes = palette_chunk.content.unpack('C*')
|
41
43
|
if transparency_chunk
|
42
44
|
alpha_channel = transparency_chunk.content.unpack('C*')
|
43
45
|
else
|
44
|
-
alpha_channel = Array.new(palatte_bytes.size / 3,
|
46
|
+
alpha_channel = Array.new(palatte_bytes.size / 3, ChunkyPNG::Color::MAX)
|
45
47
|
end
|
46
|
-
|
48
|
+
|
47
49
|
index = 0
|
48
50
|
palatte_bytes.each_slice(3) do |bytes|
|
49
51
|
bytes << alpha_channel[index]
|
50
|
-
decoding_map << ChunkyPNG::
|
52
|
+
decoding_map << ChunkyPNG::Color.rgba(*bytes)
|
51
53
|
index += 1
|
52
54
|
end
|
53
|
-
|
55
|
+
|
54
56
|
self.new(decoding_map, decoding_map)
|
55
57
|
end
|
56
|
-
|
57
|
-
# Builds a palette instance from a given
|
58
|
-
# @param [ChunkyPNG::
|
58
|
+
|
59
|
+
# Builds a palette instance from a given canvas.
|
60
|
+
# @param [ChunkyPNG::Canvas] canvas The canvas to create a palette for.
|
59
61
|
# @return [ChunkyPNG::Palette] The palette instance.
|
60
|
-
def self.
|
61
|
-
self.new(
|
62
|
+
def self.from_canvas(canvas)
|
63
|
+
self.new(canvas.pixels)
|
62
64
|
end
|
63
|
-
|
65
|
+
|
64
66
|
# Builds a palette instance from a given set of pixels.
|
65
|
-
# @param [Enumerable<
|
67
|
+
# @param [Enumerable<Fixnum>] pixels An enumeration of pixels to create a palette for
|
66
68
|
# @return [ChunkyPNG::Palette] The palette instance.
|
67
69
|
def self.from_pixels(pixels)
|
68
70
|
self.new(pixels)
|
69
71
|
end
|
70
|
-
|
72
|
+
|
71
73
|
# Checks whether the size of this palette is suitable for indexed storage.
|
72
74
|
# @return [true, false] True if the number of colors in this palette is less than 256.
|
73
75
|
def indexable?
|
74
76
|
size < 256
|
75
77
|
end
|
76
|
-
|
78
|
+
|
77
79
|
# Check whether this pelette only contains opaque colors.
|
78
80
|
# @return [true, false] True if all colors in this palette are opaque.
|
79
|
-
# @see ChunkyPNG::
|
81
|
+
# @see ChunkyPNG::Color#opaque?
|
80
82
|
def opaque?
|
81
|
-
all? { |
|
83
|
+
all? { |color| Color.opaque?(color) }
|
82
84
|
end
|
83
|
-
|
85
|
+
|
84
86
|
# Check whether this pelette only contains grayscale colors.
|
85
87
|
# @return [true, false] True if all colors in this palette are grayscale teints.
|
86
|
-
# @see ChunkyPNG::
|
88
|
+
# @see ChunkyPNG::Color#grayscale??
|
87
89
|
def grayscale?
|
88
|
-
all? { |
|
90
|
+
all? { |color| Color.grayscale?(color) }
|
89
91
|
end
|
90
92
|
|
91
93
|
# Checks whether this palette is suitable for decoding an image from a datastream.
|
@@ -97,7 +99,7 @@ module ChunkyPNG
|
|
97
99
|
def can_decode?
|
98
100
|
!@decoding_map.nil?
|
99
101
|
end
|
100
|
-
|
102
|
+
|
101
103
|
# Checks whether this palette is suitable for encoding an image from to datastream.
|
102
104
|
#
|
103
105
|
# This requires that the position of the color in the future palette chunk is known,
|
@@ -107,38 +109,38 @@ module ChunkyPNG
|
|
107
109
|
def can_encode?
|
108
110
|
!@encoding_map.nil?
|
109
111
|
end
|
110
|
-
|
112
|
+
|
111
113
|
# Returns a color, given the position in the original palette chunk.
|
112
114
|
# @param [Fixnum] index The 0-based position of the color in the palette.
|
113
|
-
# @return [ChunkyPNG::
|
115
|
+
# @return [ChunkyPNG::Color] The color that is stored in the palette under the given index
|
114
116
|
# @see ChunkyPNG::Palette#can_decode?
|
115
117
|
def [](index)
|
116
118
|
@decoding_map[index]
|
117
119
|
end
|
118
|
-
|
120
|
+
|
119
121
|
# Returns the position of a color in the palette
|
120
|
-
# @param [ChunkyPNG::
|
122
|
+
# @param [ChunkyPNG::Color] color The color for which to look up the index.
|
121
123
|
# @return [Fixnum] The 0-based position of the color in the palette.
|
122
124
|
# @see ChunkyPNG::Palette#can_encode?
|
123
125
|
def index(color)
|
124
|
-
@encoding_map[
|
126
|
+
@encoding_map[color]
|
125
127
|
end
|
126
|
-
|
128
|
+
|
127
129
|
# Creates a tRNS chunk that corresponds with this palette to store the
|
128
130
|
# alpha channel of all colors.
|
129
131
|
#
|
130
|
-
# Note that this chunk can be left out of every color in the palette is
|
132
|
+
# Note that this chunk can be left out of every color in the palette is
|
131
133
|
# opaque, and the image is encoded using indexed colors.
|
132
134
|
#
|
133
135
|
# @return [ChunkyPNG::Chunk::Transparency] The tRNS chunk.
|
134
136
|
def to_trns_chunk
|
135
|
-
ChunkyPNG::Chunk::Transparency.new('tRNS', map(
|
137
|
+
ChunkyPNG::Chunk::Transparency.new('tRNS', map { |c| ChunkyPNG::Color.a(c) }.pack('C*'))
|
136
138
|
end
|
137
|
-
|
139
|
+
|
138
140
|
# Creates a PLTE chunk that corresponds with this palette to store the
|
139
141
|
# r, g and b channels of all colors.
|
140
142
|
#
|
141
|
-
# Note that a PLTE chunk should only be included if the image is
|
143
|
+
# Note that a PLTE chunk should only be included if the image is
|
142
144
|
# encoded using index colors. After this chunk has been built, the
|
143
145
|
# palette becomes suitable for encoding an image.
|
144
146
|
#
|
@@ -146,16 +148,16 @@ module ChunkyPNG
|
|
146
148
|
# @see ChunkyPNG::Palette#can_encode?
|
147
149
|
def to_plte_chunk
|
148
150
|
@encoding_map = {}
|
149
|
-
colors
|
150
|
-
|
151
|
+
colors = []
|
152
|
+
|
151
153
|
each_with_index do |color, index|
|
152
154
|
@encoding_map[color] = index
|
153
|
-
colors +=
|
155
|
+
colors += ChunkyPNG::Color.to_truecolor_bytes(color)
|
154
156
|
end
|
155
|
-
|
157
|
+
|
156
158
|
ChunkyPNG::Chunk::Palette.new('PLTE', colors.pack('C*'))
|
157
159
|
end
|
158
|
-
|
160
|
+
|
159
161
|
# Determines the most suitable colormode for this palette.
|
160
162
|
# @return [Fixnum] The colormode which would create the smalles possible
|
161
163
|
# file for images that use this exact palette.
|
@@ -0,0 +1,43 @@
|
|
1
|
+
require 'rmagick'
|
2
|
+
|
3
|
+
module ChunkyPNG
|
4
|
+
|
5
|
+
# Methods for importing and exporting RMagick image objects.
|
6
|
+
#
|
7
|
+
# By default, this module is disabled because of the dependency on RMagick.
|
8
|
+
# You need to include this module yourself if you want to use it.
|
9
|
+
#
|
10
|
+
# @example
|
11
|
+
#
|
12
|
+
# require 'rmagick
|
13
|
+
# require 'chunky_png/rmagick'
|
14
|
+
#
|
15
|
+
# canvas = ChunkyPNG::Canvas.from_file('filename.png')
|
16
|
+
# image = ChunkyPNG::RMagick.export(canvas)
|
17
|
+
#
|
18
|
+
# # do something with the image using RMagick
|
19
|
+
#
|
20
|
+
# updated_canvas = ChunkyPNG::RMagick.import(image)
|
21
|
+
#
|
22
|
+
module RMagick
|
23
|
+
|
24
|
+
extend self
|
25
|
+
|
26
|
+
# Imports an RMagick image as Canvas object.
|
27
|
+
# @param [Magick::Image] image The image to import
|
28
|
+
# @return [ChunkyPNG::Canvas] The canvas, constructed from the RMagick image.
|
29
|
+
def import(image)
|
30
|
+
pixels = image.export_pixels_to_str(0, 0, image.columns, image.rows, 'RGBA')
|
31
|
+
ChunkyPNG::Canvas.from_rgba_stream(image.columns, image.rows, pixels)
|
32
|
+
end
|
33
|
+
|
34
|
+
# Exports a Canvas as RMagick image instance.
|
35
|
+
# @param [ChunkyPNG::Canvas] canvas The canvas to export.
|
36
|
+
# @return [Magick::Image] The RMagick image constructed from the Canvas instance.
|
37
|
+
def export(canvas)
|
38
|
+
image = Magick::Image.new(canvas.width, canvas.height)
|
39
|
+
image.import_pixels(0,0, canvas.width, canvas.height, 'RGBA', canvas.pixels.pack('N*'))
|
40
|
+
image
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
@@ -0,0 +1,108 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe ChunkyPNG::Canvas::Adam7Interlacing do
|
4
|
+
include ChunkyPNG::Canvas::Adam7Interlacing
|
5
|
+
|
6
|
+
describe '#adam7_pass_sizes' do
|
7
|
+
it "should get the pass sizes for a 8x8 image correctly" do
|
8
|
+
adam7_pass_sizes(8, 8).should == [
|
9
|
+
[1, 1], [1, 1], [2, 1], [2, 2], [4, 2], [4, 4], [8, 4]
|
10
|
+
]
|
11
|
+
end
|
12
|
+
|
13
|
+
it "should get the pass sizes for a 12x12 image correctly" do
|
14
|
+
adam7_pass_sizes(12, 12).should == [
|
15
|
+
[2, 2], [1, 2], [3, 1], [3, 3], [6, 3], [6, 6], [12, 6]
|
16
|
+
]
|
17
|
+
end
|
18
|
+
|
19
|
+
it "should get the pass sizes for a 33x47 image correctly" do
|
20
|
+
adam7_pass_sizes(33, 47).should == [
|
21
|
+
[5, 6], [4, 6], [9, 6], [8, 12], [17, 12], [16, 24], [33, 23]
|
22
|
+
]
|
23
|
+
end
|
24
|
+
|
25
|
+
it "should get the pass sizes for a 1x1 image correctly" do
|
26
|
+
adam7_pass_sizes(1, 1).should == [
|
27
|
+
[1, 1], [0, 1], [1, 0], [0, 1], [1, 0], [0, 1], [1, 0]
|
28
|
+
]
|
29
|
+
end
|
30
|
+
|
31
|
+
it "should get the pass sizes for a 0x0 image correctly" do
|
32
|
+
adam7_pass_sizes(0, 0).should == [
|
33
|
+
[0, 0], [0, 0], [0, 0], [0, 0], [0, 0], [0, 0], [0, 0]
|
34
|
+
]
|
35
|
+
end
|
36
|
+
|
37
|
+
it "should always maintain the same amount of pixels in total" do
|
38
|
+
[[8, 8], [12, 12], [33, 47], [1, 1], [0, 0]].each do |(width, height)|
|
39
|
+
pass_sizes = adam7_pass_sizes(width, height)
|
40
|
+
pass_sizes.inject(0) { |sum, (w, h)| sum + (w*h) }.should == width * height
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
describe '#adam7_multiplier_offset' do
|
46
|
+
it "should get the multiplier and offset values for pass 1 correctly" do
|
47
|
+
adam7_multiplier_offset(0).should == { :x_offset => 0, :y_multiplier => 8, :y_offset => 0, :x_multiplier => 8 }
|
48
|
+
end
|
49
|
+
|
50
|
+
it "should get the multiplier and offset values for pass 2 correctly" do
|
51
|
+
adam7_multiplier_offset(1).should == { :x_offset => 4, :y_multiplier => 8, :y_offset => 0, :x_multiplier => 8 }
|
52
|
+
end
|
53
|
+
|
54
|
+
it "should get the multiplier and offset values for pass 3 correctly" do
|
55
|
+
adam7_multiplier_offset(2).should == { :x_offset => 0, :y_multiplier => 8, :y_offset => 4, :x_multiplier => 4 }
|
56
|
+
end
|
57
|
+
|
58
|
+
it "should get the multiplier and offset values for pass 4 correctly" do
|
59
|
+
adam7_multiplier_offset(3).should == { :x_offset => 2, :y_multiplier => 4, :y_offset => 0, :x_multiplier => 4 }
|
60
|
+
end
|
61
|
+
|
62
|
+
it "should get the multiplier and offset values for pass 5 correctly" do
|
63
|
+
adam7_multiplier_offset(4).should == { :x_offset => 0, :y_multiplier => 4, :y_offset => 2, :x_multiplier => 2 }
|
64
|
+
end
|
65
|
+
|
66
|
+
it "should get the multiplier and offset values for pass 6 correctly" do
|
67
|
+
adam7_multiplier_offset(5).should == { :x_offset => 1, :y_multiplier => 2, :y_offset => 0, :x_multiplier => 2 }
|
68
|
+
end
|
69
|
+
|
70
|
+
it "should get the multiplier and offset values for pass 7 correctly" do
|
71
|
+
adam7_multiplier_offset(6).should == { :x_offset => 0, :y_multiplier => 2, :y_offset => 1, :x_multiplier => 1 }
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
describe '#adam7_merge_pass' do
|
76
|
+
before(:each) { @reference = reference_canvas('adam7') }
|
77
|
+
|
78
|
+
it "should merge the submatrices correctly" do
|
79
|
+
submatrices = [
|
80
|
+
ChunkyPNG::Canvas.new(1, 1, 168430335), # r = 10
|
81
|
+
ChunkyPNG::Canvas.new(1, 1, 336860415), # r = 20
|
82
|
+
ChunkyPNG::Canvas.new(2, 1, 505290495), # r = 30
|
83
|
+
ChunkyPNG::Canvas.new(2, 2, 677668095), # r = 40
|
84
|
+
ChunkyPNG::Canvas.new(4, 2, 838912255), # r = 50
|
85
|
+
ChunkyPNG::Canvas.new(4, 4, 1023344895), # r = 60
|
86
|
+
ChunkyPNG::Canvas.new(8, 4, 1175063295), # r = 70
|
87
|
+
]
|
88
|
+
|
89
|
+
canvas = ChunkyPNG::Canvas.new(8,8)
|
90
|
+
submatrices.each_with_index { |m, pass| adam7_merge_pass(pass, canvas, m) }
|
91
|
+
canvas.should == @reference
|
92
|
+
end
|
93
|
+
end
|
94
|
+
|
95
|
+
describe '#adam7_extract_pass' do
|
96
|
+
before(:each) { @canvas = reference_canvas('adam7') }
|
97
|
+
|
98
|
+
1.upto(7) do |pass|
|
99
|
+
it "should extract pass #{pass} correctly" do
|
100
|
+
sm = adam7_extract_pass(pass - 1, @canvas)
|
101
|
+
sm.pixels.length.should == sm.width * sm.height
|
102
|
+
sm.pixels.uniq.length.should == 1
|
103
|
+
(ChunkyPNG::Color.r(sm[0,0]) / 10).should == pass
|
104
|
+
end
|
105
|
+
end
|
106
|
+
end
|
107
|
+
|
108
|
+
end
|
@@ -0,0 +1,81 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe ChunkyPNG::Canvas::PNGDecoding do
|
4
|
+
include ChunkyPNG::Canvas::PNGDecoding
|
5
|
+
|
6
|
+
describe '#decode_png_scanline' do
|
7
|
+
|
8
|
+
it "should decode a line without filtering as is" do
|
9
|
+
bytes = [255, 255, 255, 255, 255, 255, 255, 255, 255]
|
10
|
+
decode_png_scanline(ChunkyPNG::FILTER_NONE, bytes, nil).should == bytes
|
11
|
+
end
|
12
|
+
|
13
|
+
it "should decode a line with sub filtering correctly" do
|
14
|
+
# all white pixels
|
15
|
+
bytes = [255, 255, 255, 0, 0, 0, 0, 0, 0]
|
16
|
+
decoded_bytes = decode_png_scanline(ChunkyPNG::FILTER_SUB, bytes, nil)
|
17
|
+
decoded_bytes.should == [255, 255, 255, 255, 255, 255, 255, 255, 255]
|
18
|
+
|
19
|
+
# all black pixels
|
20
|
+
bytes = [0, 0, 0, 0, 0, 0, 0, 0, 0]
|
21
|
+
decoded_bytes = decode_png_scanline(ChunkyPNG::FILTER_SUB, bytes, nil)
|
22
|
+
decoded_bytes.should == [0, 0, 0, 0, 0, 0, 0, 0, 0]
|
23
|
+
|
24
|
+
# various colors
|
25
|
+
bytes = [255, 0, 45, 0, 255, 0, 112, 200, 178]
|
26
|
+
decoded_bytes = decode_png_scanline(ChunkyPNG::FILTER_SUB, bytes, nil)
|
27
|
+
decoded_bytes.should == [255, 0, 45, 255, 255, 45, 111, 199, 223]
|
28
|
+
end
|
29
|
+
|
30
|
+
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
|
+
# previous line has various pixels
|
38
|
+
previous_bytes = [255, 255, 255, 127, 127, 127, 0, 0, 0]
|
39
|
+
bytes = [0, 127, 255, 0, 127, 255, 0, 127, 255]
|
40
|
+
decoded_bytes = decode_png_scanline(ChunkyPNG::FILTER_UP, bytes, previous_bytes)
|
41
|
+
decoded_bytes.should == [255, 126, 254, 127, 254, 126, 0, 127, 255]
|
42
|
+
end
|
43
|
+
|
44
|
+
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 = [ 0, 0, 10, 20, 10, 0, 0, 40, 10, 20, 190, 0]
|
47
|
+
decoded = decode_png_scanline(ChunkyPNG::FILTER_AVERAGE, current, previous)
|
48
|
+
decoded.should == [5, 10, 25, 45, 45, 55, 80, 125, 105, 150, 114, 165]
|
49
|
+
end
|
50
|
+
|
51
|
+
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 = [ 0, 0, 10, 20, 10, 0, 0, 40, 10, 20, 190, 0]
|
54
|
+
decoded = decode_png_scanline(ChunkyPNG::FILTER_PAETH, current, previous)
|
55
|
+
decoded.should == [10, 20, 40, 60, 60, 60, 70, 120, 90, 120, 54, 120]
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
describe '.from_datastream' do
|
60
|
+
|
61
|
+
[:indexed, :grayscale, :grayscale_alpha, :truecolor, :truecolor_alpha].each do |color_mode|
|
62
|
+
it "should decode an image with color mode #{color_mode} correctly" do
|
63
|
+
reference = ChunkyPNG::Canvas.new(10, 10, ChunkyPNG::Color.rgb(100, 100, 100))
|
64
|
+
canvas = ChunkyPNG::Canvas.from_file(resource_file("gray_10x10_#{color_mode}.png"))
|
65
|
+
canvas.should == reference
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
it "should decode a transparent image correctly" do
|
70
|
+
reference = ChunkyPNG::Canvas.new(10, 10, ChunkyPNG::Color.rgba(100, 100, 100, 128))
|
71
|
+
canvas = ChunkyPNG::Canvas.from_file(resource_file("transparent_gray_10x10.png"))
|
72
|
+
canvas.should == reference
|
73
|
+
end
|
74
|
+
|
75
|
+
it "should decode an interlaced image correctly" do
|
76
|
+
canvas_i = ChunkyPNG::Canvas.from_file(resource_file("16x16_interlaced.png"))
|
77
|
+
canvas_ni = ChunkyPNG::Canvas.from_file(resource_file("16x16_non_interlaced.png"))
|
78
|
+
canvas_i.should == canvas_ni
|
79
|
+
end
|
80
|
+
end
|
81
|
+
end
|