diff --git a/README.md b/README.md index 5144595..f180beb 100644 --- a/README.md +++ b/README.md @@ -42,3 +42,4 @@ Extensions | Tools .tar.gz .tgz | [tar_gz](http://www.gnu.org/software/tar/manual/tar.html) .tar.bz2 | [tar_bz2](http://www.gnu.org/software/tar/manual/tar.html) .a .so | [nm](https://linux.die.net/man/1/nm) +.png .jpg .gif .bmp .ppm .tiff .tga | [pil](http://python-pillow.github.io/) • [pil_half](http://python-pillow.github.io/) diff --git a/fill3.py b/fill3.py index 144b7e0..4299505 100644 --- a/fill3.py +++ b/fill3.py @@ -401,6 +401,15 @@ class Placeholder: return self.widget.appearance(dimensions) +class Fixed: + + def __init__(self, appearance): + self.appearance_min_ = appearance + + def appearance_min(self): + return self.appearance_min_ + + ########################## diff --git a/golden-files/input/circle.bmp b/golden-files/input/circle.bmp new file mode 100644 index 0000000..e3c44ac Binary files /dev/null and b/golden-files/input/circle.bmp differ diff --git a/golden-files/input/circle.gif b/golden-files/input/circle.gif new file mode 100644 index 0000000..a45c29c Binary files /dev/null and b/golden-files/input/circle.gif differ diff --git a/golden-files/input/circle.jpg b/golden-files/input/circle.jpg new file mode 100644 index 0000000..881ef2a Binary files /dev/null and b/golden-files/input/circle.jpg differ diff --git a/golden-files/input/circle.png b/golden-files/input/circle.png new file mode 100644 index 0000000..a007d54 Binary files /dev/null and b/golden-files/input/circle.png differ diff --git a/golden-files/input/circle.ppm b/golden-files/input/circle.ppm new file mode 100644 index 0000000..b640d1b Binary files /dev/null and b/golden-files/input/circle.ppm differ diff --git a/golden-files/input/circle.tga b/golden-files/input/circle.tga new file mode 100644 index 0000000..124c349 Binary files /dev/null and b/golden-files/input/circle.tga differ diff --git a/golden-files/input/circle.tiff b/golden-files/input/circle.tiff new file mode 100644 index 0000000..a3c7648 Binary files /dev/null and b/golden-files/input/circle.tiff differ diff --git a/golden-files/results/pil-circle_bmp b/golden-files/results/pil-circle_bmp new file mode 100644 index 0000000..58aecf0 --- /dev/null +++ b/golden-files/results/pil-circle_bmp @@ -0,0 +1,16 @@ +(B + (B (B + (B (B (B (B + (B (B (B + (B (B (B (B + (B (B (B (B (B + (B (B + (B (B + (B (B (B + (B (B (B (B + (B (B (B (B + (B (B (B (B + (B (B (B (B (B + (B (B (B (B (B (B + (B (B (B (B + (B \ No newline at end of file diff --git a/golden-files/results/pil-circle_gif b/golden-files/results/pil-circle_gif new file mode 100644 index 0000000..c65b9f5 --- /dev/null +++ b/golden-files/results/pil-circle_gif @@ -0,0 +1,16 @@ +(B + + (B (B + (B (B + (B (B + (B (B + (B (B + (B (B + (B (B + (B (B + (B (B + (B (B + (B (B + (B (B + (B (B + (B \ No newline at end of file diff --git a/golden-files/results/pil-circle_jpg b/golden-files/results/pil-circle_jpg new file mode 100644 index 0000000..4fe62f3 --- /dev/null +++ b/golden-files/results/pil-circle_jpg @@ -0,0 +1,16 @@ +(B (B (B (B (B (B (B (B (B +(B (B (B (B (B (B (B (B (B (B (B (B + (B (B (B (B (B (B (B (B (B (B + (B (B (B (B (B (B (B (B (B (B (B +(B (B (B (B (B (B (B (B (B (B (B (B (B (B + (B (B (B (B (B (B (B (B (B (B (B (B (B +(B (B (B (B (B (B (B (B (B (B (B (B (B (B + (B (B (B (B (B (B (B (B (B +(B (B (B (B (B (B (B (B (B (B (B (B (B (B + (B (B (B (B (B (B (B (B (B (B (B (B (B (B (B +(B (B (B (B (B (B (B (B (B (B (B (B (B (B +(B (B (B (B (B (B (B (B (B (B (B +(B (B (B (B (B (B (B (B (B (B (B (B (B (B + (B (B (B (B (B (B (B (B +(B (B (B (B (B (B (B (B (B (B (B (B (B (B (B (B + (B (B (B (B (B (B (B (B (B (B (B (B \ No newline at end of file diff --git a/golden-files/results/pil-circle_png b/golden-files/results/pil-circle_png new file mode 100644 index 0000000..58aecf0 --- /dev/null +++ b/golden-files/results/pil-circle_png @@ -0,0 +1,16 @@ +(B + (B (B + (B (B (B (B + (B (B (B + (B (B (B (B + (B (B (B (B (B + (B (B + (B (B + (B (B (B + (B (B (B (B + (B (B (B (B + (B (B (B (B + (B (B (B (B (B + (B (B (B (B (B (B + (B (B (B (B + (B \ No newline at end of file diff --git a/golden-files/results/pil-circle_ppm b/golden-files/results/pil-circle_ppm new file mode 100644 index 0000000..58aecf0 --- /dev/null +++ b/golden-files/results/pil-circle_ppm @@ -0,0 +1,16 @@ +(B + (B (B + (B (B (B (B + (B (B (B + (B (B (B (B + (B (B (B (B (B + (B (B + (B (B + (B (B (B + (B (B (B (B + (B (B (B (B + (B (B (B (B + (B (B (B (B (B + (B (B (B (B (B (B + (B (B (B (B + (B \ No newline at end of file diff --git a/golden-files/results/pil-circle_tga b/golden-files/results/pil-circle_tga new file mode 100644 index 0000000..58aecf0 --- /dev/null +++ b/golden-files/results/pil-circle_tga @@ -0,0 +1,16 @@ +(B + (B (B + (B (B (B (B + (B (B (B + (B (B (B (B + (B (B (B (B (B + (B (B + (B (B + (B (B (B + (B (B (B (B + (B (B (B (B + (B (B (B (B + (B (B (B (B (B + (B (B (B (B (B (B + (B (B (B (B + (B \ No newline at end of file diff --git a/golden-files/results/pil-circle_tiff b/golden-files/results/pil-circle_tiff new file mode 100644 index 0000000..58aecf0 --- /dev/null +++ b/golden-files/results/pil-circle_tiff @@ -0,0 +1,16 @@ +(B + (B (B + (B (B (B (B + (B (B (B + (B (B (B (B + (B (B (B (B (B + (B (B + (B (B + (B (B (B + (B (B (B (B + (B (B (B (B + (B (B (B (B + (B (B (B (B (B + (B (B (B (B (B (B + (B (B (B (B + (B \ No newline at end of file diff --git a/golden-files/results/pil_half-circle_png b/golden-files/results/pil_half-circle_png new file mode 100644 index 0000000..4caf8de --- /dev/null +++ b/golden-files/results/pil_half-circle_png @@ -0,0 +1,8 @@ +(B▀▀▀▀▀▀(B▀▀▀▀(B▀▀▀▀▀▀(B +(B▀▀▀(B▀(B▀▀(B▀▀▀▀(B▀▀(B▀(B▀▀▀(B +(B▀▀(B▀(B▀▀▀▀▀▀▀▀▀▀(B▀(B▀(B▀(B +(B▀(B▀▀▀▀▀▀▀▀▀▀▀▀▀▀(B▀(B +(B▀(B▀(B▀▀▀▀▀▀▀▀▀▀▀▀(B▀(B▀(B +(B▀(B▀(B▀(B▀▀▀▀▀▀▀▀▀▀(B▀(B▀(B▀(B +(B▀▀▀(B▀(B▀(B▀▀▀▀▀▀(B▀(B▀(B▀(B▀▀(B +(B▀▀▀▀▀(B▀(B▀(B▀▀▀▀(B▀▀▀▀▀(B \ No newline at end of file diff --git a/termstr.py b/termstr.py index f7d57ff..4f7c614 100644 --- a/termstr.py +++ b/termstr.py @@ -3,6 +3,7 @@ # Licensed under the Artistic License 2.0. import collections +import itertools import os import weakref @@ -102,10 +103,7 @@ class CharStyle: def _join_lists(lists): - result = [] - for list_ in lists: - result.extend(list_) - return result + return list(itertools.chain.from_iterable(lists)) class TermStr(collections.UserString): diff --git a/tools.py b/tools.py index 3ba199d..4817898 100644 --- a/tools.py +++ b/tools.py @@ -22,6 +22,7 @@ import tempfile import time import traceback +import PIL.Image import pygments import pygments.lexers import pygments.styles @@ -646,6 +647,59 @@ php5_syntax.dependencies = {"php"} php5_syntax.url = "https://en.wikipedia.org/wiki/PHP" +def _pil_pixels(pil_image): + data = list(pil_image.getdata()) + width = pil_image.width + return [data[row_index*width:(row_index+1)*width] + for row_index in range(pil_image.height)] + + +MAX_IMAGE_SIZE = 80 + + +def _resize_image(image, new_width): + scale = new_width / image.width + return image.resize((int(image.width * scale), int(image.height * scale)), + PIL.Image.ANTIALIAS) + + +def pil(path): + with open(path, "rb") as image_file: + with PIL.Image.open(image_file).convert("RGB") as image: + if image.width > (MAX_IMAGE_SIZE // 2): + image = _resize_image(image, MAX_IMAGE_SIZE // 2) + text = " " * 2 * image.width + result = [] + for row in _pil_pixels(image): + row_style = [] + for pixel in row: + style = termstr.CharStyle(bg_color=pixel) + row_style.extend([style, style]) + result.append(termstr.TermStr(text, tuple(row_style))) + return Status.normal, fill3.Fixed(result) +pil.dependencies = {"python3-pil"} +pil.url = "python3-pil" + + +def pil_half(path): + with open(path, "rb") as image_file: + with PIL.Image.open(image_file).convert("RGB") as image: + if image.width > MAX_IMAGE_SIZE: + image = _resize_image(image, MAX_IMAGE_SIZE) + text = "▀" * image.width + rows = _pil_pixels(image) + if image.height % 2 == 1: + rows.append([None] * image.width) + result = fill3.Fixed([ + termstr.TermStr(text, tuple(termstr.CharStyle( + fg_color=top_pixel, bg_color=bottom_pixel) + for top_pixel, bottom_pixel in zip(rows[index], rows[index+1]))) + for index in range(0, image.height, 2)]) + return Status.normal, result +pil_half.dependencies = {"python3-pil"} +pil_half.url = "python3-pil" + + ############################# @@ -768,6 +822,9 @@ def _generic_tools(): return [contents, metadata] +IMAGE_EXTENSIONS = ["png", "jpg", "gif", "bmp", "ppm", "tiff", "tga"] + + TOOLS_FOR_EXTENSIONS = \ [ (["py"], [python_syntax, python_unittests, pydoc, mypy, python_coverage, @@ -789,6 +846,7 @@ TOOLS_FOR_EXTENSIONS = \ (["tar.gz", "tgz"], [tar_gz]), (["tar.bz2"], [tar_bz2]), (["a", "so"], [nm]), + (IMAGE_EXTENSIONS, [pil, pil_half]) ] diff --git a/tools_test.py b/tools_test.py index 1068bd4..69d8a6f 100755 --- a/tools_test.py +++ b/tools_test.py @@ -212,6 +212,14 @@ class ToolsTestCase(unittest.TestCase): def test_php5_syntax(self): self._test_tool(tools.php5_syntax, [("root.php", tools.Status.ok)]) + def test_pil(self): + for extension in tools.IMAGE_EXTENSIONS: + self._test_tool(tools.pil, [("circle." + extension, + tools.Status.normal)]) + + def test_pil_half(self): + self._test_tool(tools.pil_half, [("circle.png", tools.Status.normal)]) + class LruCacheWithEvictionTestCase(unittest.TestCase):