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.
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