chunky_png 0.0.5 → 0.5.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|