rixmap 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,438 @@
1
+ # -*- coding: utf-8 -*-
2
+
3
+ #
4
+ module Rixmap
5
+ module Format
6
+
7
+ # PCX画像形式用入出力実装モジュール.
8
+ module PCX
9
+
10
+ # PCXファイルシグネチャバイト
11
+ FILE_SIGNATURE = 0x0A
12
+
13
+ # パレットセパレータバイト
14
+ PALETTE_SEPARATOR = 0x0C
15
+
16
+ # Version 2.5 with fixed EGA Palette information
17
+ FORMAT_VERSION_25 = 0x00
18
+
19
+ # Version 2.8 with modifiable EGA palette information
20
+ FORMAT_VERSION_28_PLT = 0x02
21
+
22
+ # Version 2.8 without palette information
23
+ FORMAT_VERSION_28_NOPLT = 0x03
24
+
25
+ # PC Paintbrush for Windows
26
+ FORMAT_VERSION_PC_WINDOWS = 0x04
27
+
28
+ # Version 3.0
29
+ FORMAT_VERSION_30 = 0x05
30
+
31
+ # RGB or パレット形式定数
32
+ COLORTYPE_NORMAL = 0x01
33
+
34
+ # グレースケール形式
35
+ COLORTYPE_GRAYSCALE = 0x02
36
+
37
+ # 無圧縮形式
38
+ ENCODINGTYPE_NONE = 0x00
39
+
40
+ # RLE圧縮
41
+ ENCODINGTYPE_RLE = 0x01
42
+
43
+ # PCXファイルヘッダ.
44
+ class PCXFileHeader
45
+ attr_accessor :magic
46
+ attr_accessor :version
47
+ attr_accessor :encoding
48
+ attr_accessor :bit_width
49
+
50
+ attr_accessor :left
51
+ attr_accessor :top
52
+ attr_accessor :right
53
+ attr_accessor :bottom
54
+
55
+ attr_accessor :dpi_x
56
+ attr_accessor :dpi_y
57
+
58
+ attr_accessor :palette
59
+
60
+ attr_accessor :bit_planes
61
+ attr_accessor :line_width
62
+ attr_accessor :colortype
63
+
64
+ attr_accessor :screen_x
65
+ attr_accessor :screen_y
66
+
67
+ def initialize()
68
+ @magic = FILE_SIGNATURE
69
+ @version = FORMAT_VERSION_30
70
+ @encoding = ENCODINGTYPE_RLE
71
+ @bit_width = nil
72
+ @left = nil
73
+ @top = nil
74
+ @right = nil
75
+ @bottom = nil
76
+ @dpi_x = 72
77
+ @dpi_y = 72
78
+ @palette = "\x00" * 48
79
+ @bit_planes = nil
80
+ @line_width = nil
81
+ @colortype = COLORTYPE_NORMAL
82
+ @screen_x = 0
83
+ @screen_y = 0
84
+
85
+ # packデータ
86
+ @template = "CCCCssssssa48xCSSssx54"
87
+ end
88
+
89
+ def unpack(data)
90
+ @magic, @version, @encoding, @bit_width, @left, @top, @right, @bottom, @dpi_x, @dpi_y, @palette, @bit_planes, @line_width, @colortype, @screen_x, @screen_y = data.unpack(@template)
91
+ end
92
+
93
+ def pack()
94
+ return [@magic, @version, @encoding, @bit_width, @left, @top, @right, @bottom, @dpi_x, @dpi_y, @palette, @bit_planes, @line_width, @colortype, @screen_x, @screen_y].pack(@template)
95
+ end
96
+ end
97
+
98
+ # RLEエンコーダ
99
+ class RLE
100
+ # パケットを構築します.
101
+ #
102
+ # @param [Integer] byte バイトデータ
103
+ # @param [Integer] count バイトデータの数
104
+ # @return [Array<Integer>] パケットデータ
105
+ def pack(byte, count)
106
+ cnt = count / 0x3F
107
+ rem = count % 0x3F
108
+ packed = [0xFF, byte] * cnt
109
+ if rem > 0
110
+ if byte < 0xC0 && rem == 1
111
+ packed.concat([byte])
112
+ else
113
+ packed.concat([0xC0 | rem, byte])
114
+ end
115
+ end
116
+ return packed
117
+ end
118
+
119
+ # バイト列をRLEによって圧縮します.
120
+ #
121
+ # @param [Array<Integer>] bytes バイト列
122
+ # @return [Array<Integer>] 圧縮されたバイト列
123
+ def encode(bytes)
124
+ return [] if bytes.empty?
125
+ data = []
126
+ byte0 = bytes[0]
127
+ count = 1
128
+ (1...bytes.size).each do |i|
129
+ byte = bytes[i]
130
+ if byte0 == byte
131
+ count += 1
132
+ else
133
+ data.concat(self.pack(byte0, count))
134
+ byte0 = byte
135
+ count = 1
136
+ end
137
+ end
138
+ if count > 0
139
+ data.concat(self.pack(byte0, count))
140
+ end
141
+ return data
142
+ end
143
+
144
+ # バイト列をRLEとして復元します.
145
+ #
146
+ # @param [Array<Integer>] bytes バイト列データ
147
+ # @param [Integer] total 最大復元バイト長
148
+ # @return [Array] 復元されたバイト列データ (整数配列) と、入力バイト列のどこまでを解析したかのオフセット
149
+ # @example 例えばこんな感じになります (データは適当)
150
+ # bytes = [0, 0, 0, 0, 0, ...]
151
+ # data, offset = rle.decode(bytes, 5)
152
+ # p data #=> [0, 0, 0, 0, 0]
153
+ # p offset #=> 3
154
+ def decode(bytes, total)
155
+ data = []
156
+ offset = 0
157
+ length = bytes.length
158
+ count = 0
159
+
160
+ while offset < length && count < total
161
+ byte0 = bytes[offset]
162
+ offset += 1
163
+ if byte0 >= 0xC0 # RLEパケット
164
+ cnt = byte0 & 0x3F
165
+ byte1 = bytes[offset]
166
+ offset += 1
167
+ data.concat([byte1] * cnt)
168
+ count += cnt
169
+ else # 生データ
170
+ data.push(byte0); count += 1
171
+ end
172
+ end
173
+
174
+ return [data, offset]
175
+ end
176
+ end
177
+
178
+ # PCX画像入出力処理実装.
179
+ class PCXImageIO < Rixmap::ImageIO::BaseImageIO
180
+ # 指定データがPCXとして読み込めるデータかどうかを調べます.
181
+ #
182
+ # @param [String] magic 画像の先頭データ
183
+ # @return [Boolean] PCXとして読み込める場合はtrue
184
+ def self.readable?(magic)
185
+ return magic[0].ord == FILE_SIGNATURE && magic[1].ord == FORMAT_VERSION_30
186
+ end
187
+
188
+ # 指定画像がPCXとして書き込めるかどうかを判定します.
189
+ #
190
+ # 実は透明度付のPCX画像はサポートしてなかったりします.
191
+ # 書き込み処理は実行できますが、その場合は透明度を無視して
192
+ # 書き込むようになってます.
193
+ #
194
+ # @param [Rixmap::Image] image 調べる画像
195
+ # @return [Boolean] PCXとして書き込める場合はtrue
196
+ def self.writable?(image)
197
+ if image.has_alpha?
198
+ return false
199
+ else
200
+ return true
201
+ end
202
+ end
203
+
204
+ # PCX入出力オブジェクトを初期化します.
205
+ #
206
+ # @param [Hash] options オプションパラメータ
207
+ # @option options [Boolean] :compression 圧縮するかどうか (Default: true)
208
+ def initialize(options={})
209
+ super(options)
210
+
211
+ @compression = true unless defined?(@compression)
212
+ end
213
+
214
+ # PCX形式として画像をエンコードします.
215
+ #
216
+ # @param [Rixmap::Image] image 対象画像
217
+ # @param [Hash] options オプションパラメータ
218
+ # @option options [Boolean] :compression 圧縮するかどうか (Defalt: true)
219
+ # @return [String] 画像データバイト列
220
+ def encode(image, options={})
221
+ # 圧縮フラグを取得
222
+ compression = if options[:compression].nil?
223
+ @compression
224
+ else
225
+ options[:compression]
226
+ end
227
+
228
+ # ヘッダを構築 (共通部分)
229
+ header = PCXFileHeader.new
230
+ header.left = 0
231
+ header.top = 0
232
+ header.right = image.width - 1
233
+ header.bottom = image.height - 1
234
+ header.encoding = if compression
235
+ ENCODINGTYPE_RLE
236
+ else
237
+ ENCODINGTYPE_NONE
238
+ end
239
+ header.line_width = image.width
240
+ header.bit_width = 8
241
+
242
+ # ピクセルデータ置場
243
+ pixels = Array.new
244
+
245
+ # エンコーダ
246
+ rle = RLE.new
247
+
248
+ # 各形式での処理
249
+ if image.indexed? # インデックスカラー形式
250
+ header.colortype = COLORTYPE_NORMAL
251
+ header.bit_planes = 1
252
+
253
+ # ピクセルの結合
254
+ if compression
255
+ # RLE
256
+ image.each_line do |line|
257
+ pixels.concat(rle.encode(line.palette))
258
+ end
259
+ else
260
+ # 無圧縮ピクセルデータ
261
+ image.each_line do |line|
262
+ pixels.concat(line.palette)
263
+ end
264
+ end
265
+ elsif image.grayscale? # グレースケール形式
266
+ header.colortype = COLORTYPE_GRAYSCALE
267
+
268
+ # 透明度は無視
269
+ header.bit_planes = 1
270
+
271
+ # ピクセルの結合
272
+ if compression
273
+ # RLE
274
+ image.each_line do |line|
275
+ pixels.concat(rle.encode(line.luminance))
276
+ end
277
+ else
278
+ # 無圧縮
279
+ image.each_line do |line|
280
+ pixels.concat(line.luminance)
281
+ end
282
+ end
283
+ elsif image.rgb? # RGBカラー形式
284
+ header.colortype = COLORTYPE_NORMAL
285
+
286
+ # 透明度は無視
287
+ header.bit_planes = 3
288
+
289
+ # ピクセルの結合
290
+ if compression
291
+ # RLE
292
+ image.each_line do |line|
293
+ pixels.concat(rle.encode(line.red))
294
+ pixels.concat(rle.encode(line.green))
295
+ pixels.concat(rle.encode(line.blue))
296
+ end
297
+ else
298
+ # 無圧縮
299
+ image.each_line do |line|
300
+ pixels.concat(line.red)
301
+ pixels.concat(line.green)
302
+ pixels.concat(line.blue)
303
+ end
304
+ end
305
+ end
306
+
307
+ # 結合
308
+ data = header.pack() + pixels.pack('C*')
309
+ if image.indexed? && !image.palette.nil?
310
+ data.concat("\x0C")
311
+ data.concat(image.palette.to_s('RGB'))
312
+ end
313
+
314
+ return data
315
+ end
316
+
317
+ # バイト列をPCXデータとして復元します.
318
+ #
319
+ # @param [String] data バイト列
320
+ # @param [Hash] options オプションパラメータ (現在未実装)
321
+ # @return [Rixmap::Image] 復元された画像
322
+ def decode(data, options={})
323
+ header_data = data.byteslice(0, 128)
324
+ image_data = data.byteslice(128, (data.bytesize - 128)).unpack('C*')
325
+
326
+ # ヘッダを再構築
327
+ header = PCXFileHeader.new
328
+ header.unpack(header_data)
329
+
330
+ # ヘッダを確認
331
+ unless header.magic == FILE_SIGNATURE
332
+ raise ArgumentError.new("Input data is not PCX image data")
333
+ end
334
+ unless header.version == FORMAT_VERSION_30
335
+ raise NotImplementedError.new("Currently supported only PCX Version 3.0, yet")
336
+ end
337
+ unless header.encoding == ENCODINGTYPE_NONE || header.encoding == ENCODINGTYPE_RLE
338
+ raise ArgumentError.new("Unknown encoding-type: #{header.encoding}")
339
+ end
340
+ unless header.colortype == COLORTYPE_NORMAL || header.colortype == COLORTYPE_GRAYSCALE
341
+ raise ArgumentError.new("Unknown color-type: #{header.colortype}")
342
+ end
343
+ unless header.bit_width == 8
344
+ raise NotImplementedError.new("Currently supported only 8-bit image")
345
+ end
346
+ if header.bit_planes < 1
347
+ raise ArgumentError.new("Input image has no pixel data")
348
+ end
349
+
350
+ # 画像を復元
351
+ image = nil
352
+ imwidth = header.right - header.left + 1
353
+ imheight = header.bottom - header.top + 1
354
+ total_bytes = header.line_width * header.bit_planes * imheight
355
+
356
+ # ピクセルデータを復元
357
+ pixels = nil
358
+ pixel_end_offset = nil
359
+ if header.encoding == ENCODINGTYPE_RLE
360
+ rle = RLE.new
361
+ pixels, pixel_end_offset = rle.decode(image_data, total_bytes)
362
+ else
363
+ pixels = image_data.slice(0, total_bytes)
364
+ pixel_end_offset = total_bytes
365
+ end
366
+
367
+ # まずはカラータイプで分岐
368
+ if header.colortype == COLORTYPE_GRAYSCALE # グレースケール
369
+ image = Rixmap::Image.new(Rixmap::GRAYSCALE, imwidth, imheight)
370
+ if header.bit_planes > 1 # 輝度値 + 謎プレーン
371
+ pixels.each_slice(header.line_width * header.bit_planes).each_with_index do |line, i|
372
+ break if i >= imheight
373
+ image[h].luminance = line.slice(0, imwidth)
374
+ end
375
+ else # 輝度値
376
+ pixels.each_slice(header.line_width).each_with_index do |line, i|
377
+ break if i >= imheight
378
+ image[i].luminance = line
379
+ end
380
+ end
381
+ else # カラー形式
382
+ if header.bit_planes > 3 # RGB + 謎プレーン
383
+ image = Rixmap::Image.new(Rixmap::RGB, imwidth, imheight)
384
+ roffset = 0
385
+ goffset = header.line_width
386
+ boffset = header.line_width * 2
387
+ pixels.each_slice(header.line_width * header.bit_planes).each_with_index do |line, i|
388
+ break if i >= imheight
389
+ image[i].red = line.slice(roffset, imwidth)
390
+ image[i].green = line.slice(goffset, imwidth)
391
+ image[i].blue = line.slice(boffset, imwidth)
392
+ end
393
+ elsif header.bit_planes == 3 # RGB
394
+ image = Rixmap::Image.new(Rixmap::RGB, imwidth, imheight)
395
+ roffset = 0
396
+ goffset = header.line_width
397
+ boffset = header.line_width * 2
398
+ pixels.each_slice(header.line_width * 3).each_with_index do |line, i|
399
+ break if i >= imheight
400
+ image[i].red = line.slice(roffset, imwidth)
401
+ image[i].green = line.slice(goffset, imwidth)
402
+ image[i].blue = line.slice(boffset, imwidth)
403
+ end
404
+ elsif header.bit_planes == 1 # INDEXED
405
+ image = Rixmap::Image.new(Rixmap::INDEXED, imwidth, imheight)
406
+ pixels.each_slice(header.line_width).each_with_index do |line, i|
407
+ break if i >= image.height
408
+ image[i].palette = line
409
+ end
410
+
411
+ if pixel_end_offset < image_data.length && image_data[pixel_end_offset] == PALETTE_SEPARATOR && !image.palette.nil?
412
+ # パレットある
413
+ palette_data = image_data.slice(pixel_end_offset + 1, (image_data.length - (pixel_end_offset + 1)))
414
+ palette = image.palette
415
+ palette_data.each_slice(3).each_with_index do |color, i|
416
+ break if i >= palette.size
417
+ palette[i] = color
418
+ end
419
+ end
420
+ else # !?
421
+ raise RuntimeError.new("Illegal bit-planes #{header.bit_planes}")
422
+ end
423
+ end
424
+
425
+ return image
426
+ end
427
+ end
428
+
429
+ Rixmap::ImageIO.register(:PCX, PCXImageIO, [".pcx"])
430
+ end
431
+
432
+ end
433
+ end
434
+
435
+
436
+ #==============================================================================#
437
+ # $Id: pcx.rb,v 57b1fb2cd6a6 2014/04/20 12:21:27 chikuchikugonzalez $
438
+ # vim: set sts=2 ts=2 sw=2 expandtab:
@@ -0,0 +1,239 @@
1
+ # -*- coding: utf-8 -*-
2
+
3
+ #
4
+ module Rixmap
5
+ module Format
6
+ module PNG
7
+
8
+ # PNGチャンクデータモジュール
9
+ module Chunk
10
+
11
+ # チャンクタイプ名と実装クラスの対応を登録します.
12
+ #
13
+ # @param [String] name チャンクタイプ名
14
+ # @param [Class] klass チャンク実装クラス
15
+ # @return [void]
16
+ def self.set(name, klass)
17
+ unless defined?(@chunks)
18
+ @chunks = Hash.new
19
+ end
20
+ @chunks[name] = klass
21
+ end
22
+
23
+ # チャンクタイプ名からその実装クラスを取得します.
24
+ #
25
+ # @param [String] name チャンクタイプ名
26
+ # @return [Class,nil] チャンク実装クラス. 未登録の場合はnil
27
+ def self.get(name)
28
+ unless defined?(@chunks)
29
+ @chunks = Hash.new
30
+ end
31
+ return @chunks[name]
32
+ end
33
+
34
+ # PNGチャンクベースクラス
35
+ class BaseChunk
36
+ # チャンクを初期化します.
37
+ #
38
+ # @param [String] type チャンクタイプ
39
+ # @param [String] data チャンクデータ
40
+ def initialize(type, data = nil)
41
+ @type = type
42
+ @data = nil
43
+
44
+ self.data = data unless data.nil?
45
+ end
46
+
47
+ # チャンクタイプを返します.
48
+ #
49
+ # @return [String] チャンクタイプ
50
+ def type()
51
+ return @type
52
+ end
53
+
54
+ # チャンクデータを返します.
55
+ #
56
+ # @return [String] チャンクデータ
57
+ def data()
58
+ self.pack()
59
+ return @data
60
+ end
61
+
62
+ # チャンクデータを更新します.
63
+ #
64
+ # @param [String] data チャンクデータ
65
+ def data=(data)
66
+ @data = data
67
+ self.unpack()
68
+ return nil
69
+ end
70
+
71
+ # 補助チャンクかどうかを返します.
72
+ #
73
+ # @return [Boolean] 補助チャンクならtrue
74
+ def optional?()
75
+ return (@type[0].ord & 0x20) != 0
76
+ end
77
+
78
+ # プライベートチャンクかどうかを返します.
79
+ #
80
+ # @return [Boolean] プライベートチャンクならtrue
81
+ def private?()
82
+ return (@type[1].ord & 0x20) != 0
83
+ end
84
+
85
+ # 標準チャンクかどうかを返します.
86
+ #
87
+ # @return [Boolean] 標準チャンクならtrue
88
+ def standard?()
89
+ return (@type[2].ord & 0x20) == 0
90
+ end
91
+
92
+ # コピー安全なチャンクかどうかを返します.
93
+ #
94
+ # @return [Boolean] コピー安全なチャンクならtrue
95
+ def copysafe?()
96
+ return (@type[3].ord & 0x20) != 0
97
+ end
98
+
99
+ # データ取得前の、データ構築処理を行います.
100
+ #
101
+ # #data から呼び出されます
102
+ def pack()
103
+ end
104
+ protected :pack
105
+
106
+ # データ更新後の、内部プロパティ更新処理を行います.
107
+ def unpack()
108
+ end
109
+ protected :unpack
110
+
111
+ end
112
+
113
+ # IHDRイメージヘッダチャンク.
114
+ class IHDRChunk < BaseChunk
115
+ # IHDRチャンクタイプ
116
+ TYPE = "IHDR"
117
+
118
+ # pack用IHDRデータレイアウト
119
+ LAYOUT = "N2C5"
120
+
121
+ attr_accessor :width
122
+ attr_accessor :height
123
+ attr_accessor :depth
124
+ attr_accessor :colortype
125
+ attr_accessor :compress
126
+ attr_accessor :filter
127
+ attr_accessor :interlace
128
+
129
+ def initialize(width = 0, height = 0, depth = 0,
130
+ colortype = COLORTYPE_TRUECOLOR,
131
+ compress = COMPRESSION_DEFLATE,
132
+ filter = FILTER_ADAPTIVE,
133
+ interlace = INTERLACE_NONE)
134
+ @width = width
135
+ @height = height
136
+ @depth = depth
137
+ @colortype = colortype
138
+ @compress = compress
139
+ @filter = filter
140
+ @interlace = interlace
141
+
142
+ super(IHDRChunk::TYPE)
143
+ end
144
+
145
+ def pack()
146
+ @data = [@width, @height, @depth, @colortype, @compress, @filter, @interlace].pack(IHDRChunk::LAYOUT)
147
+ end
148
+ protected :pack
149
+
150
+ def unpack()
151
+ @width, @height, @depth, @colortype, @compress, @filter, @interlace = @data.unpack(IHDRChunk::LAYOUT)
152
+ end
153
+ protected :unpack
154
+
155
+ # チャンクリストへ登録
156
+ Chunk.set(self::TYPE, self)
157
+ end
158
+
159
+ # PLTパレットチャンク
160
+ class PLTEChunk < BaseChunk
161
+ # PLTEチャンクタイプ
162
+ TYPE = 'PLTE'
163
+
164
+ # pack用PLTEチャンクデータレイアウト
165
+ LAYOUT = 'C*'
166
+
167
+ def initialize()
168
+ @colors = []
169
+ super(PLTEChunk::TYPE)
170
+ end
171
+
172
+ # 指定番号のカラーデータ (3バイト) を取得します.
173
+ #
174
+ # データオフセットではなく、3バイトごとに区切られたカラーのオフセットであることに注意.
175
+ #
176
+ # @param [Integer] offset カラー番号
177
+ # @return [Array] RGBカラーデータ
178
+ def [](offset)
179
+ return @colors[offset * 3, 3]
180
+ end
181
+
182
+ # 指定番号のカラーデータを更新します.
183
+ #
184
+ # @param [Integer] offset カラー番号
185
+ # @param [Array] rgb カラーデータ
186
+ def []=(offset, rgb)
187
+ @colors[offset * 3, 3] = rgb
188
+ end
189
+
190
+ def pack()
191
+ @data = @colors.pack(PLTEChunk::LAYOUT)
192
+ end
193
+ protected :pack
194
+
195
+ def unpack()
196
+ @colors = @data.unpack(PLTEChunk::LAYOUT)
197
+ end
198
+ protected :unpack
199
+
200
+ # チャンクリストへ登録
201
+ Chunk.set(self::TYPE, self)
202
+ end
203
+
204
+ # IDATイメージデータチャンク
205
+ class IDATChunk < BaseChunk
206
+ # IDATチャンクタイプ
207
+ TYPE = 'IDAT'
208
+
209
+ def initialize()
210
+ super(IDATChunk::TYPE)
211
+ end
212
+
213
+ # チャンクリストへ登録
214
+ Chunk.set(self::TYPE, self)
215
+ end
216
+
217
+ # IENDイメージトレーラチャンク
218
+ class IENDChunk < BaseChunk
219
+ # IENDチャンクタイプ
220
+ TYPE = 'IEND'
221
+
222
+ def initialize()
223
+ super(IENDChunk::TYPE, '')
224
+ end
225
+
226
+ # チャンクリストへ登録
227
+ Chunk.set(self::TYPE, self)
228
+ end
229
+
230
+ end
231
+
232
+ end
233
+ end
234
+ end
235
+
236
+
237
+ #==============================================================================#
238
+ # $Id: chunk.rb,v 57b1fb2cd6a6 2014/04/20 12:21:27 chikuchikugonzalez $
239
+ # vim: set sts=2 ts=2 sw=2 expandtab: