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.
@@ -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