Class: Rixmap::Format::PNG::PNGImageIO

Inherits:
ImageIO::BaseImageIO
  • Object
show all
Defined in:
lib/rixmap/format/png/imageio.rb

Overview

PNG入出力実装クラス.

さすがにPNGフル実装はきついですな(´・ω・‘)

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(options = {}) ⇒ PNGImageIO

PNG入出力オブジェクトを初期化します.

Options Hash (options):

  • :compression_level (Integer)

    圧縮レベル (0-9).

  • :filter_method (Integer)

    スキャンラインのフィルタリングメソッド



200
201
202
203
204
# File 'lib/rixmap/format/png/imageio.rb', line 200

def initialize(options={})
  @compression_level = Zlib::BEST_COMPRESSION
  @filter_method     = ADAPTIVEFILTER_UP
  super(options)
end

Instance Attribute Details

#compression_levelInteger

ピクセルデータ圧縮時の圧縮レベル.

値としては Zlib モジュールの定数か、0~9の数値を指定します.



180
181
182
# File 'lib/rixmap/format/png/imageio.rb', line 180

def compression_level
  @compression_level
end

#filter_methodInteger

スキャンライン毎のフィルタリングメソッド.

値は以下のいずれかです.



193
194
195
# File 'lib/rixmap/format/png/imageio.rb', line 193

def filter_method
  @filter_method
end

Class Method Details

.readable?(magic) ⇒ Boolean

指定マジックデータがPNG画像のものかを判定します.



163
164
165
# File 'lib/rixmap/format/png/imageio.rb', line 163

def self.readable?(magic)
  return (magic[0, FILE_SIGNATURE_SIZE] == FILE_SIGNATURE)
end

.writable?(image) ⇒ Boolean

指定画像をPNGとしてエンコードできるかを返します.



171
172
173
# File 'lib/rixmap/format/png/imageio.rb', line 171

def self.writable?(image)
  return true
end

Instance Method Details

#decode(data, options = {}) ⇒ Rixmap::Image

バイトデータをPNG画像として復元します.



314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
# File 'lib/rixmap/format/png/imageio.rb', line 314

def decode(data, options={})
  if data.byteslice(0, FILE_SIGNATURE_SIZE) != FILE_SIGNATURE
    raise ArgumentError.new("InvalidSignature detected. Input is not PNG Image Data.")
  end

  # チャンクを分割

  offset = FILE_SIGNATURE_SIZE
  length = data.bytesize
  chunks = []
  while offset < length
    chunk_size = data.byteslice(offset, offset + 4).unpack('N')[0]; offset += 4
    chunk_data = nil
    chunk_type = nil
    chunk_crc  = nil
    if chunk_size > 0
      chunk_type, chunk_data, chunk_crc = data.byteslice(offset, offset + (chunk_size + 8)).unpack("a4a#{chunk_size}N")
    elsif chunk_size == 0
      chunk_type, chunk_crc = data.byteslice(offset, offset + 8).unpack("a4N")
    else
      raise RuntimeError.new("Invalid Chunk Size: #{chunk_size}")
    end
    offset += (chunk_size + 8)

    # チャンクがあるかをチェック

    unless Chunk.has?(chunk_type)
      # ないので警告出して次へ

      warn("Unknown or Not-Implemented Chunk Type: #{chunk_type}")
      next
    end
    chunk = Chunk.get(chunk_type).new
    chunk.data = chunk_data

    chunks << chunk
    break if chunk.type == Chunk::IENDChunk::TYPE
  end

  # IHDRチャンクを処理

  ihdr = chunks.shift
  unless ihdr.type == Chunk::IHDRChunk::TYPE
    raise ArgumentError.new("Illegal Chunk Order. First chunk must to be IHDR chunk.")
  end

  image_width  = ihdr.width
  image_height = ihdr.height
  image_depth  = ihdr.depth
  colortype    = ihdr.colortype

  # 残りチャンクを処理

  image_data = ''
  palette    = nil
  chunks.each do |chunk|
    case chunk
    when Chunk::IDATChunk
      image_data.concat(chunk.data)
    when Chunk::PLTEChunk
      palette = Rixmap::Palette.new(2 ** image_depth)
      palette.size.times do |i|
        palette[i] = chunk[i]
      end
    when Chunk::IENDChunk
      break
    end
  end

  # ピクセルを復元

  pixels = Zlib.inflate(image_data).each_byte

  # ベース画像を作成

  image = nil
  case colortype
  when COLORTYPE_INDEXED
    image  = Rixmap::Image.new(Rixmap::INDEXED, image_width, image_height, :palette => palette)
    filter = AdaptiveFilter.new(image.mode)
    pixels.each_slice(image_width + 1).each_with_index do |bytes, i|
      image[i] = filter.recon(bytes)
    end
  when COLORTYPE_GRAYSCALE
    image  = Rixmap::Image.new(Rixmap::GRAYSCALE, image_width, image_height)
    filter = AdaptiveFilter.new(image.mode)
    pixels.each_slice(image_width + 1).each_with_index do |bytes, i|
      image[i] = filter.recon(bytes)
    end
  when COLORTYPE_GRAYSCALE_WITH_ALPHA
    image  = Rixmap::Image.new(Rixmap::GRAYALPHA, image_width, image_height)
    filter = AdaptiveFilter.new(image.mode)
    pixels.each_slice((image_width * 2) + 1).each_with_index do |bytes, i|
      image[i] = filter.recon(bytes)
    end
  when COLORTYPE_TRUECOLOR
    image  = Rixmap::Image.new(Rixmap::RGB, image_width, image_height)
    filter = AdaptiveFilter.new(image.mode)
    pixels.each_slice((image_width * 3) + 1).each_with_index do |bytes, i|
      image[i] = filter.recon(bytes)
    end
  when COLORTYPE_TRUECOLOR_WITH_ALPHA
    image  = Rixmap::Image.new(Rixmap::RGBA, image_width, image_height)
    filter = AdaptiveFilter.new(image.mode)
    pixels.each_slice((image_width * 4) + 1).each_with_index do |bytes, i|
      image[i] = filter.recon(bytes)
    end
  else
    raise RuntimeError.new("Unsupported Colo-Type: #{colortype}")
  end

  return image
