rixmap 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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: