terminal_image 1.0.0 → 1.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/.github/CODEOWNERS +1 -0
- data/.github/dependabot.yml +2 -0
- data/.github/workflows/release.yaml +25 -1
- data/.github/workflows/test.yaml +3 -3
- data/.gitignore +57 -0
- data/.rubocop.yml +2 -2
- data/Gemfile +12 -0
- data/Gemfile.lock +54 -45
- data/README.md +19 -0
- data/lib/terminal_image/ansi_encoder.rb +91 -0
- data/lib/terminal_image/png_converter.rb +49 -0
- data/lib/terminal_image/version.rb +1 -1
- data/lib/terminal_image.rb +28 -2
- data/terminal_image.gemspec +2 -11
- metadata +12 -120
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 59f2f7f04d128c42a96335aa00e6c443b66514ff4cb5497d167e65942813a2bc
|
|
4
|
+
data.tar.gz: e6e79383209adbcc08acc3ea2145ec2c90909e749ce85acdca20b7f0b85c48ad
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 0d004649f6006d31030d608aacf427488cfbfd84434a872dbcb9ac2b02f745dad4fa11dc118aee623d3ac92a15895b9d6737e1e6353b768f868f9f193031e955
|
|
7
|
+
data.tar.gz: 3decf6ffdaf489af2e8316ce28750299ac01947f147a0f77a98e57cadfc96bcdc798ce8e1dcec8c2f150aac268259156a3d310476204e4783a48205849ebfba2
|
data/.github/CODEOWNERS
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
* @unhappychoice
|
data/.github/dependabot.yml
CHANGED
|
@@ -2,10 +2,19 @@ name: release
|
|
|
2
2
|
|
|
3
3
|
on:
|
|
4
4
|
workflow_dispatch:
|
|
5
|
+
inputs:
|
|
6
|
+
version:
|
|
7
|
+
description: Release version
|
|
8
|
+
required: true
|
|
9
|
+
type: choice
|
|
10
|
+
options:
|
|
11
|
+
- major
|
|
12
|
+
- minor
|
|
13
|
+
- patch
|
|
5
14
|
jobs:
|
|
6
15
|
push:
|
|
7
16
|
name: Push gem to RubyGems.org
|
|
8
|
-
runs-on: ubuntu-
|
|
17
|
+
runs-on: ubuntu-24.04
|
|
9
18
|
environment:
|
|
10
19
|
name: release
|
|
11
20
|
permissions:
|
|
@@ -18,4 +27,19 @@ jobs:
|
|
|
18
27
|
with:
|
|
19
28
|
bundler-cache: true
|
|
20
29
|
ruby-version: .ruby-version
|
|
30
|
+
- name: Setup Git
|
|
31
|
+
run: |
|
|
32
|
+
git config --global user.email "unhappychoice@gmail.com"
|
|
33
|
+
git config --global user.name "Yuji Ueki"
|
|
34
|
+
- name: Install gem-release
|
|
35
|
+
run: gem install gem-release
|
|
36
|
+
- name: Bump version
|
|
37
|
+
run: |
|
|
38
|
+
git status
|
|
39
|
+
gem bump --version ${{inputs.version}} --message ':tada: Bump %{name} to %{version}'
|
|
40
|
+
git status
|
|
41
|
+
bundle config set frozen false
|
|
42
|
+
bundle install
|
|
43
|
+
git add .
|
|
44
|
+
git commit --amend --no-edit
|
|
21
45
|
- uses: rubygems/release-gem@v1
|
data/.github/workflows/test.yaml
CHANGED
|
@@ -4,7 +4,7 @@ on: [push]
|
|
|
4
4
|
|
|
5
5
|
jobs:
|
|
6
6
|
lint:
|
|
7
|
-
runs-on: ubuntu-
|
|
7
|
+
runs-on: ubuntu-24.04
|
|
8
8
|
steps:
|
|
9
9
|
- uses: actions/checkout@master
|
|
10
10
|
- name: Set up Ruby
|
|
@@ -18,7 +18,7 @@ jobs:
|
|
|
18
18
|
- name: Run test
|
|
19
19
|
run: bundle exec rubocop lib
|
|
20
20
|
test:
|
|
21
|
-
runs-on: ubuntu-
|
|
21
|
+
runs-on: ubuntu-24.04
|
|
22
22
|
services:
|
|
23
23
|
redis:
|
|
24
24
|
image: redis
|
|
@@ -31,7 +31,7 @@ jobs:
|
|
|
31
31
|
- 6379:6379
|
|
32
32
|
strategy:
|
|
33
33
|
matrix:
|
|
34
|
-
ruby: ['3.4.4', '3.3.8'
|
|
34
|
+
ruby: ['3.4.4', '3.3.8']
|
|
35
35
|
steps:
|
|
36
36
|
- uses: actions/checkout@master
|
|
37
37
|
- name: Set up Rub
|
data/.gitignore
ADDED
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
# Created by https://www.gitignore.io/api/ruby
|
|
2
|
+
# Edit at https://www.gitignore.io/?templates=ruby
|
|
3
|
+
|
|
4
|
+
### Ruby ###
|
|
5
|
+
*.gem
|
|
6
|
+
*.rbc
|
|
7
|
+
/.config
|
|
8
|
+
/coverage/
|
|
9
|
+
/InstalledFiles
|
|
10
|
+
/pkg/
|
|
11
|
+
/spec/reports/
|
|
12
|
+
/spec/examples.txt
|
|
13
|
+
/test/tmp/
|
|
14
|
+
/test/version_tmp/
|
|
15
|
+
/tmp/
|
|
16
|
+
|
|
17
|
+
# Used by dotenv library to load environment variables.
|
|
18
|
+
# .env
|
|
19
|
+
|
|
20
|
+
## Specific to RubyMotion:
|
|
21
|
+
.dat*
|
|
22
|
+
.repl_history
|
|
23
|
+
build/
|
|
24
|
+
*.bridgesupport
|
|
25
|
+
build-iPhoneOS/
|
|
26
|
+
build-iPhoneSimulator/
|
|
27
|
+
|
|
28
|
+
## Specific to RubyMotion (use of CocoaPods):
|
|
29
|
+
#
|
|
30
|
+
# We recommend against adding the Pods directory to your .gitignore. However
|
|
31
|
+
# you should judge for yourself, the pros and cons are mentioned at:
|
|
32
|
+
# https://guides.cocoapods.org/using/using-cocoapods.html#should-i-check-the-pods-directory-into-source-control
|
|
33
|
+
#
|
|
34
|
+
# vendor/Pods/
|
|
35
|
+
|
|
36
|
+
## Documentation cache and generated files:
|
|
37
|
+
/.yardoc/
|
|
38
|
+
/_yardoc/
|
|
39
|
+
/doc/
|
|
40
|
+
/rdoc/
|
|
41
|
+
|
|
42
|
+
## Environment normalization:
|
|
43
|
+
/.bundle/
|
|
44
|
+
/vendor/bundle
|
|
45
|
+
/lib/bundler/man/
|
|
46
|
+
vendor/bundle
|
|
47
|
+
|
|
48
|
+
# for a library or gem, you might want to ignore these files since the code is
|
|
49
|
+
# intended to run in multiple environments; otherwise, check them in:
|
|
50
|
+
# Gemfile.lock
|
|
51
|
+
# .ruby-version
|
|
52
|
+
# .ruby-gemset
|
|
53
|
+
|
|
54
|
+
# unless supporting rvm < 1.11.0 or doing something fancy, ignore this:
|
|
55
|
+
.rvmrc
|
|
56
|
+
|
|
57
|
+
# End of https://www.gitignore.io/api/ruby
|
data/.rubocop.yml
CHANGED
data/Gemfile
CHANGED
|
@@ -4,3 +4,15 @@ source 'https://rubygems.org'
|
|
|
4
4
|
|
|
5
5
|
# Specify your gem's dependencies in terminal_image.gemspec
|
|
6
6
|
gemspec
|
|
7
|
+
|
|
8
|
+
group :development do
|
|
9
|
+
gem 'bundler', '~> 2.0'
|
|
10
|
+
gem 'codecov'
|
|
11
|
+
gem 'guard'
|
|
12
|
+
gem 'guard-rspec'
|
|
13
|
+
gem 'guard-rubocop'
|
|
14
|
+
gem 'rake', '~> 13.4'
|
|
15
|
+
gem 'rspec', '~> 3.13'
|
|
16
|
+
gem 'rubocop'
|
|
17
|
+
gem 'simplecov'
|
|
18
|
+
end
|
data/Gemfile.lock
CHANGED
|
@@ -1,32 +1,34 @@
|
|
|
1
1
|
PATH
|
|
2
2
|
remote: .
|
|
3
3
|
specs:
|
|
4
|
-
terminal_image (1.
|
|
4
|
+
terminal_image (1.1.0)
|
|
5
5
|
base64 (~> 0.3)
|
|
6
|
+
chunky_png (~> 1.4)
|
|
6
7
|
fastimage
|
|
7
8
|
|
|
8
9
|
GEM
|
|
9
10
|
remote: https://rubygems.org/
|
|
10
11
|
specs:
|
|
11
|
-
ast (2.4.
|
|
12
|
+
ast (2.4.3)
|
|
12
13
|
base64 (0.3.0)
|
|
13
|
-
|
|
14
|
-
|
|
14
|
+
chunky_png (1.4.0)
|
|
15
|
+
codecov (0.2.12)
|
|
16
|
+
json
|
|
17
|
+
simplecov
|
|
15
18
|
coderay (1.1.3)
|
|
16
|
-
diff-lcs (1.6.
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
ffi (1.17.
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
guard (2.
|
|
19
|
+
diff-lcs (1.6.2)
|
|
20
|
+
fastimage (2.4.3)
|
|
21
|
+
ffi (1.17.4-x86_64-darwin)
|
|
22
|
+
ffi (1.17.4-x86_64-linux-gnu)
|
|
23
|
+
formatador (1.2.3)
|
|
24
|
+
reline
|
|
25
|
+
guard (2.20.2)
|
|
23
26
|
formatador (>= 0.2.4)
|
|
24
27
|
listen (>= 2.7, < 4.0)
|
|
25
28
|
logger (~> 1.6)
|
|
26
29
|
lumberjack (>= 1.0.12, < 2.0)
|
|
27
30
|
nenv (~> 0.1)
|
|
28
31
|
notiffany (~> 0.0)
|
|
29
|
-
ostruct (~> 0.6)
|
|
30
32
|
pry (>= 0.13.0)
|
|
31
33
|
shellany (~> 0.0)
|
|
32
34
|
thor (>= 0.18.1)
|
|
@@ -38,66 +40,73 @@ GEM
|
|
|
38
40
|
guard-rubocop (1.5.0)
|
|
39
41
|
guard (~> 2.0)
|
|
40
42
|
rubocop (< 2.0)
|
|
41
|
-
|
|
43
|
+
io-console (0.9.5)
|
|
44
|
+
json (3.1.0)
|
|
45
|
+
language_server-protocol (3.17.0.6)
|
|
46
|
+
lint_roller (1.1.0)
|
|
47
|
+
listen (3.10.1)
|
|
48
|
+
logger
|
|
42
49
|
rb-fsevent (~> 0.10, >= 0.10.3)
|
|
43
50
|
rb-inotify (~> 0.9, >= 0.9.10)
|
|
44
51
|
logger (1.7.0)
|
|
45
|
-
lumberjack (1.2
|
|
52
|
+
lumberjack (1.4.2)
|
|
46
53
|
method_source (1.1.0)
|
|
47
54
|
nenv (0.3.0)
|
|
48
55
|
notiffany (0.1.3)
|
|
49
56
|
nenv (~> 0.1)
|
|
50
57
|
shellany (~> 0.0)
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
parser (3.3.7.1)
|
|
58
|
+
parallel (2.3.0)
|
|
59
|
+
parser (3.3.13.0)
|
|
54
60
|
ast (~> 2.4.1)
|
|
55
61
|
racc
|
|
56
|
-
|
|
62
|
+
prism (1.9.0)
|
|
63
|
+
pry (0.16.1)
|
|
57
64
|
coderay (~> 1.1)
|
|
58
65
|
method_source (~> 1.0)
|
|
66
|
+
reline (>= 0.6.0)
|
|
59
67
|
racc (1.8.1)
|
|
60
68
|
rainbow (3.1.1)
|
|
61
|
-
rake (13.2
|
|
69
|
+
rake (13.4.2)
|
|
62
70
|
rb-fsevent (0.11.2)
|
|
63
71
|
rb-inotify (0.11.1)
|
|
64
72
|
ffi (~> 1.0)
|
|
65
|
-
regexp_parser (2.
|
|
66
|
-
|
|
67
|
-
|
|
73
|
+
regexp_parser (2.13.1)
|
|
74
|
+
reline (0.7.0)
|
|
75
|
+
io-console (~> 0.5)
|
|
76
|
+
rspec (3.13.2)
|
|
68
77
|
rspec-core (~> 3.13.0)
|
|
69
78
|
rspec-expectations (~> 3.13.0)
|
|
70
79
|
rspec-mocks (~> 3.13.0)
|
|
71
|
-
rspec-core (3.13.
|
|
80
|
+
rspec-core (3.13.6)
|
|
72
81
|
rspec-support (~> 3.13.0)
|
|
73
|
-
rspec-expectations (3.13.
|
|
82
|
+
rspec-expectations (3.13.5)
|
|
74
83
|
diff-lcs (>= 1.2.0, < 2.0)
|
|
75
84
|
rspec-support (~> 3.13.0)
|
|
76
|
-
rspec-mocks (3.13.
|
|
85
|
+
rspec-mocks (3.13.8)
|
|
77
86
|
diff-lcs (>= 1.2.0, < 2.0)
|
|
78
87
|
rspec-support (~> 3.13.0)
|
|
79
|
-
rspec-support (3.13.
|
|
80
|
-
rubocop (1.
|
|
81
|
-
|
|
82
|
-
|
|
88
|
+
rspec-support (3.13.7)
|
|
89
|
+
rubocop (1.92.2)
|
|
90
|
+
json (>= 2.3)
|
|
91
|
+
language_server-protocol (~> 3.17.0.2)
|
|
92
|
+
lint_roller (~> 1.1.0)
|
|
93
|
+
parallel (>= 1.10)
|
|
94
|
+
parser (>= 3.3.0.2)
|
|
83
95
|
rainbow (>= 2.2.2, < 4.0)
|
|
84
|
-
regexp_parser (>=
|
|
85
|
-
|
|
86
|
-
rubocop-ast (>= 1.17.0, < 2.0)
|
|
96
|
+
regexp_parser (>= 2.11.3, < 3.0)
|
|
97
|
+
rubocop-ast (>= 1.49.0, < 2.0)
|
|
87
98
|
ruby-progressbar (~> 1.7)
|
|
88
|
-
unicode-display_width (>=
|
|
89
|
-
rubocop-ast (1.
|
|
90
|
-
parser (>= 3.
|
|
99
|
+
unicode-display_width (>= 2.4.0, < 4.0)
|
|
100
|
+
rubocop-ast (1.50.0)
|
|
101
|
+
parser (>= 3.3.7.2)
|
|
102
|
+
prism (~> 1.7)
|
|
91
103
|
ruby-progressbar (1.13.0)
|
|
92
104
|
shellany (0.0.1)
|
|
93
|
-
simplecov (
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
simplecov_json_formatter (0.1.4)
|
|
99
|
-
thor (1.2.2)
|
|
100
|
-
unicode-display_width (2.6.0)
|
|
105
|
+
simplecov (1.3.2)
|
|
106
|
+
thor (1.5.0)
|
|
107
|
+
unicode-display_width (3.3.0)
|
|
108
|
+
unicode-emoji (~> 4.3)
|
|
109
|
+
unicode-emoji (4.3.0)
|
|
101
110
|
|
|
102
111
|
PLATFORMS
|
|
103
112
|
x86_64-darwin-24
|
|
@@ -109,8 +118,8 @@ DEPENDENCIES
|
|
|
109
118
|
guard
|
|
110
119
|
guard-rspec
|
|
111
120
|
guard-rubocop
|
|
112
|
-
rake (~> 13.
|
|
113
|
-
rspec (~> 3.
|
|
121
|
+
rake (~> 13.4)
|
|
122
|
+
rspec (~> 3.13)
|
|
114
123
|
rubocop
|
|
115
124
|
simplecov
|
|
116
125
|
terminal_image!
|
data/README.md
CHANGED
|
@@ -9,6 +9,7 @@
|
|
|
9
9
|
|
|
10
10
|
TerminalImage is a library to show images on terminals.
|
|
11
11
|
Currently, this library supports [iTerm2](https://iterm2.com/index.html) and terminals with [libsixel](https://github.com/saitoha/libsixel) installed.
|
|
12
|
+
On other terminals, images fall back to 24-bit ANSI colors (see [Fallback](#fallback)).
|
|
12
13
|
|
|
13
14
|

|
|
14
15
|
|
|
@@ -40,6 +41,20 @@ string = TerminalImage.encode(File.open('your-image-path.png'))
|
|
|
40
41
|
string = TerminalImage.encode_url('https://raw.githubusercontent.com/unhappychoice/terminal_image/master/images/sample.png')
|
|
41
42
|
```
|
|
42
43
|
|
|
44
|
+
## Fallback
|
|
45
|
+
|
|
46
|
+
When the terminal is not iTerm2 and `img2sixel` is not installed, images are drawn with 24-bit ("truecolor") ANSI colors.
|
|
47
|
+
Each character cell is an upper half block (`▀`): the foreground color is one pixel and the background color is the pixel below it.
|
|
48
|
+
The image is scaled down to the terminal width (`IO.console`, then `$COLUMNS`, then 80 columns) and is never scaled up.
|
|
49
|
+
Transparent pixels keep the terminal's default background.
|
|
50
|
+
|
|
51
|
+
Supported formats:
|
|
52
|
+
|
|
53
|
+
- **PNG** is decoded with the pure-Ruby [chunky_png](https://github.com/wvanbergen/chunky_png) gem and needs nothing else.
|
|
54
|
+
- **Other formats** (JPEG, GIF, WebP, ...) are converted to PNG with the [ImageMagick](https://imagemagick.org/) CLI
|
|
55
|
+
(`magick`, or `convert` for ImageMagick 6) when it is installed. Only the first frame of an animated image is shown.
|
|
56
|
+
Without ImageMagick they raise `TerminalImage::UnsupportedFormat`, a subclass of `TerminalImage::UnsupportedTerminal`.
|
|
57
|
+
|
|
43
58
|
## Contributing
|
|
44
59
|
|
|
45
60
|
Bug reports and pull requests are welcome on GitHub at https://github.com/unhappychoice/terminal_image. This project is intended to be a safe, welcoming space for collaboration, and contributors are expected to adhere to the [Contributor Covenant](http://contributor-covenant.org) code of conduct.
|
|
@@ -51,3 +66,7 @@ The gem is available as open source under the terms of the [MIT License](https:/
|
|
|
51
66
|
## Code of Conduct
|
|
52
67
|
|
|
53
68
|
Everyone interacting in the TerminalImage project’s codebases, issue trackers, chat rooms and mailing lists is expected to follow the [code of conduct](https://github.com/unhappychoice/terminal_image/blob/master/CODE_OF_CONDUCT.md).
|
|
69
|
+
|
|
70
|
+
## Author
|
|
71
|
+
|
|
72
|
+
[@unhappychoice](https://unhappychoice.com)
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'chunky_png'
|
|
4
|
+
require 'io/console'
|
|
5
|
+
|
|
6
|
+
module TerminalImage
|
|
7
|
+
# Renders a PNG with 24-bit ANSI colors, two pixel rows per character cell.
|
|
8
|
+
module AnsiEncoder
|
|
9
|
+
UPPER_HALF_BLOCK = '▀'
|
|
10
|
+
LOWER_HALF_BLOCK = '▄'
|
|
11
|
+
DEFAULT_BACKGROUND = "\e[49m"
|
|
12
|
+
RESET = "\e[0m"
|
|
13
|
+
DEFAULT_WIDTH = 80
|
|
14
|
+
MIN_VISIBLE_ALPHA = 128
|
|
15
|
+
MAX_PIXELS = 50_000_000
|
|
16
|
+
|
|
17
|
+
class << self
|
|
18
|
+
def encode(file, width: terminal_width)
|
|
19
|
+
ensure_within_pixel_limit(*png_dimensions(file))
|
|
20
|
+
file.rewind
|
|
21
|
+
image = fit_to_width(ChunkyPNG::Image.from_io(file), width)
|
|
22
|
+
row_pairs(image).map { |top, bottom| encode_line(top, bottom) }.join("\n")
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def terminal_width
|
|
26
|
+
[console_width, ENV.fetch('COLUMNS', nil).to_i].compact.find(&:positive?) || DEFAULT_WIDTH
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def ensure_within_pixel_limit(width, height)
|
|
30
|
+
return if width.to_i * height.to_i <= MAX_PIXELS
|
|
31
|
+
|
|
32
|
+
raise ImageTooLarge, "#{width}x#{height} exceeds the #{MAX_PIXELS} pixel limit of the ANSI fallback"
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
private
|
|
36
|
+
|
|
37
|
+
def png_dimensions(file)
|
|
38
|
+
file.rewind
|
|
39
|
+
file.read(24).to_s.b.unpack('@16N2').map(&:to_i)
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
def console_width
|
|
43
|
+
IO.console&.winsize&.last
|
|
44
|
+
rescue SystemCallError
|
|
45
|
+
nil
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
def fit_to_width(image, width)
|
|
49
|
+
return image if image.width <= width
|
|
50
|
+
|
|
51
|
+
height = [(image.height * width / image.width.to_f).round, 1].max
|
|
52
|
+
image.resample_nearest_neighbor(width, height)
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
def row_pairs(image)
|
|
56
|
+
(0...image.height).step(2).map do |y|
|
|
57
|
+
[image.row(y), y + 1 < image.height ? image.row(y + 1) : []]
|
|
58
|
+
end
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
def encode_line(top, bottom)
|
|
62
|
+
top.each_with_index.map { |color, x| encode_cell(color, bottom[x]) }.join + RESET
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
def encode_cell(top, bottom)
|
|
66
|
+
case [visible?(top), visible?(bottom)]
|
|
67
|
+
when [true, true] then "#{foreground(top)}#{background(bottom)}#{UPPER_HALF_BLOCK}"
|
|
68
|
+
when [true, false] then "#{foreground(top)}#{DEFAULT_BACKGROUND}#{UPPER_HALF_BLOCK}"
|
|
69
|
+
when [false, true] then "#{foreground(bottom)}#{DEFAULT_BACKGROUND}#{LOWER_HALF_BLOCK}"
|
|
70
|
+
else "#{DEFAULT_BACKGROUND} "
|
|
71
|
+
end
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
def visible?(color)
|
|
75
|
+
!color.nil? && ChunkyPNG::Color.a(color) >= MIN_VISIBLE_ALPHA
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
def foreground(color)
|
|
79
|
+
"\e[38;2;#{rgb(color)}m"
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
def background(color)
|
|
83
|
+
"\e[48;2;#{rgb(color)}m"
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
def rgb(color)
|
|
87
|
+
ChunkyPNG::Color.to_truecolor_bytes(color).join(';')
|
|
88
|
+
end
|
|
89
|
+
end
|
|
90
|
+
end
|
|
91
|
+
end
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'open3'
|
|
4
|
+
require 'stringio'
|
|
5
|
+
|
|
6
|
+
module TerminalImage
|
|
7
|
+
# Converts images to PNG with the ImageMagick CLI, reading the first frame only.
|
|
8
|
+
module PngConverter
|
|
9
|
+
COMMANDS = %w[magick convert].freeze
|
|
10
|
+
RESOURCE_LIMITS = %w[-limit memory 256MiB -limit disk 1GiB].freeze
|
|
11
|
+
|
|
12
|
+
class << self
|
|
13
|
+
def convert(file, command:)
|
|
14
|
+
file.rewind
|
|
15
|
+
png, status = pipe([command, *RESOURCE_LIMITS, '-[0]', 'png:-'], file)
|
|
16
|
+
raise UnsupportedFormat, "#{command} could not convert the image to PNG" unless status.success?
|
|
17
|
+
|
|
18
|
+
StringIO.new(png)
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def image_magick?(command)
|
|
22
|
+
output, status = Open3.capture2e(command, '-version')
|
|
23
|
+
status.success? && output.include?('ImageMagick')
|
|
24
|
+
rescue SystemCallError
|
|
25
|
+
false
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
private
|
|
29
|
+
|
|
30
|
+
def pipe(args, input)
|
|
31
|
+
Open3.popen2(*args, err: File::NULL) do |stdin, stdout, wait|
|
|
32
|
+
[stdin, stdout].each(&:binmode)
|
|
33
|
+
writer = Thread.new { stream(input, stdin) }
|
|
34
|
+
png = stdout.read
|
|
35
|
+
writer.join
|
|
36
|
+
[png, wait.value]
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
def stream(input, stdin)
|
|
41
|
+
IO.copy_stream(input, stdin)
|
|
42
|
+
rescue Errno::EPIPE
|
|
43
|
+
nil
|
|
44
|
+
ensure
|
|
45
|
+
stdin.close
|
|
46
|
+
end
|
|
47
|
+
end
|
|
48
|
+
end
|
|
49
|
+
end
|
data/lib/terminal_image.rb
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
require 'terminal_image/version'
|
|
4
|
+
require 'terminal_image/ansi_encoder'
|
|
5
|
+
require 'terminal_image/png_converter'
|
|
4
6
|
require 'base64'
|
|
5
7
|
require 'fastimage'
|
|
6
8
|
require 'open-uri'
|
|
@@ -8,6 +10,8 @@ require 'tempfile'
|
|
|
8
10
|
|
|
9
11
|
module TerminalImage
|
|
10
12
|
class UnsupportedTerminal < StandardError; end
|
|
13
|
+
class UnsupportedFormat < UnsupportedTerminal; end
|
|
14
|
+
class ImageTooLarge < UnsupportedTerminal; end
|
|
11
15
|
|
|
12
16
|
class << self
|
|
13
17
|
def show_url(url)
|
|
@@ -34,8 +38,7 @@ module TerminalImage
|
|
|
34
38
|
elsif which 'img2sixel'
|
|
35
39
|
encode_for_libsixel(file)
|
|
36
40
|
else
|
|
37
|
-
|
|
38
|
-
raise UnsupportedTerminal, 'Unsupported terminal'
|
|
41
|
+
encode_for_ansi(file)
|
|
39
42
|
end
|
|
40
43
|
end
|
|
41
44
|
|
|
@@ -50,6 +53,29 @@ module TerminalImage
|
|
|
50
53
|
`img2sixel < #{file.path} 2>/dev/null`
|
|
51
54
|
end
|
|
52
55
|
|
|
56
|
+
def encode_for_ansi(file)
|
|
57
|
+
AnsiEncoder.encode(png_source(file))
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
def png_source(file)
|
|
61
|
+
type = FastImage.type(file)
|
|
62
|
+
return file if type == :png
|
|
63
|
+
|
|
64
|
+
command = image_magick_command
|
|
65
|
+
raise UnsupportedFormat, unsupported_format_message(type) unless command
|
|
66
|
+
|
|
67
|
+
AnsiEncoder.ensure_within_pixel_limit(*FastImage.size(file))
|
|
68
|
+
PngConverter.convert(file, command: command)
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
def image_magick_command
|
|
72
|
+
PngConverter::COMMANDS.find { |cmd| which(cmd) && PngConverter.image_magick?(cmd) }
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
def unsupported_format_message(type)
|
|
76
|
+
"Install ImageMagick to show #{type || 'non-PNG'} images without iTerm2 or libsixel"
|
|
77
|
+
end
|
|
78
|
+
|
|
53
79
|
def which(cmd)
|
|
54
80
|
exts = ENV['PATHEXT'] ? ENV['PATHEXT'].split(';') : ['']
|
|
55
81
|
ENV['PATH'].split(File::PATH_SEPARATOR).each do |path|
|
data/terminal_image.gemspec
CHANGED
|
@@ -7,7 +7,7 @@ require 'terminal_image/version'
|
|
|
7
7
|
Gem::Specification.new do |spec|
|
|
8
8
|
spec.name = 'terminal_image'
|
|
9
9
|
spec.version = TerminalImage::VERSION
|
|
10
|
-
spec.required_ruby_version = '>= 3.
|
|
10
|
+
spec.required_ruby_version = '>= 3.3'
|
|
11
11
|
spec.authors = ['Yuji Ueki']
|
|
12
12
|
spec.email = ['unhappychoice@gmail.com']
|
|
13
13
|
spec.summary = 'Display images on terminal'
|
|
@@ -30,15 +30,6 @@ Gem::Specification.new do |spec|
|
|
|
30
30
|
spec.require_paths = ['lib']
|
|
31
31
|
|
|
32
32
|
spec.add_dependency 'base64', '~> 0.3'
|
|
33
|
+
spec.add_dependency 'chunky_png', '~> 1.4'
|
|
33
34
|
spec.add_dependency 'fastimage'
|
|
34
|
-
|
|
35
|
-
spec.add_development_dependency 'bundler', '~> 2.0'
|
|
36
|
-
spec.add_development_dependency 'codecov'
|
|
37
|
-
spec.add_development_dependency 'guard'
|
|
38
|
-
spec.add_development_dependency 'guard-rspec'
|
|
39
|
-
spec.add_development_dependency 'guard-rubocop'
|
|
40
|
-
spec.add_development_dependency 'rake', '~> 13.0'
|
|
41
|
-
spec.add_development_dependency 'rspec', '~> 3.0'
|
|
42
|
-
spec.add_development_dependency 'rubocop'
|
|
43
|
-
spec.add_development_dependency 'simplecov'
|
|
44
35
|
end
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: terminal_image
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 1.
|
|
4
|
+
version: 1.1.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Yuji Ueki
|
|
@@ -24,139 +24,27 @@ dependencies:
|
|
|
24
24
|
- !ruby/object:Gem::Version
|
|
25
25
|
version: '0.3'
|
|
26
26
|
- !ruby/object:Gem::Dependency
|
|
27
|
-
name:
|
|
27
|
+
name: chunky_png
|
|
28
28
|
requirement: !ruby/object:Gem::Requirement
|
|
29
|
-
requirements:
|
|
30
|
-
- - ">="
|
|
31
|
-
- !ruby/object:Gem::Version
|
|
32
|
-
version: '0'
|
|
33
|
-
type: :runtime
|
|
34
|
-
prerelease: false
|
|
35
|
-
version_requirements: !ruby/object:Gem::Requirement
|
|
36
|
-
requirements:
|
|
37
|
-
- - ">="
|
|
38
|
-
- !ruby/object:Gem::Version
|
|
39
|
-
version: '0'
|
|
40
|
-
- !ruby/object:Gem::Dependency
|
|
41
|
-
name: bundler
|
|
42
|
-
requirement: !ruby/object:Gem::Requirement
|
|
43
|
-
requirements:
|
|
44
|
-
- - "~>"
|
|
45
|
-
- !ruby/object:Gem::Version
|
|
46
|
-
version: '2.0'
|
|
47
|
-
type: :development
|
|
48
|
-
prerelease: false
|
|
49
|
-
version_requirements: !ruby/object:Gem::Requirement
|
|
50
29
|
requirements:
|
|
51
30
|
- - "~>"
|
|
52
31
|
- !ruby/object:Gem::Version
|
|
53
|
-
version: '
|
|
54
|
-
|
|
55
|
-
name: codecov
|
|
56
|
-
requirement: !ruby/object:Gem::Requirement
|
|
57
|
-
requirements:
|
|
58
|
-
- - ">="
|
|
59
|
-
- !ruby/object:Gem::Version
|
|
60
|
-
version: '0'
|
|
61
|
-
type: :development
|
|
62
|
-
prerelease: false
|
|
63
|
-
version_requirements: !ruby/object:Gem::Requirement
|
|
64
|
-
requirements:
|
|
65
|
-
- - ">="
|
|
66
|
-
- !ruby/object:Gem::Version
|
|
67
|
-
version: '0'
|
|
68
|
-
- !ruby/object:Gem::Dependency
|
|
69
|
-
name: guard
|
|
70
|
-
requirement: !ruby/object:Gem::Requirement
|
|
71
|
-
requirements:
|
|
72
|
-
- - ">="
|
|
73
|
-
- !ruby/object:Gem::Version
|
|
74
|
-
version: '0'
|
|
75
|
-
type: :development
|
|
76
|
-
prerelease: false
|
|
77
|
-
version_requirements: !ruby/object:Gem::Requirement
|
|
78
|
-
requirements:
|
|
79
|
-
- - ">="
|
|
80
|
-
- !ruby/object:Gem::Version
|
|
81
|
-
version: '0'
|
|
82
|
-
- !ruby/object:Gem::Dependency
|
|
83
|
-
name: guard-rspec
|
|
84
|
-
requirement: !ruby/object:Gem::Requirement
|
|
85
|
-
requirements:
|
|
86
|
-
- - ">="
|
|
87
|
-
- !ruby/object:Gem::Version
|
|
88
|
-
version: '0'
|
|
89
|
-
type: :development
|
|
90
|
-
prerelease: false
|
|
91
|
-
version_requirements: !ruby/object:Gem::Requirement
|
|
92
|
-
requirements:
|
|
93
|
-
- - ">="
|
|
94
|
-
- !ruby/object:Gem::Version
|
|
95
|
-
version: '0'
|
|
96
|
-
- !ruby/object:Gem::Dependency
|
|
97
|
-
name: guard-rubocop
|
|
98
|
-
requirement: !ruby/object:Gem::Requirement
|
|
99
|
-
requirements:
|
|
100
|
-
- - ">="
|
|
101
|
-
- !ruby/object:Gem::Version
|
|
102
|
-
version: '0'
|
|
103
|
-
type: :development
|
|
104
|
-
prerelease: false
|
|
105
|
-
version_requirements: !ruby/object:Gem::Requirement
|
|
106
|
-
requirements:
|
|
107
|
-
- - ">="
|
|
108
|
-
- !ruby/object:Gem::Version
|
|
109
|
-
version: '0'
|
|
110
|
-
- !ruby/object:Gem::Dependency
|
|
111
|
-
name: rake
|
|
112
|
-
requirement: !ruby/object:Gem::Requirement
|
|
113
|
-
requirements:
|
|
114
|
-
- - "~>"
|
|
115
|
-
- !ruby/object:Gem::Version
|
|
116
|
-
version: '13.0'
|
|
117
|
-
type: :development
|
|
118
|
-
prerelease: false
|
|
119
|
-
version_requirements: !ruby/object:Gem::Requirement
|
|
120
|
-
requirements:
|
|
121
|
-
- - "~>"
|
|
122
|
-
- !ruby/object:Gem::Version
|
|
123
|
-
version: '13.0'
|
|
124
|
-
- !ruby/object:Gem::Dependency
|
|
125
|
-
name: rspec
|
|
126
|
-
requirement: !ruby/object:Gem::Requirement
|
|
127
|
-
requirements:
|
|
128
|
-
- - "~>"
|
|
129
|
-
- !ruby/object:Gem::Version
|
|
130
|
-
version: '3.0'
|
|
131
|
-
type: :development
|
|
32
|
+
version: '1.4'
|
|
33
|
+
type: :runtime
|
|
132
34
|
prerelease: false
|
|
133
35
|
version_requirements: !ruby/object:Gem::Requirement
|
|
134
36
|
requirements:
|
|
135
37
|
- - "~>"
|
|
136
38
|
- !ruby/object:Gem::Version
|
|
137
|
-
version: '
|
|
138
|
-
- !ruby/object:Gem::Dependency
|
|
139
|
-
name: rubocop
|
|
140
|
-
requirement: !ruby/object:Gem::Requirement
|
|
141
|
-
requirements:
|
|
142
|
-
- - ">="
|
|
143
|
-
- !ruby/object:Gem::Version
|
|
144
|
-
version: '0'
|
|
145
|
-
type: :development
|
|
146
|
-
prerelease: false
|
|
147
|
-
version_requirements: !ruby/object:Gem::Requirement
|
|
148
|
-
requirements:
|
|
149
|
-
- - ">="
|
|
150
|
-
- !ruby/object:Gem::Version
|
|
151
|
-
version: '0'
|
|
39
|
+
version: '1.4'
|
|
152
40
|
- !ruby/object:Gem::Dependency
|
|
153
|
-
name:
|
|
41
|
+
name: fastimage
|
|
154
42
|
requirement: !ruby/object:Gem::Requirement
|
|
155
43
|
requirements:
|
|
156
44
|
- - ">="
|
|
157
45
|
- !ruby/object:Gem::Version
|
|
158
46
|
version: '0'
|
|
159
|
-
type: :
|
|
47
|
+
type: :runtime
|
|
160
48
|
prerelease: false
|
|
161
49
|
version_requirements: !ruby/object:Gem::Requirement
|
|
162
50
|
requirements:
|
|
@@ -170,9 +58,11 @@ executables: []
|
|
|
170
58
|
extensions: []
|
|
171
59
|
extra_rdoc_files: []
|
|
172
60
|
files:
|
|
61
|
+
- ".github/CODEOWNERS"
|
|
173
62
|
- ".github/dependabot.yml"
|
|
174
63
|
- ".github/workflows/release.yaml"
|
|
175
64
|
- ".github/workflows/test.yaml"
|
|
65
|
+
- ".gitignore"
|
|
176
66
|
- ".rspec"
|
|
177
67
|
- ".rubocop.yml"
|
|
178
68
|
- ".ruby-version"
|
|
@@ -188,6 +78,8 @@ files:
|
|
|
188
78
|
- images/example.png
|
|
189
79
|
- images/sample.png
|
|
190
80
|
- lib/terminal_image.rb
|
|
81
|
+
- lib/terminal_image/ansi_encoder.rb
|
|
82
|
+
- lib/terminal_image/png_converter.rb
|
|
191
83
|
- lib/terminal_image/version.rb
|
|
192
84
|
- terminal_image.gemspec
|
|
193
85
|
homepage: https://github.com/unhappychoice/terminal_image
|
|
@@ -205,7 +97,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
|
205
97
|
requirements:
|
|
206
98
|
- - ">="
|
|
207
99
|
- !ruby/object:Gem::Version
|
|
208
|
-
version: '3.
|
|
100
|
+
version: '3.3'
|
|
209
101
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
210
102
|
requirements:
|
|
211
103
|
- - ">="
|