chunky_png 1.3.9 → 1.3.14

Sign up to get free protection for your applications and to get access to all the features.
Files changed (72) hide show
  1. checksums.yaml +5 -5
  2. data/.github/workflows/ruby.yml +35 -0
  3. data/.standard.yml +16 -0
  4. data/.yardopts +1 -1
  5. data/CHANGELOG.rdoc +12 -4
  6. data/CONTRIBUTING.rdoc +17 -8
  7. data/Gemfile +15 -3
  8. data/LICENSE +1 -1
  9. data/README.md +15 -9
  10. data/Rakefile +5 -3
  11. data/benchmarks/decoding_benchmark.rb +17 -17
  12. data/benchmarks/encoding_benchmark.rb +22 -19
  13. data/benchmarks/filesize_benchmark.rb +6 -6
  14. data/bin/rake +29 -0
  15. data/bin/standardrb +29 -0
  16. data/chunky_png.gemspec +21 -15
  17. data/docs/.gitignore +3 -0
  18. data/docs/CNAME +1 -0
  19. data/docs/_config.yml +9 -0
  20. data/docs/_posts/2010-01-14-memory-efficiency-when-using-ruby.md +136 -0
  21. data/docs/_posts/2010-01-17-ode-to-array-pack-and-string-unpack.md +82 -0
  22. data/docs/_posts/2014-11-07-the-value-of-a-pure-ruby-library.md +61 -0
  23. data/docs/index.md +88 -0
  24. data/lib/chunky_png.rb +17 -30
  25. data/lib/chunky_png/canvas.rb +31 -28
  26. data/lib/chunky_png/canvas/adam7_interlacing.rb +16 -10
  27. data/lib/chunky_png/canvas/data_url_exporting.rb +3 -3
  28. data/lib/chunky_png/canvas/data_url_importing.rb +3 -3
  29. data/lib/chunky_png/canvas/drawing.rb +30 -43
  30. data/lib/chunky_png/canvas/masking.rb +14 -14
  31. data/lib/chunky_png/canvas/operations.rb +28 -24
  32. data/lib/chunky_png/canvas/png_decoding.rb +39 -33
  33. data/lib/chunky_png/canvas/png_encoding.rb +111 -103
  34. data/lib/chunky_png/canvas/resampling.rb +27 -32
  35. data/lib/chunky_png/canvas/stream_exporting.rb +8 -8
  36. data/lib/chunky_png/canvas/stream_importing.rb +8 -8
  37. data/lib/chunky_png/chunk.rb +101 -66
  38. data/lib/chunky_png/color.rb +218 -212
  39. data/lib/chunky_png/datastream.rb +24 -30
  40. data/lib/chunky_png/dimension.rb +18 -11
  41. data/lib/chunky_png/image.rb +11 -11
  42. data/lib/chunky_png/palette.rb +6 -9
  43. data/lib/chunky_png/point.rb +27 -26
  44. data/lib/chunky_png/rmagick.rb +10 -10
  45. data/lib/chunky_png/vector.rb +28 -29
  46. data/lib/chunky_png/version.rb +3 -1
  47. data/spec/chunky_png/canvas/adam7_interlacing_spec.rb +20 -21
  48. data/spec/chunky_png/canvas/data_url_exporting_spec.rb +8 -5
  49. data/spec/chunky_png/canvas/data_url_importing_spec.rb +5 -6
  50. data/spec/chunky_png/canvas/drawing_spec.rb +46 -38
  51. data/spec/chunky_png/canvas/masking_spec.rb +15 -16
  52. data/spec/chunky_png/canvas/operations_spec.rb +68 -67
  53. data/spec/chunky_png/canvas/png_decoding_spec.rb +37 -38
  54. data/spec/chunky_png/canvas/png_encoding_spec.rb +59 -50
  55. data/spec/chunky_png/canvas/resampling_spec.rb +19 -21
  56. data/spec/chunky_png/canvas/stream_exporting_spec.rb +47 -27
  57. data/spec/chunky_png/canvas/stream_importing_spec.rb +10 -11
  58. data/spec/chunky_png/canvas_spec.rb +63 -52
  59. data/spec/chunky_png/color_spec.rb +115 -114
  60. data/spec/chunky_png/datastream_spec.rb +55 -51
  61. data/spec/chunky_png/dimension_spec.rb +10 -10
  62. data/spec/chunky_png/image_spec.rb +11 -14
  63. data/spec/chunky_png/point_spec.rb +21 -23
  64. data/spec/chunky_png/rmagick_spec.rb +7 -8
  65. data/spec/chunky_png/vector_spec.rb +21 -17
  66. data/spec/chunky_png_spec.rb +2 -2
  67. data/spec/png_suite_spec.rb +35 -40
  68. data/spec/spec_helper.rb +6 -10
  69. data/tasks/benchmarks.rake +7 -8
  70. metadata +50 -12
  71. data/.travis.yml +0 -16
  72. data/lib/chunky_png/compatibility.rb +0 -15
@@ -1,10 +1,8 @@
1
-
2
-
1
+ # frozen-string-literal: true
3
2
 
4
3
  module ChunkyPNG
5
4
  class Canvas
6
-
7
- # The ChunkyPNG::Canvas::Resampling module defines methods to perform image resampling to
5
+ # The ChunkyPNG::Canvas::Resampling module defines methods to perform image resampling to
8
6
  # a {ChunkyPNG::Canvas}.
9
7
  #
10
8
  # Currently, only the nearest neighbor algorithm is implemented. Bilinear and cubic
@@ -12,22 +10,21 @@ module ChunkyPNG
12
10
  #
13
11
  # @see ChunkyPNG::Canvas
14
12
  module Resampling
15
-
16
13
  # Integer Interpolation between two values
17
14
  #
18
15
  # Used for generating indicies for interpolation (eg, nearest
19
16
  # neighbour).
20
17
  #
21
- # @param [Integer] width The width of the source
18
+ # @param [Integer] width The width of the source
22
19
  # @param [Integer] new_width The width of the destination
23
20
  # @return [Array<Integer>] An Array of Integer indicies
24
21
  def steps(width, new_width)
25
22
  indicies, residues = steps_residues(width, new_width)
26
-
23
+
27
24
  for i in 1..new_width
28
- indicies[i-1] = (indicies[i-1] + (residues[i-1] + 127)/255)
25
+ indicies[i - 1] = (indicies[i - 1] + (residues[i - 1] + 127) / 255)
29
26
  end
30
- return indicies
27
+ indicies
31
28
  end
32
29
 
33
30
  # Fractional Interpolation between two values
@@ -39,9 +36,9 @@ module ChunkyPNG
39
36
  # @param [Integer] new_width The width of the destination
40
37
  # @return [Array<Integer>, Array<Integer>] Two arrays of indicies and residues
41
38
  def steps_residues(width, new_width)
42
- indicies = Array.new(new_width, obj=nil)
43
- residues = Array.new(new_width, obj=nil)
44
-
39
+ indicies = Array.new(new_width, nil)
40
+ residues = Array.new(new_width, nil)
41
+
45
42
  # This works by accumulating the fractional error and
46
43
  # overflowing when necessary.
47
44
 
@@ -49,15 +46,15 @@ module ChunkyPNG
49
46
  # 2 * new_width
50
47
  base_step = width / new_width
51
48
  err_step = (width % new_width) << 1
52
- denominator = (new_width) << 1
53
-
49
+ denominator = new_width << 1
50
+
54
51
  # Initial pixel
55
52
  index = (width - new_width) / denominator
56
53
  err = (width - new_width) % denominator
57
54
 
58
55
  for i in 1..new_width
59
- indicies[i-1] = index
60
- residues[i-1] = (255.0 * err.to_f / denominator.to_f).round
56
+ indicies[i - 1] = index
57
+ residues[i - 1] = (255.0 * err.to_f / denominator.to_f).round
61
58
 
62
59
  index += base_step
63
60
  err += err_step
@@ -67,10 +64,9 @@ module ChunkyPNG
67
64
  end
68
65
  end
69
66
 
70
- return indicies, residues
67
+ [indicies, residues]
71
68
  end
72
69
 
73
-
74
70
  # Resamples the canvas using nearest neighbor interpolation.
75
71
  # @param [Integer] new_width The width of the resampled canvas.
76
72
  # @param [Integer] new_height The height of the resampled canvas.
@@ -79,8 +75,7 @@ module ChunkyPNG
79
75
  steps_x = steps(width, new_width)
80
76
  steps_y = steps(height, new_height)
81
77
 
82
-
83
- pixels = Array.new(new_width*new_height)
78
+ pixels = Array.new(new_width * new_height)
84
79
  i = 0
85
80
  for y in steps_y
86
81
  for x in steps_x
@@ -88,10 +83,10 @@ module ChunkyPNG
88
83
  i += 1
89
84
  end
90
85
  end
91
-
86
+
92
87
  replace_canvas!(new_width.to_i, new_height.to_i, pixels)
93
88
  end
94
-
89
+
95
90
  def resample_nearest_neighbor(new_width, new_height)
96
91
  dup.resample_nearest_neighbor!(new_width, new_height)
97
92
  end
@@ -104,19 +99,19 @@ module ChunkyPNG
104
99
  index_x, interp_x = steps_residues(width, new_width)
105
100
  index_y, interp_y = steps_residues(height, new_height)
106
101
 
107
- pixels = Array.new(new_width*new_height)
102
+ pixels = Array.new(new_width * new_height)
108
103
  i = 0
109
104
  for y in 1..new_height
110
105
  # Clamp the indicies to the edges of the image
111
- y1 = [index_y[y-1], 0].max
112
- y2 = [index_y[y-1] + 1, height - 1].min
113
- y_residue = interp_y[y-1]
106
+ y1 = [index_y[y - 1], 0].max
107
+ y2 = [index_y[y - 1] + 1, height - 1].min
108
+ y_residue = interp_y[y - 1]
114
109
 
115
110
  for x in 1..new_width
116
111
  # Clamp the indicies to the edges of the image
117
- x1 = [index_x[x-1], 0].max
118
- x2 = [index_x[x-1] + 1, width - 1].min
119
- x_residue = interp_x[x-1]
112
+ x1 = [index_x[x - 1], 0].max
113
+ x2 = [index_x[x - 1] + 1, width - 1].min
114
+ x_residue = interp_x[x - 1]
120
115
 
121
116
  pixel_11 = get_pixel(x1, y1)
122
117
  pixel_21 = get_pixel(x2, y1)
@@ -139,9 +134,9 @@ module ChunkyPNG
139
134
  def resample_bilinear(new_width, new_height)
140
135
  dup.resample_bilinear!(new_width, new_height)
141
136
  end
142
-
143
- alias_method :resample, :resample_nearest_neighbor
144
- alias_method :resize, :resample
137
+
138
+ alias resample resample_nearest_neighbor
139
+ alias resize resample
145
140
  end
146
141
  end
147
142
  end
@@ -1,9 +1,9 @@
1
+ # frozen-string-literal: true
2
+
1
3
  module ChunkyPNG
2
4
  class Canvas
3
-
4
5
  # Methods to save load a canvas from to stream, encoded in RGB, RGBA, BGR or ABGR format.
5
6
  module StreamExporting
6
-
7
7
  # Creates an RGB-formatted pixelstream with the pixel data from this canvas.
8
8
  #
9
9
  # Note that this format is fast but bloated, because no compression is used
@@ -12,7 +12,7 @@ module ChunkyPNG
12
12
  #
13
13
  # @return [String] The RGBA-formatted pixel data.
14
14
  def to_rgba_stream
15
- pixels.pack('N*')
15
+ pixels.pack("N*")
16
16
  end
17
17
 
18
18
  # Creates an RGB-formatted pixelstream with the pixel data from this canvas.
@@ -23,14 +23,14 @@ module ChunkyPNG
23
23
  #
24
24
  # @return [String] The RGB-formatted pixel data.
25
25
  def to_rgb_stream
26
- pixels.pack('NX' * pixels.length)
26
+ pixels.pack("NX" * pixels.length)
27
27
  end
28
-
28
+
29
29
  # Creates a stream of the alpha channel of this canvas.
30
30
  #
31
31
  # @return [String] The 0-255 alpha values of all pixels packed as string
32
32
  def to_alpha_channel_stream
33
- pixels.pack('C*')
33
+ pixels.pack("C*")
34
34
  end
35
35
 
36
36
  # Creates a grayscale stream of this canvas.
@@ -40,7 +40,7 @@ module ChunkyPNG
40
40
  #
41
41
  # @return [String] The 0-255 grayscale values of all pixels packed as string.
42
42
  def to_grayscale_stream
43
- pixels.pack('nX' * pixels.length)
43
+ pixels.pack("nX" * pixels.length)
44
44
  end
45
45
 
46
46
  # Creates an ABGR-formatted pixelstream with the pixel data from this canvas.
@@ -51,7 +51,7 @@ module ChunkyPNG
51
51
  #
52
52
  # @return [String] The RGBA-formatted pixel data.
53
53
  def to_abgr_stream
54
- pixels.pack('V*')
54
+ pixels.pack("V*")
55
55
  end
56
56
  end
57
57
  end
@@ -1,9 +1,9 @@
1
+ # frozen-string-literal: true
2
+
1
3
  module ChunkyPNG
2
4
  class Canvas
3
-
4
5
  # Methods to quickly load a canvas from a stream, encoded in RGB, RGBA, BGR or ABGR format.
5
6
  module StreamImporting
6
-
7
7
  # Creates a canvas by reading pixels from an RGB formatted stream with a
8
8
  # provided with and height.
9
9
  #
@@ -18,9 +18,9 @@ module ChunkyPNG
18
18
  def from_rgb_stream(width, height, stream)
19
19
  string = stream.respond_to?(:read) ? stream.read(3 * width * height) : stream.to_s[0, 3 * width * height]
20
20
  string << ChunkyPNG::EXTRA_BYTE # Add a fourth byte to the last RGB triple.
