Class: Rixmap::Format::BMP::BMPImageIO

Inherits:
ImageIO::BaseImageIO show all
Defined in:
lib/rixmap/format/bmp.rb

Overview

BMP入出力処理実装.

TODO V4対応 TODO V5対応 TODO OS/2対応

Constant Summary collapse

FILEHEADER_TEMPLATE =

ファイルヘッダテンプレート.

"a2LLL"
INFOHEADER_TEMPLATE =

情報ヘッダテンプレート (v3用)

"LllSSLLllLL"
FILEHEADER_SIZE =

ファイルヘッダサイズ

14
INFOHEADER_SIZE =

情報ヘッダサイズ (v3用)

40

Class Method Summary collapse

Instance Method Summary collapse

Methods inherited from ImageIO::BaseImageIO

#initialize, #open, #read, #readable?, #save, #writable?, #write

Constructor Details

This class inherits a constructor from Rixmap::ImageIO::BaseImageIO

Class Method Details

.readable?(magic) ⇒ Boolean

画像データの先頭から読み込める画像データかどうかを調べます.

Returns:

  • (Boolean)

    BMPとして読み込めるならtrue



108
109
110
# File 'lib/rixmap/format/bmp.rb', line 108

def self.readable?(magic)
  return magic[0..1] == 'BM'
end

.writable?(image) ⇒ Boolean

BMP形式で書き込める画像かどうかを返します.

全種類書き込めるけど、グレースケール形式はインデックス形式に変換されます.

Returns:

  • (Boolean)

    常にtrue



117
118
119
120
121
122
123
# File 'lib/rixmap/format/bmp.rb', line 117

def self.writable?(image)
  if image.kind_of?(Rixmap::Image)
    return true
  else
    return false
  end
end

Instance Method Details

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

バイト列をBMPとして復元します.

Parameters:

  • data (String)

    BMP形式画像のバイト列データ

  • options (Hash) (defaults to: {})

    オプションパラメータ

Returns:



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
308
309
310
311
312
313
314
315
316
317
# File 'lib/rixmap/format/bmp.rb', line 232

def decode(data, options={})
  unless data[0..1] == 'BM'
    raise ArgumentError.new("InvalidSignature detected. Input is not BMP Image Data.")
  end

  # ヘッダ構造体
  bf = BMPFileHeader.new
  bi = BMPInfoHeader.new

  # ファイルヘッダの復元
  bfbytes = data.byteslice(0...FILEHEADER_SIZE)
  bf.type, bf.size, bf.reserved, bf.offset = bfbytes.unpack(FILEHEADER_TEMPLATE)

  # 情報ヘッダの復元
  bi.size = data.byteslice(FILEHEADER_SIZE, 4).unpack('L')[0]
  if bi.size < INFOHEADER_SIZE
    raise NotImplementedError.new("Unsupported Info-Header Size: #{bi.size}")
  end
  bitemplate = INFOHEADER_TEMPLATE.clone
  if bi.size > 40
    bitemplate.concat("x#{INFOHEADER_SIZE - bi.size}")
  end
  bibytes = data.byteslice(FILEHEADER_SIZE, bi.size)
  _, bi.width, bi.height, bi.planes, bi.bit_count, bi.compression, bi.image_size, bi.ppm_x, bi.ppm_y, bi.used_color_count, bi.important_color_count = bibytes.unpack(bitemplate)

  # 画像を復元
  image = nil
  pixoffset = if bf.offset > 0
                bf.offset
              else
                FILEHEADER_SIZE + bi.size + bi.used_color_count * 4
              end
  pixbytes = data.byteslice(pixoffset..-1)
  bytes_per_line = ((bi.width * bi.bit_count + 31) / 32) * 4

  case bi.bit_count
  when 8
    image = Rixmap::Image.new(Rixmap::INDEXED, bi.width, bi.height)

    # パレットを復元
    ncolors = bi.used_color_count
    if ncolors <= 0
      ncolors = 2 ** bi.bit_count
    end
    palbytes = data.byteslice((FILEHEADER_SIZE + bi.size), (ncolors * 4))
    palbytes.unpack('C*').each_slice(4).each_with_index do |c, i|
      break if i >= image.palette.size
      image.palette[i] = [c[2], c[1], c[0], c[3]]
    end

    # ピクセルを復元
    bi.height.times do |h|
      line_no   = (bi.height - 1) - h
      line_data = pixbytes.byteslice((h * bytes_per_line)...((h + 1) * bytes_per_line))
      image[line_no].palette = line_data
    end
  when 24
    image = Rixmap::Image.new(Rixmap::RGB, bi.width, bi.height)

    # ピクセルを復元
    bi.height.times do |h|
      line_no   = (bi.height - 1) - h
      line_data = pixbytes.byteslice((h * bytes_per_line)...((h + 1) * bytes_per_line))
      line_data.unpack('C*').each_slice(3).each_with_index do |c, i|
        break if i >= bi.width
        image[i, line_no] = [c[2], c[1], c[0]]
      end
    end
  when 32
    image = Rixmap::Image.new(Rixmap::RGBA, bi.width, bi.height)

    # ピクセルを復元
    bi.height.times do |h|
      line_no   = (bi.height - 1) - h
      line_data = pixbytes.byteslice((h * bytes_per_line)...((h + 1) * bytes_per_line))
      line_data.unpack('C*').each_slice(4).each_with_index do |c, i|
        break if i >= bi.width
        image[i, line_no] = [c[2], c[1], c[0], c[3]]
      end
    end
  else
    raise NotImplementedError.new("Unsupported Bit-Count: #{bi.bit_count}")
  end

  return image
