chunky_png 0.0.5 → 0.5.0

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.
Files changed (43) hide show
  1. data/README.rdoc +20 -10
  2. data/chunky_png.gemspec +18 -6
  3. data/lib/chunky_png.rb +11 -28
  4. data/lib/chunky_png/canvas.rb +186 -0
  5. data/lib/chunky_png/canvas/adam7_interlacing.rb +53 -0
  6. data/lib/chunky_png/canvas/drawing.rb +8 -0
  7. data/lib/chunky_png/{pixel_matrix → canvas}/operations.rb +12 -12
  8. data/lib/chunky_png/canvas/png_decoding.rb +145 -0
  9. data/lib/chunky_png/canvas/png_encoding.rb +182 -0
  10. data/lib/chunky_png/chunk.rb +101 -23
  11. data/lib/chunky_png/color.rb +307 -0
  12. data/lib/chunky_png/datastream.rb +143 -45
  13. data/lib/chunky_png/image.rb +31 -30
  14. data/lib/chunky_png/palette.rb +49 -47
  15. data/lib/chunky_png/rmagick.rb +43 -0
  16. data/spec/chunky_png/canvas/adam7_interlacing_spec.rb +108 -0
  17. data/spec/chunky_png/canvas/png_decoding_spec.rb +81 -0
  18. data/spec/chunky_png/canvas/png_encoding_spec.rb +70 -0
  19. data/spec/chunky_png/canvas_spec.rb +71 -0
  20. data/spec/chunky_png/color_spec.rb +104 -0
  21. data/spec/chunky_png/datastream_spec.rb +32 -0
  22. data/spec/chunky_png/image_spec.rb +25 -0
  23. data/spec/chunky_png/rmagick_spec.rb +21 -0
  24. data/spec/{integration/image_spec.rb → chunky_png_spec.rb} +14 -8
  25. data/spec/resources/composited.png +0 -0
  26. data/spec/resources/cropped.png +0 -0
  27. data/spec/resources/damaged_chunk.png +0 -0
  28. data/spec/resources/damaged_signature.png +13 -0
  29. data/spec/resources/pixelstream.rgba +67 -0
  30. data/spec/resources/replaced.png +0 -0
  31. data/spec/resources/text_chunk.png +0 -0
  32. data/spec/resources/ztxt_chunk.png +0 -0
  33. data/spec/spec_helper.rb +8 -5
  34. data/tasks/github-gem.rake +1 -1
  35. metadata +37 -18
  36. data/lib/chunky_png/pixel.rb +0 -272
  37. data/lib/chunky_png/pixel_matrix.rb +0 -136
  38. data/lib/chunky_png/pixel_matrix/decoding.rb +0 -159
  39. data/lib/chunky_png/pixel_matrix/encoding.rb +0 -89
  40. data/spec/unit/decoding_spec.rb +0 -83
  41. data/spec/unit/encoding_spec.rb +0 -27
  42. data/spec/unit/pixel_matrix_spec.rb +0 -93
  43. data/spec/unit/pixel_spec.rb +0 -47
@@ -0,0 +1,145 @@
1
+ module ChunkyPNG
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.
6
+ module PNGDecoding
7
+
8
+ def from_blob(str)
9
+ from_datastream(ChunkyPNG::Datastream.from_blob(str))
10
+ end
11
+
12
+ alias :from_string :from_blob
13
+
14
+ def from_file(filename)
15
+ from_datastream(ChunkyPNG::Datastream.from_file(filename))
16
+ end
17
+
18
+ def from_io(io)
19
+ from_datastream(ChunkyPNG::Datastream.from_io(io))
20
+ end
21
+
22
+ def from_datastream(ds)
23
+ raise "Only 8-bit color depth is currently supported by ChunkyPNG!" unless ds.header_chunk.depth == 8
24
+
25
+ width = ds.header_chunk.width
26
+ height = ds.header_chunk.height
27
+ color_mode = ds.header_chunk.color
28
+ interlace = ds.header_chunk.interlace
29
+ palette = ChunkyPNG::Palette.from_chunks(ds.palette_chunk, ds.transparency_chunk)
30
+ stream = ChunkyPNG::Chunk::ImageData.combine_chunks(ds.data_chunks)
31
+ decode_png_pixelstream(stream, width, height, color_mode, palette, interlace)
32
+ end
33
+
34
+ def decode_png_pixelstream(stream, width, height, color_mode = ChunkyPNG::COLOR_TRUECOLOR, palette = nil, interlace = ChunkyPNG::INTERLACING_NONE)
35
+ raise "This palette is not suitable for decoding!" if palette && !palette.can_decode?
36
+
37
+ pixel_size = Color.bytesize(color_mode)
38
+ pixel_decoder = case color_mode
39
+ when ChunkyPNG::COLOR_TRUECOLOR then lambda { |bytes| ChunkyPNG::Color.rgb(*bytes) }
40
+ when ChunkyPNG::COLOR_TRUECOLOR_ALPHA then lambda { |bytes| ChunkyPNG::Color.rgba(*bytes) }
41
+ when ChunkyPNG::COLOR_INDEXED then lambda { |bytes| palette[bytes.first] }
42
+ when ChunkyPNG::COLOR_GRAYSCALE then lambda { |bytes| ChunkyPNG::Color.grayscale(*bytes) }
43
+ when ChunkyPNG::COLOR_GRAYSCALE_ALPHA then lambda { |bytes| ChunkyPNG::Color.grayscale(*bytes) }
44
+ else raise "No suitable pixel decoder found for color mode #{color_mode}!"
45
+ end
46
+
47
+ return case interlace
48
+ when ChunkyPNG::INTERLACING_NONE then decode_png_without_interlacing(stream, width, height, pixel_size, pixel_decoder)
49
+ when ChunkyPNG::INTERLACING_ADAM7 then decode_png_with_adam7_interlacing(stream, width, height, pixel_size, pixel_decoder)
50
+ else raise "Don't know how the handle interlacing method #{interlace}!"
51
+ end
52
+ end
53
+
54
+ protected
55
+
56
+ def decode_png_image_pass(stream, width, height, pixel_size, pixel_decoder, start_pos = 0)
57
+ pixels = []
58
+
59
+ if width > 0
60
+ decoded_bytes = Array.new(width * pixel_size, 0)
61
+ height.times do |line_no|
62
+
63
+ # get bytes of scanline
64
+ position = start_pos + line_no * (width * pixel_size + 1)
65
+ line_length = width * pixel_size
66
+ bytes = stream.unpack("@#{position}CC#{line_length}")
67
+ filter = bytes.shift
68
+ decoded_bytes = decode_png_scanline(filter, bytes, decoded_bytes, pixel_size)
69
+
70
+ # decode bytes into colors
71
+ decoded_bytes.each_slice(pixel_size) { |bytes| pixels << pixel_decoder.call(bytes) }
72
+ end
73
+ end
74
+
75
+ new(width, height, pixels)
76
+ end
77
+
78
+ def decode_png_without_interlacing(stream, width, height, pixel_size, pixel_decoder)
79
+ raise "Invalid stream length!" unless stream.length == width * height * pixel_size + height
80
+ decode_png_image_pass(stream, width, height, pixel_size, pixel_decoder)
81
+ end
82
+
83
+ def decode_png_with_adam7_interlacing(stream, width, height, pixel_size, pixel_decoder)
84
+ start_pos = 0
85
+ canvas = ChunkyPNG::Canvas.new(width, height)
86
+ 0.upto(6) do |pass|
87
+ sm_width, sm_height = adam7_pass_size(pass, width, height)
88
+ sm = decode_png_image_pass(stream, sm_width, sm_height, pixel_size, pixel_decoder, start_pos)
89
+ adam7_merge_pass(pass, canvas, sm)
90
+ start_pos += (sm_width * sm_height * pixel_size) + sm_height
91
+ end
92
+ canvas
93
+ end
94
+
95
+ def decode_png_scanline(filter, bytes, previous_bytes, pixelsize = 3)
96
+ case filter
97
+ when ChunkyPNG::FILTER_NONE then decode_png_scanline_none( bytes, previous_bytes, pixelsize)
98
+ when ChunkyPNG::FILTER_SUB then decode_png_scanline_sub( bytes, previous_bytes, pixelsize)
99
+ when ChunkyPNG::FILTER_UP then decode_png_scanline_up( bytes, previous_bytes, pixelsize)
100
+ when ChunkyPNG::FILTER_AVERAGE then decode_png_scanline_average( bytes, previous_bytes, pixelsize)
101
+ when ChunkyPNG::FILTER_PAETH then decode_png_scanline_paeth( bytes, previous_bytes, pixelsize)
102
+ else raise "Unknown filter type"
103
+ end
104
+ end
105
+
106
+ def decode_png_scanline_none(bytes, previous_bytes, pixelsize = 3)
107
+ bytes
108
+ end
109
+
110
+ def decode_png_scanline_sub(bytes, previous_bytes, pixelsize = 3)
111
+ bytes.each_with_index { |b, i| bytes[i] = (b + (i >= pixelsize ? bytes[i-pixelsize] : 0)) % 256 }
112
+ bytes
113
+ end
114
+
115
+ def decode_png_scanline_up(bytes, previous_bytes, pixelsize = 3)
116
+ bytes.each_with_index { |b, i| bytes[i] = (b + previous_bytes[i]) % 256 }
117
+ bytes
118
+ end
119
+
120
+ def decode_png_scanline_average(bytes, previous_bytes, pixelsize = 3)
121
+ bytes.each_with_index do |byte, i|
122
+ a = (i >= pixelsize) ? bytes[i - pixelsize] : 0
123
+ b = previous_bytes[i]
124
+ bytes[i] = (byte + (a + b / 2).floor) % 256
125
+ end
126
+ bytes
127
+ end
128
+
129
+ def decode_png_scanline_paeth(bytes, previous_bytes, pixelsize = 3)
130
+ bytes.each_with_index do |byte, i|
131
+ a = (i >= pixelsize) ? bytes[i - pixelsize] : 0
132
+ b = previous_bytes[i]
133
+ c = (i >= pixelsize) ? previous_bytes[i - pixelsize] : 0
134
+ p = a + b - c
135
+ pa = (p - a).abs
136
+ pb = (p - b).abs
137
+ pc = (p - c).abs
138
+ pr = (pa <= pb && pa <= pc) ? a : (pb <= pc ? b : c)
139
+ bytes[i] = (byte + pr) % 256
140
+ end
141
+ bytes
142
+ end
143
+ end
144
+ end
145
+ end
@@ -0,0 +1,182 @@
1
+ module ChunkyPNG
2
+ class Canvas
3
+
4
+ # Methods for encoding a Canvas into a PNG datastream
5
+ #
6
+ module PNGEncoding
7
+
8
+ def write(io, constraints = {})
9
+ to_datastream(constraints).write(io)
10
+ end
11
+
12
+ def save(filename, constraints = {})
13
+ File.open(filename, 'wb') { |io| write(io, constraints) }
14
+ end
15
+
16
+ def to_blob(constraints = {})
17
+ to_datastream(constraints).to_blob
18
+ end
19
+
20
+ alias :to_string :to_blob
21
+ alias :to_s :to_blob
22
+
23
+ # Converts this Canvas to a datastream, so that it can be saved as a PNG image.
24
+ # @param [Hash] constraints The constraints to use when encoding the canvas.
25
+ def to_datastream(constraints = {})
26
+ data = encode_png(constraints)
27
+ ds = Datastream.new
28
+ ds.header_chunk = Chunk::Header.new(data[:header])
29
+ ds.palette_chunk = data[:palette_chunk] if data[:palette_chunk]
30
+ ds.transparency_chunk = data[:transparency_chunk] if data[:transparency_chunk]
31
+ ds.data_chunks = Chunk::ImageData.split_in_chunks(data[:pixelstream])
32
+ ds.end_chunk = Chunk::End.new
33
+ return ds
34
+ end
35
+
36
+ protected
37
+
38
+ def encode_png(constraints = {})
39
+ encoding = determine_png_encoding(constraints)
40
+ result = {}
41
+ result[:header] = { :width => width, :height => height, :color => encoding[:color_mode], :interlace => encoding[:interlace] }
42
+
43
+ if encoding[:color_mode] == ChunkyPNG::COLOR_INDEXED
44
+ result[:palette_chunk] = encoding[:palette].to_plte_chunk
45
+ result[:transparency_chunk] = encoding[:palette].to_trns_chunk unless encoding[:palette].opaque?
46
+ end
47
+
48
+ result[:pixelstream] = encode_png_pixelstream(encoding[:color_mode], encoding[:palette], encoding[:interlace])
49
+ return result
50
+ end
51
+
52
+ def determine_png_encoding(constraints = {})
53
+ encoding = constraints
54
+ encoding[:palette] ||= palette
55
+ encoding[:color_mode] ||= encoding[:palette].best_colormode
56
+
57
+ encoding[:interlace] = case encoding[:interlace]
58
+ when nil, false, ChunkyPNG::INTERLACING_NONE then ChunkyPNG::INTERLACING_NONE
59
+ when true, ChunkyPNG::INTERLACING_ADAM7 then ChunkyPNG::INTERLACING_ADAM7
60
+ else encoding[:interlace]
61
+ end
62
+
63
+ return encoding
64
+ end
65
+
66
+ def encode_png_pixelstream(color_mode = ChunkyPNG::COLOR_TRUECOLOR, palette = nil, interlace = ChunkyPNG::INTERLACING_NONE)
67
+
68
+ if color_mode == ChunkyPNG::COLOR_INDEXED && !palette.can_encode?
69
+ raise "This palette is not suitable for encoding!"
70
+ end
71
+
72
+ pixel_size = Color.bytesize(color_mode)
73
+ pixel_encoder = case color_mode
74
+ when ChunkyPNG::COLOR_TRUECOLOR then lambda { |color| Color.to_truecolor_bytes(color) }
75
+ when ChunkyPNG::COLOR_TRUECOLOR_ALPHA then lambda { |color| Color.to_truecolor_alpha_bytes(color) }
76
+ when ChunkyPNG::COLOR_INDEXED then lambda { |color| [palette.index(color)] }
77
+ when ChunkyPNG::COLOR_GRAYSCALE then lambda { |color| Color.to_grayscale_bytes(color) }
78
+ when ChunkyPNG::COLOR_GRAYSCALE_ALPHA then lambda { |color| Color.to_grayscale_alpha_bytes(color) }
79
+ else raise "Cannot encode pixels for this mode: #{color_mode}!"
80
+ end
81
+
82
+ case interlace
83
+ when ChunkyPNG::INTERLACING_NONE then encode_png_image_without_interlacing(pixel_size, pixel_encoder)
84
+ when ChunkyPNG::INTERLACING_ADAM7 then encode_png_image_with_interlacing(pixel_size, pixel_encoder)
85
+ else raise "Unknown interlacing method!"
86
+ end
87
+ end
88
+
89
+ def encode_png_image_without_interlacing(pixel_size, pixel_encoder)
90
+ stream = ""
91
+ encode_png_image_pass_to_stream(stream, pixel_size, pixel_encoder)
92
+ stream
93
+ end
94
+
95
+ def encode_png_image_with_interlacing(pixel_size, pixel_encoder)
96
+ stream = ""
97
+ 0.upto(6) do |pass|
98
+ subcanvas = self.class.adam7_extract_pass(pass, self)
99
+ subcanvas.encode_png_image_pass_to_stream(stream, pixel_size, pixel_encoder)
100
+ end
101
+ stream
102
+ end
103
+
104
+ def encode_png_image_pass_to_stream(stream, pixel_size, pixel_encoder)
105
+ previous_bytes = Array.new(pixel_size * width, 0)
106
+ each_scanline do |line|
107
+ unencoded_bytes = line.map(&pixel_encoder).flatten
108
+ stream << encode_png_scanline_up(unencoded_bytes, previous_bytes, pixel_size).pack('C*')
109
+ previous_bytes = unencoded_bytes
110
+ end
111
+ end
112
+
113
+ # Passes to this canvas of pixel values line by line.
114
+ # @yield [Array<Fixnum>] An line of fixnums reprsenting pixels
115
+ def each_scanline(&block)
116
+ height.times do |i|
117
+ scanline = pixels[width * i, width]
118
+ yield(scanline)
119
+ end
120
+ end
121
+
122
+ def encode_png_scanline(filter, bytes, previous_bytes = nil, pixelsize = 3)
123
+ case filter
124
+ when ChunkyPNG::FILTER_NONE then encode_png_scanline_none( bytes, previous_bytes, pixelsize)
125
+ when ChunkyPNG::FILTER_SUB then encode_png_scanline_sub( bytes, previous_bytes, pixelsize)
126
+ when ChunkyPNG::FILTER_UP then encode_png_scanline_up( bytes, previous_bytes, pixelsize)
127
+ when ChunkyPNG::FILTER_AVERAGE then encode_png_scanline_average( bytes, previous_bytes, pixelsize)
128
+ when ChunkyPNG::FILTER_PAETH then encode_png_scanline_paeth( bytes, previous_bytes, pixelsize)
129
+ else raise "Unknown filter type"
130
+ end
131
+ end
132
+
133
+ def encode_png_scanline_none(original_bytes, previous_bytes = nil, pixelsize = 3)
134
+ [ChunkyPNG::FILTER_NONE] + original_bytes
135
+ end
136
+
137
+ def encode_png_scanline_sub(original_bytes, previous_bytes = nil, pixelsize = 3)
138
+ encoded_bytes = []
139
+ original_bytes.length.times do |index|
140
+ a = (index >= pixelsize) ? original_bytes[index - pixelsize] : 0
141
+ encoded_bytes[index] = (original_bytes[index] - a) % 256
142
+ end
143
+ [ChunkyPNG::FILTER_SUB] + encoded_bytes
144
+ end
145
+
146
+ def encode_png_scanline_up(original_bytes, previous_bytes, pixelsize = 3)
147
+ encoded_bytes = []
148
+ original_bytes.length.times do |index|
149
+ b = previous_bytes[index]
150
+ encoded_bytes[index] = (original_bytes[index] - b) % 256
151
+ end
152
+ [ChunkyPNG::FILTER_UP] + encoded_bytes
153
+ end
154
+
155
+ def encode_png_scanline_average(original_bytes, previous_bytes, pixelsize = 3)
156
+ encoded_bytes = []
157
+ original_bytes.length.times do |index|
158
+ a = (index >= pixelsize) ? original_bytes[index - pixelsize] : 0
159
+ b = previous_bytes[index]
160
+ encoded_bytes[index] = (original_bytes[index] - (a + b / 2).floor) % 256
161
+ end
162
+ [ChunkyPNG::FILTER_AVERAGE] + encoded_bytes
163
+ end
164
+
165
+ def encode_png_scanline_paeth(original_bytes, previous_bytes, pixelsize = 3)
166
+ encoded_bytes = []
167
+ original_bytes.length.times do |i|
168
+ a = (i >= pixelsize) ? original_bytes[i - pixelsize] : 0
169
+ b = previous_bytes[i]
170
+ c = (i >= pixelsize) ? previous_bytes[i - pixelsize] : 0
171
+ p = a + b - c
172
+ pa = (p - a).abs
173
+ pb = (p - b).abs
174
+ pc = (p - c).abs
175
+ pr = (pa <= pb && pa <= pc) ? a : (pb <= pc ? b : c)
176
+ encoded_bytes[i] = (original_bytes[i] - pr) % 256
177
+ end
178
+ [ChunkyPNG::FILTER_PAETH] + encoded_bytes
179
+ end
180
+ end
181
+ end
182
+ end
@@ -1,52 +1,77 @@
1
1
  module ChunkyPNG
