chunky_png 1.3.8 → 1.3.13

Sign up to get free protection for your applications and to get access to all the features.
Files changed (66) 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 +13 -1
  6. data/CONTRIBUTING.rdoc +17 -8
  7. data/Gemfile +5 -3
  8. data/LICENSE +1 -1
  9. data/README.md +6 -1
  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 +20 -14
  17. data/lib/chunky_png.rb +46 -45
  18. data/lib/chunky_png/canvas.rb +30 -27
  19. data/lib/chunky_png/canvas/adam7_interlacing.rb +16 -10
  20. data/lib/chunky_png/canvas/data_url_exporting.rb +3 -3
  21. data/lib/chunky_png/canvas/data_url_importing.rb +3 -3
  22. data/lib/chunky_png/canvas/drawing.rb +30 -43
  23. data/lib/chunky_png/canvas/masking.rb +14 -14
  24. data/lib/chunky_png/canvas/operations.rb +28 -24
  25. data/lib/chunky_png/canvas/png_decoding.rb +38 -32
  26. data/lib/chunky_png/canvas/png_encoding.rb +110 -102
  27. data/lib/chunky_png/canvas/resampling.rb +27 -32
  28. data/lib/chunky_png/canvas/stream_exporting.rb +8 -8
  29. data/lib/chunky_png/canvas/stream_importing.rb +8 -8
  30. data/lib/chunky_png/chunk.rb +115 -66
  31. data/lib/chunky_png/color.rb +213 -206
  32. data/lib/chunky_png/datastream.rb +24 -30
  33. data/lib/chunky_png/dimension.rb +18 -11
  34. data/lib/chunky_png/image.rb +11 -11
  35. data/lib/chunky_png/palette.rb +6 -9
  36. data/lib/chunky_png/point.rb +27 -26
  37. data/lib/chunky_png/rmagick.rb +10 -10
  38. data/lib/chunky_png/vector.rb +28 -29
  39. data/lib/chunky_png/version.rb +3 -1
  40. data/spec/chunky_png/canvas/adam7_interlacing_spec.rb +20 -21
  41. data/spec/chunky_png/canvas/data_url_exporting_spec.rb +8 -5
  42. data/spec/chunky_png/canvas/data_url_importing_spec.rb +5 -6
  43. data/spec/chunky_png/canvas/drawing_spec.rb +46 -38
  44. data/spec/chunky_png/canvas/masking_spec.rb +15 -16
  45. data/spec/chunky_png/canvas/operations_spec.rb +68 -67
  46. data/spec/chunky_png/canvas/png_decoding_spec.rb +37 -38
  47. data/spec/chunky_png/canvas/png_encoding_spec.rb +59 -50
  48. data/spec/chunky_png/canvas/resampling_spec.rb +19 -21
  49. data/spec/chunky_png/canvas/stream_exporting_spec.rb +47 -27
  50. data/spec/chunky_png/canvas/stream_importing_spec.rb +10 -11
  51. data/spec/chunky_png/canvas_spec.rb +57 -52
  52. data/spec/chunky_png/color_spec.rb +115 -114
  53. data/spec/chunky_png/datastream_spec.rb +98 -19
  54. data/spec/chunky_png/dimension_spec.rb +10 -10
  55. data/spec/chunky_png/image_spec.rb +11 -14
  56. data/spec/chunky_png/point_spec.rb +21 -23
  57. data/spec/chunky_png/rmagick_spec.rb +7 -8
  58. data/spec/chunky_png/vector_spec.rb +21 -17
  59. data/spec/chunky_png_spec.rb +2 -2
  60. data/spec/png_suite_spec.rb +35 -40
  61. data/spec/resources/itxt_chunk.png +0 -0
  62. data/spec/spec_helper.rb +15 -9
  63. data/tasks/benchmarks.rake +7 -8
  64. metadata +53 -20
  65. data/.travis.yml +0 -16
  66. 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.
@@ -118,11 +120,10 @@ module ChunkyPNG
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.
120
122
  class Header < Base
121
- attr_accessor :width, :height, :depth, :color, :compression, :filtering,
122
- :interlace
123
+ attr_accessor :width, :height, :depth, :color, :compression, :filtering, :interlace
123
124
 
124
125
  def initialize(attrs = {})
125
- super('IHDR', attrs)
126
+ super("IHDR", attrs)
126
127
  @depth ||= 8
127
128
  @color ||= ChunkyPNG::COLOR_TRUECOLOR
128
129
  @compression ||= ChunkyPNG::COMPRESSION_DEFAULT
@@ -137,31 +138,39 @@ module ChunkyPNG
137
138
  # @return [ChunkyPNG::Chunk::End] The new Header chunk instance with the
138
139
  # variables set to the values according to the content.
