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,288 @@
1
+ # -*- coding: utf-8 -*-
2
+ require 'zlib'
3
+ require_relative 'chunk'
4
+
5
+ #
6
+ module Rixmap
7
+ module Format
8
+ module PNG
9
+
10
+ # スキャンライン用フィルタ実装.
11
+ #
12
+ # @see http://www.milk-island.net/document/png/#9Filters
13
+ class AdaptiveFilter
14
+ # デフォルトフィルタ実装を初期化します.
15
+ #
16
+ # @param [Rixmap::Mode] mode 画像形式
17
+ def initialize(mode)
18
+ @prev = nil
19
+ @mode = mode
20
+ end
21
+
22
+ # フィルタ関数
23
+ #
24
+ # @param [Array] line 対象スキャンライン
25
+ # @param [Integer] method フィルタメソッド
26
+ # @return [Array] フィルタ処理後スキャンライン
27
+ def filt(line, method = ADAPTIVEFILTER_NONE)
28
+ base_line = Rixmap::Binary.new(line)
29
+ filt_line = Rixmap::Binary.new([method])
30
+ case method
31
+ when ADAPTIVEFILTER_NONE
32
+ filt_line += base_line
33
+ else
34
+ raise NotImplementedError.new("Unsupported Filter Method: #{method}")
35
+ end
36
+
37
+ @prev = base_line
38
+ return filt_line
39
+ end
40
+
41
+ # 復元関数
42
+ #
43
+ # @param [Array] line 対象スキャンライン
44
+ def recon(line)
45
+ base_line = Rixmap::Binary.new(line)
46
+ recon_line = Rixmap::Binary.new
47
+ method = base_line.shift
48
+
49
+ case method
50
+ when ADAPTIVEFILTER_NONE
51
+ recon_line += base_line
52
+ else
53
+ raise NotImplementedError.new("Unsupported Filter Method: #{method}")
54
+ end
55
+
56
+ @prev = base_line
57
+ return recon_line
58
+ end
59
+ end
60
+
61
+ # PNG入出力実装クラス.
62
+ #
63
+ # さすがにPNGフル実装はきついですな(´・ω・`)
64
+ class PNGImageIO < Rixmap::ImageIO::BaseImageIO
65
+ # 指定マジックデータがPNG画像のものかを判定します.
66
+ #
67
+ # @param [String] magic 画像先頭データ
68
+ # @return [Boolean] PNG形式のシグネチャならtrue
69
+ def self.readable?(magic)
70
+ return (magic[0, FILE_SIGNATURE_SIZE] == FILE_SIGNATURE)
71
+ end
72
+
73
+ # 指定画像をPNGとしてエンコードできるかを返します.
74
+ #
75
+ # @param [Rixmap::Image] image 対象画像
76
+ # @return [Boolean] PNGとしてエンコードできる場合はtrue
77
+ def self.writable?(image)
78
+ return true
79
+ end
80
+
81
+ # 画像をPNG形式でエンコードします.
82
+ #
83
+ # @param [Rixmap::Image] image 対象画像
84
+ # @param [Hash] options オプションパラメータ
85
+ # @return [String] エンコードされた画像データ
86
+ def encode(image, options={})
87
+ # イメージヘッダ
88
+ ihdr = Chunk::IHDRChunk.new
89
+ ihdr.width = image.width
90
+ ihdr.height = image.height
91
+ ihdr.depth = 8 #image.mode.depth
92
+
93
+ # 必要チャンク
94
+ plte = nil
95
+
96
+ # ピクセルデータ
97
+ pixels = ''.force_encoding(Encoding::BINARY)
98
+ filter = AdaptiveFilter.new(image.mode)
99
+
100
+ # 画像種別による処理
101
+ case image.mode
102
+ when Rixmap::INDEXED
103
+ ihdr.colortype = COLORTYPE_INDEXED
104
+
105
+ # パレットを設定
106
+ plte = Chunk::PLTEChunk.new
107
+ image.palette.each_with_index do |color, i|
108
+ plte[i] = [color.red, color.green, color.blue]
109
+ end
110
+
111
+ # ピクセルを作成
112
+ image.each_line do |line|
113
+ # ByteArrayはto_bytesできる
114
+ pixels.concat(filter.filt(line).to_str)
115
+ end
116
+ when Rixmap::GRAYSCALE
117
+ ihdr.colortype = COLORTYPE_GRAYSCALE
118
+ image.each_line do |line|
119
+ pixels.concat(filter.filt(line).to_str)
120
+ end
121
+ when Rixmap::GRAYALPHA
122
+ ihdr.colortype = COLORTYPE_GRAYSCALE_WITH_ALPHA
123
+ image.each_line do |line|
124
+ pixels.concat(filter.filt(line.to_s('LA')).to_str)
125
+ end
126
+ when Rixmap::RGB
127
+ ihdr.colortype = COLORTYPE_TRUECOLOR
128
+ image.each_line do |line|
129
+ pixels.concat(filter.filt(line.to_s('RGB')).to_str)
130
+ end
131
+ when Rixmap::RGBA
132
+ ihdr.colortype = COLORTYPE_TRUECOLOR_WITH_ALPHA
133
+ image.each_line do |line|
134
+ pixels.concat(filter.filt(line.to_s('RGBA')).to_str)
135
+ end
136
+ else
137
+ raise ArgumentError.new("Unknown image mode: #{image.mode}")
138
+ end
139
+
140
+ # ピクセルを圧縮
141
+ deflated_pixels = Zlib.deflate(pixels, Zlib::BEST_COMPRESSION)
142
+
143
+ # IDATチャンクを生成
144
+ idat = Chunk::IDATChunk.new
145
+ idat.data = deflated_pixels
146
+
147
+ # チャンクリスト
148
+ chunks = []
149
+ chunks << ihdr
150
+ chunks << plte unless plte.nil?
151
+ chunks << idat
152
+ chunks << Chunk::IENDChunk.new
153
+
154
+ # PNGストリームを構築
155
+ png_data = ''
156
+ png_data.concat(FILE_SIGNATURE)
157
+ png_data.force_encoding(Encoding::BINARY)
158
+ chunks.each do |chunk|
159
+ chunk_data = chunk.data
160
+ chunk_size = chunk_data.bytesize
161
+ chunk_fulldata = chunk.type + chunk_data
162
+ chunk_crc = Zlib.crc32(chunk_fulldata)
163
+ #chunk_crc = CRC.crc(chunk_fulldata) # 自前実装
164
+ png_data.concat([chunk_size].pack('N'))
165
+ png_data.concat(chunk_fulldata)
166
+ png_data.concat([chunk_crc].pack('N'))
167
+ end
168
+
169
+ return png_data
170
+ end
171
+
172
+ # バイトデータをPNG画像として復元します.
173
+ #
174
+ # @param [String] data 入力データ
175
+ # @param [Hash] options オプションパラメータ
176
+ # @return [Rixmap::Image] 復元された画像
177
+ def decode(data, options={})
178
+ if data.byteslice(0, FILE_SIGNATURE_SIZE) != FILE_SIGNATURE
179
+ raise ArgumentError.new("InvalidSignature detected. Input is not PNG Image Data.")
180
+ end
181
+
182
+ # チャンクを分割
183
+ offset = FILE_SIGNATURE_SIZE
184
+ length = data.bytesize
185
+ chunks = []
186
+ while offset < length
187
+ chunk_size = data.byteslice(offset, offset + 4).unpack('N')[0]; offset += 4
188
+ chunk_data = nil
189
+ chunk_type = nil
190
+ chunk_crc = nil
191
+ if chunk_size > 0
192
+ chunk_type, chunk_data, chunk_crc = data.byteslice(offset, offset + (chunk_size + 8)).unpack("a4a#{chunk_size}N")
193
+ elsif chunk_size == 0
194
+ chunk_type, chunk_crc = data.byteslice(offset, offset + 8).unpack("a4N")
195
+ else
196
+ raise RuntimeError.new("Invalid Chunk Size: #{chunk_size}")
197
+ end
198
+ offset += (chunk_size + 8)
199
+
200
+ chunk = Chunk.get(chunk_type).new
201
+ chunk.data = chunk_data
202
+
203
+ chunks << chunk
204
+ break if chunk.type == Chunk::IENDChunk::TYPE
205
+ end
206
+
207
+ # IHDRチャンクを処理
208
+ ihdr = chunks.shift
209
+ unless ihdr.type == Chunk::IHDRChunk::TYPE
210
+ raise ArgumentError.new("Illegal Chunk Order. First chunk must to be IHDR chunk.")
211
+ end
212
+
213
+ image_width = ihdr.width
214
+ image_height = ihdr.height
215
+ image_depth = ihdr.depth
216
+ colortype = ihdr.colortype
217
+
218
+ # 残りチャンクを処理
219
+ image_data = ''
220
+ palette = nil
221
+ chunks.each do |chunk|
222
+ case chunk
223
+ when Chunk::IDATChunk
224
+ image_data.concat(chunk.data)
225
+ when Chunk::PLTEChunk
226
+ palette = Rixmap::Palette.new(2 ** image_depth)
227
+ palette.size.times do |i|
228
+ palette[i] = chunk[i]
229
+ end
230
+ when Chunk::IENDChunk
231
+ break
232
+ end
233
+ end
234
+
235
+ # ピクセルを復元
236
+ pixels = Zlib.inflate(image_data).each_byte
237
+
238
+ # ベース画像を作成
239
+ image = nil
240
+ case colortype
241
+ when COLORTYPE_INDEXED
242
+ image = Rixmap::Image.new(Rixmap::INDEXED, image_width, image_height, :palette => palette)
243
+ filter = AdaptiveFilter.new(image.mode)
244
+ pixels.each_slice(image_width + 1).each_with_index do |bytes, i|
245
+ image[i] = filter.recon(bytes)
246
+ end
247
+ when COLORTYPE_GRAYSCALE
248
+ image = Rixmap::Image.new(Rixmap::GRAYSCALE, image_width, image_height)
249
+ filter = AdaptiveFilter.new(image.mode)
250
+ pixels.each_slice(image_width + 1).each_with_index do |bytes, i|
251
+ image[i] = filter.recon(bytes)
252
+ end
253
+ when COLORTYPE_GRAYSCALE_WITH_ALPHA
254
+ image = Rixmap::Image.new(Rixmap::GRAYALPHA, image_width, image_height)
255
+ filter = AdaptiveFilter.new(image.mode)
256
+ pixels.each_slice((image_width * 2) + 1).each_with_index do |bytes, i|
257
+ image[i] = filter.recon(bytes)
258
+ end
259
+ when COLORTYPE_TRUECOLOR
260
+ image = Rixmap::Image.new(Rixmap::RGB, image_width, image_height)
261
+ filter = AdaptiveFilter.new(image.mode)
262
+ pixels.each_slice((image_width * 3) + 1).each_with_index do |bytes, i|
263
+ image[i] = filter.recon(bytes)
264
+ end
265
+ when COLORTYPE_TRUECOLOR_WITH_ALPHA
266
+ image = Rixmap::Image.new(Rixmap::RGBA, image_width, image_height)
267
+ filter = AdaptiveFilter.new(image.mode)
268
+ pixels.each_slice((image_width * 4) + 1).each_with_index do |bytes, i|
269
+ image[i] = filter.recon(bytes)
270
+ end
271
+ else
272
+ raise RuntimeError.new("Unsupported Colo-Type: #{colortype}")
273
+ end
274
+
275
+ return image
276
+ end
277
+ end
278
+
279
+ Rixmap::ImageIO.register(:PNG, PNGImageIO, [".png"])
280
+ end
281
+
282
+ end
283
+ end
284
+
285
+
286
+ #==============================================================================#
287
+ # $Id: imageio.rb,v 57b1fb2cd6a6 2014/04/20 12:21:27 chikuchikugonzalez $
288
+ # vim: set sts=2 ts=2 sw=2 expandtab:
@@ -0,0 +1,78 @@
1
+ # -*- coding: utf-8 -*-
2
+
3
+ #
4
+ module Rixmap
5
+ module Format
6
+
7
+ # PNGフォーマット対応実装モジュール.
8
+ module PNG
9
+
10
+ # PNGシグネチャ
11
+ FILE_SIGNATURE = "\x89PNG\r\n\x1A\n".force_encoding(Encoding::BINARY)
12
+
13
+ # PNGシグネチャサイズ
14
+ FILE_SIGNATURE_SIZE = 8
15
+
16
+ # グレースケール形式
17
+ #
18
+ # ビット幅は 1, 2, 4, 8, 16
19
+ COLORTYPE_GRAYSCALE = 0x00
20
+
21
+ # RGBトゥルーカラー形式
22
+ #
23
+ # ビット幅は 8, 16
24
+ COLORTYPE_TRUECOLOR = 0x02
25
+
26
+ # インデックスカラー形式
27
+ #
28
+ # ビット幅は 1, 2, 4, 8
29
+ COLORTYPE_INDEXED = 0x03
30
+
31
+ # グレースケール形式 with 透明度
32
+ #
33
+ # ビット幅は 8, 16
34
+ COLORTYPE_GRAYSCALE_WITH_ALPHA = 0x04
35
+
36
+ # RGBトゥルーカラー形式 with 透明度
37
+ #
38
+ # ビット幅は 8, 16
39
+ COLORTYPE_TRUECOLOR_WITH_ALPHA = 0x06
40
+
41
+ # Deflate圧縮メソッド
42
+ COMPRESSION_DEFLATE = 0x00
43
+
44
+ # 基本のフィルタタイプ
45
+ FILTER_ADAPTIVE = 0x00
46
+
47
+ # インターレースなし
48
+ INTERLACE_NONE = 0x00
49
+
50
+ # Adam7インターレース
51
+ INTERLACE_ADAM7 = 0x01
52
+
53
+ # なにもしない基本フィルタ
54
+ ADAPTIVEFILTER_NONE = 0x00
55
+
56
+ # 左側差分基本フィルタ
57
+ ADAPTIVEFILTER_SUB = 0x01
58
+
59
+ # 上側差分基本フィルタ
60
+ ADAPTIVEFILTER_UP = 0x02
61
+
62
+ # 平均基本フィルタ
63
+ ADAPTIVEFILTER_AVERAGE = 0x03
64
+
65
+ # 周辺から近い色をとって、その差分をとるようなの
66
+ ADAPTIVEFILTER_PEATH = 0x04
67
+ end
68
+
69
+ end
70
+ end
71
+
72
+ require_relative 'png/chunk'
73
+ require_relative 'png/imageio'
74
+
75
+
76
+ #==============================================================================#
77
+ # $Id: png.rb,v 57b1fb2cd6a6 2014/04/20 12:21:27 chikuchikugonzalez $
78
+ # vim: set sts=2 ts=2 sw=2 expandtab: