Compare commits

..

No commits in common. "master" and "v1.6.0" have entirely different histories.

32 changed files with 465 additions and 893 deletions

View File

@ -29,6 +29,7 @@ builds:
# - mipsle
# - mips64
# - mips64le
archives:
-

210
README.md
View File

@ -1,14 +1,12 @@
# 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)
[![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)
[![ascii-image-converter-lang](https://img.shields.io/badge/Language-Go-blue)](https://golang.org/)
![Github All Releases](https://img.shields.io/github/downloads/TheZoraiz/ascii-image-converter/total?color=brightgreen&label=Release%20Downloads)
[![ascii-image-converter](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. Available on Windows, Linux and macOS.
Now supports braille art!
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. GIFs are now experimentally supported as well.
Input formats currently supported:
* JPEG/JPG
@ -16,20 +14,14 @@ Input formats currently supported:
* BMP
* WEBP
* TIFF/TIF
* GIF
<p align="center">
<img src="https://raw.githubusercontent.com/TheZoraiz/ascii-image-converter/master/example_gifs/all.gif">
</p>
* GIF (Experimental)
## Table of Contents
- [Installation](#installation)
* [Debian / Ubuntu-based](#debian-or-ubuntu-based-distros)
* [Homebrew](#homebrew)
* [AUR](#aur)
* [Scoop](#scoop)
* [Snap](#snap)
* [Snap](#brew)
* [Go](#go)
* [Linux (binaries)](#linux)
* [Windows (binaries)](#windows)
@ -42,7 +34,7 @@ Input formats currently supported:
## Installation
### Debian or Ubuntu-based Distros
### Debian or Ubuntu-based Distros
Execute the following commands in order:
@ -60,7 +52,7 @@ sudo apt install -y ascii-image-converter
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
sudo rm -rfv /etc/apt/sources.list.d/ascii-image-converter.list
```
<hr>
@ -75,37 +67,7 @@ brew install TheZoraiz/ascii-image-converter/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
### Snap
> **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.
@ -122,6 +84,7 @@ Visit [the app's snap store listing](https://snapcraft.io/ascii-image-converter)
### Go
For installing through Go
```
go install github.com/TheZoraiz/ascii-image-converter@latest
```
@ -145,7 +108,8 @@ Now you can use ascii-image-converter in the terminal. Execute `ascii-image-conv
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: Advanced System Settings
* In Search, search for and then select: System (Control Panel)
* Click the Advanced System settings link.
* 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.
@ -167,20 +131,30 @@ Example:
```
ascii-image-converter myImage.jpeg
```
<br>
Single image:
> **Note:** Piped binary input is also supported
> ```
> cat myImage.png | ascii-image-converter -
> ```
<p align="center">
<img src="https://raw.githubusercontent.com/TheZoraiz/ascii-image-converter/master/example_gifs/base.gif">
</p>
Multiple images:
<p align="center">
<img src="https://raw.githubusercontent.com/TheZoraiz/ascii-image-converter/master/example_gifs/all.gif">
</p>
GIF:
<p align="center">
<img src="https://raw.githubusercontent.com/TheZoraiz/ascii-image-converter/master/example_gifs/gif-example.gif">
</p>
### Flags
#### --color OR -C
> **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.
Display ascii art with the colors from original image. Works with the --negative flag as well.
```
ascii-image-converter [image paths/urls] -C
@ -192,50 +166,6 @@ ascii-image-converter [image paths/urls] --color
<img src="https://raw.githubusercontent.com/TheZoraiz/ascii-image-converter/master/example_gifs/color.gif">
</p>
#### --braille OR -b
> **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] -b
# Or
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
@ -291,7 +221,7 @@ ascii-image-converter [image paths/urls] -H 60
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.
Empty spaces can be passed if string is passed inside quotation marks. You can use both single or double quote for quotation marks. For repeating quotation mark inside string, append it with \ (such as \\").
```
ascii-image-converter [image paths/urls] -m "<string-of-characters>"
# Or
@ -407,10 +337,10 @@ Saves the passed GIF as an ascii art GIF with the name `<image-name>-ascii-art.g
> **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.
This flag takes an RGB value that sets the background color in saved png and gif files.
```
ascii-image-converter [image paths/urls] -s . --save-bg 255,255,255,100 # For white background
ascii-image-converter [image paths/urls] -s . --save-bg 255,255,255 # For white background
```
#### --font
@ -431,14 +361,6 @@ This flag takes an RGB value that sets the font color in saved png and gif files
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.
@ -451,14 +373,14 @@ ascii-image-converter --formats
## Library Usage
> **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).
> **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). Furthermore, GIF conversion is not advised as it isn't fully library-compatible yet.
First, install the library with:
```
go get -u github.com/TheZoraiz/ascii-image-converter/aic_package
```
For an image:
The library is to be used as follows:
```go
package main
@ -476,19 +398,24 @@ func main() {
flags := aic_package.DefaultFlags()
// 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}
// 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
// You can directly pass default flags variable to Convert() if you wish.
// For clarity, all flags are covered in this example, but you can use specific ones.
flags.Complex = false // Use complex character set
flags.Dimensions = []int{50, 25} // 50 by 25 ascii art size
flags.SaveTxtPath = "." // Save generated text in same directory
flags.SaveImagePath = "." // Save generated PNG image in same directory
flags.SaveGifPath = "." // If gif is provided, save ascii art gif in same directory
flags.Negative = false // Ascii art will have negative color-depth
flags.Colored = true // Keep colors from original image. This overrides flags.Grayscale and flags.FontColor
flags.Grayscale = false // Returns grayscale ascii art. This overrides flags.FontColor
flags.CustomMap = " .-=+#@" // Starting from darkest to brightest shades. This overrides flags.Complex
flags.FlipX = false // Flips ascii art horizontally
flags.FlipY = false // Flips ascii art vertically
flags.Full = false // Display ascii art that fills the terminal width. This overrides flags.Dimensions
flags.FontFilePath = "./RobotoMono-Regular.ttf" // File path to font .ttf file for saved png and gif files. Ignored if flags.SaveImagePath or flags.SaveGifPath are not set
flags.FontColor = [3]int{0, 0, 0} // Font color for terminal and saved png and gif files.
flags.SaveBackgroundColor = [3]int{255, 255, 255} // Font color for saved png and gif files. Ignored if flags.SaveImagePath or flags.SaveGifPath are not set.
// Conversion for an image
asciiArt, err := aic_package.Convert(filePath, flags)
if err != nil {
@ -496,34 +423,15 @@ func main() {
}
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()
// GIF CONVERSION IS AN EXPERIMENTAL FEATURE
// For a gif. This function may run infinitely, depending on the gif
// Work needs to be done on gif conversion to be more library-compatible
_, err := aic_package.Convert(filePath, flags)
if err != nil {
fmt.Println(err)
}
}
```
<br>
@ -542,11 +450,11 @@ You can fork the project and implement any changes you want for a pull request.
[github.com/nathan-fiscaletti/consolesize-go](https://github.com/nathan-fiscaletti/consolesize-go)
[github.com/disintegration/imaging](https://github.com/disintegration/imaging)
[github.com/nfnt/resize](https://github.com/nfnt/resize)
[github.com/gookit/color](https://github.com/gookit/color)
[github.com/makeworld-the-better-one/dither](https://github.com/makeworld-the-better-one/dither)
[github.com/asaskevich/govalidator](https://github.com/asaskevich/govalidator)
## License

Binary file not shown.

View File

@ -45,26 +45,20 @@ 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 {
func pathIsGif(gifPath, urlImgName string, pathIsURl bool, urlImgBytes []byte, localGif *os.File) (string, error) {
var (
originalGif *gif.GIF
err error
)
if gifPath == "-" {
originalGif, err = gif.DecodeAll(bytes.NewReader(pipedInputBytes))
} else if pathIsURl {
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)
}
return "", fmt.Errorf("can't decode %v: %v", gifPath, err)
}
var (
@ -97,33 +91,19 @@ func pathIsGif(gifPath, urlImgName string, pathIsURl bool, urlImgBytes, pipedInp
// 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")
}
fmt.Println("Error: GIF contains subimages smaller than default width and height\nProcess aborted because ascii-image-converter doesn't support subimage placement and transparency in GIFs\n")
os.Exit(0)
}
var imgSet [][]imgManip.AsciiPixel
imgSet, err = imgManip.ConvertToAsciiPixels(frameImage, dimensions, width, height, flipX, flipY, full, braille, dither)
imgSet, err = imgManip.ConvertToAsciiPixels(frameImage, dimensions, width, height, flipX, flipY, full)
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)
fmt.Println("Error:", err)
os.Exit(0)
}
asciiCharSet := imgManip.ConvertToAsciiChars(imgSet, negative, colored, complex, customMap, fontColor)
gifFramesSlice[i].asciiCharSet = asciiCharSet
gifFramesSlice[i].delay = originalGif.Delay[i]
@ -157,12 +137,12 @@ func pathIsGif(gifPath, urlImgName string, pathIsURl bool, urlImgBytes, pipedInp
saveFileName, err := createSaveFileName(gifPath, urlImgName, "-ascii-art.gif")
if err != nil {
return err
return "", err
}
fullPathName, err := getFullSavePath(saveFileName, saveGifPath)
if err != nil {
return fmt.Errorf("can't save file: %v", err)
return "", fmt.Errorf("can't save file: %v", err)
}
// Initializing some constants for gif. Done outside loop to save execution
@ -202,7 +182,7 @@ func pathIsGif(gifPath, urlImgName string, pathIsURl bool, urlImgBytes, pipedInp
colored || grayscale,
)
if err != nil {
fmt.Printf("Error: %v\n", err)
fmt.Println("Error:", err)
os.Exit(0)
}
@ -239,38 +219,34 @@ func pathIsGif(gifPath, urlImgName string, pathIsURl bool, urlImgBytes, pipedInp
gifFile, err := os.OpenFile(fullPathName, os.O_WRONLY|os.O_CREATE, 0666)
if err != nil {
return fmt.Errorf("can't save file: %v", err)
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))
}
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
}
// If gif is infinite loop
if originalGif.LoopCount == 0 {
continue
}
loopCount++
if loopCount == originalGif.LoopCount {
break
}
loopCount++
if loopCount == originalGif.LoopCount {
break
}
}
return nil
return "", nil
}

View File

@ -27,43 +27,28 @@ import (
)
// 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) {
func pathIsImage(imagePath, urlImgName string, pathIsURl bool, urlImgBytes []byte, localImg *os.File) (string, error) {
var (
imData image.Image
err error
)
if imagePath == "-" {
imData, _, err = image.Decode(bytes.NewReader(pipedInputBytes))
} else if pathIsURl {
if pathIsURl {
imData, _, err = image.Decode(bytes.NewReader(urlImgBytes))
} else {
imData, _, err = image.Decode(localImg)
}
if err != nil {
if imagePath == "-" {
return "", fmt.Errorf("can't decode piped input: %v", err)
} else {
return "", fmt.Errorf("can't decode %v: %v", imagePath, err)
}
return "", fmt.Errorf("can't decode %v: %v", imagePath, err)
}
imgSet, err := imgManip.ConvertToAsciiPixels(imData, dimensions, width, height, flipX, flipY, full, braille, dither)
imgSet, err := imgManip.ConvertToAsciiPixels(imData, dimensions, width, height, flipX, flipY, full)
if err != nil {
return "", err
}
var asciiSet [][]imgManip.AsciiChar
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
}
asciiSet := imgManip.ConvertToAsciiChars(imgSet, negative, colored, complex, customMap, fontColor)
// Save ascii art as .png image before printing it, if --save-img flag is passed
if saveImagePath != "" {
@ -73,7 +58,6 @@ func pathIsImage(imagePath, urlImgName string, pathIsURl bool, urlImgBytes, pipe
saveImagePath,
imagePath,
urlImgName,
onlySave,
); err != nil {
return "", fmt.Errorf("can't save file: %v", err)
@ -87,7 +71,6 @@ func pathIsImage(imagePath, urlImgName string, pathIsURl bool, urlImgBytes, pipe
imagePath,
saveTxtPath,
urlImgName,
onlySave,
); err != nil {
return "", fmt.Errorf("can't save file: %v", err)
@ -97,8 +80,5 @@ func pathIsImage(imagePath, urlImgName string, pathIsURl bool, urlImgBytes, pipe
ascii := flattenAscii(asciiSet, colored || grayscale, false)
result := strings.Join(ascii, "\n")
if onlySave {
return "", nil
}
return result, nil
}

View File

@ -32,17 +32,10 @@ import (
_ "golang.org/x/image/tiff"
_ "golang.org/x/image/webp"
"github.com/asaskevich/govalidator"
"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 {
@ -56,7 +49,6 @@ func DefaultFlags() Flags {
SaveGifPath: "",
Negative: false,
Colored: false,
CharBackgroundColor: false,
Grayscale: false,
CustomMap: "",
FlipX: false,
@ -64,11 +56,7 @@ func DefaultFlags() Flags {
Full: false,
FontFilePath: "",
FontColor: [3]int{255, 255, 255},
SaveBackgroundColor: [4]int{0, 0, 0, 100},
Braille: false,
Threshold: 128,
Dither: false,
OnlySave: false,
SaveBackgroundColor: [3]int{0, 0, 0},
}
}
@ -92,7 +80,6 @@ func Convert(filePath string, flags Flags) (string, error) {
saveGifPath = flags.SaveGifPath
negative = flags.Negative
colored = flags.Colored
colorBg = flags.CharBackgroundColor
grayscale = flags.Grayscale
customMap = flags.CustomMap
flipX = flags.FlipX
@ -101,87 +88,43 @@ func Convert(filePath string, flags Flags) (string, error) {
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
localFile *os.File
urlImgBytes []byte
urlImgName string = ""
err error
)
pathIsURl := isURL(filePath)
pathIsURl := govalidator.IsRequestURL(filePath)
// Different modes of reading data depending upon whether or not filePath is a url
if pathIsURl {
fmt.Printf("Fetching file from url...\r")
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()
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 {
// 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)
localFile, err = os.Open(filePath)
if err != nil {
return "", fmt.Errorf("unable to read piped input: %v", err)
return "", fmt.Errorf("unable to open file: %v", err)
}
defer localFile.Close()
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
@ -195,13 +138,11 @@ func Convert(filePath string, flags Flags) (string, error) {
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)
if path.Ext(filePath) == ".gif" {
return pathIsGif(filePath, urlImgName, pathIsURl, urlImgBytes, localFile)
} else {
return pathIsImage(filePath, urlImgName, pathIsURl, urlImgBytes, pipedInputBytes, localFile)
return pathIsImage(filePath, urlImgName, pathIsURl, urlImgBytes, localFile)
}
}

View File

@ -88,6 +88,11 @@ func createGifFrameToSave(asciiArt [][]imgManip.AsciiChar, img image.Image, colo
dc.DrawImage(tempImg, 0, 0)
// Load embedded font
tempFont, err := truetype.Parse(embeddedFontFile)
if err != nil {
return nil, err
}
// 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})

View File

@ -17,7 +17,6 @@ limitations under the License.
package aic_package
import (
"fmt"
"image"
"image/color"
@ -29,16 +28,13 @@ import (
)
//go:embed Hack-Regular.ttf
var embeddedHackRegularFont []byte
//go:embed DejaVuSans-Oblique.ttf
var embeddedDejaVuObliqueFont []byte
var embeddedFontFile []byte
var tempFont *truetype.Font
// Load embedded font
func init() {
tempFont, _ = truetype.Parse(embeddedHackRegularFont)
tempFont, _ = truetype.Parse(embeddedFontFile)
}
/*
@ -49,7 +45,7 @@ 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 {
func createImageToSave(asciiArt [][]imgManip.AsciiChar, colored bool, saveImagePath, imagePath, urlImgName string) error {
constant := 14.0
@ -74,11 +70,10 @@ func createImageToSave(asciiArt [][]imgManip.AsciiChar, colored bool, saveImageP
dc := gg.NewContext(imgWidth, imgHeight)
// Set image background
dc.SetRGBA(
dc.SetRGB(
float64(saveBgColor[0])/255,
float64(saveBgColor[1])/255,
float64(saveBgColor[2])/255,
float64(saveBgColor[3])/100,
)
dc.Clear()
@ -140,9 +135,5 @@ func createImageToSave(asciiArt [][]imgManip.AsciiChar, colored bool, saveImageP
return err
}
if onlySave {
fmt.Println("Saved " + fullPathName)
}
return dc.SavePNG(fullPathName)
}

View File

@ -1,3 +0,0 @@
## 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.

View File

@ -28,7 +28,7 @@ import (
imgManip "github.com/TheZoraiz/ascii-image-converter/image_manipulation"
)
func saveAsciiArt(asciiSet [][]imgManip.AsciiChar, imagePath, savePath, urlImgName string, onlySave bool) error {
func saveAsciiArt(asciiSet [][]imgManip.AsciiChar, imagePath, savePath, urlImgName string) error {
// To make sure uncolored ascii art is the one saved as .txt
saveAscii := flattenAscii(asciiSet, false, true)
@ -46,13 +46,7 @@ func saveAsciiArt(asciiSet [][]imgManip.AsciiChar, imagePath, savePath, urlImgNa
// 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
return ioutil.WriteFile(savePath+saveFileName, []byte(strings.Join(saveAscii, "\n")), 0666)
} else {
return fmt.Errorf("save path %v does not exist", savePath)
}
@ -67,13 +61,6 @@ func createSaveFileName(imagePath, urlImgName, label string) (string, error) {
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
@ -92,24 +79,24 @@ func flattenAscii(asciiSet [][]imgManip.AsciiChar, colored, toSaveTxt bool) []st
var ascii []string
for _, line := range asciiSet {
var tempAscii string
var tempAscii []string
for _, char := range line {
for i := 0; i < len(line); i++ {
if toSaveTxt {
tempAscii += char.Simple
tempAscii = append(tempAscii, line[i].Simple)
continue
}
if colored {
tempAscii += char.OriginalColor
tempAscii = append(tempAscii, line[i].OriginalColor)
} else if fontColor != [3]int{255, 255, 255} {
tempAscii += char.SetColor
tempAscii = append(tempAscii, line[i].SetColor)
} else {
tempAscii += char.Simple
tempAscii = append(tempAscii, line[i].Simple)
}
}
ascii = append(ascii, tempAscii)
ascii = append(ascii, strings.Join(tempAscii, ""))
}
return ascii
@ -132,15 +119,6 @@ func getFullSavePath(imageName, saveFilePath string) (string, error) {
}
}
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()
@ -168,8 +146,3 @@ func clearScreen() {
os.Exit(0)
}
}
func isInputFromPipe() bool {
fileInfo, _ := os.Stdin.Stat()
return fileInfo.Mode()&os.ModeCharDevice == 0
}

View File

@ -50,17 +50,12 @@ type Flags struct {
// 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
@ -78,30 +73,13 @@ type Flags struct {
// 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.
// Font RGB color in saved png or gif files.
// This will be ignored if Flags.SaveImagePath or Flags.SaveGifPath are not set
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
SaveBackgroundColor [3]int
}
var (
@ -115,17 +93,11 @@ var (
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
saveBgColor [3]int
)

View File

@ -1,5 +1,4 @@
//go:build (unix && ignore) || !windows
// +build unix,ignore !windows
// +build unix, !windows
package winsize

View File

@ -1,5 +1,4 @@
//go:build (windows && ignore) || !unix
// +build windows,ignore !unix
// +build windows, !unix
package winsize

View File

@ -40,7 +40,6 @@ var (
negative bool
formatsTrue bool
colored bool
colorBg bool
grayscale bool
customMap string
flipX bool
@ -49,16 +48,12 @@ var (
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 or piped stdin]",
Use: "ascii-image-converter [image paths/urls]",
Short: "Converts images and gifs into ascii art",
Version: "1.13.1",
Version: "1.6.0",
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
@ -78,7 +73,6 @@ var (
SaveGifPath: saveGifPath,
Negative: negative,
Colored: colored,
CharBackgroundColor: colorBg,
Grayscale: grayscale,
CustomMap: customMap,
FlipX: flipX,
@ -86,47 +80,29 @@ var (
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
SaveBackgroundColor: [3]int{saveBgColor[0], saveBgColor[1], saveBgColor[2]},
}
for _, imagePath := range args {
if err := printAscii(imagePath, flags); err != nil {
return
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
}
}
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() {
@ -143,16 +119,12 @@ func init() {
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 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().BoolVarP(&colored, "color", "C", false, "Display ascii art with original colors\n(Can work with the --negative flag)\n(Overrides --grayscale and --font-color flags)\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(&grayscale, "grayscale", "g", false, "Display grayscale ascii art\n(Can work 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().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")
@ -161,17 +133,14 @@ func init() {
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(&saveBgColor, "save-bg", nil, "Set background color for --save-img and --save-gif flags\nPass an RGB value\ne.g. --save-bg 255,255,255\n(Defaults to 0,0,0)\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)\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(defaultUsageTemplate + "\nCopyright © 2021 Zoraiz Hassan <hzoraiz8@gmail.com>\n" +
"Distributed under the Apache License Version 2.0 (Apache-2.0)\n" +

View File

@ -19,6 +19,8 @@ package cmd
import (
"fmt"
"path"
"github.com/TheZoraiz/ascii-image-converter/aic_package/winsize"
)
// Check input and flag values for detecting errors or invalid inputs
@ -27,8 +29,6 @@ func checkInputAndFlags(args []string) bool {
gifCount := 0
gifPresent := false
nonGifPresent := false
pipeCharPresent := false
for _, arg := range args {
extension := path.Ext(arg)
@ -38,18 +38,14 @@ func checkInputAndFlags(args []string) bool {
} else {
nonGifPresent = true
}
if arg == "-" {
pipeCharPresent = true
}
}
if gifPresent && nonGifPresent && !onlySave {
if gifPresent && nonGifPresent {
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 {
if gifCount > 1 {
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
}
@ -61,17 +57,12 @@ func checkInputAndFlags(args []string) bool {
"WEBP\n" +
"BMP\n" +
"TIFF/TIF\n" +
"GIF\n\n")
"GIF (Experimental)\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")
fmt.Printf("Error: Need at least 1 input path/url\nUse the -h flag for more info\n\n")
return true
}
@ -92,6 +83,18 @@ func checkInputAndFlags(args []string) bool {
fmt.Printf("Error: invalid values for dimensions\n\n")
return true
}
defaultTermWidth, _, err := winsize.GetTerminalSize()
if err != nil {
fmt.Printf("Error: %v\n\n", err)
return true
}
defaultTermWidth -= 1
if dimensions[0] > defaultTermWidth {
fmt.Printf("Error: set width must be lower than terminal width\n\n")
return true
}
}
if width != 0 || height != 0 {
@ -99,9 +102,21 @@ func checkInputAndFlags(args []string) bool {
if width != 0 && height != 0 {
fmt.Printf("Error: both --width and --height can't be set. Use --dimensions instead\n\n")
return true
} else {
defaultTermWidth, _, err := winsize.GetTerminalSize()
if err != nil {
fmt.Printf("Error: %v\n\n", err)
return true
}
// Check if set width exceeds terminal
defaultTermWidth -= 1
if width > defaultTermWidth {
fmt.Printf("Error: set width must be lower than terminal width\n\n")
return true
}
if width < 0 {
fmt.Printf("Error: invalid value for width\n\n")
return true
@ -117,23 +132,21 @@ func checkInputAndFlags(args []string) bool {
}
if saveBgColor == nil {
saveBgColor = []int{0, 0, 0, 100}
saveBgColor = []int{0, 0, 0}
} else {
bgValues := len(saveBgColor)
if bgValues != 4 {
fmt.Printf("Error: --save-bg requires 4 values for RGBA, got %v\n\n", bgValues)
if bgValues != 3 {
fmt.Printf("Error: --save-bg requires 3 values for RGB, 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")
if saveBgColor[0] < 0 || saveBgColor[1] < 0 || saveBgColor[2] < 0 {
fmt.Printf("Error: RBG values must be between 0 and 255\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")
if saveBgColor[0] > 255 || saveBgColor[1] > 255 || saveBgColor[2] > 255 {
fmt.Printf("Error: RBG values must be between 0 and 255\n\n")
return true
}
}
@ -158,24 +171,5 @@ func checkInputAndFlags(args []string) bool {
}
}
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.4 MiB

After

Width:  |  Height:  |  Size: 1.2 MiB

BIN
example_gifs/base.gif Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 160 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 154 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 355 KiB

After

Width:  |  Height:  |  Size: 582 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 158 KiB

After

Width:  |  Height:  |  Size: 253 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 124 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.3 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 223 KiB

After

Width:  |  Height:  |  Size: 352 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 409 KiB

After

Width:  |  Height:  |  Size: 669 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.0 MiB

After

Width:  |  Height:  |  Size: 4.9 MiB

BIN
example_gifs/url.gif Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 49 KiB

27
go.mod
View File

@ -1,35 +1,26 @@
module github.com/TheZoraiz/ascii-image-converter
go 1.17
go 1.16
require (
github.com/disintegration/imaging v1.6.2
github.com/asaskevich/govalidator v0.0.0-20210307081110-f21760c49a8d
github.com/fogleman/gg v1.3.0
github.com/fsnotify/fsnotify v1.4.9 // indirect
github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0
github.com/gookit/color v1.4.2
github.com/makeworld-the-better-one/dither/v2 v2.2.0
github.com/mitchellh/go-homedir v1.1.0
github.com/nathan-fiscaletti/consolesize-go v0.0.0-20210105204122-a87d9f614b9d
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/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/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/pflag v1.0.5 // indirect
github.com/subosito/gotenv v1.2.0 // indirect
github.com/xo/terminfo v0.0.0-20210125001918-ca9a967f8778 // indirect
github.com/spf13/viper v1.7.1
golang.org/x/image v0.0.0-20210504121937-7319ad40d33e
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
)

13
go.sum
View File

@ -19,6 +19,8 @@ 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=
@ -36,8 +38,6 @@ 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=
@ -122,8 +122,6 @@ 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=
@ -144,6 +142,8 @@ 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=
@ -226,9 +226,8 @@ 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-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/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/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=

View File

@ -16,24 +16,101 @@ limitations under the License.
package image_conversions
var (
// Reference taken from http://paulbourke.net/dataformats/asciiart/
asciiTableSimple = " .:-=+*#%@"
asciiTableDetailed = " .'`^\",:;Il!i><~+_-?][}{1)(|\\/tfjrxnuvczXYUJCLQ0OZmwqpdbkhao*#MW&8%B@$"
import (
"strconv"
// Structure for braille dots
BrailleStruct = [4][2]int{
{0x1, 0x8},
{0x2, 0x10},
{0x4, 0x20},
{0x40, 0x80},
}
BrailleThreshold uint32
"github.com/gookit/color"
)
// 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 float64 = 255
const MAX_VAL float64 = 65535
type AsciiChar struct {
OriginalColor string
@ -42,34 +119,24 @@ type AsciiChar struct {
RgbValue [3]uint32
}
/*
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) {
// 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 69 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, complex bool, customMap string, fontColor [3]int) [][]AsciiChar {
height := len(imgSet)
width := len(imgSet[0])
chosenTable := map[int]string{}
var chosenTable map[int]string
// Turn ascii character-set string into map[int]string{} literal
if customMap == "" {
var charSet string
if complex {
charSet = asciiTableDetailed
chosenTable = asciiTableDetailed
} else {
charSet = asciiTableSimple
chosenTable = asciiTableSimple
}
for index, char := range charSet {
chosenTable[index] = string(char)
}
} else {
chosenTable = map[int]string{}
@ -78,7 +145,10 @@ func ConvertToAsciiChars(imgSet [][]AsciiPixel, negative, colored, grayscale, co
}
}
var result [][]AsciiChar
result := make([][]AsciiChar, height)
for i := range result {
result[i] = make([]AsciiChar, width)
}
for i := 0; i < height; i++ {
@ -122,37 +192,25 @@ func ConvertToAsciiChars(imgSet [][]AsciiPixel, negative, colored, grayscale, co
tempInt = (len(chosenTable) - 1) - tempInt
}
rStr := strconv.Itoa(r)
gStr := strconv.Itoa(g)
bStr := strconv.Itoa(b)
var char AsciiChar
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
}
char.OriginalColor = color.Sprintf("<fg="+rStr+","+gStr+","+bStr+">%v</>", chosenTable[tempInt])
// 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]
fcR := strconv.Itoa(fontColor[0])
fcG := strconv.Itoa(fontColor[1])
fcB := strconv.Itoa(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
}
char.SetColor = color.Sprintf("<fg="+fcR+","+fcG+","+fcB+">%v</>", chosenTable[tempInt])
}
char.Simple = chosenTable[tempInt]
if colored {
char.RgbValue = imgSet[i][j].rgbValue
} else {
@ -161,123 +219,8 @@ func ConvertToAsciiChars(imgSet [][]AsciiPixel, negative, colored, grayscale, co
tempSlice = append(tempSlice, char)
}
result = append(result, tempSlice)
result[i] = tempSlice
}
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)
return result
}

View File

@ -17,8 +17,12 @@ limitations under the License.
package image_conversions
import (
"fmt"
"image"
"image/color"
"github.com/TheZoraiz/ascii-image-converter/aic_package/winsize"
"github.com/nfnt/resize"
)
type AsciiPixel struct {
@ -27,30 +31,121 @@ type AsciiPixel struct {
rgbValue [3]uint32
}
/*
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.
// 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, width, height int, flipX, flipY, full bool) ([][]AsciiPixel, error) {
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) {
smallImg, err := resizeImage(img, full, isBraille, dimensions, width, height)
var asciiWidth, asciiHeight int
var smallImg image.Image
terminalWidth, terminalHeight, err := winsize.GetTerminalSize()
if err != nil {
return nil, err
}
// We mainatin a dithered image literal along with original image
// The colors are kept from original image
var ditheredImage image.Image
if full {
asciiWidth = terminalWidth - 1
if isBraille && dither {
ditheredImage = ditherImage(smallImg)
// Passing 0 in place of width keeps the original image's aspect ratio
smallImg = resize.Resize(uint(asciiWidth), 0, img, resize.Lanczos3)
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 if (width != 0 || height != 0) && len(dimensions) == 0 {
// If either width or height is set and dimensions aren't given
if width > terminalWidth-1 {
return nil, fmt.Errorf("set width must be lower than terminal width")
}
if width != 0 && height == 0 {
// If width is set and height is not set, use width to calculate aspect ratio
asciiWidth = width
smallImg = resize.Resize(uint(asciiWidth), 0, img, resize.Lanczos3)
asciiHeight = smallImg.Bounds().Max.Y - smallImg.Bounds().Min.Y
asciiHeight = int(0.5 * float32(asciiHeight))
if asciiHeight == 0 {
asciiHeight = 1
}
smallImg = resize.Resize(uint(asciiWidth), uint(asciiHeight), img, resize.Lanczos3)
} else if height != 0 && width == 0 {
// If height is set and width is not set, use height to calculate aspect ratio
asciiHeight = height
smallImg = resize.Resize(0, uint(asciiHeight), img, resize.Lanczos3)
asciiWidth = smallImg.Bounds().Max.X - smallImg.Bounds().Min.X
asciiWidth = int(2 * float32(asciiWidth))
if asciiWidth > terminalWidth-1 {
return nil, fmt.Errorf("width calculated with aspect ratio exceeds terminal width")
}
smallImg = resize.Resize(uint(asciiWidth), uint(asciiHeight), img, resize.Lanczos3)
} else {
return nil, fmt.Errorf("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
asciiHeight = terminalHeight - 1
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 {
asciiWidth = terminalWidth - 1
smallImg = resize.Resize(uint(asciiWidth), 0, img, resize.Lanczos3)
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)
}
var imgSet [][]AsciiPixel
// Repeated despite being in cmd/root.go to maintain support for library
//
// If there are passed dimensions, check whether the width exceeds terminal width
if len(dimensions) > 0 && !full {
if dimensions[0] > terminalWidth-1 {
return nil, fmt.Errorf("set width must be lower than terminal width")
}
}
// Initialize imgSet 2D slice
imgSet := make([][]AsciiPixel, asciiHeight)
for i := range imgSet {
imgSet[i] = make([]AsciiPixel, asciiWidth)
}
b := smallImg.Bounds()
@ -64,24 +159,11 @@ func ConvertToAsciiPixels(img image.Image, dimensions []int, width, height int,
grayPixel := color.GrayModel.Convert(oldPixel)
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
charDepth := r1 // 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)
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)
@ -95,7 +177,7 @@ func ConvertToAsciiPixels(img image.Image, dimensions []int, width, height int,
})
}
imgSet = append(imgSet, temp)
imgSet[y] = temp
}
// This rarely affects performance since the ascii art 2D slice size isn't that large
@ -105,3 +187,22 @@ func ConvertToAsciiPixels(img image.Image, dimensions []int, width, height int,
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
}

View File

@ -1,166 +0,0 @@
/*
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,11 +1,10 @@
name: ascii-image-converter
base: core18
version: "1.13.1"
version: "1.6.0"
summary: Convert images and gifs into ascii art
description: |
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.
them out onto the console. Supported input formats are JPEG/JPG, PNG, WEBP, BMP, TIFF/TIF and GIF
grade: stable
confinement: strict