chunky_png 0.5.4 → 0.5.5
Sign up to get free protection for your applications and to get access to all the features.
- data/chunky_png.gemspec +2 -2
- data/lib/chunky_png.rb +1 -1
- data/lib/chunky_png/canvas/png_decoding.rb +124 -9
- data/lib/chunky_png/canvas/png_encoding.rb +117 -48
- data/lib/chunky_png/color.rb +26 -0
- data/spec/chunky_png/color_spec.rb +6 -0
- metadata +2 -2
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.5.
|
7
|
-
s.date = "2010-
|
6
|
+
s.version = "0.5.5"
|
7
|
+
s.date = "2010-02-16"
|
8
8
|
|
9
9
|
s.summary = "Pure ruby library for read/write, chunk-level access to PNG files"
|
10
10
|
s.description = <<-EOT
|
data/lib/chunky_png.rb
CHANGED
@@ -26,7 +26,7 @@ module ChunkyPNG
|
|
26
26
|
|
27
27
|
# The current version of ChunkyPNG. This value will be updated automatically
|
28
28
|
# by them gem:release rake task.
|
29
|
-
VERSION = "0.5.
|
29
|
+
VERSION = "0.5.5"
|
30
30
|
|
31
31
|
###################################################
|
32
32
|
# PNG international standard defined constants
|
@@ -1,26 +1,65 @@
|
|
1
1
|
module ChunkyPNG
|
2
2
|
class Canvas
|
3
|
-
|
4
|
-
# The PNGDecoding contains methods for decoding PNG datastreams to create a
|
5
|
-
# The datastream can be provided as filename, string or IO
|
3
|
+
|
4
|
+
# The PNGDecoding contains methods for decoding PNG datastreams to create a
|
5
|
+
# Canvas object. The datastream can be provided as filename, string or IO
|
6
|
+
# stream.
|
7
|
+
#
|
8
|
+
# Overview of the decoding process:
|
9
|
+
#
|
10
|
+
# * The optional PLTE and tRNS chunk are decoded for the color palette of
|
11
|
+
# the original image.
|
12
|
+
# * The contents of the IDAT chunks is combined, and uncompressed using
|
13
|
+
# Inflate decompression to the image pixelstream.
|
14
|
+
# * Based on the color mode, width and height of the original image, which
|
15
|
+
# is read from the PNG header (IHDR chunk), the amount of bytes
|
16
|
+
# per line is determined.
|
17
|
+
# * For every line of pixels in the original image, the determined amount
|
18
|
+
# of bytes is read from the pixel stream.
|
19
|
+
# * The read bytes are unfiltered given by the filter function specified by
|
20
|
+
# the first byte of the line.
|
21
|
+
# * The unfiltered bytes are converted into colored pixels, using the color mode.
|
22
|
+
# * All lines combined form the original image.
|
23
|
+
#
|
24
|
+
# For interlaced images, the original image was split into 7 subimages.
|
25
|
+
# These images get decoded just like the process above (from step 3), and get
|
26
|
+
# combined to form the original images.
|
27
|
+
#
|
28
|
+
# @see ChunkyPNG::Canvas::PNGEncoding
|
29
|
+
# @see http://www.w3.org/TR/PNG/ The W3C PNG format specification
|
6
30
|
module PNGDecoding
|
7
31
|
|
32
|
+
# The palette that is used to decode the image, loading from the PLTE and
|
33
|
+
# tRNS chunk from the PNG stream. For RGB(A) images, no palette is required.
|
34
|
+
# @return [ChunkyPNG::Palette]
|
8
35
|
attr_accessor :decoding_palette
|
9
36
|
|
37
|
+
# Decodes a Canvas from a PNG encoded string.
|
38
|
+
# @param [String] str The string to read from.
|
39
|
+
# @return [ChunkyPNG::Canvas] The canvas decoded from the PNG encoded string.
|
10
40
|
def from_blob(str)
|
11
41
|
from_datastream(ChunkyPNG::Datastream.from_blob(str))
|
12
42
|
end
|
13
|
-
|
43
|
+
|
14
44
|
alias :from_string :from_blob
|
15
45
|
|
46
|
+
# Decodes a Canvas from a PNG encoded file.
|
47
|
+
# @param [String] filename The file to read from.
|
48
|
+
# @return [ChunkyPNG::Canvas] The canvas decoded from the PNG file.
|
16
49
|
def from_file(filename)
|
17
50
|
from_datastream(ChunkyPNG::Datastream.from_file(filename))
|
18
51
|
end
|
19
|
-
|
52
|
+
|
53
|
+
# Decodes a Canvas from a PNG encoded stream.
|
54
|
+
# @param [IO, #read] io The stream to read from.
|
55
|
+
# @return [ChunkyPNG::Canvas] The canvas decoded from the PNG stream.
|
20
56
|
def from_io(io)
|
21
57
|
from_datastream(ChunkyPNG::Datastream.from_io(io))
|
22
58
|
end
|
23
59
|
|
60
|
+
# Decodes the Canvas from a PNG datastream instance.
|
61
|
+
# @param [ChunkyPNG::Datastream] ds The datastream to decode.
|
62
|
+
# @return [ChunkyPNG::Canvas] The canvas decoded from the PNG datastream.
|
24
63
|
def from_datastream(ds)
|
25
64
|
raise "Only 8-bit color depth is currently supported by ChunkyPNG!" unless ds.header_chunk.depth == 8
|
26
65
|
|
@@ -28,13 +67,21 @@ module ChunkyPNG
|
|
28
67
|
height = ds.header_chunk.height
|
29
68
|
color_mode = ds.header_chunk.color
|
30
69
|
interlace = ds.header_chunk.interlace
|
31
|
-
|
70
|
+
|
32
71
|
self.decoding_palette = ChunkyPNG::Palette.from_chunks(ds.palette_chunk, ds.transparency_chunk)
|
33
72
|
pixelstream = ChunkyPNG::Chunk::ImageData.combine_chunks(ds.data_chunks)
|
34
|
-
|
73
|
+
|
35
74
|
decode_png_pixelstream(pixelstream, width, height, color_mode, interlace)
|
36
75
|
end
|
37
76
|
|
77
|
+
# Decodes a canvas from a PNG encoded pixelstream, using a given width, height,
|
78
|
+
# color mode and interlacing mode.
|
79
|
+
# @param [String] stream The pixelstream to read from.
|
80
|
+
# @param [Integer] width The width of the image.
|
81
|
+
# @param [Integer] width The height of the image.
|
82
|
+
# @param [Integer] color_mode The color mode of the encoded pixelstream.
|
83
|
+
# @param [Integer] interlace The interlace method of the encoded pixelstream.
|
84
|
+
# @return [ChunkyPNG::Canvas] The decoded Canvas instance.
|
38
85
|
def decode_png_pixelstream(stream, width, height, color_mode = ChunkyPNG::COLOR_TRUECOLOR, interlace = ChunkyPNG::INTERLACING_NONE)
|
39
86
|
raise "This palette is not suitable for decoding!" if decoding_palette && !decoding_palette.can_decode?
|
40
87
|
|
@@ -47,10 +94,24 @@ module ChunkyPNG
|
|
47
94
|
|
48
95
|
protected
|
49
96
|
|
97
|
+
# Decodes a canvas from a non-interlaced PNG encoded pixelstream, using a
|
98
|
+
# given width, height and color mode.
|
99
|
+
# @param stream (see ChunkyPNG::Canvas::PNGDecoding#decode_png_pixelstream)
|
100
|
+
# @param width (see ChunkyPNG::Canvas::PNGDecoding#decode_png_pixelstream)
|
101
|
+
# @param height (see ChunkyPNG::Canvas::PNGDecoding#decode_png_pixelstream)
|
102
|
+
# @param color_mode (see ChunkyPNG::Canvas::PNGDecoding#decode_png_pixelstream)
|
103
|
+
# @return (see ChunkyPNG::Canvas::PNGDecoding#decode_png_pixelstream)
|
50
104
|
def decode_png_without_interlacing(stream, width, height, color_mode)
|
51
105
|
decode_png_image_pass(stream, width, height, color_mode)
|
52
106
|
end
|
53
107
|
|
108
|
+
# Decodes a canvas from a Adam 7 interlaced PNG encoded pixelstream, using a
|
109
|
+
# given width, height and color mode.
|
110
|
+
# @param stream (see ChunkyPNG::Canvas::PNGDecoding#decode_png_pixelstream)
|
111
|
+
# @param width (see ChunkyPNG::Canvas::PNGDecoding#decode_png_pixelstream)
|
112
|
+
# @param height (see ChunkyPNG::Canvas::PNGDecoding#decode_png_pixelstream)
|
113
|
+
# @param color_mode (see ChunkyPNG::Canvas::PNGDecoding#decode_png_pixelstream)
|
114
|
+
# @return (see ChunkyPNG::Canvas::PNGDecoding#decode_png_pixelstream)
|
54
115
|
def decode_png_with_adam7_interlacing(stream, width, height, color_mode)
|
55
116
|
canvas = ChunkyPNG::Canvas.new(width, height)
|
56
117
|
pixel_size = Color.bytesize(color_mode)
|
@@ -64,6 +125,18 @@ module ChunkyPNG
|
|
64
125
|
canvas
|
65
126
|
end
|
66
127
|
|
128
|
+
# Decodes a single PNG image pass width a given width, height and color
|
129
|
+
# mode, to a Canvas, starting at the given position in the stream.
|
130
|
+
#
|
131
|
+
# A non-interlaced image only consists of one pass, while an Adam7
|
132
|
+
# image consists of 7 passes that must be combined after decoding.
|
133
|
+
#
|
134
|
+
# @param stream (see ChunkyPNG::Canvas::PNGDecoding#decode_png_pixelstream)
|
135
|
+
# @param width (see ChunkyPNG::Canvas::PNGDecoding#decode_png_pixelstream)
|
136
|
+
# @param height (see ChunkyPNG::Canvas::PNGDecoding#decode_png_pixelstream)
|
137
|
+
# @param color_mode (see ChunkyPNG::Canvas::PNGDecoding#decode_png_pixelstream)
|
138
|
+
# @param [Integer] start_pos The position in the pixel stream to start reading.
|
139
|
+
# @return (see ChunkyPNG::Canvas::PNGDecoding#decode_png_pixelstream)
|
67
140
|
def decode_png_image_pass(stream, width, height, color_mode, start_pos = 0)
|
68
141
|
|
69
142
|
pixel_size = Color.bytesize(color_mode)
|
@@ -99,8 +172,20 @@ module ChunkyPNG
|
|
99
172
|
new(width, height, pixels)
|
100
173
|
end
|
101
174
|
|
102
|
-
|
103
|
-
|
175
|
+
# Decodes filtered bytes from a scanline from a PNG pixelstream,
|
176
|
+
# to return the original bytes of the image.
|
177
|
+
#
|
178
|
+
# The decoded bytes should be used to get the original pixels of the
|
179
|
+
# scanline, combining them using a color mode dependent color decoder.
|
180
|
+
#
|
181
|
+
# @param [Integer] filter The filter used to encode the bytes.
|
182
|
+
# @param [Array<Fixnum>] bytes The filtered bytes to decode.
|
183
|
+
# @param [Array<Fixnum>] previous_bytes The decoded bytes of the
|
184
|
+
# previous scanline.
|
185
|
+
# @param [Integer] pixelsize The amount of bytes used for every pixel.
|
186
|
+
# This depends on the used color mode and color depth.
|
187
|
+
# @return [Array<Fixnum>] The array of original bytes for the scanline,
|
188
|
+
# before they were encoded.
|
104
189
|
def decode_png_scanline(filter, bytes, previous_bytes, pixelsize = 3)
|
105
190
|
case filter
|
106
191
|
when ChunkyPNG::FILTER_NONE then decode_png_scanline_none( bytes, previous_bytes, pixelsize)
|
@@ -112,20 +197,44 @@ module ChunkyPNG
|
|
112
197
|
end
|
113
198
|
end
|
114
199
|
|
200
|
+
# Decoded filtered scanline bytes that were not filtered.
|
201
|
+
# @param bytes (see ChunkyPNG::Canvas::PNGDecoding#decode_png_scanline)
|
202
|
+
# @param previous_bytes (see ChunkyPNG::Canvas::PNGDecoding#decode_png_scanline)
|
203
|
+
# @param pixelsize (see ChunkyPNG::Canvas::PNGDecoding#decode_png_scanline)
|
204
|
+
# @return (see ChunkyPNG::Canvas::PNGDecoding#decode_png_scanline)
|
205
|
+
# @see ChunkyPNG::Canvas::PNGDecoding#decode_png_scanline
|
115
206
|
def decode_png_scanline_none(bytes, previous_bytes, pixelsize = 3)
|
116
207
|
bytes
|
117
208
|
end
|
118
209
|
|
210
|
+
# Decoded filtered scanline bytes that were filtered using SUB filtering.
|
211
|
+
# @param bytes (see ChunkyPNG::Canvas::PNGDecoding#decode_png_scanline)
|
212
|
+
# @param previous_bytes (see ChunkyPNG::Canvas::PNGDecoding#decode_png_scanline)
|
213
|
+
# @param pixelsize (see ChunkyPNG::Canvas::PNGDecoding#decode_png_scanline)
|
214
|
+
# @return (see ChunkyPNG::Canvas::PNGDecoding#decode_png_scanline)
|
215
|
+
# @see ChunkyPNG::Canvas::PNGDecoding#decode_png_scanline
|
119
216
|
def decode_png_scanline_sub(bytes, previous_bytes, pixelsize = 3)
|
120
217
|
bytes.each_with_index { |b, i| bytes[i] = (b + (i >= pixelsize ? bytes[i-pixelsize] : 0)) % 256 }
|
121
218
|
bytes
|
122
219
|
end
|
123
220
|
|
221
|
+
# Decoded filtered scanline bytes that were filtered using UP filtering.
|
222
|
+
# @param bytes (see ChunkyPNG::Canvas::PNGDecoding#decode_png_scanline)
|
223
|
+
# @param previous_bytes (see ChunkyPNG::Canvas::PNGDecoding#decode_png_scanline)
|
224
|
+
# @param pixelsize (see ChunkyPNG::Canvas::PNGDecoding#decode_png_scanline)
|
225
|
+
# @return (see ChunkyPNG::Canvas::PNGDecoding#decode_png_scanline)
|
226
|
+
# @see ChunkyPNG::Canvas::PNGDecoding#decode_png_scanline
|
124
227
|
def decode_png_scanline_up(bytes, previous_bytes, pixelsize = 3)
|
125
228
|
bytes.each_with_index { |b, i| bytes[i] = (b + previous_bytes[i]) % 256 }
|
126
229
|
bytes
|
127
230
|
end
|
128
231
|
|
232
|
+
# Decoded filtered scanline bytes that were filtered using AVERAGE filtering.
|
233
|
+
# @param bytes (see ChunkyPNG::Canvas::PNGDecoding#decode_png_scanline)
|
234
|
+
# @param previous_bytes (see ChunkyPNG::Canvas::PNGDecoding#decode_png_scanline)
|
235
|
+
# @param pixelsize (see ChunkyPNG::Canvas::PNGDecoding#decode_png_scanline)
|
236
|
+
# @return (see ChunkyPNG::Canvas::PNGDecoding#decode_png_scanline)
|
237
|
+
# @see ChunkyPNG::Canvas::PNGDecoding#decode_png_scanline
|
129
238
|
def decode_png_scanline_average(bytes, previous_bytes, pixelsize = 3)
|
130
239
|
bytes.each_with_index do |byte, i|
|
131
240
|
a = (i >= pixelsize) ? bytes[i - pixelsize] : 0
|
@@ -135,6 +244,12 @@ module ChunkyPNG
|
|
135
244
|
bytes
|
136
245
|
end
|
137
246
|
|
247
|
+
# Decoded filtered scanline bytes that were filtered using PAETH filtering.
|
248
|
+
# @param bytes (see ChunkyPNG::Canvas::PNGDecoding#decode_png_scanline)
|
249
|
+
# @param previous_bytes (see ChunkyPNG::Canvas::PNGDecoding#decode_png_scanline)
|
250
|
+
# @param pixelsize (see ChunkyPNG::Canvas::PNGDecoding#decode_png_scanline)
|
251
|
+
# @return (see ChunkyPNG::Canvas::PNGDecoding#decode_png_scanline)
|
252
|
+
# @see ChunkyPNG::Canvas::PNGDecoding#decode_png_scanline
|
138
253
|
def decode_png_scanline_paeth(bytes, previous_bytes, pixelsize = 3)
|
139
254
|
bytes.each_with_index do |byte, i|
|
140
255
|
a = (i >= pixelsize) ? bytes[i - pixelsize] : 0
|
@@ -1,20 +1,49 @@
|
|
1
1
|
module ChunkyPNG
|
2
2
|
class Canvas
|
3
3
|
|
4
|
-
# Methods for encoding a Canvas into a PNG datastream
|
4
|
+
# Methods for encoding a Canvas instance into a PNG datastream.
|
5
5
|
#
|
6
|
+
# Overview of the encoding process:
|
7
|
+
#
|
8
|
+
# * The image is split up in scanlines (i.e. rows of pixels);
|
9
|
+
# * Every pixel in this row is converted into bytes, based on the color mode;
|
10
|
+
# * Filter every byte in the row according to the filter method.
|
11
|
+
# * Concatenate all the filtered bytes of every line to a single stream
|
12
|
+
# * Compress the resulting string using deflate compression.
|
13
|
+
# * Split compressed data over one or more PNG chunks.
|
14
|
+
# * These chunks should be embedded in a datastream with at least a IHDR and
|
15
|
+
# IEND chunk and possibly a PLTE chunk.
|
16
|
+
#
|
17
|
+
# For interlaced images, the initial image is first split into 7 subimages.
|
18
|
+
# These images get encoded exectly as above, and the result gets combined
|
19
|
+
# before the compression step.
|
20
|
+
#
|
21
|
+
# @see ChunkyPNG::Canvas::PNGDecoding
|
22
|
+
# @see http://www.w3.org/TR/PNG/ The W3C PNG format specification
|
6
23
|
module PNGEncoding
|
7
24
|
|
25
|
+
# The palette used for encoding the image.This is only in used for images
|
26
|
+
# that get encoded using indexed colors.
|
27
|
+
# @return [ChunkyPNG::Palette]
|
8
28
|
attr_accessor :encoding_palette
|
9
29
|
|
30
|
+
# Writes the canvas to an IO stream, encoded as a PNG image.
|
31
|
+
# @param [IO] io The output stream to write to.
|
32
|
+
# @param constraints (see ChunkyPNG::Canvas::PNGEncoding#to_datastream)
|
10
33
|
def write(io, constraints = {})
|
11
34
|
to_datastream(constraints).write(io)
|
12
35
|
end
|
13
36
|
|
37
|
+
# Writes the canvas to a file, encoded as a PNG image.
|
38
|
+
# @param [String] filname The file to save the PNG image to.
|
39
|
+
# @param constraints (see ChunkyPNG::Canvas::PNGEncoding#to_datastream)
|
14
40
|
def save(filename, constraints = {})
|
15
41
|
File.open(filename, 'wb') { |io| write(io, constraints) }
|
16
42
|
end
|
17
43
|
|
44
|
+
# Encoded the canvas to a PNG formatted string.
|
45
|
+
# @param constraints (see ChunkyPNG::Canvas::PNGEncoding#to_datastream)
|
46
|
+
# @return [String] The PNG encoded canvas as string.
|
18
47
|
def to_blob(constraints = {})
|
19
48
|
to_datastream(constraints).to_blob
|
20
49
|
end
|
@@ -24,33 +53,36 @@ module ChunkyPNG
|
|
24
53
|
|
25
54
|
# Converts this Canvas to a datastream, so that it can be saved as a PNG image.
|
26
55
|
# @param [Hash] constraints The constraints to use when encoding the canvas.
|
56
|
+
# @return [ChunkyPNG::Datastream] The PNG datastream containing the encoded canvas.
|
57
|
+
# @see ChunkyPNG::Canvas::PNGEncoding#determine_png_encoding
|
27
58
|
def to_datastream(constraints = {})
|
28
|
-
|
59
|
+
encoding = determine_png_encoding(constraints)
|
60
|
+
|
29
61
|
ds = Datastream.new
|
30
|
-
ds.header_chunk
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
62
|
+
ds.header_chunk = Chunk::Header.new(:width => width, :height => height,
|
63
|
+
:color => encoding[:color_mode], :interlace => encoding[:interlace])
|
64
|
+
|
65
|
+
if encoding[:color_mode] == ChunkyPNG::COLOR_INDEXED
|
66
|
+
ds.palette_chunk = encoding_palette.to_plte_chunk
|
67
|
+
ds.transparency_chunk = encoding_palette.to_trns_chunk unless encoding_palette.opaque?
|
68
|
+
end
|
69
|
+
|
70
|
+
data = encode_png_pixelstream(encoding[:color_mode], encoding[:interlace])
|
71
|
+
ds.data_chunks = Chunk::ImageData.split_in_chunks(data)
|
72
|
+
ds.end_chunk = Chunk::End.new
|
35
73
|
return ds
|
36
74
|
end
|
37
75
|
|
38
76
|
protected
|
39
|
-
|
40
|
-
def encode_png(constraints = {})
|
41
|
-
encoding = determine_png_encoding(constraints)
|
42
|
-
result = {}
|
43
|
-
result[:header] = { :width => width, :height => height, :color => encoding[:color_mode], :interlace => encoding[:interlace] }
|
44
|
-
|
45
|
-
if encoding[:color_mode] == ChunkyPNG::COLOR_INDEXED
|
46
|
-
result[:palette_chunk] = encoding_palette.to_plte_chunk
|
47
|
-
result[:transparency_chunk] = encoding_palette.to_trns_chunk unless encoding_palette.opaque?
|
48
|
-
end
|
49
|
-
|
50
|
-
result[:pixelstream] = encode_png_pixelstream(encoding[:color_mode], encoding[:interlace])
|
51
|
-
return result
|
52
|
-
end
|
53
77
|
|
78
|
+
# Determines the best possible PNG encoding variables for this image, by analyzing
|
79
|
+
# the colors used for the image.
|
80
|
+
#
|
81
|
+
# You can provide constraints for the encoding variables by passing a hash with
|
82
|
+
# encoding variables to this method.
|
83
|
+
#
|
84
|
+
# @param [Hash] constraints The constraints for the encoding.
|
85
|
+
# @return [Hash] A hash with encoding options for {ChunkyPNG::Canvas::PNGEncoding#to_datastream}
|
54
86
|
def determine_png_encoding(constraints = {})
|
55
87
|
|
56
88
|
if constraints == :fast_rgb
|
@@ -80,6 +112,11 @@ module ChunkyPNG
|
|
80
112
|
return encoding
|
81
113
|
end
|
82
114
|
|
115
|
+
# Encodes the canvas according to the PNG format specification with a given color
|
116
|
+
# mode, possibly with interlacing.
|
117
|
+
# @param [Integer] color_mode The color mode to use for encoding.
|
118
|
+
# @param [Integer] interlace The interlacing method to use.
|
119
|
+
# @param [String] The PNG encoded canvas as string.
|
83
120
|
def encode_png_pixelstream(color_mode = ChunkyPNG::COLOR_TRUECOLOR, interlace = ChunkyPNG::INTERLACING_NONE)
|
84
121
|
|
85
122
|
if color_mode == ChunkyPNG::COLOR_INDEXED && (encoding_palette.nil? || !encoding_palette.can_encode?)
|
@@ -93,12 +130,23 @@ module ChunkyPNG
|
|
93
130
|
end
|
94
131
|
end
|
95
132
|
|
133
|
+
# Encodes the canvas according to the PNG format specification with a given color mode.
|
134
|
+
# @param [Integer] color_mode The color mode to use for encoding.
|
135
|
+
# @param [String] Th PNG encoded canvas as string.
|
96
136
|
def encode_png_image_without_interlacing(color_mode)
|
97
137
|
stream = ""
|
98
138
|
encode_png_image_pass_to_stream(stream, color_mode)
|
99
139
|
stream
|
100
140
|
end
|
101
141
|
|
142
|
+
# Encodes the canvas according to the PNG format specification with a given color
|
143
|
+
# mode and Adam7 interlacing.
|
144
|
+
#
|
145
|
+
# This method will split the original canva in 7 smaller canvases and encode them
|
146
|
+
# one by one, concatenating the resulting strings.
|
147
|
+
#
|
148
|
+
# @param [Integer] color_mode The color mode to use for encoding.
|
149
|
+
# @param [String] Th PNG encoded canvas as string.
|
102
150
|
def encode_png_image_with_interlacing(color_mode)
|
103
151
|
stream = ""
|
104
152
|
0.upto(6) do |pass|
|
@@ -109,35 +157,37 @@ module ChunkyPNG
|
|
109
157
|
stream
|
110
158
|
end
|
111
159
|
|
160
|
+
# Encodes the canvas to a stream, in a given color mode.
|
161
|
+
# @param [String, IO, :<<] stream The stream to write to.
|
162
|
+
# @param [Integer] color_mode The color mode to use for encoding.
|
112
163
|
def encode_png_image_pass_to_stream(stream, color_mode)
|
113
164
|
|
114
165
|
case color_mode
|
115
|
-
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
|
120
|
-
|
121
|
-
|
122
|
-
|
123
|
-
|
124
|
-
|
125
|
-
|
126
|
-
|
127
|
-
|
128
|
-
|
129
|
-
|
130
|
-
|
131
|
-
|
132
|
-
|
133
|
-
|
134
|
-
|
135
|
-
|
136
|
-
|
137
|
-
|
138
|
-
previous_bytes = unencoded_bytes
|
139
|
-
end
|
166
|
+
when ChunkyPNG::COLOR_TRUECOLOR_ALPHA
|
167
|
+
stream << pixels.pack("xN#{width}" * height)
|
168
|
+
|
169
|
+
when ChunkyPNG::COLOR_TRUECOLOR
|
170
|
+
line_packer = 'x' + ('NX' * width)
|
171
|
+
stream << pixels.pack(line_packer * height)
|
172
|
+
|
173
|
+
else
|
174
|
+
pixel_size = Color.bytesize(color_mode)
|
175
|
+
pixel_encoder = case color_mode
|
176
|
+
when ChunkyPNG::COLOR_TRUECOLOR then lambda { |color| Color.to_truecolor_bytes(color) }
|
177
|
+
when ChunkyPNG::COLOR_TRUECOLOR_ALPHA then lambda { |color| Color.to_truecolor_alpha_bytes(color) }
|
178
|
+
when ChunkyPNG::COLOR_INDEXED then lambda { |color| [encoding_palette.index(color)] }
|
179
|
+
when ChunkyPNG::COLOR_GRAYSCALE then lambda { |color| Color.to_grayscale_bytes(color) }
|
180
|
+
when ChunkyPNG::COLOR_GRAYSCALE_ALPHA then lambda { |color| Color.to_grayscale_alpha_bytes(color) }
|
181
|
+
else raise "Cannot encode pixels for this mode: #{color_mode}!"
|
182
|
+
end
|
183
|
+
|
184
|
+
previous_bytes = Array.new(pixel_size * width, 0)
|
185
|
+
each_scanline do |line|
|
186
|
+
unencoded_bytes = line.map(&pixel_encoder).flatten
|
187
|
+
stream << encode_png_scanline_up(unencoded_bytes, previous_bytes, pixel_size).pack('C*')
|
188
|
+
previous_bytes = unencoded_bytes
|
140
189
|
end
|
190
|
+
end
|
141
191
|
end
|
142
192
|
|
143
193
|
# Passes to this canvas of pixel values line by line.
|
@@ -150,6 +200,12 @@ module ChunkyPNG
|
|
150
200
|
end
|
151
201
|
end
|
152
202
|
|
203
|
+
# Encodes the bytes of a scanline with a given filter.
|
204
|
+
# @param [Integer] filter The filter method to use.
|
205
|
+
# @param [Array<Fixnum>] bytes The scanline bytes to encode.
|
206
|
+
# @param [Array<Fixnum>] previous_bytes The original bytes of the previous scanline.
|
207
|
+
# @param [Integer] pixelsize The number of bytes per pixel.
|
208
|
+
# @return [Array<Fixnum>] The filtered array of bytes.
|
153
209
|
def encode_png_scanline(filter, bytes, previous_bytes = nil, pixelsize = 3)
|
154
210
|
case filter
|
155
211
|
when ChunkyPNG::FILTER_NONE then encode_png_scanline_none( bytes, previous_bytes, pixelsize)
|
@@ -161,10 +217,17 @@ module ChunkyPNG
|
|
161
217
|
end
|
162
218
|
end
|
163
219
|
|
220
|
+
# Encodes the bytes of a scanline without filtering.
|
221
|
+
# @param [Array<Fixnum>] bytes The scanline bytes to encode.
|
222
|
+
# @param [Array<Fixnum>] previous_bytes The original bytes of the previous scanline.
|
223
|
+
# @param [Integer] pixelsize The number of bytes per pixel.
|
224
|
+
# @return [Array<Fixnum>] The filtered array of bytes.
|
164
225
|
def encode_png_scanline_none(original_bytes, previous_bytes = nil, pixelsize = 3)
|
165
226
|
[ChunkyPNG::FILTER_NONE] + original_bytes
|
166
227
|
end
|
167
228
|
|
229
|
+
# Encodes the bytes of a scanline with SUB filtering.
|
230
|
+
# @param (see ChunkyPNG::Canvas::PNGEncoding#encode_png_scanline_none)
|
168
231
|
def encode_png_scanline_sub(original_bytes, previous_bytes = nil, pixelsize = 3)
|
169
232
|
encoded_bytes = []
|
170
233
|
for index in 0...original_bytes.length do
|
@@ -174,6 +237,8 @@ module ChunkyPNG
|
|
174
237
|
[ChunkyPNG::FILTER_SUB] + encoded_bytes
|
175
238
|
end
|
176
239
|
|
240
|
+
# Encodes the bytes of a scanline with UP filtering.
|
241
|
+
# @param (see ChunkyPNG::Canvas::PNGEncoding#encode_png_scanline_none)
|
177
242
|
def encode_png_scanline_up(original_bytes, previous_bytes, pixelsize = 3)
|
178
243
|
encoded_bytes = []
|
179
244
|
for index in 0...original_bytes.length do
|
@@ -182,7 +247,9 @@ module ChunkyPNG
|
|
182
247
|
end
|
183
248
|
[ChunkyPNG::FILTER_UP] + encoded_bytes
|
184
249
|
end
|
185
|
-
|
250
|
+
|
251
|
+
# Encodes the bytes of a scanline with AVERAGE filtering.
|
252
|
+
# @param (see ChunkyPNG::Canvas::PNGEncoding#encode_png_scanline_none)
|
186
253
|
def encode_png_scanline_average(original_bytes, previous_bytes, pixelsize = 3)
|
187
254
|
encoded_bytes = []
|
188
255
|
for index in 0...original_bytes.length do
|
@@ -192,7 +259,9 @@ module ChunkyPNG
|
|
192
259
|
end
|
193
260
|
[ChunkyPNG::FILTER_AVERAGE] + encoded_bytes
|
194
261
|
end
|
195
|
-
|
262
|
+
|
263
|
+
# Encodes the bytes of a scanline with PAETH filtering.
|
264
|
+
# @param (see ChunkyPNG::Canvas::PNGEncoding#encode_png_scanline_none)
|
196
265
|
def encode_png_scanline_paeth(original_bytes, previous_bytes, pixelsize = 3)
|
197
266
|
encoded_bytes = []
|
198
267
|
for i in 0...original_bytes.length do
|
data/lib/chunky_png/color.rb
CHANGED
@@ -130,6 +130,13 @@ module ChunkyPNG
|
|
130
130
|
a(value) == 0x000000ff
|
131
131
|
end
|
132
132
|
|
133
|
+
# Returns the opaque value of this color by removing the alpha channel.
|
134
|
+
# @param [Fixnum] value The color to transform.
|
135
|
+
# @return [Fixnum] The opauq color
|
136
|
+
def opaque!(value)
|
137
|
+
value | 0x000000ff
|
138
|
+
end
|
139
|
+
|
133
140
|
# Returns true if this color is fully transparent.
|
134
141
|
#
|
135
142
|
# @param [Fixnum] value The color to test.
|
@@ -230,6 +237,25 @@ module ChunkyPNG
|
|
230
237
|
new_alpha = int8_mult(a(color), factor)
|
231
238
|
(color & 0xffffff00) | new_alpha
|
232
239
|
end
|
240
|
+
|
241
|
+
def alpha_decomposable?(color, mask, bg, tolerance = 1)
|
242
|
+
components = decompose_alpha_components(color, mask, bg)
|
243
|
+
sum = components.inject(0) { |a,b| a + b }
|
244
|
+
max = components.max * 3
|
245
|
+
return (sum + tolerance * 3) >= max
|
246
|
+
end
|
247
|
+
|
248
|
+
def decompose_alpha(color, mask, bg)
|
249
|
+
components = decompose_alpha_components(color, mask, bg)
|
250
|
+
(components.inject(0) { |a,b| a + b } / 3.0).round
|
251
|
+
end
|
252
|
+
|
253
|
+
def decompose_alpha_components(color, mask, bg)
|
254
|
+
a_r = ((r(bg) - r(color)).to_f / (r(bg) - r(mask)).to_f * MAX).round
|
255
|
+
a_g = ((g(bg) - g(color)).to_f / (g(bg) - g(mask)).to_f * MAX).round
|
256
|
+
a_b = ((b(bg) - b(color)).to_f / (b(bg) - b(mask)).to_f * MAX).round
|
257
|
+
[a_r, a_g, a_b]
|
258
|
+
end
|
233
259
|
|
234
260
|
####################################################################
|
235
261
|
# CONVERSIONS
|
@@ -91,6 +91,12 @@ describe ChunkyPNG::Color do
|
|
91
91
|
end
|
92
92
|
end
|
93
93
|
|
94
|
+
describe '#decompose_alpha' do
|
95
|
+
it "should decompose the alpha channel correctly" do
|
96
|
+
decompose_alpha(0x9fc2d6ff, @opaque, @white).should == 0x00000064
|
97
|
+
end
|
98
|
+
end
|
99
|
+
|
94
100
|
describe '#blend' do
|
95
101
|
it "should blend colors correctly" do
|
96
102
|
blend(@opaque, @black).should == 0x05324bff
|
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.5.
|
4
|
+
version: 0.5.5
|
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-
|
12
|
+
date: 2010-02-16 00:00:00 +01:00
|
13
13
|
default_executable:
|
14
14
|
dependencies:
|
15
15
|
- !ruby/object:Gem::Dependency
|