1 2 3 4 5 6 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 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112
|
from __future__ import annotations
import sys
from plumbum import colors
from .. import cli
from .termsize import get_terminal_size
class Image:
__slots__ = "size char_ratio".split()
def __init__(self, size=None, char_ratio=2.45):
self.size = size
self.char_ratio = char_ratio
def best_aspect(self, orig, term):
"""Select a best possible size matching the original aspect ratio.
Size is width, height.
The char_ratio option gives the height of each char with respect
to its width, zero for no effect."""
if not self.char_ratio: # Don't use if char ratio is 0
return term
orig_ratio = orig[0] / orig[1] / self.char_ratio
if int(term[1] / orig_ratio) <= term[0]:
return int(term[1] / orig_ratio), term[1]
return term[0], int(term[0] * orig_ratio)
def show(self, filename, double=False):
"""Display an image on the command line. Can select a size or show in double resolution."""
import PIL.Image
return (
self.show_pil_double(PIL.Image.open(filename))
if double
else self.show_pil(PIL.Image.open(filename))
)
def _init_size(self, im):
"""Return the expected image size"""
if self.size is None:
term_size = get_terminal_size()
return self.best_aspect(im.size, term_size)
return self.size
def show_pil(self, im):
"Standard show routine"
size = self._init_size(im)
new_im = im.resize(size).convert("RGB")
for y in range(size[1]):
for x in range(size[0] - 1):
pix = new_im.getpixel((x, y))
sys.stdout.write(colors.bg.rgb(*pix) + " ") # '\u2588'
sys.stdout.write(colors.reset + " \n")
sys.stdout.write(colors.reset + "\n")
sys.stdout.flush()
def show_pil_double(self, im):
"Show double resolution on some fonts"
size = self._init_size(im)
size = (size[0], size[1] * 2)
new_im = im.resize(size).convert("RGB")
for y in range(size[1] // 2):
for x in range(size[0] - 1):
pix = new_im.getpixel((x, y * 2))
pixl = new_im.getpixel((x, y * 2 + 1))
sys.stdout.write(
(colors.bg.rgb(*pixl) & colors.fg.rgb(*pix)) + "\u2580"
)
sys.stdout.write(colors.reset + " \n")
sys.stdout.write(colors.reset + "\n")
sys.stdout.flush()
class ShowImageApp(cli.Application):
"Display an image on the terminal"
double = cli.Flag(
["-d", "--double"], help="Double resolution (looks good only with some fonts)"
)
@cli.switch(["-c", "--colors"], cli.Range(1, 4), help="Level of color, 1-4")
def colors_set(self, n): # pylint: disable=no-self-use
colors.use_color = n
size = cli.SwitchAttr(["-s", "--size"], help="Size, should be in the form 100x150")
ratio = cli.SwitchAttr(
["--ratio"], float, default=2.45, help="Aspect ratio of the font"
)
@cli.positional(cli.ExistingFile)
def main(self, filename):
size = None
if self.size:
size = map(int, self.size.split("x"))
Image(size, self.ratio).show(filename, self.double)
if __name__ == "__main__":
ShowImageApp.run()
|