module HexaPDF::Filter::Predictor

def self.decoder(source, options)

See HexaPDF::Filter
def self.decoder(source, options)
  execute(:decoder, source, options)
end

def self.encoder(source, options)

See HexaPDF::Filter
def self.encoder(source, options)
  execute(:encoder, source, options)
end

def self.execute(type, source, options) # :nodoc:

:nodoc:
def self.execute(type, source, options) # :nodoc:
  return source if !options[:Predictor] || options[:Predictor] == 1
  colors = options[:Colors] || 1
  bits_per_component = options[:BitsPerComponent] || 8
  columns = options[:Columns] || 1
  if options[:Predictor] == 2
    tiff_execute(type, source, colors, bits_per_component, columns)
  elsif options[:Predictor] >= 10
    png_execute(type, source, options[:Predictor], colors, bits_per_component, columns)
  else
    raise HexaPDF::InvalidPDFObjectError, "Predictor key is invalid: #{options[:Predictor]}"
  end
end

def self.png_execute(type, source, predictor, colors, bits_per_component, columns) # :nodoc:

:nodoc:
def self.png_execute(type, source, predictor, colors, bits_per_component, columns) # :nodoc:
  Fiber.new do
    bytes_per_pixel = (bits_per_component * colors + 7) / 8
    bytes_per_row = (columns * bits_per_component * colors + 7) / 8
    bytes_per_row += 1 if type == :decoder
    # Only on encoding: Arbitrarily choose a predictor if we should choose the optimum
    predictor = predictor == 15 ? PREDICTOR_PNG_PAETH : predictor - 10
    data = ''.b
    last_line = "\0".b * (bytes_per_row + 1)
    pos = 0
    decode_row = lambda do |result|
      line = data[pos + 1, bytes_per_row - 1]
      case data.getbyte(pos)
      when PREDICTOR_PNG_SUB
        bytes_per_pixel.upto(bytes_per_row - 2) do |i|
          line.setbyte(i, (line.getbyte(i) + line.getbyte(i - bytes_per_pixel)) % 256)
        end
      when PREDICTOR_PNG_UP
        0.upto(bytes_per_row - 2) do |i|
          line.setbyte(i, (line.getbyte(i) + last_line.getbyte(i)) % 256)
        end
      when PREDICTOR_PNG_AVERAGE
        0.upto(bytes_per_row - 2) do |i|
          a = i < bytes_per_pixel ? 0 : line.getbyte(i - bytes_per_pixel)
          line.setbyte(i, (line.getbyte(i) + ((a + last_line.getbyte(i)) >> 1)) % 256)
        end
      when PREDICTOR_PNG_PAETH
        0.upto(bytes_per_row - 2) do |i|
          a = i < bytes_per_pixel ? 0 : line.getbyte(i - bytes_per_pixel)
          b = last_line.getbyte(i)
          c = i < bytes_per_pixel ? 0 : last_line.getbyte(i - bytes_per_pixel)
          point = a + b - c
          pa = (point - a).abs
          pb = (point - b).abs
          pc = (point - c).abs
          point = ((pa <= pb && pa <= pc) ? a : (pb <= pc ? b : c))
          line.setbyte(i, (line.getbyte(i) + point) % 256)
        end
      end
      result << line
      last_line = line
    end
    encode_row = lambda do |result|
      line = predictor.chr.force_encoding(Encoding::BINARY) << data[pos, bytes_per_row]
      next_last_line = line.dup
      case predictor
      when PREDICTOR_PNG_SUB
        bytes_per_row.downto(bytes_per_pixel + 1) do |i|
          line.setbyte(i, (line.getbyte(i) - line.getbyte(i - bytes_per_pixel)) % 256)
        end
      when PREDICTOR_PNG_UP
        bytes_per_row.downto(1) do |i|
          line.setbyte(i, (line.getbyte(i) - last_line.getbyte(i)) % 256)
        end
      when PREDICTOR_PNG_AVERAGE
        bytes_per_row.downto(1) do |i|
          a = i <= bytes_per_pixel ? 0 : line.getbyte(i - bytes_per_pixel)
          line.setbyte(i, (line.getbyte(i) - ((a + last_line.getbyte(i)) >> 1)) % 256)
        end
      when PREDICTOR_PNG_PAETH
        bytes_per_row.downto(1) do |i|
          a = i <= bytes_per_pixel ? 0 : line.getbyte(i - bytes_per_pixel)
          b = last_line.getbyte(i)
          c = i <= bytes_per_pixel ? 0 : last_line.getbyte(i - bytes_per_pixel)
          point = a + b - c
          pa = (point - a).abs
          pb = (point - b).abs
          pc = (point - c).abs
          point = ((pa <= pb && pa <= pc) ? a : (pb <= pc ? b : c))
          line.setbyte(i, (line.getbyte(i) - point) % 256)
        end
      end
      result << line
      last_line = next_last_line
    end
    row_action = (type == :decoder ? decode_row : encode_row)
    while source.alive? && (new_data = source.resume)
      data.slice!(0...pos)
      data << new_data
      result = ''.b
      pos = 0
      while pos + bytes_per_row <= data.length
        row_action.call(result)
        pos += bytes_per_row
      end
      Fiber.yield(result) unless result.empty?
    end
    if pos != data.length && GlobalConfiguration['filter.predictor.strict']
      raise FilterError, "Data is missing for PNG predictor"
    elsif pos != data.length && data.length != 1
      result = ''.b
      bytes_per_row = data.length - pos
      row_action.call(result)
      result
    end
  end
end

def self.tiff_execute(type, source, colors, bits_per_component, columns) # :nodoc:

:nodoc:
def self.tiff_execute(type, source, colors, bits_per_component, columns) # :nodoc:
  Fiber.new do
    bytes_per_row = (columns * bits_per_component * colors + 7) / 8
    mask = (1 << bits_per_component) - 1
    data = ''.b
    writer = HexaPDF::Utils::BitStreamWriter.new
    pos = 0
    decode_row = lambda do |result, reader|
      last_components = [0] * colors
      (columns * colors).times do |i|
        i %= colors
        tmp = (reader.read(bits_per_component) + last_components[i]) & mask
        result << writer.write(tmp, bits_per_component)
        last_components[i] = tmp
      end
      result << writer.finalize
    end
    encode_row = lambda do |result, reader|
      last_components = [0] * colors
      (columns * colors).times do |i|
        i %= colors
        tmp = reader.read(bits_per_component)
        result << writer.write((tmp - last_components[i]) & mask, bits_per_component)
        last_components[i] = tmp
      end
      result << writer.finalize
    end
    row_action = (type == :decoder ? decode_row : encode_row)
    while source.alive? && (new_data = source.resume)
      data.slice!(0...pos)
      data << new_data
      result = ''.b
      pos = 0
      while pos + bytes_per_row <= data.length
        reader = HexaPDF::Utils::BitStreamReader.new(data[pos, bytes_per_row])
        row_action.call(result, reader)
        pos += bytes_per_row
      end
      Fiber.yield(result) unless result.empty?
    end
    unless pos == data.length
      raise FilterError, "Data is missing for TIFF predictor"
    end
  end
end