2
- class Chunk
3
-
2
+
3
+ # A PNG datastream consists of multiple chunks. This module, and the classes
4
+ # contained within, help with handling these chunks. It supports both
5
+ # reading and writing chunks.
6
+ #
7
+ # All chunck types are instances of the {ChunkyPNG::Chunk::Base} class. For
8
+ # some chunk types a specialized class is available, e.g. the IHDR chunk is
9
+ # represented by the {ChunkyPNG::Chunk::Header} class. These specialized
10
+ # classes help accessing the content of the chunk. All other chunks are
11
+ # represented by the {ChunkyPNG::Chunk::Generic} class.
12
+ module Chunk
13
+
14
+ # Reads a chunk from an IO stream.
15
+ #
16
+ # @param [IO, #read] io The IO stream to read from.
17
+ # @return [ChunkyPNG::Chung::Base] The loaded chunk instance.
4
18
  def self.read(io)
5
19
 
6
20
  length, type = io.read(8).unpack('Na4')
7
21
  content = io.read(length)
8
22
  crc = io.read(4).unpack('N').first
9
-
10
- # verify_crc!(type, content, crc)
11
-
23
+
24
+ verify_crc!(type, content, crc)
25
+
12
26
  CHUNK_TYPES.fetch(type, Generic).read(type, content)
