├── demo ├── after.png ├── after2.png ├── before.png └── before2.jpg ├── versions.cfg ├── LICENSE ├── setup.py ├── .gitignore ├── img2html ├── __init__.py └── converter.py └── README.md /demo/after.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xlzd/img2html/HEAD/demo/after.png -------------------------------------------------------------------------------- /demo/after2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xlzd/img2html/HEAD/demo/after2.png -------------------------------------------------------------------------------- /demo/before.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xlzd/img2html/HEAD/demo/before.png -------------------------------------------------------------------------------- /demo/before2.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xlzd/img2html/HEAD/demo/before2.jpg -------------------------------------------------------------------------------- /versions.cfg: -------------------------------------------------------------------------------- 1 | [versions] 2 | 3 | # Added by buildout at 2017-04-02 17:21:48.412750 4 | zc.recipe.egg = 2.0.3 5 | 6 | # Added by buildout at 2018-04-16 19:17:56.096710 7 | Jinja2 = 2.10 8 | MarkupSafe = 1.0 9 | Pillow = 5.1.0 10 | six = 1.11.0 11 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE 2 | Version 2, December 2004 3 | 4 | Copyright (C) 2004 Sam Hocevar 5 | 6 | Everyone is permitted to copy and distribute verbatim or modified 7 | copies of this license document, and changing it is allowed as long 8 | as the name is changed. 9 | 10 | DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE 11 | TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION 12 | 13 | 0. You just DO WHAT THE FUCK YOU WANT TO. -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # coding: utf-8 3 | 4 | from setuptools import setup 5 | 6 | setup( 7 | name='img2html', 8 | version='0.0.7', 9 | author='xlzd', 10 | author_email='i@xlzd.me', 11 | description='Convert image to HTML', 12 | url='https://github.com/xlzd/img2html', 13 | license='WTFPL', 14 | packages=['img2html'], 15 | install_requires=[ 16 | 'jinja2', 17 | 'pillow' 18 | ], 19 | entry_points={ 20 | 'console_scripts': [ 21 | 'img2html=img2html:main' 22 | ] 23 | } 24 | ) 25 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | *$py.class 5 | 6 | # C extensions 7 | *.so 8 | 9 | # Distribution / packaging 10 | .Python 11 | env/ 12 | build/ 13 | develop-eggs/ 14 | dist/ 15 | downloads/ 16 | eggs/ 17 | .eggs/ 18 | lib/ 19 | lib64/ 20 | parts/ 21 | sdist/ 22 | var/ 23 | *.egg-info/ 24 | .installed.cfg 25 | *.egg 26 | 27 | # PyInstaller 28 | # Usually these files are written by a python script from a template 29 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 30 | *.manifest 31 | *.spec 32 | 33 | # Installer logs 34 | pip-log.txt 35 | pip-delete-this-directory.txt 36 | 37 | # Unit test / coverage reports 38 | htmlcov/ 39 | .tox/ 40 | .coverage 41 | .coverage.* 42 | .cache 43 | nosetests.xml 44 | coverage.xml 45 | *,cover 46 | .hypothesis/ 47 | 48 | # Translations 49 | *.mo 50 | *.pot 51 | 52 | # Django stuff: 53 | *.log 54 | local_settings.py 55 | 56 | # Flask stuff: 57 | instance/ 58 | .webassets-cache 59 | 60 | # Scrapy stuff: 61 | .scrapy 62 | 63 | # Sphinx documentation 64 | docs/_build/ 65 | 66 | # PyBuilder 67 | target/ 68 | 69 | # IPython Notebook 70 | .ipynb_checkpoints 71 | 72 | # pyenv 73 | .python-version 74 | 75 | # celery beat schedule file 76 | celerybeat-schedule 77 | 78 | # dotenv 79 | .env 80 | 81 | # virtualenv 82 | venv/ 83 | ENV/ 84 | 85 | # Spyder project settings 86 | .spyderproject 87 | 88 | # Rope project settings 89 | .ropeproject 90 | 91 | bin/ 92 | .idea/ -------------------------------------------------------------------------------- /img2html/__init__.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # encoding=utf-8 3 | 4 | from __future__ import print_function, unicode_literals 5 | 6 | import argparse 7 | import codecs 8 | 9 | from img2html.converter import Img2HTMLConverter 10 | 11 | 12 | def main(): 13 | parser = argparse.ArgumentParser(description='img2html : Convert image to HTML') 14 | parser.add_argument('-b', '--background', default='000000', metavar='#RRGGBB', 15 | help='background color (#RRGGBB format)') 16 | parser.add_argument('-s', '--size', default=10, type=int, metavar='(4~30)', 17 | help='font size (int)') 18 | parser.add_argument('-c', '--char', default='爱', metavar='CHAR', 19 | help='characters') 20 | parser.add_argument('-t', '--title', default='img2html by xlzd', metavar='TITLE', 21 | help='html title') 22 | parser.add_argument('-f', '--font', default='monospace', metavar='FONT', 23 | help='html font') 24 | parser.add_argument('-i', '--in', metavar='IN', help='image to convert', required=True) 25 | parser.add_argument('-o', '--out', default=None, metavar='OUT', 26 | help='output file') 27 | 28 | args, text = parser.parse_known_args() 29 | 30 | converter = Img2HTMLConverter( 31 | font_size=args.size, 32 | char=args.char, 33 | background=args.background, 34 | title=args.title, 35 | font_family=args.font, 36 | ) 37 | html = converter.convert(getattr(args, 'in')) 38 | 39 | if args.out: 40 | with codecs.open(args.out, 'wb', encoding='utf-8') as fp: 41 | fp.write(html) 42 | else: 43 | print(html) 44 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # img2html: Convert a image to HTML [![Version][version-badge]][version-link] ![WTFPL License][license-badge] 2 | 3 | 4 | `img2html` 用于将图片转化为 HTML 页面,并没有什么实际作用,只是为了好玩。 5 | 6 | ``` 7 | ___ __ __ ___ 8 | __ /'___`\ /\ \ /\ \__ /\_ \ 9 | /\_\ ___ ___ __ /\_\ /\ \ \ \ \___ \ \ ,_\ ___ ___ \//\ \ 10 | \/\ \ /' __` __`\ /'_ `\ \/_/// /__ \ \ _ `\ \ \ \/ /' __` __`\ \ \ \ 11 | \ \ \ /\ \/\ \/\ \ /\ \L\ \ // /_\ \ \ \ \ \ \ \ \ \_ /\ \/\ \/\ \ \_\ \_ 12 | \ \_\\ \_\ \_\ \_\\ \____ \ /\______/ \ \_\ \_\ \ \__\\ \_\ \_\ \_\ /\____\ 13 | \/_/ \/_/\/_/\/_/ \/___L\ \ \/_____/ \/_/\/_/ \/__/ \/_/\/_/\/_/ \/____/ 14 | /\____/ 15 | \_/__/ 16 | ``` 17 | 18 | 19 | ### 示例 20 | 21 | 转换后的 HTML 页面: [https://xlzd.me/hide/img2html/](https://xlzd.me/hide/img2html/) 22 | 23 | 24 | 原始图片 | 转换后 25 | :-------------------------:|:-------------------------: 26 | ![](https://raw.githubusercontent.com/xlzd/img2html/master/demo/before2.jpg) | ![](https://raw.githubusercontent.com/xlzd/img2html/master/demo/after2.png) 27 | ![](https://raw.githubusercontent.com/xlzd/img2html/master/demo/before.png) | ![](https://raw.githubusercontent.com/xlzd/img2html/master/demo/after.png) 28 | 29 | ### 使用方式 30 | --- 31 | 32 | #### 命令行 33 | ``` 34 | usage: img2html [-h] [-b #RRGGBB] [-s 4~30] [-c CHAR] [-t TITLE] [-f FONT] -i 35 | IN [-o OUT] 36 | 37 | img2html : Convert image to HTML 38 | 39 | optional arguments: 40 | -b #RRGGBB, --background #RRGGBB background color (#RRGGBB format) 41 | -s (4~30), --size (4~30) font size (int) 42 | -c CHAR, --char CHAR characters 43 | -t TITLE, --title TITLE html title 44 | -f FONT, --font FONT html font 45 | -i IN, --in IN image to convert 46 | -o OUT, --out OUT output file 47 | ``` 48 | 49 | 50 | #### 代码调用 51 | 52 | ```Python 53 | from img2html.converter import Img2HTMLConverter 54 | 55 | converter = Img2HTMLConverter(*some config here*) 56 | html = converter.convert(*image_path*) 57 | 58 | # done, so easy. 59 | ``` 60 | 61 | 62 | ### 安装 63 | --- 64 | 65 | `img2html` 已经上传到了 [PYPI](https://pypi.python.org/pypi/img2html),所以最简单的安装方式就是使用 pip: 66 | 67 | ``` 68 | $ pip install img2html 69 | ``` 70 | 71 | 更新: 72 | 73 | ``` 74 | $ pip install img2html --upgrade 75 | ``` 76 | 77 | 78 | 当然,你也可以通过源码安装: 79 | 80 | ``` 81 | $ git clone https://github.com/xlzd/img2html.git 82 | $ cd img2html 83 | $ python setup.py install 84 | ``` 85 | 86 | 87 | ### License 88 | --- 89 | 90 | WTFPL ([here](https://github.com/xlzd/img2html/blob/master/LICENSE)) 91 | 92 | 93 | [version-badge]: https://img.shields.io/pypi/v/img2html.svg?label=version 94 | [version-link]: https://pypi.python.org/pypi/img2html/ 95 | [license-badge]: https://img.shields.io/badge/license-WTFPL-007EC7.svg 96 | -------------------------------------------------------------------------------- /img2html/converter.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # encoding=utf-8 3 | 4 | from __future__ import print_function, unicode_literals 5 | 6 | from collections import namedtuple 7 | from itertools import cycle 8 | 9 | import jinja2 10 | from PIL import Image 11 | 12 | Point = namedtuple('Point', ['x', 'y']) 13 | Pixel = namedtuple('Pixel', ['r', 'g', 'b']) 14 | RenderItem = namedtuple('RenderItem', ['color', 'char']) 15 | RenderGroup = list 16 | HTMLImage = list 17 | 18 | TEMPLATE = ''' 19 | 20 | 21 | 22 | {{ title }} 23 | 33 | 34 | 35 |
36 | {% for group in html_image %} 37 | {% for item in group %}{{ item.char }}{% endfor %} 38 |
39 | {% endfor %} 40 |
41 | 42 | ''' 43 | 44 | 45 | _c = cycle(r'/-\|') 46 | 47 | 48 | def _progress_callback(percent): 49 | if percent == 100: 50 | print('\rDone! ') 51 | else: 52 | import sys, time 53 | lca = getattr(_progress_callback, '_last_call_at', 0) 54 | if time.time() - lca > 0.1: 55 | _progress_callback._last_call_at = time.time() 56 | sys.stdout.write('\r{} progress: {:.2f}%'.format(_c.next(), percent)) 57 | sys.stdout.flush() 58 | 59 | 60 | class Img2HTMLConverter(object): 61 | def __init__(self, 62 | font_size=10, 63 | char='䦗', 64 | background='#000000', 65 | title='img2html by xlzd', 66 | font_family='monospace', 67 | progress_callback=None): 68 | self.font_size = font_size 69 | self.background = background 70 | self.title = title 71 | self.font_family = font_family 72 | if isinstance(char, str): 73 | char = char.decode('utf-8') 74 | self.char = cycle(char) 75 | self._prg_cb = progress_callback or _progress_callback 76 | 77 | def convert(self, source): 78 | image = Image.open(source) 79 | 80 | width, height = image.size 81 | row_blocks = int(round(float(width) / self.font_size)) 82 | col_blocks = int(round(float(height) / self.font_size)) 83 | 84 | html_image = HTMLImage() 85 | progress = 0.0 86 | step = 1. / (col_blocks * row_blocks) 87 | 88 | for col in xrange(col_blocks): 89 | render_group = RenderGroup() 90 | for row in xrange(row_blocks): 91 | pixels = [] 92 | for y in xrange(self.font_size): 93 | for x in xrange(self.font_size): 94 | point = Point(row * self.font_size + x, col * self.font_size + y) 95 | if point.x >= width or point.y >= height: 96 | continue 97 | pixels.append(Pixel(*image.getpixel(point)[:3])) 98 | average = self.get_average(pixels=pixels) 99 | color = self.rgb2hex(average) 100 | render_item = RenderItem(color=color, char=self.char.next()) 101 | render_group.append(render_item) 102 | 103 | progress += step 104 | self._prg_cb(progress * 100) 105 | 106 | html_image.append(render_group) 107 | 108 | self._prg_cb(100) 109 | return self.render(html_image) 110 | 111 | def render(self, html_image): 112 | template = jinja2.Template(TEMPLATE) 113 | return template.render( 114 | html_image=html_image, 115 | size=self.font_size, 116 | background=self.background, 117 | title=self.title, 118 | font_family=self.font_family, 119 | width=self.font_size * len(html_image[0]) * 2 120 | ) 121 | 122 | @staticmethod 123 | def rgb2hex(pixel): 124 | return '{:02x}{:02x}{:02x}'.format(*pixel) 125 | 126 | @staticmethod 127 | def get_average(pixels): 128 | r, g, b = 0, 0, 0 129 | for pixel in pixels: 130 | r += pixel.r 131 | g += pixel.g 132 | b += pixel.b 133 | base = float(len(pixels)) 134 | return Pixel( 135 | r=int(round(r / base)), 136 | g=int(round(g / base)), 137 | b=int(round(b / base)), 138 | ) 139 | --------------------------------------------------------------------------------