Class: Origami::Filter::CCITTFax

Inherits:
Object
  • Object
show all
Includes:
Origami::Filter
Defined in:
lib/origami/filters/ccitt.rb,
lib/origami/filters/ccitt/tables.rb

Overview

Class representing a Filter used to encode and decode data with CCITT-facsimile compression algorithm.

Defined Under Namespace

Classes: DecodeParms

Constant Summary collapse

EOL =
codeword('000000000001')
RTC =
codeword('000000000001' * 6)
WHITE_TERMINAL_ENCODE_TABLE =
{
  0 => '00110101',
  1 => '000111',
  2 => '0111',
  3 => '1000',
  4 => '1011',
  5 => '1100',
  6 => '1110',
  7 => '1111',
  8 => '10011',
  9 => '10100',
  10 => '00111',
  11 => '01000',
  12 => '001000',
  13 => '000011',
  14 => '110100',
  15 => '110101',
  16 => '101010',
  17 => '101011',
  18 => '0100111',
  19 => '0001100',
  20 => '0001000',
  21 => '0010111',
  22 => '0000011',
  23 => '0000100',
  24 => '0101000',
  25 => '0101011',
  26 => '0010011',
  27 => '0100100',
  28 => '0011000',
  29 => '00000010',
  30 => '00000011',
  31 => '00011010',
  32 => '00011011',
  33 => '00010010',
  34 => '00010011',
  35 => '00010100',
  36 => '00010101',
  37 => '00010110',
  38 => '00010111',
  39 => '00101000',
  40 => '00101001',
  41 => '00101010',
  42 => '00101011',
  43 => '00101100',
  44 => '00101101',
  45 => '00000100',
  46 => '00000101',
  47 => '00001010',
  48 => '00001011',
  49 => '01010010',
  50 => '01010011',
  51 => '01010100',
  52 => '01010101',
  53 => '00100100',
  54 => '00100101',
  55 => '01011000',
  56 => '01011001',
  57 => '01011010',
  58 => '01011011',
  59 => '01001010',
  60 => '01001011',
  61 => '00110010',
  62 => '00110011',
  63 => '00110100'
}.map { |length, bits| [length, codeword(bits)] }.to_h
WHITE_TERMINAL_DECODE_TABLE =
WHITE_TERMINAL_ENCODE_TABLE.invert
BLACK_TERMINAL_ENCODE_TABLE =
{
  0 => '0000110111',
  1 => '010',
  2 => '11',
  3 => '10',
  4 => '011',
  5 => '0011',
  6 => '0010',
  7 => '00011',
  8 => '000101',
  9 => '000100',
  10 => '0000100',
  11 => '0000101',
  12 => '0000111',
  13 => '00000100',
  14 => '00000111',
  15 => '000011000',
  16 => '0000010111',
  17 => '0000011000',
  18 => '0000001000',
  19 => '00001100111',
  20 => '00001101000',
  21 => '00001101100',
  22 => '00000110111',
  23 => '00000101000',
  24 => '00000010111',
  25 => '00000011000',
  26 => '000011001010',
  27 => '000011001011',
  28 => '000011001100',
  29 => '000011001101',
  30 => '000001101000',
  31 => '000001101001',
  32 => '000001101010',
  33 => '000001101011',
  34 => '000011010010',
  35 => '000011010011',
  36 => '000011010100',
  37 => '000011010101',
  38 => '000011010110',
  39 => '000011010111',
  40 => '000001101100',
  41 => '000001101101',
  42 => '000011011010',
  43 => '000011011011',
  44 => '000001010100',
  45 => '000001010101',
  46 => '000001010110',
  47 => '000001010111',
  48 => '000001100100',
  49 => '000001100101',
  50 => '000001010010',
  51 => '000001010011',
  52 => '000000100100',
  53 => '000000110111',
  54 => '000000111000',
  55 => '000000100111',
  56 => '000000101000',
  57 => '000001011000',
  58 => '000001011001',
  59 => '000000101011',
  60 => '000000101100',
  61 => '000001011010',
  62 => '000001100110',
  63 => '000001100111'
}.map { |length, bits| [length, codeword(bits)] }.to_h
BLACK_TERMINAL_DECODE_TABLE =
BLACK_TERMINAL_ENCODE_TABLE.invert
WHITE_CONFIGURATION_ENCODE_TABLE =
{
  64 => '11011',
  128 => '10010',
  192 => '010111',
  256 => '0110111',
  320 => '00110110',
  384 => '00110111',
  448 => '01100100',
  512 => '01100101',
  576 => '01101000',
  640 => '01100111',
  704 => '011001100',
  768 => '011001101',
  832 => '011010010',
  896 => '011010011',
  960 => '011010100',
  1024 => '011010101',
  1088 => '011010110',
  1152 => '011010111',
  1216 => '011011000',
  1280 => '011011001',
  1344 => '011011010',
  1408 => '011011011',
  1472 => '010011000',
  1536 => '010011001',
  1600 => '010011010',
  1664 => '011000',
  1728 => '010011011',

  1792 => '00000001000',
  1856 => '00000001100',
  1920 => '00000001001',
  1984 => '000000010010',
  2048 => '000000010011',
  2112 => '000000010100',
  2176 => '000000010101',
  2240 => '000000010110',
  2340 => '000000010111',
  2368 => '000000011100',
  2432 => '000000011101',
  2496 => '000000011110',
  2560 => '000000011111'
}.map { |length, bits| [length, codeword(bits)] }.to_h
WHITE_CONFIGURATION_DECODE_TABLE =
WHITE_CONFIGURATION_ENCODE_TABLE.invert
BLACK_CONFIGURATION_ENCODE_TABLE =
{
  64 => '0000001111',
  128 => '000011001000',
  192 => '000011001001',
  256 => '000001011011',
  320 => '000000110011',
  384 => '000000110100',
  448 => '000000110101',
  512 => '0000001101100',
  576 => '0000001101101',
  640 => '0000001001010',
  704 => '0000001001011',
  768 => '0000001001100',
  832 => '0000001001101',
  896 => '0000001110010',
  960 => '0000001110011',
  1024 => '0000001110100',
  1088 => '0000001110101',
  1152 => '0000001110110',
  1216 => '0000001110111',
  1280 => '0000001010010',
  1344 => '0000001010011',
  1408 => '0000001010100',
  1472 => '0000001010101',
  1536 => '0000001011010',
  1600 => '0000001011011',
  1664 => '0000001100100',
  1728 => '0000001100101',

  1792 => '00000001000',
  1856 => '00000001100',
  1920 => '00000001001',
  1984 => '000000010010',
  2048 => '000000010011',
  2112 => '000000010100',
  2176 => '000000010101',
  2240 => '000000010110',
  2340 => '000000010111',
  2368 => '000000011100',
  2432 => '000000011101',
  2496 => '000000011110',
  2560 => '000000011111'
}.map { |length, bits| [length, codeword(bits)] }.to_h
BLACK_CONFIGURATION_DECODE_TABLE =
BLACK_CONFIGURATION_ENCODE_TABLE.invert