139
140
  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])
141
+ fields = content.unpack("NNC5")
142
+ new(
143
+ width: fields[0],
144
+ height: fields[1],
145
+ depth: fields[2],
146
+ color: fields[3],
147
+ compression: fields[4],
148
+ filtering: fields[5],
149
+ interlace: fields[6]
150
+ )
148
151
  end
149
152
 
150
153
  # Returns the content for this chunk when it gets written to a file, by
151
154
  # packing the image information variables into the correct format.
152
155
  # @return [String] The 13-byte content for the header chunk.
153
156
  def content
154
- [width, height, depth, color, compression, filtering, interlace].
155
- pack('NNC5')
157
+ [
158
+ width,
159
+ height,
160
+ depth,
161
+ color,
162
+ compression,
163
+ filtering,
164
+ interlace,
165
+ ].pack("NNC5")
156
166
  end
157
167
  end
158
168
 
159
169
  # The End (IEND) chunk indicates the last chunk of a PNG stream. It does
160
170
  # not contain any data.
161
171
  class End < Base
162
-
163
172
  def initialize
164
- super('IEND')
173
+ super("IEND")
165
174
  end
166
175
 
167
176
  # Reads the END chunk. It will check if the content is empty.
@@ -170,16 +179,16 @@ module ChunkyPNG
170
179
  # @param content [String] The content read from the chunk. Should be
171
180
  # empty.
172
181
  # @return [ChunkyPNG::Chunk::End] The new End chunk instance.
173
- # @raise [RuntimeError] Raises an exception if the content was not empty.
182
+ # @raise [ChunkyPNG::ExpectationFailed] Raises an exception if the content was not empty.
174
183
  def self.read(type, content)
175
- raise ExpectationFailed, 'The IEND chunk should be empty!' if content.bytesize > 0
176
- self.new
184
+ raise ExpectationFailed, "The IEND chunk should be empty!" if content.bytesize > 0
185
+ new
177
186
  end
178
187
 
179
188
  # Returns an empty string, because this chunk should always be empty.
180
189
  # @return [""] An empty string.
181
190
  def content
182
- ChunkyPNG::Datastream.empty_bytearray
191
+ "".b
183
192
  end
184
193
  end
185
194
 
@@ -214,7 +223,7 @@ module ChunkyPNG
214
223
  # @return [Array<Integer>] Returns an array of alpha channel values
215
224
  # [0-255].
216
225
  def palette_alpha_channel
217
- content.unpack('C*')
226
+ content.unpack("C*")
218
227
  end
219
228
 
220
229
  # Returns the truecolor entry to be replaced by transparent pixels,
@@ -224,9 +233,8 @@ module ChunkyPNG
224
233
  #
225
234
  # @return [Integer] The color to replace with fully transparent pixels.
226
235
  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
236
+ decode_method_name = :"decode_png_resample_#{bit_depth}bit_value"
237
+ values = content.unpack("nnn").map { |c| ChunkyPNG::Canvas.send(decode_method_name, c) }
230
238
  ChunkyPNG::Color.rgb(*values)
231
239
  end
232
240
 
@@ -238,7 +246,7 @@ module ChunkyPNG
238
246
  # @return [Integer] The (grayscale) color to replace with fully
239
247
  # transparent pixels.
240
248
  def grayscale_entry(bit_depth)
241
- value = ChunkyPNG::Canvas.send(:"decode_png_resample_#{bit_depth}bit_value", content.unpack('n')[0])
249
+ value = ChunkyPNG::Canvas.send(:"decode_png_resample_#{bit_depth}bit_value", content.unpack("n")[0])
242
250
  ChunkyPNG::Color.grayscale(value)
243
251
  end
244
252
  end
@@ -255,7 +263,7 @@ module ChunkyPNG
255
263
  def self.split_in_chunks(data, level = Zlib::DEFAULT_COMPRESSION, chunk_size = 2147483647)
256
264
  streamdata = Zlib::Deflate.deflate(data, level)
257
265
  # TODO: Split long streamdata over multiple chunks
258
- [ ChunkyPNG::Chunk::ImageData.new('IDAT', streamdata) ]
266
+ [ChunkyPNG::Chunk::ImageData.new("IDAT", streamdata)]
259
267
  end
260
268
  end
261
269
 
@@ -271,12 +279,12 @@ module ChunkyPNG
271
279
  attr_accessor :keyword, :value
272
280
 
273
281
  def initialize(keyword, value)
274
- super('tEXt')
282
+ super("tEXt")
275
283
  @keyword, @value = keyword, value
276
284
  end
277
285
 
278
286
  def self.read(type, content)
279
- keyword, value = content.unpack('Z*a*')
287
+ keyword, value = content.unpack("Z*a*")
280
288
  new(keyword, value)
281
289
  end
282
290
 
@@ -285,7 +293,7 @@ module ChunkyPNG
285
293
  #
286
294
  # @return The content that should be written to the datastream.
287
295
  def content
288
- [keyword, value].pack('Z*a*')
296
+ [keyword, value].pack("Z*a*")
289
297
  end
290
298
  end
291
299
 
@@ -299,12 +307,12 @@ module ChunkyPNG
299
307
  attr_accessor :keyword, :value
300
308
 
301
309
  def initialize(keyword, value)
302
- super('zTXt')
310
+ super("zTXt")
303
311
  @keyword, @value = keyword, value
304
312
  end
305
313
 
306
314
  def self.read(type, content)
307
- keyword, compression, value = content.unpack('Z*Ca*')
315
+ keyword, compression, value = content.unpack("Z*Ca*")
308
316
  raise ChunkyPNG::NotSupported, "Compression method #{compression.inspect} not supported!" unless compression == ChunkyPNG::COMPRESSION_DEFAULT
309
317
  new(keyword, Zlib::Inflate.inflate(value))
310
318
  end
@@ -314,8 +322,11 @@ module ChunkyPNG
314
322
  #
315
323
  # @return The content that should be written to the datastream.
316
324
  def content
317
- [keyword, ChunkyPNG::COMPRESSION_DEFAULT, Zlib::Deflate.deflate(value)].
318
- pack('Z*Ca*')
325
+ [
326
+ keyword,
327
+ ChunkyPNG::COMPRESSION_DEFAULT,
328
+ Zlib::Deflate.deflate(value),
329
+ ].pack("Z*Ca*")
319
330
  end
320
331
  end
321
332
 
@@ -323,58 +334,96 @@ module ChunkyPNG
323
334
  # ratio for display of the image.
324
335
  #
325
336
  # http://www.libpng.org/pub/png/spec/1.2/PNG-Chunks.html#C.pHYs
326
- #
327
- class Physical < Generic
337
+ class Physical < Base
328
338
  attr_accessor :ppux, :ppuy, :unit
329
339
 
330
340
  def initialize(ppux, ppuy, unit = :unknown)
331
- raise ArgumentError, 'unit must be either :meters or :unknown' unless [:meters, :unknown].member?(unit)
332
- super('pHYs')
341
+ raise ArgumentError, "unit must be either :meters or :unknown" unless [:meters, :unknown].member?(unit)
342
+ super("pHYs")
333
343
  @ppux, @ppuy, @unit = ppux, ppuy, unit
334
344
  end
335
345
 
336
346
  def dpix
337
- raise ChunkyPNG::UnitsUnknown, 'the PNG specifies its physical aspect ratio, but does not specify the units of its pixels\' physical dimensions' unless unit == :meters
347
+ raise ChunkyPNG::UnitsUnknown, "the PNG specifies its physical aspect ratio, but does not specify the units of its pixels' physical dimensions" unless unit == :meters
338
348
  ppux * INCHES_PER_METER
339
349
  end
340
350
 
341
351
  def dpiy
342
- raise ChunkyPNG::UnitsUnknown, 'the PNG specifies its physical aspect ratio, but does not specify the units of its pixels\' physical dimensions' unless unit == :meters
352
+ raise ChunkyPNG::UnitsUnknown, "the PNG specifies its physical aspect ratio, but does not specify the units of its pixels' physical dimensions" unless unit == :meters
343
353
  ppuy * INCHES_PER_METER
344
354
  end
345
355
 
346
356
  def self.read(type, content)
347
- ppux, ppuy, unit = content.unpack('NNC')
357
+ ppux, ppuy, unit = content.unpack("NNC")
348
358
  unit = unit == 1 ? :meters : :unknown
349
359
  new(ppux, ppuy, unit)
350
360
  end
351
361
 
352
- # Creates the content to write to the stream, by concatenating the
353
- # keyword with the deflated value, joined by a null character.
354
- #
355
- # @return The content that should be written to the datastream.
362
+ # Assembles the content to write to the stream for this chunk.
363
+ # @return [String] The binary content that should be written to the datastream.
356
364
  def content
357
- [ppux, ppuy, unit == :meters ? 1 : 0].pack('NNC')
365
+ [ppux, ppuy, unit == :meters ? 1 : 0].pack("NNC")
358
366
  end
359
367
 
360
368
  INCHES_PER_METER = 0.0254
361
369
  end
362
370
 
363
- # The Text (iTXt) chunk contains keyword/value metadata about the PNG
364
- # stream.
371
+ # The InternationalText (iTXt) chunk contains keyword/value metadata about the PNG
372
+ # stream, translated to a given locale.
365
373
  #
