class Slascii::CLI

Constants

PALETTES

Darker to lighter

Attributes

output[R]

Public Instance Methods

run() click to toggle source
# File lib/slascii/cli.rb, line 21
def run
  ### Program EntryPoint ###
  options = parse_options

  palette = options[:palette]
  palette = palette.reverse if options[:invert]

  @banner = options.fetch(:banner, true)
  @output = if options[:chars]
    make_art_to_character_count(options[:filename], palette, options[:chars])
  else
    make_art_to_width(options[:filename], palette, options[:width])
  end
end
write_output() click to toggle source
# File lib/slascii/cli.rb, line 36
def write_output
  puts output
  puts "\n\n(#{output.length} chars)" if @banner
end

Private Instance Methods

make_art_to_character_count(path, palette, count) click to toggle source
# File lib/slascii/cli.rb, line 111
def make_art_to_character_count(path, palette, count)
  vlog "Generating output to #{count} character limit"

  maps = Hash.new do |hash, width|
    output = make_art_to_width(path, palette, width)
    vlog "Trying #{width}: #{output.length} characters"
    hash[width] = output
  end

  # Find the lower bound
  lower = 40.downto(1).find do |bound|
    maps[bound].length <= count
  end

  # Starting from the lower bound, find the first width N that exceeds the character limit, then return map[N - 1]
  lower.upto(Float::INFINITY).find do |n|
    return maps[n] if maps[n + 1].length > count
  end
end
make_art_to_width(path, palette, width) click to toggle source
# File lib/slascii/cli.rb, line 131
def make_art_to_width(path, palette, width)
  img = open_image(path)
  img.colorspace 'Gray'

  img.resize "#{width}x10000"

  # Turn the 2d array of [r, g, b] values into a 2d array of [r] values (r == g == b because grayscale)
  pixels = img.get_pixels.map { |row| row.map(&:first) }

  # Find the grayscale limits of the photo so we can map only the used range to our palette, which
  # is probably much smaller than the full 255 grayscale values

  (darkest, lightest) = pixels.flatten.minmax

  range = lightest - darkest

  # The gap between each palette value
  step = range.fdiv(palette.size - 1)

  pixels.map do |row|
    row.map do |pixel|
      palette.fetch(((pixel - darkest) / step).round)
    end.join
  end.join("\n")
end
open_image(path) click to toggle source
# File lib/slascii/cli.rb, line 104
def open_image(path)
  MiniMagick::Image.open(path)
rescue StandardError => ex
  puts "Unable to open #{path}: #{ex.message}"
  exit(1)
end
parse_options() click to toggle source
# File lib/slascii/cli.rb, line 51
def parse_options
  options = {}

  parser = OptionParser.new do |opts|
    opts.banner = "Usage: thumbs.rb [options] FILE_NAME"

    opts.on('-c', '--chars C', 'Specify the number of characters to use (default 4000, Slack message limit)') do |c|
      options[:chars] = c.to_i
    end

    opts.on('-w', '--width N', 'Specify width of output (overrides --chars)') do |n|
      options[:width] = n.to_i
    end

    opts.on '-p', '--palette P', "Specify character palette to use: #{PALETTES.keys.join(', ')}" do |p|
      unless (options[:palette] = PALETTES[p.to_sym])
        puts "Invalid palette: #{p}"
        puts "Available palettes:"
        PALETTES.keys.each { |k| puts "  - #{k}" }
        exit
      end
    end

    opts.on '-b', '--no-banner', 'Do not print the character count below the output' do
      options[:banner] = false
    end

    opts.on '-i', '--invert', 'Invert colors' do
      options[:invert] = true
    end

    opts.on '-v', '--verbose', 'Debugging output' do
      @verbose = true
    end
  end

  parser.parse!

  options[:filename] = ARGV.pop

  raise 'Width must be greater than 1' if options[:width] && options[:width] < 1
  raise 'Missing filename' unless options[:filename]
  raise 'Cannot specify both --chars and --width' if options[:chars] && options[:width]

  options[:chars] = 4000 if options[:chars].nil? && options[:width].nil?
  options[:palette] ||= PALETTES.values.first

  options
rescue StandardError => ex
  puts "Error: #{ex.message}\n\n#{parser}"
  exit(1)
end
verbose?() click to toggle source
# File lib/slascii/cli.rb, line 43
def verbose?
  @verbose == true
end
vlog(message) click to toggle source
# File lib/slascii/cli.rb, line 47
def vlog(message)
  puts message if verbose?
end