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