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.
Files changed (43) hide show
  1. data/README.rdoc +20 -10
  2. data/chunky_png.gemspec +18 -6
  3. data/lib/chunky_png.rb +11 -28
  4. data/lib/chunky_png/canvas.rb +186 -0
  5. data/lib/chunky_png/canvas/adam7_interlacing.rb +53 -0
  6. data/lib/chunky_png/canvas/drawing.rb +8 -0
  7. data/lib/chunky_png/{pixel_matrix → canvas}/operations.rb +12 -12
  8. data/lib/chunky_png/canvas/png_decoding.rb +145 -0
  9. data/lib/chunky_png/canvas/png_encoding.rb +182 -0
  10. data/lib/chunky_png/chunk.rb +101 -23
  11. data/lib/chunky_png/color.rb +307 -0
  12. data/lib/chunky_png/datastream.rb +143 -45
  13. data/lib/chunky_png/image.rb +31 -30
  14. data/lib/chunky_png/palette.rb +49 -47
  15. data/lib/chunky_png/rmagick.rb +43 -0
  16. data/spec/chunky_png/canvas/adam7_interlacing_spec.rb +108 -0
  17. data/spec/chunky_png/canvas/png_decoding_spec.rb +81 -0
  18. data/spec/chunky_png/canvas/png_encoding_spec.rb +70 -0
  19. data/spec/chunky_png/canvas_spec.rb +71 -0
  20. data/spec/chunky_png/color_spec.rb +104 -0
  21. data/spec/chunky_png/datastream_spec.rb +32 -0
  22. data/spec/chunky_png/image_spec.rb +25 -0
  23. data/spec/chunky_png/rmagick_spec.rb +21 -0
  24. data/spec/{integration/image_spec.rb → chunky_png_spec.rb} +14 -8
  25. data/spec/resources/composited.png +0 -0
  26. data/spec/resources/cropped.png +0 -0
  27. data/spec/resources/damaged_chunk.png +0 -0
  28. data/spec/resources/damaged_signature.png +13 -0
  29. data/spec/resources/pixelstream.rgba +67 -0
  30. data/spec/resources/replaced.png +0 -0
  31. data/spec/resources/text_chunk.png +0 -0
  32. data/spec/resources/ztxt_chunk.png +0 -0
  33. data/spec/spec_helper.rb +8 -5
  34. data/tasks/github-gem.rake +1 -1
  35. metadata +37 -18
  36. data/lib/chunky_png/pixel.rb +0 -272
  37. data/lib/chunky_png/pixel_matrix.rb +0 -136
  38. data/lib/chunky_png/pixel_matrix/decoding.rb +0 -159
  39. data/lib/chunky_png/pixel_matrix/encoding.rb +0 -89
  40. data/spec/unit/decoding_spec.rb +0 -83
  41. data/spec/unit/encoding_spec.rb +0 -27
  42. data/spec/unit/pixel_matrix_spec.rb +0 -93
  43. data/spec/unit/pixel_spec.rb +0 -47
@@ -1,43 +1,44 @@
1
1
  module ChunkyPNG
2
- class Image
3
-
4
- attr_reader :pixel_matrix
5
-
6
- def initialize(width, height, background_color = ChunkyPNG::Pixel::TRANSPARENT)
7
- @pixel_matrix = ChunkyPNG::PixelMatrix.new(width, height, background_color)
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
- def width
15
- pixel_matrix.width
16
- end
9
+ attr_accessor :metadata
17
10
 
18
- def height
19
- pixel_matrix.height
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 [](x, y)
23
- pixel_matrix[x,y]
24
- end
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 pixels
31
- pixel_matrix.pixels
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 write(io, constraints = {})
35
- datastream = pixel_matrix.to_datastream(constraints)
36
- datastream.write(io)
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 save(filename, constraints = {})
40
- File.open(filename, 'w') { |io| write(io, constraints) }
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
@@ -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 that image,
6
- # but can also use an implicit palette, e.g. all truecolor colors or all grayscale colors.
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 palette is provided
9
- # in a PNG datastream, and it supports encoding colors to an explicit matrix.
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::Pixel
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<ChunkyPNG::Pixel>] enum The set of colors to include in this palette.
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, 255)
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::Pixel.rgba(*bytes)
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 pixel matrix.
58
- # @param [ChunkyPNG::PixelMatrix] pixel_matrix The pixel matrix to create a palette for.
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.from_pixel_matrix(pixel_matrix)
61
- self.new(pixel_matrix.pixels.map { |fn| ChunkyPNG::Pixel.new(fn) })
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<ChunkyPNG::Pixel>] pixels An enumeration of pixels to create a palette for
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::Pixel#opaque?
81
+ # @see ChunkyPNG::Color#opaque?
80
82
  def opaque?
81
- all? { |pixel| pixel.opaque? }
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::Pixel#grayscale??
88
+ # @see ChunkyPNG::Color#grayscale??
87
89
  def grayscale?
88
- all? { |pixel| pixel.grayscale? }
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::Pixel] The color that is stored in the palette under the given index
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::Pixel] color The color for which to look up the index.
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[ChunkyPNG::Pixel.new(color)]
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(&:a).pack('C*'))
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 += color.to_truecolor_bytes
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