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
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: e0626fcd5c88b2477eb1f76891857c83fb10d881
|
4
|
+
data.tar.gz: 29724fe5ca19969bb9bf66358272222189b17f33
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 9e1f19e832e56b253d9db10794d4b414fc0211222c8e24ac572ffa93399e505bab04648b51ad1598916959480e2769fbac6f74253557f8a2a2ec81fca06aab8f
|
7
|
+
data.tar.gz: de73725df2c7703abd73e105ea770c4efe89d5db7caa01da45a747335a3d819003a946a8aaa31aa08869e0a8a6662e578cd23227d31daab00eabc686ac86e0ef
|
data/README.md
ADDED
@@ -0,0 +1,93 @@
|
|
1
|
+
PNM - A Ruby library for PNM image files (PBM, PGM, PPM)
|
2
|
+
========================================================
|
3
|
+
|
4
|
+
PNM is a pure [Ruby][Ruby] library for creating, reading,
|
5
|
+
and writing of `PNM` image files (Portable Anymap):
|
6
|
+
|
7
|
+
- `PBM` (Portable Bitmap),
|
8
|
+
- `PGM` (Portable Graymap), and
|
9
|
+
- `PPM` (Portable Pixmap).
|
10
|
+
|
11
|
+
Examples
|
12
|
+
--------
|
13
|
+
|
14
|
+
Create a PGM grayscale image from a two-dimensional array of gray values:
|
15
|
+
|
16
|
+
require 'pnm'
|
17
|
+
|
18
|
+
# pixel data
|
19
|
+
pixels = [[ 0, 10, 20],
|
20
|
+
[10, 20, 30]]
|
21
|
+
|
22
|
+
# optional settings
|
23
|
+
options = {:maxgray => 30, :comment => 'Test Image'}
|
24
|
+
|
25
|
+
# create the image object
|
26
|
+
image = PNM::Image.new(:pgm, pixels, options)
|
27
|
+
|
28
|
+
# retrieve some image properties
|
29
|
+
image.info # => "PGM 3x2 Grayscale"
|
30
|
+
image.width # => 3
|
31
|
+
image.height # => 2
|
32
|
+
|
33
|
+
See PNM::Image.new for a more detailed description of pixel data formats
|
34
|
+
and available options.
|
35
|
+
|
36
|
+
Write an image to a file:
|
37
|
+
|
38
|
+
image.write('test.pgm')
|
39
|
+
|
40
|
+
# use ASCII or "plain" format (default is binary)
|
41
|
+
image.write('test.pgm', :ascii)
|
42
|
+
|
43
|
+
# write to an I/O stream
|
44
|
+
File.open('test.pgm', 'w') {|f| image.write(f) }
|
45
|
+
|
46
|
+
Read an image from a file (returns a PNM::Image object):
|
47
|
+
|
48
|
+
image = PNM.read('test.pgm')
|
49
|
+
image.comment # => "Test Image"
|
50
|
+
image.maxgray # => 30
|
51
|
+
image.pixels # => [[0, 10, 20], [10, 20, 30]]
|
52
|
+
|
53
|
+
|
54
|
+
Installation
|
55
|
+
------------
|
56
|
+
|
57
|
+
To install PNM, you can either
|
58
|
+
|
59
|
+
- use `gem install pnm`, or
|
60
|
+
|
61
|
+
- clone or download the repository and use
|
62
|
+
`rake build` and `[sudo] gem install pnm`.
|
63
|
+
|
64
|
+
Requirements
|
65
|
+
------------
|
66
|
+
|
67
|
+
- No additional Ruby gems or native libraries are needed.
|
68
|
+
|
69
|
+
- PNM has been tested with Ruby 1.9.3 and Ruby 2.0.0
|
70
|
+
on Linux and on Windows.
|
71
|
+
|
72
|
+
Documentation
|
73
|
+
-------------
|
74
|
+
|
75
|
+
Documentation should be available via `ri PNM`.
|
76
|
+
|
77
|
+
Reporting bugs
|
78
|
+
--------------
|
79
|
+
|
80
|
+
Report bugs on the PNM home page: <https://github.com/stomar/pnm/>
|
81
|
+
|
82
|
+
License
|
83
|
+
-------
|
84
|
+
|
85
|
+
Copyright © 2013 Marcus Stollsteimer
|
86
|
+
|
87
|
+
`PNM` is free software: you can redistribute it and/or modify
|
88
|
+
it under the terms of the GNU General Public License version 3 or later (GPLv3+),
|
89
|
+
see [www.gnu.org/licenses/gpl.html](http://www.gnu.org/licenses/gpl.html).
|
90
|
+
There is NO WARRANTY, to the extent permitted by law.
|
91
|
+
|
92
|
+
|
93
|
+
[Ruby]: http://www.ruby-lang.org/
|
data/Rakefile
ADDED
@@ -0,0 +1,34 @@
|
|
1
|
+
# rakefile for the PNM library.
|
2
|
+
#
|
3
|
+
# Copyright (C) 2013 Marcus Stollsteimer
|
4
|
+
|
5
|
+
require 'rake/testtask'
|
6
|
+
|
7
|
+
require_relative 'lib/pnm'
|
8
|
+
|
9
|
+
|
10
|
+
def gemspec_file
|
11
|
+
'pnm.gemspec'
|
12
|
+
end
|
13
|
+
|
14
|
+
|
15
|
+
task :default => [:test]
|
16
|
+
|
17
|
+
Rake::TestTask.new do |t|
|
18
|
+
t.pattern = 'test/**/test_*.rb'
|
19
|
+
t.ruby_opts << '-rubygems'
|
20
|
+
t.verbose = true
|
21
|
+
t.warning = true
|
22
|
+
end
|
23
|
+
|
24
|
+
|
25
|
+
desc 'Run benchmarks'
|
26
|
+
task :benchmark do
|
27
|
+
Dir['benchmark/**/bm_*.rb'].each {|f| require_relative f }
|
28
|
+
end
|
29
|
+
|
30
|
+
|
31
|
+
desc 'Build gem'
|
32
|
+
task :build do
|
33
|
+
sh "gem build #{gemspec_file}"
|
34
|
+
end
|
@@ -0,0 +1,85 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
# bm_converter.rb: Benchmarks for the PNM library.
|
3
|
+
#
|
4
|
+
# Copyright (C) 2013 Marcus Stollsteimer
|
5
|
+
|
6
|
+
require 'benchmark'
|
7
|
+
require_relative '../lib/pnm'
|
8
|
+
require_relative '../lib/pnm/converter'
|
9
|
+
|
10
|
+
class ConverterBenchmark
|
11
|
+
|
12
|
+
def initialize
|
13
|
+
@repetitions = ARGV[0].to_i if ARGV[0] =~ /[0-9]+/
|
14
|
+
@repetitions ||= 10
|
15
|
+
|
16
|
+
@srcpath = File.dirname(__FILE__)
|
17
|
+
|
18
|
+
print 'Initializing test data...'
|
19
|
+
@pbm_image = PNM.read(File.expand_path("#{@srcpath}/random_image.pbm"))
|
20
|
+
@pgm_image = PNM.read(File.expand_path("#{@srcpath}/random_image.pgm"))
|
21
|
+
@ppm_image = PNM.read(File.expand_path("#{@srcpath}/random_image.ppm"))
|
22
|
+
puts " done\n"
|
23
|
+
|
24
|
+
@user = 0.0
|
25
|
+
@system = 0.0
|
26
|
+
@total = 0.0
|
27
|
+
end
|
28
|
+
|
29
|
+
def run
|
30
|
+
puts "Running benchmarks (#{@repetitions} repetitions)..."
|
31
|
+
|
32
|
+
run_benchmark(@pbm_image)
|
33
|
+
run_benchmark(@pgm_image)
|
34
|
+
run_benchmark(@ppm_image)
|
35
|
+
|
36
|
+
puts "\nTotal: " <<
|
37
|
+
@user.round(2).to_s.ljust(11) <<
|
38
|
+
@system.round(2).to_s.ljust(11) <<
|
39
|
+
@total.round(2).to_s
|
40
|
+
end
|
41
|
+
|
42
|
+
def run_benchmark(image)
|
43
|
+
type = image.type
|
44
|
+
type_string = type.upcase
|
45
|
+
width = image.width
|
46
|
+
height = image.height
|
47
|
+
array = image.pixels
|
48
|
+
ascii = PNM::Converter.array2ascii(array)
|
49
|
+
binary = PNM::Converter.array2binary(type, array)
|
50
|
+
|
51
|
+
puts
|
52
|
+
|
53
|
+
Benchmark.bm(18) do |bm|
|
54
|
+
bm.report("#{type_string} / ascii2array") {
|
55
|
+
@repetitions.times do
|
56
|
+
PNM::Converter.ascii2array(type, ascii)
|
57
|
+
end
|
58
|
+
}
|
59
|
+
|
60
|
+
bm.report("#{type_string} / array2ascii") {
|
61
|
+
@repetitions.times do
|
62
|
+
PNM::Converter.array2ascii(array)
|
63
|
+
end
|
64
|
+
}
|
65
|
+
|
66
|
+
bm.report("#{type_string} / binary2array") {
|
67
|
+
@repetitions.times do
|
68
|
+
PNM::Converter.binary2array(type, width, height, binary)
|
69
|
+
end
|
70
|
+
}
|
71
|
+
|
72
|
+
bm.report("#{type_string} / array2binary") {
|
73
|
+
@repetitions.times do
|
74
|
+
PNM::Converter.array2binary(type, array)
|
75
|
+
end
|
76
|
+
}
|
77
|
+
|
78
|
+
@user += bm.list.map {|tms| tms.utime }.reduce(:+)
|
79
|
+
@system += bm.list.map {|tms| tms.stime }.reduce(:+)
|
80
|
+
@total += bm.list.map {|tms| tms.total }.reduce(:+)
|
81
|
+
end
|
82
|
+
end
|
83
|
+
end
|
84
|
+
|
85
|
+
ConverterBenchmark.new.run
|
Binary file
|
Binary file
|
Binary file
|
data/lib/pnm.rb
ADDED
@@ -0,0 +1,155 @@
|
|
1
|
+
# = pnm.rb - create/read/write PNM image files (PBM, PGM, PPM)
|
2
|
+
#
|
3
|
+
# See PNM module for documentation.
|
4
|
+
|
5
|
+
require_relative 'pnm/version'
|
6
|
+
require_relative 'pnm/image'
|
7
|
+
require_relative 'pnm/parser'
|
8
|
+
require_relative 'pnm/converter'
|
9
|
+
|
10
|
+
|
11
|
+
# PNM is a pure Ruby library for creating, reading,
|
12
|
+
# and writing of +PNM+ image files (Portable Anymap):
|
13
|
+
#
|
14
|
+
# - +PBM+ (Portable Bitmap),
|
15
|
+
# - +PGM+ (Portable Graymap), and
|
16
|
+
# - +PPM+ (Portable Pixmap).
|
17
|
+
#
|
18
|
+
# == Examples
|
19
|
+
#
|
20
|
+
# Create a PGM grayscale image from a two-dimensional array of gray values:
|
21
|
+
#
|
22
|
+
# require 'pnm'
|
23
|
+
#
|
24
|
+
# # pixel data
|
25
|
+
# pixels = [[ 0, 10, 20],
|
26
|
+
# [10, 20, 30]]
|
27
|
+
#
|
28
|
+
# # optional settings
|
29
|
+
# options = {:maxgray => 30, :comment => 'Test Image'}
|
30
|
+
#
|
31
|
+
# # create the image object
|
32
|
+
# image = PNM::Image.new(:pgm, pixels, options)
|
33
|
+
#
|
34
|
+
# # retrieve some image properties
|
35
|
+
# image.info # => "PGM 3x2 Grayscale"
|
36
|
+
# image.width # => 3
|
37
|
+
# image.height # => 2
|
38
|
+
#
|
39
|
+
# See PNM::Image.new for a more detailed description of pixel data formats
|
40
|
+
# and available options.
|
41
|
+
#
|
42
|
+
# Write an image to a file:
|
43
|
+
#
|
44
|
+
# image.write('test.pgm')
|
45
|
+
#
|
46
|
+
# # use ASCII or "plain" format (default is binary)
|
47
|
+
# image.write('test.pgm', :ascii)
|
48
|
+
#
|
49
|
+
# # write to an I/O stream
|
50
|
+
# File.open('test.pgm', 'w') {|f| image.write(f) }
|
51
|
+
#
|
52
|
+
# Read an image from a file (returns a PNM::Image object):
|
53
|
+
#
|
54
|
+
# image = PNM.read('test.pgm')
|
55
|
+
# image.comment # => "Test Image"
|
56
|
+
# image.maxgray # => 30
|
57
|
+
# image.pixels # => [[0, 10, 20], [10, 20, 30]]
|
58
|
+
#
|
59
|
+
# == See also
|
60
|
+
#
|
61
|
+
# Further information on the PNM library is available on the
|
62
|
+
# project home page: <https://github.com/stomar/pnm/>.
|
63
|
+
#
|
64
|
+
# == Author
|
65
|
+
#
|
66
|
+
# Copyright (C) 2013 Marcus Stollsteimer
|
67
|
+
#
|
68
|
+
# License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>
|
69
|
+
#
|
70
|
+
#--
|
71
|
+
#
|
72
|
+
# == PNM magic numbers
|
73
|
+
#
|
74
|
+
# Magic Number Type Encoding
|
75
|
+
# ------------ ---------------- -------
|
76
|
+
# P1 Portable bitmap ASCII
|
77
|
+
# P2 Portable graymap ASCII
|
78
|
+
# P3 Portable pixmap ASCII
|
79
|
+
# P4 Portable bitmap Binary
|
80
|
+
# P5 Portable graymap Binary
|
81
|
+
# P6 Portable pixmap Binary
|
82
|
+
#
|
83
|
+
#++
|
84
|
+
#
|
85
|
+
module PNM
|
86
|
+
|
87
|
+
LIBNAME = 'pnm' # :nodoc:
|
88
|
+
HOMEPAGE = 'https://github.com/stomar/pnm' # :nodoc:
|
89
|
+
TAGLINE = 'create/read/write PNM image files (PBM, PGM, PPM)' # :nodoc:
|
90
|
+
|
91
|
+
COPYRIGHT = <<-copyright.gsub(/^ +/, '') # :nodoc:
|
92
|
+
Copyright (C) 2012-2013 Marcus Stollsteimer.
|
93
|
+
License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>.
|
94
|
+
This is free software: you are free to change and redistribute it.
|
95
|
+
There is NO WARRANTY, to the extent permitted by law.
|
96
|
+
copyright
|
97
|
+
|
98
|
+
# Reads an image from +file+ (a filename or an IO object).
|
99
|
+
#
|
100
|
+
# Returns a PNM::Image object.
|
101
|
+
def self.read(file)
|
102
|
+
raw_data = nil
|
103
|
+
if file.kind_of?(String)
|
104
|
+
raw_data = File.binread(file)
|
105
|
+
else
|
106
|
+
file.binmode
|
107
|
+
raw_data = file.read
|
108
|
+
end
|
109
|
+
|
110
|
+
content = Parser.parse(raw_data)
|
111
|
+
|
112
|
+
case content[:magic_number]
|
113
|
+
when 'P1'
|
114
|
+
type = :pbm
|
115
|
+
encoding = :ascii
|
116
|
+
when 'P2'
|
117
|
+
type = :pgm
|
118
|
+
encoding = :ascii
|
119
|
+
when 'P3'
|
120
|
+
type = :ppm
|
121
|
+
encoding = :ascii
|
122
|
+
when 'P4'
|
123
|
+
type = :pbm
|
124
|
+
encoding = :binary
|
125
|
+
when 'P5'
|
126
|
+
type = :pgm
|
127
|
+
encoding = :binary
|
128
|
+
when 'P6'
|
129
|
+
type = :ppm
|
130
|
+
encoding = :binary
|
131
|
+
end
|
132
|
+
|
133
|
+
width = content[:width].to_i
|
134
|
+
height = content[:height].to_i
|
135
|
+
maxgray = content[:maxgray].to_i
|
136
|
+
pixels = if encoding == :ascii
|
137
|
+
Converter.ascii2array(type, content[:data])
|
138
|
+
else
|
139
|
+
Converter.binary2array(type, width, height, content[:data])
|
140
|
+
end
|
141
|
+
|
142
|
+
options = {:maxgray => maxgray}
|
143
|
+
options[:comment] = content[:comments].join("\n") if content[:comments]
|
144
|
+
|
145
|
+
Image.new(type, pixels, options)
|
146
|
+
end
|
147
|
+
|
148
|
+
def self.magic_number # :nodoc:
|
149
|
+
{
|
150
|
+
:pbm => {:ascii => 'P1', :binary => 'P4'},
|
151
|
+
:pgm => {:ascii => 'P2', :binary => 'P5'},
|
152
|
+
:ppm => {:ascii => 'P3', :binary => 'P6'}
|
153
|
+
}
|
154
|
+
end
|
155
|
+
end
|
@@ -0,0 +1,101 @@
|
|
1
|
+
module PNM
|
2
|
+
|
3
|
+
# Converter for pixel data. Only for internal usage.
|
4
|
+
class Converter
|
5
|
+
|
6
|
+
# Returns the number of bytes needed for one row of pixels
|
7
|
+
# (in binary encoding).
|
8
|
+
def self.byte_width(type, width)
|
9
|
+
case type
|
10
|
+
when :pbm
|
11
|
+
(width - 1) / 8 + 1
|
12
|
+
when :pgm
|
13
|
+
width
|
14
|
+
when :ppm
|
15
|
+
3 * width
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
# Converts from ASCII format to an array of pixel values.
|
20
|
+
#
|
21
|
+
# +type+:: +:pbm+, +:pgm+, or +:ppm+.
|
22
|
+
# +data+:: A string containing the raw pixel data in ASCII format.
|
23
|
+
#
|
24
|
+
# Returns a two-dimensional array of bilevel, gray, or RGB values.
|
25
|
+
def self.ascii2array(type, data)
|
26
|
+
pixels = data.split("\n").map do |row|
|
27
|
+
row.split(/ +/).map {|value| value.to_i }
|
28
|
+
end
|
29
|
+
|
30
|
+
pixels.map! {|row| row.each_slice(3).to_a } if type == :ppm
|
31
|
+
|
32
|
+
pixels
|
33
|
+
end
|
34
|
+
|
35
|
+
# Converts from binary format to an array of pixel values.
|
36
|
+
#
|
37
|
+
# +type+:: +:pbm+, +:pgm+, or +:ppm+.
|
38
|
+
# +width+, +height+:: The image dimensions in pixels.
|
39
|
+
# +data+:: A string containing the raw pixel data in binary format.
|
40
|
+
#
|
41
|
+
# Returns a two-dimensional array of bilevel, gray, or RGB values.
|
42
|
+
def self.binary2array(type, width, height, data)
|
43
|
+
bytes_per_row = byte_width(type, width)
|
44
|
+
|
45
|
+
if data.size == bytes_per_row * height + 1 && data[-1] =~ /[ \n\t\r]/
|
46
|
+
data.slice!(-1)
|
47
|
+
end
|
48
|
+
|
49
|
+
if data.size != bytes_per_row * height
|
50
|
+
raise 'data size does not match expected size'
|
51
|
+
end
|
52
|
+
|
53
|
+
case type
|
54
|
+
when :pbm
|
55
|
+
pixels = data.scan(/.{#{bytes_per_row}}/m)
|
56
|
+
pixels.map! {|row| row.unpack('B*').first[0, width].each_char.map {|char| char.to_i } }
|
57
|
+
when :pgm
|
58
|
+
pixels = data.each_byte.each_slice(bytes_per_row).to_a
|
59
|
+
when :ppm
|
60
|
+
pixels = data.each_byte.each_slice(bytes_per_row).map {|row| row.each_slice(3).to_a }
|
61
|
+
end
|
62
|
+
|
63
|
+
pixels
|
64
|
+
end
|
65
|
+
|
66
|
+
# Converts a two-dimensional array of pixel values to an ASCII format string.
|
67
|
+
#
|
68
|
+
# +data+:: A two-dimensional array of bilevel, gray, or RGB values.
|
69
|
+
#
|
70
|
+
# Returns a string containing the pixel data in ASCII format.
|
71
|
+
def self.array2ascii(data)
|
72
|
+
case data.first.first
|
73
|
+
when Array
|
74
|
+
output = data.map {|row| row.flatten.join(' ') }.join("\n")
|
75
|
+
else
|
76
|
+
output = data.map {|row| row.join(' ') }.join("\n")
|
77
|
+
end
|
78
|
+
|
79
|
+
output << "\n"
|
80
|
+
end
|
81
|
+
|
82
|
+
# Converts a two-dimensional array of pixel values to a binary format string.
|
83
|
+
#
|
84
|
+
# +type+:: +:pbm+, +:pgm+, or +:ppm+.
|
85
|
+
# +data+:: A two-dimensional array of bilevel, gray, or RGB values.
|
86
|
+
#
|
87
|
+
# Returns a string containing the pixel data in binary format.
|
88
|
+
def self.array2binary(type, data)
|
89
|
+
height = data.size
|
90
|
+
|
91
|
+
if type == :pbm
|
92
|
+
binary_rows = data.map {|row| row.join }
|
93
|
+
data_string = binary_rows.pack('B*' * height)
|
94
|
+
else
|
95
|
+
data_string = data.flatten.pack('C*')
|
96
|
+
end
|
97
|
+
|
98
|
+
data_string
|
99
|
+
end
|
100
|
+
end
|
101
|
+
end
|