chunky_png 0.0.2 → 0.0.3
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/README.rdoc +38 -9
- data/chunky_png.gemspec +3 -3
- data/lib/chunky_png.rb +27 -0
- data/lib/chunky_png/chunk.rb +4 -10
- data/lib/chunky_png/image.rb +25 -5
- data/lib/chunky_png/palette.rb +95 -12
- data/lib/chunky_png/pixel.rb +17 -5
- data/lib/chunky_png/pixel_matrix.rb +45 -15
- data/lib/chunky_png/pixel_matrix/decoding.rb +112 -28
- data/lib/chunky_png/pixel_matrix/encoding.rb +37 -31
- data/spec/resources/16x16_interlaced.png +0 -0
- data/spec/resources/16x16_non_interlaced.png +0 -0
- data/spec/resources/adam7.png +0 -0
- data/spec/resources/transparent_gray_10x10.png +0 -0
- data/spec/unit/decoding_spec.rb +45 -6
- data/spec/unit/encoding_spec.rb +3 -3
- data/spec/unit/pixel_matrix_spec.rb +14 -5
- data/tasks/github-gem.rake +6 -1
- metadata +6 -2
data/.gitignore
CHANGED
data/README.rdoc
CHANGED
@@ -1,19 +1,46 @@
|
|
1
1
|
= Chunky PNG
|
2
2
|
|
3
3
|
This library can read and write PNG files. It is written in pure Ruby for
|
4
|
-
maximum portability.
|
4
|
+
maximum portability. Let me rephrase: it does NOT require RMagick or any other
|
5
|
+
memory leaking image library.
|
6
|
+
|
7
|
+
Source code:: http://github.com/wvanbergen/chunky_png/tree
|
8
|
+
RDoc:: http://rdoc.info/projects/wvanbergen/chunky_png
|
9
|
+
Wiki:: http://wiki.github.com/wvanbergen/chunky_png
|
10
|
+
Issue tracker:: http://github.com/wvanbergen/chunky_png/issues
|
11
|
+
|
12
|
+
== Features
|
13
|
+
|
14
|
+
* Decodes almost any image that the PNG standard allows, except for images
|
15
|
+
that use a different color depth than 8 bits. This includes all standard
|
16
|
+
color modes and all transparency, interlacing and filtering options.
|
17
|
+
* Encodes images supports all color modes (true color, grayscale and indexed)
|
18
|
+
and transparency for all these color modes. The best color mode will be
|
19
|
+
chosen automatically, based on the image's colors.
|
20
|
+
* R/W access to the image's pixels.
|
21
|
+
* R/W access to all image metadata that is stored in chunks.
|
22
|
+
|
23
|
+
== Classes
|
24
|
+
|
25
|
+
The main classes used within ChunkyPNG are:
|
26
|
+
|
27
|
+
<tt>ChunkyPNG::Image</tt> :: create PNG images from scratch or based on another PNG image.
|
28
|
+
<tt>ChunkyPNG::Datastream</tt> :: low-level read and write access to PNG images from or to a file or stream.
|
29
|
+
<tt>ChunkyPNG::PixelMatrix</tt> :: represents an images as a matrix of pixels.
|
30
|
+
<tt>ChunkyPNG::Pixel</tt> :: represents a single pixel or color value.
|
5
31
|
|
6
32
|
== Usage
|
7
33
|
|
8
34
|
require 'chunky_png'
|
9
35
|
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
36
|
+
# Creating an image from scratch
|
37
|
+
png = ChunkyPNG::Image.new(16, 16, ChunkyPNG::Pixel::TRANSPARENT)
|
38
|
+
png[1,1] = ChunkyPNG::Pixel.rgba(255, 255, 255, 128)
|
39
|
+
png[2,1] = ChunkyPNG::Pixel.rgba(255, 255, 255, 128)
|
40
|
+
png.save('filename.png')
|
41
|
+
|
42
|
+
# Modify an image - TODO
|
43
|
+
# Inspecting metadata - TODO
|
17
44
|
|
18
45
|
(Note: this is subject to change while work is in progress)
|
19
46
|
|
@@ -21,4 +48,6 @@ maximum portability. It requires the standard Zlib library.
|
|
21
48
|
|
22
49
|
The library is written by Willem van Bergen for Floorplanner.com, and released
|
23
50
|
under the MIT license (see LICENSE). Please contact me for questions or
|
24
|
-
remarks. Patches are greatly appreciated! :-)
|
51
|
+
remarks. Patches are greatly appreciated! :-)
|
52
|
+
|
53
|
+
P.S.: _why is why this library is so chunky.
|
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.3"
|
7
|
+
s.date = "2010-01-13"
|
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 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)
|
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 spec/resources/transparent_gray_10x10.png README.rdoc spec/resources/gray_10x10_indexed.png spec/resources/16x16_non_interlaced.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 spec/resources/16x16_interlaced.png spec/resources/adam7.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
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
@@ -10,9 +10,36 @@ require 'chunky_png/pixel_matrix/decoding'
|
|
10
10
|
require 'chunky_png/pixel_matrix'
|
11
11
|
require 'chunky_png/image'
|
12
12
|
|
13
|
+
# ChunkyPNG
|
14
|
+
#
|
15
|
+
# The ChunkyPNG module defines some constants that are used in the
|
16
|
+
# PNG specification.
|
13
17
|
module ChunkyPNG
|
14
18
|
extend self
|
15
19
|
|
20
|
+
###################################################
|
21
|
+
# PNG international standard defined constants
|
22
|
+
###################################################
|
23
|
+
|
24
|
+
COLOR_GRAYSCALE = 0
|
25
|
+
COLOR_TRUECOLOR = 2
|
26
|
+
COLOR_INDEXED = 3
|
27
|
+
COLOR_GRAYSCALE_ALPHA = 4
|
28
|
+
COLOR_TRUECOLOR_ALPHA = 6
|
29
|
+
|
30
|
+
FILTERING_DEFAULT = 0
|
31
|
+
|
32
|
+
COMPRESSION_DEFAULT = 0
|
33
|
+
|
34
|
+
INTERLACING_NONE = 0
|
35
|
+
INTERLACING_ADAM7 = 1
|
36
|
+
|
37
|
+
FILTER_NONE = 0
|
38
|
+
FILTER_SUB = 1
|
39
|
+
FILTER_UP = 2
|
40
|
+
FILTER_AVERAGE = 3
|
41
|
+
FILTER_PAETH = 4
|
42
|
+
|
16
43
|
def load_from_io(io)
|
17
44
|
ChunkyPNG::Datastream.read(io)
|
18
45
|
end
|
data/lib/chunky_png/chunk.rb
CHANGED
@@ -45,21 +45,15 @@ module ChunkyPNG
|
|
45
45
|
|
46
46
|
class Header < Base
|
47
47
|
|
48
|
-
COLOR_GRAYSCALE = 0
|
49
|
-
COLOR_TRUECOLOR = 2
|
50
|
-
COLOR_INDEXED = 3
|
51
|
-
COLOR_GRAYSCALE_ALPHA = 4
|
52
|
-
COLOR_TRUECOLOR_ALPHA = 6
|
53
|
-
|
54
48
|
attr_accessor :width, :height, :depth, :color, :compression, :filtering, :interlace
|
55
49
|
|
56
50
|
def initialize(attrs = {})
|
57
51
|
super('IHDR', attrs)
|
58
52
|
@depth ||= 8
|
59
|
-
@color ||= COLOR_TRUECOLOR
|
60
|
-
@compression ||=
|
61
|
-
@filtering ||=
|
62
|
-
@interlace ||=
|
53
|
+
@color ||= ChunkyPNG::COLOR_TRUECOLOR
|
54
|
+
@compression ||= ChunkyPNG::COMPRESSION_DEFAULT
|
55
|
+
@filtering ||= ChunkyPNG::FILTERING_DEFAULT
|
56
|
+
@interlace ||= ChunkyPNG::INTERLACING_NONE
|
63
57
|
end
|
64
58
|
|
65
59
|
def self.read(type, content)
|
data/lib/chunky_png/image.rb
CHANGED
@@ -1,23 +1,43 @@
|
|
1
1
|
module ChunkyPNG
|
2
2
|
class Image
|
3
3
|
|
4
|
-
attr_reader :
|
4
|
+
attr_reader :pixel_matrix
|
5
5
|
|
6
6
|
def initialize(width, height, background_color = ChunkyPNG::Pixel::TRANSPARENT)
|
7
|
-
@
|
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)
|
8
12
|
end
|
9
13
|
|
10
14
|
def width
|
11
|
-
|
15
|
+
pixel_matrix.width
|
12
16
|
end
|
13
17
|
|
14
18
|
def height
|
15
|
-
|
19
|
+
pixel_matrix.height
|
20
|
+
end
|
21
|
+
|
22
|
+
def [](x, y)
|
23
|
+
pixel_matrix[x,y]
|
24
|
+
end
|
25
|
+
|
26
|
+
def []=(x, y, pixel)
|
27
|
+
pixel_matrix[x,y] = pixel
|
28
|
+
end
|
29
|
+
|
30
|
+
def pixels
|
31
|
+
pixel_matrix.pixels
|
16
32
|
end
|
17
33
|
|
18
34
|
def write(io, constraints = {})
|
19
|
-
datastream =
|
35
|
+
datastream = pixel_matrix.to_datastream(constraints)
|
20
36
|
datastream.write(io)
|
21
37
|
end
|
38
|
+
|
39
|
+
def save(filename, constraints = {})
|
40
|
+
File.open(filename, 'w') { |io| write(io, constraints) }
|
41
|
+
end
|
22
42
|
end
|
23
43
|
end
|
data/lib/chunky_png/palette.rb
CHANGED
@@ -1,16 +1,36 @@
|
|
1
1
|
module ChunkyPNG
|
2
|
-
|
3
|
-
|
4
|
-
#
|
5
|
-
|
6
|
-
|
2
|
+
|
3
|
+
# A palette describes the set of colors that is being used for an image.
|
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.
|
7
|
+
#
|
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.
|
10
|
+
#
|
11
|
+
# @see ChunkyPNG::Pixel
|
7
12
|
class Palette < SortedSet
|
8
13
|
|
9
|
-
|
14
|
+
# Builds a new palette given a set (Enumerable instance) of colors.
|
15
|
+
#
|
16
|
+
# @param [Enumerbale<ChunkyPNG::Pixel>] enum The set of colors to include in this palette.
|
17
|
+
# This Enumerbale can contains duplicates.
|
18
|
+
# @param [Array] decoding_map An array of colors in the exact order at which
|
19
|
+
# they appeared in the palette chunk, so that this array can be used for decoding.
|
20
|
+
def initialize(enum, decoding_map = nil)
|
10
21
|
super(enum)
|
11
|
-
@decoding_map =
|
22
|
+
@decoding_map = decoding_map if decoding_map
|
12
23
|
end
|
13
24
|
|
25
|
+
# Builds a palette instance from a PLTE chunk and optionally a tRNS chunk
|
26
|
+
# from a PNG datastream.
|
27
|
+
#
|
28
|
+
# This method will cerate a palette that is suitable for decoding an image.
|
29
|
+
#
|
30
|
+
# @param [ChunkyPNG::Chunk::Palette] The palette chunk to load from
|
31
|
+
# @param [ChunkyPNG::Chunk::Transparency, nil] The optional transparency chunk.
|
32
|
+
# @return [ChunkyPNG::Palette] The loaded palette instance.
|
33
|
+
# @see ChunkyPNG::Palette#can_decode?
|
14
34
|
def self.from_chunks(palette_chunk, transparency_chunk = nil)
|
15
35
|
return nil if palette_chunk.nil?
|
16
36
|
|
@@ -31,45 +51,99 @@ module ChunkyPNG
|
|
31
51
|
index += 1
|
32
52
|
end
|
33
53
|
|
34
|
-
self.new(decoding_map)
|
54
|
+
self.new(decoding_map, decoding_map)
|
35
55
|
end
|
36
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.
|
59
|
+
# @return [ChunkyPNG::Palette] The palette instance.
|
37
60
|
def self.from_pixel_matrix(pixel_matrix)
|
38
61
|
self.new(pixel_matrix.pixels)
|
39
62
|
end
|
40
63
|
|
64
|
+
# 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
|
66
|
+
# @return [ChunkyPNG::Palette] The palette instance.
|
41
67
|
def self.from_pixels(pixels)
|
42
68
|
self.new(pixels)
|
43
69
|
end
|
44
70
|
|
71
|
+
# Checks whether the size of this palette is suitable for indexed storage.
|
72
|
+
# @return [true, false] True if the number of colors in this palette is less than 256.
|
45
73
|
def indexable?
|
46
74
|
size < 256
|
47
75
|
end
|
48
76
|
|
77
|
+
# Check whether this pelette only contains opaque colors.
|
78
|
+
# @return [true, false] True if all colors in this palette are opaque.
|
79
|
+
# @see ChunkyPNG::Pixel#opaque?
|
49
80
|
def opaque?
|
50
81
|
all? { |pixel| pixel.opaque? }
|
51
82
|
end
|
83
|
+
|
84
|
+
# Check whether this pelette only contains grayscale colors.
|
85
|
+
# @return [true, false] True if all colors in this palette are grayscale teints.
|
86
|
+
# @see ChunkyPNG::Pixel#grayscale??
|
87
|
+
def grayscale?
|
88
|
+
all? { |pixel| pixel.grayscale? }
|
89
|
+
end
|
52
90
|
|
91
|
+
# Checks whether this palette is suitable for decoding an image from a datastream.
|
92
|
+
#
|
93
|
+
# This requires that the positions of the colors in the original palette chunk is known,
|
94
|
+
# which is stored as an array in the +@decoding_map+ instance variable.
|
95
|
+
#
|
96
|
+
# @return [true, false] True if a decoding map was built when this palette was loaded.
|
53
97
|
def can_decode?
|
54
98
|
!@decoding_map.nil?
|
55
99
|
end
|
56
100
|
|
101
|
+
# Checks whether this palette is suitable for encoding an image from to datastream.
|
102
|
+
#
|
103
|
+
# This requires that the position of the color in the future palette chunk is known,
|
104
|
+
# which is stored as a hash in the +@encoding_map+ instance variable.
|
105
|
+
#
|
106
|
+
# @return [true, false] True if a encoding map was built when this palette was loaded.
|
57
107
|
def can_encode?
|
58
108
|
!@encoding_map.nil?
|
59
109
|
end
|
60
110
|
|
111
|
+
# Returns a color, given the position in the original palette chunk.
|
112
|
+
# @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
|
114
|
+
# @see ChunkyPNG::Palette#can_decode?
|
61
115
|
def [](index)
|
62
116
|
@decoding_map[index]
|
63
117
|
end
|
64
118
|
|
119
|
+
# Returns the position of a color in the palette
|
120
|
+
# @param [ChunkyPNG::Pixel] color The color for which to look up the index.
|
121
|
+
# @return [Fixnum] The 0-based position of the color in the palette.
|
122
|
+
# @see ChunkyPNG::Palette#can_encode?
|
65
123
|
def index(color)
|
66
124
|
@encoding_map[color]
|
67
125
|
end
|
68
126
|
|
127
|
+
# Creates a tRNS chunk that corresponds with this palette to store the
|
128
|
+
# alpha channel of all colors.
|
129
|
+
#
|
130
|
+
# Note that this chunk can be left out of every color in the palette is
|
131
|
+
# opaque, and the image is encoded using indexed colors.
|
132
|
+
#
|
133
|
+
# @return [ChunkyPNG::Chunk::Transparency] The tRNS chunk.
|
69
134
|
def to_trns_chunk
|
70
135
|
ChunkyPNG::Chunk::Transparency.new('tRNS', map(&:a).pack('C*'))
|
71
136
|
end
|
72
137
|
|
138
|
+
# Creates a PLTE chunk that corresponds with this palette to store the
|
139
|
+
# r, g and b channels of all colors.
|
140
|
+
#
|
141
|
+
# Note that a PLTE chunk should only be included if the image is
|
142
|
+
# encoded using index colors. After this chunk has been built, the
|
143
|
+
# palette becomes suitable for encoding an image.
|
144
|
+
#
|
145
|
+
# @return [ChunkyPNG::Chunk::Palette] The PLTE chunk.
|
146
|
+
# @see ChunkyPNG::Palette#can_encode?
|
73
147
|
def to_plte_chunk
|
74
148
|
@encoding_map = {}
|
75
149
|
colors = []
|
@@ -82,13 +156,22 @@ module ChunkyPNG
|
|
82
156
|
ChunkyPNG::Chunk::Palette.new('PLTE', colors.pack('C*'))
|
83
157
|
end
|
84
158
|
|
159
|
+
# Determines the most suitable colormode for this palette.
|
160
|
+
# @return [Fixnum] The colormode which would create the smalles possible
|
161
|
+
# file for images that use this exact palette.
|
85
162
|
def best_colormode
|
86
|
-
if
|
87
|
-
|
163
|
+
if grayscale?
|
164
|
+
if opaque?
|
165
|
+
ChunkyPNG::COLOR_GRAYSCALE
|
166
|
+
else
|
167
|
+
ChunkyPNG::COLOR_GRAYSCALE_ALPHA
|
168
|
+
end
|
169
|
+
elsif indexable?
|
170
|
+
ChunkyPNG::COLOR_INDEXED
|
88
171
|
elsif opaque?
|
89
|
-
ChunkyPNG::
|
172
|
+
ChunkyPNG::COLOR_TRUECOLOR
|
90
173
|
else
|
91
|
-
ChunkyPNG::
|
174
|
+
ChunkyPNG::COLOR_TRUECOLOR_ALPHA
|
92
175
|
end
|
93
176
|
end
|
94
177
|
end
|
data/lib/chunky_png/pixel.rb
CHANGED
@@ -48,16 +48,28 @@ module ChunkyPNG
|
|
48
48
|
a == 0x000000ff
|
49
49
|
end
|
50
50
|
|
51
|
+
def grayscale?
|
52
|
+
r == g && r == b
|
53
|
+
end
|
54
|
+
|
51
55
|
def inspect
|
52
56
|
'#%08x' % @value
|
53
57
|
end
|
54
58
|
|
59
|
+
def hash
|
60
|
+
@value.hash
|
61
|
+
end
|
62
|
+
|
55
63
|
def eql?(other)
|
56
64
|
other.kind_of?(self.class) && other.value == self.value
|
57
65
|
end
|
58
66
|
|
59
67
|
alias :== :eql?
|
60
68
|
|
69
|
+
def <=>(other)
|
70
|
+
other.value <=> self.value
|
71
|
+
end
|
72
|
+
|
61
73
|
def to_truecolor_alpha_bytes
|
62
74
|
[r,g,b,a]
|
63
75
|
end
|
@@ -89,11 +101,11 @@ module ChunkyPNG
|
|
89
101
|
|
90
102
|
def self.bytesize(color_mode)
|
91
103
|
case color_mode
|
92
|
-
when ChunkyPNG::
|
93
|
-
when ChunkyPNG::
|
94
|
-
when ChunkyPNG::
|
95
|
-
when ChunkyPNG::
|
96
|
-
when ChunkyPNG::
|
104
|
+
when ChunkyPNG::COLOR_INDEXED then 1
|
105
|
+
when ChunkyPNG::COLOR_TRUECOLOR then 3
|
106
|
+
when ChunkyPNG::COLOR_TRUECOLOR_ALPHA then 4
|
107
|
+
when ChunkyPNG::COLOR_GRAYSCALE then 1
|
108
|
+
when ChunkyPNG::COLOR_GRAYSCALE_ALPHA then 2
|
97
109
|
else raise "Don't know the bytesize of pixels in this colormode: #{color_mode}!"
|
98
110
|
end
|
99
111
|
end
|
@@ -1,19 +1,38 @@
|
|
1
1
|
module ChunkyPNG
|
2
2
|
|
3
|
+
# The ChunkPNG::PixelMatrix class represents a matrix of pixels of which an i
|
4
|
+
# mage consists. This class supports loading a PixelMatrix from a PNG datastream,
|
5
|
+
# and creating a PNG datastream bse don this matrix.
|
6
|
+
#
|
7
|
+
# This class offers per-pixel access to the matrix by using x,y coordinates. It uses
|
8
|
+
# a palette (see {ChunkyPNG::Palette}) to keep track of the different colors used in
|
9
|
+
# this matrix.
|
10
|
+
#
|
11
|
+
# @see ChunkyPNG::Datastream
|
3
12
|
class PixelMatrix
|
4
13
|
|
5
14
|
include Encoding
|
6
15
|
extend Decoding
|
7
16
|
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
attr_reader :width, :height, :pixels
|
17
|
+
# @return [Integer] The number of columns in this pixel matrix
|
18
|
+
attr_reader :width
|
19
|
+
|
20
|
+
# @return [Integer] The number of rows in this pixel matrix
|
21
|
+
attr_reader :height
|
15
22
|
|
23
|
+
# @return [Array<ChunkyPNG::Pixel>] The list of pixels in this matrix.
|
24
|
+
# This array always should have +width * height+ elements.
|
25
|
+
attr_reader :pixels
|
16
26
|
|
27
|
+
# Initializes a new PixelMatrix instance
|
28
|
+
# @param [Integer] width The width in pixels of this matrix
|
29
|
+
# @param [Integer] width The height in pixels of this matrix
|
30
|
+
# @param [ChunkyPNG::Pixel, Array<ChunkyPNG::Pixel>] initial The initial value of te pixels:
|
31
|
+
#
|
32
|
+
# * If a color is passed to this parameter, this color will be used as background color.
|
33
|
+
#
|
34
|
+
# * If an array of pixels is provided, these pixels will be used as initial value. Note
|
35
|
+
# that the amount of pixels in this array should equal +width * height+.
|
17
36
|
def initialize(width, height, initial = ChunkyPNG::Pixel::TRANSPARENT)
|
18
37
|
|
19
38
|
@width, @height = width, height
|
@@ -27,14 +46,24 @@ module ChunkyPNG
|
|
27
46
|
end
|
28
47
|
end
|
29
48
|
|
49
|
+
# Replaces a single pixel in this matrix.
|
50
|
+
# @param [Integer] x The x-coordinate of the pixel (column)
|
51
|
+
# @param [Integer] y The y-coordinate of the pixel (row)
|
52
|
+
# @param [ChunkyPNG::Pixel] pixel The new pixel for the provided coordinates.
|
30
53
|
def []=(x, y, pixel)
|
31
54
|
@pixels[y * width + x] = pixel
|
32
55
|
end
|
33
56
|
|
57
|
+
# Returns a single pixel from this matrix.
|
58
|
+
# @param [Integer] x The x-coordinate of the pixel (column)
|
59
|
+
# @param [Integer] y The y-coordinate of the pixel (row)
|
60
|
+
# @return [ChunkyPNG::Pixel] The current pixel at the provided coordinates.
|
34
61
|
def [](x, y)
|
35
62
|
@pixels[y * width + x]
|
36
63
|
end
|
37
64
|
|
65
|
+
# Passes to this matrix of pixels line by line.
|
66
|
+
# @yield [Array<ChunkyPNG::Pixel>] An line of pixels
|
38
67
|
def each_scanline(&block)
|
39
68
|
height.times do |i|
|
40
69
|
scanline = @pixels[width * i, width]
|
@@ -42,18 +71,15 @@ module ChunkyPNG
|
|
42
71
|
end
|
43
72
|
end
|
44
73
|
|
74
|
+
# Returns the palette used for this pixel matrix.
|
75
|
+
# @return [ChunkyPNG::Palette] A pallete which contains all the colors that are
|
76
|
+
# being used for this image.
|
45
77
|
def palette
|
46
78
|
ChunkyPNG::Palette.from_pixels(@pixels)
|
47
79
|
end
|
48
80
|
|
49
|
-
|
50
|
-
|
51
|
-
end
|
52
|
-
|
53
|
-
def indexable?
|
54
|
-
palette.indexable?
|
55
|
-
end
|
56
|
-
|
81
|
+
# Converts this PixelMatrix to a datastream, so that it can be saved as a PNG image.
|
82
|
+
# @param [Hash] constraints The constraints to use when encoding the matrix.
|
57
83
|
def to_datastream(constraints = {})
|
58
84
|
data = encode(constraints)
|
59
85
|
ds = Datastream.new
|
@@ -65,6 +91,10 @@ module ChunkyPNG
|
|
65
91
|
return ds
|
66
92
|
end
|
67
93
|
|
94
|
+
# Equality check to compare this pixel matrix with other matrices.
|
95
|
+
# @param other The object to compare this Matrix to.
|
96
|
+
# @return [true, false] True if the size and pixel values of the other matrix
|
97
|
+
# are exactly the same as this matrix size and pixel values.
|
68
98
|
def eql?(other)
|
69
99
|
other.kind_of?(self.class) && other.pixels == self.pixels &&
|
70
100
|
other.width == self.width && other.height == self.height
|
@@ -3,56 +3,116 @@ module ChunkyPNG
|
|
3
3
|
module Decoding
|
4
4
|
|
5
5
|
def decode(ds)
|
6
|
+
raise "Only 8-bit color depth is currently supported by ChunkyPNG!" unless ds.header_chunk.depth == 8
|
7
|
+
|
6
8
|
stream = Zlib::Inflate.inflate(ds.data_chunks.map(&:content).join(''))
|
7
9
|
width = ds.header_chunk.width
|
8
10
|
height = ds.header_chunk.height
|
9
11
|
color_mode = ds.header_chunk.color
|
12
|
+
interlace = ds.header_chunk.interlace
|
10
13
|
palette = ChunkyPNG::Palette.from_chunks(ds.palette_chunk, ds.transparency_chunk)
|
11
|
-
decode_pixelstream(stream, width, height, color_mode, palette)
|
14
|
+
decode_pixelstream(stream, width, height, color_mode, palette, interlace)
|
12
15
|
end
|
13
16
|
|
14
|
-
def decode_pixelstream(stream, width, height, color_mode = ChunkyPNG::
|
15
|
-
|
16
|
-
pixel_size = Pixel.bytesize(color_mode)
|
17
|
-
raise "Invalid stream length!" unless stream.length == width * height * pixel_size + height
|
17
|
+
def decode_pixelstream(stream, width, height, color_mode = ChunkyPNG::COLOR_TRUECOLOR, palette = nil, interlace = ChunkyPNG::INTERLACING_NONE)
|
18
18
|
raise "This palette is not suitable for decoding!" if palette && !palette.can_decode?
|
19
|
-
|
19
|
+
|
20
|
+
pixel_size = Pixel.bytesize(color_mode)
|
20
21
|
pixel_decoder = case color_mode
|
21
|
-
when ChunkyPNG::
|
22
|
-
when ChunkyPNG::
|
23
|
-
when ChunkyPNG::
|
24
|
-
when ChunkyPNG::
|
25
|
-
when ChunkyPNG::
|
22
|
+
when ChunkyPNG::COLOR_TRUECOLOR then lambda { |bytes| ChunkyPNG::Pixel.rgb(*bytes) }
|
23
|
+
when ChunkyPNG::COLOR_TRUECOLOR_ALPHA then lambda { |bytes| ChunkyPNG::Pixel.rgba(*bytes) }
|
24
|
+
when ChunkyPNG::COLOR_INDEXED then lambda { |bytes| palette[bytes.first] }
|
25
|
+
when ChunkyPNG::COLOR_GRAYSCALE then lambda { |bytes| ChunkyPNG::Pixel.grayscale(*bytes) }
|
26
|
+
when ChunkyPNG::COLOR_GRAYSCALE_ALPHA then lambda { |bytes| ChunkyPNG::Pixel.grayscale(*bytes) }
|
26
27
|
else raise "No suitable pixel decoder found for color mode #{color_mode}!"
|
27
28
|
end
|
28
29
|
|
30
|
+
pixels = case interlace
|
31
|
+
when ChunkyPNG::INTERLACING_NONE then decode_interlacing_none(stream, width, height, pixel_size, pixel_decoder)
|
32
|
+
when ChunkyPNG::INTERLACING_ADAM7 then decode_interlacing_adam7(stream, width, height, pixel_size, pixel_decoder)
|
33
|
+
else raise "Don't know how the handle interlacing method #{interlace}!"
|
34
|
+
end
|
35
|
+
|
36
|
+
return ChunkyPNG::PixelMatrix.new(width, height, pixels)
|
37
|
+
end
|
38
|
+
|
39
|
+
protected
|
40
|
+
|
41
|
+
def decode_image_pass(stream, width, height, pixel_size, pixel_decoder, start_pos = 0)
|
29
42
|
pixels = []
|
30
43
|
decoded_bytes = Array.new(width * pixel_size, 0)
|
31
44
|
height.times do |line_no|
|
32
45
|
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
decoded_bytes.each_slice(pixel_size) { |bytes| pixels << pixel_decoder.call(bytes) }
|
42
|
-
end
|
46
|
+
if width > 0
|
47
|
+
|
48
|
+
# get bytes of scanline
|
49
|
+
position = start_pos + line_no * (width * pixel_size + 1)
|
50
|
+
line_length = width * pixel_size
|
51
|
+
bytes = stream.unpack("@#{position}CC#{line_length}")
|
52
|
+
filter = bytes.shift
|
53
|
+
decoded_bytes = decode_scanline(filter, bytes, decoded_bytes, pixel_size)
|
43
54
|
|
44
|
-
|
55
|
+
# decode bytes into colors
|
56
|
+
decoded_bytes.each_slice(pixel_size) { |bytes| pixels << pixel_decoder.call(bytes) }
|
57
|
+
end
|
58
|
+
end
|
59
|
+
pixels
|
45
60
|
end
|
46
61
|
|
47
|
-
|
62
|
+
def decode_interlacing_none(stream, width, height, pixel_size, pixel_decoder)
|
63
|
+
raise "Invalid stream length!" unless stream.length == width * height * pixel_size + height
|
64
|
+
decode_image_pass(stream, width, height, pixel_size, pixel_decoder)
|
65
|
+
end
|
48
66
|
|
67
|
+
def decode_interlacing_adam7(stream, width, height, pixel_size, pixel_decoder)
|
68
|
+
start_pos = 0
|
69
|
+
sub_matrices = adam7_pass_sizes(width, height).map do |(pass_width, pass_height)|
|
70
|
+
pixels = decode_image_pass(stream, pass_width, pass_height, pixel_size, pixel_decoder, start_pos)
|
71
|
+
start_pos += (pass_width * pass_height * pixel_size) + pass_height
|
72
|
+
[pass_width, pass_height, pixels]
|
73
|
+
end
|
74
|
+
|
75
|
+
pixels = Array.new(width * height, ChunkyPNG::Pixel::TRANSPARENT)
|
76
|
+
0.upto(6) { |pass| adam7_merge_pass(pass, width, height, pixels, *sub_matrices[pass]) }
|
77
|
+
pixels
|
78
|
+
end
|
79
|
+
|
80
|
+
def adam7_multiplier_offset(pass)
|
81
|
+
{
|
82
|
+
:x_multiplier => 8 >> (pass >> 1),
|
83
|
+
:x_offset => (pass & 1 == 0) ? 0 : 8 >> ((pass + 1) >> 1),
|
84
|
+
:y_multiplier => pass == 0 ? 8 : 8 >> ((pass - 1) >> 1),
|
85
|
+
:y_offset => (pass == 0 || pass & 1 == 1) ? 0 : 8 >> (pass >> 1)
|
86
|
+
}
|
87
|
+
end
|
88
|
+
|
89
|
+
def adam7_merge_pass(pass, width, height, pixels, sm_width, sm_height, sm_pixels)
|
90
|
+
m_o = adam7_multiplier_offset(pass)
|
91
|
+
0.upto(sm_height - 1) do |y|
|
92
|
+
0.upto(sm_width - 1) do |x|
|
93
|
+
new_x = x * m_o[:x_multiplier] + m_o[:x_offset]
|
94
|
+
new_y = y * m_o[:y_multiplier] + m_o[:y_offset]
|
95
|
+
pixels[width * new_y + new_x] = sm_pixels[sm_width * y + x]
|
96
|
+
end
|
97
|
+
end
|
98
|
+
pixels
|
99
|
+
end
|
100
|
+
|
101
|
+
def adam7_pass_sizes(width, height)
|
102
|
+
(0...7).map do |pass|
|
103
|
+
m_o = adam7_multiplier_offset(pass)
|
104
|
+
[ ((width - m_o[:x_offset] ) / m_o[:x_multiplier].to_f).ceil,
|
105
|
+
((height - m_o[:y_offset] ) / m_o[:y_multiplier].to_f).ceil,]
|
106
|
+
end
|
107
|
+
end
|
108
|
+
|
49
109
|
def decode_scanline(filter, bytes, previous_bytes, pixelsize = 3)
|
50
110
|
case filter
|
51
|
-
when FILTER_NONE then decode_scanline_none(
|
52
|
-
when FILTER_SUB then decode_scanline_sub(
|
53
|
-
when FILTER_UP then decode_scanline_up(
|
54
|
-
when FILTER_AVERAGE then
|
55
|
-
when FILTER_PAETH then
|
111
|
+
when ChunkyPNG::FILTER_NONE then decode_scanline_none( bytes, previous_bytes, pixelsize)
|
112
|
+
when ChunkyPNG::FILTER_SUB then decode_scanline_sub( bytes, previous_bytes, pixelsize)
|
113
|
+
when ChunkyPNG::FILTER_UP then decode_scanline_up( bytes, previous_bytes, pixelsize)
|
114
|
+
when ChunkyPNG::FILTER_AVERAGE then decode_scanline_average( bytes, previous_bytes, pixelsize)
|
115
|
+
when ChunkyPNG::FILTER_PAETH then decode_scanline_paeth( bytes, previous_bytes, pixelsize)
|
56
116
|
else raise "Unknown filter type"
|
57
117
|
end
|
58
118
|
end
|
@@ -70,6 +130,30 @@ module ChunkyPNG
|
|
70
130
|
bytes.each_with_index { |b, i| bytes[i] = (b + previous_bytes[i]) % 256 }
|
71
131
|
bytes
|
72
132
|
end
|
133
|
+
|
134
|
+
def decode_scanline_average(bytes, previous_bytes, pixelsize = 3)
|
135
|
+
bytes.each_with_index do |byte, i|
|
136
|
+
a = (i >= pixelsize) ? bytes[i - pixelsize] : 0
|
137
|
+
b = previous_bytes[i]
|
138
|
+
bytes[i] = (byte + (a + b / 2).floor) % 256
|
139
|
+
end
|
140
|
+
bytes
|
141
|
+
end
|
142
|
+
|
143
|
+
def decode_scanline_paeth(bytes, previous_bytes, pixelsize = 3)
|
144
|
+
bytes.each_with_index do |byte, i|
|
145
|
+
a = (i >= pixelsize) ? bytes[i - pixelsize] : 0
|
146
|
+
b = previous_bytes[i]
|
147
|
+
c = (i >= pixelsize) ? previous_bytes[i - pixelsize] : 0
|
148
|
+
p = a + b - c
|
149
|
+
pa = (p - a).abs
|
150
|
+
pb = (p - b).abs
|
151
|
+
pc = (p - c).abs
|
152
|
+
pr = (pa <= pb && pa <= pc) ? a : (pb <= pc ? b : c)
|
153
|
+
bytes[i] = (byte + pr) % 256
|
154
|
+
end
|
155
|
+
bytes
|
156
|
+
end
|
73
157
|
end
|
74
158
|
end
|
75
159
|
end
|
@@ -7,7 +7,7 @@ module ChunkyPNG
|
|
7
7
|
result = {}
|
8
8
|
result[:header] = { :width => width, :height => height, :color => encoding[:color_mode] }
|
9
9
|
|
10
|
-
if encoding[:color_mode] == ChunkyPNG::
|
10
|
+
if encoding[:color_mode] == ChunkyPNG::COLOR_INDEXED
|
11
11
|
result[:palette_chunk] = encoding[:palette].to_plte_chunk
|
12
12
|
result[:transparency_chunk] = encoding[:palette].to_trns_chunk unless encoding[:palette].opaque?
|
13
13
|
end
|
@@ -25,58 +25,64 @@ module ChunkyPNG
|
|
25
25
|
return encoding
|
26
26
|
end
|
27
27
|
|
28
|
-
def encode_pixelstream(color_mode = ChunkyPNG::
|
28
|
+
def encode_pixelstream(color_mode = ChunkyPNG::COLOR_TRUECOLOR, palette = nil)
|
29
29
|
|
30
30
|
pixel_encoder = case color_mode
|
31
|
-
when ChunkyPNG::
|
32
|
-
when ChunkyPNG::
|
33
|
-
when ChunkyPNG::
|
34
|
-
when ChunkyPNG::
|
35
|
-
when ChunkyPNG::
|
31
|
+
when ChunkyPNG::COLOR_TRUECOLOR then lambda { |pixel| pixel.to_truecolor_bytes }
|
32
|
+
when ChunkyPNG::COLOR_TRUECOLOR_ALPHA then lambda { |pixel| pixel.to_truecolor_alpha_bytes }
|
33
|
+
when ChunkyPNG::COLOR_INDEXED then lambda { |pixel| pixel.to_indexed_bytes(palette) }
|
34
|
+
when ChunkyPNG::COLOR_GRAYSCALE then lambda { |pixel| pixel.to_grayscale_bytes }
|
35
|
+
when ChunkyPNG::COLOR_GRAYSCALE_ALPHA then lambda { |pixel| pixel.to_grayscale_alpha_bytes }
|
36
36
|
else raise "Cannot encode pixels for this mode: #{color_mode}!"
|
37
37
|
end
|
38
38
|
|
39
|
-
|
39
|
+
if color_mode == ChunkyPNG::COLOR_INDEXED && !palette.can_encode?
|
40
|
+
raise "This palette is not suitable for encoding!"
|
41
|
+
end
|
40
42
|
|
41
|
-
|
43
|
+
pixel_size = Pixel.bytesize(color_mode)
|
42
44
|
|
43
45
|
stream = ""
|
44
|
-
|
46
|
+
previous_bytes = Array.new(pixel_size * width, 0)
|
45
47
|
each_scanline do |line|
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
else
|
50
|
-
stream << encode_scanline_sub(bytes, previous, pixelsize).pack('C*')
|
51
|
-
end
|
52
|
-
previous = bytes
|
48
|
+
unencoded_bytes = line.map(&pixel_encoder).flatten
|
49
|
+
stream << encode_scanline_up(unencoded_bytes, previous_bytes, pixel_size).pack('C*')
|
50
|
+
previous_bytes = unencoded_bytes
|
53
51
|
end
|
54
52
|
return stream
|
55
|
-
end
|
53
|
+
end
|
56
54
|
|
57
55
|
def encode_scanline(filter, bytes, previous_bytes = nil, pixelsize = 3)
|
58
56
|
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!"
|
57
|
+
when ChunkyPNG::FILTER_NONE then encode_scanline_none( bytes, previous_bytes, pixelsize)
|
58
|
+
when ChunkyPNG::FILTER_SUB then encode_scanline_sub( bytes, previous_bytes, pixelsize)
|
59
|
+
when ChunkyPNG::FILTER_UP then encode_scanline_up( bytes, previous_bytes, pixelsize)
|
60
|
+
when ChunkyPNG::FILTER_AVERAGE then raise "Average filter are not yet supported!"
|
61
|
+
when ChunkyPNG::FILTER_PAETH then raise "Paeth filter are not yet supported!"
|
64
62
|
else raise "Unknown filter type"
|
65
63
|
end
|
66
64
|
end
|
67
65
|
|
68
|
-
def encode_scanline_none(
|
69
|
-
[FILTER_NONE] +
|
66
|
+
def encode_scanline_none(original_bytes, previous_bytes = nil, pixelsize = 3)
|
67
|
+
[ChunkyPNG::FILTER_NONE] + original_bytes
|
70
68
|
end
|
71
69
|
|
72
|
-
def encode_scanline_sub(
|
73
|
-
|
74
|
-
|
70
|
+
def encode_scanline_sub(original_bytes, previous_bytes = nil, pixelsize = 3)
|
71
|
+
encoded_bytes = []
|
72
|
+
original_bytes.length.times do |index|
|
73
|
+
a = (index >= pixelsize) ? original_bytes[index - pixelsize] : 0
|
74
|
+
encoded_bytes[index] = (original_bytes[index] - a) % 256
|
75
|
+
end
|
76
|
+
[ChunkyPNG::FILTER_SUB] + encoded_bytes
|
75
77
|
end
|
76
78
|
|
77
|
-
def encode_scanline_up(
|
78
|
-
|
79
|
-
|
79
|
+
def encode_scanline_up(original_bytes, previous_bytes, pixelsize = 3)
|
80
|
+
encoded_bytes = []
|
81
|
+
original_bytes.length.times do |index|
|
82
|
+
b = previous_bytes[index]
|
83
|
+
encoded_bytes[index] = (original_bytes[index] - b) % 256
|
84
|
+
end
|
85
|
+
[ChunkyPNG::FILTER_UP] + encoded_bytes
|
80
86
|
end
|
81
87
|
end
|
82
88
|
end
|
Binary file
|
Binary file
|
Binary file
|
Binary file
|
data/spec/unit/decoding_spec.rb
CHANGED
@@ -7,23 +7,23 @@ describe ChunkyPNG::PixelMatrix::Decoding do
|
|
7
7
|
|
8
8
|
it "should decode a line without filtering as is" do
|
9
9
|
bytes = [255, 255, 255, 255, 255, 255, 255, 255, 255]
|
10
|
-
decode_scanline(ChunkyPNG::
|
10
|
+
decode_scanline(ChunkyPNG::FILTER_NONE, bytes, nil).should == bytes
|
11
11
|
end
|
12
12
|
|
13
13
|
it "should decode a line with sub filtering correctly" do
|
14
14
|
# all white pixels
|
15
15
|
bytes = [255, 255, 255, 0, 0, 0, 0, 0, 0]
|
16
|
-
decoded_bytes = decode_scanline(ChunkyPNG::
|
16
|
+
decoded_bytes = decode_scanline(ChunkyPNG::FILTER_SUB, bytes, nil)
|
17
17
|
decoded_bytes.should == [255, 255, 255, 255, 255, 255, 255, 255, 255]
|
18
18
|
|
19
19
|
# all black pixels
|
20
20
|
bytes = [0, 0, 0, 0, 0, 0, 0, 0, 0]
|
21
|
-
decoded_bytes = decode_scanline(ChunkyPNG::
|
21
|
+
decoded_bytes = decode_scanline(ChunkyPNG::FILTER_SUB, bytes, nil)
|
22
22
|
decoded_bytes.should == [0, 0, 0, 0, 0, 0, 0, 0, 0]
|
23
23
|
|
24
24
|
# various colors
|
25
25
|
bytes = [255, 0, 45, 0, 255, 0, 112, 200, 178]
|
26
|
-
decoded_bytes = decode_scanline(ChunkyPNG::
|
26
|
+
decoded_bytes = decode_scanline(ChunkyPNG::FILTER_SUB, bytes, nil)
|
27
27
|
decoded_bytes.should == [255, 0, 45, 255, 255, 45, 111, 199, 223]
|
28
28
|
end
|
29
29
|
|
@@ -31,14 +31,53 @@ describe ChunkyPNG::PixelMatrix::Decoding do
|
|
31
31
|
# previous line is all black
|
32
32
|
previous_bytes = [0, 0, 0, 0, 0, 0, 0, 0, 0]
|
33
33
|
bytes = [255, 255, 255, 127, 127, 127, 0, 0, 0]
|
34
|
-
decoded_bytes = decode_scanline(ChunkyPNG::
|
34
|
+
decoded_bytes = decode_scanline(ChunkyPNG::FILTER_UP, bytes, previous_bytes)
|
35
35
|
decoded_bytes.should == [255, 255, 255, 127, 127, 127, 0, 0, 0]
|
36
36
|
|
37
37
|
# previous line has various pixels
|
38
38
|
previous_bytes = [255, 255, 255, 127, 127, 127, 0, 0, 0]
|
39
39
|
bytes = [0, 127, 255, 0, 127, 255, 0, 127, 255]
|
40
|
-
decoded_bytes = decode_scanline(ChunkyPNG::
|
40
|
+
decoded_bytes = decode_scanline(ChunkyPNG::FILTER_UP, bytes, previous_bytes)
|
41
41
|
decoded_bytes.should == [255, 126, 254, 127, 254, 126, 0, 127, 255]
|
42
42
|
end
|
43
43
|
end
|
44
|
+
|
45
|
+
describe '#adam7_pass_sizes' do
|
46
|
+
it "should get the pass sizes for a 8x8 image correctly" do
|
47
|
+
adam7_pass_sizes(8, 8).should == [
|
48
|
+
[1, 1], [1, 1], [2, 1], [2, 2], [4, 2], [4, 4], [8, 4]
|
49
|
+
]
|
50
|
+
end
|
51
|
+
|
52
|
+
it "should get the pass sizes for a 12x12 image correctly" do
|
53
|
+
adam7_pass_sizes(12, 12).should == [
|
54
|
+
[2, 2], [1, 2], [3, 1], [3, 3], [6, 3], [6, 6], [12, 6]
|
55
|
+
]
|
56
|
+
end
|
57
|
+
|
58
|
+
it "should get the pass sizes for a 33x47 image correctly" do
|
59
|
+
adam7_pass_sizes(33, 47).should == [
|
60
|
+
[5, 6], [4, 6], [9, 6], [8, 12], [17, 12], [16, 24], [33, 23]
|
61
|
+
]
|
62
|
+
end
|
63
|
+
|
64
|
+
it "should get the pass sizes for a 1x1 image correctly" do
|
65
|
+
adam7_pass_sizes(1, 1).should == [
|
66
|
+
[1, 1], [0, 1], [1, 0], [0, 1], [1, 0], [0, 1], [1, 0]
|
67
|
+
]
|
68
|
+
end
|
69
|
+
|
70
|
+
it "should get the pass sizes for a 0x0 image correctly" do
|
71
|
+
adam7_pass_sizes(0, 0).should == [
|
72
|
+
[0, 0], [0, 0], [0, 0], [0, 0], [0, 0], [0, 0], [0, 0]
|
73
|
+
]
|
74
|
+
end
|
75
|
+
|
76
|
+
it "should always maintain the same amount of pixels in total" do
|
77
|
+
[[8, 8], [12, 12], [33, 47], [1, 1], [0, 0]].each do |(width, height)|
|
78
|
+
pass_sizes = adam7_pass_sizes(width, height)
|
79
|
+
pass_sizes.inject(0) { |sum, (w, h)| sum + (w*h) }.should == width * height
|
80
|
+
end
|
81
|
+
end
|
82
|
+
end
|
44
83
|
end
|
data/spec/unit/encoding_spec.rb
CHANGED
@@ -7,20 +7,20 @@ describe ChunkyPNG::PixelMatrix::Encoding do
|
|
7
7
|
|
8
8
|
it "should encode a scanline without filtering correctly" do
|
9
9
|
bytes = [0, 0, 0, 1, 1, 1, 2, 2, 2]
|
10
|
-
encoded_bytes = encode_scanline(ChunkyPNG::
|
10
|
+
encoded_bytes = encode_scanline(ChunkyPNG::FILTER_NONE, bytes, nil)
|
11
11
|
encoded_bytes.should == [0, 0, 0, 0, 1, 1, 1, 2, 2, 2]
|
12
12
|
end
|
13
13
|
|
14
14
|
it "should encode a scanline with sub filtering correctly" do
|
15
15
|
bytes = [255, 255, 255, 255, 255, 255, 255, 255, 255]
|
16
|
-
encoded_bytes = encode_scanline(ChunkyPNG::
|
16
|
+
encoded_bytes = encode_scanline(ChunkyPNG::FILTER_SUB, bytes, nil)
|
17
17
|
encoded_bytes.should == [1, 255, 255, 255, 0, 0, 0, 0, 0, 0]
|
18
18
|
end
|
19
19
|
|
20
20
|
it "should encode a scanline with up filtering correctly" do
|
21
21
|
bytes = [255, 255, 255, 255, 255, 255, 255, 255, 255]
|
22
22
|
previous_bytes = [255, 255, 255, 255, 255, 255, 255, 255, 255]
|
23
|
-
encoded_bytes = encode_scanline(ChunkyPNG::
|
23
|
+
encoded_bytes = encode_scanline(ChunkyPNG::FILTER_UP, bytes, previous_bytes)
|
24
24
|
encoded_bytes.should == [2, 0, 0, 0, 0, 0, 0, 0, 0, 0]
|
25
25
|
end
|
26
26
|
end
|
@@ -4,16 +4,25 @@ describe ChunkyPNG::PixelMatrix do
|
|
4
4
|
|
5
5
|
describe '.decode' do
|
6
6
|
|
7
|
-
before(:each) do
|
8
|
-
@reference = ChunkyPNG::PixelMatrix.new(10, 10, ChunkyPNG::Pixel.rgb(100, 100, 100))
|
9
|
-
end
|
10
|
-
|
11
7
|
[:indexed, :grayscale, :grayscale_alpha, :truecolor, :truecolor_alpha].each do |color_mode|
|
12
8
|
it "should decode an image with color mode #{color_mode} correctly" do
|
9
|
+
reference = ChunkyPNG::PixelMatrix.new(10, 10, ChunkyPNG::Pixel.rgb(100, 100, 100))
|
13
10
|
ds = ChunkyPNG.load(resource_file("gray_10x10_#{color_mode}.png"))
|
14
|
-
ds.pixel_matrix.should ==
|
11
|
+
ds.pixel_matrix.should == reference
|
15
12
|
end
|
16
13
|
end
|
14
|
+
|
15
|
+
it "should decode a transparent image correctly" do
|
16
|
+
reference = ChunkyPNG::PixelMatrix.new(10, 10, ChunkyPNG::Pixel.rgba(100, 100, 100, 128))
|
17
|
+
ds = ChunkyPNG.load(resource_file("transparent_gray_10x10.png"))
|
18
|
+
ds.pixel_matrix.should == reference
|
19
|
+
end
|
20
|
+
|
21
|
+
it "should decode an interlaced image correctly" do
|
22
|
+
ds_i = ChunkyPNG.load(resource_file("16x16_interlaced.png"))
|
23
|
+
ds_ni = ChunkyPNG.load(resource_file("16x16_non_interlaced.png"))
|
24
|
+
ds_i.pixel_matrix.should == ds_ni.pixel_matrix
|
25
|
+
end
|
17
26
|
end
|
18
27
|
|
19
28
|
describe '.encode' do
|
data/tasks/github-gem.rake
CHANGED
@@ -124,7 +124,7 @@ module GithubGem
|
|
124
124
|
desc "Perform all checks that would occur before a release"
|
125
125
|
task(:release_checks => checks)
|
126
126
|
|
127
|
-
release_tasks = [:release_checks, :set_version, :build, :github_release]
|
127
|
+
release_tasks = [:release_checks, :set_version, :build, :github_release, :gemcutter_release]
|
128
128
|
release_tasks << [:rubyforge_release] if gemspec.rubyforge_project
|
129
129
|
|
130
130
|
desc "Release a new verison of the gem"
|
@@ -132,6 +132,7 @@ module GithubGem
|
|
132
132
|
|
133
133
|
task(:check_rubyforge) { check_rubyforge_task }
|
134
134
|
task(:rubyforge_release) { rubyforge_release_task }
|
135
|
+
task(:gemcutter_release) { gemcutter_release_task }
|
135
136
|
task(:github_release => [:commit_modified_files, :tag_version]) { github_release_task }
|
136
137
|
task(:tag_version) { tag_version_task }
|
137
138
|
task(:commit_modified_files) { commit_modified_files_task }
|
@@ -228,6 +229,10 @@ module GithubGem
|
|
228
229
|
def rubyforge_release_task
|
229
230
|
sh 'rubyforge', 'add_release', gemspec.rubyforge_project, gemspec.name, gemspec.version.to_s, "pkg/#{gemspec.name}-#{gemspec.version}.gem"
|
230
231
|
end
|
232
|
+
|
233
|
+
def gemcutter_release_task
|
234
|
+
sh "gem push pkg/#{gemspec.name}-#{gemspec.version}.gem"
|
235
|
+
end
|
231
236
|
|
232
237
|
# Gem release task.
|
233
238
|
# All work is done by the task's dependencies, so just display a release completed message.
|
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.3
|
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-13 00:00:00 +01:00
|
13
13
|
default_executable:
|
14
14
|
dependencies:
|
15
15
|
- !ruby/object:Gem::Dependency
|
@@ -44,8 +44,10 @@ files:
|
|
44
44
|
- lib/chunky_png/chunk.rb
|
45
45
|
- spec/unit/encoding_spec.rb
|
46
46
|
- Rakefile
|
47
|
+
- spec/resources/transparent_gray_10x10.png
|
47
48
|
- README.rdoc
|
48
49
|
- spec/resources/gray_10x10_indexed.png
|
50
|
+
- spec/resources/16x16_non_interlaced.png
|
49
51
|
- spec/integration/image_spec.rb
|
50
52
|
- lib/chunky_png/pixel.rb
|
51
53
|
- lib/chunky_png/palette.rb
|
@@ -54,6 +56,8 @@ files:
|
|
54
56
|
- tasks/github-gem.rake
|
55
57
|
- spec/unit/decoding_spec.rb
|
56
58
|
- spec/resources/gray_10x10_grayscale_alpha.png
|
59
|
+
- spec/resources/16x16_interlaced.png
|
60
|
+
- spec/resources/adam7.png
|
57
61
|
- lib/chunky_png/pixel_matrix/encoding.rb
|
58
62
|
- lib/chunky_png/image.rb
|
59
63
|
- spec/unit/pixel_spec.rb
|