end

#encode(image, options = {}) ⇒ String

画像をPNG形式でエンコードします.

Options Hash (options):

  • :filter_method (Integer)

    スキャンラインのフィルタリングメソッド



212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
# File 'lib/rixmap/format/png/imageio.rb', line 212

def encode(image, options={})
  # イメージヘッダ

  ihdr = Chunk::IHDRChunk.new
  ihdr.width  = image.width
  ihdr.height = image.height
  ihdr.depth  = 8 #image.mode.depth


  # 必要チャンク

  plte = nil

  # ピクセルデータ

  pixels = ''.force_encoding(Encoding::BINARY)
  filter = AdaptiveFilter.new(image.mode)
  filter_method = nil
  case options[:filter_method]
  when ADAPTIVEFILTER_NONE, ADAPTIVEFILTER_SUB, ADAPTIVEFILTER_UP, ADAPTIVEFILTER_AVERAGE, ADAPTIVEFILTER_PEATH
    filter_method = options[:filter_method].to_i
  else
    filter_method = @filter_method
  end
  compression_level = @compression_level
  unless options[:compression_level].nil?
    compression_level = options[:compression_level].to_i
  end

  # 画像種別による処理

  case image.mode
  when Rixmap::INDEXED
    ihdr.colortype = COLORTYPE_INDEXED

    # パレットを設定

    plte = Chunk::PLTEChunk.new
    image.palette.each_with_index do |color, i|
      plte[i] = [color.red, color.green, color.blue]
    end

    # ピクセルを作成

    image.each_line do |line|
      # ByteArrayはto_bytesできる

      pixels.concat(filter.filt(line, filter_method).to_str)
    end
  when Rixmap::GRAYSCALE
    ihdr.colortype = COLORTYPE_GRAYSCALE
    image.each_line do |line|
      pixels.concat(filter.filt(line, filter_method).to_str)
    end
  when Rixmap::GRAYALPHA
    ihdr.colortype = COLORTYPE_GRAYSCALE_WITH_ALPHA
    image.each_line do |line|
      pixels.concat(filter.filt(line.to_s('LA'), filter_method).to_str)
    end
  when Rixmap::RGB
    ihdr.colortype = COLORTYPE_TRUECOLOR
    image.each_line do |line|
      pixels.concat(filter.filt(line.to_s('RGB'), filter_method).to_str)
    end
  when Rixmap::RGBA
    ihdr.colortype = COLORTYPE_TRUECOLOR_WITH_ALPHA
    image.each_line do |line|
      pixels.concat(filter.filt(line.to_s('RGBA'), filter_method).to_str)
    end
  else
    raise ArgumentError.new("Unknown image mode: #{image.mode}")
  end

  # ピクセルを圧縮

  deflated_pixels = Zlib.deflate(pixels, compression_level)

  # IDATチャンクを生成

  idat = Chunk::IDATChunk.new
  idat.data = deflated_pixels

  # チャンクリスト

  chunks = []
  chunks << ihdr
  chunks << plte unless plte.nil?
  chunks << idat
  chunks << Chunk::IENDChunk.new

  # PNGストリームを構築

  png_data = ''
  png_data.concat(FILE_SIGNATURE)
  png_data.force_encoding(Encoding::BINARY)
  chunks.each do |chunk|
    chunk_data = chunk.data
    chunk_size = chunk_data.bytesize
    chunk_fulldata = chunk.type + chunk_data
    chunk_crc = Zlib.crc32(chunk_fulldata)
    #chunk_crc = CRC.crc(chunk_fulldata)   # 自前実装

    png_data.concat([chunk_size].pack('N'))
    png_data.concat(chunk_fulldata)
    png_data.concat([chunk_crc].pack('N'))
  end

  return png_data
end