chunky_png 0.5.4 → 0.5.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/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
|