Class: Pura::Bmp::Decoder

Inherits:
Object
  • Object
show all
Defined in:
lib/pura/bmp/decoder.rb

Constant Summary collapse

BI_RGB =

Compression types

0
BI_RLE8 =
1
BI_RLE4 =
2
BI_BITFIELDS =
3

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(data) ⇒ Decoder

Returns a new instance of Decoder.



23
24
25
26
# File 'lib/pura/bmp/decoder.rb', line 23

def initialize(data)
  @data = data
  @pos = 0
end

Class Method Details

.decode(input) ⇒ Object



14
15
16
17
18
19
20
21
# File 'lib/pura/bmp/decoder.rb', line 14

def self.decode(input)
  data = if input.is_a?(String) && !input.include?("\x00") && input.bytesize < 4096 && File.exist?(input)
           File.binread(input)
         else
           input.b
         end
  new(data).decode
end

Instance Method Details

#decodeObject

Raises:



28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
# File 'lib/pura/bmp/decoder.rb', line 28

def decode
  # File header (14 bytes)
  magic = read_bytes(2)
  raise DecodeError, "Not a BMP file (missing BM signature)" unless magic == "BM"

  _file_size = read_uint32_le
  _reserved1 = read_uint16_le
  _reserved2 = read_uint16_le
  pixel_offset = read_uint32_le

  # Info header
  header_size = read_uint32_le
  raise DecodeError, "Unsupported BMP header size: #{header_size}" unless header_size >= 40

  width = read_int32_le
  height = read_int32_le
  _planes = read_uint16_le
  bit_depth = read_uint16_le
  compression = read_uint32_le
  _image_size = read_uint32_le
  _x_ppm = read_int32_le
  _y_ppm = read_int32_le
  colors_used = read_uint32_le
  _colors_important = read_uint32_le

  # Handle negative height (top-down storage)
  top_down = height.negative?
  height = height.abs

  raise DecodeError, "Invalid dimensions: #{width}x#{height}" if width <= 0 || height <= 0

  # Read additional header bytes if header is larger than 40
  extra_header = header_size - 40
  bitfield_masks = nil

  if compression == BI_BITFIELDS && extra_header >= 12
    r_mask = read_uint32_le
    g_mask = read_uint32_le
    b_mask = read_uint32_le
    extra_header -= 12
    bitfield_masks = { r: r_mask, g: g_mask, b: b_mask }
  end

  skip_bytes(extra_header) if extra_header.positive?

  # Read color palette for indexed images
  palette = nil
  if bit_depth <= 8
    num_colors = colors_used.positive? ? colors_used : (1 << bit_depth)
    palette = read_palette(num_colors)
  end

  # Seek to pixel data
  @pos = pixel_offset

  # Decode pixel data
  pixels = case bit_depth
           when 24
             decode_24bit(width, height, top_down)
           when 32
             decode_32bit(width, height, top_down, bitfield_masks)
           when 8
             if compression == BI_RLE8
               decode_rle8(width, height, top_down, palette)
             else
               decode_8bit(width, height, top_down, palette)
             end
           when 4
             decode_4bit(width, height, top_down, palette)
           when 1
             decode_1bit(width, height, top_down, palette)
           else
             raise DecodeError, "Unsupported bit depth: #{bit_depth}"
           end

  Image.new(width, height, pixels)
end