Module: Przn::ImageUtil

Defined in:
lib/przn/image_util.rb

Constant Summary collapse

PNG_MAGIC =
"\x89PNG\r\n\x1a\n".b.freeze

Class Method Summary collapse

Class Method Details

.image_size(path) ⇒ Object



7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
# File 'lib/przn/image_util.rb', line 7

def image_size(path)
  return nil unless File.exist?(path)

  File.open(path, 'rb') do |f|
    header = f.read(8)
    return nil unless header && header.size >= 4

    # PNG
    if header.b == "\x89PNG\r\n\x1a\n".b
      f.seek(16)
      w = f.read(4)&.unpack1('N')
      h = f.read(4)&.unpack1('N')
      return [w, h] if w && h
    end

    # JPEG
    f.seek(0)
    if header.b[0..1] == "\xFF\xD8".b
      f.seek(2)
      loop do
        marker = f.read(2)
        break unless marker && marker.size == 2 && marker.getbyte(0) == 0xFF
        type = marker.getbyte(1)
        if [0xC0, 0xC1, 0xC2].include?(type)
          f.read(3)
          h = f.read(2)&.unpack1('n')
          w = f.read(2)&.unpack1('n')
          return [w, h] if w && h
        end
        len = f.read(2)&.unpack1('n')
        break unless len && len >= 2
        f.seek(len - 2, IO::SEEK_CUR)
      end
    end

    # GIF
    f.seek(0)
    sig = f.read(6)
    if sig&.start_with?("GIF8")
      w = f.read(2)&.unpack1('v')
      h = f.read(2)&.unpack1('v')
      return [w, h] if w && h
    end
  end
  nil
rescue
  nil
end

.kitty_icat(path, cols:, rows:, x:, y:) ⇒ Object

Display image using kitten icat with –place for positioning



57
58
59
60
61
62
# File 'lib/przn/image_util.rb', line 57

def kitty_icat(path, cols:, rows:, x:, y:)
  args = ['kitten', 'icat', '--transfer-mode', 'stream',
          '--place', "#{cols}x#{rows}@#{x}x#{y}",
          File.expand_path(path)]
  IO.popen(args, 'r', err: File::NULL) { |io| io.read }
end

.kitty_place(image_id:, cols:, rows:) ⇒ Object

Kitty Graphics Protocol: place a previously-uploaded image at the current cursor position, scaled to fit ‘cols` x `rows` cells.



88
89
90
# File 'lib/przn/image_util.rb', line 88

def kitty_place(image_id:, cols:, rows:)
  "\e_Ga=p,i=#{image_id},c=#{cols},r=#{rows},q=2\e\\"
end

.kitty_terminal?Boolean

Returns:

  • (Boolean)


64
65
66
# File 'lib/przn/image_util.rb', line 64

def kitty_terminal?
  ENV['TERM'] == 'xterm-kitty' || ENV['TERM_PROGRAM'] == 'kitty'
end

.kitty_upload_png(path, image_id:) ⇒ Object

Kitty Graphics Protocol: upload a PNG file by path with the given id. Kitty reads the file directly from disk; we just send a small APC control sequence with the base64-encoded path. Use this once per image; subsequent renders only need a placement command. sw.kovidgoyal.net/kitty/graphics-protocol/



81
82
83
84
# File 'lib/przn/image_util.rb', line 81

def kitty_upload_png(path, image_id:)
  encoded = [File.expand_path(path)].pack('m0')
  "\e_Ga=t,t=f,f=100,i=#{image_id},q=2;#{encoded}\e\\"
end

.png?(path) ⇒ Boolean

Returns:

  • (Boolean)


70
71
72
73
74
# File 'lib/przn/image_util.rb', line 70

def png?(path)
  File.open(path, 'rb') { |f| f.read(8)&.b == PNG_MAGIC }
rescue Errno::ENOENT
  false
end

.sixel_available?Boolean

Sixel via img2sixel

Returns:

  • (Boolean)


93
94
95
96
# File 'lib/przn/image_util.rb', line 93

def sixel_available?
  @sixel_available = system('command -v img2sixel > /dev/null 2>&1') if @sixel_available.nil?
  @sixel_available
end

.sixel_encode(path, width: nil, height: nil) ⇒ Object



98
99
100
101
102
103
104
105
106
107
# File 'lib/przn/image_util.rb', line 98

def sixel_encode(path, width: nil, height: nil)
  return nil unless sixel_available?
  return nil unless File.exist?(path)

  args = ['img2sixel']
  args += ['-w', width.to_s] if width
  args += ['-h', height.to_s] if height
  args << path
  IO.popen(args, 'r', err: File::NULL) { |io| io.read }
end