366
374
  # The metadata in this chunk can be encoded using UTF-8 characters.
367
375
  # Moreover, it is possible to define the language of the metadata, and give
368
376
  # a translation of the keyword name. Finally, it supports bot compressed
369
377
  # and uncompressed values.
370
378
  #
371
- # @todo This chunk is currently not implemented, but merely read and
372
- # written back intact.
379
+ # http://www.libpng.org/pub/png/spec/1.2/PNG-Chunks.html#C.iTXt
373
380
  #
374
381
  # @see ChunkyPNG::Chunk::Text
375
382
  # @see ChunkyPNG::Chunk::CompressedText
376
- class InternationalText < Generic
377
- # TODO
383
+ class InternationalText < Base
384
+ attr_accessor :keyword, :text, :language_tag, :translated_keyword, :compressed, :compression
385
+
386
+ def initialize(keyword, text, language_tag = "", translated_keyword = "", compressed = ChunkyPNG::UNCOMPRESSED_CONTENT, compression = ChunkyPNG::COMPRESSION_DEFAULT)
387
+ super("iTXt")
388
+ @keyword = keyword
389
+ @text = text
390
+ @language_tag = language_tag
391
+ @translated_keyword = translated_keyword
392
+ @compressed = compressed
393
+ @compression = compression
394
+ end
395
+
396
+ # Reads the tTXt chunk.
397
+ # @param type [String] The four character chunk type indicator (= "iTXt").
398
+ # @param content [String] The content read from the chunk.
399
+ # @return [ChunkyPNG::Chunk::InternationalText] The new End chunk instance.
400
+ # @raise [ChunkyPNG::InvalidUTF8] If the chunk contains data that is not UTF8-encoded text.
401
+ # @raise [ChunkyPNG::NotSupported] If the chunk refers to an unsupported compression method.
402
+ # Currently uncompressed data and deflate are supported.
403
+ def self.read(type, content)
404
+ keyword, compressed, compression, language_tag, translated_keyword, text = content.unpack("Z*CCZ*Z*a*")
405
+ raise ChunkyPNG::NotSupported, "Compression flag #{compressed.inspect} not supported!" unless compressed == ChunkyPNG::UNCOMPRESSED_CONTENT || compressed == ChunkyPNG::COMPRESSED_CONTENT
406
+ raise ChunkyPNG::NotSupported, "Compression method #{compression.inspect} not supported!" unless compression == ChunkyPNG::COMPRESSION_DEFAULT
407
+
408
+ text = Zlib::Inflate.inflate(text) if compressed == ChunkyPNG::COMPRESSED_CONTENT
409
+
410
+ text.force_encoding("utf-8")
411
+ raise ChunkyPNG::InvalidUTF8, "Invalid unicode encountered in iTXt chunk" unless text.valid_encoding?
412
+
413
+ translated_keyword.force_encoding("utf-8")
414
+ raise ChunkyPNG::InvalidUTF8, "Invalid unicode encountered in iTXt chunk" unless translated_keyword.valid_encoding?
415
+
416
+ new(keyword, text, language_tag, translated_keyword, compressed, compression)
417
+ end
418
+
419
+ # Assembles the content to write to the stream for this chunk.
420
+ # @return [String] The binary content that should be written to the datastream.
421
+ def content
422
+ text_field = text.encode("utf-8")
423
+ text_field = compressed == ChunkyPNG::COMPRESSED_CONTENT ? Zlib::Deflate.deflate(text_field) : text_field
424
+
425
+ [keyword, compressed, compression, language_tag, translated_keyword.encode("utf-8"), text_field].pack("Z*CCZ*Z*a*")
426
+ end
378
427
  end
379
428
 
380
429
  # Maps chunk types to classes, based on the four byte chunk type indicator
@@ -385,15 +434,15 @@ module ChunkyPNG
385
434
  #
386
435
  # @see ChunkyPNG::Chunk.read
387
436
  CHUNK_TYPES = {
388
- 'IHDR' => Header,
389
- 'IEND' => End,
390
- 'IDAT' => ImageData,
391
- 'PLTE' => Palette,
392
- 'tRNS' => Transparency,
393
- 'tEXt' => Text,
394
- 'zTXt' => CompressedText,
395
- 'iTXt' => InternationalText,
396
- 'pHYs' => Physical,
437
+ "IHDR" => Header,
438
+ "IEND" => End,
439
+ "IDAT" => ImageData,
440
+ "PLTE" => Palette,
441
+ "tRNS" => Transparency,
442
+ "tEXt" => Text,
443
+ "zTXt" => CompressedText,
444
+ "iTXt" => InternationalText,
445
+ "pHYs" => Physical,
397
446
  }
398
447
  end
399
448
  end