21
- unpacker = 'NX' * (width * height)
21
+ unpacker = "NX" * (width * height)
22
22
  pixels = string.unpack(unpacker).map { |color| color | 0x000000ff }
23
- self.new(width, height, pixels)
23
+ new(width, height, pixels)
24
24
  end
25
25
 
26
26
  # Creates a canvas by reading pixels from an RGBA formatted stream with a
@@ -36,7 +36,7 @@ module ChunkyPNG
36
36
  # @return [ChunkyPNG::Canvas] The newly constructed canvas instance.
37
37
  def from_rgba_stream(width, height, stream)
38
38
  string = stream.respond_to?(:read) ? stream.read(4 * width * height) : stream.to_s[0, 4 * width * height]
39
- self.new(width, height, string.unpack("N*"))
39
+ new(width, height, string.unpack("N*"))
40
40
  end
41
41
 
42
42
  # Creates a canvas by reading pixels from an BGR formatted stream with a
@@ -53,8 +53,8 @@ module ChunkyPNG
53
53
  def from_bgr_stream(width, height, stream)
54
54
  string = ChunkyPNG::EXTRA_BYTE.dup # Add a first byte to the first BGR triple.
55
55
  string << (stream.respond_to?(:read) ? stream.read(3 * width * height) : stream.to_s[0, 3 * width * height])
56
- pixels = string.unpack("@1" << ('XV' * (width * height))).map { |color| color | 0x000000ff }
57
- self.new(width, height, pixels)
56
+ pixels = string.unpack("@1#{"XV" * (width * height)}").map { |color| color | 0x000000ff }
57
+ new(width, height, pixels)
58
58
  end
59
59
 
60
60
  # Creates a canvas by reading pixels from an ARGB formatted stream with a
@@ -70,7 +70,7 @@ module ChunkyPNG
70
70
  # @return [ChunkyPNG::Canvas] The newly constructed canvas instance.
71
71
  def from_abgr_stream(width, height, stream)
72
72
  string = stream.respond_to?(:read) ? stream.read(4 * width * height) : stream.to_s[0, 4 * width * height]
73
- self.new(width, height, string.unpack("V*"))
73
+ new(width, height, string.unpack("V*"))
74
74
  end
75
75
  end
76
76
  end
@@ -1,3 +1,5 @@
1
+ # frozen-string-literal: true
2
+
1
3
  module ChunkyPNG
2
4
  # A PNG datastream consists of multiple chunks. This module, and the classes
3
5
  # contained within, help with handling these chunks. It supports both reading
@@ -16,10 +18,10 @@ module ChunkyPNG
16
18
  # @param io [IO, #read] The IO stream to read from.
17
19
  # @return [ChunkyPNG::Chung::Base] The loaded chunk instance.
18
20
  def self.read(io)
19
- length, type = read_bytes(io, 8).unpack('Na4')
21
+ length, type = read_bytes(io, 8).unpack("Na4")
20
22
 
21
23
  content = read_bytes(io, length)
22
- crc = read_bytes(io, 4).unpack('N').first
24
+ crc = read_bytes(io, 4).unpack("N").first
23
25
  verify_crc!(type, content, crc)
24
26
 
25
27
  CHUNK_TYPES.fetch(type, Generic).read(type, content)
@@ -74,8 +76,8 @@ module ChunkyPNG
74
76
  # @param io [IO] The IO stream to write to.
75
77
  # @param content [String] The content for this chunk.
76
78
  def write_with_crc(io, content)
77
- io << [content.length].pack('N') << type << content
78
- io << [Zlib.crc32(content, Zlib.crc32(type))].pack('N')
79
+ io << [content.length].pack("N") << type << content
80
+ io << [Zlib.crc32(content, Zlib.crc32(type))].pack("N")
79
81
  end
80
82
 
81
83
  # Writes the chunk to the IO stream.
@@ -84,7 +86,7 @@ module ChunkyPNG
84
86
  # and will calculate and append the checksum automatically.
85
87
  # @param io [IO] The IO stream to write to.
86
88
  def write(io)
87
- write_with_crc(io, content || '')
89
+ write_with_crc(io, content || "")
88
90
  end
89
91
  end
90
92
 
@@ -95,8 +97,8 @@ module ChunkyPNG
95
97
  # written by the +write+ method.
96
98
  attr_accessor :content
97
99
 
98
- def initialize(type, content = '')
99
- super(type, :content => content)
100
+ def initialize(type, content = "")
101
+ super(type, content: content)
100
102
  end
101
103
 
102
104
  # Creates an instance, given the chunk's type and content.
@@ -117,12 +119,13 @@ module ChunkyPNG
117
119
  # PNG spec, except for color depth: Only 8-bit depth images are supported.
