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