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