Compare commits

...

19 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
26 changed files with 435 additions and 335 deletions

View File

@ -30,7 +30,6 @@ builds:
# - mips64 # - mips64
# - mips64le # - mips64le
archives: archives:
- -
name_template: "{{ .ProjectName }}_{{ .Os }}_{{ .Arch }}" name_template: "{{ .ProjectName }}_{{ .Os }}_{{ .Arch }}"

View File

@ -28,6 +28,7 @@ Input formats currently supported:
* [Debian / Ubuntu-based](#debian-or-ubuntu-based-distros) * [Debian / Ubuntu-based](#debian-or-ubuntu-based-distros)
* [Homebrew](#homebrew) * [Homebrew](#homebrew)
* [AUR](#aur) * [AUR](#aur)
* [Scoop](#scoop)
* [Snap](#snap) * [Snap](#snap)
* [Go](#go) * [Go](#go)
* [Linux (binaries)](#linux) * [Linux (binaries)](#linux)
@ -41,7 +42,7 @@ Input formats currently supported:
## Installation ## Installation
### Debian or Ubuntu-based Distros ### Debian or Ubuntu-based Distros
Execute the following commands in order: Execute the following commands in order:
@ -59,7 +60,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: To remove the package source (which means you won't be getting any further updates), execute this command:
``` ```
sudo rm -rfv /etc/apt/sources.list.d/ascii-image-converter.list sudo rm -v /etc/apt/sources.list.d/ascii-image-converter.list
``` ```
<hr> <hr>
@ -92,6 +93,15 @@ AUR helper:
``` ```
<aur-helper> -S ascii-image-converter-git <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> <hr>
@ -158,13 +168,19 @@ Example:
ascii-image-converter myImage.jpeg ascii-image-converter myImage.jpeg
``` ```
> **Note:** Piped binary input is also supported
> ```
> cat myImage.png | ascii-image-converter -
> ```
### Flags ### Flags
#### --color OR -C #### --color OR -C
> **Note:** Your terminal must support 24-bit colors for appropriate results > **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. Works with the --negative flag as well. Display ascii art with the colors from original image.
``` ```
ascii-image-converter [image paths/urls] -C ascii-image-converter [image paths/urls] -C
@ -391,10 +407,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 > **Note:** This flag will be ignored if `--save-img` or `--save-gif` flags are not set
This flag takes an RGB value that sets the background color in saved png and gif files. 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] -s . --save-bg 255,255,255 # For white background ascii-image-converter [image paths/urls] -s . --save-bg 255,255,255,100 # For white background
``` ```
#### --font #### --font
@ -415,6 +431,14 @@ 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 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 #### --formats
Display supported input formats. Display supported input formats.
@ -460,11 +484,10 @@ func main() {
flags.SaveImagePath = "." flags.SaveImagePath = "."
flags.CustomMap = " .-=+#@" flags.CustomMap = " .-=+#@"
flags.FontFilePath = "./RobotoMono-Regular.ttf" // If file is in current directory flags.FontFilePath = "./RobotoMono-Regular.ttf" // If file is in current directory
flags.SaveBackgroundColor = [3]int{50, 50, 50} flags.SaveBackgroundColor = [4]int{50, 50, 50, 100}
// This MUST be set to true for environments where a terminal isn't available (such as web servers) // Note: For environments where a terminal isn't available (such as web servers), you MUST
// However, for this, one of flags.Width, flags.Height or flags.Dimensions must be set. // specify atleast one of flags.Width, flags.Height or flags.Dimensions
flags.NoTermSizeComparison = true
// Conversion for an image // Conversion for an image
asciiArt, err := aic_package.Convert(filePath, flags) asciiArt, err := aic_package.Convert(filePath, flags)
@ -523,8 +546,6 @@ You can fork the project and implement any changes you want for a pull request.
[github.com/gookit/color](https://github.com/gookit/color) [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) [github.com/makeworld-the-better-one/dither](https://github.com/makeworld-the-better-one/dither)
## License ## License

View File

@ -45,20 +45,26 @@ as an ascii art gif.
Multi-threading has been implemented in multiple places due to long execution time Multi-threading has been implemented in multiple places due to long execution time
*/ */
func pathIsGif(gifPath, urlImgName string, pathIsURl bool, urlImgBytes []byte, localGif *os.File) error { func pathIsGif(gifPath, urlImgName string, pathIsURl bool, urlImgBytes, pipedInputBytes []byte, localGif *os.File) error {
var ( var (
originalGif *gif.GIF originalGif *gif.GIF
err error err error
) )
if pathIsURl { if gifPath == "-" {
originalGif, err = gif.DecodeAll(bytes.NewReader(pipedInputBytes))
} else if pathIsURl {
originalGif, err = gif.DecodeAll(bytes.NewReader(urlImgBytes)) originalGif, err = gif.DecodeAll(bytes.NewReader(urlImgBytes))
} else { } else {
originalGif, err = gif.DecodeAll(localGif) originalGif, err = gif.DecodeAll(localGif)
} }
if err != nil { if err != nil {
return fmt.Errorf("can't decode %v: %v", gifPath, err) if gifPath == "-" {
return fmt.Errorf("can't decode piped input: %v", err)
} else {
return fmt.Errorf("can't decode %v: %v", gifPath, err)
}
} }
var ( var (
@ -91,24 +97,33 @@ func pathIsGif(gifPath, urlImgName string, pathIsURl bool, urlImgBytes []byte, l
// If a frame is found that is smaller than the first frame, then this gif contains smaller subimages that are // 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 // positioned inside the original gif. This behavior isn't supported by this app
if firstGifFrameWidth != frameImage.Bounds().Dx() || firstGifFrameHeight != frameImage.Bounds().Dy() { if firstGifFrameWidth != frameImage.Bounds().Dx() || firstGifFrameHeight != frameImage.Bounds().Dy() {
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") 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) os.Exit(0)
} }
var imgSet [][]imgManip.AsciiPixel var imgSet [][]imgManip.AsciiPixel
imgSet, err = imgManip.ConvertToAsciiPixels(frameImage, dimensions, width, height, flipX, flipY, full, braille, dither, noTermSizeComparison) imgSet, err = imgManip.ConvertToAsciiPixels(frameImage, dimensions, width, height, flipX, flipY, full, braille, dither)
if err != nil { if err != nil {
fmt.Println("Error:", err) fmt.Printf("Error: %v\n", err)
os.Exit(0) os.Exit(0)
} }
var asciiCharSet [][]imgManip.AsciiChar var asciiCharSet [][]imgManip.AsciiChar
if braille { if braille {
asciiCharSet = imgManip.ConvertToBrailleChars(imgSet, negative, colored, colorBg, fontColor, threshold) asciiCharSet, err = imgManip.ConvertToBrailleChars(imgSet, negative, colored, grayscale, colorBg, fontColor, threshold)
} else { } else {
asciiCharSet = imgManip.ConvertToAsciiChars(imgSet, negative, colored, complex, colorBg, customMap, fontColor) 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].asciiCharSet = asciiCharSet
gifFramesSlice[i].delay = originalGif.Delay[i] gifFramesSlice[i].delay = originalGif.Delay[i]
@ -187,7 +202,7 @@ func pathIsGif(gifPath, urlImgName string, pathIsURl bool, urlImgBytes []byte, l
colored || grayscale, colored || grayscale,
) )
if err != nil { if err != nil {
fmt.Println("Error:", err) fmt.Printf("Error: %v\n", err)
os.Exit(0) os.Exit(0)
} }
@ -231,25 +246,29 @@ func pathIsGif(gifPath, urlImgName string, pathIsURl bool, urlImgBytes []byte, l
gif.EncodeAll(gifFile, outGif) gif.EncodeAll(gifFile, outGif)
fmt.Printf(" \r") fmt.Printf(" \r")
fmt.Println("Saved " + fullPathName)
} }
// Display the gif // Display the gif
loopCount := 0 if !onlySave {
for { loopCount := 0
for i, asciiFrame := range asciiArtSet { for {
clearScreen() for i, asciiFrame := range asciiArtSet {
fmt.Println(asciiFrame) clearScreen()
time.Sleep(time.Duration((time.Second * time.Duration(originalGif.Delay[i])) / 100)) fmt.Println(asciiFrame)
} time.Sleep(time.Duration((time.Second * time.Duration(originalGif.Delay[i])) / 100))
}
// If gif is infinite loop // If gif is infinite loop
if originalGif.LoopCount == 0 { if originalGif.LoopCount == 0 {
continue continue
} }
loopCount++ loopCount++
if loopCount == originalGif.LoopCount { if loopCount == originalGif.LoopCount {
break break
}
} }
} }

View File

@ -27,23 +27,29 @@ import (
) )
// This function decodes the passed image and returns an ascii art string, optionaly saving it as a .txt and/or .png file // 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 []byte, localImg *os.File) (string, error) { func pathIsImage(imagePath, urlImgName string, pathIsURl bool, urlImgBytes, pipedInputBytes []byte, localImg *os.File) (string, error) {
var ( var (
imData image.Image imData image.Image
err error err error
) )
if pathIsURl { if imagePath == "-" {
imData, _, err = image.Decode(bytes.NewReader(pipedInputBytes))
} else if pathIsURl {
imData, _, err = image.Decode(bytes.NewReader(urlImgBytes)) imData, _, err = image.Decode(bytes.NewReader(urlImgBytes))
} else { } else {
imData, _, err = image.Decode(localImg) imData, _, err = image.Decode(localImg)
} }
if err != nil { if err != nil {
return "", fmt.Errorf("can't decode %v: %v", imagePath, err) if imagePath == "-" {
return "", fmt.Errorf("can't decode piped input: %v", err)
} else {
return "", fmt.Errorf("can't decode %v: %v", imagePath, err)
}
} }
imgSet, err := imgManip.ConvertToAsciiPixels(imData, dimensions, width, height, flipX, flipY, full, braille, dither, noTermSizeComparison) imgSet, err := imgManip.ConvertToAsciiPixels(imData, dimensions, width, height, flipX, flipY, full, braille, dither)
if err != nil { if err != nil {
return "", err return "", err
} }
@ -51,9 +57,12 @@ func pathIsImage(imagePath, urlImgName string, pathIsURl bool, urlImgBytes []byt
var asciiSet [][]imgManip.AsciiChar var asciiSet [][]imgManip.AsciiChar
if braille { if braille {
asciiSet = imgManip.ConvertToBrailleChars(imgSet, negative, colored, colorBg, fontColor, threshold) asciiSet, err = imgManip.ConvertToBrailleChars(imgSet, negative, colored, grayscale, colorBg, fontColor, threshold)
} else { } else {
asciiSet = imgManip.ConvertToAsciiChars(imgSet, negative, colored, complex, colorBg, customMap, fontColor) 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 // Save ascii art as .png image before printing it, if --save-img flag is passed
@ -64,6 +73,7 @@ func pathIsImage(imagePath, urlImgName string, pathIsURl bool, urlImgBytes []byt
saveImagePath, saveImagePath,
imagePath, imagePath,
urlImgName, urlImgName,
onlySave,
); err != nil { ); err != nil {
return "", fmt.Errorf("can't save file: %v", err) return "", fmt.Errorf("can't save file: %v", err)
@ -77,6 +87,7 @@ func pathIsImage(imagePath, urlImgName string, pathIsURl bool, urlImgBytes []byt
imagePath, imagePath,
saveTxtPath, saveTxtPath,
urlImgName, urlImgName,
onlySave,
); err != nil { ); err != nil {
return "", fmt.Errorf("can't save file: %v", err) return "", fmt.Errorf("can't save file: %v", err)
@ -86,5 +97,8 @@ func pathIsImage(imagePath, urlImgName string, pathIsURl bool, urlImgBytes []byt
ascii := flattenAscii(asciiSet, colored || grayscale, false) ascii := flattenAscii(asciiSet, colored || grayscale, false)
result := strings.Join(ascii, "\n") result := strings.Join(ascii, "\n")
if onlySave {
return "", nil
}
return result, nil return result, nil
} }

View File

@ -35,32 +35,40 @@ import (
"github.com/golang/freetype/truetype" "github.com/golang/freetype/truetype"
) )
var pipedInputTypes = []string{
"image/png",
"image/jpeg",
"image/webp",
"image/tiff",
"image/bmp",
}
// Return default configuration for flags. // Return default configuration for flags.
// Can be sent directly to ConvertImage() for default ascii art // Can be sent directly to ConvertImage() for default ascii art
func DefaultFlags() Flags { func DefaultFlags() Flags {
return Flags{ return Flags{
Complex: false, Complex: false,
Dimensions: nil, Dimensions: nil,
Width: 0, Width: 0,
Height: 0, Height: 0,
SaveTxtPath: "", SaveTxtPath: "",
SaveImagePath: "", SaveImagePath: "",
SaveGifPath: "", SaveGifPath: "",
Negative: false, Negative: false,
Colored: false, Colored: false,
CharBackgroundColor: false, CharBackgroundColor: false,
Grayscale: false, Grayscale: false,
CustomMap: "", CustomMap: "",
FlipX: false, FlipX: false,
FlipY: false, FlipY: false,
Full: false, Full: false,
FontFilePath: "", FontFilePath: "",
FontColor: [3]int{255, 255, 255}, FontColor: [3]int{255, 255, 255},
SaveBackgroundColor: [3]int{0, 0, 0}, SaveBackgroundColor: [4]int{0, 0, 0, 100},
Braille: false, Braille: false,
Threshold: 128, Threshold: 128,
Dither: false, Dither: false,
NoTermSizeComparison: false, OnlySave: false,
} }
} }
@ -96,44 +104,84 @@ func Convert(filePath string, flags Flags) (string, error) {
braille = flags.Braille braille = flags.Braille
threshold = flags.Threshold threshold = flags.Threshold
dither = flags.Dither dither = flags.Dither
noTermSizeComparison = flags.NoTermSizeComparison onlySave = flags.OnlySave
inputIsGif = path.Ext(filePath) == ".gif"
// Declared at the start since some variables are initially used in conditional blocks // Declared at the start since some variables are initially used in conditional blocks
var ( var (
localFile *os.File localFile *os.File
urlImgBytes []byte urlImgBytes []byte
urlImgName string = "" urlImgName string = ""
err error pipedInputBytes []byte
err error
) )
pathIsURl := isURL(filePath) pathIsURl := isURL(filePath)
// Different modes of reading data depending upon whether or not filePath is a url // Different modes of reading data depending upon whether or not filePath is a url
if pathIsURl {
fmt.Printf("Fetching file from url...\r")
retrievedImage, err := http.Get(filePath) if filePath != "-" {
if err != nil { if pathIsURl {
return "", fmt.Errorf("can't fetch content: %v", err) 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()
} }
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 { } else {
// Check file/data type of piped input
localFile, err = os.Open(filePath) if !isInputFromPipe() {
if err != nil { return "", fmt.Errorf("there is no input being piped to stdin")
return "", fmt.Errorf("unable to open file: %v", err)
} }
defer localFile.Close()
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 path to font file is provided, use it
@ -151,9 +199,9 @@ func Convert(filePath string, flags Flags) (string, error) {
tempFont, _ = truetype.Parse(embeddedDejaVuObliqueFont) tempFont, _ = truetype.Parse(embeddedDejaVuObliqueFont)
} }
if path.Ext(filePath) == ".gif" { if inputIsGif {
return "", pathIsGif(filePath, urlImgName, pathIsURl, urlImgBytes, localFile) return "", pathIsGif(filePath, urlImgName, pathIsURl, urlImgBytes, pipedInputBytes, localFile)
} else { } else {
return pathIsImage(filePath, urlImgName, pathIsURl, urlImgBytes, localFile) return pathIsImage(filePath, urlImgName, pathIsURl, urlImgBytes, pipedInputBytes, localFile)
} }
} }

View File

@ -17,6 +17,7 @@ limitations under the License.
package aic_package package aic_package
import ( import (
"fmt"
"image" "image"
"image/color" "image/color"
@ -48,7 +49,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. Size of resulting image may also be considerably larger than original image.
*/ */
func createImageToSave(asciiArt [][]imgManip.AsciiChar, colored bool, saveImagePath, imagePath, urlImgName string) error { func createImageToSave(asciiArt [][]imgManip.AsciiChar, colored bool, saveImagePath, imagePath, urlImgName string, onlySave bool) error {
constant := 14.0 constant := 14.0
@ -73,10 +74,11 @@ func createImageToSave(asciiArt [][]imgManip.AsciiChar, colored bool, saveImageP
dc := gg.NewContext(imgWidth, imgHeight) dc := gg.NewContext(imgWidth, imgHeight)
// Set image background // Set image background
dc.SetRGB( dc.SetRGBA(
float64(saveBgColor[0])/255, float64(saveBgColor[0])/255,
float64(saveBgColor[1])/255, float64(saveBgColor[1])/255,
float64(saveBgColor[2])/255, float64(saveBgColor[2])/255,
float64(saveBgColor[3])/100,
) )
dc.Clear() dc.Clear()
@ -138,5 +140,9 @@ func createImageToSave(asciiArt [][]imgManip.AsciiChar, colored bool, saveImageP
return err return err
} }
if onlySave {
fmt.Println("Saved " + fullPathName)
}
return dc.SavePNG(fullPathName) return dc.SavePNG(fullPathName)
} }

View File

@ -28,7 +28,7 @@ import (
imgManip "github.com/TheZoraiz/ascii-image-converter/image_manipulation" imgManip "github.com/TheZoraiz/ascii-image-converter/image_manipulation"
) )
func saveAsciiArt(asciiSet [][]imgManip.AsciiChar, imagePath, savePath, urlImgName string) error { func saveAsciiArt(asciiSet [][]imgManip.AsciiChar, imagePath, savePath, urlImgName string, onlySave bool) error {
// To make sure uncolored ascii art is the one saved as .txt // To make sure uncolored ascii art is the one saved as .txt
saveAscii := flattenAscii(asciiSet, false, true) saveAscii := flattenAscii(asciiSet, false, true)
@ -46,7 +46,13 @@ func saveAsciiArt(asciiSet [][]imgManip.AsciiChar, imagePath, savePath, urlImgNa
// If path exists // If path exists
if _, err := os.Stat(savePath); !os.IsNotExist(err) { if _, err := os.Stat(savePath); !os.IsNotExist(err) {
return ioutil.WriteFile(savePath+saveFileName, []byte(strings.Join(saveAscii, "\n")), 0666) 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 { } else {
return fmt.Errorf("save path %v does not exist", savePath) return fmt.Errorf("save path %v does not exist", savePath)
} }
@ -61,6 +67,13 @@ func createSaveFileName(imagePath, urlImgName, label string) (string, error) {
return newName + label, nil return newName + label, nil
} }
if imagePath == "-" {
if inputIsGif {
return "piped-gif" + label, nil
}
return "piped-img" + label, nil
}
fileInfo, err := os.Stat(imagePath) fileInfo, err := os.Stat(imagePath)
if err != nil { if err != nil {
return "", err return "", err
@ -120,7 +133,7 @@ func getFullSavePath(imageName, saveFilePath string) (string, error) {
} }
func isURL(urlString string) bool { func isURL(urlString string) bool {
if len(urlString) < 7 { if len(urlString) < 8 {
return false return false
} else if urlString[:7] == "http://" || urlString[:8] == "https://" { } else if urlString[:7] == "http://" || urlString[:8] == "https://" {
return true return true
@ -155,3 +168,8 @@ func clearScreen() {
os.Exit(0) os.Exit(0)
} }
} }
func isInputFromPipe() bool {
fileInfo, _ := os.Stdin.Stat()
return fileInfo.Mode()&os.ModeCharDevice == 0
}

View File

@ -83,7 +83,7 @@ type Flags struct {
// Background RGB color in saved png or gif files. // Background RGB color in saved png or gif files.
// This will be ignored if Flags.SaveImagePath or Flags.SaveGifPath are not set // This will be ignored if Flags.SaveImagePath or Flags.SaveGifPath are not set
SaveBackgroundColor [3]int SaveBackgroundColor [4]int
// Use braille characters instead of ascii. Terminal must support UTF-8 encoding. // Use braille characters instead of ascii. Terminal must support UTF-8 encoding.
// Otherwise, problems may be encountered with colored or even uncolored braille art. // Otherwise, problems may be encountered with colored or even uncolored braille art.
@ -99,36 +99,33 @@ type Flags struct {
// is meant for braille art. Therefore, it will be ignored if Flags.Braille is false // is meant for braille art. Therefore, it will be ignored if Flags.Braille is false
Dither bool Dither bool
// Set this to true to disable comparing ascii art size to terminal. However, at least // If Flags.SaveImagePath, Flags.SaveTxtPath or Flags.SaveGifPath are set, then don't
// one of Flags.Width, Flags.Height or Flags.Dimensions should be passed to keep it from // print on terminal
// throwing an error. OnlySave bool
//
// Note: This option is added for using the library in an environment without terminals (such as web servers).
// Furthermore, coloring options will not work outside of a terminal environment.
NoTermSizeComparison bool
} }
var ( var (
dimensions []int dimensions []int
width int width int
height int height int
complex bool complex bool
saveTxtPath string saveTxtPath string
saveImagePath string saveImagePath string
saveGifPath string saveGifPath string
grayscale bool grayscale bool
negative bool negative bool
colored bool colored bool
colorBg bool colorBg bool
customMap string customMap string
flipX bool flipX bool
flipY bool flipY bool
full bool full bool
fontPath string fontPath string
fontColor [3]int fontColor [3]int
saveBgColor [3]int saveBgColor [4]int
braille bool braille bool
threshold int threshold int
dither bool dither bool
noTermSizeComparison bool onlySave bool
inputIsGif bool
) )

View File

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

View File

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

View File

@ -52,12 +52,13 @@ var (
braille bool braille bool
threshold int threshold int
dither bool dither bool
onlySave bool
// Root commands // Root commands
rootCmd = &cobra.Command{ rootCmd = &cobra.Command{
Use: "ascii-image-converter [image paths/urls]", Use: "ascii-image-converter [image paths/urls or piped stdin]",
Short: "Converts images and gifs into ascii art", Short: "Converts images and gifs into ascii art",
Version: "1.9.2", 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.", 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 // Not RunE since help text is getting larger and seeing it for every error impacts user experience
@ -68,50 +69,64 @@ var (
} }
flags := aic_package.Flags{ flags := aic_package.Flags{
Complex: complex, Complex: complex,
Dimensions: dimensions, Dimensions: dimensions,
Width: width, Width: width,
Height: height, Height: height,
SaveTxtPath: saveTxtPath, SaveTxtPath: saveTxtPath,
SaveImagePath: saveImagePath, SaveImagePath: saveImagePath,
SaveGifPath: saveGifPath, SaveGifPath: saveGifPath,
Negative: negative, Negative: negative,
Colored: colored, Colored: colored,
CharBackgroundColor: colorBg, CharBackgroundColor: colorBg,
Grayscale: grayscale, Grayscale: grayscale,
CustomMap: customMap, CustomMap: customMap,
FlipX: flipX, FlipX: flipX,
FlipY: flipY, FlipY: flipY,
Full: full, Full: full,
FontFilePath: fontFile, FontFilePath: fontFile,
FontColor: [3]int{fontColor[0], fontColor[1], fontColor[2]}, FontColor: [3]int{fontColor[0], fontColor[1], fontColor[2]},
SaveBackgroundColor: [3]int{saveBgColor[0], saveBgColor[1], saveBgColor[2]}, SaveBackgroundColor: [4]int{saveBgColor[0], saveBgColor[1], saveBgColor[2], saveBgColor[3]},
Braille: braille, Braille: braille,
Threshold: threshold, Threshold: threshold,
Dither: dither, Dither: dither,
NoTermSizeComparison: false, OnlySave: onlySave,
}
if args[0] == "-" {
printAscii(args[0], flags)
return
} }
for _, imagePath := range args { for _, imagePath := range args {
if err := printAscii(imagePath, flags); err != nil {
if asciiArt, err := aic_package.Convert(imagePath, flags); err == nil { return
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 // Cobra configuration from here on
func Execute() { func Execute() {
@ -128,7 +143,7 @@ func init() {
rootCmd.Flags().SortFlags = false rootCmd.Flags().SortFlags = false
// rootCmd.PersistentFlags().StringVar(&cfgFile, "config", "", "config file (default is $HOME/.ascii-image-converter.yaml)") // 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\nTerminal must support 24-bit colors\n(Inverts with --negative flag)\n(Overrides --grayscale and --font-color flags)\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().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().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(&width, "width", "W", 0, "Set width for ascii art in CHARACTER length\nHeight is kept to aspect ratio\ne.g. -W 60\n")
@ -146,9 +161,10 @@ 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().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(&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().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 RGB value\ne.g. --save-bg 255,255,255\n(Defaults to 0,0,0)\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().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().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().BoolVar(&formatsTrue, "formats", false, "Display supported input formats\n")
rootCmd.PersistentFlags().BoolP("help", "h", false, "Help for "+rootCmd.Name()+"\n") rootCmd.PersistentFlags().BoolP("help", "h", false, "Help for "+rootCmd.Name()+"\n")

View File

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

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.1 MiB

After

Width:  |  Height:  |  Size: 1.4 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 184 KiB

After

Width:  |  Height:  |  Size: 154 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 582 KiB

After

Width:  |  Height:  |  Size: 355 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 253 KiB

After

Width:  |  Height:  |  Size: 158 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 216 KiB

After

Width:  |  Height:  |  Size: 124 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 352 KiB

After

Width:  |  Height:  |  Size: 223 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 669 KiB

After

Width:  |  Height:  |  Size: 409 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.9 MiB

After

Width:  |  Height:  |  Size: 3.0 MiB

25
go.mod
View File

@ -1,26 +1,35 @@
module github.com/TheZoraiz/ascii-image-converter module github.com/TheZoraiz/ascii-image-converter
go 1.16 go 1.17
require ( require (
github.com/asaskevich/govalidator v0.0.0-20210307081110-f21760c49a8d
github.com/disintegration/imaging v1.6.2 github.com/disintegration/imaging v1.6.2
github.com/fogleman/gg v1.3.0 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/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0
github.com/gookit/color v1.4.2 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/makeworld-the-better-one/dither/v2 v2.2.0
github.com/mitchellh/go-homedir v1.1.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/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/mapstructure v1.4.1 // indirect
github.com/pelletier/go-toml v1.9.1 // indirect github.com/pelletier/go-toml v1.9.1 // indirect
github.com/spf13/afero v1.6.0 // indirect github.com/spf13/afero v1.6.0 // indirect
github.com/spf13/cast v1.3.1 // 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/jwalterweatherman v1.1.0 // indirect
github.com/spf13/viper v1.7.1 github.com/spf13/pflag v1.0.5 // indirect
golang.org/x/image v0.0.0-20210628002857-a66eb6448b8d 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/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/ini.v1 v1.62.0 // indirect
gopkg.in/yaml.v2 v2.4.0 // indirect
) )

2
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/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-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/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 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/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8=
github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs= github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs=

View File

@ -16,12 +16,6 @@ limitations under the License.
package image_conversions package image_conversions
import (
"strconv"
"github.com/gookit/color"
)
var ( var (
// Reference taken from http://paulbourke.net/dataformats/asciiart/ // Reference taken from http://paulbourke.net/dataformats/asciiart/
asciiTableSimple = " .:-=+*#%@" asciiTableSimple = " .:-=+*#%@"
@ -55,7 +49,7 @@ to a 2D image_conversions.AsciiChar slice
If complex parameter is true, values are compared to 70 levels of color density in ASCII characters. 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. Otherwise, values are compared to 10 levels of color density in ASCII characters.
*/ */
func ConvertToAsciiChars(imgSet [][]AsciiPixel, negative, colored, complex, colorBg bool, customMap string, fontColor [3]int) [][]AsciiChar { func ConvertToAsciiChars(imgSet [][]AsciiPixel, negative, colored, grayscale, complex, colorBg bool, customMap string, fontColor [3]int) ([][]AsciiChar, error) {
height := len(imgSet) height := len(imgSet)
width := len(imgSet[0]) width := len(imgSet[0])
@ -128,29 +122,34 @@ func ConvertToAsciiChars(imgSet [][]AsciiPixel, negative, colored, complex, colo
tempInt = (len(chosenTable) - 1) - tempInt tempInt = (len(chosenTable) - 1) - tempInt
} }
rStr := strconv.Itoa(r)
gStr := strconv.Itoa(g)
bStr := strconv.Itoa(b)
var char AsciiChar var char AsciiChar
char.Simple = chosenTable[tempInt] asciiChar := chosenTable[tempInt]
char.Simple = asciiChar
var err error
if colorBg { if colorBg {
char.OriginalColor = color.Sprintf("<bg="+rStr+","+gStr+","+bStr+">%v</>", chosenTable[tempInt]) char.OriginalColor, err = getColoredCharForTerm(uint8(r), uint8(g), uint8(b), asciiChar, true)
} else { } else {
char.OriginalColor = color.Sprintf("<fg="+rStr+","+gStr+","+bStr+">%v</>", chosenTable[tempInt]) 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 font color is not set, use a simple string. Otherwise, use True color
if fontColor != [3]int{255, 255, 255} { if fontColor != [3]int{255, 255, 255} {
fcR := strconv.Itoa(fontColor[0]) fcR := fontColor[0]
fcG := strconv.Itoa(fontColor[1]) fcG := fontColor[1]
fcB := strconv.Itoa(fontColor[2]) fcB := fontColor[2]
if colorBg { if colorBg {
char.SetColor = color.Sprintf("<bg="+fcR+","+fcG+","+fcB+">%v</>", chosenTable[tempInt]) char.SetColor, err = getColoredCharForTerm(uint8(fcR), uint8(fcG), uint8(fcB), asciiChar, true)
} else { } else {
char.SetColor = color.Sprintf("<fg="+fcR+","+fcG+","+fcB+">%v</>", chosenTable[tempInt]) char.SetColor, err = getColoredCharForTerm(uint8(fcR), uint8(fcG), uint8(fcB), asciiChar, false)
}
if err != nil {
return nil, err
} }
} }
@ -165,7 +164,7 @@ func ConvertToAsciiChars(imgSet [][]AsciiPixel, negative, colored, complex, colo
result = append(result, tempSlice) result = append(result, tempSlice)
} }
return result return result, nil
} }
/* /*
@ -174,7 +173,7 @@ to a 2D image_conversions.AsciiChar slice
Unlike ConvertToAsciiChars(), this function calculates braille characters instead of ascii Unlike ConvertToAsciiChars(), this function calculates braille characters instead of ascii
*/ */
func ConvertToBrailleChars(imgSet [][]AsciiPixel, negative, colored, colorBg bool, fontColor [3]int, threshold int) [][]AsciiChar { func ConvertToBrailleChars(imgSet [][]AsciiPixel, negative, colored, grayscale, colorBg bool, fontColor [3]int, threshold int) ([][]AsciiChar, error) {
BrailleThreshold = uint32(threshold) BrailleThreshold = uint32(threshold)
@ -216,29 +215,33 @@ func ConvertToBrailleChars(imgSet [][]AsciiPixel, negative, colored, colorBg boo
} }
} }
rStr := strconv.Itoa(r)
gStr := strconv.Itoa(g)
bStr := strconv.Itoa(b)
var char AsciiChar var char AsciiChar
char.Simple = brailleChar char.Simple = brailleChar
var err error
if colorBg { if colorBg {
char.OriginalColor = color.Sprintf("<bg="+rStr+","+gStr+","+bStr+">%v</>", brailleChar) char.OriginalColor, err = getColoredCharForTerm(uint8(r), uint8(g), uint8(b), brailleChar, true)
} else { } else {
char.OriginalColor = color.Sprintf("<fg="+rStr+","+gStr+","+bStr+">%v</>", brailleChar) 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 font color is not set, use a simple string. Otherwise, use True color
if fontColor != [3]int{255, 255, 255} { if fontColor != [3]int{255, 255, 255} {
fcR := strconv.Itoa(fontColor[0]) fcR := fontColor[0]
fcG := strconv.Itoa(fontColor[1]) fcG := fontColor[1]
fcB := strconv.Itoa(fontColor[2]) fcB := fontColor[2]
if colorBg { if colorBg {
char.SetColor = color.Sprintf("<bg="+fcR+","+fcG+","+fcB+">%v</>", brailleChar) char.SetColor, err = getColoredCharForTerm(uint8(fcR), uint8(fcG), uint8(fcB), brailleChar, true)
} else { } else {
char.SetColor = color.Sprintf("<fg="+fcR+","+fcG+","+fcB+">%v</>", brailleChar) char.SetColor, err = getColoredCharForTerm(uint8(fcR), uint8(fcG), uint8(fcB), brailleChar, false)
}
if err != nil {
return nil, err
} }
} }
@ -254,7 +257,7 @@ func ConvertToBrailleChars(imgSet [][]AsciiPixel, negative, colored, colorBg boo
result = append(result, tempSlice) result = append(result, tempSlice)
} }
return result return result, nil
} }
// Iterate through the BrailleStruct table to see which dots need to be highlighted // Iterate through the BrailleStruct table to see which dots need to be highlighted

View File

@ -34,16 +34,10 @@ getting numeric data for ASCII character comparison.
The returned 2D AsciiPixel slice contains each corresponding pixel's values 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, noTermSizeComparison bool) ([][]AsciiPixel, error) { func ConvertToAsciiPixels(img image.Image, dimensions []int, width, height int, flipX, flipY, full, isBraille, dither bool) ([][]AsciiPixel, error) {
var smallImg image.Image smallImg, err := resizeImage(img, full, isBraille, dimensions, width, height)
var err error
if noTermSizeComparison {
smallImg, err = resizeImageNoTerm(img, isBraille, dimensions, width, height)
} else {
smallImg, err = resizeImage(img, full, isBraille, dimensions, width, height)
}
if err != nil { if err != nil {
return nil, err return nil, err
} }

View File

@ -23,6 +23,7 @@ import (
"github.com/TheZoraiz/ascii-image-converter/aic_package/winsize" "github.com/TheZoraiz/ascii-image-converter/aic_package/winsize"
"github.com/disintegration/imaging" "github.com/disintegration/imaging"
gookitColor "github.com/gookit/color"
"github.com/makeworld-the-better-one/dither/v2" "github.com/makeworld-the-better-one/dither/v2"
) )
@ -44,16 +45,16 @@ func resizeImage(img image.Image, full, isBraille bool, dimensions []int, width,
var asciiWidth, asciiHeight int var asciiWidth, asciiHeight int
var smallImg image.Image var smallImg image.Image
terminalWidth, terminalHeight, err := winsize.GetTerminalSize()
if err != nil {
return nil, err
}
imgWidth := float64(img.Bounds().Dx()) imgWidth := float64(img.Bounds().Dx())
imgHeight := float64(img.Bounds().Dy()) imgHeight := float64(img.Bounds().Dy())
aspectRatio := imgWidth / imgHeight aspectRatio := imgWidth / imgHeight
if full { if full {
terminalWidth, _, err := winsize.GetTerminalSize()
if err != nil {
return nil, err
}
asciiWidth = terminalWidth - 1 asciiWidth = terminalWidth - 1
asciiHeight = int(float64(asciiWidth) / aspectRatio) asciiHeight = int(float64(asciiWidth) / aspectRatio)
asciiHeight = int(0.5 * float64(asciiHeight)) asciiHeight = int(0.5 * float64(asciiHeight))
@ -61,10 +62,6 @@ func resizeImage(img image.Image, full, isBraille bool, dimensions []int, width,
} else if (width != 0 || height != 0) && len(dimensions) == 0 { } else if (width != 0 || height != 0) && len(dimensions) == 0 {
// If either width or height is set and dimensions aren't given // 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 != 0 && height == 0 {
// If width is set and height is not set, use width to calculate aspect ratio // If width is set and height is not set, use width to calculate aspect ratio
@ -87,17 +84,18 @@ func resizeImage(img image.Image, full, isBraille bool, dimensions []int, width,
asciiWidth = 1 asciiWidth = 1
} }
if asciiWidth > terminalWidth-1 {
return nil, fmt.Errorf("width calculated with aspect ratio exceeds terminal width")
}
} else { } else {
return nil, fmt.Errorf("both width and height can't be set. Use dimensions instead") return nil, fmt.Errorf("error: both width and height can't be set. Use dimensions instead")
} }
} else if len(dimensions) == 0 { } else if len(dimensions) == 0 {
// This condition calculates aspect ratio according to terminal height // This condition calculates aspect ratio according to terminal height
terminalWidth, terminalHeight, err := winsize.GetTerminalSize()
if err != nil {
return nil, err
}
asciiHeight = terminalHeight - 1 asciiHeight = terminalHeight - 1
asciiWidth = int(float64(asciiHeight) * aspectRatio) asciiWidth = int(float64(asciiHeight) * aspectRatio)
asciiWidth = int(2 * float64(asciiWidth)) asciiWidth = int(2 * float64(asciiWidth))
@ -110,69 +108,13 @@ func resizeImage(img image.Image, full, isBraille bool, dimensions []int, width,
} }
} else { } else {
// Else, set passed dimensions
asciiWidth = dimensions[0] asciiWidth = dimensions[0]
asciiHeight = dimensions[1] asciiHeight = dimensions[1]
} }
// Repeated despite being in cmd/root.go to maintain support for library // Because one braille character has 8 dots (4 rows and 2 columns)
//
// 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")
}
}
if isBraille {
asciiWidth *= 2
asciiHeight *= 4
}
smallImg = imaging.Resize(img, asciiWidth, asciiHeight, imaging.Lanczos)
return smallImg, nil
}
func resizeImageNoTerm(img image.Image, 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 (width != 0 || height != 0) && len(dimensions) == 0 {
if width != 0 && height == 0 {
asciiWidth = width
asciiHeight = int(float64(asciiWidth) / aspectRatio)
asciiHeight = int(0.5 * float64(asciiHeight))
if asciiHeight == 0 {
asciiHeight = 1
}
} else if height != 0 && width == 0 {
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 {
asciiWidth = dimensions[0]
asciiHeight = dimensions[1]
} else {
return nil, fmt.Errorf("error: at least one of width, height or dimensions should be passed for NoTermSizeComparison")
}
if isBraille { if isBraille {
asciiWidth *= 2 asciiWidth *= 2
asciiHeight *= 4 asciiHeight *= 4
@ -200,3 +142,25 @@ func reverse(imgSet [][]AsciiPixel, flipX, flipY bool) [][]AsciiPixel {
return imgSet 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,6 +1,6 @@
name: ascii-image-converter name: ascii-image-converter
base: core18 base: core18
version: "1.9.2" version: "1.13.1"
summary: Convert images and gifs into ascii art summary: Convert images and gifs into ascii art
description: | description: |
ascii-image-converter is a command-line tool that converts images into ascii art and prints ascii-image-converter is a command-line tool that converts images into ascii art and prints