chunky_png 0.0.1 → 0.0.2
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/.gitignore +2 -0
- data/chunky_png.gemspec +4 -4
- data/lib/chunky_png.rb +3 -0
- data/lib/chunky_png/chunk.rb +4 -1
- data/lib/chunky_png/datastream.rb +9 -10
- data/lib/chunky_png/image.rb +3 -17
- data/lib/chunky_png/palette.rb +64 -60
- data/lib/chunky_png/pixel.rb +101 -0
- data/lib/chunky_png/pixel_matrix.rb +36 -118
- data/lib/chunky_png/pixel_matrix/decoding.rb +75 -0
- data/lib/chunky_png/pixel_matrix/encoding.rb +83 -0
- data/spec/integration/image_spec.rb +11 -18
- data/spec/resources/gray_10x10.png +0 -0
- data/spec/resources/gray_10x10_grayscale.png +0 -0
- data/spec/resources/gray_10x10_grayscale_alpha.png +0 -0
- data/spec/resources/gray_10x10_indexed.png +0 -0
- data/spec/resources/gray_10x10_truecolor.png +0 -0
- data/spec/resources/gray_10x10_truecolor_alpha.png +0 -0
- data/spec/unit/decoding_spec.rb +44 -0
- data/spec/unit/encoding_spec.rb +27 -0
- data/spec/unit/pixel_matrix_spec.rb +20 -54
- data/spec/unit/pixel_spec.rb +32 -0
- metadata +17 -3
- data/spec/resources/indexed_10x10.png +0 -0
data/.gitignore
CHANGED
data/chunky_png.gemspec
CHANGED
@@ -3,8 +3,8 @@ Gem::Specification.new do |s|
|
|
3
3
|
|
4
4
|
# Do not change the version and date fields by hand. This will be done
|
5
5
|
# automatically by the gem release script.
|
6
|
-
s.version = "0.0.
|
7
|
-
s.date = "2010-01-
|
6
|
+
s.version = "0.0.2"
|
7
|
+
s.date = "2010-01-12"
|
8
8
|
|
9
9
|
s.summary = "Pure ruby library for read/write, chunk-level access to PNG files"
|
10
10
|
s.description = "Pure ruby library for read/write, chunk-level access to PNG files"
|
@@ -20,6 +20,6 @@ Gem::Specification.new do |s|
|
|
20
20
|
|
21
21
|
# Do not change the files and test_files fields by hand. This will be done
|
22
22
|
# automatically by the gem release script.
|
23
|
-
s.files = %w(spec/spec_helper.rb .gitignore lib/chunky_png/pixel_matrix.rb LICENSE lib/chunky_png/chunk.rb Rakefile README.rdoc spec/resources/
|
24
|
-
s.test_files = %w(spec/integration/image_spec.rb spec/unit/pixel_matrix_spec.rb)
|
23
|
+
s.files = %w(spec/spec_helper.rb spec/resources/gray_10x10_grayscale.png spec/resources/gray_10x10.png .gitignore spec/resources/gray_10x10_truecolor_alpha.png lib/chunky_png/pixel_matrix.rb LICENSE spec/resources/gray_10x10_truecolor.png lib/chunky_png/pixel_matrix/decoding.rb lib/chunky_png/chunk.rb spec/unit/encoding_spec.rb Rakefile README.rdoc spec/resources/gray_10x10_indexed.png spec/integration/image_spec.rb lib/chunky_png/pixel.rb lib/chunky_png/palette.rb lib/chunky_png/datastream.rb chunky_png.gemspec tasks/github-gem.rake spec/unit/decoding_spec.rb spec/resources/gray_10x10_grayscale_alpha.png lib/chunky_png/pixel_matrix/encoding.rb lib/chunky_png/image.rb spec/unit/pixel_spec.rb spec/unit/pixel_matrix_spec.rb lib/chunky_png.rb)
|
24
|
+
s.test_files = %w(spec/unit/encoding_spec.rb spec/integration/image_spec.rb spec/unit/decoding_spec.rb spec/unit/pixel_spec.rb spec/unit/pixel_matrix_spec.rb)
|
25
25
|
end
|
data/lib/chunky_png.rb
CHANGED
@@ -4,6 +4,9 @@ require 'zlib'
|
|
4
4
|
require 'chunky_png/datastream'
|
5
5
|
require 'chunky_png/chunk'
|
6
6
|
require 'chunky_png/palette'
|
7
|
+
require 'chunky_png/pixel'
|
8
|
+
require 'chunky_png/pixel_matrix/encoding'
|
9
|
+
require 'chunky_png/pixel_matrix/decoding'
|
7
10
|
require 'chunky_png/pixel_matrix'
|
8
11
|
require 'chunky_png/image'
|
9
12
|
|
data/lib/chunky_png/chunk.rb
CHANGED
@@ -90,6 +90,9 @@ module ChunkyPNG
|
|
90
90
|
|
91
91
|
class Palette < Generic
|
92
92
|
end
|
93
|
+
|
94
|
+
class Transparency < Generic
|
95
|
+
end
|
93
96
|
|
94
97
|
class ImageData < Generic
|
95
98
|
end
|
@@ -97,7 +100,7 @@ module ChunkyPNG
|
|
97
100
|
# Maps chunk types to classes.
|
98
101
|
# If a chunk type is not given in this hash, a generic chunk type will be used.
|
99
102
|
CHUNK_TYPES = {
|
100
|
-
'IHDR' => Header, 'IEND' => End, 'IDAT' => ImageData, 'PLTE' => Palette
|
103
|
+
'IHDR' => Header, 'IEND' => End, 'IDAT' => ImageData, 'PLTE' => Palette, 'tRNS' => Transparency
|
101
104
|
}
|
102
105
|
|
103
106
|
end
|
@@ -7,6 +7,7 @@ module ChunkyPNG
|
|
7
7
|
attr_accessor :header_chunk
|
8
8
|
attr_accessor :other_chunks
|
9
9
|
attr_accessor :palette_chunk
|
10
|
+
attr_accessor :transparency_chunk
|
10
11
|
attr_accessor :data_chunks
|
11
12
|
attr_accessor :end_chunk
|
12
13
|
|
@@ -17,10 +18,11 @@ module ChunkyPNG
|
|
17
18
|
until io.eof?
|
18
19
|
chunk = ChunkyPNG::Chunk.read(io)
|
19
20
|
case chunk
|
20
|
-
when ChunkyPNG::Chunk::Header
|
21
|
-
when ChunkyPNG::Chunk::Palette
|
22
|
-
when ChunkyPNG::Chunk::
|
23
|
-
when ChunkyPNG::Chunk::
|
21
|
+
when ChunkyPNG::Chunk::Header then ds.header_chunk = chunk
|
22
|
+
when ChunkyPNG::Chunk::Palette then ds.palette_chunk = chunk
|
23
|
+
when ChunkyPNG::Chunk::Transparency then ds.transparency_chunk = chunk
|
24
|
+
when ChunkyPNG::Chunk::ImageData then ds.data_chunks << chunk
|
25
|
+
when ChunkyPNG::Chunk::End then ds.end_chunk = chunk
|
24
26
|
else ds.other_chunks << chunk
|
25
27
|
end
|
26
28
|
end
|
@@ -35,7 +37,8 @@ module ChunkyPNG
|
|
35
37
|
def chunks
|
36
38
|
cs = [header_chunk]
|
37
39
|
cs += other_chunks
|
38
|
-
cs << palette_chunk
|
40
|
+
cs << palette_chunk if palette_chunk
|
41
|
+
cs << transparency_chunk if transparency_chunk
|
39
42
|
cs += data_chunks
|
40
43
|
cs << end_chunk
|
41
44
|
return cs
|
@@ -62,11 +65,7 @@ module ChunkyPNG
|
|
62
65
|
end
|
63
66
|
|
64
67
|
def pixel_matrix
|
65
|
-
@pixel_matrix ||=
|
66
|
-
data = data_chunks.map(&:content).join('')
|
67
|
-
streamdata = Zlib::Inflate.inflate(data)
|
68
|
-
matrix = ChunkyPNG::PixelMatrix.load(header, streamdata)
|
69
|
-
end
|
68
|
+
@pixel_matrix ||= ChunkyPNG::PixelMatrix.decode(self)
|
70
69
|
end
|
71
70
|
end
|
72
71
|
end
|
data/lib/chunky_png/image.rb
CHANGED
@@ -3,7 +3,7 @@ module ChunkyPNG
|
|
3
3
|
|
4
4
|
attr_reader :pixels
|
5
5
|
|
6
|
-
def initialize(width, height, background_color = ChunkyPNG::
|
6
|
+
def initialize(width, height, background_color = ChunkyPNG::Pixel::TRANSPARENT)
|
7
7
|
@pixels = ChunkyPNG::PixelMatrix.new(width, height, background_color)
|
8
8
|
end
|
9
9
|
|
@@ -15,22 +15,8 @@ module ChunkyPNG
|
|
15
15
|
pixels.height
|
16
16
|
end
|
17
17
|
|
18
|
-
def write(io)
|
19
|
-
datastream =
|
20
|
-
|
21
|
-
palette = pixels.palette
|
22
|
-
if palette.indexable?
|
23
|
-
datastream.header_chunk = ChunkyPNG::Chunk::Header.new(:width => width, :height => height, :color => ChunkyPNG::Chunk::Header::COLOR_INDEXED)
|
24
|
-
datastream.palette_chunk = palette.to_plte_chunk
|
25
|
-
datastream.data_chunks = datastream.idat_chunks(pixels.to_indexed_pixelstream(palette))
|
26
|
-
datastream.end_chunk = ChunkyPNG::Chunk::End.new
|
27
|
-
else
|
28
|
-
raise 'd'
|
29
|
-
datastream.header_chunk = ChunkyPNG::Chunk::Header.new(:width => width, :height => height)
|
30
|
-
datastream.data_chunks = datastream.idat_chunks(pixels.to_rgb_pixelstream)
|
31
|
-
datastream.end_chunk = ChunkyPNG::Chunk::End.new
|
32
|
-
end
|
33
|
-
|
18
|
+
def write(io, constraints = {})
|
19
|
+
datastream = pixels.to_datastream(constraints)
|
34
20
|
datastream.write(io)
|
35
21
|
end
|
36
22
|
end
|
data/lib/chunky_png/palette.rb
CHANGED
@@ -4,88 +4,92 @@ module ChunkyPNG
|
|
4
4
|
# PALETTE CLASS
|
5
5
|
###########################################
|
6
6
|
|
7
|
-
class Palette <
|
7
|
+
class Palette < SortedSet
|
8
8
|
|
9
|
-
def
|
10
|
-
|
9
|
+
def initialize(enum)
|
10
|
+
super(enum)
|
11
|
+
@decoding_map = enum if enum.kind_of?(Array)
|
11
12
|
end
|
12
13
|
|
13
|
-
def self.
|
14
|
-
|
14
|
+
def self.from_chunks(palette_chunk, transparency_chunk = nil)
|
15
|
+
return nil if palette_chunk.nil?
|
16
|
+
|
17
|
+
decoding_map = []
|
18
|
+
index = 0
|
19
|
+
|
20
|
+
palatte_bytes = palette_chunk.content.unpack('C*')
|
21
|
+
if transparency_chunk
|
22
|
+
alpha_channel = transparency_chunk.content.unpack('C*')
|
23
|
+
else
|
24
|
+
alpha_channel = Array.new(palatte_bytes.size / 3, 255)
|
25
|
+
end
|
26
|
+
|
27
|
+
index = 0
|
28
|
+
palatte_bytes.each_slice(3) do |bytes|
|
29
|
+
bytes << alpha_channel[index]
|
30
|
+
decoding_map << ChunkyPNG::Pixel.rgba(*bytes)
|
31
|
+
index += 1
|
32
|
+
end
|
33
|
+
|
34
|
+
self.new(decoding_map)
|
35
|
+
end
|
36
|
+
|
37
|
+
def self.from_pixel_matrix(pixel_matrix)
|
38
|
+
self.new(pixel_matrix.pixels)
|
15
39
|
end
|
16
40
|
|
17
|
-
def self.
|
18
|
-
|
19
|
-
colors.each { |color| palette << color }
|
20
|
-
palette
|
41
|
+
def self.from_pixels(pixels)
|
42
|
+
self.new(pixels)
|
21
43
|
end
|
22
44
|
|
23
45
|
def indexable?
|
24
46
|
size < 256
|
25
47
|
end
|
26
48
|
|
49
|
+
def opaque?
|
50
|
+
all? { |pixel| pixel.opaque? }
|
51
|
+
end
|
52
|
+
|
53
|
+
def can_decode?
|
54
|
+
!@decoding_map.nil?
|
55
|
+
end
|
56
|
+
|
57
|
+
def can_encode?
|
58
|
+
!@encoding_map.nil?
|
59
|
+
end
|
60
|
+
|
61
|
+
def [](index)
|
62
|
+
@decoding_map[index]
|
63
|
+
end
|
64
|
+
|
27
65
|
def index(color)
|
28
|
-
@
|
66
|
+
@encoding_map[color]
|
67
|
+
end
|
68
|
+
|
69
|
+
def to_trns_chunk
|
70
|
+
ChunkyPNG::Chunk::Transparency.new('tRNS', map(&:a).pack('C*'))
|
29
71
|
end
|
30
72
|
|
31
73
|
def to_plte_chunk
|
32
|
-
@
|
74
|
+
@encoding_map = {}
|
33
75
|
colors = []
|
34
76
|
|
35
77
|
each_with_index do |color, index|
|
36
|
-
@
|
37
|
-
colors += color.
|
78
|
+
@encoding_map[color] = index
|
79
|
+
colors += color.to_truecolor_bytes
|
38
80
|
end
|
39
81
|
|
40
|
-
ChunkyPNG::Chunk::
|
41
|
-
end
|
42
|
-
end
|
43
|
-
|
44
|
-
###########################################
|
45
|
-
# COLOR CLASS
|
46
|
-
###########################################
|
47
|
-
|
48
|
-
class Color
|
49
|
-
|
50
|
-
attr_accessor :r, :g, :b
|
51
|
-
|
52
|
-
def initialize(r, g, b)
|
53
|
-
@r, @g, @b = r, g, b
|
54
|
-
end
|
55
|
-
|
56
|
-
### CONSTRUCTORS ###########################################
|
57
|
-
|
58
|
-
def self.rgb(r, g, b)
|
59
|
-
new(r, g, b)
|
60
|
-
end
|
61
|
-
|
62
|
-
### COLOR CONSTANTS ###########################################
|
63
|
-
|
64
|
-
BLACK = rgb( 0, 0, 0)
|
65
|
-
WHITE = rgb(255, 255, 255)
|
66
|
-
|
67
|
-
### CONVERSION ###########################################
|
68
|
-
|
69
|
-
def index(palette)
|
70
|
-
palette.index(self)
|
71
|
-
end
|
72
|
-
|
73
|
-
def to_rgb_array
|
74
|
-
[r, g, b]
|
75
|
-
end
|
76
|
-
|
77
|
-
def to_rgb
|
78
|
-
to_rgb_array.pack('CCC')
|
79
|
-
end
|
80
|
-
|
81
|
-
def inspect
|
82
|
-
'#%02x%02x%02x' % [r, g, b]
|
82
|
+
ChunkyPNG::Chunk::Palette.new('PLTE', colors.pack('C*'))
|
83
83
|
end
|
84
84
|
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
85
|
+
def best_colormode
|
86
|
+
if indexable?
|
87
|
+
ChunkyPNG::Chunk::Header::COLOR_INDEXED
|
88
|
+
elsif opaque?
|
89
|
+
ChunkyPNG::Chunk::Header::COLOR_TRUECOLOR
|
90
|
+
else
|
91
|
+
ChunkyPNG::Chunk::Header::COLOR_TRUECOLOR_ALPHA
|
92
|
+
end
|
89
93
|
end
|
90
94
|
end
|
91
95
|
end
|
@@ -0,0 +1,101 @@
|
|
1
|
+
module ChunkyPNG
|
2
|
+
|
3
|
+
class Pixel
|
4
|
+
|
5
|
+
attr_reader :value
|
6
|
+
|
7
|
+
def initialize(value)
|
8
|
+
@value = value
|
9
|
+
end
|
10
|
+
|
11
|
+
def self.rgb(r, g, b)
|
12
|
+
rgba(r, g, b, 255)
|
13
|
+
end
|
14
|
+
|
15
|
+
def self.rgba(r, g, b, a)
|
16
|
+
new(r << 24 | g << 16 | b << 8 | a)
|
17
|
+
end
|
18
|
+
|
19
|
+
def self.grayscale(teint, a = 255)
|
20
|
+
rgba(teint, teint, teint, a)
|
21
|
+
end
|
22
|
+
|
23
|
+
def self.from_rgb_stream(stream)
|
24
|
+
self.rgb(*stream.unpack('C3'))
|
25
|
+
end
|
26
|
+
|
27
|
+
def self.from_rgba_stream(stream)
|
28
|
+
self.rgba(*stream.unpack('C4'))
|
29
|
+
end
|
30
|
+
|
31
|
+
def r
|
32
|
+
(@value & 0xff000000) >> 24
|
33
|
+
end
|
34
|
+
|
35
|
+
def g
|
36
|
+
(@value & 0x00ff0000) >> 16
|
37
|
+
end
|
38
|
+
|
39
|
+
def b
|
40
|
+
(@value & 0x0000ff00) >> 8
|
41
|
+
end
|
42
|
+
|
43
|
+
def a
|
44
|
+
@value & 0x000000ff
|
45
|
+
end
|
46
|
+
|
47
|
+
def opaque?
|
48
|
+
a == 0x000000ff
|
49
|
+
end
|
50
|
+
|
51
|
+
def inspect
|
52
|
+
'#%08x' % @value
|
53
|
+
end
|
54
|
+
|
55
|
+
def eql?(other)
|
56
|
+
other.kind_of?(self.class) && other.value == self.value
|
57
|
+
end
|
58
|
+
|
59
|
+
alias :== :eql?
|
60
|
+
|
61
|
+
def to_truecolor_alpha_bytes
|
62
|
+
[r,g,b,a]
|
63
|
+
end
|
64
|
+
|
65
|
+
def to_truecolor_bytes
|
66
|
+
[r,g,b]
|
67
|
+
end
|
68
|
+
|
69
|
+
def index(palette)
|
70
|
+
palette.index(self)
|
71
|
+
end
|
72
|
+
|
73
|
+
def to_indexed_bytes(palette)
|
74
|
+
[index(palette)]
|
75
|
+
end
|
76
|
+
|
77
|
+
def to_grayscale_bytes
|
78
|
+
[r] # Assumption: r == g == b
|
79
|
+
end
|
80
|
+
|
81
|
+
def to_grayscale_alpha_bytes
|
82
|
+
[r, a] # Assumption: r == g == b
|
83
|
+
end
|
84
|
+
|
85
|
+
BLACK = rgb( 0, 0, 0)
|
86
|
+
WHITE = rgb(255, 255, 255)
|
87
|
+
|
88
|
+
TRANSPARENT = rgba(0, 0, 0, 0)
|
89
|
+
|
90
|
+
def self.bytesize(color_mode)
|
91
|
+
case color_mode
|
92
|
+
when ChunkyPNG::Chunk::Header::COLOR_INDEXED then 1
|
93
|
+
when ChunkyPNG::Chunk::Header::COLOR_TRUECOLOR then 3
|
94
|
+
when ChunkyPNG::Chunk::Header::COLOR_TRUECOLOR_ALPHA then 4
|
95
|
+
when ChunkyPNG::Chunk::Header::COLOR_GRAYSCALE then 1
|
96
|
+
when ChunkyPNG::Chunk::Header::COLOR_GRAYSCALE_ALPHA then 2
|
97
|
+
else raise "Don't know the bytesize of pixels in this colormode: #{color_mode}!"
|
98
|
+
end
|
99
|
+
end
|
100
|
+
end
|
101
|
+
end
|
@@ -2,6 +2,9 @@ module ChunkyPNG
|
|
2
2
|
|
3
3
|
class PixelMatrix
|
4
4
|
|
5
|
+
include Encoding
|
6
|
+
extend Decoding
|
7
|
+
|
5
8
|
FILTER_NONE = 0
|
6
9
|
FILTER_SUB = 1
|
7
10
|
FILTER_UP = 2
|
@@ -9,11 +12,23 @@ module ChunkyPNG
|
|
9
12
|
FILTER_PAETH = 4
|
10
13
|
|
11
14
|
attr_reader :width, :height, :pixels
|
15
|
+
|
16
|
+
|
17
|
+
def initialize(width, height, initial = ChunkyPNG::Pixel::TRANSPARENT)
|
18
|
+
|
19
|
+
@width, @height = width, height
|
20
|
+
|
21
|
+
if initial.kind_of?(ChunkyPNG::Pixel)
|
22
|
+
@pixels = Array.new(width * height, initial)
|
23
|
+
elsif initial.kind_of?(Array) && initial.size == width * height
|
24
|
+
@pixels = initial
|
25
|
+
else
|
26
|
+
raise "Cannot use this value as initial pixel matrix: #{initial.inspect}!"
|
27
|
+
end
|
28
|
+
end
|
12
29
|
|
13
|
-
def
|
14
|
-
|
15
|
-
matrix.decode_pixelstream(content, header)
|
16
|
-
return matrix
|
30
|
+
def []=(x, y, pixel)
|
31
|
+
@pixels[y * width + x] = pixel
|
17
32
|
end
|
18
33
|
|
19
34
|
def [](x, y)
|
@@ -27,131 +42,34 @@ module ChunkyPNG
|
|
27
42
|
end
|
28
43
|
end
|
29
44
|
|
30
|
-
def []=(x, y, color)
|
31
|
-
@pixels[y * width + x] = Pixel.new(color)
|
32
|
-
end
|
33
|
-
|
34
|
-
def initialize(width, height, background_color = ChunkyPNG::Color::WHITE)
|
35
|
-
@width, @height = width, height
|
36
|
-
@pixels = Array.new(width * height, Pixel.new(background_color))
|
37
|
-
end
|
38
|
-
|
39
|
-
def decode_pixelstream(stream, header = nil)
|
40
|
-
verify_length!(stream.length)
|
41
|
-
@pixels = []
|
42
|
-
|
43
|
-
decoded_bytes = Array.new(header.width * 3, 0)
|
44
|
-
height.times do |line_no|
|
45
|
-
position = line_no * (width * 3 + 1)
|
46
|
-
line_length = header.width * 3
|
47
|
-
bytes = stream.unpack("@#{position}CC#{line_length}")
|
48
|
-
filter = bytes.shift
|
49
|
-
decoded_bytes = decode_scanline(filter, bytes, decoded_bytes, header)
|
50
|
-
decoded_colors = decode_colors(decoded_bytes, header)
|
51
|
-
@pixels += decoded_colors.map { |c| Pixel.new(c) }
|
52
|
-
end
|
53
|
-
|
54
|
-
raise "Invalid amount of pixels" if @pixels.size != width * height
|
55
|
-
end
|
56
|
-
|
57
|
-
def decode_colors(bytes, header)
|
58
|
-
(0...width).map { |i| ChunkyPNG::Color.rgb(bytes[i*3+0], bytes[i*3+1], bytes[i*3+2]) }
|
59
|
-
end
|
60
|
-
|
61
|
-
def decode_scanline(filter, bytes, previous_bytes, header = nil)
|
62
|
-
case filter
|
63
|
-
when FILTER_NONE then decode_scanline_none( bytes, previous_bytes, header)
|
64
|
-
when FILTER_SUB then decode_scanline_sub( bytes, previous_bytes, header)
|
65
|
-
when FILTER_UP then decode_scanline_up( bytes, previous_bytes, header)
|
66
|
-
when FILTER_AVERAGE then raise "Average filter are not yet supported!"
|
67
|
-
when FILTER_PAETH then raise "Paeth filter are not yet supported!"
|
68
|
-
else raise "Unknown filter type"
|
69
|
-
end
|
70
|
-
end
|
71
|
-
|
72
|
-
def decode_scanline_none(bytes, previous_bytes, header = nil)
|
73
|
-
bytes
|
74
|
-
end
|
75
|
-
|
76
|
-
def decode_scanline_sub(bytes, previous_bytes, header = nil)
|
77
|
-
bytes.each_with_index { |b, i| bytes[i] = (b + (i >= 3 ? bytes[i-3] : 0)) % 256 }
|
78
|
-
bytes
|
79
|
-
end
|
80
|
-
|
81
|
-
def decode_scanline_up(bytes, previous_bytes, header = nil)
|
82
|
-
bytes.each_with_index { |b, i| bytes[i] = (b + previous_bytes[i]) % 256 }
|
83
|
-
bytes
|
84
|
-
end
|
85
|
-
|
86
|
-
def verify_length!(bytes_count)
|
87
|
-
raise "Invalid stream length!" unless bytes_count == width * height * 3 + height
|
88
|
-
end
|
89
|
-
|
90
|
-
def encode_scanline(filter, bytes, previous_bytes, header = nil)
|
91
|
-
case filter
|
92
|
-
when FILTER_NONE then encode_scanline_none( bytes, previous_bytes, header)
|
93
|
-
when FILTER_SUB then encode_scanline_sub( bytes, previous_bytes, header)
|
94
|
-
when FILTER_UP then encode_scanline_up( bytes, previous_bytes, header)
|
95
|
-
when FILTER_AVERAGE then raise "Average filter are not yet supported!"
|
96
|
-
when FILTER_PAETH then raise "Paeth filter are not yet supported!"
|
97
|
-
else raise "Unknown filter type"
|
98
|
-
end
|
99
|
-
end
|
100
|
-
|
101
|
-
def encode_scanline_none(bytes, previous_bytes, header = nil)
|
102
|
-
[FILTER_NONE] + bytes
|
103
|
-
end
|
104
|
-
|
105
|
-
def encode_scanline_sub(bytes, previous_bytes, header = nil)
|
106
|
-
encoded = (3...bytes.length).map { |n| (bytes[n-3] - bytes[n]) % 256 }
|
107
|
-
[FILTER_SUB] + bytes[0...3] + encoded
|
108
|
-
end
|
109
|
-
|
110
|
-
def encode_scanline_up(bytes, previous_bytes, header = nil)
|
111
|
-
encoded = (0...bytes.length).map { |n| previous_bytes[n] - bytes[n] % 256 }
|
112
|
-
[FILTER_UP] + encoded
|
113
|
-
end
|
114
|
-
|
115
45
|
def palette
|
116
46
|
ChunkyPNG::Palette.from_pixels(@pixels)
|
117
47
|
end
|
118
48
|
|
119
|
-
def
|
120
|
-
|
121
|
-
end
|
122
|
-
|
123
|
-
def to_indexed_pixelstream(palette)
|
124
|
-
stream = ""
|
125
|
-
each_scanline do |line|
|
126
|
-
bytes = line.map { |p| p.color.index(palette) }
|
127
|
-
stream << encode_scanline(FILTER_NONE, bytes, nil, nil).pack('C*')
|
128
|
-
end
|
129
|
-
return stream
|
49
|
+
def opaque?
|
50
|
+
pixels.all? { |pixel| pixel.opaque? }
|
130
51
|
end
|
131
52
|
|
132
|
-
def
|
133
|
-
|
134
|
-
each_scanline do |line|
|
135
|
-
bytes = line.map { |p| p.color.to_rgb_array }.flatten
|
136
|
-
stream << encode_scanline(FILTER_NONE, bytes, nil, nil).pack('C*')
|
137
|
-
end
|
138
|
-
return stream
|
53
|
+
def indexable?
|
54
|
+
palette.indexable?
|
139
55
|
end
|
140
|
-
end
|
141
|
-
|
142
|
-
class Pixel
|
143
|
-
attr_accessor :color, :alpha
|
144
56
|
|
145
|
-
def
|
146
|
-
|
57
|
+
def to_datastream(constraints = {})
|
58
|
+
data = encode(constraints)
|
59
|
+
ds = Datastream.new
|
60
|
+
ds.header_chunk = Chunk::Header.new(data[:header])
|
61
|
+
ds.palette_chunk = data[:palette_chunk] if data[:palette_chunk]
|
62
|
+
ds.transparency_chunk = data[:transparency_chunk] if data[:transparency_chunk]
|
63
|
+
ds.data_chunks = ds.idat_chunks(data[:pixelstream])
|
64
|
+
ds.end_chunk = Chunk::End.new
|
65
|
+
return ds
|
147
66
|
end
|
148
67
|
|
149
|
-
def
|
150
|
-
|
68
|
+
def eql?(other)
|
69
|
+
other.kind_of?(self.class) && other.pixels == self.pixels &&
|
70
|
+
other.width == self.width && other.height == self.height
|
151
71
|
end
|
152
72
|
|
153
|
-
|
154
|
-
alpha = 255
|
155
|
-
end
|
73
|
+
alias :== :eql?
|
156
74
|
end
|
157
75
|
end
|
@@ -0,0 +1,75 @@
|
|
1
|
+
module ChunkyPNG
|
2
|
+
class PixelMatrix
|
3
|
+
module Decoding
|
4
|
+
|
5
|
+
def decode(ds)
|
6
|
+
stream = Zlib::Inflate.inflate(ds.data_chunks.map(&:content).join(''))
|
7
|
+
width = ds.header_chunk.width
|
8
|
+
height = ds.header_chunk.height
|
9
|
+
color_mode = ds.header_chunk.color
|
10
|
+
palette = ChunkyPNG::Palette.from_chunks(ds.palette_chunk, ds.transparency_chunk)
|
11
|
+
decode_pixelstream(stream, width, height, color_mode, palette)
|
12
|
+
end
|
13
|
+
|
14
|
+
def decode_pixelstream(stream, width, height, color_mode = ChunkyPNG::Chunk::Header::COLOR_TRUECOLOR, palette = nil)
|
15
|
+
|
16
|
+
pixel_size = Pixel.bytesize(color_mode)
|
17
|
+
raise "Invalid stream length!" unless stream.length == width * height * pixel_size + height
|
18
|
+
raise "This palette is not suitable for decoding!" if palette && !palette.can_decode?
|
19
|
+
|
20
|
+
pixel_decoder = case color_mode
|
21
|
+
when ChunkyPNG::Chunk::Header::COLOR_TRUECOLOR then lambda { |bytes| ChunkyPNG::Pixel.rgb(*bytes) }
|
22
|
+
when ChunkyPNG::Chunk::Header::COLOR_TRUECOLOR_ALPHA then lambda { |bytes| ChunkyPNG::Pixel.rgba(*bytes) }
|
23
|
+
when ChunkyPNG::Chunk::Header::COLOR_INDEXED then lambda { |bytes| palette[bytes.first] }
|
24
|
+
when ChunkyPNG::Chunk::Header::COLOR_GRAYSCALE then lambda { |bytes| ChunkyPNG::Pixel.grayscale(*bytes) }
|
25
|
+
when ChunkyPNG::Chunk::Header::COLOR_GRAYSCALE_ALPHA then lambda { |bytes| ChunkyPNG::Pixel.grayscale(*bytes) }
|
26
|
+
else raise "No suitable pixel decoder found for color mode #{color_mode}!"
|
27
|
+
end
|
28
|
+
|
29
|
+
pixels = []
|
30
|
+
decoded_bytes = Array.new(width * pixel_size, 0)
|
31
|
+
height.times do |line_no|
|
32
|
+
|
33
|
+
# get bytes of scanline
|
34
|
+
position = line_no * (width * pixel_size + 1)
|
35
|
+
line_length = width * pixel_size
|
36
|
+
bytes = stream.unpack("@#{position}CC#{line_length}")
|
37
|
+
filter = bytes.shift
|
38
|
+
decoded_bytes = decode_scanline(filter, bytes, decoded_bytes, pixel_size)
|
39
|
+
|
40
|
+
# decode bytes into colors
|
41
|
+
decoded_bytes.each_slice(pixel_size) { |bytes| pixels << pixel_decoder.call(bytes) }
|
42
|
+
end
|
43
|
+
|
44
|
+
return ChunkyPNG::PixelMatrix.new(width, height, pixels)
|
45
|
+
end
|
46
|
+
|
47
|
+
protected
|
48
|
+
|
49
|
+
def decode_scanline(filter, bytes, previous_bytes, pixelsize = 3)
|
50
|
+
case filter
|
51
|
+
when FILTER_NONE then decode_scanline_none( bytes, previous_bytes, pixelsize)
|
52
|
+
when FILTER_SUB then decode_scanline_sub( bytes, previous_bytes, pixelsize)
|
53
|
+
when FILTER_UP then decode_scanline_up( bytes, previous_bytes, pixelsize)
|
54
|
+
when FILTER_AVERAGE then raise "Average filter are not yet supported!"
|
55
|
+
when FILTER_PAETH then raise "Paeth filter are not yet supported!"
|
56
|
+
else raise "Unknown filter type"
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
def decode_scanline_none(bytes, previous_bytes, pixelsize = 3)
|
61
|
+
bytes
|
62
|
+
end
|
63
|
+
|
64
|
+
def decode_scanline_sub(bytes, previous_bytes, pixelsize = 3)
|
65
|
+
bytes.each_with_index { |b, i| bytes[i] = (b + (i >= pixelsize ? bytes[i-pixelsize] : 0)) % 256 }
|
66
|
+
bytes
|
67
|
+
end
|
68
|
+
|
69
|
+
def decode_scanline_up(bytes, previous_bytes, pixelsize = 3)
|
70
|
+
bytes.each_with_index { |b, i| bytes[i] = (b + previous_bytes[i]) % 256 }
|
71
|
+
bytes
|
72
|
+
end
|
73
|
+
end
|
74
|
+
end
|
75
|
+
end
|
@@ -0,0 +1,83 @@
|
|
1
|
+
module ChunkyPNG
|
2
|
+
class PixelMatrix
|
3
|
+
module Encoding
|
4
|
+
|
5
|
+
def encode(constraints = {})
|
6
|
+
encoding = determine_encoding(constraints)
|
7
|
+
result = {}
|
8
|
+
result[:header] = { :width => width, :height => height, :color => encoding[:color_mode] }
|
9
|
+
|
10
|
+
if encoding[:color_mode] == ChunkyPNG::Chunk::Header::COLOR_INDEXED
|
11
|
+
result[:palette_chunk] = encoding[:palette].to_plte_chunk
|
12
|
+
result[:transparency_chunk] = encoding[:palette].to_trns_chunk unless encoding[:palette].opaque?
|
13
|
+
end
|
14
|
+
|
15
|
+
result[:pixelstream] = encode_pixelstream(encoding[:color_mode], encoding[:palette])
|
16
|
+
return result
|
17
|
+
end
|
18
|
+
|
19
|
+
protected
|
20
|
+
|
21
|
+
def determine_encoding(constraints = {})
|
22
|
+
encoding = constraints
|
23
|
+
encoding[:palette] ||= palette
|
24
|
+
encoding[:color_mode] ||= encoding[:palette].best_colormode
|
25
|
+
return encoding
|
26
|
+
end
|
27
|
+
|
28
|
+
def encode_pixelstream(color_mode = ChunkyPNG::Chunk::Header::COLOR_TRUECOLOR, palette = nil)
|
29
|
+
|
30
|
+
pixel_encoder = case color_mode
|
31
|
+
when ChunkyPNG::Chunk::Header::COLOR_TRUECOLOR then lambda { |pixel| pixel.to_truecolor_bytes }
|
32
|
+
when ChunkyPNG::Chunk::Header::COLOR_TRUECOLOR_ALPHA then lambda { |pixel| pixel.to_truecolor_alpha_bytes }
|
33
|
+
when ChunkyPNG::Chunk::Header::COLOR_INDEXED then lambda { |pixel| pixel.to_indexed_bytes(palette) }
|
34
|
+
when ChunkyPNG::Chunk::Header::COLOR_GRAYSCALE then lambda { |pixel| pixel.to_grayscale_bytes }
|
35
|
+
when ChunkyPNG::Chunk::Header::COLOR_GRAYSCALE_ALPHA then lambda { |pixel| pixel.to_grayscale_alpha_bytes }
|
36
|
+
else raise "Cannot encode pixels for this mode: #{color_mode}!"
|
37
|
+
end
|
38
|
+
|
39
|
+
raise "This palette is not suitable for encoding!" if palette && !palette.can_encode?
|
40
|
+
|
41
|
+
pixelsize = Pixel.bytesize(color_mode)
|
42
|
+
|
43
|
+
stream = ""
|
44
|
+
previous = nil
|
45
|
+
each_scanline do |line|
|
46
|
+
bytes = line.map(&pixel_encoder).flatten
|
47
|
+
if previous
|
48
|
+
stream << encode_scanline_up(bytes, previous, pixelsize).pack('C*')
|
49
|
+
else
|
50
|
+
stream << encode_scanline_sub(bytes, previous, pixelsize).pack('C*')
|
51
|
+
end
|
52
|
+
previous = bytes
|
53
|
+
end
|
54
|
+
return stream
|
55
|
+
end
|
56
|
+
|
57
|
+
def encode_scanline(filter, bytes, previous_bytes = nil, pixelsize = 3)
|
58
|
+
case filter
|
59
|
+
when FILTER_NONE then encode_scanline_none( bytes, previous_bytes, pixelsize)
|
60
|
+
when FILTER_SUB then encode_scanline_sub( bytes, previous_bytes, pixelsize)
|
61
|
+
when FILTER_UP then encode_scanline_up( bytes, previous_bytes, pixelsize)
|
62
|
+
when FILTER_AVERAGE then raise "Average filter are not yet supported!"
|
63
|
+
when FILTER_PAETH then raise "Paeth filter are not yet supported!"
|
64
|
+
else raise "Unknown filter type"
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
def encode_scanline_none(bytes, previous_bytes = nil, pixelsize = 3)
|
69
|
+
[FILTER_NONE] + bytes
|
70
|
+
end
|
71
|
+
|
72
|
+
def encode_scanline_sub(bytes, previous_bytes = nil, pixelsize = 3)
|
73
|
+
encoded = (pixelsize...bytes.length).map { |n| (bytes[n-pixelsize] - bytes[n]) % 256 }
|
74
|
+
[FILTER_SUB] + bytes[0...pixelsize] + encoded
|
75
|
+
end
|
76
|
+
|
77
|
+
def encode_scanline_up(bytes, previous_bytes, pixelsize = 3)
|
78
|
+
encoded = (0...bytes.length).map { |n| previous_bytes[n] - bytes[n] % 256 }
|
79
|
+
[FILTER_UP] + encoded
|
80
|
+
end
|
81
|
+
end
|
82
|
+
end
|
83
|
+
end
|
@@ -1,22 +1,15 @@
|
|
1
1
|
require File.expand_path('../spec_helper.rb', File.dirname(__FILE__))
|
2
2
|
|
3
|
-
describe ChunkyPNG
|
4
|
-
|
5
|
-
it "should
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
png.palette_chunk.should_not be_nil
|
16
|
-
png.data_chunks.should_not be_empty
|
17
|
-
# `open #{filename}`
|
18
|
-
|
19
|
-
File.unlink(filename)
|
20
|
-
end
|
3
|
+
describe ChunkyPNG do
|
4
|
+
|
5
|
+
# it "should create reference images for all color modes" do
|
6
|
+
# image = ChunkyPNG::Image.new(10, 10, ChunkyPNG::Pixel.rgb(100, 100, 100))
|
7
|
+
# [:indexed, :grayscale, :grayscale_alpha, :truecolor, :truecolor_alpha].each do |color_mode|
|
8
|
+
#
|
9
|
+
# color_mode_id = ChunkyPNG::Chunk::Header.const_get("COLOR_#{color_mode.to_s.upcase}")
|
10
|
+
# filename = resource_file("gray_10x10_#{color_mode}.png")
|
11
|
+
# File.open(filename, 'w') { |f| image.write(f, :color_mode => color_mode_id) }
|
12
|
+
# end
|
13
|
+
# end
|
21
14
|
end
|
22
15
|
|
Binary file
|
Binary file
|
Binary file
|
Binary file
|
Binary file
|
Binary file
|
@@ -0,0 +1,44 @@
|
|
1
|
+
require File.expand_path('../spec_helper.rb', File.dirname(__FILE__))
|
2
|
+
|
3
|
+
describe ChunkyPNG::PixelMatrix::Decoding do
|
4
|
+
include ChunkyPNG::PixelMatrix::Decoding
|
5
|
+
|
6
|
+
describe '#decode_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_scanline(ChunkyPNG::PixelMatrix::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_scanline(ChunkyPNG::PixelMatrix::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_scanline(ChunkyPNG::PixelMatrix::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_scanline(ChunkyPNG::PixelMatrix::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_scanline(ChunkyPNG::PixelMatrix::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_scanline(ChunkyPNG::PixelMatrix::FILTER_UP, bytes, previous_bytes)
|
41
|
+
decoded_bytes.should == [255, 126, 254, 127, 254, 126, 0, 127, 255]
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
require File.expand_path('../spec_helper.rb', File.dirname(__FILE__))
|
2
|
+
|
3
|
+
describe ChunkyPNG::PixelMatrix::Encoding do
|
4
|
+
include ChunkyPNG::PixelMatrix::Encoding
|
5
|
+
|
6
|
+
describe '#encode_scanline' do
|
7
|
+
|
8
|
+
it "should encode a scanline without filtering correctly" do
|
9
|
+
bytes = [0, 0, 0, 1, 1, 1, 2, 2, 2]
|
10
|
+
encoded_bytes = encode_scanline(ChunkyPNG::PixelMatrix::FILTER_NONE, bytes, nil)
|
11
|
+
encoded_bytes.should == [0, 0, 0, 0, 1, 1, 1, 2, 2, 2]
|
12
|
+
end
|
13
|
+
|
14
|
+
it "should encode a scanline with sub filtering correctly" do
|
15
|
+
bytes = [255, 255, 255, 255, 255, 255, 255, 255, 255]
|
16
|
+
encoded_bytes = encode_scanline(ChunkyPNG::PixelMatrix::FILTER_SUB, bytes, nil)
|
17
|
+
encoded_bytes.should == [1, 255, 255, 255, 0, 0, 0, 0, 0, 0]
|
18
|
+
end
|
19
|
+
|
20
|
+
it "should encode a scanline with up filtering correctly" do
|
21
|
+
bytes = [255, 255, 255, 255, 255, 255, 255, 255, 255]
|
22
|
+
previous_bytes = [255, 255, 255, 255, 255, 255, 255, 255, 255]
|
23
|
+
encoded_bytes = encode_scanline(ChunkyPNG::PixelMatrix::FILTER_UP, bytes, previous_bytes)
|
24
|
+
encoded_bytes.should == [2, 0, 0, 0, 0, 0, 0, 0, 0, 0]
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
@@ -2,70 +2,36 @@ require File.expand_path('../spec_helper.rb', File.dirname(__FILE__))
|
|
2
2
|
|
3
3
|
describe ChunkyPNG::PixelMatrix do
|
4
4
|
|
5
|
-
describe '
|
6
|
-
before(:each) do
|
7
|
-
@matrix = ChunkyPNG::PixelMatrix.new(3, 3)
|
8
|
-
end
|
9
|
-
|
10
|
-
it "should decode a line without filtering as is" do
|
11
|
-
bytes = Array.new(@matrix.width * 3, ChunkyPNG::Color.rgb(10, 20, 30))
|
12
|
-
@matrix.decode_scanline(ChunkyPNG::PixelMatrix::FILTER_NONE, bytes, nil).should == bytes
|
13
|
-
end
|
5
|
+
describe '.decode' do
|
14
6
|
|
15
|
-
|
16
|
-
|
17
|
-
bytes = [255, 255, 255, 0, 0, 0, 0, 0, 0]
|
18
|
-
decoded_bytes = @matrix.decode_scanline(ChunkyPNG::PixelMatrix::FILTER_SUB, bytes, nil)
|
19
|
-
decoded_bytes.should == [255, 255, 255, 255, 255, 255, 255, 255, 255]
|
20
|
-
|
21
|
-
# all black pixels
|
22
|
-
bytes = [0, 0, 0, 0, 0, 0, 0, 0, 0]
|
23
|
-
decoded_bytes = @matrix.decode_scanline(ChunkyPNG::PixelMatrix::FILTER_SUB, bytes, nil)
|
24
|
-
decoded_bytes.should == [0, 0, 0, 0, 0, 0, 0, 0, 0]
|
25
|
-
|
26
|
-
# various colors
|
27
|
-
bytes = [255, 0, 45, 0, 255, 0, 112, 200, 178]
|
28
|
-
decoded_bytes = @matrix.decode_scanline(ChunkyPNG::PixelMatrix::FILTER_SUB, bytes, nil)
|
29
|
-
decoded_bytes.should == [255, 0, 45, 255, 255, 45, 111, 199, 223]
|
7
|
+
before(:each) do
|
8
|
+
@reference = ChunkyPNG::PixelMatrix.new(10, 10, ChunkyPNG::Pixel.rgb(100, 100, 100))
|
30
9
|
end
|
31
10
|
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
decoded_bytes.should == [255, 255, 255, 127, 127, 127, 0, 0, 0]
|
38
|
-
|
39
|
-
# previous line has various pixels
|
40
|
-
previous_bytes = [255, 255, 255, 127, 127, 127, 0, 0, 0]
|
41
|
-
bytes = [0, 127, 255, 0, 127, 255, 0, 127, 255]
|
42
|
-
decoded_bytes = @matrix.decode_scanline(ChunkyPNG::PixelMatrix::FILTER_UP, bytes, previous_bytes)
|
43
|
-
decoded_bytes.should == [255, 126, 254, 127, 254, 126, 0, 127, 255]
|
11
|
+
[:indexed, :grayscale, :grayscale_alpha, :truecolor, :truecolor_alpha].each do |color_mode|
|
12
|
+
it "should decode an image with color mode #{color_mode} correctly" do
|
13
|
+
ds = ChunkyPNG.load(resource_file("gray_10x10_#{color_mode}.png"))
|
14
|
+
ds.pixel_matrix.should == @reference
|
15
|
+
end
|
44
16
|
end
|
45
17
|
end
|
46
18
|
|
47
|
-
describe '
|
19
|
+
describe '.encode' do
|
48
20
|
before(:each) do
|
49
|
-
@
|
50
|
-
end
|
51
|
-
|
52
|
-
it "should encode a scanline without filtering correctly" do
|
53
|
-
bytes = [0, 0, 0, 1, 1, 1, 2, 2, 2]
|
54
|
-
encoded_bytes = @matrix.encode_scanline(ChunkyPNG::PixelMatrix::FILTER_NONE, bytes, nil, nil)
|
55
|
-
encoded_bytes.should == [0, 0, 0, 0, 1, 1, 1, 2, 2, 2]
|
21
|
+
@reference = ChunkyPNG::PixelMatrix.new(10, 10, ChunkyPNG::Pixel.rgb(100, 100, 100))
|
56
22
|
end
|
57
23
|
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
24
|
+
[:indexed, :grayscale, :grayscale_alpha, :truecolor, :truecolor_alpha].each do |color_mode|
|
25
|
+
it "should encode an image with color mode #{color_mode} correctly" do
|
26
|
+
|
27
|
+
filename = resource_file("_tmp_#{color_mode}.png")
|
28
|
+
File.open(filename, 'w') { |f| @reference.to_datastream.write(f) }
|
29
|
+
|
30
|
+
ChunkyPNG.load(filename).pixel_matrix.should == @reference
|
31
|
+
|
32
|
+
File.unlink(filename)
|
33
|
+
end
|
62
34
|
end
|
63
35
|
|
64
|
-
it "should encode a scanline with up filtering correctly" do
|
65
|
-
bytes = [255, 255, 255, 255, 255, 255, 255, 255, 255]
|
66
|
-
previous_bytes = [255, 255, 255, 255, 255, 255, 255, 255, 255]
|
67
|
-
encoded_bytes = @matrix.encode_scanline(ChunkyPNG::PixelMatrix::FILTER_UP, bytes, previous_bytes, nil)
|
68
|
-
encoded_bytes.should == [2, 0, 0, 0, 0, 0, 0, 0, 0, 0]
|
69
|
-
end
|
70
36
|
end
|
71
37
|
end
|
@@ -0,0 +1,32 @@
|
|
1
|
+
require File.expand_path('../spec_helper.rb', File.dirname(__FILE__))
|
2
|
+
|
3
|
+
describe ChunkyPNG::Pixel do
|
4
|
+
|
5
|
+
before(:each) do
|
6
|
+
@white = ChunkyPNG::Pixel.rgba(255, 255, 255, 255)
|
7
|
+
@black = ChunkyPNG::Pixel.rgba( 0, 0, 0, 255)
|
8
|
+
@opaque = ChunkyPNG::Pixel.rgba( 10, 100, 150, 255)
|
9
|
+
@non_opaque = ChunkyPNG::Pixel.rgba( 10, 100, 150, 100)
|
10
|
+
@fully_transparent = ChunkyPNG::Pixel.rgba( 10, 100, 150, 0)
|
11
|
+
end
|
12
|
+
|
13
|
+
it "should represent pixels as the correct number" do
|
14
|
+
@white.value.should == 0xffffffff
|
15
|
+
@black.value.should == 0x000000ff
|
16
|
+
@opaque.value.should == 0x0a6496ff
|
17
|
+
end
|
18
|
+
|
19
|
+
it "should correctly check for opaqueness" do
|
20
|
+
@white.should be_opaque
|
21
|
+
@black.should be_opaque
|
22
|
+
@opaque.should be_opaque
|
23
|
+
@non_opaque.should_not be_opaque
|
24
|
+
@fully_transparent.should_not be_opaque
|
25
|
+
end
|
26
|
+
|
27
|
+
it "should convert the individual color values back correctly" do
|
28
|
+
@opaque.to_truecolor_bytes.should == [10, 100, 150]
|
29
|
+
@non_opaque.to_truecolor_alpha_bytes.should == [10, 100, 150, 100]
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: chunky_png
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.0.
|
4
|
+
version: 0.0.2
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Willem van Bergen
|
@@ -9,7 +9,7 @@ autorequire:
|
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
11
|
|
12
|
-
date: 2010-01-
|
12
|
+
date: 2010-01-12 00:00:00 +01:00
|
13
13
|
default_executable:
|
14
14
|
dependencies:
|
15
15
|
- !ruby/object:Gem::Dependency
|
@@ -33,19 +33,30 @@ extra_rdoc_files:
|
|
33
33
|
- README.rdoc
|
34
34
|
files:
|
35
35
|
- spec/spec_helper.rb
|
36
|
+
- spec/resources/gray_10x10_grayscale.png
|
37
|
+
- spec/resources/gray_10x10.png
|
36
38
|
- .gitignore
|
39
|
+
- spec/resources/gray_10x10_truecolor_alpha.png
|
37
40
|
- lib/chunky_png/pixel_matrix.rb
|
38
41
|
- LICENSE
|
42
|
+
- spec/resources/gray_10x10_truecolor.png
|
43
|
+
- lib/chunky_png/pixel_matrix/decoding.rb
|
39
44
|
- lib/chunky_png/chunk.rb
|
45
|
+
- spec/unit/encoding_spec.rb
|
40
46
|
- Rakefile
|
41
47
|
- README.rdoc
|
42
|
-
- spec/resources/
|
48
|
+
- spec/resources/gray_10x10_indexed.png
|
43
49
|
- spec/integration/image_spec.rb
|
50
|
+
- lib/chunky_png/pixel.rb
|
44
51
|
- lib/chunky_png/palette.rb
|
45
52
|
- lib/chunky_png/datastream.rb
|
46
53
|
- chunky_png.gemspec
|
47
54
|
- tasks/github-gem.rake
|
55
|
+
- spec/unit/decoding_spec.rb
|
56
|
+
- spec/resources/gray_10x10_grayscale_alpha.png
|
57
|
+
- lib/chunky_png/pixel_matrix/encoding.rb
|
48
58
|
- lib/chunky_png/image.rb
|
59
|
+
- spec/unit/pixel_spec.rb
|
49
60
|
- spec/unit/pixel_matrix_spec.rb
|
50
61
|
- lib/chunky_png.rb
|
51
62
|
has_rdoc: true
|
@@ -82,5 +93,8 @@ signing_key:
|
|
82
93
|
specification_version: 3
|
83
94
|
summary: Pure ruby library for read/write, chunk-level access to PNG files
|
84
95
|
test_files:
|
96
|
+
- spec/unit/encoding_spec.rb
|
85
97
|
- spec/integration/image_spec.rb
|
98
|
+
- spec/unit/decoding_spec.rb
|
99
|
+
- spec/unit/pixel_spec.rb
|
86
100
|
- spec/unit/pixel_matrix_spec.rb
|
Binary file
|