module Catpix

Provides a function to print images in the terminal. A range of different formats is supported (check out what ImageMagick supports). Under the hood, this module uses two components:

Some other minor features like centering and handling background colours are supplied directly by this module.

Constants

MAX_OPACITY

Public Class Methods

print_image(path, options={}) click to toggle source

Print an image to the terminal.

All formats supported by ImageMagick are supported. The image’s colours will be mapped onto the extended 256 colour palette. Also by default, it will be scaled down to fit the width of the terminal while keeping its proportions. This can be changed using the ‘options` parameter.

@param [Hash] options Adjust some parameters of the image when printed. @option options [Float] :limit_x A factor of the terminal window’s width.

If present, the image will be scaled down
to fit (proportions are kept). Using 0
will disable the scaling. [default: 1.0]

@option options [Float] :limit_y A factor of the terminal window’s height.

If present, the image will be scaled down
to fit (proportions are kept). Using 0
will disable the scaling. [default: 0]

@option options [Boolean] :center_x Center the image horizontally in the

terminal window. [default: false]

@option options [Boolean] :center_y Center the image vertically in the

terminal window. [default: false]

@option options [String] :bg Background colour to use in case there are

any fully transparent pixels in the image.
This can be a RGB value '#c0ffee' or a tco
alias 'red' or 'blue'. [default: nil]

@option options [Boolean] :bg_fill Fill the margins around the image with

background colour. [default: false]

@option options [String] :resolution Determines the pixel size of the

rendered image. Can be set to `high`,
`low` or `auto` (default). If set to
`auto` the resolution will be picked
automatically based on your terminal's
support of unicode.
print_image_url(url) click to toggle source

Private Class Methods

can_use_utf8?() click to toggle source
# File lib/funny_terminal/catpix/private.rb, line 31
def self.can_use_utf8?
  ENV.values_at("LC_ALL", "LC_CTYPE", "LANG").compact.first.include?("UTF-8")
end
default_options() click to toggle source
# File lib/funny_terminal/catpix/private.rb, line 13
def self.default_options
  {
    limit_x: 1.0,
    limit_y: 0,
    center_x: false,
    center_y: false,
    bg: nil,
    bg_fill: false,
    resolution: 'auto'
  }
end
do_print_image_hr(img, margins, options) click to toggle source

Print the image in high resolution (using unicode’s upper half block)

# File lib/funny_terminal/catpix/private.rb, line 200
def self.do_print_image_hr(img, margins, options)
  print prep_vert_margin margins[:top], margins[:colour]

  0.step(img.rows - 1, 2) do |row|
    # line buffering makes it about 20% faster
    buffer = prep_horiz_margin margins[:left], margins[:colour]
    0.upto(img.columns - 1) do |col|
      top_pixel = img.pixel_color col, row
      colour_top = if top_pixel.opacity < MAX_OPACITY
        get_normal_rgb top_pixel
      else
        options[:bg]
      end

      bottom_pixel = img.pixel_color col, row + 1
      colour_bottom = if bottom_pixel.opacity < MAX_OPACITY
        get_normal_rgb bottom_pixel
      else
        options[:bg]
      end

      buffer += prep_hr_pixel colour_top, colour_bottom
    end
    buffer += prep_horiz_margin margins[:right], margins[:colour]
    puts buffer
  end

  print prep_vert_margin margins[:bottom], margins[:colour]
end
do_print_image_lr(img, margins, options) click to toggle source

Print the image in low resolution

# File lib/funny_terminal/catpix/private.rb, line 178
def self.do_print_image_lr(img, margins, options)
  print prep_vert_margin margins[:top], margins[:colour]

  0.upto(img.rows - 1) do |row|
    buffer = prep_horiz_margin margins[:left], margins[:colour]
    0.upto(img.columns - 1) do |col|
      pixel = img.pixel_color col, row

      buffer += if pixel.opacity == MAX_OPACITY
        prep_lr_pixel options[:bg]
      else
        prep_lr_pixel get_normal_rgb pixel
      end
    end
    buffer += prep_horiz_margin margins[:right], margins[:colour]
    puts buffer
  end

  print prep_vert_margin margins[:bottom], margins[:colour]
end
get_margins(img, center_x, center_y) click to toggle source

Determine the margins based on the centering options

# File lib/funny_terminal/catpix/private.rb, line 117
def self.get_margins(img, center_x, center_y)
  margins = {}
  tw, th = get_screen_size

  x_space = tw - img.columns
  if center_x
    margins[:left] = x_space / 2
    margins[:right] = x_space / 2 + x_space % 2
  else
    margins[:left] = 0
    margins[:right] = x_space
  end

  y_space = th - img.rows
  if center_y
    margins[:top] = y_space / 2
    margins[:bottom] = y_space / 2 + y_space % 2
  else
    margins[:top] = 0
    margins[:bottom] = 0
  end

  if high_res? and margins[:top] % 2 and margins[:bottom] % 2
    margins[:top] -= 1
    margins[:bottom] += 1
  end

  margins
end
get_normal_rgb(pixel) click to toggle source

Returns the normalised RGB of a ImageMagick’s pixel

# File lib/funny_terminal/catpix/private.rb, line 112
def self.get_normal_rgb(pixel)
  [pixel.red, pixel.green, pixel.blue].map { |v| 255*(v/65535.0) }
end
get_screen_size() click to toggle source

Returns normalised size of the terminal window

Catpix can use either two blank spaces to approximate a pixel in the temrinal or the ‘upper half block’ and ‘bottom half block’ characters.

Depending on which of the above will be used, the screen size must be normalised accordingly.

# File lib/funny_terminal/catpix/private.rb, line 71
def self.get_screen_size
  th, tw = TermInfo.screen_size
  if high_res? then [tw, th * 2] else [tw / 2, th] end
end
high_res?() click to toggle source
# File lib/funny_terminal/catpix/private.rb, line 27
def self.high_res?
  @@resolution == 'high'
end
load_image(path) click to toggle source
# File lib/funny_terminal/catpix/private.rb, line 76
def self.load_image(path)
  Magick::Image::read(path).first
end
load_image_from_url(url) click to toggle source
# File lib/funny_terminal/catpix/private.rb, line 80
def self.load_image_from_url(url)
  img = Magick::ImageList.new
  urlimg = open(url) # Image Remote URL
  img.from_blob(urlimg.read)
end
prep_horiz_margin(size, colour) click to toggle source
# File lib/funny_terminal/catpix/private.rb, line 167
def self.prep_horiz_margin(size, colour)
  buffer = ""
  if high_res?
    size.times { buffer += prep_hr_pixel nil, nil }
  else
    size.times { buffer += prep_lr_pixel nil }
  end
  buffer.bg colour
end
prep_hr_pixel(colour_top, colour_bottom) click to toggle source
# File lib/funny_terminal/catpix/private.rb, line 43
def self.prep_hr_pixel(colour_top, colour_bottom)
  upper = "\u2580"
  lower = "\u2584"

  return " " if colour_bottom.nil? and colour_top.nil?
  return lower.fg colour_bottom if colour_top.nil?
  return upper.fg colour_top if colour_bottom.nil?

  c_top = Tco::match_colour colour_top
  c_bottom = Tco::match_colour colour_bottom
  if c_top == c_bottom
    return " ".bg "@#{c_top}"
  end

  upper.fg("@#{c_top}").bg("@#{c_bottom}")
end
prep_lr_pixel(colour) click to toggle source
# File lib/funny_terminal/catpix/private.rb, line 35
def self.prep_lr_pixel(colour)
  colour ? "  ".bg(colour) : "  "
end
prep_vert_margin(size, colour) click to toggle source
# File lib/funny_terminal/catpix/private.rb, line 147
def self.prep_vert_margin(size, colour)
  tw, th = get_screen_size

  buffer = ""
  if high_res?
    (size / 2).times do
      sub_buffer = ""
      tw.times { sub_buffer += prep_hr_pixel nil, nil }
      buffer += sub_buffer.bg(colour) + "\n"
    end
  else
    size.times do
      sub_buffer = ""
      tw.times { sub_buffer += prep_lr_pixel nil }
      buffer += sub_buffer.bg(colour) + "\n"
    end
  end
  buffer
end
print_hr_pixel(colour_top, colour_bottom) click to toggle source
print_lr_pixel(colour) click to toggle source
resize!(img, limit_x=0, limit_y=0) click to toggle source

Scale the image down based on the limits while keeping the aspect ratio

# File lib/funny_terminal/catpix/private.rb, line 86
def self.resize!(img, limit_x=0, limit_y=0)
  tw, th = get_screen_size
  iw = img.columns
  ih = img.rows

  width = if limit_x > 0
    (tw * limit_x).to_i
  else
    iw
  end

  height = if limit_y > 0
    (th * limit_y).to_i
  else
    ih
  end

  # Resize the image if it's bigger than the limited viewport
  if iw > width or ih > height
    img.change_geometry "#{width}x#{height}" do |cols, rows, img_handle|
      img_handle.resize! (cols).to_i, (rows).to_i
    end
  end
end