├── .github └── workflows │ └── python-app.yml ├── Images ├── dog.png ├── dogExample.png ├── giraffe.jpg ├── python.png └── zenalc.png ├── LICENSE ├── README.md ├── ascii_art ├── Test Files │ ├── python.txt │ └── pythonInverted.txt ├── ascii_art.py ├── run.py └── test_ascii_art.py ├── example.gif └── requirements.txt /.github/workflows/python-app.yml: -------------------------------------------------------------------------------- 1 | # This workflow will install Python dependencies, run tests and lint with a single version of Python 2 | # For more information see: https://help.github.com/actions/language-and-framework-guides/using-python-with-github-actions 3 | 4 | name: Python application 5 | 6 | on: 7 | push: 8 | branches: [ master ] 9 | pull_request: 10 | branches: [ master ] 11 | 12 | jobs: 13 | build: 14 | 15 | runs-on: ubuntu-latest 16 | 17 | steps: 18 | - uses: actions/checkout@v2 19 | - name: Set up Python 3.8 20 | uses: actions/setup-python@v2 21 | with: 22 | python-version: 3.8 23 | - name: Install dependencies 24 | run: | 25 | python -m pip install --upgrade pip 26 | pip install flake8 pytest 27 | if [ -f requirements.txt ]; then pip install -r requirements.txt; fi 28 | - name: Lint with flake8 29 | run: | 30 | # stop the build if there are Python syntax errors or undefined names 31 | flake8 . --count --select=E9,F63,F7,F82 --show-source --statistics 32 | # exit-zero treats all errors as warnings. The GitHub editor is 127 chars wide 33 | flake8 . --count --max-complexity=10 --max-line-length=127 --statistics 34 | - name: Test with pytest 35 | run: | 36 | pytest 37 | -------------------------------------------------------------------------------- /Images/dog.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ZENALC/ascii-art/71e83e95c3c6243bbfe56cf537cb958e0996cc69/Images/dog.png -------------------------------------------------------------------------------- /Images/dogExample.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ZENALC/ascii-art/71e83e95c3c6243bbfe56cf537cb958e0996cc69/Images/dogExample.png -------------------------------------------------------------------------------- /Images/giraffe.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ZENALC/ascii-art/71e83e95c3c6243bbfe56cf537cb958e0996cc69/Images/giraffe.jpg -------------------------------------------------------------------------------- /Images/python.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ZENALC/ascii-art/71e83e95c3c6243bbfe56cf537cb958e0996cc69/Images/python.png -------------------------------------------------------------------------------- /Images/zenalc.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ZENALC/ascii-art/71e83e95c3c6243bbfe56cf537cb958e0996cc69/Images/zenalc.png -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 Mihir Shrestha 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 | # Python program that converts an image file into its equivalent ASCII text file. 2 | ## Installation 3 | ```pip install -r requirements.txt``` 4 | ## Usage 5 | To run, use: ```python run.py [path/to/image]``` 6 | ### Optional Arguments 7 | To get a list of all optional arguments: ```python run.py -h``` \ 8 | To copy generated ASCII text to clipboard: ```python run.py [path/to/image] --copy``` \ 9 | To print generated ASCII text: ```python run.py [path/to/image] --print [optional_color]``` \ 10 | To write generated ASCII text to a text file: ```python run.py [path/to/image] --write [optional_filename]``` \ 11 | To change algorithm used to generate ASCII text: ```python run.py [path/to/image] --choice [choice_name]``` \ 12 | To invert generated ASCII text ```python run.py [path/to/image] --inverted``` \ 13 | To change height of generated ASCII text ```python run.py [path/to/image] --height HEIGHT``` \ 14 | To change width of generated ASCII text ```python run.py [path/to/image] --width WIDTH``` 15 | 16 | ### Example 17 | 18 | -------------------------------------------------------------------------------- /ascii_art/Test Files/python.txt: -------------------------------------------------------------------------------- 1 | BBBBBBBBBBBBBBBBBBBBBB@@$$@@@@BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB 2 | BBBBBBBBBBBBBBBBBB@@@@ooddkk&&$$BBBBBBBBBB@@@@@@BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB 3 | BBBBBBBBBBBBBBBB$$oozzUUOOQQzz00BBBBBB$$BBMMooWW@@@@BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB 4 | BBBBBBBBBBBBBB@@hhvv##$$$$$$@@OOUU$$%%OOzzYYCCXXXXdd@@BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB 5 | BBBBBBBBBBBB@@88vv&&$$$$$$$$$$$$LL00zzZZ%%$$$$$$MMUU00@@BBBBBBBBBBBBBBBBBBBBBBBBBBBBBB 6 | BBBBBBBBBBBB@@qqOO$$$$$$$$$$$$$$WW||##$$$$$$$$$$$$$$YYdd@@BBBBBBBBBBBBBBBBBBBBBBBBBBBB 7 | BBBBBBBBBBBB$$UU##$$$$$$@@00aa$$$$zz@@$$MMMM$$$$$$$$&&zzBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB 8 | BBBBBBBBBBBB@@XX88$$$$$$JJOOzz&&$$YY88MMvvvv&&$$$$$$$$UUMM@@BBBBBBBBBBBBBBBBBBBBBBBBBB 9 | BBBBBBBBBBBB$$UUMM$$$$@@hh$$dddd$$YY%%OO**ddOO$$$$$$$$JJWW@@BBBBBBBBBBBBBBBBBBBBBBBBBB 10 | BBBBBBBBBBBB$$qqmm$$$$$$$$$$$$$$@@zz$$BB$$%%**$$$$$$BB||dd$$BBBBBBBBBBBBBBBBBBBBBBBBBB 11 | BBBBBBBBBBBB$$mm))BB$$$$$$$$$$$$ppJJ$$$$$$$$$$$$$$$$00--]]CC@@BBBBBBBBBBBBBBBBBBBBBBBB 12 | BBBBBBBBBB$$pp[[??xx88$$$$$$$$bb}}))##$$$$$$$$$$$$mm[[))))??YY$$BBBBBBBBBBBBBBBBBBBBBB 13 | BBBBBBBB$$hh[[11(([[))ccCCXX//]]11{{{{CCooWW**OOtt]]))(((({{llCC$$BBBBBBBBBBBBBBBBBBBB 14 | BBBBBB@@&&)){{((((((11[[[[[[{{(((((())]]]]]]]]]]{{((((((}}~~}}]]bb$$BBBBBBBBBBBBBBBBBB 15 | BBBBBB$$cc]](((((((((((((((((((((((((((((((((((((((((())~~11||{{\\BBBBBBBBBBBBBBBBBBBB 16 | BBBB@@MM[[))((((((((((((((((((((((((((((((((((((((((((]]??||((((??OO$$BBBBBBBBBBBBBBBB 17 | BBBB$$00??((((((((((((((((((((((((((((((((((((((((((((__{{(((((({{\\BBBBBBBBBBBBBBBBBB 18 | BBBB$$jj}}((((((((((((((((((((((((((((((((((((((((((((--[[||(((((([[oo@@BBBBBBBBBBBBBB 19 | BB@@8811))((((((((((((((((((((((((((((((((((((((((((((11++--[[}}[[iiOO$$BBBBBBBBBBBBBB 20 | BB@@hh??(((((((((((((((((((((((((((((((((((((((((((((((())}}??--]]--zz$$BBBBBBBBBBBBBB 21 | BB@@&&\\]](((((((((((((((((((((((((((((((((((((())[[11(((((((((((({{rr$$BBBBBBBBBBBBBB 22 | BBBB@@&&jj??{{))((((((((((((((((((((((((((((((}}[[[[}}(((((((((())}}//@@BBBBBBBBBBBBBB 23 | BBBBBB@@@@wwtt[[??}}11))(((((((((((((((())}}??ffoonn}}((((((}}__++~~((%%BBBBBBBBBBBBBB 24 | BBBBBBBBBB$$@@##ZZvv\\{{??[[}}}}}}[[]]??11rrdd@@$$\\11(((([[++{{((11\\%%BBBBBBBBBBBBBB 25 | BBBBBBBBBBBBBB@@$$$$BBWWkkLLccuuvvYYOOkk&&$$$$@@MM}}))(())++))(((({{//@@BBBBBBBBBBBBBB 26 | BBBBBBBBBBBBBBBB@@$$$$88MM**oohhhhhh**&&@@$$BB$$00]]((((}}--||((((}}xx$$BBBBBBBBBBBBBB 27 | BBBBBBBBBBBB@@$$WWZZxx\\))111111111111((ttOOBBBB((11((((}}__||(((([[JJ$$BBBBBBBBBBBBBB 28 | BBBBBBBBBB@@&&YY))))\\ttffjjjjjjjjjjjjff//11rrvv]]((((((((__??))||??bb$$BBBBBBBBBBBBBB 29 | BBBBBBBB@@##\\))ffjjjjffffffffjjjjjjjjjjjjrr[[~~(((((((((())??++++))%%@@BBBBBBBBBBBBBB 30 | BBBBBBBB%%tt\\jjjjjjffffjjjj//||11{{{{))//((__))((((((((((((((((__UU$$BBBBBBBBBBBBBBBB 31 | BBBBBB$$mm11jj//\\\\ffjj\\11ffCCddooooqq{{~~))(((((((((((((((())}}MM@@BBBBBBBBBBBBBBBB 32 | BBBBBB$$XX[[))rr\\~~ff))jjaa@@WWppqqZZjj[[((((]]--((((((((((((]]XX$$BBBBBBBBBBBBBBBBBB 33 | BBBBBB@@kkXXaa88\\//||xxBB$$aa????]]]]}}11}}++__11((((((((((11{{&&@@BBBBBBBBBBBBBBBBBB 34 | BBBBBBBB@@$$$$aa{{tt\\88@@@@MMtt{{111111\\[[--((((((((((((((]]mm##WW$$BBBBBBBBBBBBBBBB 35 | BBBBBBBBBBBB@@WW))[[ww$$BBBB@@@@&&WWWW88$$rr{{((((((((((((((??{{}}{{ww$$BBBBBBBBBBBBBB 36 | BBBBBBBBBBBBBB@@kkwwBBBBBBBBBBBB@@@@@@@@%%\\11((((((((((((((__[[||))]]oo@@BB$$@@BBBBBB 37 | BBBBBBBBBBBBBBBB@@$$BBBBBBBBBBBBBBBBBB@@oo[[((((((((((((((((}}++??--!!00$$BBCCnn&&@@BB 38 | BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB$$QQ]]((((((((((((((((((11[[}}--qq$$uu??__dd$$BB 39 | BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB@@nn}}(((((((((((((((((((((((({{ttrr]]((]]aa@@BB 40 | BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB@@ff{{(((((((((((((())))((((((((}}}}(([[ff@@BBBB 41 | BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB$$cc[[(((((((((())]]{{{{}}((((((((((11}}**@@BBBB 42 | BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB@@oo]]11((((((}}??XX&&WW1111(((((())]]pp$$BBBBBB 43 | BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB$$mm[[]][[]]ffbb$$@@$$LL--11))}}??mm$$BBBBBBBB 44 | BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB$$ooYYJJaa@@$$BBBBBBBBJJ||11//dd$$BBBBBBBBBB 45 | BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB@@$$$$@@BBBBBBBBBBBB$$%%&&BB$$BBBBBBBBBBBB 46 | BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB@@BBBBBBBBBBBBBBBB 47 | BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB 48 | BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB 49 | BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB 50 | BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB 51 | -------------------------------------------------------------------------------- /ascii_art/Test Files/pythonInverted.txt: -------------------------------------------------------------------------------- 1 | \\\\\\\\\\\\\\\\\\\\\\^^``^^^^\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\ 2 | \\\\\\\\\\\\\\\\\\^^^^ii??__::``\\\\\\\\\\^^^^^^\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\ 3 | \\\\\\\\\\\\\\\\``iirrtt))||rr((\\\\\\``\\IIii;;^^^^\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\ 4 | \\\\\\\\\\\\\\^^++nnll``````^^))tt``""))rrff\\jjjj??^^\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\ 5 | \\\\\\\\\\\\^^,,nn::````````````\\((rr11""``````IItt((^^\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\ 6 | \\\\\\\\\\\\^^[[))``````````````;;QQll``````````````ff??^^\\\\\\\\\\\\\\\\\\\\\\\\\\\\ 7 | \\\\\\\\\\\\``ttll``````^^((~~````rr^^``IIII````````::rr\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\ 8 | \\\\\\\\\\\\^^jj,,``````//))rr::``ff,,IInnnn::````````ttII^^\\\\\\\\\\\\\\\\\\\\\\\\\\ 9 | \\\\\\\\\\\\``ttII````^^++``????``ff""))!!??))````````//;;^^\\\\\\\\\\\\\\\\\\\\\\\\\\ 10 | \\\\\\\\\\\\``[[{{``````````````^^rr``\\``""!!``````\\QQ??``\\\\\\\\\\\\\\\\\\\\\\\\\\ 11 | \\\\\\\\\\\\``{{OO\\````````````]]//````````````````((bbpp\\^^\\\\\\\\\\\\\\\\\\\\\\\\ 12 | \\\\\\\\\\``]]qqddcc,,````````--wwOOll````````````{{qqOOOOddff``\\\\\\\\\\\\\\\\\\\\\\ 13 | \\\\\\\\``++qqZZ00qqOOxx\\jjJJppZZmmmm\\ii;;!!))UUppOO0000mm##\\``\\\\\\\\\\\\\\\\\\\\ 14 | \\\\\\^^::OOmm000000ZZqqqqqqmm000000OOppppppppppmm000000wwaawwpp--``\\\\\\\\\\\\\\\\\\ 15 | \\\\\\``xxpp000000000000000000000000000000000000000000OOaaZZQQmmLL\\\\\\\\\\\\\\\\\\\\ 16 | \\\\^^IIqqOO000000000000000000000000000000000000000000ppddQQ0000dd))``\\\\\\\\\\\\\\\\ 17 | \\\\``((dd00000000000000000000000000000000000000000000kkmm000000mmCC\\\\\\\\\\\\\\\\\\ 18 | \\\\``XXww00000000000000000000000000000000000000000000bbqqQQ000000qqii^^\\\\\\\\\\\\\\ 19 | \\^^,,ZZOO00000000000000000000000000000000000000000000ZZhhbbqqwwqqoo))``\\\\\\\\\\\\\\ 20 | \\^^++dd000000000000000000000000000000000000000000000000OOwwddbbppbbrr``\\\\\\\\\\\\\\ 21 | \\^^::LLpp00000000000000000000000000000000000000OOqqZZ000000000000mmzz``\\\\\\\\\\\\\\ 22 | \\\\^^::XXddmmOO000000000000000000000000000000wwqqqqww0000000000OOwwJJ^^\\\\\\\\\\\\\\ 23 | \\\\\\^^^^}}UUqqddwwZZOO0000000000000000OOwwddYYiivvww000000wwkkhhaa00""\\\\\\\\\\\\\\ 24 | \\\\\\\\\\``^^ll11nnCCmmddqqwwwwwwqqppddZZzz??^^``LLZZ0000qqhhmm00ZZCC""\\\\\\\\\\\\\\ 25 | \\\\\\\\\\\\\\^^````\\;;__\\xxuunnff))__::````^^IIwwOO00OOhhOO0000mmJJ^^\\\\\\\\\\\\\\ 26 | \\\\\\\\\\\\\\\\^^````,,II!!ii++++++!!::^^``\\``((pp0000wwbbQQ0000wwcc``\\\\\\\\\\\\\\ 27 | \\\\\\\\\\\\^^``;;11ccLLOOZZZZZZZZZZZZ00UU))\\\\00ZZ0000wwkkQQ0000qq//``\\\\\\\\\\\\\\ 28 | \\\\\\\\\\^^::ffOOOOCCUUYYXXXXXXXXXXXXYYJJZZzznnpp00000000kkddOOQQdd--``\\\\\\\\\\\\\\ 29 | \\\\\\\\^^llCCOOYYXXXXYYYYYYYYXXXXXXXXXXXXzzqqaa0000000000OOddhhhhOO""^^\\\\\\\\\\\\\\ 30 | \\\\\\\\""UULLXXXXXXYYYYXXXXJJQQZZmmmmOOJJ00kkOO0000000000000000kktt``\\\\\\\\\\\\\\\\ 31 | \\\\\\``{{ZZXXJJCCCCYYXXLLZZYY\\??iiii[[mmaaOO0000000000000000OOwwII^^\\\\\\\\\\\\\\\\ 32 | \\\\\\``jjqqOOzzCCaaYYOOXX~~^^;;]][[11XXqq0000ppbb000000000000ppjj``\\\\\\\\\\\\\\\\\\ 33 | \\\\\\^^__jj~~,,CCJJQQcc\\``~~ddddppppwwZZwwhhkkZZ0000000000ZZmm::^^\\\\\\\\\\\\\\\\\\ 34 | \\\\\\\\^^````~~mmUULL,,^^^^IIUUmmZZZZZZCCqqbb00000000000000pp{{ll;;``\\\\\\\\\\\\\\\\ 35 | \\\\\\\\\\\\^^;;OOqq}}``\\\\^^^^::;;;;,,``zzmm00000000000000ddmmwwmm}}``\\\\\\\\\\\\\\ 36 | \\\\\\\\\\\\\\^^__}}\\\\\\\\\\\\^^^^^^^^""LLZZ00000000000000kkqqQQOOppii^^\\``^^\\\\\\ 37 | \\\\\\\\\\\\\\\\^^``\\\\\\\\\\\\\\\\\\^^iiqq0000000000000000wwhhddbb**((``\\\\vv::^^\\ 38 | \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\``||pp000000000000000000ZZqqwwbb[[``uuddkk??``\\ 39 | \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\^^vvww000000000000000000000000mmUUzzpp00pp~~^^\\ 40 | \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\^^YYmm00000000000000OOOO00000000wwww00qqYY^^\\\\ 41 | \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\``xxqq0000000000OOppmmmmww0000000000ZZww!!^^\\\\ 42 | \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\^^iippZZ000000wwddjj::;;ZZZZ000000OOpp]]``\\\\\\ 43 | \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\``{{qqppqqppYY--``^^``\\bbZZOOwwdd{{``\\\\\\\\ 44 | \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\``iiff//~~^^``\\\\\\\\//QQZZJJ??``\\\\\\\\\\ 45 | \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\^^````^^\\\\\\\\\\\\``""::\\``\\\\\\\\\\\\ 46 | \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\^^\\\\\\\\\\\\\\\\ 47 | \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\ 48 | \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\ 49 | \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\ 50 | \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\ 51 | -------------------------------------------------------------------------------- /ascii_art/ascii_art.py: -------------------------------------------------------------------------------- 1 | from colorama import init, Fore 2 | from PIL import Image 3 | import pyperclip 4 | import os 5 | 6 | 7 | class ASCIIArt: 8 | CONSTANT = 3.85 # constant we will use to determine which character to use 9 | characters = r"`^\",:;Il!i~+_-?][}{1)(|\\/tfjrxnuvczXYUJCLQ0OZmwqpdbkhao*#MW&8%B@$" 10 | inverseCharacters = characters[::-1] # same array as above but in reverse 11 | 12 | def __init__(self, imagePath, choice='AVERAGE', inverted=False, dimension=(50, 50)): 13 | self.imagePath = imagePath 14 | self.choice = choice.upper() 15 | self.inverted = inverted 16 | self.dimension = dimension 17 | self.imageFile = os.path.basename(imagePath) 18 | self.image = self.get_image(imagePath, dimension) 19 | self.width, self.height = self.image.size 20 | self.matrix = self.get_image_matrix() 21 | self.renderedText = self.renderText() 22 | 23 | def get_image_matrix(self): 24 | """Helper function to get 2D array of RGB values""" 25 | imagePixels = self.get_image_pixels() 26 | return [imagePixels[y:y + self.width] for y in range(0, len(imagePixels), self.width)] 27 | 28 | def get_image_pixels(self): 29 | """Helper function to get RGB tuples from an Image object""" 30 | return list(self.image.getdata()) 31 | 32 | def get_character(self, brightness): 33 | """Helper function to get character depending on inverse values""" 34 | index = round(brightness / self.CONSTANT) 35 | if self.inverted: 36 | return self.inverseCharacters[index] 37 | else: 38 | return self.characters[index] 39 | 40 | def get_character_matrix(self): 41 | """Helper function to get character matrix depending on algorithm choice and inverted values""" 42 | 43 | def getAverage(x, y): 44 | return self.get_character(self.get_average(self.matrix[y][x])) 45 | 46 | def getLightness(x, y): 47 | return self.get_character(self.get_lightness(self.matrix[y][x])) 48 | 49 | def getLuminosity(x, y): 50 | return self.get_character(self.get_luminosity(self.matrix[y][x])) 51 | 52 | if self.choice == 'AVERAGE': # Use average formula to get brightness array 53 | return [[getAverage(x, y) for x in range(self.width)] for y in range(self.height)] 54 | elif self.choice == 'LIGHTNESS': # Use lightness formula to get brightness array 55 | return [[getLightness(x, y) for x in range(self.width)] for y in range(self.height)] 56 | elif self.choice == 'LUMINOSITY': # Use luminosity formula to get brightness array 57 | return [[getLuminosity(x, y) for x in range(self.width)] for y in range(self.height)] 58 | else: 59 | raise ValueError("Invalid algorithm choice. Possible values are average, lightness, and luminosity.") 60 | 61 | def renderText(self): 62 | """Main function to return ASCII format of image""" 63 | characterMatrix = self.get_character_matrix() 64 | renderedText = '' 65 | for row in characterMatrix: 66 | for element in row: 67 | renderedText += element * 2 68 | renderedText += '\n' 69 | 70 | return renderedText 71 | 72 | def copy_to_clipboard(self): 73 | """Helper function to copy to clipboard""" 74 | pyperclip.copy(self.renderedText) 75 | print("ASCII image copied to clipboard.") 76 | 77 | def print(self, color=None): 78 | """Helper function to print rendered text""" 79 | if color: 80 | init(convert=True) # initialize colorama module 81 | color = color.upper() 82 | if color == "GREEN": 83 | print(Fore.GREEN + self.renderedText) 84 | elif color == "RED": 85 | print(Fore.RED + self.renderedText) 86 | elif color == "BLUE": 87 | print(Fore.BLUE + self.renderedText) 88 | elif color == "YELLOW": 89 | print(Fore.YELLOW + self.renderedText) 90 | elif color == "CYAN": 91 | print(Fore.CYAN + self.renderedText) 92 | elif color == "MAGENTA": 93 | print(Fore.MAGENTA + self.renderedText) 94 | else: 95 | print("Invalid color. Supported colors are green, red, blue, yellow, cyan, and magenta.") 96 | else: 97 | print(self.renderedText) 98 | 99 | def write_to_file(self, fileName=None): 100 | """Helper function to write to a txt file""" 101 | if not fileName: 102 | fileName = self.imageFile 103 | outputName, _ = os.path.splitext(fileName) 104 | outputFile = outputName + '.txt' 105 | folderName = 'ASCII Files' 106 | 107 | previousPath = os.getcwd() 108 | os.chdir('../') 109 | 110 | if not os.path.exists(folderName): 111 | os.mkdir(folderName) 112 | os.chdir(folderName) 113 | 114 | if os.path.exists(outputFile): 115 | counter = 0 116 | while os.path.exists(outputFile): 117 | outputFile = f'{outputName}{counter}.txt' 118 | counter += 1 119 | 120 | with open(outputFile, 'w') as f: 121 | f.write(self.renderedText) 122 | 123 | print(f"ASCII image saved to {os.path.abspath(outputFile)}.") 124 | os.chdir(previousPath) 125 | 126 | def __repr__(self): 127 | return f'ASCIIArt{self.imagePath, self.choice, self.inverted, self.dimension}' 128 | 129 | def __str__(self): 130 | return self.__repr__() 131 | 132 | @staticmethod 133 | def get_image(path, dimension): 134 | """Helper function to retrieve an Image object""" 135 | with Image.open(path) as image: 136 | if image.mode == 'P': # If transparent, convert to RGBA 137 | image = image.convert('RGBA') 138 | else: # Else force convert to RGB 139 | image = image.convert('RGB') 140 | image.thumbnail(dimension) # Resizing image 141 | return image 142 | 143 | @staticmethod 144 | def get_average(rgbTuple): 145 | """Helper function to get average value of an RGB tuple""" 146 | return sum(rgbTuple) / len(rgbTuple) 147 | 148 | @staticmethod 149 | def get_luminosity(rgbTuple): 150 | """Helper function to get luminosity value of an RGB tuple""" 151 | return rgbTuple[0] * .21 + rgbTuple[1] * .72 + rgbTuple[2] * .07 152 | 153 | @staticmethod 154 | def get_lightness(rgbTuple): 155 | """Helper function to get lightness of an RGB tuple""" 156 | return (max(rgbTuple) - min(rgbTuple)) / 2 157 | -------------------------------------------------------------------------------- /ascii_art/run.py: -------------------------------------------------------------------------------- 1 | from ascii_art import ASCIIArt 2 | import argparse 3 | 4 | 5 | def main(): 6 | parser = argparse.ArgumentParser(description='Convert an image to ASCII') 7 | parser.add_argument('imagePath', type=str, help='path to image file that will be converted') 8 | parser.add_argument('--inverted', help='invert the generated ASCII', 9 | action="store_true") 10 | parser.add_argument('--copy', help='copy the generated ASCII to clipboard', 11 | action="store_true") 12 | parser.add_argument('--choice', help='pick what type of algorithm to use', 13 | nargs=1, default=["average"], choices=('luminosity', 'lightness', 'average')) 14 | parser.add_argument('--height', help='specify height for generated ascii text', 15 | nargs=1, default=[50], type=int) 16 | parser.add_argument('--width', help='specify width for generated ascii text', 17 | nargs=1, default=[50], type=int) 18 | parser.add_argument('--print', help='print the generated ASCII with specified color if provided', 19 | nargs='?', const=1, choices=('red', 'blue', 'yellow', 'green', 'cyan', 'magenta')) 20 | parser.add_argument('--write', help='write the generated ASCII to specified filename if provided', 21 | nargs='?', const=1) 22 | args = parser.parse_args() 23 | 24 | dimension = (args.width[0], args.height[0]) 25 | monkeyText = ASCIIArt(args.imagePath, inverted=args.inverted, choice=args.choice[0], dimension=dimension) 26 | if args.copy: 27 | monkeyText.copy_to_clipboard() 28 | if args.print: 29 | if args.print != 1: 30 | monkeyText.print(args.print) 31 | else: 32 | monkeyText.print() 33 | if args.write: 34 | if args.write != 1: 35 | monkeyText.write_to_file(args.write) 36 | else: 37 | monkeyText.write_to_file() 38 | 39 | 40 | if __name__ == '__main__': 41 | main() 42 | -------------------------------------------------------------------------------- /ascii_art/test_ascii_art.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | import os 3 | 4 | 5 | @pytest.fixture 6 | def asciiObject(): 7 | from ascii_art import ASCIIArt as Art 8 | return Art('Images/python.png') 9 | 10 | 11 | def test_matrix(asciiObject): 12 | assert asciiObject.get_image_matrix() is not None 13 | assert asciiObject.matrix[0][0] is not None 14 | 15 | 16 | def test_image_pixels(asciiObject): 17 | assert asciiObject.get_image_pixels() is not None 18 | assert asciiObject.get_image_pixels()[0] is not None 19 | 20 | 21 | def test_get_brightness(asciiObject): 22 | asciiObject.inverted = True 23 | assert asciiObject.get_character(0) == '$' 24 | 25 | asciiObject.inverted = False 26 | assert asciiObject.get_character(0) == '`' 27 | 28 | 29 | def test_get_average(asciiObject): 30 | rgbTuple = (1, 2, 3) 31 | assert asciiObject.get_average(rgbTuple) == 2 32 | 33 | 34 | def test_get_lightness(asciiObject): 35 | rgbTuple = (1, 2, 3) 36 | assert asciiObject.get_lightness(rgbTuple) == 1 37 | 38 | 39 | def test_get_luminosity(asciiObject): 40 | rgbTuple = (1, 2, 3) 41 | assert round(asciiObject.get_luminosity(rgbTuple), 2) == 1.86 42 | 43 | 44 | def test_renderText(asciiObject): 45 | os.chdir(os.path.join('ascii_art', 'Test Files')) 46 | with open('python.txt', 'r') as f: 47 | assert asciiObject.renderText() == f.read() 48 | 49 | with open('pythonInverted.txt', 'r') as f: 50 | asciiObject.inverted = True 51 | assert asciiObject.renderText() == f.read() 52 | -------------------------------------------------------------------------------- /example.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ZENALC/ascii-art/71e83e95c3c6243bbfe56cf537cb958e0996cc69/example.gif -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | pyperclip~=1.8.0 2 | Pillow~=7.1.2 3 | colorama~=0.4.3 4 | pytest~=5.4.3 --------------------------------------------------------------------------------