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