end

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

BMP形式として画像をエンコードします.

Parameters:

  • image (Rixmap::Image)

    対象画像

  • options (Hash) (defaults to: {})

    オプションパラメータ

Returns:

  • (String)

    エンコードされた画像のバイト列



130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
# File 'lib/rixmap/format/bmp.rb', line 130

def encode(image, options={})
  # ヘッダ
  bf = BMPFileHeader.new('BM', 0, 0, 0)
  bi = BMPInfoHeader.new(INFOHEADER_SIZE, 0, 0, 1, 0, 0, 0, 3780, 3780, 0, 0)

  # 基本情報
  bi.width       = image.width
  bi.height      = image.height
  bi.bit_count   = image.mode.depth
  bi.compression = CompressionType::RGB

  # データ部品
  bytes_per_line = ((image.width * image.mode.depth + 31) / 32) * 4
  palbuf = ''
  palbuf.force_encoding(Encoding::ASCII_8BIT)
  pixbuf = ''
  pixbuf.force_encoding(Encoding::ASCII_8BIT)

  # 画像形式で分岐
  case image.mode
  when Rixmap::INDEXED
    bi.used_color_count      = image.palette.size
    bi.important_color_count = image.palette.size
    palbuf = image.palette.to_s('BGRA')
    padding_size = bytes_per_line - image.width
    padding_bytes = "\x00" * padding_size
    (image.height - 1).downto(0) do |h|
      line = image[h].to_s('P')
      pixbuf.concat(line)
      pixbuf.concat(padding_bytes)
    end

  when Rixmap::GRAYSCALE
    # 疑似パレットデータ
    graypal = Rixmap::Palette.new(256)
    256.times do |i|
      graypal[i] = [i, i, i, 255]
    end
    palbuf = graypal.to_s('BGRA')

    # ピクセルデータ
    padding_size = bytes_per_line - image.width
    padding_bytes = "\x00" * padding_size
    (image.height - 1).downto(0) do |h|
      line = image[h].to_s('L')
      pixbuf.concat(line)
      pixbuf.concat(padding_bytes)
    end
  when Rixmap::GRAYALPHA
    bytes_per_line = ((image.width * 32 + 31) / 32) * 4
    bi.bit_count = 32

    # RGBA扱いにする
    padding_size = bytes_per_line - (image.width * 4)
    padding_bytes = "\x00" * padding_size
    (image.height - 1).downto(0) do |h|
      line = image[h].to_s('LLLA')
      pixbuf.concat(line)
      pixbuf.concat(padding_bytes)
    end
  when Rixmap::RGB
    padding_size = bytes_per_line - (image.width * 3)
    padding_bytes = "\x00" * padding_size
    (image.height - 1).downto(0) do |h|
      line = image[h].to_s('BGR')
      pixbuf.concat(line)
      pixbuf.concat(padding_bytes)
    end
  when Rixmap::RGBA
    padding_size = bytes_per_line - (image.width * 4)
    padding_bytes = "\x00" * padding_size
    (image.height - 1).downto(0) do |h|
      line = image[h].to_s('BGRA')
      pixbuf.concat(line)
      pixbuf.concat(padding_bytes)
    end
  else
    raise "Unsupported Image Mode: #{image.mode}"
  end

  # ヘッダを調整
  bf.offset     = FILEHEADER_SIZE + INFOHEADER_SIZE + palbuf.bytesize
  bi.image_size = pixbuf.bytesize
  bf.size       = bf.offset + bi.image_size

  # パック
  bfbytes = bf.to_a.pack(FILEHEADER_TEMPLATE)
  bibytes = bi.to_a.pack(INFOHEADER_TEMPLATE)

  # 結合
  imgbytes = bfbytes.clone
  imgbytes.concat(bibytes)
  imgbytes.concat(palbuf)
  imgbytes.concat(pixbuf)
  return imgbytes
end