13
27
  end
14
28
 
29
+ # Verifies the CRC of a chunk.
30
+ # @param [String] type The chunk's type.
31
+ # @param [String] content The chunk's content.
32
+ # @param [Fixnum] content The chunk's content.
33
+ # @raise [RuntimeError] An exception is raised if the found CRC value
34
+ # is not equal to the expected CRC value.
35
+ def self.verify_crc!(type, content, found_crc)
36
+ expected_crc = Zlib.crc32(content, Zlib.crc32(type))
37
+ raise "Chuck CRC mismatch!" if found_crc != expected_crc
38
+ end
39
+
15
40
  class Base
16
41
  attr_accessor :type
17
-
42
+
18
43
  def initialize(type, attributes = {})
19
44
  self.type = type
20
45
  attributes.each { |k, v| send("#{k}=", v) }
21
46
  end
22
-
47
+
23
48
  def write_with_crc(io, content)
24
49
  io << [content.length].pack('N') << type << content
25
50
  io << [Zlib.crc32(content, Zlib.crc32(type))].pack('N')
26
51
  end
27
-
52
+
28
53
  def write(io)
29
54
  write_with_crc(io, content || '')
30
55
  end
31
56
  end
32
-
57
+
33
58
  class Generic < Base
34
-
59
+
35
60
  attr_accessor :content
