pruim 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
data/README ADDED
@@ -0,0 +1,44 @@
1
+ +------------------------------------+
2
+ | Pruim -- a Pure RUby IMage library |
3
+ +------------------------------------+
4
+
5
+ Introduction
6
+ ------------
7
+
8
+ Pruim is a Pure RUby IMage library, with the indent of supporting
9
+ multiple image formats. Currently only supports loading from and
10
+ saving to PPM files and paletted BMP files. Contributions of
11
+ other formats are welcome. :)
12
+
13
+ Requirements
14
+ ------------
15
+
16
+ BinData (gem install bindata) is required. It makes decoding images much easier.
17
+ Watch and autowatchr for testing.
18
+ Run watchr test/test.watchr to run the tests.
19
+
20
+
21
+ License
22
+ -------
23
+
24
+ You may use Pruim under the Zlib license:
25
+
26
+ Pruim is Copyright (C) 2011 Beoran beoran@rubyforge.org
27
+
28
+ This software is provided 'as-is', without any express or implied
29
+ warranty. In no event will the authors be held liable for any damages
30
+ arising from the use of this software.
31
+
32
+ Permission is granted to anyone to use this software for any purpose,
33
+ including commercial applications, and to alter it and redistribute it
34
+ freely, subject to the following restrictions:
35
+
36
+ 1. The origin of this software must not be misrepresented; you must not
37
+ claim that you wrote the original software. If you use this software
38
+ in a product, an acknowledgment in the product documentation would be
39
+ appreciated but is not required.
40
+ 2. Altered source versions must be plainly marked as such, and must not be
41
+ misrepresented as being the original software.
42
+ 3. This notice may not be removed or altered from any source distribution.
43
+
44
+
data/Rakefile ADDED
@@ -0,0 +1,49 @@
1
+ require 'rubygems'
2
+ require 'rubygems/package_task'
3
+ require 'rake/clean'
4
+ CLEAN.include("pkg/*.gem")
5
+
6
+
7
+ PRUIM_VERSION = "0.1.0"
8
+
9
+ def apply_spec_defaults(s)
10
+ end
11
+
12
+
13
+ spec = Gem::Specification.new do |s|
14
+ s.platform = Gem::Platform::RUBY
15
+ s.summary = "Pure Ruby Image Library."
16
+ s.name = 'pruim'
17
+ s.version = '0.1.0'
18
+ s.add_dependency('bindata', '>= 1.0.0')
19
+ s.add_development_dependency('atto', '>= 0.9.2')
20
+ s.require_path = 'lib'
21
+ s.files = ['README', 'Rakefile'] +
22
+ FileList["lib/pruim.rb", "lib/pruim/*.rb",
23
+ "test/*.rb" , "test/pruim/*.rb" ].to_a
24
+ s.description = <<EOF
25
+ Pruim is a Pure Ruby Image Library. Currently only supports BMP, PPM and PBM
26
+ formats, but preserves palette ordering on reading and writing.
27
+ EOF
28
+ s.author = 'beoran'
29
+ s.email = 'beoran@rubyforge.org'
30
+ s.homepage = 'http://github.com/beoran/pruim'
31
+ s.date = Time.now.strftime '%Y-%m-%d'
32
+ end
33
+
34
+ Gem::PackageTask.new(spec) do |pkg|
35
+ pkg.need_zip = false
36
+ pkg.need_tar = false
37
+ end
38
+
39
+
40
+ task :test do
41
+ for file in FileList["test/*.rb" , "test/pruim/*.rb" ] do
42
+ puts("Running tests for #{file}:")
43
+ res = system("ruby -I lib #{file}")
44
+ puts res ? "OK!" : "Failed!"
45
+ end
46
+ end
47
+
48
+ task :default => :test
49
+
data/lib/pruim/bmp.rb ADDED
@@ -0,0 +1,238 @@
1
+ require 'bindata'
2
+
3
+ module Pruim
4
+ class BMP
5
+ include Codec
6
+
7
+ class Header < BinData::Record
8
+ endian :little
9
+ string :magic, :length => 2
10
+ uint32 :filesize
11
+ uint16 :creator1
12
+ uint16 :creator2
13
+ uint32 :bitmap_offset
14
+ def set(*args)
15
+ self.magic, self.filesize, self.creator1, self.creator2,
16
+ self.bitmap_offset = *args
17
+ self
18
+ end
19
+ end
20
+
21
+ class CoreHeader < BinData::Record
22
+ endian :little
23
+ uint32 :header_size
24
+ int32 :width
25
+ int32 :height
26
+ uint16 :nplanes
27
+ uint16 :bitspp
28
+ def set(*args)
29
+ self.header_size, self.width, self.height, self.nplanes,
30
+ self.bitspp = *args
31
+ self
32
+ end
33
+ end
34
+
35
+ class ExtraHeader < BinData::Record
36
+ endian :little
37
+ # uint32 :header_size
38
+ # int32 :width
39
+ # int32 :height
40
+ # uint16 :nplanes
41
+ # uint16 :bitspp
42
+ uint32 :compress_type
43
+ uint32 :bmp_bytesz
44
+ int32 :hres
45
+ int32 :vres
46
+ uint32 :ncolors
47
+ uint32 :nimpcolors
48
+
49
+ def set(*args)
50
+ self.compress_type, self.bmp_bytesz, self.hres, self.vres, self.ncolors,
51
+ self.nimpcolors = *args
52
+ self
53
+ end
54
+ end
55
+
56
+ BI_RGB = 0 #
57
+ BI_RLE8 = 1 # RLE 8-bit/pixel Can be used only with 8-bit/pixel bitmaps
58
+ BI_RLE4 = 2 # RLE 4-bit/pixel Can be used only with 4-bit/pixel bitmaps
59
+ BI_BITFIELDS = 3 # Bit field or Huffman 1D compression for BITMAPCOREHEADER2
60
+ BI_JPEG = 4 # JPEG or RLE-24 compression for BITMAPCOREHEADER2
61
+ BI_PNG = 5 # PNG The bitmap contains a PNG image.
62
+ BITMAPINFOHEADER=40 # Commonly used
63
+ BITMAPV5HEADER =124# Latest version
64
+ HEADER_SIZE = 14
65
+
66
+
67
+ class BGRX < BinData::Record
68
+ uint8 :b
69
+ uint8 :g
70
+ uint8 :r
71
+ uint8 :x
72
+ def set(b, g, r, x)
73
+ self.b = b
74
+ self.g = g
75
+ self.r = r
76
+ self.x = x
77
+ self
78
+ end
79
+ end
80
+
81
+ class BGR < BinData::Record
82
+ uint8 :b
83
+ uint8 :g
84
+ uint8 :r
85
+
86
+ def set(b, g, r)
87
+ self.b = b
88
+ self.g = g
89
+ self.r = r
90
+ self
91
+ end
92
+ end
93
+
94
+ class ColorTableRGBX < BinData::Record
95
+ array :type => BGRX
96
+ end
97
+
98
+ def can_decode?(io)
99
+ header = Header.read(io)
100
+ io.rewind
101
+ return header && header.magic == 'BM'
102
+ end
103
+
104
+
105
+ def read_palette(io, header, core, extra)
106
+ palette = Pruim::Palette.new
107
+ ncolors = 2 ** core.bitspp
108
+ for i in 0..255
109
+ color = BGRX.read(io)
110
+ palette.new_rgb(color.r, color.g, color.b)
111
+ raise "Unexpected end of file whilst reading bmp palette!" if io.eof?
112
+ end
113
+ return palette
114
+ end
115
+
116
+
117
+ def decode_bpp8_rgb(io, header, core, extra, padding)
118
+ data = []
119
+ for ypos in (0...core.height)
120
+ size = core.width + padding
121
+ # read a line with padding
122
+ raise "End of file when reading BMP btyes!" if io.eof?
123
+ str = io.read(size)
124
+ raise "Short read when reading BMP bytes!" unless str && str.bytesize == size
125
+ # make an array out of it and drop the padding
126
+ arr = str.bytes.to_a
127
+ arr.pop(padding)
128
+ data = arr + data # prepend data
129
+ end
130
+ return data
131
+ end
132
+
133
+ # Calculate padding size
134
+ def calc_padding(wide, palette = true)
135
+ bs = palette ? 1 : 3
136
+ padding = (1 * bs * wide) % 4;
137
+ padding = 4 - padding if padding != 0
138
+ return padding
139
+ end
140
+
141
+
142
+ def decode_bpp8(io, header, core, extra)
143
+ # p header, core, extra
144
+ io.seek(HEADER_SIZE + core.header_size)
145
+ palette = read_palette(io, header, core, extra)
146
+ # Skip to the bitmap, gap may be there...
147
+ io.seek(header.bitmap_offset)
148
+ # Calculate padding size
149
+ padding = calc_padding(core.width, true)
150
+ data = nil
151
+ case extra.compress_type
152
+ when BI_RGB
153
+ data = decode_bpp8_rgb(io, header, core, extra, padding)
154
+ else
155
+ return nil
156
+ end
157
+ image = Image.new(core.width, core.height, :depth => core.bitspp,
158
+ :palette => palette, :pages => 1, :data => [data])
159
+ return image
160
+ end
161
+
162
+ def decode(io)
163
+ header = Header.read(io)
164
+ core = CoreHeader.read(io)
165
+ extra = nil
166
+ if (core.header_size == BITMAPINFOHEADER)
167
+ extra = ExtraHeader.read(io)
168
+ end
169
+ io.seek(HEADER_SIZE + core.header_size)
170
+ # skip rest of header that we don't support
171
+ if core.bitspp == 8
172
+ return decode_bpp8(io, header, core, extra)
173
+ end
174
+ # TODO: real color bitmaps.
175
+ return nil
176
+ end
177
+
178
+ def encode_palette(image, io)
179
+ image.palette.each do |color|
180
+ r, g, b = *Color.to_rgb(color)
181
+ bgrx = BGRX.new.set(b, g, r, 0)
182
+ bgrx.write(io)
183
+ end
184
+ end
185
+
186
+ def encode_bpp8_rgb(image, io, padding)
187
+ page = image.active
188
+ ypos = image.h - 1
189
+ while ypos >= 0
190
+ row = page.row(ypos)
191
+ row = row + [0] * padding
192
+ str = row.pack('C*')
193
+ io.write(str)
194
+ ypos -= 1
195
+ end
196
+ end
197
+
198
+
199
+ def encode_bpp8(image, io, padding)
200
+ encode_palette(image, io)
201
+ encode_bpp8_rgb(image, io, padding)
202
+ end
203
+
204
+ def encode_bpp24(image, io, padding)
205
+ end
206
+
207
+ def encode(image, io)
208
+ bitcount = (image.palette? ? 8 : 24)
209
+ bitmap_size = ((image.w * bitcount) / 8) * image.h
210
+ info_size = BITMAPINFOHEADER
211
+ # Data for the palette
212
+ if image.palette?
213
+ info_size += image.palette.size * 4
214
+ end
215
+ total_size = 14 + info_size + bitmap_size
216
+ # write bmp header info
217
+ header = Header.new.set('BM', total_size, 0, 0, 14 + info_size)
218
+ header.write(io)
219
+ core = CoreHeader.new.set(BITMAPINFOHEADER, image.w, image.h, 1, bitcount)
220
+ core.write(io)
221
+ extra = ExtraHeader.new.set(BI_RGB, 0, 0, bitcount, image.palette.size, 0)
222
+ extra.write(io)
223
+ # Calculate padding size
224
+ padding = calc_padding(image.active.w, image.palette?)
225
+ if image.palette?
226
+ encode_bpp8(image, io, padding)
227
+ else
228
+ encode_bpp24(image, io, padding)
229
+ end
230
+ return image
231
+ end
232
+
233
+
234
+
235
+ end
236
+ end
237
+
238
+
@@ -0,0 +1,74 @@
1
+ module Pruim
2
+ module Codec
3
+
4
+ def self.register(name, klass)
5
+ @codecs ||= {}
6
+ @codecs[name] = klass
7
+ end
8
+
9
+ # Returns a codec based on it's short name.
10
+ def self.for_name(name)
11
+ return Pruim.const_get(name.upcase)
12
+ end
13
+
14
+ # Returns the codec to use for the given filename, based on the extension
15
+ def self.for_filename(filename)
16
+ ext = File.extname(flename) # get filename extension
17
+ return self.codec_for(name)
18
+ end
19
+
20
+ # Returns an instance of codec to use for the given filename, based on the
21
+ # extension of the filename.
22
+ def self.new_for_filename(filename)
23
+ codec = for_filename(filename)
24
+ return codec.new()
25
+ end
26
+
27
+ # Returns a new instance of a codec based on it's short name.
28
+ def self.new_for_name(name)
29
+ codec = for_name(name)
30
+ return nil unless codec
31
+ return codec.new()
32
+ end
33
+
34
+ # Returns a codec based on it's short name or on the filename's extension.
35
+ def self.for_filename_name(filename, codecname = nil)
36
+ codec = nil
37
+ return for_name(codecname) if codecname
38
+ return for_filename(codecname)
39
+ end
40
+
41
+ # Returns a new instance of a codec based on it's short name or on
42
+ # the filename's extension.
43
+ def self.new_for_filename_name(filename, codecname = nil)
44
+ codec = for_filename_name(filename, codecname)
45
+ return nil unless codec
46
+ return codec.new()
47
+ end
48
+
49
+ # Stream should be an StringIO, or otherwise IO compatible object.
50
+ def decode(io)
51
+ raise "not implemented"
52
+ end
53
+
54
+ def encode(image, io)
55
+ raise "not implemented"
56
+ end
57
+
58
+ def can_decode?(io)
59
+ raise "not implemented"
60
+ end
61
+
62
+ def can_encode?(image)
63
+ raise "not implemented"
64
+ end
65
+
66
+ def encode_will_degrade?(image)
67
+ raise "not implemented"
68
+ end
69
+
70
+ def text
71
+ return "A codec that does nothing."
72
+ end
73
+ end
74
+ end
@@ -0,0 +1,34 @@
1
+ module Pruim
2
+ class Color
3
+ def self.rgba(r, g, b, a)
4
+ (a | (b << 8) | (g << 16) | (r << 24))
5
+ end
6
+
7
+ def self.rgb(r, g, b)
8
+ return rgba(r, g, b, 255)
9
+ end
10
+
11
+ # Cpolors are encoded in abgr
12
+ def self.to_rgba(color)
13
+ a = (color) & 255
14
+ b = (color >> 8) & 255
15
+ g = (color >> 16) & 255
16
+ r = (color >> 24) & 255
17
+ return r, g, b, a
18
+ end
19
+
20
+ def self.to_rgb(color)
21
+ r, g, b, a = to_rgba(color)
22
+ return r, g, b
23
+ end
24
+
25
+ BRIGHT_TRESHOLD = 382
26
+
27
+ # Returns true if the color is bright (above threshold)
28
+ # And false if black. Transparency is takeninto account as well.
29
+ def to_bool(treshold = BRIGHT_TRESHOLD)
30
+
31
+ end
32
+
33
+ end
34
+ end
@@ -0,0 +1,160 @@
1
+ module Pruim
2
+ # An image consists of one or more pages.
3
+ # Whether a page is a layer, a frame in an animation
4
+ # or both, is determined by the properties of the page.
5
+
6
+ class Image
7
+
8
+ attr_reader :w
9
+ attr_reader :h
10
+ attr_reader :palette
11
+ attr_reader :pages
12
+ # Currently "active" page.
13
+ attr_reader :active
14
+ # image mode, may be one of :monochrome, :palette, :grayscale, :rgba
15
+ attr_reader :mode
16
+ # Color depth, may be one of 1, 2, 4, 8, 16, 24, 32
17
+ attr_reader :depth
18
+
19
+ # Extra information data hash table.
20
+ attr_reader :info
21
+
22
+ def self.depth_for_colors(ncolors)
23
+ (Math.log(ncolors) / Math.log(2)).to_i
24
+ end
25
+
26
+ # Sets the comment for this image
27
+ def comment=(comm)
28
+ @info[:comment] = comm
29
+ end
30
+
31
+ # gets the comment for this image
32
+ def comment
33
+ @info[:comment]
34
+ end
35
+
36
+ def initialize(w, h, extra = {})
37
+ @w = w
38
+ @h = h
39
+ @info = {}
40
+ @palette = extra[:palette]
41
+ @mode = extra[:mode]
42
+ @mode ||= (@palette ? :palette : :rgba)
43
+ @palette = Palette.new if !@palette && @mode == :palette
44
+ @depth = extra[:depth]
45
+ @depth ||= (@palette ? 8 : 32)
46
+ @pages = []
47
+ @ordered = {}
48
+ @active = nil
49
+ # Construct many pages.
50
+ if extra[:pages]
51
+ data = extra[:data] || []
52
+ extra[:pages].times { |i| self.new_page(@w, @h, :data => data[i]) }
53
+ # Construct a single page if data is given nevertheless
54
+ elsif extra[:data]
55
+ self.new_page(@w, @h, :data => extra[:data])
56
+ end
57
+ # Set comments if any
58
+ self.comment = extra[:comment]
59
+ end
60
+
61
+ def palette?
62
+ return !(@palette.nil?)
63
+ end
64
+
65
+ # Sets the page with the given index as active if it exists.
66
+ def activate(index)
67
+ @active = @pages[index]
68
+ end
69
+
70
+ # Create a new a page and adds it to this image.
71
+ # The page is also set as the active page.
72
+ def new_page(w = nil, h = nil, extra = {})
73
+ w ||= self.w
74
+ h ||= self.h
75
+ page = Page.new(self, w, h, extra)
76
+ return self.add_page(page)
77
+ end
78
+
79
+ # Adds a page to this image. The page is also set as the active page.
80
+ def add_page(page)
81
+ @pages << page
82
+ @ordered[page.frame] = {} unless @ordered[page.frame]
83
+ @ordered[page.frame][page.layer] = [] unless @ordered[page.frame][page.layer]
84
+ @ordered[page.frame][page.layer] << page
85
+ @active = page
86
+ return page
87
+ end
88
+
89
+ # Returns an array of all pages at the given frame and layer
90
+ def pages_at(frame = 0, layer = 0)
91
+ @ordered[frame, layer]
92
+ end
93
+
94
+ # Gets a pixel from the current active page, if any.
95
+ def getpixel(x, y)
96
+ @active.getpixel!(x, y)
97
+ end
98
+
99
+ # Sets a pixel to the current active page, if any.
100
+ def putpixel(x, y, color)
101
+ @active.putpixel!(x, y, color)
102
+ end
103
+
104
+ # Fills the current active page, if any.
105
+ def fill(color)
106
+ @active.fill(color)
107
+ end
108
+
109
+ # Saves the image to the given filename using the given codec
110
+ # If codec is missig, it's determined from the filename's extension.
111
+ def save_as(filename, codecname = nil)
112
+ codec = Codec.new_for_filename_name(filename, codecname)
113
+ return false unless codec
114
+ io = File.open(filename, 'wb+')
115
+ res = codec.encode(self, io)
116
+ io.close
117
+ return res
118
+ end
119
+
120
+ # Loads the image from the given filename using the given codec
121
+ # If codec is missig, it's determined from the filename's extension.
122
+ def self.load_from(filename, codecname = nil)
123
+ codec = Codec.new_for_filename_name(filename, codecname)
124
+ return false unless codec
125
+ io = File.open(filename, 'rb+')
126
+ res = nil
127
+ if codec.can_decode?(io)
128
+ res = codec.decode(io)
129
+ else
130
+ raise "Malformed file #{filename} cannot be decoded as a #{codec} file."
131
+ end
132
+ io.close
133
+ return res
134
+ end
135
+
136
+ # Creates a new rgb color for use with this image.
137
+ # If the image is palleted, the color is added to the palette and the index
138
+ # is returned, otherwise the color is returned
139
+ def new_rgb(r, g, b)
140
+ if palette?
141
+ return @palette.new_rgb
142
+ else
143
+ return Color.rgb(r, g, b)
144
+ end
145
+ end
146
+
147
+ # Creates a new rgba color for use with this image.
148
+ # If the image is palleted, the color is added to the palette and the index
149
+ # is returned, otherwise the color is returned
150
+ def new_rgba(r, g, b, a)
151
+ if palette?
152
+ return @palette.new_rgba
153
+ else
154
+ return Color.rgba(r, g, b, a)
155
+ end
156
+ end
157
+
158
+
159
+ end
160
+ end
@@ -0,0 +1,15 @@
1
+ module Pruim
2
+ class Movie
3
+ attr_reader :w
4
+ attr_reader :h
5
+ attr_reader :frames
6
+
7
+ def initialize(w, h, index = 0, nframes = 1, nlayers = 1, data = nil)
8
+ @w = w
9
+ @h = h
10
+ @frames = Array.new(nframes) do | index |
11
+ Movie.new(self, w, h, index, nlayers, data)
12
+ end
13
+ end
14
+ end
15
+ end
data/lib/pruim/page.rb ADDED
@@ -0,0 +1,82 @@
1
+ module Pruim
2
+ # A Page can represents both a layer and a frame of animation, for
3
+ # multi-page image formats.
4
+ # Inside a Page, bitmap data is stored in an array as follows:
5
+ # For a monochrome image, true and false are stored, where true is "black"
6
+ # meaning pixel activated, on, say, an LCD screen, and false means white
7
+ # or pixel not activated.
8
+ # For :palette mode images, what is stored are palette indexes.
9
+ # for an RBGA , integers are stored that encode the color in an 8888 bits
10
+ # ABGR way
11
+ class Page
12
+ attr_reader :image
13
+ attr_reader :w
14
+ attr_reader :h
15
+ attr_reader :layer
16
+ attr_reader :frame
17
+ attr_reader :x
18
+ attr_reader :y
19
+
20
+ # Extra information data hash table.
21
+ attr_reader :info
22
+
23
+ # Returns a row of pixels with the given y coordinates as an array
24
+ def row(y)
25
+ return @data.slice(y * self.w, self.w)
26
+ end
27
+
28
+
29
+ def pixels
30
+ return @data
31
+ end
32
+
33
+ def initialize(image, w, h, extra = {})
34
+ @image = image
35
+ @info = {}
36
+ @w = w
37
+ @h = h
38
+ @frame = extra[:frame] || 0
39
+ @layer = extra[:layer] || 0
40
+ @x = extra[:x] || 0
41
+ @y = extra[:y] || 0
42
+ @data = extra[:data]
43
+ if !@data
44
+ @data = Array.new(@h * @w, 0)
45
+ end
46
+ end
47
+
48
+ # Returns true if the coordinates are outside of the limits of this page.
49
+ def outside?(x, y)
50
+ return true if (x < 0 ) || (y < 0 )
51
+ return true if (x >= @w) || (y >= @h)
52
+ return false
53
+ end
54
+
55
+ # Gets the pixel at coordinates x,y. Returns nil if out of bounds.
56
+ def getpixel!(x, y)
57
+ @data[y * @w + x]
58
+ end
59
+
60
+ # Gets the pixel at coordinates x,y. Returns nil if out of bounds.
61
+ def getpixel(x, y)
62
+ return getpixel!(x, y)
63
+ end
64
+
65
+ def putpixel!(x, y, color)
66
+ @data[y * @w + x] = color
67
+ end
68
+
69
+ def putpixel(x, y, color)
70
+ return nil if outside?(x, y)
71
+ putpixel!(x, y, color)
72
+ end
73
+
74
+ def fill(color)
75
+ for i in (0..(@h*@w))
76
+ @data[i] = color
77
+ end
78
+ end
79
+
80
+
81
+ end
82
+ end
@@ -0,0 +1,17 @@
1
+ module Pruim
2
+ class Palette < Array
3
+
4
+ # Adds a new RGB color to this palette. Returns the palette index.
5
+ def new_rgb(r, g, b)
6
+ self << Pruim::Color.rgb(r, g, b)
7
+ return self.size - 1
8
+ end
9
+
10
+ # Adds a new RGBA color to this palette. Returns the palette index.
11
+ def new_rgba(r, g, b, a)
12
+ self << Pruim::Color.rgba(r, g, b, a)
13
+ return self.size - 1
14
+ end
15
+
16
+ end
17
+ end
data/lib/pruim/pbm.rb ADDED
@@ -0,0 +1,70 @@
1
+ # Tiny PBM parser, only works for 2 color (monochrome) pbm's
2
+ #
3
+ module Pruim
4
+ # Tiny PBM parser integrated with Prium codecs.
5
+ class PBM
6
+ include Codec
7
+ Codec.register('pbm', self)
8
+
9
+ # Read in a pbm file from an io object.
10
+ def decode(stream)
11
+ header = stream.gets
12
+ lines = []
13
+ until stream.eof?
14
+ lines << stream.gets
15
+ end
16
+ data = []
17
+ image = nil
18
+ page = nil
19
+ w, h = nil, nil
20
+ y = 0
21
+ comment = ''
22
+ lines.each do |line|
23
+ if line[0] == '#'
24
+ comment << line.chomp.sub(/\A#/, '')
25
+ next
26
+ end
27
+ if !w
28
+ w , h = line.chomp.split(' ').map { |v| v.to_i }
29
+ next
30
+ end
31
+ # Converts 1 to true, rest to false.
32
+ bits = line.chomp.split('').map { |v| (v == '1') }
33
+ data += bits
34
+ end
35
+ image = Image.new(w, h, :mode => :monochrome, :data => data)
36
+ image.comment = comment
37
+ return image
38
+ end
39
+
40
+ def encode(image, stream)
41
+ stream.puts("P1")
42
+ stream.puts("##{image.comment}") if image.comment
43
+ stream.puts("#{image.w} #{image.h}")
44
+ page = image.pages.first
45
+ for y in (0...page.h) do
46
+ for x in (0...page.w) do
47
+ white = page.getpixel!(x, y)
48
+ stream.write(white ? '1' : '0')
49
+ end
50
+ stream.puts()
51
+ end
52
+ end
53
+
54
+ def can_decode?(stream)
55
+ header = stream.gets
56
+ stream.rewind
57
+ return header == "P1\n"
58
+ end
59
+
60
+ # Can only encode monocrome images.
61
+ def can_encode?(image)
62
+ return image.mode == :monochrome
63
+ end
64
+
65
+ # Will only save first page of image.
66
+ def encode_will_degrade?(image)
67
+ return (image.pages.size > 1)
68
+ end
69
+ end # class PBM
70
+ end # module Pruim
data/lib/pruim/ppm.rb ADDED
@@ -0,0 +1,76 @@
1
+ module Pruim
2
+ class PPM
3
+ include Codec
4
+ Codec.register('ppm', self)
5
+
6
+ def decode(stream)
7
+ header = stream.gets
8
+ lines = []
9
+ until stream.eof?
10
+ lines << stream.gets
11
+ end
12
+ image = nil
13
+ page = nil
14
+ w, h, d = nil, nil
15
+ y = 0
16
+ comment = ''
17
+ lines.each do |line|
18
+ if line[0] == '#'
19
+ comment << line.chomp.sub(/\A#/, '')
20
+ next
21
+ end
22
+ if !image
23
+ w , h = line.chomp.split(' ').map { |v| v.to_i }
24
+ image = Image.new(w, h)
25
+ page = image.new_page(w, h)
26
+ next
27
+ end
28
+ if !d
29
+ d = line.chomp.to_i
30
+ next
31
+ end
32
+ triplets = line.chomp.split(' ').map { |v| v.to_i }
33
+ for x in (0...page.w) do
34
+ r, g, b = triplets.shift(3)
35
+ color = Color.rgb(r, g, b)
36
+ page.putpixel(x, y, color)
37
+ end
38
+ y += 1
39
+ end
40
+ image.comment = comment
41
+ return image
42
+ end
43
+
44
+
45
+ def encode(image, stream)
46
+ stream.puts("P3")
47
+ stream.puts("##{image.comment}") if image.comment
48
+ stream.puts("#{image.w} #{image.h}")
49
+ stream.puts("255")
50
+ page = image.pages.first
51
+ for y in (0...page.h) do
52
+ for x in (0...page.w) do
53
+ color = page.getpixel!(x, y)
54
+ r, g, b = Color.to_rgb(color)
55
+ stream.write(" ") if x > 0
56
+ stream.write("#{r} #{g} #{b}")
57
+ end
58
+ stream.write("\n")
59
+ end
60
+ end
61
+
62
+ def can_decode?(stream)
63
+ header = stream.gets
64
+ stream.rewind
65
+ return header == "P3\n"
66
+ end
67
+
68
+ def can_encode?(image)
69
+ return true
70
+ end
71
+
72
+ def encode_will_degrade?(image)
73
+ return (image.pages.size > 1)
74
+ end
75
+ end
76
+ end
data/lib/pruim.rb ADDED
@@ -0,0 +1,10 @@
1
+ module Pruim
2
+ autoload :Color , 'pruim/color'
3
+ autoload :Codec , 'pruim/codec'
4
+ autoload :Palette , 'pruim/palette'
5
+ autoload :Image , 'pruim/image'
6
+ autoload :Page , 'pruim/page'
7
+ autoload :PBM , 'pruim/pbm'
8
+ autoload :PPM , 'pruim/ppm'
9
+ autoload :BMP , 'pruim/bmp'
10
+ end
data/test/atto.rb ADDED
@@ -0,0 +1,9 @@
1
+ # Atto is a tiny KISS library full of useful functionality for TDD and BDD.
2
+ module Atto
3
+ # Ansi colors for red/green display after testing.
4
+ autoload :Ansi, 'atto/ansi'
5
+ # TDD
6
+ autoload :Test, 'atto/test'
7
+ # BDD
8
+ autoload :Spec, 'atto/spec'
9
+ end
@@ -0,0 +1,90 @@
1
+ require 'test_helper'
2
+ require 'pruim'
3
+
4
+ test_w, test_h = 64, 32
5
+ bitmap1_w, bitmap1_h = 64, 64
6
+
7
+ assert { Pruim }
8
+ assert { Pruim::BMP }
9
+ inname = test_file('data', 'bitmap1.bmp')
10
+ inname2 = test_file('data', 'bitmap_alpha.bmp')
11
+ outname = test_file('data', 'out1.bmp')
12
+ outname2= test_file('data', 'out2.bmp')
13
+
14
+ codec = Pruim::Codec.for_name('bmp')
15
+ assert { codec }
16
+
17
+ image = Pruim::Image.load_from(inname, :bmp)
18
+
19
+ #
20
+ # fin = File.open(inname, 'r+')
21
+ # assert { codec.can_decode?(fin) }
22
+ # image = nil
23
+ # assert { image = codec.decode(fin) }
24
+ # fin.close
25
+ assert { image.w == bitmap1_w }
26
+ assert { image.h == bitmap1_h }
27
+
28
+ image.save_as(outname, :bmp)
29
+
30
+ #
31
+ # fout = File.open(outname, 'w+')
32
+ # assert { codec.encode(image, fout) }
33
+ # fout.close
34
+
35
+ # system("display #{outname} &")
36
+
37
+
38
+ image2 = Pruim::Image.new(test_w, test_h, :mode => :palette, :pages => 1)
39
+ assert { image2 }
40
+ assert { image2.w == test_w }
41
+ assert { image2.h == test_h }
42
+ gray = image2.palette.new_rgb(127, 128, 129)
43
+ red = image2.palette.new_rgb(255, 0, 0)
44
+ green = image2.palette.new_rgb(0 , 255, 255)
45
+ blue = image2.palette.new_rgb(0 , 0, 255)
46
+ p red
47
+ image2.fill(gray)
48
+ image2.putpixel(4, 5, red)
49
+ image2.putpixel(5, 6, green)
50
+ image2.putpixel(6, 7, blue)
51
+ assert { image2.getpixel(4, 5) == red }
52
+ assert { image2.getpixel(5, 6) == green }
53
+ assert { image2.getpixel(6, 7) == blue }
54
+
55
+ image2.save_as(outname2, :bmp)
56
+ #
57
+ # fout2 = File.open(outname2, 'w+')
58
+ # assert { codec.encode(image2, fout2) }
59
+ # fout2.close
60
+ assert { system("display #{outname2} &") }
61
+
62
+
63
+
64
+ #
65
+ # assert { codec }
66
+ # assert { codec.encode(image, fout) }
67
+ # fout.close
68
+ # # system("eog #{outname} &")
69
+ # fin = File.open(outname, 'r+')
70
+ # image2 = nil
71
+ # assert { image2 = codec.decode(fin) }
72
+ # fin.close
73
+ # page2 = image2.active
74
+ # assert { page2 }
75
+ # assert { image2.w == test_w }
76
+ # assert { image2.h == test_h }
77
+ # assert { page2.w == test_w }
78
+ # assert { page2.h == test_h }
79
+ #
80
+ # assert { page2.getpixel!(1, 1) == green }
81
+ # assert { page2.getpixel!(10, 10) == blue }
82
+ # assert { page2.getpixel!(0, 0) == red }
83
+ #
84
+ # assert { image2.getpixel(1, 1) == green }
85
+ # assert { image2.getpixel(10, 10) == blue }
86
+ # assert { image2.getpixel(0, 0) == red }
87
+ #
88
+
89
+
90
+
@@ -0,0 +1,34 @@
1
+ require 'test_helper'
2
+ require 'pruim'
3
+
4
+ test_w, test_h = 64, 32
5
+
6
+ assert { Pruim }
7
+ assert { Pruim::Image }
8
+ image = Pruim::Image.new(test_w, test_h, :pages => 1)
9
+ assert { image }
10
+ assert { image.w == test_w }
11
+ assert { image.h == test_h }
12
+ red = Pruim::Color.rgb(255, 0, 0)
13
+ green = Pruim::Color.rgb(0 ,255, 255)
14
+ blue = Pruim::Color.rgb(0 , 0, 255)
15
+ page = image.active
16
+ assert { page }
17
+ assert { page.w == test_w }
18
+ assert { page.h == test_h }
19
+ page.fill(red)
20
+ page.putpixel(1, 1, green)
21
+ page.putpixel(10, 10, blue)
22
+ assert { image.getpixel(1, 1) == green }
23
+ assert { image.getpixel(10, 10) == blue }
24
+ assert { image.getpixel(0, 0) == red }
25
+
26
+ image2 = Pruim::Image.new(2, 3, :data => [ 1, 2, 3, 4, 5, 6])
27
+ assert { image2 }
28
+ assert { image2.getpixel(0, 0) == 1 }
29
+ assert { image2.getpixel(1, 1) == 4 }
30
+ assert { image2.getpixel(1, 2) == 6 }
31
+
32
+
33
+
34
+
@@ -0,0 +1,15 @@
1
+ require 'test_helper'
2
+ require 'pruim'
3
+
4
+ test_w = 2
5
+ test_h = 3
6
+ assert { Pruim }
7
+ assert { Pruim::Page }
8
+ page = Pruim::Page.new(nil, test_w, test_h, :data => [1, 2, 3, 4, 5, 6] )
9
+ assert { page }
10
+ assert { page.w == test_w }
11
+ assert { page.h == test_h }
12
+ assert { page.getpixel(0,0) == 1 }
13
+ assert { page.getpixel(1,2) == 6 }
14
+
15
+
@@ -0,0 +1,36 @@
1
+ require 'test_helper'
2
+ require 'pruim'
3
+
4
+ test_w, test_h = 16, 16
5
+ test_comment = "Test PBM file for Pruim."
6
+ assert { Pruim }
7
+ assert { Pruim::Image }
8
+ image = Pruim::Image.new(test_w, test_h, :mode => :monochrome, :pages => 1)
9
+ page = image.active
10
+ page.fill(false)
11
+ page.putpixel(1, 1, true)
12
+ page.putpixel(10, 10, true)
13
+ image.comment = test_comment
14
+
15
+ outname = test_file("out.pbm")
16
+ assert { image.save_as(outname, "pbm") }
17
+
18
+ image2 = Pruim::Image.load_from(outname, "pbm")
19
+ #
20
+ # fin = File.open(outname, 'r+')
21
+ # image2 = nil
22
+ # assert { image2 = codec.decode(fin) }
23
+ # fin.close
24
+ page2 = image2.active
25
+ assert { page2 }
26
+ assert { image2.w == test_w }
27
+ assert { image2.h == test_h }
28
+ assert { page2.w == test_w }
29
+ assert { page2.h == test_h }
30
+ assert { page2.getpixel!(1, 1) }
31
+ assert { page2.getpixel!(10, 10) }
32
+ assert { !(page2.getpixel!(0, 0)) }
33
+ assert { image2.getpixel(1, 1) }
34
+ assert { image2.getpixel(10, 10) }
35
+ assert { !(image2.getpixel(0, 0)) }
36
+ assert { image2.comment == test_comment }
@@ -0,0 +1,44 @@
1
+ require 'test_helper'
2
+ require 'pruim'
3
+
4
+ test_w, test_h = 64, 32
5
+ test_comment = "Test PPM file for Pruim."
6
+ assert { Pruim }
7
+ assert { Pruim::Image }
8
+ image = Pruim::Image.new(test_w, test_h, :pages => 1)
9
+ red = Pruim::Color.rgb(255, 0, 0)
10
+ green = Pruim::Color.rgb(0 ,255, 255)
11
+ blue = Pruim::Color.rgb(0 , 0, 255)
12
+ page = image.active
13
+ page.fill(red)
14
+ page.putpixel(1, 1, green)
15
+ page.putpixel(10, 10, blue)
16
+ image.comment = test_comment
17
+
18
+ outname = test_file("out.ppm")
19
+ assert { image.save_as(outname, "ppm") }
20
+
21
+ image2 = Pruim::Image.load_from(outname, "ppm")
22
+ #
23
+ # fin = File.open(outname, 'r+')
24
+ # image2 = nil
25
+ # assert { image2 = codec.decode(fin) }
26
+ # fin.close
27
+ page2 = image2.active
28
+ assert { page2 }
29
+ assert { image2.w == test_w }
30
+ assert { image2.h == test_h }
31
+ assert { page2.w == test_w }
32
+ assert { page2.h == test_h }
33
+
34
+ assert { page2.getpixel!(1, 1) == green }
35
+ assert { page2.getpixel!(10, 10) == blue }
36
+ assert { page2.getpixel!(0, 0) == red }
37
+
38
+ assert { image2.getpixel(1, 1) == green }
39
+ assert { image2.getpixel(10, 10) == blue }
40
+ assert { image2.getpixel(0, 0) == red }
41
+ assert { image2.comment == test_comment }
42
+
43
+
44
+
@@ -0,0 +1,20 @@
1
+ # require 'nanotest'
2
+ # require 'redgreen'
3
+
4
+ require 'pathname'
5
+ require 'timeout'
6
+
7
+ $: << '.'
8
+
9
+ require 'atto'
10
+ include Atto::Test
11
+
12
+
13
+ def test_file(*fname)
14
+ return File.join('test', *fname)
15
+ end
16
+
17
+
18
+ $: << '../lib'
19
+
20
+
@@ -0,0 +1,47 @@
1
+ require 'test_helper'
2
+ require 'pruim'
3
+
4
+ test_w, test_h = 64, 32
5
+
6
+ assert { Pruim }
7
+ assert { Pruim::Image }
8
+ image = Pruim::Image.new(test_w, test_h)
9
+ assert { image }
10
+ assert { image.w == test_w }
11
+ assert { image.h == test_h }
12
+ red = Pruim::Color.rgb(255, 0, 0)
13
+ green = Pruim::Color.rgb(0 ,255, 255)
14
+ blue = Pruim::Color.rgb(0 , 0, 255)
15
+ page = image.new_page(test_w, test_h)
16
+ assert { page }
17
+ assert { page.w == test_w }
18
+ assert { page.h == test_h }
19
+ page.fill(red)
20
+ page.putpixel(1, 1, green)
21
+ page.putpixel(10, 10, blue)
22
+ outname = test_file("out.ppm")
23
+ fout = File.open(outname, 'w+')
24
+ codec = Pruim::Codec.new_codec_for('ppm')
25
+ assert { codec }
26
+ assert { codec.encode(image, fout) }
27
+ fout.close
28
+ # system("eog #{outname} &")
29
+ fin = File.open(outname, 'r+')
30
+ image2 = nil
31
+ assert { image2 = codec.decode(fin) }
32
+ fin.close
33
+ page2 = image2.pages.first
34
+ assert { page2 }
35
+ assert { image2.w == test_w }
36
+ assert { image2.h == test_h }
37
+ assert { page2.w == test_w }
38
+ assert { page2.h == test_h }
39
+
40
+ assert { page2.getpixel!(1, 1) == green }
41
+ assert { page2.getpixel!(10, 10) == blue }
42
+ assert { page2.getpixel!(0, 0) == red }
43
+
44
+
45
+
46
+
47
+
metadata ADDED
@@ -0,0 +1,94 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: pruim
3
+ version: !ruby/object:Gem::Version
4
+ prerelease:
5
+ version: 0.1.0
6
+ platform: ruby
7
+ authors:
8
+ - beoran
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+
13
+ date: 2011-12-13 00:00:00 Z
14
+ dependencies:
15
+ - !ruby/object:Gem::Dependency
16
+ name: bindata
17
+ prerelease: false
18
+ requirement: &id001 !ruby/object:Gem::Requirement
19
+ none: false
20
+ requirements:
21
+ - - ">="
22
+ - !ruby/object:Gem::Version
23
+ version: 1.0.0
24
+ type: :runtime
25
+ version_requirements: *id001
26
+ - !ruby/object:Gem::Dependency
27
+ name: atto
28
+ prerelease: false
29
+ requirement: &id002 !ruby/object:Gem::Requirement
30
+ none: false
31
+ requirements:
32
+ - - ">="
33
+ - !ruby/object:Gem::Version
34
+ version: 0.9.2
35
+ type: :development
36
+ version_requirements: *id002
37
+ description: " Pruim is a Pure Ruby Image Library. Currently only supports BMP, PPM and PBM\n formats, but preserves palette ordering on reading and writing.\n"
38
+ email: beoran@rubyforge.org
39
+ executables: []
40
+
41
+ extensions: []
42
+
43
+ extra_rdoc_files: []
44
+
45
+ files:
46
+ - README
47
+ - Rakefile
48
+ - lib/pruim.rb
49
+ - lib/pruim/pbm.rb
50
+ - lib/pruim/bmp.rb
51
+ - lib/pruim/ppm.rb
52
+ - lib/pruim/page.rb
53
+ - lib/pruim/image.rb
54
+ - lib/pruim/palette.rb
55
+ - lib/pruim/codec.rb
56
+ - lib/pruim/color.rb
57
+ - lib/pruim/movie.rb
58
+ - test/test_helper.rb
59
+ - test/test_image.rb
60
+ - test/atto.rb
61
+ - test/pruim/test_bmp.rb
62
+ - test/pruim/test_image.rb
63
+ - test/pruim/test_pbm.rb
64
+ - test/pruim/test_ppm.rb
65
+ - test/pruim/test_page.rb
66
+ homepage: http://github.com/beoran/pruim
67
+ licenses: []
68
+
69
+ post_install_message:
70
+ rdoc_options: []
71
+
72
+ require_paths:
73
+ - lib
74
+ required_ruby_version: !ruby/object:Gem::Requirement
75
+ none: false
76
+ requirements:
77
+ - - ">="
78
+ - !ruby/object:Gem::Version
79
+ version: "0"
80
+ required_rubygems_version: !ruby/object:Gem::Requirement
81
+ none: false
82
+ requirements:
83
+ - - ">="
84
+ - !ruby/object:Gem::Version
85
+ version: "0"
86
+ requirements: []
87
+
88
+ rubyforge_project:
89
+ rubygems_version: 1.8.12
90
+ signing_key:
91
+ specification_version: 3
92
+ summary: Pure Ruby Image Library.
93
+ test_files: []
94
+