chunky_png 0.0.5 → 0.5.0

Sign up to get free protection for your applications and to get access to all the features.
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