118
120
  # Note that it is still possible to access the chunk for such an image, but
119
121
  # ChunkyPNG will raise an exception if you try to access the pixel data.
122
+ #
123
+ # @see https://www.w3.org/TR/PNG/#11IHDR
120
124
  class Header < Base
121
- attr_accessor :width, :height, :depth, :color, :compression, :filtering,
122
- :interlace
125
+ attr_accessor :width, :height, :depth, :color, :compression, :filtering, :interlace
123
126
 
124
127
  def initialize(attrs = {})
125
- super('IHDR', attrs)
128
+ super("IHDR", attrs)
126
129
  @depth ||= 8
127
130
  @color ||= ChunkyPNG::COLOR_TRUECOLOR
128
131
  @compression ||= ChunkyPNG::COMPRESSION_DEFAULT
@@ -137,31 +140,41 @@ module ChunkyPNG
137
140
  # @return [ChunkyPNG::Chunk::End] The new Header chunk instance with the
138
141
  # variables set to the values according to the content.
139
142
  def self.read(type, content)
140
- fields = content.unpack('NNC5')
141
- new(:width => fields[0],
142
- :height => fields[1],
143
- :depth => fields[2],
144
- :color => fields[3],
145
- :compression => fields[4],
146
- :filtering => fields[5],
147
- :interlace => fields[6])
143
+ fields = content.unpack("NNC5")
144
+ new(
145
+ width: fields[0],
146
+ height: fields[1],
147
+ depth: fields[2],
148
+ color: fields[3],
149
+ compression: fields[4],
150
+ filtering: fields[5],
151
+ interlace: fields[6]
152
+ )
148
153
  end
149
154
 
150
155
  # Returns the content for this chunk when it gets written to a file, by
151
156
  # packing the image information variables into the correct format.
152
157
  # @return [String] The 13-byte content for the header chunk.
153
158
  def content
154
- [width, height, depth, color, compression, filtering, interlace].
155
- pack('NNC5')
159
+ [
160
+ width,
161
+ height,
162
+ depth,
163
+ color,
164
+ compression,
165
+ filtering,
166
+ interlace,
167
+ ].pack("NNC5")
156
168
  end
157
169
  end
158
170
 
159
171
  # The End (IEND) chunk indicates the last chunk of a PNG stream. It does
160
172
  # not contain any data.
173
+ #
174
+ # @see https://www.w3.org/TR/PNG/#11IEND
161
175
  class End < Base
162
-
163
176
  def initialize
164
- super('IEND')
177
+ super("IEND")
165
178
  end
166
179
 
167
180
  # Reads the END chunk. It will check if the content is empty.
@@ -172,20 +185,21 @@ module ChunkyPNG
172
185
  # @return [ChunkyPNG::Chunk::End] The new End chunk instance.
173
186
  # @raise [ChunkyPNG::ExpectationFailed] Raises an exception if the content was not empty.
174
187
  def self.read(type, content)
175
- raise ExpectationFailed, 'The IEND chunk should be empty!' if content.bytesize > 0
176
- self.new
188
+ raise ExpectationFailed, "The IEND chunk should be empty!" if content.bytesize > 0
189
+ new
177
190
  end
178
191
 
179
192
  # Returns an empty string, because this chunk should always be empty.
180
193
  # @return [""] An empty string.
181
194
  def content
182
- ChunkyPNG::Datastream.empty_bytearray
195
+ "".b
183
196
  end
184
197
  end
185
198
 
186
199
  # The Palette (PLTE) chunk contains the image's palette, i.e. the
187
200
  # 8-bit RGB colors this image is using.
188
201
  #
202
+ # @see https://www.w3.org/TR/PNG/#11PLTE
189
203
  # @see ChunkyPNG::Chunk::Transparency
190
204
  # @see ChunkyPNG::Palette
191
205
  class Palette < Generic
@@ -203,6 +217,7 @@ module ChunkyPNG
203
217
  # Images having a color mode that already includes an alpha channel, this
204
218
  # chunk should not be included.
205
219
  #
220
+ # @see https://www.w3.org/TR/PNG/#11tRNS
206
221
  # @see ChunkyPNG::Chunk::Palette
207
222
  # @see ChunkyPNG::Palette
208
223
  class Transparency < Generic
@@ -214,7 +229,7 @@ module ChunkyPNG
214
229
  # @return [Array<Integer>] Returns an array of alpha channel values
215
230
  # [0-255].
216
231
  def palette_alpha_channel
217
- content.unpack('C*')
232
+ content.unpack("C*")
218
233
  end
219
234
 
220
235
  # Returns the truecolor entry to be replaced by transparent pixels,
@@ -224,9 +239,8 @@ module ChunkyPNG
224
239
  #
225
240
  # @return [Integer] The color to replace with fully transparent pixels.
