pnm 0.1.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.
- checksums.yaml +7 -0
- data/README.md +93 -0
- data/Rakefile +34 -0
- data/benchmark/bm_converter.rb +85 -0
- data/benchmark/random_image.pbm +0 -0
- data/benchmark/random_image.pgm +0 -0
- data/benchmark/random_image.ppm +0 -0
- data/lib/pnm.rb +155 -0
- data/lib/pnm/converter.rb +101 -0
- data/lib/pnm/image.rb +129 -0
- data/lib/pnm/parser.rb +83 -0
- data/lib/pnm/version.rb +4 -0
- data/pnm.gemspec +42 -0
- data/test/bilevel_2_binary.pbm +0 -0
- data/test/bilevel_ascii.pbm +9 -0
- data/test/bilevel_binary.pbm +0 -0
- data/test/color_ascii.ppm +6 -0
- data/test/color_binary.ppm +0 -0
- data/test/grayscale_ascii.pgm +8 -0
- data/test/grayscale_binary.pgm +0 -0
- data/test/grayscale_binary_crlf.pgm +5 -0
- data/test/test_converter.rb +183 -0
- data/test/test_image.rb +113 -0
- data/test/test_parser.rb +119 -0
- data/test/test_pnm.rb +105 -0
- metadata +88 -0
data/lib/pnm/image.rb
ADDED
@@ -0,0 +1,129 @@
|
|
1
|
+
module PNM
|
2
|
+
|
3
|
+
# Class for +PBM+, +PGM+, and +PPM+ images. See PNM module for examples.
|
4
|
+
class Image
|
5
|
+
|
6
|
+
# The type of the image. See ::new for details.
|
7
|
+
attr_reader :type
|
8
|
+
|
9
|
+
# The width of the image in pixels.
|
10
|
+
attr_reader :width
|
11
|
+
|
12
|
+
# The height of the image in pixels.
|
13
|
+
attr_reader :height
|
14
|
+
|
15
|
+
# The maximum gray or color value. See ::new for details.
|
16
|
+
attr_reader :maxgray
|
17
|
+
|
18
|
+
# The pixel data, given as a two-dimensional array.
|
19
|
+
# See ::new for details.
|
20
|
+
attr_reader :pixels
|
21
|
+
|
22
|
+
# An optional multiline comment string.
|
23
|
+
attr_reader :comment
|
24
|
+
|
25
|
+
# Creates an image from a two-dimensional array of bilevel,
|
26
|
+
# gray, or RGB values.
|
27
|
+
#
|
28
|
+
# +type+:: The type of the image (+:pbm+, +:pgm+, or +:ppm+).
|
29
|
+
# +pixels+:: The pixel data, given as a two-dimensional array of
|
30
|
+
#
|
31
|
+
# * for PBM: bilevel values (0 or 1),
|
32
|
+
# * for PGM: gray values between 0 and +maxgray+,
|
33
|
+
# * for PPM: an array of 3 values between 0 and +maxgray+,
|
34
|
+
# corresponding to red, green, and blue (RGB).
|
35
|
+
#
|
36
|
+
# PPM also accepts an array of gray values.
|
37
|
+
#
|
38
|
+
# A value of 0 means that the color is turned off.
|
39
|
+
#
|
40
|
+
# Optional settings that can be specified in the +options+ hash:
|
41
|
+
#
|
42
|
+
# +maxgray+:: The maximum gray or color value.
|
43
|
+
# For PGM and PPM, +maxgray+ must be less or equal 255
|
44
|
+
# (the default value). For PBM, this setting is ignored
|
45
|
+
# and +maxgray+ is always set to 1.
|
46
|
+
# +comment+:: A multiline comment string (default: empty string).
|
47
|
+
def initialize(type, pixels, options = {})
|
48
|
+
@type = type
|
49
|
+
@width = pixels.first.size
|
50
|
+
@height = pixels.size
|
51
|
+
@maxgray = options[:maxgray] || 255
|
52
|
+
@comment = (options[:comment] || '').chomp
|
53
|
+
@pixels = pixels
|
54
|
+
|
55
|
+
@maxgray = 1 if type == :pbm
|
56
|
+
|
57
|
+
if type == :ppm && !pixels.first.first.kind_of?(Array)
|
58
|
+
@pixels = pixels.map {|row| row.map {|pixel| gray_to_rgb(pixel) } }
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
# Writes the image to +file+ (a filename or an IO object),
|
63
|
+
# using the specified encoding.
|
64
|
+
# Valid encodings are +:binary+ (default) and +:ascii+.
|
65
|
+
#
|
66
|
+
# Returns the number of bytes written.
|
67
|
+
def write(file, encoding = :binary)
|
68
|
+
content = if encoding == :ascii
|
69
|
+
to_ascii
|
70
|
+
elsif encoding == :binary
|
71
|
+
to_binary
|
72
|
+
end
|
73
|
+
|
74
|
+
if file.kind_of?(String)
|
75
|
+
File.binwrite(file, content)
|
76
|
+
else
|
77
|
+
file.binmode
|
78
|
+
file.write content
|
79
|
+
end
|
80
|
+
end
|
81
|
+
|
82
|
+
# Returns a string with a short image format description.
|
83
|
+
def info
|
84
|
+
"#{type.to_s.upcase} #{width}x#{height} #{type_string}"
|
85
|
+
end
|
86
|
+
|
87
|
+
alias :to_s :info
|
88
|
+
|
89
|
+
private
|
90
|
+
|
91
|
+
def type_string # :nodoc:
|
92
|
+
case type
|
93
|
+
when :pbm
|
94
|
+
'Bilevel'
|
95
|
+
when :pgm
|
96
|
+
'Grayscale'
|
97
|
+
when :ppm
|
98
|
+
'Color'
|
99
|
+
end
|
100
|
+
end
|
101
|
+
|
102
|
+
def header(encoding) # :nodoc:
|
103
|
+
header = "#{PNM.magic_number[type][encoding]}\n"
|
104
|
+
if comment
|
105
|
+
comment.split("\n").each {|line| header << "# #{line}\n" }
|
106
|
+
end
|
107
|
+
header << "#{width} #{height}\n"
|
108
|
+
header << "#{maxgray}\n" unless type == :pbm
|
109
|
+
|
110
|
+
header
|
111
|
+
end
|
112
|
+
|
113
|
+
def to_ascii # :nodoc:
|
114
|
+
data_string = Converter.array2ascii(pixels)
|
115
|
+
|
116
|
+
header(:ascii) << data_string
|
117
|
+
end
|
118
|
+
|
119
|
+
def to_binary # :nodoc:
|
120
|
+
data_string = Converter.array2binary(type, pixels)
|
121
|
+
|
122
|
+
header(:binary) << data_string
|
123
|
+
end
|
124
|
+
|
125
|
+
def gray_to_rgb(gray_value) # :nodoc:
|
126
|
+
Array.new(3, gray_value)
|
127
|
+
end
|
128
|
+
end
|
129
|
+
end
|
data/lib/pnm/parser.rb
ADDED
@@ -0,0 +1,83 @@
|
|
1
|
+
module PNM
|
2
|
+
|
3
|
+
# Parser for PNM image files. Only for internal usage.
|
4
|
+
class Parser
|
5
|
+
|
6
|
+
# Parses PNM image data.
|
7
|
+
#
|
8
|
+
# +content+:: A string containing the image file content.
|
9
|
+
#
|
10
|
+
# Returns a hash containing the parsed data as strings:
|
11
|
+
#
|
12
|
+
# * +:magic_number+,
|
13
|
+
# * +:maxgray+ (only for PGM and PPM),
|
14
|
+
# * +:width+,
|
15
|
+
# * +:height+,
|
16
|
+
# * +:data+,
|
17
|
+
# * +:comments+ (only if present): an array of comment strings.
|
18
|
+
def self.parse(content)
|
19
|
+
content = content.dup
|
20
|
+
|
21
|
+
magic_number = nil
|
22
|
+
tokens = []
|
23
|
+
comments = []
|
24
|
+
|
25
|
+
until magic_number
|
26
|
+
token = get_next_token!(content)
|
27
|
+
|
28
|
+
if token[0] == '#'
|
29
|
+
comments << token.gsub(/# */, '')
|
30
|
+
else
|
31
|
+
magic_number = token
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
raise "Unknown magic number" unless token_number[magic_number]
|
36
|
+
|
37
|
+
while tokens.size < token_number[magic_number]
|
38
|
+
content.gsub!(/\A[ \t\r\n]+/, '')
|
39
|
+
token = get_next_token!(content)
|
40
|
+
if token[0] == '#'
|
41
|
+
comments << token.gsub(/# */, '')
|
42
|
+
else
|
43
|
+
tokens << token
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
width, height, maxgray = tokens
|
48
|
+
|
49
|
+
result = {
|
50
|
+
:magic_number => magic_number,
|
51
|
+
:width => width,
|
52
|
+
:height => height,
|
53
|
+
:data => content
|
54
|
+
}
|
55
|
+
result[:maxgray] = maxgray if maxgray
|
56
|
+
result[:comments] = comments unless comments.empty?
|
57
|
+
|
58
|
+
result
|
59
|
+
end
|
60
|
+
|
61
|
+
def self.token_number # :nodoc:
|
62
|
+
{
|
63
|
+
'P1' => 2,
|
64
|
+
'P2' => 3,
|
65
|
+
'P3' => 3,
|
66
|
+
'P4' => 2,
|
67
|
+
'P5' => 3,
|
68
|
+
'P6' => 3
|
69
|
+
}
|
70
|
+
end
|
71
|
+
|
72
|
+
def self.get_next_token!(content) # :nodoc:
|
73
|
+
if content[0] == '#'
|
74
|
+
token, rest = content.split("\n", 2)
|
75
|
+
else
|
76
|
+
token, rest = content.split(/[ \t\r\n]|(?=#)/, 2)
|
77
|
+
end
|
78
|
+
content.clear << rest
|
79
|
+
|
80
|
+
token
|
81
|
+
end
|
82
|
+
end
|
83
|
+
end
|
data/lib/pnm/version.rb
ADDED
data/pnm.gemspec
ADDED
@@ -0,0 +1,42 @@
|
|
1
|
+
require './lib/pnm'
|
2
|
+
|
3
|
+
version = PNM::VERSION
|
4
|
+
date = PNM::DATE
|
5
|
+
homepage = PNM::HOMEPAGE
|
6
|
+
tagline = PNM::TAGLINE
|
7
|
+
|
8
|
+
Gem::Specification.new do |s|
|
9
|
+
s.name = 'pnm'
|
10
|
+
s.version = version
|
11
|
+
s.date = date
|
12
|
+
|
13
|
+
s.description = 'PNM is a pure Ruby library for creating, reading, ' +
|
14
|
+
'and writing of PNM image files (Portable Anymap): ' +
|
15
|
+
'PBM (Portable Bitmap), ' +
|
16
|
+
'PGM (Portable Graymap), and ' +
|
17
|
+
'PPM (Portable Pixmap).'
|
18
|
+
s.summary = "PNM - #{tagline}"
|
19
|
+
|
20
|
+
s.authors = ['Marcus Stollsteimer']
|
21
|
+
s.email = 'sto.mar@web.de'
|
22
|
+
s.homepage = homepage
|
23
|
+
|
24
|
+
s.license = 'GPL-3'
|
25
|
+
|
26
|
+
s.required_ruby_version = '>=1.9.3'
|
27
|
+
|
28
|
+
s.add_development_dependency('rake')
|
29
|
+
|
30
|
+
s.require_path = 'lib'
|
31
|
+
|
32
|
+
s.test_files = Dir.glob('test/**/test_*.rb')
|
33
|
+
|
34
|
+
s.files = %w{
|
35
|
+
README.md
|
36
|
+
Rakefile
|
37
|
+
pnm.gemspec
|
38
|
+
} +
|
39
|
+
Dir.glob('{benchmark,lib,test}/**/*')
|
40
|
+
|
41
|
+
s.rdoc_options = ['--charset=UTF-8']
|
42
|
+
end
|
Binary file
|
Binary file
|
Binary file
|
Binary file
|
@@ -0,0 +1,183 @@
|
|
1
|
+
# test_converter.rb: Unit tests for the PNM library.
|
2
|
+
#
|
3
|
+
# Copyright (C) 2013 Marcus Stollsteimer
|
4
|
+
|
5
|
+
require 'minitest/spec'
|
6
|
+
require 'minitest/autorun'
|
7
|
+
require 'pnm/converter'
|
8
|
+
|
9
|
+
|
10
|
+
describe PNM::Converter do
|
11
|
+
|
12
|
+
before do
|
13
|
+
@converter = PNM::Converter
|
14
|
+
|
15
|
+
@pbm6 = {
|
16
|
+
:width => 6,
|
17
|
+
:height => 2,
|
18
|
+
:array => [[0,1,0,0,1,1], [0,0,0,1,1,1]],
|
19
|
+
:ascii => "0 1 0 0 1 1\n0 0 0 1 1 1\n",
|
20
|
+
:binary => ['4C1C'].pack('H*')
|
21
|
+
}
|
22
|
+
|
23
|
+
@pbm14 = {
|
24
|
+
:width => 14,
|
25
|
+
:height => 2,
|
26
|
+
:array => [[0,0,1,1,1,0,0,0,1,1,0,0,1,0], [0,1,0,1,1,0,1,1,1,0,1,1,1,1]],
|
27
|
+
:ascii => "0 0 1 1 1 0 0 0 1 1 0 0 1 0\n0 1 0 1 1 0 1 1 1 0 1 1 1 1\n",
|
28
|
+
:binary => ['38C85BBC'].pack('H*')
|
29
|
+
}
|
30
|
+
|
31
|
+
@pbm = @pbm14
|
32
|
+
|
33
|
+
@pgm = {
|
34
|
+
:width => 4,
|
35
|
+
:height => 3,
|
36
|
+
:array => [[0, 85, 170, 255], [85, 170, 255, 0], [170, 255, 0, 85]],
|
37
|
+
:ascii => "0 85 170 255\n85 170 255 0\n170 255 0 85\n",
|
38
|
+
:binary => ['0055AAFF55AAFF00AAFF0055'].pack('H*')
|
39
|
+
}
|
40
|
+
|
41
|
+
@ppm = {
|
42
|
+
:width => 3,
|
43
|
+
:height => 2,
|
44
|
+
:array => [[[0,128,255], [128,255,0], [255,0,128]],
|
45
|
+
[[255,128,0], [128,0,255], [0,255,128]]],
|
46
|
+
:ascii => "0 128 255 128 255 0 255 0 128\n255 128 0 128 0 255 0 255 128\n",
|
47
|
+
:binary => ['0080FF80FF00FF0080FF80008000FF00FF80'].pack('H*')
|
48
|
+
}
|
49
|
+
end
|
50
|
+
|
51
|
+
it 'can convert from ASCII encoded PBM data' do
|
52
|
+
data = @pbm[:ascii]
|
53
|
+
expected = @pbm[:array]
|
54
|
+
|
55
|
+
@converter.ascii2array(:pbm, data).must_equal expected
|
56
|
+
end
|
57
|
+
|
58
|
+
it 'can convert from ASCII encoded PGM data' do
|
59
|
+
data = @pgm[:ascii]
|
60
|
+
expected = @pgm[:array]
|
61
|
+
|
62
|
+
@converter.ascii2array(:pgm, data).must_equal expected
|
63
|
+
end
|
64
|
+
|
65
|
+
it 'can convert from ASCII encoded PPM data' do
|
66
|
+
data = @ppm[:ascii]
|
67
|
+
expected = @ppm[:array]
|
68
|
+
|
69
|
+
@converter.ascii2array(:ppm, data).must_equal expected
|
70
|
+
end
|
71
|
+
|
72
|
+
it 'can convert from binary encoded PBM data (width 6)' do
|
73
|
+
width = @pbm6[:width]
|
74
|
+
height = @pbm6[:height]
|
75
|
+
data = @pbm6[:binary]
|
76
|
+
expected = @pbm6[:array]
|
77
|
+
|
78
|
+
@converter.binary2array(:pbm, width, height, data).must_equal expected
|
79
|
+
end
|
80
|
+
|
81
|
+
it 'can convert from binary encoded PBM data (width 14)' do
|
82
|
+
width = @pbm14[:width]
|
83
|
+
height = @pbm14[:height]
|
84
|
+
data = @pbm14[:binary]
|
85
|
+
expected = @pbm14[:array]
|
86
|
+
|
87
|
+
@converter.binary2array(:pbm, width, height, data).must_equal expected
|
88
|
+
end
|
89
|
+
|
90
|
+
it 'can convert from binary encoded PGM data' do
|
91
|
+
width = @pgm[:width]
|
92
|
+
height = @pgm[:height]
|
93
|
+
data = @pgm[:binary]
|
94
|
+
expected = @pgm[:array]
|
95
|
+
|
96
|
+
@converter.binary2array(:pgm, width, height, data).must_equal expected
|
97
|
+
end
|
98
|
+
|
99
|
+
it 'can convert from binary encoded PPM data' do
|
100
|
+
width = @ppm[:width]
|
101
|
+
height = @ppm[:height]
|
102
|
+
data = @ppm[:binary]
|
103
|
+
expected = @ppm[:array]
|
104
|
+
|
105
|
+
@converter.binary2array(:ppm, width, height, data).must_equal expected
|
106
|
+
end
|
107
|
+
|
108
|
+
it 'accepts an additional whitespace character for binary encoded data' do
|
109
|
+
width = @pbm14[:width]
|
110
|
+
height = @pbm14[:height]
|
111
|
+
data = @pbm14[:binary] + "\t"
|
112
|
+
expected = @pbm14[:array]
|
113
|
+
|
114
|
+
@converter.binary2array(:pbm, width, height, data).must_equal expected
|
115
|
+
end
|
116
|
+
|
117
|
+
it 'can convert to ASCII encoded PBM data' do
|
118
|
+
data = @pbm[:array]
|
119
|
+
expected = @pbm[:ascii]
|
120
|
+
|
121
|
+
@converter.array2ascii(data).must_equal expected
|
122
|
+
end
|
123
|
+
|
124
|
+
it 'can convert to ASCII encoded PGM data' do
|
125
|
+
data = @pgm[:array]
|
126
|
+
expected = @pgm[:ascii]
|
127
|
+
|
128
|
+
@converter.array2ascii(data).must_equal expected
|
129
|
+
end
|
130
|
+
|
131
|
+
it 'can convert to ASCII encoded PPM data' do
|
132
|
+
data = @ppm[:array]
|
133
|
+
expected = @ppm[:ascii]
|
134
|
+
|
135
|
+
@converter.array2ascii(data).must_equal expected
|
136
|
+
end
|
137
|
+
|
138
|
+
it 'can convert to binary encoded PBM data (width 6)' do
|
139
|
+
data = @pbm6[:array]
|
140
|
+
expected = @pbm6[:binary]
|
141
|
+
|
142
|
+
@converter.array2binary(:pbm, data).must_equal expected
|
143
|
+
end
|
144
|
+
|
145
|
+
it 'can convert to binary encoded PBM data (width 14)' do
|
146
|
+
data = @pbm14[:array]
|
147
|
+
expected = @pbm14[:binary]
|
148
|
+
|
149
|
+
@converter.array2binary(:pbm, data).must_equal expected
|
150
|
+
end
|
151
|
+
|
152
|
+
it 'can convert to binary encoded PGM data' do
|
153
|
+
data = @pgm[:array]
|
154
|
+
expected = @pgm[:binary]
|
155
|
+
|
156
|
+
@converter.array2binary(:pgm, data).must_equal expected
|
157
|
+
end
|
158
|
+
|
159
|
+
it 'can convert to binary encoded PPM data' do
|
160
|
+
data = @ppm[:array]
|
161
|
+
expected = @ppm[:binary]
|
162
|
+
|
163
|
+
@converter.array2binary(:ppm, data).must_equal expected
|
164
|
+
end
|
165
|
+
|
166
|
+
it 'can calculate correct byte widths for a PBM image' do
|
167
|
+
@converter.byte_width(:pbm, 0).must_equal 0
|
168
|
+
@converter.byte_width(:pbm, 1).must_equal 1
|
169
|
+
@converter.byte_width(:pbm, 7).must_equal 1
|
170
|
+
@converter.byte_width(:pbm, 8).must_equal 1
|
171
|
+
@converter.byte_width(:pbm, 9).must_equal 2
|
172
|
+
@converter.byte_width(:pbm, 64).must_equal 8
|
173
|
+
@converter.byte_width(:pbm, 65).must_equal 9
|
174
|
+
end
|
175
|
+
|
176
|
+
it 'can calculate correct byte widths for a PGM image' do
|
177
|
+
@converter.byte_width(:pgm, 13).must_equal 13
|
178
|
+
end
|
179
|
+
|
180
|
+
it 'can calculate correct byte widths for a PPM image' do
|
181
|
+
@converter.byte_width(:ppm, 13).must_equal 39
|
182
|
+
end
|
183
|
+
end
|