├── .gitignore ├── ASCIITransformer.py ├── LICENSE ├── README.md ├── RobotoMono-Bold.ttf ├── images ├── ascii-dog.txt ├── dog.jpg ├── lucy-ascii.png ├── lucy.jpg ├── tree-ascii.png └── tree.jpeg ├── main.py └── requirements.txt /.gitignore: -------------------------------------------------------------------------------- 1 | .idea 2 | .ipynb_checkpoints 3 | __pycache__ 4 | env 5 | .DS_Store 6 | -------------------------------------------------------------------------------- /ASCIITransformer.py: -------------------------------------------------------------------------------- 1 | import cv2 2 | import numpy as np 3 | from PIL import Image, ImageDraw, ImageFont 4 | 5 | SHADING_STYLES = { 6 | "long": "#$@B%8&WM#*oahkbdpqwmZO0QLCJUYXzcvunxrjft/\|()1{}[]?-_+~<>i!lI;:,^`'.", # Long default shading 7 | "normal": "@%#*+=-:. ", # Default shading 8 | "custom": "$. " # Custom shading (editable for experimental testing) 9 | } 10 | 11 | 12 | class ASCIITransformer: 13 | def __init__(self, fname="", height=25, shading_style="normal", dark_style=False, font_size=20): 14 | self.open(fname) 15 | self.new_height = height 16 | self.shading_style = SHADING_STYLES[shading_style] 17 | self.dark_style = dark_style 18 | if dark_style: 19 | self.shading_style = self.shading_style[::-1] 20 | self.font_size = font_size 21 | 22 | def __to_ascii(self, brightness): 23 | return self.shading_style[round((brightness / 255) * (len(self.shading_style) - 1))] 24 | 25 | def __stringify(self, img=None): 26 | if img is None: 27 | img = self.img 28 | # Calculate ascii image shape 29 | img_height, img_width = img.shape 30 | new_width = (2 * self.new_height * img_width) // img_height 31 | 32 | # Scale original image to ascii shape 33 | scaled_img = cv2.resize(img, dsize=(new_width, self.new_height), interpolation=cv2.INTER_CUBIC) 34 | 35 | # Create an ascii image 36 | ascii_arr = np.empty((self.new_height, new_width), dtype=str) 37 | for i in range(self.new_height): 38 | for j in range(new_width): 39 | ascii_arr[i, j] = self.__to_ascii(scaled_img[i, j]) 40 | 41 | # Save ascii array shape for image sizing later 42 | self.ascii_arr_shape = ascii_arr.shape 43 | 44 | # Save image to txt file 45 | lines = '\n'.join([''.join([''.join(char) for char in row]) for row in ascii_arr]) 46 | return lines 47 | 48 | def __imagify(self, img=None): 49 | # Get ascii string lines 50 | lines = self.__stringify(img) 51 | 52 | # Setup image 53 | bg_color, fg_color = ((0, 0), (255, 255)) if self.dark_style is True else ((255, 255), (0, 0)) 54 | img_size = (int(self.ascii_arr_shape[1] * self.font_size * 0.65), int(self.ascii_arr_shape[0] * self.font_size * 1.2)) 55 | img = Image.new('LA', img_size, bg_color) 56 | d = ImageDraw.Draw(img) 57 | font = ImageFont.truetype("RobotoMono-Bold.ttf", self.font_size) 58 | d.text((10, 10), lines, fill=fg_color, font=font, spacing=0.5) 59 | return img 60 | 61 | def open(self, fname): 62 | if fname != "": 63 | self.img = cv2.imread(fname, cv2.IMREAD_GRAYSCALE) 64 | 65 | def transform_to_txt(self, fname="ascii-img.txt"): 66 | ascii_lines = self.__stringify() 67 | file = open(fname, "w") 68 | file.writelines(ascii_lines) 69 | 70 | def transform_to_img(self, fname="ascii-img.png"): 71 | img = self.__imagify() 72 | img.save(fname) 73 | 74 | def transform_to_video(self): 75 | vid = cv2.VideoCapture(0) 76 | print("HOW TO USE:\n" 77 | "* Press \'q\' at any time to quit video\n" 78 | "* Press keys 1-3 to adjust video quality where 1=lowest 3=highest\n" 79 | "* Toggle dark mode by pressing \"d\"") 80 | while (True): 81 | ret, frame = vid.read() 82 | grayscale = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY) 83 | ascii_img = np.asarray(self.__imagify(grayscale))[:, :, :1] 84 | cv2.imshow('frame', ascii_img) 85 | 86 | key = cv2.waitKey(1) 87 | if key == ord('q'): 88 | break 89 | elif key == ord('n'): 90 | self.shading_style = "normal" 91 | elif key == ord('l'): 92 | self.shading_style = "long" 93 | elif key == ord('c'): 94 | self.shading_style = "custom" 95 | elif key == ord('d'): 96 | self.__reverse_shading() 97 | elif key == ord('='): 98 | self.new_height += 1 99 | elif key == ord('-'): 100 | self.new_height -= 1 101 | elif key == ord(']'): 102 | self.font_size += 1 103 | elif key == ord('['): 104 | self.font_size -= 1 105 | elif key == ord('1'): 106 | self.new_height = 10 107 | self.font_size = 45 108 | elif key == ord('2'): 109 | self.new_height = 30 110 | self.font_size = 15 111 | elif key == ord('3'): 112 | self.new_height = 50 113 | self.font_size = 10 114 | 115 | vid.release() 116 | cv2.destroyAllWindows() 117 | 118 | def __reverse_shading(self): 119 | self.dark_style = not self.dark_style 120 | self.shading_style = self.shading_style[::-1] -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 Dylan Kai Lau 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # ascii-transformer 2 | 3 | ## Installation 4 | 5 | ### Installation using Conda: 6 | 1. Open terminal and locate folder to copy to 7 | 2. `git clone https://github.com/kailau02/ascii-transformer.git` 8 | 3. `conda create --name ./env --file requirements.txt` 9 | 10 | ### Global python3 installation: 11 | 1. Open terminal and locate folder to copy to 12 | 2. `git clone https://github.com/kailau02/ascii-transformer.git` 13 | 3. `pip3 install opencv-python numpy pillow` 14 | 15 | ## How to Use 16 | In `main.py`, you must instantiate the `ASCIITransformer` class-object and then you can call methods to transform images and videos to ASCII representations 17 | 18 | ### Webcam mode 19 | 20 | #### Basic setup 21 | * The webcam mode loads by default from `main.py` by writing in terminal `python3 main.py` 22 | * Make sure to allow the program access to your webcam to run 23 | * The script may need to be ran twice before working properly 24 | * While the program is running, you can set the ascii density/quality by clicking `1` `2` and `3` on your keyboard 25 | 26 | #### Usage 27 | Call the video capture method using `obj.transform_to_video()` 28 | 29 | ### Image mode 30 | * Open an existing image on object instantiation like: `obj = ASCIITransformer(fname="image.jpeg")` 31 | * Or, open an image directly from the object like: `obj.open(fname="image.jpeg")` 32 | * Convert the image to an ASCII image like: `obj.transform_to_img(fname="resulting-image.png")` 33 | 34 | ### Text mode 35 | * Open an existing image on object instantiation like: `obj = ASCIITransformer(fname="image.jpeg")` 36 | * Or, open an image directly from the object like: `obj.open(fname="image.jpeg")` 37 | * Convert the image to an ASCII text file like: `obj.transform_to_txt(fname="resulting-text.txt")` 38 | 39 | ## Video Example 40 | [![Dolly Zoom Video Example](https://img.youtube.com/vi/Ad_T9uC9uCg/0.jpg)](https://www.youtube.com/watch?v=Ad_T9uC9uCg) 41 | -------------------------------------------------------------------------------- /RobotoMono-Bold.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kailau02/ascii-transformer/aa3757ab676e922366028f2989a859334ae5ee54/RobotoMono-Bold.ttf -------------------------------------------------------------------------------- /images/ascii-dog.txt: -------------------------------------------------------------------------------- 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 | .... -------------------------------------------------------------------------------- /images/dog.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kailau02/ascii-transformer/aa3757ab676e922366028f2989a859334ae5ee54/images/dog.jpg -------------------------------------------------------------------------------- /images/lucy-ascii.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kailau02/ascii-transformer/aa3757ab676e922366028f2989a859334ae5ee54/images/lucy-ascii.png -------------------------------------------------------------------------------- /images/lucy.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kailau02/ascii-transformer/aa3757ab676e922366028f2989a859334ae5ee54/images/lucy.jpg -------------------------------------------------------------------------------- /images/tree-ascii.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kailau02/ascii-transformer/aa3757ab676e922366028f2989a859334ae5ee54/images/tree-ascii.png -------------------------------------------------------------------------------- /images/tree.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kailau02/ascii-transformer/aa3757ab676e922366028f2989a859334ae5ee54/images/tree.jpeg -------------------------------------------------------------------------------- /main.py: -------------------------------------------------------------------------------- 1 | from ASCIITransformer import ASCIITransformer 2 | 3 | ''' 4 | ASCIITransformer Class: 5 | 6 | Constructor parameters: 7 | 1. fname=(str) string for image you will convert (if using webcam, you can leave this blank) 8 | 2. height=(int) number of rows of ascii characters, width will be proportionally calculated 9 | 3. shading_style=(str) "normal", "long", or "custom" 10 | 4. dark_style=(bool) create ascii image with dark theme or light theme 11 | 5. font_size=(int) adjust font size 12 | 13 | Methods: 14 | .open(fname={input file name}) # To open an image for conversion 15 | .transform_to_txt(fname={output file name}) # Transform opened image to ascii in .txt document 16 | .transform_to_img(fname={output file name}}) # Transform opened image to ascii in .png document 17 | .transfrom_to_video() # Realtime video transform 18 | 19 | # Video example: 20 | transformer = ASCIITransformer(height=40, font_size=12, dark_style=True) 21 | transformer.transform_to_video() 22 | 23 | # Photo example: 24 | img_transformer = ASCIITransformer(fname="images/tree.jpeg", height=70, font_size=10) 25 | img_transformer.transform_to_img(fname="images/tree-ascii.png") 26 | 27 | # Text example: 28 | transformer = ASCIITransformer(fname="images/dog.jpg") 29 | transformer.transform_to_txt(fname="images/ascii-dog.txt") 30 | 31 | ''' 32 | transformer = ASCIITransformer(height=40, font_size=12, dark_style=True) 33 | transformer.transform_to_video() -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | # This file may be used to create an environment using: 2 | # $ conda create --name --file 3 | # platform: osx-64 4 | blas=1.0=mkl 5 | bzip2=1.0.8=h1de35cc_0 6 | ca-certificates=2021.7.5=hecd8cb5_1 7 | cairo=1.16.0=h8023c5d_1 8 | certifi=2021.5.30=py37hecd8cb5_0 9 | ffmpeg=4.0=h01ea3c9_0 10 | fontconfig=2.13.1=ha9ee91d_0 11 | freetype=2.10.4=ha233b18_0 12 | gettext=0.21.0=h7535e17_0 13 | glib=2.69.0=hdf23fa2_0 14 | graphite2=1.3.14=h38d11af_0 15 | harfbuzz=1.8.8=hb8d4a28_0 16 | hdf5=1.10.2=hfa1e0ec_1 17 | icu=58.2=h0a44026_3 18 | intel-openmp=2021.2.0=hecd8cb5_564 19 | jasper=2.0.14=h636a363_1 20 | jpeg=9b=he5867d9_2 21 | lcms2=2.12=hf1fd2bf_0 22 | libcxx=10.0.0=1 23 | libffi=3.3=hb1e8313_2 24 | libgfortran=3.0.1=h93005f0_2 25 | libiconv=1.16=h1de35cc_0 26 | libopencv=3.4.2=h7c891bd_1 27 | libopus=1.3.1=h1de35cc_0 28 | libpng=1.6.37=ha441bb4_0 29 | libtiff=4.2.0=h87d7836_0 30 | libvpx=1.7.0=h378b8a2_0 31 | libwebp-base=1.2.0=h9ed2024_0 32 | libxml2=2.9.12=hcdb78fc_0 33 | llvm-openmp=10.0.0=h28b9765_0 34 | lz4-c=1.9.3=h23ab428_0 35 | mkl=2021.2.0=hecd8cb5_269 36 | mkl-service=2.3.0=py37h9ed2024_1 37 | mkl_fft=1.3.0=py37h4a7008c_2 38 | mkl_random=1.2.1=py37hb2f4e1b_2 39 | ncurses=6.2=h0a44026_1 40 | numpy=1.20.2=py37h4b4dc7a_0 41 | numpy-base=1.20.2=py37he0bd621_0 42 | olefile=0.46=py37_0 43 | opencv=3.4.2=py37h6fd60c2_1 44 | openjpeg=2.3.0=hb95cd4c_1 45 | openssl=1.1.1k=h9ed2024_0 46 | pcre=8.45=h23ab428_0 47 | pillow=8.3.1=py37ha4cf6ea_0 48 | pip=21.1.3=py37hecd8cb5_0 49 | pixman=0.40.0=haf1e3a3_0 50 | py-opencv=3.4.2=py37h7c891bd_1 51 | python=3.7.10=h88f2d9e_0 52 | readline=8.1=h9ed2024_0 53 | setuptools=52.0.0=py37hecd8cb5_0 54 | six=1.16.0=pyhd3eb1b0_0 55 | sqlite=3.36.0=hce871da_0 56 | tk=8.6.10=hb0a8c7a_0 57 | wheel=0.36.2=pyhd3eb1b0_0 58 | xz=5.2.5=h1de35cc_0 59 | zlib=1.2.11=h1de35cc_3 60 | zstd=1.4.9=h322a384_0 61 | --------------------------------------------------------------------------------