226
241
  def truecolor_entry(bit_depth)
227
- values = content.unpack('nnn').map do |c|
228
- ChunkyPNG::Canvas.send(:"decode_png_resample_#{bit_depth}bit_value", c)
229
- end
242
+ decode_method_name = :"decode_png_resample_#{bit_depth}bit_value"
243
+ values = content.unpack("nnn").map { |c| ChunkyPNG::Canvas.send(decode_method_name, c) }
230
244
  ChunkyPNG::Color.rgb(*values)
231
245
  end
232
246
 
@@ -238,12 +252,23 @@ module ChunkyPNG
238
252
  # @return [Integer] The (grayscale) color to replace with fully
239
253
  # transparent pixels.
240
254
  def grayscale_entry(bit_depth)
241
- value = ChunkyPNG::Canvas.send(:"decode_png_resample_#{bit_depth}bit_value", content.unpack('n')[0])
255
+ value = ChunkyPNG::Canvas.send(:"decode_png_resample_#{bit_depth}bit_value", content.unpack("n")[0])
242
256
  ChunkyPNG::Color.grayscale(value)
243
257
  end
244
258
  end
245
259
 
260
+ # An image data (IDAT) chunk holds (part of) the compressed image pixel data.
261
+ #
262
+ # The data of an image can be split over multiple chunks, which will have to be combined
263
+ # and inflated in order to decode an image. See {{.combine_chunks}} to combine chunks
264
+ # to decode, and {{.split_in_chunks}} for encoding a pixeldata stream into IDAT chunks.
265
+ #
266
+ # @see https://www.w3.org/TR/PNG/#11IDAT
246
267
  class ImageData < Generic
268
+ # Combines the list of IDAT chunks and inflates their contents to produce the
269
+ # pixeldata stream for the image.
270
+ #
271
+ # @return [String] The combined, inflated pixeldata as binary string
247
272
  def self.combine_chunks(data_chunks)
248
273
  zstream = Zlib::Inflate.new
249
274
  data_chunks.each { |c| zstream << c.content }
@@ -252,31 +277,38 @@ module ChunkyPNG
252
277
  inflated
253
278
  end
254
279
 
280
+ # Splits and compresses a pixeldata stream into a list of IDAT chunks.
281
+ #
282
+ # @param data [String] The binary string of pixeldata
283
+ # @param level [Integer] The compression level to use.
284
+ # @param chunk_size [Integer] The maximum size of a chunk.
285
+ # @return Array<ChunkyPNG::Chunk::ImageData> The list of IDAT chunks.
255
286
  def self.split_in_chunks(data, level = Zlib::DEFAULT_COMPRESSION, chunk_size = 2147483647)
256
287
  streamdata = Zlib::Deflate.deflate(data, level)
257
288
  # TODO: Split long streamdata over multiple chunks
258
- [ ChunkyPNG::Chunk::ImageData.new('IDAT', streamdata) ]
289
+ [ChunkyPNG::Chunk::ImageData.new("IDAT", streamdata)]
259
290
  end
260
291
  end
261
292
 
262
293
  # The Text (tEXt) chunk contains keyword/value metadata about the PNG
263
- # stream. In this chunk, the value is stored uncompressed.
294
+ # stream. In this chunk, the value is stored uncompressed.
264
295
  #
265
296
  # The tEXt chunk only supports Latin-1 encoded textual data. If you need
266
297
  # UTF-8 support, check out the InternationalText chunk type.
267
298
  #
299
+ # @see https://www.w3.org/TR/PNG/#11tEXt
268
300
  # @see ChunkyPNG::Chunk::CompressedText
269
301
  # @see ChunkyPNG::Chunk::InternationalText
270
302
  class Text < Base
271
303
  attr_accessor :keyword, :value
272
304
 
273
305
  def initialize(keyword, value)
274
- super('tEXt')
306
+ super("tEXt")
275
307
  @keyword, @value = keyword, value
276
308
  end
277
309
 
278
310
  def self.read(type, content)
279
- keyword, value = content.unpack('Z*a*')
311
+ keyword, value = content.unpack("Z*a*")
280
312
  new(keyword, value)
281
313
  end
282
314
 
@@ -285,7 +317,7 @@ module ChunkyPNG
285
317
  #
286
318
  # @return The content that should be written to the datastream.
287
319
  def content
288
- [keyword, value].pack('Z*a*')
320
+ [keyword, value].pack("Z*a*")
289
321
  end
290
322
  end
291
323
 
@@ -293,18 +325,19 @@ module ChunkyPNG
293
325
  # PNG stream. In this chunk, the value is compressed using Deflate
294
326
  # compression.
295
327
  #