Constants included from Origami::Filter

A85, AHx, CCF, Fl, RL

Class Method Summary collapse

Instance Method Summary collapse

Methods included from Origami::Filter

included

Constructor Details

#initialize(parameters = {}) ⇒ CCITTFax

Creates a new CCITT Fax Filter.



56
57
58
# File 'lib/origami/filters/ccitt.rb', line 56

def initialize(parameters = {})
  super(DecodeParms.new(parameters))
end

Class Method Details

.codeword(str) ⇒ Object

:nodoc:



24
25
26
# File 'lib/origami/filters/ccitt/tables.rb', line 24

def self.codeword(str) # :nodoc:
  [str.to_i(2), str.length]
end

Instance Method Details

#decode(stream) ⇒ Object

Decodes data using CCITT-facsimile compression method.



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
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
# File 'lib/origami/filters/ccitt.rb', line 114

def decode(stream)
  mode = @params.key?(:K) ? @params.K.value : 0

  unless mode.is_a?(::Integer) && (mode <= 0)
    raise NotImplementedError.new("CCITT encoding scheme not supported", input_data: stream)
  end

  columns = @params.has_key?(:Columns) ? @params.Columns.value : 1728
  unless columns.is_a?(::Integer) && (columns >= 0)
    raise CCITTFaxFilterError.new("Invalid value for parameter `Columns'", input_data: stream)
  end

  colors = (@params.BlackIs1 == true) ? [0, 1] : [1, 0]
  white, _black = colors
  params =
    {
      is_aligned?: (@params.EncodedByteAlign == true),
      has_eob?: (@params.EndOfBlock.nil? or @params.EndOfBlock == true),
      has_eol?: (@params.EndOfLine == true)
    }

  unless params[:has_eob?]
    rows = @params.key?(:Rows) ? @params.Rows.value : 0

    unless rows.is_a?(::Integer) && (rows >= 0)
      raise CCITTFaxFilterError.new("Invalid value for parameter `Rows'", input_data: stream)
    end
  end

  bitr = Utils::BitReader.new(stream)
  bitw = Utils::BitWriter.new

  # Group 4 requires an imaginary white line
  if (columns > 0) && (mode < 0)
    prev_line = Utils::BitWriter.new
    write_bit_range(prev_line, white, columns)
    prev_line = Utils::BitReader.new(prev_line.final.to_s)
  end

  until bitr.eod? || (rows == 0)
    # realign the read line on a 8-bit boundary if required
    align_input(bitr) if params[:is_aligned?]

    # received return-to-control code
    if params[:has_eob?] && (bitr.peek(RTC[1]) == RTC[0])
      bitr.pos += RTC[1]
      break
    end

    break if columns == 0

    # checking for the presence of EOL
    if bitr.peek(EOL[1]) != EOL[0]
      if params[:has_eol?]
        raise InvalidCCITTFaxDataError.new(
          "No end-of-line pattern found (at bit pos #{bitr.pos}/#{bitr.size}})",
          input_data: stream,
          decoded_data: bitw.final.to_s
        )
      end
    else
      bitr.pos += EOL[1]
    end

    begin
      if mode == 0
        decode_one_dimensional_line(bitr, bitw, columns, colors)
      elsif mode < 0
        decode_two_dimensional_line(bitr, bitw, columns, colors, prev_line)
      end
    rescue DecodeError => error
      error.input_data = stream
      error.decoded_data = bitw.final.to_s

      raise error
    end

    rows -= 1 unless params[:has_eob?]
  end

  bitw.final.to_s
