Class: Cabriolet::Decompressors::Quantum

Inherits:
Base
  • Object
show all
Defined in:
lib/cabriolet/decompressors/quantum.rb

Overview

Quantum handles Quantum-compressed data using arithmetic coding Based on libmspack qtmd.c implementation

The Quantum method was created by David Stafford, adapted by Microsoft Corporation.

Defined Under Namespace

Classes: MSBBitstream, Model, ModelSymbol

Constant Summary collapse

FRAME_SIZE =

Frame size (32KB per frame)

32_768
MAX_MATCH =

Match constants

1028
POSITION_BASE =

Position slot tables (same as in qtmd.c)

[
  0, 1, 2, 3, 4, 6, 8, 12, 16, 24, 32, 48, 64, 96, 128, 192, 256, 384,
  512, 768, 1024, 1536, 2048, 3072, 4096, 6144, 8192, 12_288, 16_384,
  24_576, 32_768, 49_152, 65_536, 98_304, 131_072, 196_608, 262_144,
  393_216, 524_288, 786_432, 1_048_576, 1_572_864
].freeze
EXTRA_BITS =
[
  0, 0, 0, 0, 1, 1, 2, 2, 3, 3, 4, 4, 5, 5, 6, 6, 7, 7, 8, 8,
  9, 9, 10, 10, 11, 11, 12, 12, 13, 13, 14, 14, 15, 15, 16, 16,
  17, 17, 18, 18, 19, 19
].freeze
LENGTH_BASE =
[
  0, 1, 2, 3, 4, 5, 6, 8, 10, 12, 14, 18, 22, 26,
  30, 38, 46, 54, 62, 78, 94, 110, 126, 158, 190, 222, 254
].freeze
LENGTH_EXTRA =
[
  0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 2, 2, 2, 2,
  3, 3, 3, 3, 4, 4, 4, 4, 5, 5, 5, 5, 0
].freeze

Instance Attribute Summary collapse

Attributes inherited from Base

#buffer_size, #input, #io_system, #output

Instance Method Summary collapse

Methods inherited from Base

#free

Constructor Details

#initialize(io_system, input, output, buffer_size, window_bits: 10) ⇒ Quantum

Initialize Quantum decompressor

Parameters:



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
# File 'lib/cabriolet/decompressors/quantum.rb', line 71

def initialize(io_system, input, output, buffer_size, window_bits: 10)
  super(io_system, input, output, buffer_size)

  # Validate window_bits
  unless (10..21).cover?(window_bits)
    raise ArgumentError,
          "Quantum window_bits must be 10-21, got #{window_bits}"
  end

  @window_bits = window_bits
  @window_size = 1 << window_bits

  # Initialize window
  @window = "\0" * @window_size
  @window_posn = 0
  @frame_todo = FRAME_SIZE

  # Arithmetic coding state
  @h = 0xFFFF
  @l = 0
  @c = 0
  @header_read = false

  # Initialize bitstream for MSB-first reading
  @bitstream = MSBBitstream.new(io_system, input, buffer_size)

  # Initialize models
  initialize_models
end

Instance Attribute Details

#window_bitsObject (readonly)

Returns the value of attribute window_bits.



41
42
43
# File 'lib/cabriolet/decompressors/quantum.rb', line 41

def window_bits
  @window_bits
end

#window_sizeObject (readonly)

Returns the value of attribute window_size.



41
42
43
# File 'lib/cabriolet/decompressors/quantum.rb', line 41

def window_size
  @window_size
end

Instance Method Details

#decompress(bytes) ⇒ Integer

Decompress Quantum data

Parameters:

  • bytes (Integer)

    Number of bytes to decompress

Returns:

  • (Integer)

    Number of bytes decompressed



105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
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
# File 'lib/cabriolet/decompressors/quantum.rb', line 105

def decompress(bytes)
  return 0 if bytes <= 0

  output_data = String.new(capacity: bytes)
  bytes_todo = bytes

  while bytes_todo.positive?
    # Read header if needed (initializes C register)
    read_frame_header unless @header_read

    # Calculate how much to decode this iteration
    frame_end = @window_posn + [bytes_todo, @frame_todo,
                                @window_size - @window_posn].min

    # Decode symbols
    while @window_posn < frame_end
      selector = decode_symbol(@model7)

      if selector < 4
        # Literal byte from one of 4 models
        model = case selector
                when 0 then @model0
                when 1 then @model1
                when 2 then @model2
                else @model3
                end

        sym = decode_symbol(model)
        @window.setbyte(@window_posn, sym)
        @window_posn += 1
        @frame_todo -= 1
      else
        # Match
        match_offset, match_length = decode_match(selector)

        # Validate match doesn't exceed frame or window
        if @window_posn + match_length > @window_size
          raise DecompressionError,
                "Match exceeds window boundary"
        end

        @frame_todo -= match_length

        # Copy match
        copy_match(match_offset, match_length)
      end
    end

    # Extract decoded bytes for output
    output_amount = [@window_posn, bytes_todo].min
    output_data << @window[0, output_amount]
    bytes_todo -= output_amount

    # Handle frame completion
    if @frame_todo.zero?
      # Re-align to byte boundary
      @bitstream.byte_align

      # Skip trailer bytes until 0xFF
      loop do
        byte = @bitstream.read_bits(8)
        break if byte == 0xFF
      end

      @header_read = false
      @frame_todo = FRAME_SIZE
    end

    # Handle window wrap
    @window_posn = 0 if @window_posn == @window_size
  end

  # Write output
  io_system.write(output, output_data)
  bytes
end