328
+ # @see https://www.w3.org/TR/PNG/#11zTXt
296
329
  # @see ChunkyPNG::Chunk::CompressedText
297
330
  # @see ChunkyPNG::Chunk::InternationalText
298
331
  class CompressedText < Base
299
332
  attr_accessor :keyword, :value
300
333
 
301
334
  def initialize(keyword, value)
302
- super('zTXt')
335
+ super("zTXt")
303
336
  @keyword, @value = keyword, value
304
337
  end
305
338
 
306
339
  def self.read(type, content)
307
- keyword, compression, value = content.unpack('Z*Ca*')
340
+ keyword, compression, value = content.unpack("Z*Ca*")
308
341
  raise ChunkyPNG::NotSupported, "Compression method #{compression.inspect} not supported!" unless compression == ChunkyPNG::COMPRESSION_DEFAULT
309
342
  new(keyword, Zlib::Inflate.inflate(value))
310
343
  end
@@ -314,36 +347,39 @@ module ChunkyPNG
314
347
  #
315
348
  # @return The content that should be written to the datastream.
316
349
  def content
317
- [keyword, ChunkyPNG::COMPRESSION_DEFAULT, Zlib::Deflate.deflate(value)].
318
- pack('Z*Ca*')
350
+ [
351
+ keyword,
352
+ ChunkyPNG::COMPRESSION_DEFAULT,
353
+ Zlib::Deflate.deflate(value),
354
+ ].pack("Z*Ca*")
319
355
  end
320
356
  end
321
357
 
322
358
  # The Physical (pHYs) chunk specifies the intended pixel size or aspect
323
359
  # ratio for display of the image.
324
360
  #
325
- # http://www.libpng.org/pub/png/spec/1.2/PNG-Chunks.html#C.pHYs
361
+ # @see https://www.w3.org/TR/PNG/#11pHYs
326
362
  class Physical < Base
327
363
  attr_accessor :ppux, :ppuy, :unit
328
364
 
329
365
  def initialize(ppux, ppuy, unit = :unknown)
330
- raise ArgumentError, 'unit must be either :meters or :unknown' unless [:meters, :unknown].member?(unit)
331
- super('pHYs')
366
+ raise ArgumentError, "unit must be either :meters or :unknown" unless [:meters, :unknown].member?(unit)
367
+ super("pHYs")
332
368
  @ppux, @ppuy, @unit = ppux, ppuy, unit
333
369
  end
334
370
 
335
371
  def dpix
336
- raise ChunkyPNG::UnitsUnknown, 'the PNG specifies its physical aspect ratio, but does not specify the units of its pixels\' physical dimensions' unless unit == :meters
372
+ raise ChunkyPNG::UnitsUnknown, "the PNG specifies its physical aspect ratio, but does not specify the units of its pixels' physical dimensions" unless unit == :meters
337
373
  ppux * INCHES_PER_METER
338
374
  end
339
375
 
340
376
  def dpiy
341
- raise ChunkyPNG::UnitsUnknown, 'the PNG specifies its physical aspect ratio, but does not specify the units of its pixels\' physical dimensions' unless unit == :meters
377
+ raise ChunkyPNG::UnitsUnknown, "the PNG specifies its physical aspect ratio, but does not specify the units of its pixels' physical dimensions" unless unit == :meters
342
378
  ppuy * INCHES_PER_METER
343
379
  end
344
380
 
345
381
  def self.read(type, content)
346
- ppux, ppuy, unit = content.unpack('NNC')
382
+ ppux, ppuy, unit = content.unpack("NNC")
347
383
  unit = unit == 1 ? :meters : :unknown
348
384
  new(ppux, ppuy, unit)
349
385
  end
@@ -351,7 +387,7 @@ module ChunkyPNG
351
387
  # Assembles the content to write to the stream for this chunk.
352
388
  # @return [String] The binary content that should be written to the datastream.
353
389
  def content
354
- [ppux, ppuy, unit == :meters ? 1 : 0].pack('NNC')
390
+ [ppux, ppuy, unit == :meters ? 1 : 0].pack("NNC")
355
391
  end
356
392
 
357
393
  INCHES_PER_METER = 0.0254
@@ -365,15 +401,14 @@ module ChunkyPNG
365
401
  # a translation of the keyword name. Finally, it supports bot compressed
366
402
  # and uncompressed values.
367
403
  #
368
- # http://www.libpng.org/pub/png/spec/1.2/PNG-Chunks.html#C.iTXt
369
- #
404
+ # @see https://www.w3.org/TR/PNG/#11iTXt
370
405
  # @see ChunkyPNG::Chunk::Text
371
406
  # @see ChunkyPNG::Chunk::CompressedText
372
407
  class InternationalText < Base
373
408
  attr_accessor :keyword, :text, :language_tag, :translated_keyword, :compressed, :compression
