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