class Qrio::Qr

Attributes

candidates[R]
finder_patterns[R]
matches[R]
qr[R]
qr_bounds[R]

Public Class Methods

load(filename) click to toggle source
# File lib/qrio/qr.rb, line 10
def self.load(filename)
  instance = new
  instance.load_image(filename)

  instance.scan(:horizontal)
  instance.scan(:vertical)

  instance.filter_candidates
  instance.find_intersections
  instance.set_qr_bounds

  instance.build_normalized_qr
  instance.find_alignment_pattern

  instance
end
new() click to toggle source
# File lib/qrio/qr.rb, line 6
def initialize
  initialize_storage
end

Public Instance Methods

add_candidate(new_candidate, direction) click to toggle source
# File lib/qrio/qr.rb, line 67
def add_candidate(new_candidate, direction)
  added = false

  @candidates[direction].each_with_index do |existing, index|
    if new_candidate.adjacent?(existing)
      @candidates[direction][index] = existing.union(new_candidate)
      added = true
    end
  end

  @candidates[direction] << new_candidate unless added
end
build_normalized_qr() click to toggle source

extract the qr into a smaller matrix and rotate to standard orientation

# File lib/qrio/qr.rb, line 128
def build_normalized_qr
  return false if @sampling_grid.nil?

  original_orientation = @sampling_grid.orientation

  @sampling_grid.normalize
  @extracted_matrix = @sampling_grid.matrix

  bits = []
  @sampling_grid.extracted_pixels do |x, y|
    bits << @extracted_matrix[x, y]
  end
  @qr = QrMatrix.new(bits, @sampling_grid.logical_width, @sampling_grid.logical_height)

  @translated_matches = {
    :horizontal => [],
    :vertical   => []
  }
  @translated_finder_patterns = []
  @translated_neighbors = []

  @matches[:horizontal].each do |m|
    m = m.translate(*@qr_bounds.top_left)
    if original_orientation > 0
      (4 - original_orientation).times do
        m = m.rotate(@qr_bounds.width, @qr_bounds.height)
      end
    end
    @translated_matches[:horizontal] << m
  end

  @matches[:vertical].each do |m|
    m = m.translate(*@qr_bounds.top_left)
    if original_orientation > 0
      (4 - original_orientation).times do
        m = m.rotate(@qr_bounds.width, @qr_bounds.height)
      end
    end
    @translated_matches[:vertical] << m
  end
end
filter_candidates() click to toggle source
# File lib/qrio/qr.rb, line 80
def filter_candidates
  [:horizontal, :vertical].each do |direction|
    @candidates[direction].uniq.each do |candidate|
      @matches[direction] << candidate if candidate.matches_aspect_ratio?
    end
  end
end
find_alignment_pattern() click to toggle source
# File lib/qrio/qr.rb, line 170
def find_alignment_pattern
  test_x = @sampling_grid.top_right.center.first - (@sampling_grid.block_width * 3)
  test_y = @sampling_grid.bottom_left.center.last - (@sampling_grid.block_height * 3)

  test_x = (test_x - @sampling_grid.block_width  / 2.0).round
  test_y = (test_y - @sampling_grid.block_height / 2.0).round

  # TODO : this is where the AP *should* be, given no image skew.
  # starting here, find center of nearest blob that "looks" like an AP.
  # offset from predicted will be used in sampling grid to account for skew
  @alignment_pattern = Qrio::Region.new(
    test_x, test_y,
    (test_x + @sampling_grid.block_width).round,
    (test_y + @sampling_grid.block_height).round
  )
end
find_candidate(offset, origin, segment, direction) click to toggle source
# File lib/qrio/qr.rb, line 63
def find_candidate(offset, origin, segment, direction)
  FinderPatternSlice.build_matching(offset, origin, segment, direction)
end
find_intersections() click to toggle source

find intersections of horizontal and vertical slices, these are (likely) finder patterns

# File lib/qrio/qr.rb, line 112
def find_intersections
  @matches[:horizontal].each do |h|
    @matches[:vertical].each do |v|
      @finder_patterns << h.union(v) if h.intersects?(v)
    end
  end
end
load_image(filename) click to toggle source
# File lib/qrio/qr.rb, line 27
def load_image(filename)
  initialize_storage

  image_type = File.extname(filename).upcase.gsub('.', '')
  image_loader_class = "#{ image_type }ImageLoader"
  image_loader_class = ImageLoader.const_get(image_loader_class) rescue nil

  if image_loader_class.nil?
    raise "Image type '#{ image_type }' not supported"
  end

  @input_matrix = image_loader_class.load(filename)
end
rle(vector) click to toggle source

transform a vector of bits in to a run-length encoded vector of widths example: [1, 1, 1, 1, 0, 0, 1, 1, 1] => [4, 2, 3]

# File lib/qrio/qr.rb, line 90
def rle(vector)
  v = vector.dup

  pattern = []
  length = 1
  last = v.shift

  v.each do |current|
    if current === last
      length += 1
    else
      pattern << length
      length = 1
      last = current
    end
  end

  pattern << length
end
scan(direction) click to toggle source
# File lib/qrio/qr.rb, line 41
def scan(direction)
  vectors = direction == :horizontal ? @input_matrix.rows : @input_matrix.columns
  vectors.each_with_index do |vector, offset|
    pattern = rle(vector)

    if pattern.length >= 5
      origin = 0
      segment = pattern.slice!(0,4)

      while next_length = pattern.shift
        segment << next_length

        if candidate = find_candidate(offset, origin, segment, direction)
          add_candidate(candidate, direction)
        end

        origin += segment.shift
      end
    end
  end
end
set_qr_bounds() click to toggle source
# File lib/qrio/qr.rb, line 120
def set_qr_bounds
  if @finder_patterns.length >= 3
    @sampling_grid = SamplingGrid.new(@input_matrix, @finder_patterns)
    @qr_bounds = @sampling_grid.bounds
  end
end

Private Instance Methods

initialize_storage() click to toggle source
# File lib/qrio/qr.rb, line 189
def initialize_storage
  @candidates = {
    :horizontal => [],
    :vertical   => [],
  }
  @matches = {
    :horizontal => [],
    :vertical   => [],
  }
  @finder_patterns = []
  @input_matrix = @extracted_matrix = nil
end