Compare commits

...

81 Commits

Author SHA1 Message Date
Zoraiz d05a757c5e Bin piping restricted to a special character 2022-11-23 13:06:36 +05:00
Zoraiz fbf87d33d5 Added bin piping support for #28 2022-11-14 19:21:07 +05:00
Zoraiz Hassan a6a4120adb
Merge pull request #29 from brian6932/patch-1
Updated Scoop directions
2022-11-06 04:03:12 -08:00
Brian b229f94116
Updated Scoop directions
The PR to main has been merged https://github.com/ScoopInstaller/Main/pull/4087
2022-11-04 18:15:43 -04:00
Zoraiz Hassan 17a7ef5c49 Updated README.md and minor logging corrections 2022-05-31 23:31:20 +05:00
Zoraiz Hassan 62789959c8
Merge pull request #27 from jpagny/master
Used RGBA for color-bg flag
2022-05-31 23:14:41 +05:00
Jérôme Pagny 63f31c675e Used RGBA for color-bg flag 2022-05-31 11:21:17 +02:00
Zoraiz Hassan 66a0777ae8
Updated README.md 2022-03-03 20:11:32 +05:00
Zoraiz Hassan 82c26c541a
Merge pull request #21 from brian6932/scoop-installer
Added Scoop install method to readme
2022-03-03 20:08:28 +05:00
Brian 088dd05e30
Added Scoop install method to readme 2022-03-03 04:45:35 -05:00
Zoraiz Hassan 2cc0d5f40d Update Go version 2021-10-09 22:20:25 +05:00
Zoraiz Hassan f31ac047ea Added --only-save and patch for #14 2021-10-06 15:32:35 +05:00
Zoraiz 39918a97fb Optimized example gif sizes 2021-09-17 22:11:20 +05:00
Zoraiz eebac8a229 Updated example gifs 2021-09-17 18:22:14 +05:00
Zoraiz 518011b970 Updated README.md 2021-09-15 11:56:39 +05:00
Zoraiz 3c6a1a589f Updated a comment 2021-09-12 14:58:18 +05:00
Zoraiz df5eafe9a3 Added 8-bit color support (#9, #11) 2021-09-11 02:16:17 +05:00
Zoraiz dd8f65207e Updated README.md 2021-09-10 16:43:51 +05:00
Zoraiz f75ab428e5 New patch for #10 2021-09-10 16:25:30 +05:00
Zoraiz 6793039277 Patch for #10 2021-09-10 16:09:19 +05:00
Zoraiz 178c48fbe1 Library update 2021-09-10 00:34:08 +05:00
Zoraiz de35b19962 Updated README.md 2021-09-06 17:59:10 +05:00
Zoraiz 2db0093e58 Added dithering and improved performance 2021-09-06 17:34:49 +05:00
Zoraiz Hassan 8be1d4c9b5
Updated README.md 2021-09-05 15:06:32 +05:00
Zoraiz Hassan 19f9581235
Merge pull request #7 from atreyaved/patch-1
added a better README.md
2021-09-05 15:04:59 +05:00
atreyaved 7c0c89df66
Update README.md 2021-09-05 14:15:19 +05:30
Zoraiz a8701af97a Updated README.md 2021-09-05 01:09:23 +05:00
Zoraiz e6e52d7aec Updated README.md and gifs 2021-09-05 00:59:37 +05:00
Zoraiz 917e1a5f0e Updated README.md 2021-09-05 00:18:31 +05:00
Zoraiz 202179dd2d Added --color-bg flag 2021-09-04 15:11:19 +05:00
Zoraiz 50b6b0081f Version update 2021-09-03 22:34:50 +05:00
Zoraiz d7df581771 Added font saving braille 2021-09-03 19:39:24 +05:00
Zoraiz 26d910fc9d Updated README.md 2021-09-03 13:03:05 +05:00
Zoraiz 6102f4dcaa Updated README.md 2021-09-03 00:36:50 +05:00
Zoraiz ed4e662781 Updated README.md 2021-09-03 00:26:01 +05:00
Zoraiz f318be9c56 Added braille art support 2021-09-03 00:09:02 +05:00
Zoraiz 1f3cd6ad00 Added braille gif 2021-09-02 23:07:04 +05:00
Zoraiz f5ffd897ca Refactored code, changed resize library 2021-08-28 20:06:48 +05:00
Zoraiz 1c8b009dd1 Added --save-bg and --font-color flag 2021-08-09 18:49:05 +05:00
Zoraiz d11ab9f718 Added --width, --height and --font flags 2021-08-06 20:10:26 +05:00
Zoraiz 367f986d8a Merge branch 'master' of github.com:TheZoraiz/ascii-image-converter 2021-08-02 22:12:45 +05:00
Zoraiz b435f8e83d Updated README.md 2021-08-02 22:12:07 +05:00
Zoraiz Hassan fbd9fb9c46
Updated README.md 2021-08-01 16:47:41 +05:00
Zoraiz 7a423ea49b Updated README.md 2021-08-01 16:46:10 +05:00
Zoraiz 5bbc900c9f Upadted README.md 2021-08-01 16:34:11 +05:00
Zoraiz 93f6d83216 Added macOS support (#6) 2021-08-01 15:57:03 +05:00
Zoraiz 70c91d7114 Added macOS to goreleaser 2021-07-29 16:02:10 +05:00
Zoraiz Hassan 1bf70473f4
Merge pull request #6 from xmarcos/fix-macos-clear
Added support for `clear` command on macOS (darwin)
2021-07-29 03:44:32 -07:00
Marcos Sader 4597533c4f
Added support for `clear` command on macOS (darwin)
This fixes the issue "Error: your platform is unsupported, terminal can't be cleared" when trying to convert a GIF in macOS.
2021-07-29 00:45:25 -03:00
Zoraiz Hassan 37be61171d
Updated README.md 2021-07-28 21:49:48 +05:00
Zoraiz 8574603aa9 Changed package installation directory 2021-07-25 23:57:40 +05:00
Zoraiz df31045bae Added grayscale flag 2021-07-25 23:27:16 +05:00
Zoraiz c06c4ef1c6 Updated README.md 2021-07-24 14:29:27 +05:00
Zoraiz 2a9202fd14 Changed APT source 2021-07-24 13:57:14 +05:00
Zoraiz e899c074c2 Updated README.md 2021-07-24 04:28:18 +05:00
Zoraiz 3f6d1840c5 Restored accidental file misplacement 2021-07-24 02:56:27 +05:00
Zoraiz ed3dfa1ed5 Added APT repo 2021-07-24 02:53:47 +05:00
Zoraiz Hassan a421952bfe
Updated README.md 2021-07-22 07:01:24 -07:00
Zoraiz Hassan 0f487a803a
Updated README.md 2021-07-22 06:49:34 -07:00
Zoraiz Hassan 81a5bd7e23
Updated README.md 2021-07-22 06:47:22 -07:00
Zoraiz 44eec9d122 Updated README.md 2021-07-21 22:36:05 +05:00
Zoraiz 97f1d679b0 Release v1.4.0 2021-07-21 22:23:58 +05:00
Zoraiz 01ab53a4d3 Update example gifs 2021-07-21 21:40:40 +05:00
Zoraiz Hassan 1f3a0af57a
Added more badges 2021-06-21 23:08:37 +05:00
Zoraiz 25aa7fb66d Renamed NOTE.md 2021-06-11 00:00:27 +05:00
Zoraiz 03a9748a9f Fixed bug with output redirect for linux 2021-06-10 23:59:02 +05:00
Zoraiz d3f8df41cb Version update 2021-06-05 23:30:42 +05:00
Zoraiz 8f46492eda Fixed bug with slightly taller image saved 2021-06-05 23:28:28 +05:00
Zoraiz d35ced81ae Extra line to a help text 2021-06-03 23:09:03 +05:00
Zoraiz 2d59d44a3e Fixed bug that saves url image as png on text flag 2021-06-03 22:54:29 +05:00
Zoraiz Hassan b707ee0d44
Updated README.md 2021-06-03 17:03:36 +05:00
Zoraiz Hassan 1c2888c9fb
Updated README.md 2021-06-03 14:38:28 +05:00
Zoraiz 5cf6697ab4 Updated README.md 2021-06-03 13:55:45 +05:00
Zoraiz 7f1dec750d Updated snapcraft.yaml 2021-06-03 13:49:55 +05:00
Zoraiz 3d91be1a1b Error handing for dimensions, bolder font on saved image 2021-06-03 13:49:13 +05:00
Zoraiz 0889006bf2 Changed DefaultFlags() function in package 2021-06-03 11:54:47 +05:00
Zoraiz 2c930421fb Updated README.md 2021-06-03 00:54:09 +05:00
Zoraiz a3d77f75a1 Changed --save flag to --save-img to save as image, added separate flag for text saving 2021-06-03 00:52:41 +05:00
Zoraiz 3a85eb379e Added more gifs 2021-06-03 00:02:27 +05:00
Zoraiz Hassan d61b112283
Updated README.md 2021-06-01 15:33:29 +05:00
Zoraiz Hassan 0d40aefe6c
Updated README.md 2021-05-31 20:29:35 +05:00
36 changed files with 2355 additions and 572 deletions

2
.gitignore vendored
View File

@ -2,3 +2,5 @@ pics/
build/
snap/
dist/
build.sh
todo.txt

View File

@ -1,4 +1,3 @@
project_name: ascii-image-converter
before:
@ -6,19 +5,31 @@ before:
- go mod tidy
builds:
- env:
- CGO_ENABLED=0
-
goos:
- linux
- windows
- darwin
goarch:
# Architectures for executable binary generation
- amd64
- arm64
- arm
- 386
# # Specifying all architectures for .deb and .rpm generation
# - 386
# - amd64
# - arm
# - arm64
# - ppc64
# - ppc64le
# - mips
# - mipsle
# - mips64
# - mips64le
archives:
-
name_template: "{{ .ProjectName }}_{{ .Os }}_{{ .Arch }}"
@ -30,14 +41,16 @@ archives:
files:
- LICENSE.txt
- README.md
replacements:
linux: Linux
windows: Windows
darwin: macOS
amd64: amd64_64bit
arm64: arm64_64bit
386: i386_32bit
arm: arm_32bit
arm: armv6_32bit
checksum:
name_template: 'sha256_checksums.txt'
@ -51,3 +64,51 @@ changelog:
exclude:
- '^docs:'
- '^test:'
brews:
-
name: ascii-image-converter
tap:
owner: TheZoraiz
name: homebrew-ascii-image-converter
url_template: https://github.com/TheZoraiz/ascii-image-converter/releases/download/{{ .Tag }}/{{ .ArtifactName }}
commit_author:
name: Zoraiz Hassan
email: hzoraiz8@gmail.com
homepage: https://github.com/TheZoraiz/ascii-image-converter
description: Convert images into ascii art
license: Apache 2.0
skip_upload: true
test: |
system "#{bin}/ascii-image-converter --version"
install: |
bin.install "ascii-image-converter"
nfpms:
-
package_name: ascii-image-converter
file_name_template: "{{ .ProjectName }}_{{ .Version }}_{{ .Arch }}"
homepage: https://github.com/TheZoraiz/ascii-image-converter
vendor: Zoraiz Hassan
maintainer: Zoraiz Hassan <hzoraiz8@gmail.com>
description: Convert images into ascii art
license: Apache 2.0
formats:
- deb
- rpm
release: 1
section: graphics
priority: optional
bindir: /usr/bin
rpm:
compression: lzma

401
README.md
View File

@ -1,21 +1,34 @@
# ascii-image-converter
[![ascii-image-converter](https://snapcraft.io/ascii-image-converter/badge.svg)](https://snapcraft.io/ascii-image-converter)
[![release-version](https://img.shields.io/github/v/release/TheZoraiz/ascii-image-converter?label=Latest%20Version)](https://github.com/TheZoraiz/ascii-image-converter/releases/latest)
[![license](https://img.shields.io/badge/License-Apache%202.0-blue.svg)](https://github.com/TheZoraiz/ascii-image-converter/blob/master/LICENSE.txt)
[![language](https://img.shields.io/badge/Language-Go-blue)](https://golang.org/)
![release-downloads](https://img.shields.io/github/downloads/TheZoraiz/ascii-image-converter/total?color=1d872d&label=Release%20Downloads)
[![ascii-image-converter-snap](https://snapcraft.io/ascii-image-converter/badge.svg)](https://snapcraft.io/ascii-image-converter)
ascii-image-converter is a command-line tool that converts images into ascii art and prints them out onto the console. It is cross-platform so both Windows and Linux distributions are supported.
ascii-image-converter is a command-line tool that converts images into ascii art and prints them out onto the console. Available on Windows, Linux and macOS.
It's also available as a package to be used in Go applications.
Now supports braille art!
Image formats currently supported:
Input formats currently supported:
* JPEG/JPG
* PNG
* BMP
* WEBP
* TIFF/TIF
* GIF
<p align="center">
<img src="https://raw.githubusercontent.com/TheZoraiz/ascii-image-converter/master/example_gifs/all.gif">
</p>
## Table of Contents
- [Installation](#installation)
* [Debian / Ubuntu-based](#debian-or-ubuntu-based-distros)
* [Homebrew](#homebrew)
* [AUR](#aur)
* [Scoop](#scoop)
* [Snap](#snap)
* [Go](#go)
* [Linux (binaries)](#linux)
@ -24,29 +37,96 @@ Image formats currently supported:
* [Flags](#flags)
- [Library Usage](#library-usage)
- [Contributing](#contributing)
- [Packages used](#packages-used)
- [Packages Used](#packages-used)
- [License](#license)
## Installation
### Debian or Ubuntu-based Distros
Execute the following commands in order:
```
echo 'deb [trusted=yes] https://apt.fury.io/ascii-image-converter/ /' | sudo tee /etc/apt/sources.list.d/ascii-image-converter.list
```
```
sudo apt update
```
```
sudo apt install -y ascii-image-converter
```
<br>
To remove the package source (which means you won't be getting any further updates), execute this command:
```
sudo rm -v /etc/apt/sources.list.d/ascii-image-converter.list
```
<hr>
### Homebrew
Installation with homebrew is available for both Linux and macOS.
```
brew install TheZoraiz/ascii-image-converter/ascii-image-converter
```
[Link to homebrew repository](https://github.com/TheZoraiz/homebrew-ascii-image-converter)
<hr>
### AUR
The AUR repo is maintained by [magnus-tesshu](https://aur.archlinux.org/account/magnus-tesshu)
Standard way:
```
git clone https://aur.archlinux.org/ascii-image-converter-git.git
```
```
cd ascii-image-converter-git/
```
```
makepkg -si
```
AUR helper:
```
<aur-helper> -S ascii-image-converter-git
```
<hr>
### Scoop
The scoop manifest is maintained by [brian6932](https://github.com/brian6932)
```
scoop install ascii-image-converter
```
<hr>
### Snap
You can download through snap. However, the snap will not have access to hidden images and images outside the $HOME directory.
> **Note:** The snap will not have access to hidden files and files outside the $HOME directory. This includes write access for saving ascii art as well.
```
sudo snap install ascii-image-converter
```
Visit [the app's snap store listing](https://snapcraft.io/ascii-image-converter) for instructions regarding enabling snapd on your distribution.
[![Get it from the Snap Store](https://snapcraft.io/static/images/badges/en/snap-store-black.svg)](https://snapcraft.io/ascii-image-converter)
<hr>
### Go
For installing through Go
```
go install github.com/TheZoraiz/ascii-image-converter@latest
```
<hr>
For physically installing the binaries, follow the steps with respect to your OS.
### Linux
@ -58,28 +138,27 @@ Now, open a terminal in the same directory and execute this command:
```
sudo cp ascii-image-converter /usr/local/bin/
```
Now you can use ascii-image-converter in the terminal. Execute "ascii-image-converter -h" for more details.
Now you can use ascii-image-converter in the terminal. Execute `ascii-image-converter -h` for more details.
### Windows
You will need to set an Environment Variable to the folder the ascii-image-converter.exe executable is placed in to be able to use it in the command prompt. Follow the instructions in case of confusion:
Download the archive for your Windows architecture [here](https://github.com/TheZoraiz/ascii-image-converter/releases/latest), extract it, and open the extracted folder. Now, copy the folder path from the top of the file explorer and follow these instructions:
* In Search, search for and then select: System (Control Panel)
* Click the Advanced System settings link.
* In Search, search for and then select: Advanced System Settings
* Click Environment Variables. In the section User Variables find the Path environment variable and select it. Click "Edit".
* In the Edit Environment Variable window, click "New" and then paste the path of the folder that you copied initially.
* Click "Ok" on all open windows.
Now, restart any open command prompt and execute "ascii-image-converter -h" for more details.
Now, restart any open command prompt and execute `ascii-image-converter -h` for more details.
<br>
## CLI Usage
Note: Decrease font size or increase terminal width (like zooming out) for maximum quality ascii art
> **Note:** Decrease font size or increase terminal width (like zooming out) for maximum quality ascii art
The basic usage for converting an image into ascii art is as follows. You can also supply multiple image paths and urls.
The basic usage for converting an image into ascii art is as follows. You can also supply multiple image paths and urls as well as a GIF.
```
ascii-image-converter [image paths/urls]
@ -88,30 +167,20 @@ Example:
```
ascii-image-converter myImage.jpeg
```
<br>
Single image:
<p align="center">
<img src="https://raw.githubusercontent.com/TheZoraiz/ascii-image-converter/master/example_gifs/base.gif">
</p>
> **Note:** Piped binary input is also supported
> ```
> cat myImage.png | ascii-image-converter -
> ```
Multiple images:
<p align="center">
<img src="https://raw.githubusercontent.com/TheZoraiz/ascii-image-converter/master/example_gifs/all.gif">
</p>
Image from URL:
<p align="center">
<img src="https://raw.githubusercontent.com/TheZoraiz/ascii-image-converter/master/example_gifs/url.gif">
</p>
### Flags
#### --color OR -C
Display ascii art with the colors from original image. Works with the --negative flag as well.
> **Note:** Your terminal must support 24-bit or 8-bit colors for appropriate results. If 24-bit colors aren't supported, 8-bit color escape codes will be used
Display ascii art with the colors from original image.
```
ascii-image-converter [image paths/urls] -C
@ -123,18 +192,53 @@ ascii-image-converter [image paths/urls] --color
<img src="https://raw.githubusercontent.com/TheZoraiz/ascii-image-converter/master/example_gifs/color.gif">
</p>
#### --complex OR -c
#### --braille OR -b
Print the image with a wider array of ascii characters for more detailed lighting density. Sometimes improves accuracy.
> **Note:** Braille pattern display heavily depends on which terminal or font you're using. In windows, try changing the font from command prompt properties if braille characters don't display
Use braille characters instead of ascii. For this flag, your terminal must support braille patters (UTF-8) properly. Otherwise, you may encounter problems with colored or even uncolored braille art.
```
ascii-image-converter [image paths/urls] -c
ascii-image-converter [image paths/urls] -b
# Or
ascii-image-converter [image paths/urls] --complex
ascii-image-converter [image paths/urls] --braille
```
<p align="center">
<img src="https://raw.githubusercontent.com/TheZoraiz/ascii-image-converter/master/example_gifs/braille.gif">
</p>
#### --threshold
Set threshold value to compare for braille art when converting each pixel into a dot. Value must be between 0 and 255.
Example:
```
ascii-image-converter [image paths/urls] -b --threshold 170
```
#### --dither
Apply dithering on image to make braille art more visible. Since braille dots can only be on or off, dithering images makes them more visible in braille art.
Example:
```
ascii-image-converter [image paths/urls] -b --dither
```
<p align="center">
<img src="https://raw.githubusercontent.com/TheZoraiz/ascii-image-converter/master/example_gifs/dither.gif">
</p>
#### --color-bg
If any of the coloring flags is passed, this flag will transfer its color to each character's background. instead of foreground. However, this option isn't available for `--save-img` and `--save-gif`
```
ascii-image-converter [image paths/urls] -C --color-bg
```
#### --dimensions OR -d
Note: Don't immediately append another flag with -d
> **Note:** Don't immediately append another flag with -d
Set the width and height for ascii art in CHARACTER lengths.
```
@ -144,15 +248,45 @@ ascii-image-converter [image paths/urls] --dimensions <width>,<height>
```
Example:
```
ascii-image-converter [image paths/urls] -d 100,30
ascii-image-converter [image paths/urls] -d 60,30
```
<p align="center">
<img src="https://raw.githubusercontent.com/TheZoraiz/ascii-image-converter/master/example_gifs/dimensions.gif">
</p>
#### --width OR -W
> **Note:** Don't immediately append another flag with -W
Set width of ascii art. Height is calculated according to aspect ratio.
```
ascii-image-converter [image paths/urls] -W <width>
# Or
ascii-image-converter [image paths/urls] --width <width>
```
Example:
```
ascii-image-converter [image paths/urls] -W 60
```
#### --height OR -H
> **Note:** Don't immediately append another flag with -H
Set height of ascii art. Width is calculated according to aspect ratio.
```
ascii-image-converter [image paths/urls] -H <height>
# Or
ascii-image-converter [image paths/urls] --height <height>
```
Example:
```
ascii-image-converter [image paths/urls] -H 60
```
#### --map OR -m
Note: Don't immediately append another flag with -m
> **Note:** Don't immediately append another flag with -m
Pass a string of your own ascii characters to map against. Passed characters must start from darkest character and end with lightest. There is no limit to number of characters.
@ -172,6 +306,16 @@ ascii-image-converter [image paths/urls] -m " .-=+#@"
<img src="https://raw.githubusercontent.com/TheZoraiz/ascii-image-converter/master/example_gifs/map.gif">
</p>
#### --grayscale OR -g
Display ascii art in grayscale colors. This is the same as --color flag, except each character will be encoded with a grayscale RGB value.
```
ascii-image-converter [image paths/urls] -g
# Or
ascii-image-converter [image paths/urls] --grayscale
```
#### --negative OR -n
Display ascii art in negative colors. Works with both uncolored and colored text from --color flag.
@ -186,32 +330,22 @@ ascii-image-converter [image paths/urls] --negative
<img src="https://raw.githubusercontent.com/TheZoraiz/ascii-image-converter/master/example_gifs/negative.gif">
</p>
#### --save OR -s
Note: Don't immediately append another flag with -s
Save ascii art in the format `<image-name>.<image-extension>-ascii-art.txt` in the directory path passed to the flag.
Example for current directory:
#### --complex OR -c
Print the image with a wider array of ascii characters for more detailed lighting density. Sometimes improves accuracy.
```
ascii-image-converter [image paths/urls] --save .
ascii-image-converter [image paths/urls] -c
# Or
ascii-image-converter [image paths/urls] -s .
ascii-image-converter [image paths/urls] --complex
```
<p align="center">
<img src="https://raw.githubusercontent.com/TheZoraiz/ascii-image-converter/master/example_gifs/save.gif">
</p>
#### --formats OR -f
Display supported image formats.
#### --full OR -f
Print ascii art that fits the terminal width while maintaining aspect ratio.
```
ascii-image-converter [image paths/urls] --formats
# Or
ascii-image-converter [image paths/urls] -f
# Or
ascii-image-converter [image paths/urls] --full
```
#### --flipX OR -x
@ -224,10 +358,6 @@ ascii-image-converter [image paths/urls] --flipX
ascii-image-converter [image paths/urls] -x
```
<p align="center">
<img src="https://raw.githubusercontent.com/TheZoraiz/ascii-image-converter/master/example_gifs/flipx.gif">
</p>
#### --flipY OR -y
Flip the ascii art vertically on the terminal.
@ -237,60 +367,163 @@ ascii-image-converter [image paths/urls] --flipY
ascii-image-converter [image paths/urls] -y
```
#### --save-img OR -s
> **Note:** Don't immediately append another flag with -s
Saves the ascii as a PNG image with the name `<image-name>-ascii-art.png` in the directory path passed to the flag. Can work with both --color and --negative flag.
Example for current directory:
```
ascii-image-converter [image paths/urls] --save-img .
# Or
ascii-image-converter [image paths/urls] -s .
```
#### --save-txt
Similar to --save-img but it creates a TXT file with the name `<image-name>-ascii-art.txt` in the directory path passed to the flag. Only saves uncolored text.
Example for current directory:
```
ascii-image-converter [image paths/urls] --save-txt .
```
#### --save-gif
> **Note:** This is an experimental feature and may not result in the finest quality GIFs, because all GIFs still aren't supported by ascii-image-converter.
Saves the passed GIF as an ascii art GIF with the name `<image-name>-ascii-art.gif` in the directory path passed to the flag.
<p align="center">
<img src="https://raw.githubusercontent.com/TheZoraiz/ascii-image-converter/master/example_gifs/flipy.gif">
<img src="https://raw.githubusercontent.com/TheZoraiz/ascii-image-converter/master/example_gifs/save.gif">
</p>
<br>
#### --save-bg
You can combine flags as well. Following command outputs colored and negative ascii art, flips ascii art horizontally and vertically, with fixed 100 by 30 character dimensions, custom defined ascii characters " .-=+#@" and saves the output in current directory as well.
> **Note:** This flag will be ignored if `--save-img` or `--save-gif` flags are not set
This flag takes an RGBA value that sets the background color in saved png and gif files. The fourth value (alpha value) is the measure of background opacity ranging between 0 and 100.
```
ascii-image-converter [image paths/urls] -Cnxyd 100,30 -m " .-=+#@" -s ./
ascii-image-converter [image paths/urls] -s . --save-bg 255,255,255,100 # For white background
```
#### --font
> **Note:** This flag will be ignored if `--save-img` or `--save-gif` flags are not set
This flag takes path to a font .ttf file that will be used to set font in saved png or gif files.
```
ascii-image-converter [image paths/urls] -s . --font /path/to/font-file.ttf
```
#### --font-color
This flag takes an RGB value that sets the font color in saved png and gif files as well as displayed ascii art in terminal.
```
ascii-image-converter [image paths/urls] -s . --font-color 0,0,0 # For black font color
```
#### --only-save
Don't print ascii art on the terminal if some saving flag is passed.
```
ascii-image-converter [image paths/urls] -s . --only-save
```
#### --formats
Display supported input formats.
```
ascii-image-converter --formats
```
<br>
## Library Usage
First import the library with:
> **Note:** The library may throw errors during Go tests due to some unresolved bugs with the [consolesize-go](https://github.com/nathan-fiscaletti/consolesize-go) package (Only during tests, not main program execution).
First, install the library with:
```
go get github.com/TheZoraiz/ascii-image-converter/aic_package
go get -u github.com/TheZoraiz/ascii-image-converter/aic_package
```
The library is to be used as follows:
For an image:
```go
package main
import (
"fmt"
"github.com/TheZoraiz/ascii-image-converter/aic_package"
)
func main() {
// If image is in current directory. This can also be a URL to an image.
imagePath := "myImage.jpeg"
// If file is in current directory. This can also be a URL to an image or gif.
filePath := "myImage.jpeg"
flags := aic_package.DefaultFlags()
// This part is optional. You can directly pass flags variable to ConvertImage() if you wish.
// For clarity, all flags are covered in this example.
flags["complex"] = true // Use complex character set
flags["dimensions"] = []int{50, 25} // 50 by 25 ascii art size
flags["savePath"] = "." // Saves to current directory
flags["negative"] = true // Ascii art will have negative color-depth
flags["colored"] = true // Keep colors from original image
flags["customMap"] = " .-=+#@" // Starting from darkest to brightest shades. This overrites "complex" flag
flags["flipX"] = true // Flips ascii art horizontally
flags["flipY"] = true // Flips ascii art vertically
// This part is optional.
// You can directly pass default flags variable to aic_package.Convert() if you wish.
// There are more flags, but these are the ones shown for demonstration
flags.Dimensions = []int{50, 25}
flags.Colored = true
flags.SaveTxtPath = "."
flags.SaveImagePath = "."
flags.CustomMap = " .-=+#@"
flags.FontFilePath = "./RobotoMono-Regular.ttf" // If file is in current directory
flags.SaveBackgroundColor = [4]int{50, 50, 50, 100}
// Return ascii art as a single string
asciiArt, err := aic_package.ConvertImage(imagePath, flags)
// Note: For environments where a terminal isn't available (such as web servers), you MUST
// specify atleast one of flags.Width, flags.Height or flags.Dimensions
// Conversion for an image
asciiArt, err := aic_package.Convert(filePath, flags)
if err != nil {
fmt.Println(err)
}
fmt.Printf("%v\n", asciiArt)
}
```
<br>
> **Note:** GIF conversion is not advised as the function may run infinitely, depending on the GIF. More work needs to be done on this to make it more library-compatible.
For a GIF:
```go
package main
import (
"fmt"
"github.com/TheZoraiz/ascii-image-converter/aic_package"
)
func main() {
filePath = "myGif.gif"
flags := aic_package.DefaultFlags()
_, err := aic_package.Convert(filePath, flags)
if err != nil {
fmt.Println(err)
}
}
```
<br>
@ -299,19 +532,21 @@ func main() {
You can fork the project and implement any changes you want for a pull request. However, for major changes, please open an issue first to discuss what you would like to implement.
## Packages used
## Packages Used
[github.com/spf13/cobra](https://github.com/spf13/cobra)
[github.com/fogleman/gg](https://github.com/fogleman/gg)
[github.com/mitchellh/go-homedir](https://github.com/mitchellh/go-homedir)
[github.com/nathan-fiscaletti/consolesize-go](https://github.com/nathan-fiscaletti/consolesize-go)
[github.com/nfnt/resize](https://github.com/nfnt/resize)
[github.com/disintegration/imaging](https://github.com/disintegration/imaging)
[github.com/gookit/color](https://github.com/gookit/color)
[github.com/asaskevich/govalidator](https://github.com/asaskevich/govalidator)
[github.com/makeworld-the-better-one/dither](https://github.com/makeworld-the-better-one/dither)
## License

Binary file not shown.

Binary file not shown.

276
aic_package/convert_gif.go Normal file
View File

@ -0,0 +1,276 @@
/*
Copyright © 2021 Zoraiz Hassan <hzoraiz8@gmail.com>
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package aic_package
import (
"bytes"
"fmt"
"image"
"image/color/palette"
"image/draw"
"image/gif"
"os"
"runtime"
"strconv"
"strings"
"sync"
"time"
imgManip "github.com/TheZoraiz/ascii-image-converter/image_manipulation"
)
type GifFrame struct {
asciiCharSet [][]imgManip.AsciiChar
delay int
}
/*
This function grabs each image frame from passed gif and turns it into ascii art. If SaveGifPath flag is passed,
it'll turn each ascii art into an image instance of the same dimensions as the original gif and save them
as an ascii art gif.
Multi-threading has been implemented in multiple places due to long execution time
*/
func pathIsGif(gifPath, urlImgName string, pathIsURl bool, urlImgBytes, pipedInputBytes []byte, localGif *os.File) error {
var (
originalGif *gif.GIF
err error
)
if gifPath == "-" {
originalGif, err = gif.DecodeAll(bytes.NewReader(pipedInputBytes))
} else if pathIsURl {
originalGif, err = gif.DecodeAll(bytes.NewReader(urlImgBytes))
} else {
originalGif, err = gif.DecodeAll(localGif)
}
if err != nil {
if gifPath == "-" {
return fmt.Errorf("can't decode piped input: %v", err)
} else {
return fmt.Errorf("can't decode %v: %v", gifPath, err)
}
}
var (
asciiArtSet = make([]string, len(originalGif.Image))
gifFramesSlice = make([]GifFrame, len(originalGif.Image))
counter = 0
concurrentProcesses = 0
wg sync.WaitGroup
hostCpuCount = runtime.NumCPU()
)
fmt.Printf("Generating ascii art... 0%%\r")
// Get first frame of gif and its dimensions
firstGifFrame := originalGif.Image[0].SubImage(originalGif.Image[0].Rect)
firstGifFrameWidth := firstGifFrame.Bounds().Dx()
firstGifFrameHeight := firstGifFrame.Bounds().Dy()
// Multi-threaded loop to decrease execution time
for i, frame := range originalGif.Image {
wg.Add(1)
concurrentProcesses++
go func(i int, frame *image.Paletted) {
frameImage := frame.SubImage(frame.Rect)
// If a frame is found that is smaller than the first frame, then this gif contains smaller subimages that are
// positioned inside the original gif. This behavior isn't supported by this app
if firstGifFrameWidth != frameImage.Bounds().Dx() || firstGifFrameHeight != frameImage.Bounds().Dy() {
if urlImgName == "" {
fmt.Printf("Error: " + gifPath + " contains subimages smaller than default width and height\n\nProcess aborted because ascii-image-converter doesn't support subimage placement and transparency in GIFs\n\n")
} else {
fmt.Printf("Error: " + urlImgName + " contains subimages smaller than default width and height\n\nProcess aborted because ascii-image-converter doesn't support subimage placement and transparency in GIFs\n\n")
}
os.Exit(0)
}
var imgSet [][]imgManip.AsciiPixel
imgSet, err = imgManip.ConvertToAsciiPixels(frameImage, dimensions, width, height, flipX, flipY, full, braille, dither)
if err != nil {
fmt.Printf("Error: %v\n", err)
os.Exit(0)
}
var asciiCharSet [][]imgManip.AsciiChar
if braille {
asciiCharSet, err = imgManip.ConvertToBrailleChars(imgSet, negative, colored, grayscale, colorBg, fontColor, threshold)
} else {
asciiCharSet, err = imgManip.ConvertToAsciiChars(imgSet, negative, colored, grayscale, complex, colorBg, customMap, fontColor)
}
if err != nil {
fmt.Printf("Error: %v\n", err)
os.Exit(0)
}
gifFramesSlice[i].asciiCharSet = asciiCharSet
gifFramesSlice[i].delay = originalGif.Delay[i]
ascii := flattenAscii(asciiCharSet, colored || grayscale, false)
asciiArtSet[i] = strings.Join(ascii, "\n")
counter++
percentage := int((float64(counter) / float64(len(originalGif.Image))) * 100)
fmt.Printf("Generating ascii art... " + strconv.Itoa(percentage) + "%%\r")
wg.Done()
}(i, frame)
// Limit concurrent processes according to host's CPU count to avoid overwhelming memory
if concurrentProcesses == hostCpuCount {
wg.Wait()
concurrentProcesses = 0
}
}
wg.Wait()
fmt.Printf(" \r")
// Save ascii art as .gif file before displaying it, if --save-gif flag is passed
if saveGifPath != "" {
// Storing save path string before executing ascii art to gif conversion
// This is done to avoid wasting time for invalid path errors
saveFileName, err := createSaveFileName(gifPath, urlImgName, "-ascii-art.gif")
if err != nil {
return err
}
fullPathName, err := getFullSavePath(saveFileName, saveGifPath)
if err != nil {
return fmt.Errorf("can't save file: %v", err)
}
// Initializing some constants for gif. Done outside loop to save execution
outGif := &gif.GIF{
LoopCount: originalGif.LoopCount,
}
opts := gif.Options{
NumColors: 256,
Drawer: draw.FloydSteinberg,
}
// Initializing slices for each ascii art image as well as delay
var (
palettedImageSlice = make([]*image.Paletted, len(gifFramesSlice))
delaySlice = make([]int, len(gifFramesSlice))
)
// For the purpose of displaying counter and limiting concurrent processes
counter = 0
concurrentProcesses = 0
fmt.Printf("Saving gif... 0%%\r")
// Multi-threaded loop to decrease execution time
for i, gifFrame := range gifFramesSlice {
wg.Add(1)
concurrentProcesses++
go func(i int, gifFrame GifFrame) {
img := originalGif.Image[i].SubImage(originalGif.Image[i].Rect)
tempImg, err := createGifFrameToSave(
gifFrame.asciiCharSet,
img,
colored || grayscale,
)
if err != nil {
fmt.Printf("Error: %v\n", err)
os.Exit(0)
}
// Following code takes tempImg as image.Image instance and converts it into *image.Paletted instance
b := tempImg.Bounds()
palettedImg := image.NewPaletted(b, palette.Plan9[:opts.NumColors])
opts.Drawer.Draw(palettedImg, b, tempImg, image.Point{})
palettedImageSlice[i] = palettedImg
delaySlice[i] = gifFrame.delay
counter++
percentage := int((float64(counter) / float64(len(gifFramesSlice))) * 100)
fmt.Printf("Saving gif... " + strconv.Itoa(percentage) + "%%\r")
wg.Done()
}(i, gifFrame)
// Limit concurrent processes according to host's CPU count to avoid overwhelming memory
if concurrentProcesses == hostCpuCount {
wg.Wait()
concurrentProcesses = 0
}
}
wg.Wait()
outGif.Image = palettedImageSlice
outGif.Delay = delaySlice
gifFile, err := os.OpenFile(fullPathName, os.O_WRONLY|os.O_CREATE, 0666)
if err != nil {
return fmt.Errorf("can't save file: %v", err)
}
defer gifFile.Close()
gif.EncodeAll(gifFile, outGif)
fmt.Printf(" \r")
fmt.Println("Saved " + fullPathName)
}
// Display the gif
if !onlySave {
loopCount := 0
for {
for i, asciiFrame := range asciiArtSet {
clearScreen()
fmt.Println(asciiFrame)
time.Sleep(time.Duration((time.Second * time.Duration(originalGif.Delay[i])) / 100))
}
// If gif is infinite loop
if originalGif.LoopCount == 0 {
continue
}
loopCount++
if loopCount == originalGif.LoopCount {
break
}
}
}
return nil
}

View File

@ -20,218 +20,85 @@ import (
"bytes"
"fmt"
"image"
"io/ioutil"
"net/http"
"os"
"path"
"strings"
// Image format initialization
_ "image/jpeg"
_ "image/png"
// Image format initialization
_ "golang.org/x/image/bmp"
_ "golang.org/x/image/tiff"
_ "golang.org/x/image/webp"
imgManip "github.com/TheZoraiz/ascii-image-converter/image_manipulation"
"github.com/asaskevich/govalidator"
)
// Return default configuration for flags
func DefaultFlags() map[string]interface{} {
return map[string]interface{}{
"complex": false,
"dimensions": nil,
"savePath": "",
"negative": false,
"colored": false,
"customMap": "",
"flipX": false,
"flipY": false,
}
}
// This function decodes the passed image and returns an ascii art string, optionaly saving it as a .txt and/or .png file
func pathIsImage(imagePath, urlImgName string, pathIsURl bool, urlImgBytes, pipedInputBytes []byte, localImg *os.File) (string, error) {
/*
ConvertImage takes an image path/url as its first argument
and a map of flags as the second argument, with which it alters
the returned ascii art string.
The "flags" argument should be declared as follows before passing:
flags := map[string]interface{}{
"complex": bool, // Pass true for using complex character set
"dimensions": []int, // Pass 2 integer dimensions. Pass nil to ignore
"savePath": string, // System path to save the ascii art string. Pass "" to ignore
"negative": bool, // Pass true for negative color-depth ascii art
"colored": bool, // Pass true for returning colored ascii string
"customMap": string, // Custom map of ascii chars e.g. " .-+#@" . Nullifies "complex" flag. Pass "" to ignore.
"flipX": bool, // Pass true to return horizontally flipped ascii art
"flipY": bool, // Pass true to return vertically flipped ascii art
}
*/
func ConvertImage(imagePath string, flags map[string]interface{}) (string, error) {
complex := flags["complex"].(bool)
var dimensions []int
if flags["dimensions"] == nil {
dimensions = nil
} else {
dimensions = flags["dimensions"].([]int)
}
savePath := flags["savePath"].(string)
negative := flags["negative"].(bool)
colored := flags["colored"].(bool)
customMap := flags["customMap"].(string)
flipX := flags["flipX"].(bool)
flipY := flags["flipY"].(bool)
numberOfDimensions := len(dimensions)
if dimensions != nil && numberOfDimensions != 2 {
return "", fmt.Errorf("requires 2 dimensions, got %v", numberOfDimensions)
}
// Declared at the start since some variables are initially used in conditional blocks
var (
pic *os.File
urlImgBytes []byte
urlImgName string = ""
err error
imData image.Image
err error
)
pathIsURl := govalidator.IsRequestURL(imagePath)
// Different modes of reading data depending upon whether or not imagePath is a url
if pathIsURl {
fmt.Printf("Fetching image from url...\r")
retrievedImage, err := http.Get(imagePath)
if err != nil {
return "", fmt.Errorf("can't fetching image: %v", err)
}
urlImgBytes, err = ioutil.ReadAll(retrievedImage.Body)
if err != nil {
return "", fmt.Errorf("failed to read fetched content: %v", err)
}
defer retrievedImage.Body.Close()
urlImgName = path.Base(imagePath)
fmt.Printf(" \r") // To erase "Fetching image from url..." text from console
} else {
pic, err = os.Open(imagePath)
if err != nil {
return "", fmt.Errorf("unable to open file: %v", err)
}
defer pic.Close()
}
var imData image.Image
if pathIsURl {
if imagePath == "-" {
imData, _, err = image.Decode(bytes.NewReader(pipedInputBytes))
} else if pathIsURl {
imData, _, err = image.Decode(bytes.NewReader(urlImgBytes))
} else {
imData, _, err = image.Decode(pic)
imData, _, err = image.Decode(localImg)
}
if err != nil {
return "", fmt.Errorf("error decoding %v. %v", imagePath, err)
}
imgSet, err := imgManip.ConvertToAsciiPixels(imData, dimensions, flipX, flipY)
if err != nil {
return "", fmt.Errorf("%v", err)
}
asciiSet := imgManip.ConvertToAscii(imgSet, negative, colored, complex, customMap)
ascii := flattenAscii(asciiSet, colored)
// Save ascii art before printing it, if --save flag is passed
if savePath != "" {
if err := saveAsciiArt(asciiSet, imagePath, savePath, urlImgName); err != nil {
fmt.Printf("Error: %v\n\n", err)
os.Exit(0) // Because this error will be thrown for every image passed to this function if we use "return"
}
}
result := strings.Join(ascii, "\n")
return result, nil
}
func checkOS() string {
if string(os.PathSeparator) == "/" && string(os.PathListSeparator) == ":" {
return "linux"
} else {
return "windows"
}
}
// flattenAscii flattens a two-dimensional grid of ascii characters into a one dimension
// of lines of ascii
func flattenAscii(asciiSet [][]imgManip.AsciiChar, color bool) []string {
var ascii []string
for _, line := range asciiSet {
var tempAscii []string
for i := 0; i < len(line); i++ {
if color {
tempAscii = append(tempAscii, line[i].Colored)
} else {
tempAscii = append(tempAscii, line[i].Simple)
}
}
ascii = append(ascii, strings.Join(tempAscii, ""))
}
return ascii
}
func saveAsciiArt(asciiSet [][]imgManip.AsciiChar, imagePath, savePath, urlImgName string) error {
// To make sure uncolored ascii art is the one saved
saveAscii := flattenAscii(asciiSet, false)
saveFileName, err := createSaveFileName(imagePath, urlImgName)
if err != nil {
return err
}
savePathLastChar := string(savePath[len(savePath)-1])
// Check if path is closed with appropriate path separator (depending on OS)
if savePathLastChar != string(os.PathSeparator) {
if checkOS() == "linux" {
savePath += "/"
if imagePath == "-" {
return "", fmt.Errorf("can't decode piped input: %v", err)
} else {
savePath += "\\"
return "", fmt.Errorf("can't decode %v: %v", imagePath, err)
}
}
// If path exists
if _, err := os.Stat(savePath); !os.IsNotExist(err) {
return ioutil.WriteFile(savePath+saveFileName, []byte(strings.Join(saveAscii, "\n")), 0666)
} else {
return fmt.Errorf("save path %v does exist", savePath)
}
}
func createSaveFileName(imagePath string, urlImgName string) (string, error) {
if urlImgName != "" {
return urlImgName + "-ascii-art.txt", nil
}
fileInfo, err := os.Stat(imagePath)
imgSet, err := imgManip.ConvertToAsciiPixels(imData, dimensions, width, height, flipX, flipY, full, braille, dither)
if err != nil {
return "", err
}
currName := fileInfo.Name()
currExt := path.Ext(currName)
newName := currName[:len(currName)-len(currExt)] // e.g. Grabs myImage from myImage.jpeg
var asciiSet [][]imgManip.AsciiChar
// Something like myImage.jpeg-ascii-art.txt
return newName + "." + currExt[1:] + "-ascii-art.txt", nil
if braille {
asciiSet, err = imgManip.ConvertToBrailleChars(imgSet, negative, colored, grayscale, colorBg, fontColor, threshold)
} else {
asciiSet, err = imgManip.ConvertToAsciiChars(imgSet, negative, colored, grayscale, complex, colorBg, customMap, fontColor)
}
if err != nil {
return "", err
}
// Save ascii art as .png image before printing it, if --save-img flag is passed
if saveImagePath != "" {
if err := createImageToSave(
asciiSet,
colored || grayscale,
saveImagePath,
imagePath,
urlImgName,
onlySave,
); err != nil {
return "", fmt.Errorf("can't save file: %v", err)
}
}
// Save ascii art as .txt file before printing it, if --save-txt flag is passed
if saveTxtPath != "" {
if err := saveAsciiArt(
asciiSet,
imagePath,
saveTxtPath,
urlImgName,
onlySave,
); err != nil {
return "", fmt.Errorf("can't save file: %v", err)
}
}
ascii := flattenAscii(asciiSet, colored || grayscale, false)
result := strings.Join(ascii, "\n")
if onlySave {
return "", nil
}
return result, nil
}

207
aic_package/convert_root.go Normal file
View File

@ -0,0 +1,207 @@
/*
Copyright © 2021 Zoraiz Hassan <hzoraiz8@gmail.com>
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package aic_package
import (
"fmt"
"io/ioutil"
"net/http"
"os"
"path"
// Image format initialization
_ "image/jpeg"
_ "image/png"
// Image format initialization
_ "golang.org/x/image/bmp"
_ "golang.org/x/image/tiff"
_ "golang.org/x/image/webp"
"github.com/golang/freetype/truetype"
)
var pipedInputTypes = []string{
"image/png",
"image/jpeg",
"image/webp",
"image/tiff",
"image/bmp",
}
// Return default configuration for flags.
// Can be sent directly to ConvertImage() for default ascii art
func DefaultFlags() Flags {
return Flags{
Complex: false,
Dimensions: nil,
Width: 0,
Height: 0,
SaveTxtPath: "",
SaveImagePath: "",
SaveGifPath: "",
Negative: false,
Colored: false,
CharBackgroundColor: false,
Grayscale: false,
CustomMap: "",
FlipX: false,
FlipY: false,
Full: false,
FontFilePath: "",
FontColor: [3]int{255, 255, 255},
SaveBackgroundColor: [4]int{0, 0, 0, 100},
Braille: false,
Threshold: 128,
Dither: false,
OnlySave: false,
}
}
/*
Convert() takes an image or gif path/url as its first argument
and a aic_package.Flags literal as the second argument, with which it alters
the returned ascii art string.
*/
func Convert(filePath string, flags Flags) (string, error) {
if flags.Dimensions == nil {
dimensions = nil
} else {
dimensions = flags.Dimensions
}
width = flags.Width
height = flags.Height
complex = flags.Complex
saveTxtPath = flags.SaveTxtPath
saveImagePath = flags.SaveImagePath
saveGifPath = flags.SaveGifPath
negative = flags.Negative
colored = flags.Colored
colorBg = flags.CharBackgroundColor
grayscale = flags.Grayscale
customMap = flags.CustomMap
flipX = flags.FlipX
flipY = flags.FlipY
full = flags.Full
fontPath = flags.FontFilePath
fontColor = flags.FontColor
saveBgColor = flags.SaveBackgroundColor
braille = flags.Braille
threshold = flags.Threshold
dither = flags.Dither
onlySave = flags.OnlySave
inputIsGif = path.Ext(filePath) == ".gif"
// Declared at the start since some variables are initially used in conditional blocks
var (
localFile *os.File
urlImgBytes []byte
urlImgName string = ""
pipedInputBytes []byte
err error
)
pathIsURl := isURL(filePath)
// Different modes of reading data depending upon whether or not filePath is a url
if filePath != "-" {
if pathIsURl {
fmt.Printf("Fetching file from url...\r")
retrievedImage, err := http.Get(filePath)
if err != nil {
return "", fmt.Errorf("can't fetch content: %v", err)
}
urlImgBytes, err = ioutil.ReadAll(retrievedImage.Body)
if err != nil {
return "", fmt.Errorf("failed to read fetched content: %v", err)
}
defer retrievedImage.Body.Close()
urlImgName = path.Base(filePath)
fmt.Printf(" \r") // To erase "Fetching image from url..." text from terminal
} else {
localFile, err = os.Open(filePath)
if err != nil {
return "", fmt.Errorf("unable to open file: %v", err)
}
defer localFile.Close()
}
} else {
// Check file/data type of piped input
if !isInputFromPipe() {
return "", fmt.Errorf("there is no input being piped to stdin")
}
pipedInputBytes, err = ioutil.ReadAll(os.Stdin)
if err != nil {
return "", fmt.Errorf("unable to read piped input: %v", err)
}
fileType := http.DetectContentType(pipedInputBytes)
invalidInput := true
if fileType == "image/gif" {
inputIsGif = true
invalidInput = false
} else {
for _, inputType := range pipedInputTypes {
if fileType == inputType {
invalidInput = false
break
}
}
}
// Not sure if I should uncomment this.
// The output may be piped to another program and a warning would contaminate that
if invalidInput {
// fmt.Println("Warning: file type of piped input could not be determined, treating it as an image")
}
}
// If path to font file is provided, use it
if fontPath != "" {
fontFile, err := ioutil.ReadFile(fontPath)
if err != nil {
return "", fmt.Errorf("unable to open font file: %v", err)
}
// tempFont is globally declared in aic_package/create_ascii_image.go
if tempFont, err = truetype.Parse(fontFile); err != nil {
return "", fmt.Errorf("unable to parse font file: %v", err)
}
} else if braille {
tempFont, _ = truetype.Parse(embeddedDejaVuObliqueFont)
}
if inputIsGif {
return "", pathIsGif(filePath, urlImgName, pathIsURl, urlImgBytes, pipedInputBytes, localFile)
} else {
return pathIsImage(filePath, urlImgName, pathIsURl, urlImgBytes, pipedInputBytes, localFile)
}
}

View File

@ -0,0 +1,140 @@
/*
Copyright © 2021 Zoraiz Hassan <hzoraiz8@gmail.com>
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package aic_package
import (
"image"
"image/color"
_ "embed"
imgManip "github.com/TheZoraiz/ascii-image-converter/image_manipulation"
"github.com/golang/freetype/truetype"
"github.com/fogleman/gg"
)
/*
Unlike createImageToSave(), this function is optimized to maintain original image dimensions and shrink ascii
art font size to match it. This allows for greater execution speed, which is necessary since a gif contains
multiple images that need to be converted to ascii art, and the potential loss of ascii art quality (since
large ascii art instances will shrink the font too much).
Furthermore, maintaining original gif's width and height also allows for gifs of smaller size.
*/
func createGifFrameToSave(asciiArt [][]imgManip.AsciiChar, img image.Image, colored bool) (image.Image, error) {
// Original image dimensions
x := img.Bounds().Dx()
y := img.Bounds().Dy()
// Ascii art dimensions
asciiWidth := len(asciiArt[0])
asciiHeight := len(asciiArt)
// Iterators to move pointer on the image to be made
var xIter float64
var yIter float64
var fontSize float64
// Conditions to alter resulting ascii gif dimensions according to ascii art dimensions
if asciiWidth > asciiHeight*2 {
yIter = float64(y) / float64(asciiHeight)
xIter = yIter / 2
x = int(xIter * float64(asciiWidth))
fontSize = xIter
} else {
xIter = float64(x) / float64(asciiWidth)
yIter = xIter * 2
y = int(yIter * float64(asciiHeight))
fontSize = xIter
}
// 10 extra pixels on both x and y-axis to have 5 pixels of padding on each side
x += 10
y += 10
tempImg := image.NewRGBA(image.Rect(0, 0, x, y))
dc := gg.NewContext(x, y)
// Set image background
dc.SetRGB(
float64(saveBgColor[0])/255,
float64(saveBgColor[1])/255,
float64(saveBgColor[2])/255,
)
dc.Clear()
dc.DrawImage(tempImg, 0, 0)
// Font size increased during assignment to become more visible. This will not affect image drawing
fontFace := truetype.NewFace(tempFont, &truetype.Options{Size: fontSize * 1.5})
dc.SetFontFace(fontFace)
// Font color of text on picture is white by default
dc.SetColor(color.White)
// Pointer to track y-axis on the image frame
yImgPointer := 5.0
// These nested loops print each character in asciArt 2D slice separately
// so that their RGB colors can be maintained in the resulting image
for _, line := range asciiArt {
// Pointer to track x-axis on the image frame
xImgPointer := 5.0
for _, char := range line {
if colored {
// dc.SetColor() sets color for EACH character before printing it
r := uint8(char.RgbValue[0])
g := uint8(char.RgbValue[1])
b := uint8(char.RgbValue[2])
dc.SetColor(color.RGBA{r, g, b, 255})
} else {
r := uint8(fontColor[0])
g := uint8(fontColor[1])
b := uint8(fontColor[2])
dc.SetColor(color.RGBA{r, g, b, 255})
}
dc.DrawStringWrapped(char.Simple, xImgPointer, yImgPointer, 0, 0, float64(x), 1.8, gg.AlignLeft)
// Incremet x-axis pointer character so new one can be printed after it
// Set to the same constant as in line
xImgPointer += xIter
}
dc.DrawStringWrapped("\n", xImgPointer, yImgPointer, 0, 0, float64(x), 1.8, gg.AlignLeft)
// Incremet pointer for y axis after every line printed, so
// new line can start at below the previous one on next iteration
yImgPointer += yIter
}
return dc.Image(), nil
}

View File

@ -0,0 +1,148 @@
/*
Copyright © 2021 Zoraiz Hassan <hzoraiz8@gmail.com>
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package aic_package
import (
"fmt"
"image"
"image/color"
_ "embed"
imgManip "github.com/TheZoraiz/ascii-image-converter/image_manipulation"
"github.com/fogleman/gg"
"github.com/golang/freetype/truetype"
)
//go:embed Hack-Regular.ttf
var embeddedHackRegularFont []byte
//go:embed DejaVuSans-Oblique.ttf
var embeddedDejaVuObliqueFont []byte
var tempFont *truetype.Font
// Load embedded font
func init() {
tempFont, _ = truetype.Parse(embeddedHackRegularFont)
}
/*
Unlike createGifFrameToSave(), this function is altered to ignore execution time and has a fixed font size.
This creates maximum quality ascii art, although the resulting image will not have the same dimensions
as the original image, but the ascii art quality will be maintained. This is required, since smaller provided
images will considerably decrease ascii art quality because of smaller font size.
Size of resulting image may also be considerably larger than original image.
*/
func createImageToSave(asciiArt [][]imgManip.AsciiChar, colored bool, saveImagePath, imagePath, urlImgName string, onlySave bool) error {
constant := 14.0
x := len(asciiArt[0])
y := len(asciiArt)
// Multipying resulting image dimensions with respect to constant
x = int(constant * float64(x))
y = int(constant * float64(y))
y = y * 2
// 10 extra pixels on both x and y-axis to have 5 pixels of padding on each side
y += 10
x += 10
tempImg := image.NewRGBA(image.Rect(0, 0, x, y))
imgWidth := tempImg.Bounds().Dx()
imgHeight := tempImg.Bounds().Dy()
dc := gg.NewContext(imgWidth, imgHeight)
// Set image background
dc.SetRGBA(
float64(saveBgColor[0])/255,
float64(saveBgColor[1])/255,
float64(saveBgColor[2])/255,
float64(saveBgColor[3])/100,
)
dc.Clear()
dc.DrawImage(tempImg, 0, 0)
fontFace := truetype.NewFace(tempFont, &truetype.Options{Size: constant * 1.5})
dc.SetFontFace(fontFace)
// Font color of text on picture is white by default
dc.SetColor(color.White)
// Pointer to track y-axis on the image frame
yImgPointer := 5.0
// These nested loops print each character in asciArt 2D slice separately
// so that their RGB colors can be maintained in the resulting image
for _, line := range asciiArt {
// Pointer to track x-axis on the image frame
xImgPointer := 5.0
for _, char := range line {
if colored {
// dc.SetColor() sets color for EACH character before printing it
r := uint8(char.RgbValue[0])
g := uint8(char.RgbValue[1])
b := uint8(char.RgbValue[2])
dc.SetColor(color.RGBA{r, g, b, 255})
} else {
r := uint8(fontColor[0])
g := uint8(fontColor[1])
b := uint8(fontColor[2])
dc.SetColor(color.RGBA{r, g, b, 255})
}
dc.DrawStringWrapped(char.Simple, xImgPointer, yImgPointer, 0, 0, float64(x), 1.8, gg.AlignLeft)
// Incremet x-axis pointer character so new one can be printed after it
// Set to the same constant as in line
xImgPointer += float64(constant)
}
dc.DrawStringWrapped("\n", xImgPointer, yImgPointer, 0, 0, float64(x), 1.8, gg.AlignLeft)
// Incremet pointer for y axis after every line printed, so
// new line can start at below the previous one on next iteration
yImgPointer += float64(constant * 2)
}
imageName, err := createSaveFileName(imagePath, urlImgName, "-ascii-art.png")
if err != nil {
return err
}
fullPathName, err := getFullSavePath(imageName, saveImagePath)
if err != nil {
return err
}
if onlySave {
fmt.Println("Saved " + fullPathName)
}
return dc.SavePNG(fullPathName)
}

3
aic_package/readme.md Normal file
View File

@ -0,0 +1,3 @@
## Note
The font `DejaVuSans-Oblique.ttf` is used for saving braille art .png images since it supports unicode and gave the best results. `Hack-Regular.ttf` is used for saving normal ascii art .png images.

175
aic_package/util.go Normal file
View File

@ -0,0 +1,175 @@
/*
Copyright © 2021 Zoraiz Hassan <hzoraiz8@gmail.com>
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package aic_package
import (
"fmt"
"io/ioutil"
"os"
"os/exec"
"path"
"runtime"
"strings"
imgManip "github.com/TheZoraiz/ascii-image-converter/image_manipulation"
)
func saveAsciiArt(asciiSet [][]imgManip.AsciiChar, imagePath, savePath, urlImgName string, onlySave bool) error {
// To make sure uncolored ascii art is the one saved as .txt
saveAscii := flattenAscii(asciiSet, false, true)
saveFileName, err := createSaveFileName(imagePath, urlImgName, "-ascii-art.txt")
if err != nil {
return err
}
savePathLastChar := string(savePath[len(savePath)-1])
// Check if path is closed with appropriate path separator (depending on OS)
if savePathLastChar != string(os.PathSeparator) {
savePath += string(os.PathSeparator)
}
// If path exists
if _, err := os.Stat(savePath); !os.IsNotExist(err) {
err := ioutil.WriteFile(savePath+saveFileName, []byte(strings.Join(saveAscii, "\n")), 0666)
if err != nil {
return err
} else if onlySave {
fmt.Println("Saved " + savePath + saveFileName)
}
return nil
} else {
return fmt.Errorf("save path %v does not exist", savePath)
}
}
// Returns new image file name along with extension
func createSaveFileName(imagePath, urlImgName, label string) (string, error) {
if urlImgName != "" {
currExt := path.Ext(urlImgName)
newName := urlImgName[:len(urlImgName)-len(currExt)] // e.g. Grabs myImage from myImage.jpeg
return newName + label, nil
}
if imagePath == "-" {
if inputIsGif {
return "piped-gif" + label, nil
}
return "piped-img" + label, nil
}
fileInfo, err := os.Stat(imagePath)
if err != nil {
return "", err
}
currName := fileInfo.Name()
currExt := path.Ext(currName)
newName := currName[:len(currName)-len(currExt)] // e.g. Grabs myImage from myImage.jpeg
return newName + label, nil
}
// flattenAscii flattens a two-dimensional grid of ascii characters into a one dimension
// of lines of ascii
func flattenAscii(asciiSet [][]imgManip.AsciiChar, colored, toSaveTxt bool) []string {
var ascii []string
for _, line := range asciiSet {
var tempAscii string
for _, char := range line {
if toSaveTxt {
tempAscii += char.Simple
continue
}
if colored {
tempAscii += char.OriginalColor
} else if fontColor != [3]int{255, 255, 255} {
tempAscii += char.SetColor
} else {
tempAscii += char.Simple
}
}
ascii = append(ascii, tempAscii)
}
return ascii
}
// Returns path with the file name concatenated to it
func getFullSavePath(imageName, saveFilePath string) (string, error) {
savePathLastChar := string(saveFilePath[len(saveFilePath)-1])
// Check if path is closed with appropriate path separator (depending on OS)
if savePathLastChar != string(os.PathSeparator) {
saveFilePath += string(os.PathSeparator)
}
// If path exists
if _, err := os.Stat(saveFilePath); !os.IsNotExist(err) {
return saveFilePath + imageName, nil
} else {
return "", err
}
}
func isURL(urlString string) bool {
if len(urlString) < 8 {
return false
} else if urlString[:7] == "http://" || urlString[:8] == "https://" {
return true
}
return false
}
// Following is for clearing screen when showing gif
var clear map[string]func()
func init() {
clear = make(map[string]func())
clear["linux"] = func() {
cmd := exec.Command("clear")
cmd.Stdout = os.Stdout
cmd.Run()
}
clear["windows"] = func() {
cmd := exec.Command("cmd", "/c", "cls")
cmd.Stdout = os.Stdout
cmd.Run()
}
clear["darwin"] = clear["linux"]
}
func clearScreen() {
value, ok := clear[runtime.GOOS]
if ok {
value()
} else {
fmt.Println("Error: your platform is unsupported, terminal can't be cleared")
os.Exit(0)
}
}
func isInputFromPipe() bool {
fileInfo, _ := os.Stdin.Stat()
return fileInfo.Mode()&os.ModeCharDevice == 0
}

131
aic_package/vars.go Normal file
View File

@ -0,0 +1,131 @@
/*
Copyright © 2021 Zoraiz Hassan <hzoraiz8@gmail.com>
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package aic_package
type Flags struct {
// Set dimensions of ascii art. Accepts a slice of 2 integers
// e.g. []int{60,30}.
// This overrides Flags.Width and Flags.Height
Dimensions []int
// Set width of ascii art while calculating height from aspect ratio.
// Setting this along with Flags.Height will throw an error
Width int
// Set height of ascii art while calculating width from aspect ratio.
// Setting this along with Flags.Width will throw an error
Height int
// Use set of 69 characters instead of the default 10
Complex bool
// Path to save ascii art .txt file
SaveTxtPath string
// Path to save ascii art .png file
SaveImagePath string
// Path to save ascii art .gif file, if gif is passed
SaveGifPath string
// Invert ascii art character mapping as well as colors
Negative bool
// Keep colors from the original image. This uses the True color codes for
// the terminal and will work on saved .png and .gif files as well.
// This overrides Flags.Grayscale and Flags.FontColor
Colored bool
// If Flags.Colored, Flags.Grayscale or Flags.FontColor is set, use that color
// on each character's background in the terminal
CharBackgroundColor bool
// Keep grayscale colors from the original image. This uses the True color
// codes for the terminal and will work on saved .png and .gif files as well
// This overrides Flags.FontColor
Grayscale bool
// Pass custom ascii art characters as a string.
// e.g. " .-=+#@".
// This overrides Flags.Complex
CustomMap string
// Flip ascii art horizontally
FlipX bool
// Flip ascii art vertically
FlipY bool
// Use terminal width to calculate ascii art size while keeping aspect ratio.
// This overrides Flags.Dimensions, Flags.Width and Flags.Height
Full bool
// File path to a font .ttf file to use when saving ascii art gif or png file.
// This will be ignored if Flags.SaveImagePath or Flags.SaveGifPath are not set
FontFilePath string
// Font RGB color for terminal display and saved png or gif files.
FontColor [3]int
// Background RGB color in saved png or gif files.
// This will be ignored if Flags.SaveImagePath or Flags.SaveGifPath are not set
SaveBackgroundColor [4]int
// Use braille characters instead of ascii. Terminal must support UTF-8 encoding.
// Otherwise, problems may be encountered with colored or even uncolored braille art.
// This overrides Flags.Complex and Flags.CustomMap
Braille bool
// Threshold for braille art if Flags.Braille is set to true. Value provided must
// be between 0 and 255. Ideal value is 128.
// This will be ignored if Flags.Braille is not set
Threshold int
// Apply FloydSteinberg dithering on an image before ascii conversion. This option
// is meant for braille art. Therefore, it will be ignored if Flags.Braille is false
Dither bool
// If Flags.SaveImagePath, Flags.SaveTxtPath or Flags.SaveGifPath are set, then don't
// print on terminal
OnlySave bool
}
var (
dimensions []int
width int
height int
complex bool
saveTxtPath string
saveImagePath string
saveGifPath string
grayscale bool
negative bool
colored bool
colorBg bool
customMap string
flipX bool
flipY bool
full bool
fontPath string
fontColor [3]int
saveBgColor [4]int
braille bool
threshold int
dither bool
onlySave bool
inputIsGif bool
)

View File

@ -0,0 +1,3 @@
## Note
These files are just wrappers around [consolesize-go](https://github.com/nathan-fiscaletti/consolesize-go) package. For unix, they resort to terminal size calculation from stdin if stdout is not directed to terminal. For windows, they currently throw an error.

View File

@ -0,0 +1,51 @@
//go:build (unix && ignore) || !windows
// +build unix,ignore !windows
package winsize
import (
"os"
"syscall"
"unsafe"
"github.com/nathan-fiscaletti/consolesize-go"
)
// By default, this functions calculates terminal dimensions from stdout but in case
// stdout isn't a the terminal, it'll calculate terminal dimensions from stdin. This
// functionality isn't supported for windows yet
func GetTerminalSize() (int, int, error) {
// Check if stdout is terminal
fileInfo, err := os.Stdout.Stat()
if err != nil {
return 0, 0, err
}
var stdoutIsTerminal bool
if (fileInfo.Mode() & os.ModeCharDevice) != 0 {
stdoutIsTerminal = true
} else {
stdoutIsTerminal = false
}
if stdoutIsTerminal {
x, y := consolesize.GetConsoleSize()
return x, y, nil
} else {
// Get size from stdin if stdout is not terminal
var sz struct {
rows uint16
cols uint16
xpixels uint16
ypixels uint16
}
_, _, _ = syscall.Syscall(syscall.SYS_IOCTL,
uintptr(syscall.Stdin), uintptr(syscall.TIOCGWINSZ), uintptr(unsafe.Pointer(&sz)))
return int(sz.cols), int(sz.rows), nil
}
}

View File

@ -0,0 +1,22 @@
//go:build (windows && ignore) || !unix
// +build windows,ignore !unix
package winsize
import (
"fmt"
"github.com/nathan-fiscaletti/consolesize-go"
)
// By default, this functions calculates terminal dimensions from stdout but in case
// stdout isn't a the terminal, it'll throw an error instead of panicking.
func GetTerminalSize() (int, int, error) {
x, y := consolesize.GetConsoleSize()
if x < 1 && y < 1 {
return x, y, fmt.Errorf("altering stdout isn't currently supported on windows")
} else {
return x, y, nil
}
}

View File

@ -13,6 +13,7 @@ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package cmd
import (
@ -21,15 +22,6 @@ import (
"github.com/TheZoraiz/ascii-image-converter/aic_package"
// Image format initialization
_ "image/jpeg"
_ "image/png"
// Image format initialization
_ "golang.org/x/image/bmp"
_ "golang.org/x/image/tiff"
_ "golang.org/x/image/webp"
homedir "github.com/mitchellh/go-homedir"
"github.com/spf13/cobra"
"github.com/spf13/viper"
@ -37,70 +29,104 @@ import (
var (
// Flags
cfgFile string
complex bool
dimensions []int
savePath string
negative bool
formatsTrue bool
colored bool
customMap string
flipX bool
flipY bool
cfgFile string
complex bool
dimensions []int
width int
height int
saveTxtPath string
saveImagePath string
saveGifPath string
negative bool
formatsTrue bool
colored bool
colorBg bool
grayscale bool
customMap string
flipX bool
flipY bool
full bool
fontFile string
fontColor []int
saveBgColor []int
braille bool
threshold int
dither bool
onlySave bool
// Root commands
rootCmd = &cobra.Command{
Use: "ascii-image-converter [image paths/urls]",
Short: "Converts images into ascii art",
Version: "1.2.6",
Use: "ascii-image-converter [image paths/urls or piped stdin]",
Short: "Converts images and gifs into ascii art",
Version: "1.13.1",
Long: "This tool converts images into ascii art and prints them on the terminal.\nFurther configuration can be managed with flags.",
// Not RunE since help text is getting larger and seeing it for every error impacts user experience
Run: func(cmd *cobra.Command, args []string) {
if formatsTrue {
fmt.Printf("Supported image formats: JPEG/JPG, PNG, WEBP, BMP, TIFF/TIF\n\n")
if checkInputAndFlags(args) {
return
}
if len(args) < 1 {
fmt.Printf("Error: Need at least 1 image path/url\n\n")
cmd.Help()
flags := aic_package.Flags{
Complex: complex,
Dimensions: dimensions,
Width: width,
Height: height,
SaveTxtPath: saveTxtPath,
SaveImagePath: saveImagePath,
SaveGifPath: saveGifPath,
Negative: negative,
Colored: colored,
CharBackgroundColor: colorBg,
Grayscale: grayscale,
CustomMap: customMap,
FlipX: flipX,
FlipY: flipY,
Full: full,
FontFilePath: fontFile,
FontColor: [3]int{fontColor[0], fontColor[1], fontColor[2]},
SaveBackgroundColor: [4]int{saveBgColor[0], saveBgColor[1], saveBgColor[2], saveBgColor[3]},
Braille: braille,
Threshold: threshold,
Dither: dither,
OnlySave: onlySave,
}
if args[0] == "-" {
printAscii(args[0], flags)
return
}
if customMap != "" && len(customMap) < 2 {
fmt.Printf("Need at least 2 characters for --map flag\n\n")
return
}
flags := map[string]interface{}{
"complex": complex,
"dimensions": dimensions,
"savePath": savePath,
"negative": negative,
"colored": colored,
"customMap": customMap,
"flipX": flipX,
"flipY": flipY,
}
// fmt.Println(flags)
for _, imagePath := range args {
if asciiArt, err := aic_package.ConvertImage(imagePath, flags); err == nil {
fmt.Printf("%s", asciiArt)
} else {
fmt.Printf("Error: %v", err)
if err := printAscii(imagePath, flags); err != nil {
return
}
fmt.Println()
}
},
}
)
func printAscii(imagePath string, flags aic_package.Flags) error {
if asciiArt, err := aic_package.Convert(imagePath, flags); err == nil {
fmt.Printf("%s", asciiArt)
} else {
fmt.Printf("Error: %v\n", err)
// Because this error will then be thrown for every image path/url passed
// if save path is invalid
if err.Error()[:15] == "can't save file" {
fmt.Println()
return err
}
}
if !onlySave {
fmt.Println()
}
return nil
}
// Cobra configuration from here on
func Execute() {
@ -113,22 +139,43 @@ func Execute() {
func init() {
cobra.OnInitialize(initConfig)
rootCmd.PersistentFlags().SortFlags = false
rootCmd.Flags().SortFlags = false
// rootCmd.PersistentFlags().StringVar(&cfgFile, "config", "", "config file (default is $HOME/.ascii-image-converter.yaml)")
rootCmd.PersistentFlags().BoolVarP(&colored, "color", "C", false, "Display ascii art with the colors from original image\n(Can work with the -n flag)\n")
rootCmd.PersistentFlags().BoolVarP(&colored, "color", "C", false, "Display ascii art with original colors\nIf 24-bit colors aren't supported, uses 8-bit\n(Inverts with --negative flag)\n(Overrides --grayscale and --font-color flags)\n")
rootCmd.PersistentFlags().BoolVar(&colorBg, "color-bg", false, "If some color flag is passed, use that color\non character background instead of foreground\n(Inverts with --negative flag)\n(Only applicable for terminal display)\n")
rootCmd.PersistentFlags().IntSliceVarP(&dimensions, "dimensions", "d", nil, "Set width and height for ascii art in CHARACTER length\ne.g. -d 60,30 (defaults to terminal height)\n(Overrides --width and --height flags)\n")
rootCmd.PersistentFlags().IntVarP(&width, "width", "W", 0, "Set width for ascii art in CHARACTER length\nHeight is kept to aspect ratio\ne.g. -W 60\n")
rootCmd.PersistentFlags().IntVarP(&height, "height", "H", 0, "Set height for ascii art in CHARACTER length\nWidth is kept to aspect ratio\ne.g. -H 60\n")
rootCmd.PersistentFlags().StringVarP(&customMap, "map", "m", "", "Give custom ascii characters to map against\nOrdered from darkest to lightest\ne.g. -m \" .-+#@\" (Quotation marks excluded from map)\n(Overrides --complex flag)\n")
rootCmd.PersistentFlags().BoolVarP(&braille, "braille", "b", false, "Use braille characters instead of ascii\nTerminal must support braille patterns properly\n(Overrides --complex and --map flags)\n")
rootCmd.PersistentFlags().IntVar(&threshold, "threshold", 0, "Threshold for braille art\nValue between 0-255 is accepted\ne.g. --threshold 170\n(Defaults to 128)\n")
rootCmd.PersistentFlags().BoolVar(&dither, "dither", false, "Apply dithering on image for braille\nart conversion\n(Only applicable with --braille flag)\n(Negates --threshold flag)\n")
rootCmd.PersistentFlags().BoolVarP(&grayscale, "grayscale", "g", false, "Display grayscale ascii art\n(Inverts with --negative flag)\n(Overrides --font-color flag)\n")
rootCmd.PersistentFlags().BoolVarP(&complex, "complex", "c", false, "Display ascii characters in a larger range\nMay result in higher quality\n")
rootCmd.PersistentFlags().IntSliceVarP(&dimensions, "dimensions", "d", nil, "Set width and height for ascii art in CHARACTER length\ne.g. -d 100,30 (defaults to terminal height)\n")
rootCmd.PersistentFlags().BoolVarP(&formatsTrue, "formats", "f", false, "Display supported image formats\n")
rootCmd.PersistentFlags().StringVarP(&customMap, "map", "m", "", "Give custom ascii characters to map against\nOrdered from darkest to lightest\ne.g. -m \" .-+#@\" (Quotation marks excluded from map)\n(Cancels --complex flag)\n")
rootCmd.PersistentFlags().BoolVarP(&negative, "negative", "n", false, "Display ascii art in negative colors\n(Can work with the --color flag)\n")
rootCmd.PersistentFlags().StringVarP(&savePath, "save", "s", "", "Save ascii art in the format:\n<image-name>.<image-extension>-ascii-art.txt\nFile will be saved in passed path\n(pass . for current directory)\n")
rootCmd.PersistentFlags().BoolVarP(&full, "full", "f", false, "Use largest dimensions for ascii art\nthat fill the terminal width\n(Overrides --dimensions, --width and --height flags)\n")
rootCmd.PersistentFlags().BoolVarP(&negative, "negative", "n", false, "Display ascii art in negative colors\n")
rootCmd.PersistentFlags().BoolVarP(&flipX, "flipX", "x", false, "Flip ascii art horizontally\n")
rootCmd.PersistentFlags().BoolVarP(&flipY, "flipY", "y", false, "Flip ascii art vertically\n")
rootCmd.PersistentFlags().StringVarP(&saveImagePath, "save-img", "s", "", "Save ascii art as a .png file\nFormat: <image-name>-ascii-art.png\nImage will be saved in passed path\n(pass . for current directory)\n")
rootCmd.PersistentFlags().StringVar(&saveTxtPath, "save-txt", "", "Save ascii art as a .txt file\nFormat: <image-name>-ascii-art.txt\nFile will be saved in passed path\n(pass . for current directory)\n")
rootCmd.PersistentFlags().StringVar(&saveGifPath, "save-gif", "", "If input is a gif, save it as a .gif file\nFormat: <gif-name>-ascii-art.gif\nGif will be saved in passed path\n(pass . for current directory)\n")
rootCmd.PersistentFlags().IntSliceVar(&saveBgColor, "save-bg", nil, "Set background color for --save-img\nand --save-gif flags\nPass an RGBA value\ne.g. --save-bg 255,255,255,100\n(Defaults to 0,0,0,100)\n")
rootCmd.PersistentFlags().StringVar(&fontFile, "font", "", "Set font for --save-img and --save-gif flags\nPass file path to font .ttf file\ne.g. --font ./RobotoMono-Regular.ttf\n(Defaults to Hack-Regular for ascii and\n DejaVuSans-Oblique for braille)\n")
rootCmd.PersistentFlags().IntSliceVar(&fontColor, "font-color", nil, "Set font color for terminal as well as\n--save-img and --save-gif flags\nPass an RGB value\ne.g. --font-color 0,0,0\n(Defaults to 255,255,255)\n")
rootCmd.PersistentFlags().BoolVar(&onlySave, "only-save", false, "Don't print ascii art on terminal\nif some saving flag is passed\n")
rootCmd.PersistentFlags().BoolVar(&formatsTrue, "formats", false, "Display supported input formats\n")
rootCmd.PersistentFlags().BoolP("help", "h", false, "Help for "+rootCmd.Name()+"\n")
rootCmd.PersistentFlags().BoolP("version", "v", false, "Version for "+rootCmd.Name())
rootCmd.SetVersionTemplate("{{printf \"v%s\" .Version}}\n")
defaultUsageTemplate := rootCmd.UsageTemplate()
rootCmd.SetUsageTemplate("\nCopyright © 2021 Zoraiz Hassan <hzoraiz8@gmail.com>\n" +
rootCmd.SetUsageTemplate(defaultUsageTemplate + "\nCopyright © 2021 Zoraiz Hassan <hzoraiz8@gmail.com>\n" +
"Distributed under the Apache License Version 2.0 (Apache-2.0)\n" +
"For further details, visit https://github.com/TheZoraiz/ascii-image-converter\n\n" +
defaultUsageTemplate)
"For further details, visit https://github.com/TheZoraiz/ascii-image-converter\n")
}
// initConfig reads in config file and ENV variables if set.

181
cmd/util.go Normal file
View File

@ -0,0 +1,181 @@
/*
Copyright © 2021 Zoraiz Hassan <hzoraiz8@gmail.com>
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package cmd
import (
"fmt"
"path"
)
// Check input and flag values for detecting errors or invalid inputs
func checkInputAndFlags(args []string) bool {
gifCount := 0
gifPresent := false
nonGifPresent := false
pipeCharPresent := false
for _, arg := range args {
extension := path.Ext(arg)
if extension == ".gif" {
gifPresent = true
gifCount++
} else {
nonGifPresent = true
}
if arg == "-" {
pipeCharPresent = true
}
}
if gifPresent && nonGifPresent && !onlySave {
fmt.Printf("Error: There are other inputs along with GIFs\nDue to the potential looping nature of GIFs, non-GIFs must not be supplied alongside\n\n")
return true
}
if gifCount > 1 && !onlySave {
fmt.Printf("Error: There are multiple GIFs supplied\nDue to the potential looping nature of GIFs, only one GIF per command is supported\n\n")
return true
}
if formatsTrue {
fmt.Printf("Supported input formats:\n\n" +
"JPEG/JPG\n" +
"PNG\n" +
"WEBP\n" +
"BMP\n" +
"TIFF/TIF\n" +
"GIF\n\n")
return true
}
if len(args) < 1 {
fmt.Printf("Error: Need at least 1 input path/url or piped input\nUse the -h flag for more info\n\n")
return true
}
if len(args) > 1 && pipeCharPresent {
fmt.Printf("Error: You cannot pass in piped input alongside other inputs\n\n")
return true
}
if customMap != "" && len(customMap) < 2 {
fmt.Printf("Need at least 2 characters for --map flag\n\n")
return true
}
if dimensions != nil {
numberOfDimensions := len(dimensions)
if numberOfDimensions != 2 {
fmt.Printf("Error: requires 2 dimensions, got %v\n\n", numberOfDimensions)
return true
}
if dimensions[0] < 1 || dimensions[1] < 1 {
fmt.Printf("Error: invalid values for dimensions\n\n")
return true
}
}
if width != 0 || height != 0 {
if width != 0 && height != 0 {
fmt.Printf("Error: both --width and --height can't be set. Use --dimensions instead\n\n")
return true
} else {
if width < 0 {
fmt.Printf("Error: invalid value for width\n\n")
return true
}
if height < 0 {
fmt.Printf("Error: invalid value for height\n\n")
return true
}
}
}
if saveBgColor == nil {
saveBgColor = []int{0, 0, 0, 100}
} else {
bgValues := len(saveBgColor)
if bgValues != 4 {
fmt.Printf("Error: --save-bg requires 4 values for RGBA, got %v\n\n", bgValues)
return true
}
if saveBgColor[0] < 0 || saveBgColor[1] < 0 || saveBgColor[2] < 0 || saveBgColor[3] < 0 {
fmt.Printf("Error: RBG values must be between 0 and 255\n")
fmt.Printf("Error: Opacity value must be between 0 and 100\n\n")
return true
}
if saveBgColor[0] > 255 || saveBgColor[1] > 255 || saveBgColor[2] > 255 || saveBgColor[3] > 100 {
fmt.Printf("Error: RBG values must be between 0 and 255\n")
fmt.Printf("Error: Opacity value must be between 0 and 100\n\n")
return true
}
}
if fontColor == nil {
fontColor = []int{255, 255, 255}
} else {
fontColorValues := len(fontColor)
if fontColorValues != 3 {
fmt.Printf("Error: --font-color requires 3 values for RGB, got %v\n\n", fontColorValues)
return true
}
if fontColor[0] < 0 || fontColor[1] < 0 || fontColor[2] < 0 {
fmt.Printf("Error: RBG values must be between 0 and 255\n\n")
return true
}
if fontColor[0] > 255 || fontColor[1] > 255 || fontColor[2] > 255 {
fmt.Printf("Error: RBG values must be between 0 and 255\n\n")
return true
}
}
if threshold == 0 {
threshold = 128
}
if threshold < 0 || threshold > 255 {
fmt.Printf("Error: threshold must be between 0 and 255\n\n")
return true
}
if dither && !braille {
fmt.Printf("Error: image dithering is only reserved for --braille flag\n\n")
return true
}
if (saveTxtPath == "" && saveImagePath == "" && saveGifPath == "") && onlySave {
fmt.Printf("Error: you need to supply one of --save-img, --save-txt or --save-gif for using --only-save\n\n")
return true
}
return false
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.2 MiB

After

Width:  |  Height:  |  Size: 1.4 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 210 KiB

BIN
example_gifs/braille.gif Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 154 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 226 KiB

After

Width:  |  Height:  |  Size: 355 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 507 KiB

After

Width:  |  Height:  |  Size: 158 KiB

BIN
example_gifs/dither.gif Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 124 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 354 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 349 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 283 KiB

After

Width:  |  Height:  |  Size: 223 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 833 KiB

After

Width:  |  Height:  |  Size: 409 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.7 MiB

After

Width:  |  Height:  |  Size: 3.0 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 49 KiB

31
go.mod
View File

@ -1,24 +1,35 @@
module github.com/TheZoraiz/ascii-image-converter
go 1.16
go 1.17
require (
github.com/asaskevich/govalidator v0.0.0-20210307081110-f21760c49a8d
github.com/fsnotify/fsnotify v1.4.9 // indirect
github.com/disintegration/imaging v1.6.2
github.com/fogleman/gg v1.3.0
github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0
github.com/gookit/color v1.4.2
github.com/magiconair/properties v1.8.5 // indirect
github.com/makeworld-the-better-one/dither/v2 v2.2.0
github.com/mitchellh/go-homedir v1.1.0
github.com/mitchellh/mapstructure v1.4.1 // indirect
github.com/nathan-fiscaletti/consolesize-go v0.0.0-20210105204122-a87d9f614b9d
github.com/nfnt/resize v0.0.0-20180221191011-83c6a9932646
github.com/spf13/cobra v1.1.3
github.com/spf13/viper v1.7.1
golang.org/x/image v0.0.0-20210628002857-a66eb6448b8d
)
require (
github.com/fsnotify/fsnotify v1.4.9 // indirect
github.com/hashicorp/hcl v1.0.0 // indirect
github.com/inconshreveable/mousetrap v1.0.0 // indirect
github.com/magiconair/properties v1.8.5 // indirect
github.com/mitchellh/mapstructure v1.4.1 // indirect
github.com/pelletier/go-toml v1.9.1 // indirect
github.com/spf13/afero v1.6.0 // indirect
github.com/spf13/cast v1.3.1 // indirect
github.com/spf13/cobra v1.1.3
github.com/spf13/jwalterweatherman v1.1.0 // indirect
github.com/spf13/viper v1.7.1
golang.org/x/image v0.0.0-20210504121937-7319ad40d33e
golang.org/x/sys v0.0.0-20210511113859-b0526f3d8744 // indirect
github.com/spf13/pflag v1.0.5 // indirect
github.com/subosito/gotenv v1.2.0 // indirect
github.com/xo/terminfo v0.0.0-20210125001918-ca9a967f8778 // indirect
golang.org/x/sys v0.0.0-20210601080250-7ecdf8ef093b // indirect
golang.org/x/text v0.3.6 // indirect
gopkg.in/ini.v1 v1.62.0 // indirect
gopkg.in/yaml.v2 v2.4.0 // indirect
)

21
go.sum
View File

@ -19,8 +19,6 @@ github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRF
github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e/go.mod h1:3U/XgcO3hCbHZ8TKRvWD2dDTCfh9M9ya+I9JpbB7O8o=
github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da/go.mod h1:Q73ZrmVTwzkszR9V5SSuryQ31EELlFMUz1kKyl939pY=
github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8=
github.com/asaskevich/govalidator v0.0.0-20210307081110-f21760c49a8d h1:Byv0BzEl3/e6D5CLfI0j/7hiIEtvGVFPCZ7Ei2oq8iQ=
github.com/asaskevich/govalidator v0.0.0-20210307081110-f21760c49a8d/go.mod h1:WaHUgvxTVq04UNunO+XhnAqY/wQc+bxr74GqbsZ/Jqw=
github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q=
github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8=
github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs=
@ -38,7 +36,11 @@ github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ=
github.com/dgryski/go-sip13 v0.0.0-20181026042036-e10d5fee7954/go.mod h1:vAd38F8PWV+bWy6jNmig1y/TA+kYO4g3RSRF0IAv0no=
github.com/disintegration/imaging v1.6.2 h1:w1LecBlG2Lnp8B3jk5zSuNqd7b4DXhcjwek1ei82L+c=
github.com/disintegration/imaging v1.6.2/go.mod h1:44/5580QXChDfwIclfc/PCwrr44amcmDAg8hxG0Ewe4=
github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4=
github.com/fogleman/gg v1.3.0 h1:/7zJX8F6AaYQc57WQCyN9cAIz+4bCJGO9B+dyW29am8=
github.com/fogleman/gg v1.3.0/go.mod h1:R/bRT+9gY/C5z7JzPU0zXsXHKM4/ayA+zqcVNZzPa1k=
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
github.com/fsnotify/fsnotify v1.4.9 h1:hsms1Qyu0jgnwNXIxa+/V/PDsU6CfLf6CNO8H7IWoS4=
github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ=
@ -50,6 +52,8 @@ github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V
github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
github.com/gogo/protobuf v1.2.1/go.mod h1:hp+jE20tsWTFYpLwKvXlhS1hjn+gTNwPg2I6zVXpSg4=
github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0 h1:DACJavvAHhabrF08vX0COfcOBJRhZ8lUbR+ZWIs0Y5g=
github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0/go.mod h1:E/TSTwGwJL78qG/PmXZO1EjYhfJinVAhrmmHX6Z8B9k=
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
github.com/golang/groupcache v0.0.0-20190129154638-5b532d6fd5ef/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
@ -118,6 +122,8 @@ github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
github.com/magiconair/properties v1.8.1/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ=
github.com/magiconair/properties v1.8.5 h1:b6kJs+EmPFMYGkow9GiUyCyOvIwYetYJ3fSaWak/Gls=
github.com/magiconair/properties v1.8.5/go.mod h1:y3VJvCyxH9uVvJTWEGAELF3aiYNyPKd5NZ3oSwXrF60=
github.com/makeworld-the-better-one/dither/v2 v2.2.0 h1:VTMAiyyO1YIO07fZwuLNZZasJgKUmvsIA48ze3ALHPQ=
github.com/makeworld-the-better-one/dither/v2 v2.2.0/go.mod h1:VBtN8DXO7SNtyGmLiGA7IsFeKrBkQPze1/iAeM95arc=
github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU=
github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4=
github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=
@ -138,8 +144,6 @@ github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3Rllmb
github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
github.com/nathan-fiscaletti/consolesize-go v0.0.0-20210105204122-a87d9f614b9d h1:PQW4Aqovdqc9efHl9EVA+bhKmuZ4ME1HvSYYDvaDiK0=
github.com/nathan-fiscaletti/consolesize-go v0.0.0-20210105204122-a87d9f614b9d/go.mod h1:cxIIfNMTwff8f/ZvRouvWYF6wOoO7nj99neWSx2q/Es=
github.com/nfnt/resize v0.0.0-20180221191011-83c6a9932646 h1:zYyBkD/k9seD2A7fsi6Oo2LfFZAehjjQMERAvZLEDnQ=
github.com/nfnt/resize v0.0.0-20180221191011-83c6a9932646/go.mod h1:jpp1/29i3P1S/RLdc7JQKbRpFeM1dOBd8T9ki5s+AY8=
github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U=
github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc=
github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic=
@ -222,8 +226,9 @@ golang.org/x/exp v0.0.0-20190829153037-c13cbed26979/go.mod h1:86+5VVa7VpoJ4kLfm0
golang.org/x/exp v0.0.0-20191030013958-a1ab85dbe136/go.mod h1:JXzH8nQsPlswgeRAPE3MuO9GYsAcnJvJ4vnMwN/5qkY=
golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js=
golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
golang.org/x/image v0.0.0-20210504121937-7319ad40d33e h1:PzJMNfFQx+QO9hrC1GwZ4BoPGeNGhfeQEgcQFArEjPk=
golang.org/x/image v0.0.0-20210504121937-7319ad40d33e/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
golang.org/x/image v0.0.0-20191009234506-e7c1f5e7dbb8/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
golang.org/x/image v0.0.0-20210628002857-a66eb6448b8d h1:RNPAfi2nHY7C2srAV8A49jpsYr0ADedCk1wq6fTMTvs=
golang.org/x/image v0.0.0-20210628002857-a66eb6448b8d/go.mod h1:023OzeP/+EPmXeapQh35lcL3II3LrY8Ic+EFFKVhULM=
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
@ -272,8 +277,8 @@ golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7w
golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210511113859-b0526f3d8744 h1:yhBbb4IRs2HS9PPlAg6DMC6mUOKexJBNsLf4Z+6En1Q=
golang.org/x/sys v0.0.0-20210511113859-b0526f3d8744/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210601080250-7ecdf8ef093b h1:qh4f65QIVFjq9eBURLEYWqaEXmOyqdUyiBSgaXWccWk=
golang.org/x/sys v0.0.0-20210601080250-7ecdf8ef093b/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=

View File

@ -16,155 +16,95 @@ limitations under the License.
package image_conversions
import (
"strconv"
var (
// Reference taken from http://paulbourke.net/dataformats/asciiart/
asciiTableSimple = " .:-=+*#%@"
asciiTableDetailed = " .'`^\",:;Il!i><~+_-?][}{1)(|\\/tfjrxnuvczXYUJCLQ0OZmwqpdbkhao*#MW&8%B@$"
"github.com/gookit/color"
// Structure for braille dots
BrailleStruct = [4][2]int{
{0x1, 0x8},
{0x2, 0x10},
{0x4, 0x20},
{0x40, 0x80},
}
BrailleThreshold uint32
)
// Reference taken from http://paulbourke.net/dataformats/asciiart/
var asciiTableSimple = map[int]string{
0: " ",
1: ".",
2: ":",
3: "-",
4: "=",
5: "+",
6: "*",
7: "#",
8: "%",
9: "@",
}
// Reference taken from http://paulbourke.net/dataformats/asciiart/
var asciiTableDetailed = map[int]string{
0: " ",
1: ".",
2: "'",
3: "`",
4: "^",
5: "\"",
6: ",",
7: ":",
8: ";",
9: "I",
10: "l",
11: "!",
12: "i",
13: ">",
14: "<",
15: "~",
16: "+",
17: "_",
18: "-",
19: "?",
20: "]",
21: "[",
22: "}",
23: "{",
24: "1",
25: ")",
26: "(",
27: "|",
28: "/",
29: "t",
30: "f",
31: "j",
32: "r",
33: "x",
34: "n",
35: "u",
36: "v",
37: "c",
38: "z",
39: "X",
40: "Y",
41: "U",
42: "J",
43: "C",
44: "L",
45: "Q",
46: "0",
47: "O",
48: "Z",
49: "m",
50: "w",
51: "q",
52: "p",
53: "d",
54: "b",
55: "k",
56: "h",
57: "a",
58: "o",
59: "*",
60: "#",
61: "M",
62: "W",
63: "&",
64: "8",
65: "%",
66: "B",
67: "@",
68: "$",
}
// For each individual element of imgSet in ConvertToASCIISlice()
const MAX_VAL float32 = 65535
const MAX_VAL float64 = 255
type AsciiChar struct {
Colored string
Simple string
OriginalColor string
SetColor string
Simple string
RgbValue [3]uint32
}
// Converts the 2D AsciiPixel slice of image data (each instance representing each pixel of original image)
// to a 2D AsciiChar slice with each colored and simple string having an ASCII character corresponding to
// the original grayscale and RGB values in AsciiPixel.
//
// If complex parameter is true, values are compared to 69 levels of color density in ASCII characters.
// Otherwise, values are compared to 10 levels of color density in ASCII characters.
func ConvertToAscii(imgSet [][]AsciiPixel, negative bool, colored bool, complex bool, customMap string) [][]AsciiChar {
/*
Converts the 2D image_conversions.AsciiPixel slice of image data (each instance representing each compressed pixel of original image)
to a 2D image_conversions.AsciiChar slice
If complex parameter is true, values are compared to 70 levels of color density in ASCII characters.
Otherwise, values are compared to 10 levels of color density in ASCII characters.
*/
func ConvertToAsciiChars(imgSet [][]AsciiPixel, negative, colored, grayscale, complex, colorBg bool, customMap string, fontColor [3]int) ([][]AsciiChar, error) {
height := len(imgSet)
width := len(imgSet[0])
var chosenTable map[int]string
chosenTable := map[int]string{}
// Turn ascii character-set string into map[int]string{} literal
if customMap == "" {
var charSet string
if complex {
chosenTable = asciiTableDetailed
charSet = asciiTableDetailed
} else {
chosenTable = asciiTableSimple
charSet = asciiTableSimple
}
for index, char := range charSet {
chosenTable[index] = string(char)
}
} else {
chosenTable = map[int]string{}
for index, char := range customMap {
chosenTable[index] = string(char)
}
}
result := make([][]AsciiChar, height)
for i := range result {
result[i] = make([]AsciiChar, width)
}
var result [][]AsciiChar
for i := 0; i < height; i++ {
var tempSlice []AsciiChar
for j := 0; j < width; j++ {
value := float32(imgSet[i][j].grayscaleValue)
value := float64(imgSet[i][j].charDepth)
// Gets appropriate string index from asciiTableSimple by percentage comparisons with its length
tempFloat := (value / MAX_VAL) * float32(len(chosenTable))
// Gets appropriate string index from chosenTable by percentage comparisons with its length
tempFloat := (value / MAX_VAL) * float64(len(chosenTable))
if value == MAX_VAL {
tempFloat = float32(len(chosenTable) - 1)
tempFloat = float64(len(chosenTable) - 1)
}
tempInt := int(tempFloat)
r := int(imgSet[i][j].rgbValue[0])
g := int(imgSet[i][j].rgbValue[1])
b := int(imgSet[i][j].rgbValue[2])
var r, g, b int
if colored {
r = int(imgSet[i][j].rgbValue[0])
g = int(imgSet[i][j].rgbValue[1])
b = int(imgSet[i][j].rgbValue[2])
} else {
r = int(imgSet[i][j].grayscaleValue[0])
g = int(imgSet[i][j].grayscaleValue[1])
b = int(imgSet[i][j].grayscaleValue[2])
}
if negative {
// Select character from opposite side of table as well as turn pixels negative
@ -172,22 +112,172 @@ func ConvertToAscii(imgSet [][]AsciiPixel, negative bool, colored bool, complex
g = 255 - g
b = 255 - b
// To preserve negative rgb values for saving png image later down the line, since it uses imgSet
if colored {
imgSet[i][j].rgbValue = [3]uint32{uint32(r), uint32(g), uint32(b)}
} else {
imgSet[i][j].grayscaleValue = [3]uint32{uint32(r), uint32(g), uint32(b)}
}
tempInt = (len(chosenTable) - 1) - tempInt
}
rStr := strconv.Itoa(r)
gStr := strconv.Itoa(g)
bStr := strconv.Itoa(b)
var char AsciiChar
char.Colored = color.Sprintf("<fg="+rStr+","+gStr+","+bStr+">%v</>", chosenTable[tempInt])
char.Simple = chosenTable[tempInt]
asciiChar := chosenTable[tempInt]
char.Simple = asciiChar
var err error
if colorBg {
char.OriginalColor, err = getColoredCharForTerm(uint8(r), uint8(g), uint8(b), asciiChar, true)
} else {
char.OriginalColor, err = getColoredCharForTerm(uint8(r), uint8(g), uint8(b), asciiChar, false)
}
if (colored || grayscale) && err != nil {
return nil, err
}
// If font color is not set, use a simple string. Otherwise, use True color
if fontColor != [3]int{255, 255, 255} {
fcR := fontColor[0]
fcG := fontColor[1]
fcB := fontColor[2]
if colorBg {
char.SetColor, err = getColoredCharForTerm(uint8(fcR), uint8(fcG), uint8(fcB), asciiChar, true)
} else {
char.SetColor, err = getColoredCharForTerm(uint8(fcR), uint8(fcG), uint8(fcB), asciiChar, false)
}
if err != nil {
return nil, err
}
}
if colored {
char.RgbValue = imgSet[i][j].rgbValue
} else {
char.RgbValue = imgSet[i][j].grayscaleValue
}
tempSlice = append(tempSlice, char)
}
result[i] = tempSlice
result = append(result, tempSlice)
}
return result
return result, nil
}
/*
Converts the 2D image_conversions.AsciiPixel slice of image data (each instance representing each compressed pixel of original image)
to a 2D image_conversions.AsciiChar slice
Unlike ConvertToAsciiChars(), this function calculates braille characters instead of ascii
*/
func ConvertToBrailleChars(imgSet [][]AsciiPixel, negative, colored, grayscale, colorBg bool, fontColor [3]int, threshold int) ([][]AsciiChar, error) {
BrailleThreshold = uint32(threshold)
height := len(imgSet)
width := len(imgSet[0])
var result [][]AsciiChar
for i := 0; i < height; i += 4 {
var tempSlice []AsciiChar
for j := 0; j < width; j += 2 {
brailleChar := getBrailleChar(i, j, negative, imgSet)
var r, g, b int
if colored {
r = int(imgSet[i][j].rgbValue[0])
g = int(imgSet[i][j].rgbValue[1])
b = int(imgSet[i][j].rgbValue[2])
} else {
r = int(imgSet[i][j].grayscaleValue[0])
g = int(imgSet[i][j].grayscaleValue[1])
b = int(imgSet[i][j].grayscaleValue[2])
}
if negative {
// Select character from opposite side of table as well as turn pixels negative
r = 255 - r
g = 255 - g
b = 255 - b
if colored {
imgSet[i][j].rgbValue = [3]uint32{uint32(r), uint32(g), uint32(b)}
} else {
imgSet[i][j].grayscaleValue = [3]uint32{uint32(r), uint32(g), uint32(b)}
}
}
var char AsciiChar
char.Simple = brailleChar
var err error
if colorBg {
char.OriginalColor, err = getColoredCharForTerm(uint8(r), uint8(g), uint8(b), brailleChar, true)
} else {
char.OriginalColor, err = getColoredCharForTerm(uint8(r), uint8(g), uint8(b), brailleChar, false)
}
if (colored || grayscale) && err != nil {
return nil, err
}
// If font color is not set, use a simple string. Otherwise, use True color
if fontColor != [3]int{255, 255, 255} {
fcR := fontColor[0]
fcG := fontColor[1]
fcB := fontColor[2]
if colorBg {
char.SetColor, err = getColoredCharForTerm(uint8(fcR), uint8(fcG), uint8(fcB), brailleChar, true)
} else {
char.SetColor, err = getColoredCharForTerm(uint8(fcR), uint8(fcG), uint8(fcB), brailleChar, false)
}
if err != nil {
return nil, err
}
}
if colored {
char.RgbValue = imgSet[i][j].rgbValue
} else {
char.RgbValue = imgSet[i][j].grayscaleValue
}
tempSlice = append(tempSlice, char)
}
result = append(result, tempSlice)
}
return result, nil
}
// Iterate through the BrailleStruct table to see which dots need to be highlighted
func getBrailleChar(x, y int, negative bool, imgSet [][]AsciiPixel) string {
brailleChar := 0x2800
for i := 0; i < 4; i++ {
for j := 0; j < 2; j++ {
if negative {
if imgSet[x+i][y+j].charDepth <= BrailleThreshold {
brailleChar += BrailleStruct[i][j]
}
} else {
if imgSet[x+i][y+j].charDepth >= BrailleThreshold {
brailleChar += BrailleStruct[i][j]
}
}
}
}
return string(brailleChar)
}

View File

@ -17,77 +17,40 @@ limitations under the License.
package image_conversions
import (
"fmt"
"image"
"image/color"
"github.com/nathan-fiscaletti/consolesize-go"
"github.com/nfnt/resize"
)
type AsciiPixel struct {
grayscaleValue uint32
rgbValue []uint32
charDepth uint32
grayscaleValue [3]uint32
rgbValue [3]uint32
}
// This function shrinks the passed image according to passed dimensions or terminal
// size if none are passed. Stores each pixel's grayscale and RGB values in an AsciiPixel
// instance to simplify getting numeric data for ASCII character comparison.
//
// The returned 2D AsciiPixel slice contains each corresponding pixel's values. Grayscale value
// ranges from 0 to 65535, while RGB values are separate.
func ConvertToAsciiPixels(img image.Image, dimensions []int, flipX, flipY bool) ([][]AsciiPixel, error) {
/*
This function shrinks the passed image according to specified or default dimensions.
Stores each pixel's grayscale and RGB values in an AsciiPixel instance to simplify
getting numeric data for ASCII character comparison.
var asciiWidth, asciiHeight int
var smallImg image.Image
The returned 2D AsciiPixel slice contains each corresponding pixel's values
*/
func ConvertToAsciiPixels(img image.Image, dimensions []int, width, height int, flipX, flipY, full, isBraille, dither bool) ([][]AsciiPixel, error) {
if len(dimensions) == 0 {
smallImg, err := resizeImage(img, full, isBraille, dimensions, width, height)
// Following code in this condition calculates ratio according to terminal height
terminalWidth, terminalHeight := consolesize.GetConsoleSize()
asciiHeight = terminalHeight - 1
// Passing 0 in place of width keeps the original image's aspect ratio
smallImg = resize.Resize(0, uint(asciiHeight), img, resize.Lanczos3)
asciiWidth = smallImg.Bounds().Max.X - smallImg.Bounds().Min.X
// To fix aspect ratio in eventual ascii art
asciiWidth = int(2 * float32(asciiWidth))
// If ascii width exceeds terminal width, change ratio with respect to terminal width
if asciiWidth > terminalWidth {
smallImg = resize.Resize(uint(terminalWidth), 0, img, resize.Lanczos3)
asciiWidth = terminalWidth - 1
asciiHeight = smallImg.Bounds().Max.Y - smallImg.Bounds().Min.Y
// To fix aspect ratio in eventual ascii art
asciiHeight = int(0.5 * float32(asciiHeight))
}
smallImg = resize.Resize(uint(asciiWidth), uint(asciiHeight), img, resize.Lanczos3)
} else {
asciiWidth = dimensions[0]
asciiHeight = dimensions[1]
smallImg = resize.Resize(uint(asciiWidth), uint(asciiHeight), img, resize.Lanczos3)
if err != nil {
return nil, err
}
// If there are passed dimensions, check whether the width exceeds terminal width
if len(dimensions) > 0 {
defaultTermWidth, _ := consolesize.GetConsoleSize()
defaultTermWidth -= 1
if dimensions[0] > defaultTermWidth {
return nil, fmt.Errorf("set width is larger than terminal width")
}
// We mainatin a dithered image literal along with original image
// The colors are kept from original image
var ditheredImage image.Image
if isBraille && dither {
ditheredImage = ditherImage(smallImg)
}
// Initialize imgSet 2D slice
imgSet := make([][]AsciiPixel, asciiHeight)
for i := range imgSet {
imgSet[i] = make([]AsciiPixel, asciiWidth)
}
var imgSet [][]AsciiPixel
b := smallImg.Bounds()
@ -98,24 +61,41 @@ func ConvertToAsciiPixels(img image.Image, dimensions []int, flipX, flipY bool)
for x := b.Min.X; x < b.Max.X; x++ {
oldPixel := smallImg.At(x, y)
pixel := color.GrayModel.Convert(oldPixel)
grayPixel := color.GrayModel.Convert(oldPixel)
// We only need Red from Red, Green, Blue (RGB) for grayscaleValue in AsciiPixel since they have the same value for grayscale images
r1, _, _, _ := pixel.RGBA()
r1, g1, b1, _ := grayPixel.RGBA()
charDepth := r1 / 257 // Only Red is needed from RGB for charDepth in AsciiPixel since they have the same value for grayscale images
r1 = uint32(r1 / 257)
g1 = uint32(g1 / 257)
b1 = uint32(b1 / 257)
// Get colored RGB values of original pixel for rgbValue in AsciiPixel
if isBraille && dither {
// Change charDepth if image dithering is applied
// Note that neither grayscale nor original color values are changed.
// Only charDepth is kept from dithered image. This is because a
// dithered image loses its colors so it's only used to check braille
// dots' visibility
ditheredGrayPixel := color.GrayModel.Convert(ditheredImage.At(x, y))
charDepth, _, _, _ = ditheredGrayPixel.RGBA()
charDepth = charDepth / 257
}
// Get co1ored RGB values of original pixel for rgbValue in AsciiPixel
r2, g2, b2, _ := oldPixel.RGBA()
r2 = uint32(r2 / 257)
g2 = uint32(g2 / 257)
b2 = uint32(b2 / 257)
temp = append(temp, AsciiPixel{
grayscaleValue: r1,
rgbValue: []uint32{r2, g2, b2},
charDepth: charDepth,
grayscaleValue: [3]uint32{r1, g1, b1},
rgbValue: [3]uint32{r2, g2, b2},
})
}
imgSet[y] = temp
imgSet = append(imgSet, temp)
}
// This rarely affects performance since the ascii art 2D slice size isn't that large
@ -125,22 +105,3 @@ func ConvertToAsciiPixels(img image.Image, dimensions []int, flipX, flipY bool)
return imgSet, nil
}
func reverse(imgSet [][]AsciiPixel, flipX, flipY bool) [][]AsciiPixel {
if flipX {
for _, row := range imgSet {
for i, j := 0, len(row)-1; i < j; i, j = i+1, j-1 {
row[i], row[j] = row[j], row[i]
}
}
}
if flipY {
for i, j := 0, len(imgSet)-1; i < j; i, j = i+1, j-1 {
imgSet[i], imgSet[j] = imgSet[j], imgSet[i]
}
}
return imgSet
}

166
image_manipulation/util.go Normal file
View File

@ -0,0 +1,166 @@
/*
Copyright © 2021 Zoraiz Hassan <hzoraiz8@gmail.com>
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package image_conversions
import (
"fmt"
"image"
"image/color"
"github.com/TheZoraiz/ascii-image-converter/aic_package/winsize"
"github.com/disintegration/imaging"
gookitColor "github.com/gookit/color"
"github.com/makeworld-the-better-one/dither/v2"
)
func ditherImage(img image.Image) image.Image {
palette := []color.Color{
color.Black,
color.White,
}
d := dither.NewDitherer(palette)
d.Matrix = dither.FloydSteinberg
return d.DitherCopy(img)
}
func resizeImage(img image.Image, full, isBraille bool, dimensions []int, width, height int) (image.Image, error) {
var asciiWidth, asciiHeight int
var smallImg image.Image
imgWidth := float64(img.Bounds().Dx())
imgHeight := float64(img.Bounds().Dy())
aspectRatio := imgWidth / imgHeight
if full {
terminalWidth, _, err := winsize.GetTerminalSize()
if err != nil {
return nil, err
}
asciiWidth = terminalWidth - 1
asciiHeight = int(float64(asciiWidth) / aspectRatio)
asciiHeight = int(0.5 * float64(asciiHeight))
} else if (width != 0 || height != 0) && len(dimensions) == 0 {
// If either width or height is set and dimensions aren't given
if width != 0 && height == 0 {
// If width is set and height is not set, use width to calculate aspect ratio
asciiWidth = width
asciiHeight = int(float64(asciiWidth) / aspectRatio)
asciiHeight = int(0.5 * float64(asciiHeight))
if asciiHeight == 0 {
asciiHeight = 1
}
} else if height != 0 && width == 0 {
// If height is set and width is not set, use height to calculate aspect ratio
asciiHeight = height
asciiWidth = int(float64(asciiHeight) * aspectRatio)
asciiWidth = int(2 * float64(asciiWidth))
if asciiWidth == 0 {
asciiWidth = 1
}
} else {
return nil, fmt.Errorf("error: both width and height can't be set. Use dimensions instead")
}
} else if len(dimensions) == 0 {
// This condition calculates aspect ratio according to terminal height
terminalWidth, terminalHeight, err := winsize.GetTerminalSize()
if err != nil {
return nil, err
}
asciiHeight = terminalHeight - 1
asciiWidth = int(float64(asciiHeight) * aspectRatio)
asciiWidth = int(2 * float64(asciiWidth))
// If ascii width exceeds terminal width, change ratio with respect to terminal width
if asciiWidth >= terminalWidth {
asciiWidth = terminalWidth - 1
asciiHeight = int(float64(asciiWidth) / aspectRatio)
asciiHeight = int(0.5 * float64(asciiHeight))
}
} else {
// Else, set passed dimensions
asciiWidth = dimensions[0]
asciiHeight = dimensions[1]
}
// Because one braille character has 8 dots (4 rows and 2 columns)
if isBraille {
asciiWidth *= 2
asciiHeight *= 4
}
smallImg = imaging.Resize(img, asciiWidth, asciiHeight, imaging.Lanczos)
return smallImg, nil
}
func reverse(imgSet [][]AsciiPixel, flipX, flipY bool) [][]AsciiPixel {
if flipX {
for _, row := range imgSet {
for i, j := 0, len(row)-1; i < j; i, j = i+1, j-1 {
row[i], row[j] = row[j], row[i]
}
}
}
if flipY {
for i, j := 0, len(imgSet)-1; i < j; i, j = i+1, j-1 {
imgSet[i], imgSet[j] = imgSet[j], imgSet[i]
}
}
return imgSet
}
var termColorLevel string = gookitColor.TermColorLevel().String()
// This functions calculates terminal color level between rgb colors and 256-colors
// and returns the character with escape codes appropriately
func getColoredCharForTerm(r, g, b uint8, char string, background bool) (string, error) {
var coloredChar string
if termColorLevel == "millions" {
colorRenderer := gookitColor.RGB(uint8(r), uint8(g), uint8(b), background)
coloredChar = colorRenderer.Sprintf("%v", char)
} else if termColorLevel == "hundreds" {
colorRenderer := gookitColor.RGB(uint8(r), uint8(g), uint8(b), background).C256()
coloredChar = colorRenderer.Sprintf("%v", char)
} else {
return "", fmt.Errorf("your terminal supports neither 24-bit nor 8-bit colors. Other coloring options aren't available")
}
return coloredChar, nil
}

View File

@ -1,10 +1,11 @@
name: ascii-image-converter
base: core18
version: "1.2.6"
summary: Converts images into ascii art
version: "1.13.1"
summary: Convert images and gifs into ascii art
description: |
This tool converts images into ascii format and prints them onto the terminal window.
Supported image formats are JPEG/JPG, PNG, WEBP, BMP and TIFF/TIF. Further configuration can be managed by flags.
ascii-image-converter is a command-line tool that converts images into ascii art and prints
them out onto the console. Supported input formats are JPEG/JPG, PNG, WEBP, BMP, TIFF/TIF and GIF.
Now supports braille art.
grade: stable
confinement: strict