chunky_png 0.0.5 → 0.5.0
Sign up to get free protection for your applications and to get access to all the features.
- data/README.rdoc +20 -10
- data/chunky_png.gemspec +18 -6
- data/lib/chunky_png.rb +11 -28
- data/lib/chunky_png/canvas.rb +186 -0
- data/lib/chunky_png/canvas/adam7_interlacing.rb +53 -0
- data/lib/chunky_png/canvas/drawing.rb +8 -0
- data/lib/chunky_png/{pixel_matrix → canvas}/operations.rb +12 -12
- data/lib/chunky_png/canvas/png_decoding.rb +145 -0
- data/lib/chunky_png/canvas/png_encoding.rb +182 -0
- data/lib/chunky_png/chunk.rb +101 -23
- data/lib/chunky_png/color.rb +307 -0
- data/lib/chunky_png/datastream.rb +143 -45
- data/lib/chunky_png/image.rb +31 -30
- data/lib/chunky_png/palette.rb +49 -47
- data/lib/chunky_png/rmagick.rb +43 -0
- data/spec/chunky_png/canvas/adam7_interlacing_spec.rb +108 -0
- data/spec/chunky_png/canvas/png_decoding_spec.rb +81 -0
- data/spec/chunky_png/canvas/png_encoding_spec.rb +70 -0
- data/spec/chunky_png/canvas_spec.rb +71 -0
- data/spec/chunky_png/color_spec.rb +104 -0
- data/spec/chunky_png/datastream_spec.rb +32 -0
- data/spec/chunky_png/image_spec.rb +25 -0
- data/spec/chunky_png/rmagick_spec.rb +21 -0
- data/spec/{integration/image_spec.rb → chunky_png_spec.rb} +14 -8
- data/spec/resources/composited.png +0 -0
- data/spec/resources/cropped.png +0 -0
- data/spec/resources/damaged_chunk.png +0 -0
- data/spec/resources/damaged_signature.png +13 -0
- data/spec/resources/pixelstream.rgba +67 -0
- data/spec/resources/replaced.png +0 -0
- data/spec/resources/text_chunk.png +0 -0
- data/spec/resources/ztxt_chunk.png +0 -0
- data/spec/spec_helper.rb +8 -5
- data/tasks/github-gem.rake +1 -1
- metadata +37 -18
- data/lib/chunky_png/pixel.rb +0 -272
- data/lib/chunky_png/pixel_matrix.rb +0 -136
- data/lib/chunky_png/pixel_matrix/decoding.rb +0 -159
- data/lib/chunky_png/pixel_matrix/encoding.rb +0 -89
- data/spec/unit/decoding_spec.rb +0 -83
- data/spec/unit/encoding_spec.rb +0 -27
- data/spec/unit/pixel_matrix_spec.rb +0 -93
- data/spec/unit/pixel_spec.rb +0 -47
@@ -0,0 +1,307 @@
|
|
1
|
+
module ChunkyPNG
|
2
|
+
|
3
|
+
# The Color module defines methods for handling colors. Within the ChunkyPNG
|
4
|
+
# library, the concepts of pixels and colors are both used, and they are
|
5
|
+
# both represented by a Fixnum.
|
6
|
+
#
|
7
|
+
# Pixels/colors are represented in RGBA componetns. Each of the four
|
8
|
+
# components is stored with a depth of 8 bits (maximum value = 255 =
|
9
|
+
# {ChunkyPNG::Color::MAX}). Together, these components are stored in a 4-byte
|
10
|
+
# Fixnum.
|
11
|
+
#
|
12
|
+
# A color will always be represented using these 4 components in memory.
|
13
|
+
# When the image is encoded, a more suitable representation can be used
|
14
|
+
# (e.g. rgb, grayscale, palette-based), for which several conversion methods
|
15
|
+
# are provided in this module.
|
16
|
+
module Color
|
17
|
+
extend self
|
18
|
+
|
19
|
+
# The maximum value of each color component.
|
20
|
+
MAX = 0xff
|
21
|
+
|
22
|
+
####################################################################
|
23
|
+
# CONSTRUCTING COLOR VALUES
|
24
|
+
####################################################################
|
25
|
+
|
26
|
+
# Creates a new color using an r, g, b triple and an alpha value.
|
27
|
+
# @return [Fixnum] The newly constructed color value.
|
28
|
+
def rgba(r, g, b, a)
|
29
|
+
r << 24 | g << 16 | b << 8 | a
|
30
|
+
end
|
31
|
+
|
32
|
+
# Creates a new color using an r, g, b triple.
|
33
|
+
# @return [Fixnum] The newly constructed color value.
|
34
|
+
def rgb(r, g, b, a = MAX)
|
35
|
+
rgba(r, g, b, a)
|
36
|
+
end
|
37
|
+
|
38
|
+
# Creates a new color using a grayscale teint.
|
39
|
+
# @return [ChunkyPNG::Color] The newly constructed color value.
|
40
|
+
def grayscale(teint, a = MAX)
|
41
|
+
rgba(teint, teint, teint, a)
|
42
|
+
end
|
43
|
+
|
44
|
+
# Creates a new color using a grayscale teint and alpha value.
|
45
|
+
# @return [Fixnum] The newly constructed color value.
|
46
|
+
def grayscale_alpha(teint, a)
|
47
|
+
rgba(teint, teint, teint, a)
|
48
|
+
end
|
49
|
+
|
50
|
+
####################################################################
|
51
|
+
# COLOR IMPORTING
|
52
|
+
####################################################################
|
53
|
+
|
54
|
+
# Creates a color by unpacking an rgb triple from a string.
|
55
|
+
#
|
56
|
+
# @param [String] stream The string to load the color from. It should be
|
57
|
+
# at least 3 + pos bytes long.
|
58
|
+
# @param [Fixnum] pos The position in the string to load the triple from.
|
59
|
+
# @return [Fixnum] The newly constructed color value.
|
60
|
+
def from_rgb_stream(stream, pos = 0)
|
61
|
+
rgb(*stream.unpack("@#{pos}C3"))
|
62
|
+
end
|
63
|
+
|
64
|
+
# Creates a color by unpacking an rgba triple from a string
|
65
|
+
#
|
66
|
+
# @param [String] stream The string to load the color from. It should be
|
67
|
+
# at least 4 + pos bytes long.
|
68
|
+
# @param [Fixnum] pos The position in the string to load the triple from.
|
69
|
+
# @return [Fixnum] The newly constructed color value.
|
70
|
+
def from_rgba_stream(stream, pos = 0)
|
71
|
+
rgba(*stream.unpack("@#{pos}C4"))
|
72
|
+
end
|
73
|
+
|
74
|
+
# Creates a color by converting it from a string in hex notation.
|
75
|
+
#
|
76
|
+
# It supports colors with (#rrggbbaa) or without (#rrggbb) alpha channel.
|
77
|
+
# Color strings may include the prefix "0x" or "#".
|
78
|
+
#
|
79
|
+
# @param [String] str The color in hex notation. @return [Fixnum] The
|
80
|
+
# converted color value.
|
81
|
+
def from_hex(str)
|
82
|
+
case str
|
83
|
+
when /^(?:#|0x)?([0-9a-f]{6})$/i then ($1.hex << 8) | 0xff
|
84
|
+
when /^(?:#|0x)?([0-9a-f]{8})$/i then $1.hex
|
85
|
+
else raise "Not a valid hex color notation: #{str.inspect}!"
|
86
|
+
end
|
87
|
+
end
|
88
|
+
|
89
|
+
####################################################################
|
90
|
+
# PROPERTIES
|
91
|
+
####################################################################
|
92
|
+
|
93
|
+
# Returns the red-component from the color value.
|
94
|
+
#
|
95
|
+
# @param [Fixnum] value The color value.
|
96
|
+
# @return [Fixnum] A value between 0 and MAX.
|
97
|
+
def r(value)
|
98
|
+
(value & 0xff000000) >> 24
|
99
|
+
end
|
100
|
+
|
101
|
+
# Returns the green-component from the color value.
|
102
|
+
#
|
103
|
+
# @param [Fixnum] value The color value.
|
104
|
+
# @return [Fixnum] A value between 0 and MAX.
|
105
|
+
def g(value)
|
106
|
+
(value & 0x00ff0000) >> 16
|
107
|
+
end
|
108
|
+
|
109
|
+
# Returns the blue-component from the color value.
|
110
|
+
#
|
111
|
+
# @param [Fixnum] value The color value.
|
112
|
+
# @return [Fixnum] A value between 0 and MAX.
|
113
|
+
def b(value)
|
114
|
+
(value & 0x0000ff00) >> 8
|
115
|
+
end
|
116
|
+
|
117
|
+
# Returns the alpha channel value for the color value.
|
118
|
+
#
|
119
|
+
# @param [Fixnum] value The color value.
|
120
|
+
# @return [Fixnum] A value between 0 and MAX.
|
121
|
+
def a(value)
|
122
|
+
value & 0x000000ff
|
123
|
+
end
|
124
|
+
|
125
|
+
# Returns true if this color is fully opaque.
|
126
|
+
#
|
127
|
+
# @param [Fixnum] value The color to test.
|
128
|
+
# @return [true, false] True if the alpha channel equals MAX.
|
129
|
+
def opaque?(value)
|
130
|
+
a(value) == 0x000000ff
|
131
|
+
end
|
132
|
+
|
133
|
+
# Returns true if this color is fully transparent.
|
134
|
+
#
|
135
|
+
# @param [Fixnum] value The color to test.
|
136
|
+
# @return [true, false] True if the r, g and b component are equal.
|
137
|
+
def grayscale?(value)
|
138
|
+
r(value) == b(value) && b(value) == g(value)
|
139
|
+
end
|
140
|
+
|
141
|
+
# Returns true if this color is fully transparent.
|
142
|
+
#
|
143
|
+
# @param [Fixnum] value The color to test.
|
144
|
+
# @return [true, false] True if the alpha channel equals 0.
|
145
|
+
def fully_transparent?(value)
|
146
|
+
a(value) == 0x00000000
|
147
|
+
end
|
148
|
+
|
149
|
+
####################################################################
|
150
|
+
# ALPHA COMPOSITION
|
151
|
+
####################################################################
|
152
|
+
|
153
|
+
# Multiplies two fractions using integer math, where the fractions are stored using an
|
154
|
+
# integer between 0 and 255. This method is used as a helper method for compositing
|
155
|
+
# colors using integer math.
|
156
|
+
#
|
157
|
+
# This is a quicker implementation of ((a * b) / 255.0).round.
|
158
|
+
#
|
159
|
+
# @param [Fixnum] a The first fraction.
|
160
|
+
# @param [Fixnum] b The second fraction.
|
161
|
+
# @return [Fixnum] The result of the multiplication.
|
162
|
+
def int8_mult(a, b)
|
163
|
+
t = a * b + 0x80
|
164
|
+
((t >> 8) + t) >> 8
|
165
|
+
end
|
166
|
+
|
167
|
+
# Composes two colors with an alpha channel using integer math.
|
168
|
+
#
|
169
|
+
# This version is faster than the version based on floating point math, so this
|
170
|
+
# compositing function is used by default.
|
171
|
+
#
|
172
|
+
# @param [Fixnum] fg The foreground color.
|
173
|
+
# @param [Fixnum] bg The foreground color.
|
174
|
+
# @return [Fixnum] The composited color.
|
175
|
+
# @see ChunkyPNG::Color#compose_precise
|
176
|
+
def compose_quick(fg, bg)
|
177
|
+
return fg if opaque?(fg)
|
178
|
+
return bg if fully_transparent?(fg)
|
179
|
+
|
180
|
+
a_com = int8_mult(0xff - a(fg), a(bg))
|
181
|
+
new_r = int8_mult(a(fg), r(fg)) + int8_mult(a_com, r(bg))
|
182
|
+
new_g = int8_mult(a(fg), g(fg)) + int8_mult(a_com, g(bg))
|
183
|
+
new_b = int8_mult(a(fg), b(fg)) + int8_mult(a_com, b(bg))
|
184
|
+
new_a = a(fg) + a_com
|
185
|
+
rgba(new_r, new_g, new_b, new_a)
|
186
|
+
end
|
187
|
+
|
188
|
+
# Composes two colors with an alpha channel using floating point math.
|
189
|
+
#
|
190
|
+
# This method uses more precise floating point math, but this precision is lost
|
191
|
+
# when the result is converted back to an integer. Because it is slower than
|
192
|
+
# the version based on integer math, that version is preferred.
|
193
|
+
#
|
194
|
+
# @param [Fixnum] fg The foreground color.
|
195
|
+
# @param [Fixnum] bg The foreground color.
|
196
|
+
# @return [Fixnum] The composited color.
|
197
|
+
# @see ChunkyPNG::Color#compose_quick
|
198
|
+
def compose_precise(fg, bg)
|
199
|
+
return fg if opaque?(fg)
|
200
|
+
return bg if fully_transparent?(fg)
|
201
|
+
|
202
|
+
fg_a = a(fg).to_f / MAX
|
203
|
+
bg_a = a(bg).to_f / MAX
|
204
|
+
a_com = (1.0 - fg_a) * bg_a
|
205
|
+
|
206
|
+
new_r = (fg_a * r(fg) + a_com * r(bg)).round
|
207
|
+
new_g = (fg_a * g(fg) + a_com * g(bg)).round
|
208
|
+
new_b = (fg_a * b(fg) + a_com * b(bg)).round
|
209
|
+
new_a = ((fg_a + a_com) * MAX).round
|
210
|
+
rgba(new_r, new_g, new_b, new_a)
|
211
|
+
end
|
212
|
+
|
213
|
+
alias :compose :compose_quick
|
214
|
+
|
215
|
+
# Blends the foreground and background color by taking the average of
|
216
|
+
# the components.
|
217
|
+
#
|
218
|
+
# @param [Fixnum] fg The foreground color.
|
219
|
+
# @param [Fixnum] bg The foreground color.
|
220
|
+
# @return [Fixnum] The blended color.
|
221
|
+
def blend(fg, bg)
|
222
|
+
(fg + bg) >> 1
|
223
|
+
end
|
224
|
+
|
225
|
+
####################################################################
|
226
|
+
# CONVERSIONS
|
227
|
+
####################################################################
|
228
|
+
|
229
|
+
# Returns a string representing this color using hex notation (i.e. #rrggbbaa).
|
230
|
+
#
|
231
|
+
# @param [Fixnum] value The color to convert.
|
232
|
+
# @return [String] The color in hex notation, starting with a pound sign.
|
233
|
+
def to_hex(color, include_alpha = true)
|
234
|
+
include_alpha ? ('#%08x' % color) : ('#%06x' % [color >> 8])
|
235
|
+
end
|
236
|
+
|
237
|
+
# Returns an array with the separate RGBA values for this color.
|
238
|
+
#
|
239
|
+
# @param [Fixnum] color The color to convert.
|
240
|
+
# @return [Array<Fixnum>] An array with 4 Fixnum elements.
|
241
|
+
def to_truecolor_alpha_bytes(color)
|
242
|
+
[r(color), g(color), b(color), a(color)]
|
243
|
+
end
|
244
|
+
|
245
|
+
# Returns an array with the separate RGB values for this color.
|
246
|
+
# The alpha channel will be discarded.
|
247
|
+
#
|
248
|
+
# @param [Fixnum] color The color to convert.
|
249
|
+
# @return [Array<Fixnum>] An array with 3 Fixnum elements.
|
250
|
+
def to_truecolor_bytes(color)
|
251
|
+
[r(color), g(color), b(color)]
|
252
|
+
end
|
253
|
+
|
254
|
+
# Returns an array with the grayscale teint value for this color.
|
255
|
+
#
|
256
|
+
# This method expects the r,g and b value to be equal, and the alpha
|
257
|
+
# channel will be discarded.
|
258
|
+
#
|
259
|
+
# @param [Fixnum] color The grayscale color to convert.
|
260
|
+
# @return [Array<Fixnum>] An array with 1 Fixnum element.
|
261
|
+
def to_grayscale_bytes(color)
|
262
|
+
[r(color)] # assumption r == g == b
|
263
|
+
end
|
264
|
+
|
265
|
+
# Returns an array with the grayscale teint and alpha channel values
|
266
|
+
# for this color.
|
267
|
+
#
|
268
|
+
# This method expects the r,g and b value to be equal.
|
269
|
+
#
|
270
|
+
# @param [Fixnum] color The grayscale color to convert.
|
271
|
+
# @return [Array<Fixnum>] An array with 2 Fixnum elements.
|
272
|
+
def to_grayscale_alpha_bytes(color)
|
273
|
+
[r(color), a(color)] # assumption r == g == b
|
274
|
+
end
|
275
|
+
|
276
|
+
####################################################################
|
277
|
+
# COLOR CONSTANTS
|
278
|
+
####################################################################
|
279
|
+
|
280
|
+
# Black pixel/color
|
281
|
+
BLACK = rgb( 0, 0, 0)
|
282
|
+
|
283
|
+
# White pixel/color
|
284
|
+
WHITE = rgb(255, 255, 255)
|
285
|
+
|
286
|
+
# Fully transparent pixel/color
|
287
|
+
TRANSPARENT = rgba(255, 255, 255, 0)
|
288
|
+
|
289
|
+
####################################################################
|
290
|
+
# STATIC UTILITY METHODS
|
291
|
+
####################################################################
|
292
|
+
|
293
|
+
# Returns the size in bytes of a pixel when it is stored using a given color mode.
|
294
|
+
# @param [Fixnum] color_mode The color mode in which the pixels are stored.
|
295
|
+
# @return [Fixnum] The number of bytes used per pixel in a datastream.
|
296
|
+
def bytesize(color_mode)
|
297
|
+
case color_mode
|
298
|
+
when ChunkyPNG::COLOR_INDEXED then 1
|
299
|
+
when ChunkyPNG::COLOR_TRUECOLOR then 3
|
300
|
+
when ChunkyPNG::COLOR_TRUECOLOR_ALPHA then 4
|
301
|
+
when ChunkyPNG::COLOR_GRAYSCALE then 1
|
302
|
+
when ChunkyPNG::COLOR_GRAYSCALE_ALPHA then 2
|
303
|
+
else raise "Don't know the bytesize of pixels in this colormode: #{color_mode}!"
|
304
|
+
end
|
305
|
+
end
|
306
|
+
end
|
307
|
+
end
|
@@ -1,71 +1,169 @@
|
|
1
1
|
module ChunkyPNG
|
2
|
-
|
2
|
+
|
3
|
+
# The Datastream class represents a PNG formatted datastream. It supports
|
4
|
+
# both reading from and writing to strings, stremas and files.
|
5
|
+
#
|
6
|
+
# A PNG datastream begins with the PNG signature, and than contains multiple
|
7
|
+
# chunks, starting with a header (IHDR) chunk and finishing with an end
|
8
|
+
# (IEND) chunk.
|
9
|
+
#
|
10
|
+
# @see ChunkyPNG::Chunk
|
3
11
|
class Datastream
|
4
|
-
|
12
|
+
|
13
|
+
# The signature that each PNG file or stream should begin with.
|
5
14
|
SIGNATURE = [137, 80, 78, 71, 13, 10, 26, 10].pack('C8')
|
6
|
-
|
15
|
+
|
16
|
+
# The header chunk of this datastream.
|
17
|
+
# @return [ChunkyPNG::Chunk::Header]
|
7
18
|
attr_accessor :header_chunk
|
19
|
+
|
20
|
+
# All other chunks in this PNG file.
|
21
|
+
# @return [Array<ChunkyPNG::Chunk::Generic>]
|
8
22
|
attr_accessor :other_chunks
|
23
|
+
|
24
|
+
# The chunk containing the image's palette.
|
25
|
+
# @return [ChunkyPNG::Chunk::Palette]
|
9
26
|
attr_accessor :palette_chunk
|
27
|
+
|
28
|
+
# The chunk containing the transparency information of the palette.
|
29
|
+
# @return [ChunkyPNG::Chunk::Transparency]
|
10
30
|
attr_accessor :transparency_chunk
|
31
|
+
|
32
|
+
# The chunks that together compose the images pixel data.
|
33
|
+
# @return [Array<ChunkyPNG::Chunk::ImageData>]
|
11
34
|
attr_accessor :data_chunks
|
35
|
+
|
36
|
+
# The empty chunk that signals the end of this datastream
|
37
|
+
# @return [ChunkyPNG::Chunk::Header]
|
12
38
|
attr_accessor :end_chunk
|
13
39
|
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
40
|
+
|
41
|
+
# Initializes a new Datastream instance.
|
42
|
+
def initialize
|
43
|
+
@other_chunks = []
|
44
|
+
@data_chunks = []
|
45
|
+
end
|
46
|
+
|
47
|
+
#############################################
|
48
|
+
# LOADING DATASTREAMS
|
49
|
+
#############################################
|
50
|
+
|
51
|
+
class << self
|
52
|
+
|
53
|
+
# Reads a PNG datastream from a string.
|
54
|
+
# @param [String] str The PNG encoded string to load from.
|
55
|
+
# @return [ChunkyPNG::Datastream] The loaded datastream instance.
|
56
|
+
def from_blob(str)
|
57
|
+
from_io(StringIO.new(str))
|
58
|
+
end
|
59
|
+
|
60
|
+
alias :from_string :from_blob
|
61
|
+
|
62
|
+
# Reads a PNG datastream from a file.
|
63
|
+
# @param [String] filename The path of the file to load from.
|
64
|
+
# @return [ChunkyPNG::Datastream] The loaded datastream instance.
|
65
|
+
def from_file(filename)
|
66
|
+
ds = nil
|
67
|
+
File.open(filename, 'rb') { |f| ds = from_io(f) }
|
68
|
+
ds
|
69
|
+
end
|
70
|
+
|
71
|
+
# Reads a PNG datastream from an input stream
|
72
|
+
# @param [IO] io The stream to read from.
|
73
|
+
# @return [ChunkyPNG::Datastream] The loaded datastream instance.
|
74
|
+
def from_io(io)
|
75
|
+
verify_signature!(io)
|
76
|
+
|
77
|
+
ds = self.new
|
78
|
+
until io.eof?
|
79
|
+
chunk = ChunkyPNG::Chunk.read(io)
|
80
|
+
case chunk
|
81
|
+
when ChunkyPNG::Chunk::Header then ds.header_chunk = chunk
|
82
|
+
when ChunkyPNG::Chunk::Palette then ds.palette_chunk = chunk
|
83
|
+
when ChunkyPNG::Chunk::Transparency then ds.transparency_chunk = chunk
|
84
|
+
when ChunkyPNG::Chunk::ImageData then ds.data_chunks << chunk
|
85
|
+
when ChunkyPNG::Chunk::End then ds.end_chunk = chunk
|
86
|
+
else ds.other_chunks << chunk
|
87
|
+
end
|
27
88
|
end
|
89
|
+
return ds
|
90
|
+
end
|
91
|
+
|
92
|
+
# Verifies that the current stream is a PNG datastream by checking its signature.
|
93
|
+
#
|
94
|
+
# This method reads the PNG signature from the stream, setting the current position
|
95
|
+
# of the stream directly after the signature, where the IHDR chunk should begin.
|
96
|
+
#
|
97
|
+
# @raise [RuntimeError] An exception is raised if the PNG signature is not found at
|
98
|
+
# the beginning of the stream.
|
99
|
+
def verify_signature!(io)
|
100
|
+
signature = io.read(ChunkyPNG::Datastream::SIGNATURE.length)
|
101
|
+
raise "PNG signature not found!" unless signature == ChunkyPNG::Datastream::SIGNATURE
|
28
102
|
end
|
29
|
-
return ds
|
30
103
|
end
|
31
104
|
|
32
|
-
|
33
|
-
|
34
|
-
|
105
|
+
#############################################
|
106
|
+
# CHUNKS
|
107
|
+
#############################################
|
108
|
+
|
109
|
+
# Enumerates the chunks in this datastream.
|
110
|
+
#
|
111
|
+
# This will iterate over the chunks using the order in which the chunks
|
112
|
+
# should appear in the PNG file.
|
113
|
+
#
|
114
|
+
# @yield [ChunkyPNG::Chunk::Base] The chunks in this datastrean, one by one.
|
115
|
+
# @see ChunkyPNG::Datastream#chunks
|
116
|
+
def each_chunk
|
117
|
+
yield(header_chunk)
|
118
|
+
other_chunks.each { |chunk| yield(chunk) }
|
119
|
+
yield(palette_chunk) if palette_chunk
|
120
|
+
yield(transparency_chunk) if transparency_chunk
|
121
|
+
data_chunks.each { |chunk| yield(chunk) }
|
122
|
+
yield(end_chunk)
|
35
123
|
end
|
36
|
-
|
124
|
+
|
125
|
+
# Returns an enumerator instance for this datastream's chunks.
|
126
|
+
# @return [Enumerable::Enumerator] An enumerator for the :each_chunk method.
|
127
|
+
# @see ChunkyPNG::Datastream#each_chunk
|
37
128
|
def chunks
|
38
|
-
|
39
|
-
cs += other_chunks
|
40
|
-
cs << palette_chunk if palette_chunk
|
41
|
-
cs << transparency_chunk if transparency_chunk
|
42
|
-
cs += data_chunks
|
43
|
-
cs << end_chunk
|
44
|
-
return cs
|
129
|
+
enum_for(:each_chunk)
|
45
130
|
end
|
46
131
|
|
47
|
-
|
48
|
-
|
49
|
-
|
132
|
+
# Returns all the textual metadata key/value pairs as hash.
|
133
|
+
def metadata
|
134
|
+
metadata = {}
|
135
|
+
other_chunks.select do |chunk|
|
136
|
+
metadata[chunk.keyword] = chunk.value if chunk.respond_to?(:keyword)
|
137
|
+
end
|
138
|
+
metadata
|
50
139
|
end
|
51
|
-
|
140
|
+
|
141
|
+
#############################################
|
142
|
+
# WRITING DATASTREAMS
|
143
|
+
#############################################
|
144
|
+
|
145
|
+
# Writes the datastream to the given output stream.
|
146
|
+
# @param [IO] io The output stream to write to.
|
52
147
|
def write(io)
|
53
148
|
io << SIGNATURE
|
54
|
-
|
55
|
-
end
|
56
|
-
|
57
|
-
def idat_chunks(data)
|
58
|
-
streamdata = Zlib::Deflate.deflate(data)
|
59
|
-
# TODO: Split long streamdata over multiple chunks
|
60
|
-
return [ ChunkyPNG::Chunk::ImageData.new('IDAT', streamdata) ]
|
149
|
+
each_chunk { |c| c.write(io) }
|
61
150
|
end
|
62
|
-
|
63
|
-
|
64
|
-
|
151
|
+
|
152
|
+
# Saves this datastream as a PNG file.
|
153
|
+
# @param [String] filename The filename to use.
|
154
|
+
def save(filename)
|
155
|
+
File.open(filename, 'wb') { |f| write(f) }
|
65
156
|
end
|
66
|
-
|
67
|
-
|
68
|
-
|
157
|
+
|
158
|
+
# Encodes this datastream into a string.
|
159
|
+
# @return [String] The encoded PNG datastream.
|
160
|
+
def to_blob
|
161
|
+
str = StringIO.new
|
162
|
+
write(str)
|
163
|
+
return str.string
|
69
164
|
end
|
165
|
+
|
166
|
+
alias :to_string :to_blob
|
167
|
+
alias :to_s :to_blob
|
70
168
|
end
|
71
169
|
end
|