36
-
61
+
37
62
  def initialize(type, content = '')
38
63
  super(type, :content => content)
39
64
  end
40
-
65
+
41
66
  def self.read(type, content)
42
67
  self.new(type, content)
43
68
  end
44
69
  end
45
-
70
+
46
71
  class Header < Base
47
-
72
+
48
73
  attr_accessor :width, :height, :depth, :color, :compression, :filtering, :interlace
49
-
74
+
50
75
  def initialize(attrs = {})
51
76
  super('IHDR', attrs)
52
77
  @depth ||= 8
@@ -55,7 +80,7 @@ module ChunkyPNG
55
80
  @filtering ||= ChunkyPNG::FILTERING_DEFAULT
56
81
  @interlace ||= ChunkyPNG::INTERLACING_NONE
57
82
  end
58
-
83
+
59
84
  def self.read(type, content)
60
85
  fields = content.unpack('NNC5')
61
86
  self.new(:width => fields[0], :height => fields[1], :depth => fields[2], :color => fields[3],
@@ -66,17 +91,17 @@ module ChunkyPNG
66
91
  [width, height, depth, color, compression, filtering, interlace].pack('NNC5')
67
92
  end
68
93
  end
69
-
94
+
70
95
  class End < Base
71
96
  def initialize
72
97
  super('IEND')
73
98
  end
74
-
99
+
75
100
  def self.read(type, content)
76
101
  raise 'The IEND chunk should be empty!' if content != ''
77
102
  self.new
78
103
  end
79
-
104
+
80
105
  def content
81
106
  ''
82
107
  end
@@ -84,18 +109,71 @@ module ChunkyPNG
84
109
 
85
110
  class Palette < Generic
86
111
  end
87
-
112
+
88
113
  class Transparency < Generic
89
114
  end
90
115
 
91
116
  class ImageData < Generic
117
+
118
+ def self.combine_chunks(data_chunks)
119
+ Zlib::Inflate.inflate(data_chunks.map(&:content).join(''))
120
+ end
121
+
122
+ def self.split_in_chunks(data, chunk_size = 2147483647)
123
+ streamdata = Zlib::Deflate.deflate(data)
124
+ # TODO: Split long streamdata over multiple chunks
125
+ [ ChunkyPNG::Chunk::ImageData.new('IDAT', streamdata) ]
126
+ end
92
127
  end
93
-
128
+
129
+ class Text < Base
130
+
131
+ attr_accessor :keyword, :value
132
+
133
+ def initialize(keyword, value)
134
+ super('tEXt')
135
+ @keyword, @value = keyword, value
136
+ end
137
+
138
+ def self.read(type, content)
139
+ keyword, value = content.unpack('Z*a*')
140
+ new(keyword, value)
141
+ end
142
+
143
+ def content
144
+ [keyword, value].pack('Z*a*')
145
+ end
146
+ end
147
+
148
+ class CompressedText < Base
149
+
150
+ attr_accessor :keyword, :value
151
+
152
+ def initialize(keyword, value)
153
+ super('tEXt')
154
+ @keyword, @value = keyword, value
155
+ end
156
+
157
+ def self.read(type, content)
158
+ keyword, compression, value = content.unpack('Z*Ca*')
159
+ raise "Compression method #{compression.inspect} not supported!" unless compression == ChunkyPNG::COMPRESSION_DEFAULT
160
+ new(keyword, Zlib::Inflate.inflate(value))
161
+ end
162
+
163
+ def content
164
+ [keyword, ChunkyPNG::COMPRESSION_DEFAULT, Zlib::Deflate.deflate(value)].pack('Z*Ca*')
165
+ end
166
+ end
167
+
168
+ class InternationalText < Generic
169
+ # TODO
170
+ end
171
+
94
172
  # Maps chunk types to classes.
95
173
  # If a chunk type is not given in this hash, a generic chunk type will be used.
96
174
  CHUNK_TYPES = {
97
- 'IHDR' => Header, 'IEND' => End, 'IDAT' => ImageData, 'PLTE' => Palette, 'tRNS' => Transparency
175
+ 'IHDR' => Header, 'IEND' => End, 'IDAT' => ImageData, 'PLTE' => Palette, 'tRNS' => Transparency,
176
+ 'tEXt' => Text, 'zTXt' => CompressedText, 'iTXt' => InternationalText
98
177
  }
99
-
100
178
  end
101
179
  end