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.
@@ -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.4"
7
- s.date = "2010-01-18"
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
@@ -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.4"
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 Canvas object.
5
- # The datastream can be provided as filename, string or IO object.
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
- data = encode_png(constraints)
59
+ encoding = determine_png_encoding(constraints)
60
+
29
61
  ds = Datastream.new
30
- ds.header_chunk = Chunk::Header.new(data[:header])
31
- ds.palette_chunk = data[:palette_chunk] if data[:palette_chunk]
32
- ds.transparency_chunk = data[:transparency_chunk] if data[:transparency_chunk]
33
- ds.data_chunks = Chunk::ImageData.split_in_chunks(data[:pixelstream])
34
- ds.end_chunk = Chunk::End.new
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
- when ChunkyPNG::COLOR_TRUECOLOR_ALPHA
116
- stream << pixels.pack("xN#{width}" * height)
117
-
118
- when ChunkyPNG::COLOR_TRUECOLOR
119
- line_packer = 'x' + ('NX' * width)
120
- stream << pixels.pack(line_packer * height)
121
-
122
- else
123
-
124
- pixel_size = Color.bytesize(color_mode)
125
- pixel_encoder = case color_mode
126
- when ChunkyPNG::COLOR_TRUECOLOR then lambda { |color| Color.to_truecolor_bytes(color) }
127
- when ChunkyPNG::COLOR_TRUECOLOR_ALPHA then lambda { |color| Color.to_truecolor_alpha_bytes(color) }
128
- when ChunkyPNG::COLOR_INDEXED then lambda { |color| [encoding_palette.index(color)] }
129
- when ChunkyPNG::COLOR_GRAYSCALE then lambda { |color| Color.to_grayscale_bytes(color) }
130
- when ChunkyPNG::COLOR_GRAYSCALE_ALPHA then lambda { |color| Color.to_grayscale_alpha_bytes(color) }
131
- else raise "Cannot encode pixels for this mode: #{color_mode}!"
132
- end
133
-
134
- previous_bytes = Array.new(pixel_size * width, 0)
135
- each_scanline do |line|
136
- unencoded_bytes = line.map(&pixel_encoder).flatten
137
- stream << encode_png_scanline_up(unencoded_bytes, previous_bytes, pixel_size).pack('C*')
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
@@ -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
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-01-18 00:00:00 +01:00
12
+ date: 2010-02-16 00:00:00 +01:00
13
13
  default_executable:
14
14
  dependencies:
15
15
  - !ruby/object:Gem::Dependency