374
409
 
375
- def initialize(keyword, text, language_tag = '', translated_keyword = '', compressed = ChunkyPNG::UNCOMPRESSED_CONTENT, compression = ChunkyPNG::COMPRESSION_DEFAULT)
376
- super('iTXt')
410
+ def initialize(keyword, text, language_tag = "", translated_keyword = "", compressed = ChunkyPNG::UNCOMPRESSED_CONTENT, compression = ChunkyPNG::COMPRESSION_DEFAULT)
411
+ super("iTXt")
377
412
  @keyword = keyword
378
413
  @text = text
379
414
  @language_tag = language_tag
@@ -382,7 +417,7 @@ module ChunkyPNG
382
417
  @compression = compression
383
418
  end
384
419
 
385
- # Reads the tTXt chunk.
420
+ # Reads the iTXt chunk.
386
421
  # @param type [String] The four character chunk type indicator (= "iTXt").
387
422
  # @param content [String] The content read from the chunk.
388
423
  # @return [ChunkyPNG::Chunk::InternationalText] The new End chunk instance.
@@ -390,16 +425,16 @@ module ChunkyPNG
390
425
  # @raise [ChunkyPNG::NotSupported] If the chunk refers to an unsupported compression method.
391
426
  # Currently uncompressed data and deflate are supported.
392
427
  def self.read(type, content)
393
- keyword, compressed, compression, language_tag, translated_keyword, text = content.unpack('Z*CCZ*Z*a*')
428
+ keyword, compressed, compression, language_tag, translated_keyword, text = content.unpack("Z*CCZ*Z*a*")
394
429
  raise ChunkyPNG::NotSupported, "Compression flag #{compressed.inspect} not supported!" unless compressed == ChunkyPNG::UNCOMPRESSED_CONTENT || compressed == ChunkyPNG::COMPRESSED_CONTENT
395
430
  raise ChunkyPNG::NotSupported, "Compression method #{compression.inspect} not supported!" unless compression == ChunkyPNG::COMPRESSION_DEFAULT
396
431
 
397
432
  text = Zlib::Inflate.inflate(text) if compressed == ChunkyPNG::COMPRESSED_CONTENT
398
433
 
399
- text.force_encoding('utf-8')
434
+ text.force_encoding("utf-8")
400
435
  raise ChunkyPNG::InvalidUTF8, "Invalid unicode encountered in iTXt chunk" unless text.valid_encoding?
401
436
 
402
- translated_keyword.force_encoding('utf-8')
437
+ translated_keyword.force_encoding("utf-8")
403
438
  raise ChunkyPNG::InvalidUTF8, "Invalid unicode encountered in iTXt chunk" unless translated_keyword.valid_encoding?
404
439
 
405
440
  new(keyword, text, language_tag, translated_keyword, compressed, compression)
@@ -408,10 +443,10 @@ module ChunkyPNG
408
443
  # Assembles the content to write to the stream for this chunk.
409
444
  # @return [String] The binary content that should be written to the datastream.
410
445
  def content
411
- text_field = text.encode('utf-8')
412
- text_field = (compressed == ChunkyPNG::COMPRESSED_CONTENT) ? Zlib::Deflate.deflate(text_field) : text_field
446
+ text_field = text.encode("utf-8")
447
+ text_field = compressed == ChunkyPNG::COMPRESSED_CONTENT ? Zlib::Deflate.deflate(text_field) : text_field
413
448
 
414
- [keyword, compressed, compression, language_tag, translated_keyword.encode('utf-8'), text_field].pack('Z*CCZ*Z*a*')
449
+ [keyword, compressed, compression, language_tag, translated_keyword.encode("utf-8"), text_field].pack("Z*CCZ*Z*a*")
415
450
  end
416
451
  end
417
452
 
@@ -423,15 +458,15 @@ module ChunkyPNG
423
458
  #
424
459
  # @see ChunkyPNG::Chunk.read
425
460
  CHUNK_TYPES = {
426
- 'IHDR' => Header,
427
- 'IEND' => End,
428
- 'IDAT' => ImageData,
429
- 'PLTE' => Palette,
430
- 'tRNS' => Transparency,
431
- 'tEXt' => Text,
432
- 'zTXt' => CompressedText,
433
- 'iTXt' => InternationalText,
434
- 'pHYs' => Physical,
461
+ "IHDR" => Header,
462
+ "IEND" => End,
463
+ "IDAT" => ImageData,
464
+ "PLTE" => Palette,
465
+ "tRNS" => Transparency,
466
+ "tEXt" => Text,
467
+ "zTXt" => CompressedText,
468
+ "iTXt" => InternationalText,
469
+ "pHYs" => Physical,
435
470
  }
436
471
  end
437
472
  end