pnm 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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
@@ -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
@@ -0,0 +1,4 @@
1
+ module PNM
2
+ VERSION = '0.1.0' # :nodoc:
3
+ DATE = '2013-10-26' # :nodoc:
4
+ end
@@ -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
@@ -0,0 +1,9 @@
1
+ P1
2
+ # Bilevel
3
+ 5 6
4
+ 0 0 0 0 0
5
+ 0 1 1 1 0
6
+ 0 0 1 0 0
7
+ 0 0 1 0 0
8
+ 0 0 1 0 0
9
+ 0 0 0 0 0
Binary file
@@ -0,0 +1,6 @@
1
+ P3
2
+ 5 3
3
+ 6
4
+ 0 6 0 1 5 1 2 4 2 3 3 4 4 2 6
5
+ 1 5 2 2 4 2 3 3 2 4 2 2 5 1 2
6
+ 2 4 6 3 3 4 4 2 2 5 1 1 6 0 0
Binary file
@@ -0,0 +1,8 @@
1
+ P2
2
+ # Grayscale
3
+ # (with multiline comment)
4
+ 4 3
5
+ 250
6
+ 0 50 100 150
7
+ 50 100 150 200
8
+ 100 150 200 250
Binary file
@@ -0,0 +1,5 @@
1
+ P5
2
+ 2 3
3
+ 255
4
+ AB
5
+ AB
@@ -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