Class: Aspera::Preview::Terminal

Inherits:
Object
  • Object
show all
Defined in:
lib/aspera/preview/terminal.rb

Overview

Render an image for terminal output. Uses either colored text blocks or the iTerm2 inline-image protocol when available.

Class Method Summary collapse

Class Method Details

.build(blob, text: false, reserve: 3, double: true, font_ratio: DEFAULT_FONT_RATIO) ⇒ String

Render an image blob for display in the current terminal.

Parameters:

  • blob (String)

    The image as a binary string

  • text (Boolean) (defaults to: false)

    ‘true` to display the image as text, `false` to use iTerm2 if supported

  • reserve (Integer) (defaults to: 3)

    Number of lines to reserve for other text than the image

  • double (Boolean) (defaults to: true)

    ‘true` to use colors on half lines, `false` to use colors on full lines

  • font_ratio (Float) (defaults to: DEFAULT_FONT_RATIO)

    ratio = font height / font width

Returns:

  • (String)

    The image as text, or the iTerm2 escape sequence



130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
# File 'lib/aspera/preview/terminal.rb', line 130

def build(blob, text: false, reserve: 3, double: true, font_ratio: DEFAULT_FONT_RATIO)
  return '[Image display requires a terminal]' unless Environment.terminal?
  return iterm_display_image(blob) if iterm_supported? && !text
  pixel_colors =
    begin
      Log.log.debug('Trying chunky_png')
      Backend::ChunkyPNG.new(blob, reserve: reserve, double: double, font_ratio: font_ratio).terminal_pixels
    rescue => e
      Log.log.debug(e.message)
      begin
        Log.log.debug('Trying rmagick')
        Backend::RMagick.new(blob, reserve: reserve, double: double, font_ratio: font_ratio).terminal_pixels
      rescue => e
        Log.log.debug(e.message)
        nil
      end
    end
  if pixel_colors.nil?
    return iterm_display_image(blob) if iterm_supported?
    raise 'Cannot decode picture.'
  end
  # Convert decoded pixels into terminal glyphs.
  text_pixels = []
  pixel_colors.each_with_index do |row_data, row|
    next if double && (row.odd? || row.eql?(pixel_colors.length - 1))
    row_data.each_with_index do |pixel_rgb, col|
      text_pixels.push("\n") if col.eql?(0) && !row.eql?(0)
      if double
        text_pixels.push(Rainbow('').background(pixel_rgb).foreground(pixel_colors[row + 1][col]))
      else
        text_pixels.push(Rainbow(' ').background(pixel_rgb))
      end
    end
  end
  return text_pixels.join
end

.iterm_display_image(blob) ⇒ String

Build the iTerm2 inline-image escape sequence. iterm2.com/documentation-images.html

Parameters:

  • blob (String)

    image binary content

Returns:

  • (String)

    escape sequence that displays the image inline



172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
# File 'lib/aspera/preview/terminal.rb', line 172

def iterm_display_image(blob)
  # image = Magick::ImageList.new.from_blob(blob)
  # Parameters accepted by the iTerm2 inline-image protocol.
  arguments = {
    inline:              1,
    preserveAspectRatio: 1,
    size:                blob.length
    # width:               image.columns,
    # height:              image.rows
  }.map{ |k, v| "#{k}=#{v}"}.join(';')
  # `\a` is BEL and `\e` is ESC.
  # See: https://github.com/ruby/ruby/blob/master/doc/syntax/literals.rdoc#label-Strings
  # Return the full escape sequence expected by iTerm2-compatible terminals.
  return "\e]1337;File=#{arguments}:#{Base64.strict_encode64(blob)}\a"
end

.iterm_supported?Boolean

Detect whether the current terminal supports iTerm2 inline images.

Returns:

  • (Boolean)

    ‘true` when the current terminal advertises iTerm2 image support



191
192
193
194
195
196
# File 'lib/aspera/preview/terminal.rb', line 191

def iterm_supported?
  TERM_ENV_VARS.each do |env_var|
    return true if ITERM_NAMES.any?{ |term| ENV[env_var]&.include?(term)}
  end
  false
end