rixmap 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,446 @@
1
+ # -*- coding: utf-8 -*-
2
+
3
+ #
4
+ module Rixmap
5
+ module Format
6
+
7
+ # X-PixMap画像形式実装モジュール.
8
+ module XPM
9
+
10
+ # XPMカラーテーブル
11
+ #
12
+ # 文字と対応するカラー表現を持ちます.
13
+ class XPMColorTable
14
+ include Enumerable
15
+
16
+ # カラー名に使用する文字の一覧
17
+ #
18
+ # @return [String] 文字リスト
19
+ CHARACTERS = " 0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ+/"
20
+
21
+ # @return [Integer] 色コードサイズ
22
+ attr_reader :code_size
23
+
24
+ # @return [Array<String>] コードリスト
25
+ attr_reader :codes
26
+
27
+ # @return [Array<String>] 色データ
28
+ attr_reader :colors
29
+
30
+ # パレットからカラーテーブルを作成します.
31
+ #
32
+ # @param [Rixmap::Palette] palette 元にするパレット
33
+ def initialize(palette)
34
+ @codes = []
35
+ @colors = []
36
+ @code_size = 0
37
+
38
+ nchars = CHARACTERS.size
39
+ csize = Math.log(palette.size, nchars).ceil
40
+ palette.each_with_index do |c, i|
41
+ code = []
42
+ tmp = i
43
+ csize.downto(1) do |j|
44
+ base = (nchars ** (j - 1))
45
+ ch = tmp / base
46
+ tmp = tmp % base
47
+ code << CHARACTERS[ch]
48
+ end
49
+ @codes[i] = code.join('')
50
+ @colors[i] = "#%02X%02X%02X" % [c.red, c.green, c.blue]
51
+ end
52
+ @code_size = csize
53
+ end
54
+
55
+ # 各カラーテーブルデータを順に処理します.
56
+ #
57
+ # カラーテーブルデータは [code, color] の配列で渡されます
58
+ #
59
+ # @yield [Array] [code, color] 形式の配列
60
+ # @return [Enumerator] ブロックを渡さなかった場合はEnumeratorを返します.
61
+ def each
62
+ if block_given?
63
+ @codes.each_with_index do |code, i|
64
+ yield [code, @colors[i]]
65
+ end
66
+ else
67
+ return Enumerator.new(self)
68
+ end
69
+ end
70
+ end
71
+
72
+ # XPM1形式のマジックデータ
73
+ MAGIC_XPM1 = "/* XPM */"
74
+
75
+ # XPM2形式のマジックデータ
76
+ MAGIC_XPM2 = "! XPM2"
77
+
78
+ # XPM1形式のマジックデータを検出するための正規表現
79
+ MAGIC_XPM1_PATTERN = %r{^/\*\s*XPM\s*\*/.*$}
80
+
81
+ # XPM2形式のマジックデータを検出するための正規表現
82
+ MAGIC_XPM2_PATTERN = %r{^!\s+XPM2.*$}
83
+
84
+ # XPM1形式用入出力実装クラス.
85
+ class XPM1ImageIO < Rixmap::ImageIO::BaseImageIO
86
+ # XPM1形式で読み込めるかどうかを調べます.
87
+ #
88
+ # @param [String] magic 先頭バイトデータ
89
+ # @return [Boolean] XPM1形式で読み込める場合はtrue
90
+ def self.readable?(magic)
91
+ if MAGIC_XPM1_PATTERN.match(magci)
92
+ return true
93
+ else
94
+ return false
95
+ end
96
+ end
97
+
98
+ # XPM1形式で書き込めるかどうかを調べます.
99
+ #
100
+ # @param [Rixmap::Image] image 調べる画像
101
+ # @return [Boolean] XPM1形式で書き込める場合はtrue
102
+ def self.writable?(image)
103
+ if image.indexed?
104
+ return true
105
+ else
106
+ return false
107
+ end
108
+ end
109
+
110
+ # 画像をXPM1形式でバイト列にエンコードします.
111
+ #
112
+ # @param [Rixmap::Image] image 対象画像
113
+ # @param [Hash] options オプションパラメータ (未使用)
114
+ # @return [String] バイト列
115
+ def encode(image, options={})
116
+ raise ArgumentError.new("input image is not supported") unless image.indexed?
117
+
118
+ # 画像データ格納変数名
119
+ varname = "XFACE"
120
+
121
+ # 画像データ
122
+ tokens = []
123
+ tokens << MAGIC_XPM1
124
+ tokens << "static char* #{varname}[] = {"
125
+
126
+ # パレットデータ
127
+ ctbl = XPMColorTable.new(image.palette)
128
+ tokens << %Q{"#{image.width} #{image.height} #{image.palette.size} #{ctbl.code_size}",}
129
+ ctbl.each do |item|
130
+ tokens << %Q{"#{item[0]}\tc\t#{item[1]}",}
131
+ end
132
+
133
+ # ピクセルデータ
134
+ image.height.times do |h|
135
+ line = image[h].collect {|v| ctbl.codes[v] }.join("")
136
+ tokens << %Q{"#{line}",}
137
+ end
138
+ tokens[-1] = tokens[-1].slice(0..-2) # 最終行の末尾を調整
139
+
140
+ # データ終端
141
+ tokens << "};"
142
+
143
+ # 連結
144
+ return tokens.join("\n")
145
+ end
146
+
147
+ # バイト列データからXPM1形式として画像を復元します.
148
+ #
149
+ # @param [String] data XPM1形式画像データ
150
+ # @param [Hash] options オプションパラメータ (未使用)
151
+ # @return [Rixmap::Image] 復元された画像
152
+ def decode(data, options={})
153
+ magic, imdata = data.split($/, 2)
154
+ unless MAGIC_XPM1_PATTERN.match(magic)
155
+ raise ArgumentError.new("input image is not XPM image")
156
+ end
157
+
158
+ # 画像データからコメント部分を除去
159
+ imdata.gsub!(%r{/\*.*\*/}, "") # Remove C Style Comments
160
+ imdata.gsub!(%r{//.*$}, "") # Remove C++ Style Comments
161
+
162
+ # XPM画像かどうかをチェック
163
+ xpmmatch = %r{static\s+char\s*\*\s*([[:graph:]]+)\s*\[\]\s*=\s*\{(.*)\};}m.match(imdata)
164
+ unless xpmmatch
165
+ raise ArgumentError.new("input image data is broken as XPM")
166
+ end
167
+
168
+ # 行分割
169
+ #xpmlines = Enumerator.new(xpmmatch[2].strip.split($/))
170
+ xpmlines = xpmmatch[2].strip.split($/).to_enum
171
+
172
+ begin
173
+ # 画像情報を解析
174
+ xpminfo = %r{"\s*([[:digit:]]+)\s+([[:digit:]]+)\s+([[:digit:]]+)\s+([[:digit:]]+)\s*"}.match(xpmlines.next)
175
+ unless xpminfo
176
+ raise ArgumentError.new("input image data is broken as XPM")
177
+ end
178
+ width = xpminfo[1].to_i
179
+ height = xpminfo[2].to_i
180
+ ncolors = xpminfo[3].to_i
181
+ nchars = xpminfo[4].to_i # 一色あたりの文字数
182
+
183
+ # 画像オブジェクト
184
+ palette = Rixmap::Palette.new(ncolors)
185
+ image = Rixmap::Image.new(Rixmap::INDEXED, width, height, :palette => palette)
186
+
187
+ # 色表を構築
188
+ ctbl = {}
189
+ cptn = %r{"\s*(.{#{nchars}})\s+(c)\s+#([0-9A-Fa-f]{6})\s*.*"}
190
+ ncolors.times do |i|
191
+ cline = xpmlines.next
192
+ cmatch = cptn.match(cline)
193
+ if cmatch
194
+ ckey = cmatch[1]
195
+ cvalue = cmatch[3]
196
+ ctbl[ckey] = i
197
+ palette[i] = Rixmap::Color.new(*(cvalue.unpack('a2a2a2').collect {|e| e.to_i(16) }))
198
+ end
199
+ end
200
+
201
+ # ピクセルを解析
202
+ pixptn = %r{"\s*(.{#{nchars * width}})\s*"}
203
+ packfmt = "a#{nchars}" * width
204
+ height.times do |h|
205
+ pixline = xpmlines.next
206
+ pixmatch = pixptn.match(pixline)
207
+ if pixmatch
208
+ pixmatch[1].unpack(packfmt).each_with_index do |e, w|
209
+ image[w, h] = ctbl[e]
210
+ end
211
+ end
212
+ end
213
+
214
+ return image
215
+ rescue StopIteration
216
+ raise RuntimeError.new("illegal content")
217
+ end
218
+ end
219
+ end
220
+
221
+ # XPM2形式用入出力実装クラス.
222
+ class XPM2ImageIO < Rixmap::ImageIO::BaseImageIO
223
+ # XPM2形式で読み込めるかどうかを調べます.
224
+ #
225
+ # @param [String] magic 先頭バイトデータ
226
+ # @return [Boolean] XPM2形式で読み込める場合はtrue
227
+ def self.readable?(magic)
228
+ if MAGIC_XPM2_PATTERN.match(magci)
229
+ return true
230
+ else
231
+ return false
232
+ end
233
+ end
234
+
235
+ # XPM2形式で書き込めるかどうかを調べます.
236
+ #
237
+ # @param [Rixmap::Image] image 調べる画像
238
+ # @return [Boolean] XPM2形式で書き込める場合はtrue
239
+ def self.writable?(image)
240
+ if image.indexed?
241
+ return true
242
+ else
243
+ return false
244
+ end
245
+ end
246
+
247
+ # 画像をXPM2形式でバイト列にエンコードします.
248
+ #
249
+ # @param [Rixmap::Image] image 対象画像
250
+ # @param [Hash] options オプションパラメータ (未使用)
251
+ # @return [String] バイト列
252
+ def encode(image, options={})
253
+ raise ArgumentError.new("input image is not supported") unless image.indexed?
254
+
255
+ # 書き込みバッファ
256
+ tokens = []
257
+ tokens << MAGIC_XPM2
258
+
259
+ # パレット
260
+ ctbl = XPMColorTable.new(image.palette)
261
+ tokens << "#{image.width} #{image.height} #{image.palette.size} #{ctbl.code_size}"
262
+ ctbl.each do |item|
263
+ tokens << "#{item[0]}\tc\t#{item[1]}"
264
+ end
265
+
266
+ # ピクセルデータ
267
+ image.height.times do |h|
268
+ line = image[h].collect {|pixel| ctbl.codes[pixel] }.join("")
269
+ tokens << line
270
+ end
271
+
272
+ # 連結
273
+ return tokens.join("\n")
274
+ end
275
+
276
+ # バイト列データからXPM2形式として画像を復元します.
277
+ #
278
+ # @param [String] data XPM2形式画像データ
279
+ # @param [Hash] options オプションパラメータ (未使用)
280
+ # @return [Rixmap::Image] 復元された画像
281
+ def decode(data, options={})
282
+ magic, imdata = data.split($/, 2)
283
+ unless MAGIC_XPM2_PATTERN.match(magic)
284
+ raise ArgumentError.new("input image is not XPM image")
285
+ end
286
+
287
+ # 行分割
288
+ #xpmlines = Enumerator.new(imdata.split($/))
289
+ xpmlines = imdata.split($/).to_enum
290
+
291
+ begin
292
+ # 画像情報
293
+ xpminfo = %r{\s*([[:digit:]]+)\s+([[:digit:]]+)\s+([[:digit:]]+)\s+([[:digit:]]+)\s*}.match(xpmlines.next)
294
+ unless xpminfo
295
+ raise ArgumentError.new("input image data is broken as XPM")
296
+ end
297
+ width = xpminfo[1].to_i
298
+ height = xpminfo[2].to_i
299
+ ncolors = xpminfo[3].to_i
300
+ nchars = xpminfo[4].to_i
301
+
302
+ # 画像オブジェクト
303
+ palette = Rixmap::Palette.new(ncolors)
304
+ image = Rixmap::Image.new(Rixmap::INDEXED, width, height, :palette => palette)
305
+
306
+ # カラーテーブル
307
+ ctbl = {}
308
+ cptn = %r{\s*(.{#{nchars}})\s+(c)\s+#([0-9A-Fa-f]{6})\s*.*}
309
+ ncolors.times do |i|
310
+ cmatch = cptn.match(xpmlines.next)
311
+ if cmatch
312
+ ckey = cmatch[1]
313
+ cvalue = cmatch[3]
314
+ ctbl[ckey] = i
315
+ palette[i] = Rixmap::Color.new(*(cvalue.unpack('a2a2a2').collect {|e| e.to_i(16) }))
316
+ end
317
+ end
318
+
319
+ # ピクセルデータ
320
+ pixptn = %r{\s*(.{#{nchars * width}})\s*}
321
+ packfmt = "a#{nchars}" * width
322
+ height.times do |h|
323
+ pixmatch = pixptn.match(xpmlines.next)
324
+ if pixmatch
325
+ pixmatch[1].unpack(packfmt).each_with_index do |e, w|
326
+ image[w, h] = ctbl[e]
327
+ end
328
+ end
329
+ end
330
+
331
+ return image
332
+ rescue StopIteration
333
+ raise RuntimeError.new("illegal content")
334
+ end
335
+ end
336
+ end
337
+
338
+ # XPM形式用入出力実装クラス.
339
+ #
340
+ # XPM1とXPM2の出力を分けたり、読み込み時に自動判定したりします.
341
+ class XPMImageIO < Rixmap::ImageIO::BaseImageIO
342
+ # 指定データがXPM1またはXPM2で読み込めるかどうかを調べます.
343
+ #
344
+ # XPM1形式が優先されます.
345
+ #
346
+ # @param [String] magic 先頭バイトデータ
347
+ # @return [Boolean] XPM形式で読み込める場合はtrue
348
+ def self.readable?(magic)
349
+ return XPM1ImageIO.readable?(magic) || XPM2ImageIO.readable?(magic)
350
+ end
351
+
352
+ # 指定画像がXPM1またはXPM2で書き込めるかを判定します.
353
+ #
354
+ # XPM1形式が優先されます.
355
+ #
356
+ # @param [Rixmap::Image] image 調べる画像
357
+ # @return [Boolean] XPM形式で書き込める場合はtrue
358
+ def self.writable?(image)
359
+ return XPM1ImageIO.writable?(image) || XPM2ImageIO.writable?(image)
360
+ end
361
+
362
+ # @param [Hash] options オプションパラメータ
363
+ # @option options [Symbol,Integer,String] :version
364
+ # 処理する際のバージョン. (default: auto)
365
+ # 指定できる値は以下の通り.
366
+ #
367
+ # - :auto
368
+ # - :xpm1
369
+ # - :xpm2
370
+ # - 0 (:autoと同じ)
371
+ # - 1 (:xpm1と同じ)
372
+ # - 2 (:xpm2と同じ)
373
+ def initialize(options = {})
374
+ super(options)
375
+
376
+ @version = :auto unless defined?(@version)
377
+ # 書き込み時の指定とかどうしよう
378
+
379
+ # インスタンスは初期化しておく
380
+ @xpm1iio = XPM1ImageIO.new(options)
381
+ @xpm2iio = XPM2ImageIO.new(options)
382
+ end
383
+
384
+ # 画像をバイト列にエンコードします.
385
+ #
386
+ # @param [Rixmap::Image] image 対象画像
387
+ # @param [Hash] options オプションパラメータ
388
+ # @option options [Symbol,Integer,String] :version エンコードバージョン. 値は #initialize を参照のこと
389
+ # @return [String] バイト列
390
+ # @see XPM1ImageIO#encode
391
+ # @see XPM2ImageIO#encode
392
+ def encode(image, options={})
393
+ version = if options[:version].nil?
394
+ @version
395
+ else
396
+ options[:version]
397
+ end
398
+
399
+ case version
400
+ when 0, 1, :xpm1, :auto
401
+ return @xpm1iio.encode(image, options)
402
+ when 2, :xpm2
403
+ return @xpm2iio.encode(image, options)
404
+ else
405
+ raise NotImplementedError.new("XPM Version #{version} is not implemented")
406
+ end
407
+ end
408
+
409
+ # 画像をバイト列から復元します.
410
+ #
411
+ # @param [String] data バイト列
412
+ # @param [Hash] options オプションパラメータ
413
+ # @option options [Symbol,Integer,String] :version エンコードバージョン. 値は #initialize を参照のこと
414
+ # @return [Rixmap::Image] 復元された画像
415
+ # @see XPM1ImageIO#decode
416
+ # @see XPM2ImageIO#decode
417
+ def decode(data, options={})
418
+ version = if options[:version].nil?
419
+ @version
420
+ else
421
+ options[:version]
422
+ end
423
+
424
+ case version
425
+ when 0, 1, :xpm1, :auto
426
+ return @xpm1iio.decode(data, options)
427
+ when 2, :xpm2
428
+ return @xpm2iio.decode(data, options)
429
+ else
430
+ raise NotImplementedError.new("XPM Version #{version} is not implemented")
431
+ end
432
+ end
433
+ end
434
+
435
+ Rixmap::ImageIO.register(:XPM1, XPM1ImageIO)
436
+ Rixmap::ImageIO.register(:XPM2, XPM2ImageIO)
437
+ Rixmap::ImageIO.register(:XPM, XPMImageIO, [".xpm"])
438
+ end
439
+
440
+ end
441
+ end
442
+
443
+
444
+ #==============================================================================#
445
+ # $Id: xpm.rb,v 57b1fb2cd6a6 2014/04/20 12:21:27 chikuchikugonzalez $
446
+ # vim: set sts=2 ts=2 sw=2 expandtab:
@@ -0,0 +1,10 @@
1
+ # -*- coding: utf-8 -*-
2
+ require_relative 'format/bmp'
3
+ require_relative 'format/pcx'
4
+ require_relative 'format/png'
5
+ require_relative 'format/xpm'
6
+
7
+
8
+ #==============================================================================#
9
+ # $Id: format.rb,v 57b1fb2cd6a6 2014/04/20 12:21:27 chikuchikugonzalez $
10
+ # vim: set sts=2 ts=2 sw=2 expandtab:
@@ -0,0 +1,26 @@
1
+ # -*- coding: utf-8 -*-
2
+
3
+ #
4
+ module Rixmap
5
+
6
+ # メジャーバージョン番号
7
+ VERSION_MAJOR = 0
8
+
9
+ # マイナーバージョン番号
10
+ VERSION_MINOR = 1
11
+
12
+ # リビジョン番号のようなもの
13
+ VERSION_TEENY = 0
14
+
15
+ # バージョン番号 (数値表現)
16
+ VERSION_NUMBER = (VERSION_MAJOR << 16) | (VERSION_MINOR << 8) | VERSION_TEENY
17
+
18
+ # バージョン番号 (文字列)
19
+ VERSION = "#{VERSION_MAJOR}.#{VERSION_MINOR}.#{VERSION_TEENY}"
20
+
21
+ end
22
+
23
+
24
+ #==============================================================================#
25
+ # $Id: version.rb,v 431aac54543a 2014/04/20 15:12:27 chikuchikugonzalez $
26
+ # vim: set sts=2 ts=2 sw=2 expandtab:
data/lib/rixmap.rb ADDED
@@ -0,0 +1,24 @@
1
+ # -*- coding: utf-8 -*-
2
+ #
3
+ # Rixmapルートファイル(`・ω・´)
4
+ #
5
+
6
+ # Rixmapライブラリルートモジュール.
7
+ module Rixmap
8
+ end
9
+
10
+ # 1. バージョンファイルを読み込み
11
+ require_relative 'rixmap/version'
12
+
13
+ # 2. C拡張を読み込み
14
+ require_relative "#{RUBY_PLATFORM}/rixmap"
15
+
16
+ # 3. Ruby実装を読み込み
17
+ require_relative 'rixmap/format'
18
+
19
+ # 4. その他
20
+
21
+
22
+ #==============================================================================#
23
+ # $Id: rixmap.rb,v 57b1fb2cd6a6 2014/04/20 12:21:27 chikuchikugonzalez $
24
+ # vim: set sts=2 ts=2 sw=2 expandtab:
@@ -0,0 +1,12 @@
1
+ # -*- coding: utf-8 -*-
2
+ #
3
+ # Rixmap::Binary Spec
4
+ require_relative '../lib/rixmap'
5
+
6
+ describe Rixmap::Binary do
7
+ end
8
+
9
+
10
+ #==============================================================================#
11
+ # $Id: binary_spec.rb,v add33526d522 2014/04/19 16:27:29 chikuchikugonzalez $
12
+ # vim: set sts=2 ts=2 sw=2 expandtab:
@@ -0,0 +1,76 @@
1
+ # -*- coding: utf-8 -*-
2
+ #
3
+ # Rixmap::Color Spec
4
+ require_relative '../lib/rixmap'
5
+
6
+ describe Rixmap::Color do
7
+ context "Color.new(0)" do
8
+ before do
9
+ @color = Rixmap::Color.new(0)
10
+ end
11
+
12
+ it "red should be 0" do expect(@color.red).to eq(0) end
13
+ it "green should be 0" do expect(@color.green).to eq(0) end
14
+ it "blue should be 0" do expect(@color.blue).to eq(0) end
15
+ it "alpha should be 255" do expect(@color.alpha).to eq(255) end
16
+ it "luminance should be 0" do expect(@color.luminance).to eq(0) end
17
+ it "convert to array is [0, 0, 0, 255]" do expect(@color.to_a).to eq([0, 0, 0, 255]) end
18
+ end
19
+
20
+ context "Color.new(32, 127)" do
21
+ before do
22
+ @color = Rixmap::Color.new(32, 127)
23
+ end
24
+
25
+ it "red should be 32" do expect(@color.red).to eq(32) end
26
+ it "green should be 32" do expect(@color.green).to eq(32) end
27
+ it "blue should be 32" do expect(@color.blue).to eq(32) end
28
+ it "alpha should be 127" do expect(@color.alpha).to eq(127) end
29
+ it "luminance should be 32" do expect(@color.luminance).to eq(32) end
30
+ it "convert to array is [32, 32, 32, 127]" do expect(@color.to_a).to eq([32, 32, 32, 127]) end
31
+ end
32
+
33
+ context "Color.new(64, 94, 114)" do
34
+ before do
35
+ @color = Rixmap::Color.new(64, 94, 114)
36
+ end
37
+
38
+ it "red should be 64" do expect(@color.red).to eq(64) end
39
+ it "green should be 94" do expect(@color.green).to eq(94) end
40
+ it "blue should be 114" do expect(@color.blue).to eq(114) end
41
+ it "alpha should be 255" do expect(@color.alpha).to eq(255) end
42
+ it "luminance should be 91" do expect(@color.luminance).to eq(91) end
43
+ it "convert to array is [64, 94, 114, 255]" do expect(@color.to_a).to eq([64, 94, 114, 255]) end
44
+ end
45
+
46
+ context "Color.new(128, 256, 384, 64)" do
47
+ before do
48
+ @color = Rixmap::Color.new(128, 256, 384, 64)
49
+ end
50
+
51
+ it "red should be 128" do expect(@color.red).to eq(128) end
52
+ it "green should be 255" do expect(@color.green).to eq(255) end
53
+ it "blue should be 255" do expect(@color.blue).to eq(255) end
54
+ it "alpha should be 64" do expect(@color.alpha).to eq(64) end
55
+ it "luminance should be 213" do expect(@color.luminance).to eq(213) end
56
+ it "convert to array is [128, 255, 255, 64]" do expect(@color.to_a).to eq([128, 255, 255, 64]) end
57
+ end
58
+
59
+ context "Color.new('#9932CC')" do
60
+ before do
61
+ @color = Rixmap::Color.new("#9932CC")
62
+ end
63
+
64
+ it "red should be 153" do expect(@color.red).to eq(153) end
65
+ it "green should be 50" do expect(@color.green).to eq(50) end
66
+ it "blue should be 204" do expect(@color.blue).to eq(204) end
67
+ it "alpha should be 255" do expect(@color.alpha).to eq(255) end
68
+ it "luminance should be 136" do expect(@color.luminance).to eq(136) end
69
+ it "convert to array is [153, 50, 204, 255]" do expect(@color.to_a).to eq([153, 50, 204, 255]) end
70
+ end
71
+ end
72
+
73
+
74
+ #==============================================================================#
75
+ # $Id: color_spec.rb,v 5b9a0968eca5 2014/04/20 13:37:36 chikuchikugonzalez $
76
+ # vim: set sts=2 ts=2 sw=2 expandtab: