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:
-
[rmagick](rmagick.github.io/) to read and scale the images and
-
[tco](github.com/pazdera/tco) to map their pixels on the extended colour palette in the terminal.
Some other minor features like centering and handling background colours are supplied directly by this module.
Constants
- MAX_OPACITY
Public Class Methods
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.
# File lib/funny_terminal/catpix.rb, line 49 def self.print_image(path, options={}) options = default_options.merge! options if options[:resolution] == 'auto' options[:resolution] = can_use_utf8? ? 'high' : 'low' end @@resolution = options[:resolution] img = load_image path resize! img, options[:limit_x], options[:limit_y] margins = get_margins img, options[:center_x], options[:center_y] margins[:colour] = options[:bg_fill] ? options[:bg] : nil if high_res? do_print_image_hr img, margins, options else do_print_image_lr img, margins, options end end
# File lib/funny_terminal/catpix.rb, line 70 def self.print_image_url(url) options = { resolution: 'auto', limit_x: 0.5, limit_y: 0, center_x: true, center_y: true, bg: "white", bg_fill: true, } if options[:resolution] == 'auto' options[:resolution] = can_use_utf8? ? 'high' : 'low' end @@resolution = options[:resolution] img = load_image_from_url url resize! img, options[:limit_x], options[:limit_y] margins = get_margins img, options[:center_x], options[:center_y] margins[:colour] = options[:bg_fill] ? options[:bg] : nil if high_res? do_print_image_hr img, margins, options else do_print_image_lr img, margins, options end end
Private Class Methods
# 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
# 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
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
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
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
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
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
# File lib/funny_terminal/catpix/private.rb, line 27 def self.high_res? @@resolution == 'high' end
# File lib/funny_terminal/catpix/private.rb, line 76 def self.load_image(path) Magick::Image::read(path).first end
# 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
# 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
# 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
# File lib/funny_terminal/catpix/private.rb, line 35 def self.prep_lr_pixel(colour) colour ? " ".bg(colour) : " " end
# 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
# File lib/funny_terminal/catpix/private.rb, line 60 def self.print_hr_pixel(colour_top, colour_bottom) print prep_hr_pixel colour_top, colour_bottom end
# File lib/funny_terminal/catpix/private.rb, line 39 def self.print_lr_pixel(colour) print prep_lr_pixel colour end
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