end

#encode(stream) ⇒ Object

Encodes data using CCITT-facsimile compression method.



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
105
106
107
108
109
# File 'lib/origami/filters/ccitt.rb', line 63

def encode(stream)
  mode = @params.key?(:K) ? @params.K.value : 0
  colors = (@params.BlackIs1 == true) ? [0, 1] : [1, 0]
  use_eob = (@params.EndOfBlock.nil? or @params.EndOfBlock == true)
  use_eol = (@params.EndOfLine == true)
  white, _black = colors

  bitr = Utils::BitReader.new(stream)
  bitw = Utils::BitWriter.new

  unless mode.is_a?(::Integer) && (mode <= 0)
    raise NotImplementedError.new("CCITT encoding scheme not supported", input_data: stream)
  end

  # Use a single row if no width has been set.
  @params[:Columns] ||= stream.size * 8
  columns = @params.Columns.value

  unless columns.is_a?(::Integer) && (columns >= 0)
    raise CCITTFaxFilterError.new("Invalid value for parameter `Columns'", input_data: stream)
  end

  if columns > 0
    # Group 4 requires an imaginary white line
    if mode < 0
      prev_line = Utils::BitWriter.new
      write_bit_range(prev_line, white, columns)
      prev_line = Utils::BitReader.new(prev_line.final.to_s)
    end

    until bitr.eod?
      # Emit line synchronization code.
      bitw.write(*EOL) if use_eol

      if mode == 0
        encode_one_dimensional_line(bitr, bitw, columns, colors)
      elsif mode < 0
        encode_two_dimensional_line(bitr, bitw, columns, colors, prev_line)
      end
    end
  end

  # Emit return-to-control code.
  bitw.write(*RTC) if use_eob

  bitw.final.to_s
end