rixmap 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,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: