From df31045bae333f5d2ce4da46ed4d5783c7000bf7 Mon Sep 17 00:00:00 2001
From: Zoraiz
Date: Sun, 25 Jul 2021 23:27:16 +0500
Subject: [PATCH] Added grayscale flag
---
README.md | 17 +++++++++--
aic_package/convert_gif.go | 4 +--
aic_package/convert_image.go | 4 +--
aic_package/convert_root.go | 5 ++++
aic_package/create_ascii_image.go | 17 +++++------
cmd/root.go | 13 +++++----
image_manipulation/ascii_conversions.go | 38 ++++++++++++++++++-------
image_manipulation/image_conversions.go | 24 +++++++++-------
snapcraft.yaml | 4 +--
9 files changed, 82 insertions(+), 44 deletions(-)
diff --git a/README.md b/README.md
index 176e5b1..78f1c90 100644
--- a/README.md
+++ b/README.md
@@ -188,6 +188,16 @@ ascii-image-converter [image paths/urls] -m " .-=+#@"
+#### --grayscale OR -g
+
+Display ascii art in grayscale colors. This is the same as --color flag, except each character will be encoded with a grayscale RGB value.
+
+```
+ascii-image-converter [image paths/urls] -g
+# Or
+ascii-image-converter [image paths/urls] --grayscale
+```
+
#### --negative OR -n
Display ascii art in negative colors. Works with both uncolored and colored text from --color flag.
@@ -327,11 +337,12 @@ func main() {
flags.SaveImagePath = "." // Save generated PNG image in same directory
flags.SaveGifPath = "." // If gif was provided, save ascii art gif in same directory
flags.Negative = true // Ascii art will have negative color-depth
- flags.Colored = true // Keep colors from original image
- flags.CustomMap = " .-=+#@" // Starting from darkest to brightest shades. This overrites "complex" flag
+ flags.Colored = true // Keep colors from original image. This overrides flags.Grayscale
+ flags.Grayscale = true // Returns grayscale ascii art
+ flags.CustomMap = " .-=+#@" // Starting from darkest to brightest shades. This overrides flags.Complex
flags.FlipX = true // Flips ascii art horizontally
flags.FlipY = true // Flips ascii art vertically
- flags.Full = true // Display ascii art that fills the terminal width
+ flags.Full = true // Display ascii art that fills the terminal width. This overrides flags.Dimensions
// For an image
asciiArt, err := aic_package.Convert(filePath, flags)
diff --git a/aic_package/convert_gif.go b/aic_package/convert_gif.go
index 1209540..946fa8e 100644
--- a/aic_package/convert_gif.go
+++ b/aic_package/convert_gif.go
@@ -107,7 +107,7 @@ func pathIsGif(gifPath, urlImgName string, pathIsURl bool, urlImgBytes []byte, l
gifFramesSlice[i].asciiCharSet = asciiCharSet
gifFramesSlice[i].delay = originalGif.Delay[i]
- ascii := flattenAscii(asciiCharSet, colored)
+ ascii := flattenAscii(asciiCharSet, colored || grayscale)
asciiArtSet[i] = strings.Join(ascii, "\n")
@@ -179,7 +179,7 @@ func pathIsGif(gifPath, urlImgName string, pathIsURl bool, urlImgBytes []byte, l
tempImg, err := createGifFrameToSave(
gifFrame.asciiCharSet,
img,
- colored,
+ colored || grayscale,
)
if err != nil {
fmt.Println(err)
diff --git a/aic_package/convert_image.go b/aic_package/convert_image.go
index 2ed0246..808d2b3 100644
--- a/aic_package/convert_image.go
+++ b/aic_package/convert_image.go
@@ -56,7 +56,7 @@ func pathIsImage(imagePath, urlImgName string, pathIsURl bool, urlImgBytes []byt
if saveImagePath != "" {
if err := createImageToSave(
asciiSet,
- colored,
+ colored || grayscale,
saveImagePath,
imagePath,
urlImgName,
@@ -79,7 +79,7 @@ func pathIsImage(imagePath, urlImgName string, pathIsURl bool, urlImgBytes []byt
}
}
- ascii := flattenAscii(asciiSet, colored)
+ ascii := flattenAscii(asciiSet, colored || grayscale)
result := strings.Join(ascii, "\n")
return result, nil
diff --git a/aic_package/convert_root.go b/aic_package/convert_root.go
index 5d2adb3..8c180c6 100644
--- a/aic_package/convert_root.go
+++ b/aic_package/convert_root.go
@@ -43,6 +43,7 @@ type Flags struct {
SaveGifPath string
Negative bool
Colored bool
+ Grayscale bool
CustomMap string
FlipX bool
FlipY bool
@@ -55,6 +56,7 @@ var (
saveTxtPath string
saveImagePath string
saveGifPath string
+ grayscale bool
negative bool
colored bool
customMap string
@@ -74,6 +76,7 @@ func DefaultFlags() Flags {
SaveGifPath: "",
Negative: false,
Colored: false,
+ Grayscale: false,
CustomMap: "",
FlipX: false,
FlipY: false,
@@ -96,6 +99,7 @@ The "flags" argument should be declared as follows before passing:
SaveGifPath : string, // System path to save the ascii art gif as a .gif file. Pass "" to ignore
Negative: bool, // Pass true for negative color-depth ascii art
Colored: bool, // Pass true for returning colored ascii string
+ Grayscale: bool // Pass true for returning grayscale ascii string
CustomMap: string, // Custom map of ascii chars e.g. " .-+#@" . Nullifies "complex" flag. Pass "" to ignore.
FlipX: bool, // Pass true to return horizontally flipped ascii art
FlipY: bool, // Pass true to return vertically flipped ascii art
@@ -115,6 +119,7 @@ func Convert(filePath string, flags Flags) (string, error) {
saveGifPath = flags.SaveGifPath
negative = flags.Negative
colored = flags.Colored
+ grayscale = flags.Grayscale
customMap = flags.CustomMap
flipX = flags.FlipX
flipY = flags.FlipY
diff --git a/aic_package/create_ascii_image.go b/aic_package/create_ascii_image.go
index 7d012fb..1026a43 100644
--- a/aic_package/create_ascii_image.go
+++ b/aic_package/create_ascii_image.go
@@ -27,10 +27,17 @@ import (
"github.com/golang/freetype/truetype"
)
-// To embed font directly into the binary, instead of packaging it as a separate file
//go:embed RobotoMono-Bold.ttf
var embeddedFontFile []byte
+var tempFont *truetype.Font
+
+// Load embedded font
+func init() {
+ // Error not handled because the same font file will always be used
+ tempFont, _ = truetype.Parse(embeddedFontFile)
+}
+
/*
Unlike createGifFrameToSave(), this function is altered to ignore execution time and has a fixed font size.
This creates maximum quality ascii art, although the resulting image will not have the same dimensions
@@ -69,13 +76,7 @@ func createImageToSave(asciiArt [][]imgManip.AsciiChar, colored bool, saveImageP
dc.DrawImage(tempImg, 0, 0)
- // Load embedded font
- tempFont, err := truetype.Parse(embeddedFontFile)
- if err != nil {
- return err
- }
robotoBoldFontFace := truetype.NewFace(tempFont, &truetype.Options{Size: constant * 1.5})
-
dc.SetFontFace(robotoBoldFontFace)
// Font color of text on picture is white by default
@@ -98,7 +99,7 @@ func createImageToSave(asciiArt [][]imgManip.AsciiChar, colored bool, saveImageP
b := uint8(char.RgbValue[2])
if colored {
- // Simple put, dc.SetColor() sets color for EACH character before printing it
+ // Simply put, dc.SetColor() sets color for EACH character before printing it
dc.SetColor(color.RGBA{r, g, b, 255})
}
diff --git a/cmd/root.go b/cmd/root.go
index 1c3f9f9..449f680 100644
--- a/cmd/root.go
+++ b/cmd/root.go
@@ -39,6 +39,7 @@ var (
negative bool
formatsTrue bool
colored bool
+ grayscale bool
customMap string
flipX bool
flipY bool
@@ -48,7 +49,7 @@ var (
rootCmd = &cobra.Command{
Use: "ascii-image-converter [image paths/urls]",
Short: "Converts images and gifs into ascii art",
- Version: "1.4.0",
+ Version: "1.4.1",
Long: "This tool converts images into ascii art and prints them on the terminal.\nFurther configuration can be managed with flags.",
// Not RunE since help text is getting larger and seeing it for every error impacts user experience
@@ -66,6 +67,7 @@ var (
SaveGifPath: saveGifPath,
Negative: negative,
Colored: colored,
+ Grayscale: grayscale,
CustomMap: customMap,
FlipX: flipX,
FlipY: flipY,
@@ -185,12 +187,13 @@ 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\n(Can work with the --negative flag)\n")
+ rootCmd.PersistentFlags().BoolVarP(&colored, "color", "C", false, "Display ascii art with original colors\n(Can work with the --negative flag)\n(Overrides --grayscale flag)\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")
- rootCmd.PersistentFlags().StringVarP(&customMap, "map", "m", "", "Give custom ascii characters to map against\nOrdered from darkest to lightest\ne.g. -m \" .-+#@\" (Quotation marks excluded from map)\n(Cancels --complex flag)\n")
+ rootCmd.PersistentFlags().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(&grayscale, "grayscale", "g", false, "Display grayscale ascii art\n(Can work with --negative 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 that\nfill the terminal width\n(Cancels --dimensions flag)\n")
- rootCmd.PersistentFlags().BoolVarP(&negative, "negative", "n", false, "Display ascii art in negative colors\n(Can work with the --color flag)\n")
+ rootCmd.PersistentFlags().BoolVarP(&full, "full", "f", false, "Use largest dimensions for ascii art that\nfill the terminal width\n(Overrides --dimensions flag)\n")
+ rootCmd.PersistentFlags().BoolVarP(&negative, "negative", "n", false, "Display ascii art in negative colors\n")
rootCmd.PersistentFlags().BoolVarP(&flipX, "flipX", "x", false, "Flip ascii art horizontally\n")
rootCmd.PersistentFlags().BoolVarP(&flipY, "flipY", "y", false, "Flip ascii art vertically\n")
rootCmd.PersistentFlags().StringVarP(&saveImagePath, "save-img", "s", "", "Save ascii art as a .png file\nFormat: -ascii-art.png\nImage will be saved in passed path\n(pass . for current directory)\n")
diff --git a/image_manipulation/ascii_conversions.go b/image_manipulation/ascii_conversions.go
index e0135e8..5f30705 100644
--- a/image_manipulation/ascii_conversions.go
+++ b/image_manipulation/ascii_conversions.go
@@ -110,12 +110,12 @@ var asciiTableDetailed = map[int]string{
}
// For each individual element of imgSet in ConvertToASCIISlice()
-const MAX_VAL float32 = 65535
+const MAX_VAL float64 = 65535
type AsciiChar struct {
Colored string
Simple string
- RgbValue []uint32
+ RgbValue [3]uint32
}
// Converts the 2D AsciiPixel slice of image data (each instance representing each pixel of original image)
@@ -124,7 +124,7 @@ type AsciiChar struct {
//
// If complex parameter is true, values are compared to 69 levels of color density in ASCII characters.
// Otherwise, values are compared to 10 levels of color density in ASCII characters.
-func ConvertToAscii(imgSet [][]AsciiPixel, negative bool, colored bool, complex bool, customMap string) [][]AsciiChar {
+func ConvertToAscii(imgSet [][]AsciiPixel, negative, colored, complex bool, customMap string) [][]AsciiChar {
height := len(imgSet)
width := len(imgSet[0])
@@ -155,18 +155,26 @@ func ConvertToAscii(imgSet [][]AsciiPixel, negative bool, colored bool, complex
var tempSlice []AsciiChar
for j := 0; j < width; j++ {
- value := float32(imgSet[i][j].grayscaleValue)
+ value := float64(imgSet[i][j].charDepth)
// Gets appropriate string index from asciiTableSimple by percentage comparisons with its length
- tempFloat := (value / MAX_VAL) * float32(len(chosenTable))
+ tempFloat := (value / MAX_VAL) * float64(len(chosenTable))
if value == MAX_VAL {
- tempFloat = float32(len(chosenTable) - 1)
+ tempFloat = float64(len(chosenTable) - 1)
}
tempInt := int(tempFloat)
- r := int(imgSet[i][j].rgbValue[0])
- g := int(imgSet[i][j].rgbValue[1])
- b := int(imgSet[i][j].rgbValue[2])
+ var r, g, b int
+
+ if colored {
+ r = int(imgSet[i][j].rgbValue[0])
+ g = int(imgSet[i][j].rgbValue[1])
+ b = int(imgSet[i][j].rgbValue[2])
+ } else {
+ r = int(imgSet[i][j].grayscaleValue[0])
+ g = int(imgSet[i][j].grayscaleValue[1])
+ b = int(imgSet[i][j].grayscaleValue[2])
+ }
if negative {
// Select character from opposite side of table as well as turn pixels negative
@@ -175,7 +183,11 @@ func ConvertToAscii(imgSet [][]AsciiPixel, negative bool, colored bool, complex
b = 255 - b
// To preserve negative rgb values for saving png image later down the line, since it uses imgSet
- imgSet[i][j].rgbValue = []uint32{uint32(r), uint32(g), uint32(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)}
+ }
tempInt = (len(chosenTable) - 1) - tempInt
}
@@ -189,7 +201,11 @@ func ConvertToAscii(imgSet [][]AsciiPixel, negative bool, colored bool, complex
char.Colored = color.Sprintf("%v>", chosenTable[tempInt])
char.Simple = chosenTable[tempInt]
- char.RgbValue = imgSet[i][j].rgbValue
+ if colored {
+ char.RgbValue = imgSet[i][j].rgbValue
+ } else {
+ char.RgbValue = imgSet[i][j].grayscaleValue
+ }
tempSlice = append(tempSlice, char)
}
diff --git a/image_manipulation/image_conversions.go b/image_manipulation/image_conversions.go
index 8378860..fd54814 100644
--- a/image_manipulation/image_conversions.go
+++ b/image_manipulation/image_conversions.go
@@ -26,8 +26,9 @@ import (
)
type AsciiPixel struct {
- grayscaleValue uint32
- rgbValue []uint32
+ charDepth uint32
+ grayscaleValue [3]uint32
+ rgbValue [3]uint32
}
// This function shrinks the passed image according to passed dimensions or terminal
@@ -120,20 +121,25 @@ func ConvertToAsciiPixels(img image.Image, dimensions []int, flipX, flipY, full
for x := b.Min.X; x < b.Max.X; x++ {
oldPixel := smallImg.At(x, y)
- pixel := color.GrayModel.Convert(oldPixel)
+ grayPixel := color.GrayModel.Convert(oldPixel)
// We only need Red from Red, Green, Blue (RGB) for grayscaleValue in AsciiPixel since they have the same value for grayscale images
- r1, _, _, _ := pixel.RGBA()
+ r1, g1, b1, _ := grayPixel.RGBA()
+ charDepth := r1
+ r1 = uint32(r1 / 257)
+ g1 = uint32(g1 / 257)
+ b1 = uint32(b1 / 257)
- // Get colored RGB values of original pixel for rgbValue in AsciiPixel
+ // Get co1ored RGB values of original pixel for rgbValue in AsciiPixel
r2, g2, b2, _ := oldPixel.RGBA()
r2 = uint32(r2 / 257)
g2 = uint32(g2 / 257)
b2 = uint32(b2 / 257)
temp = append(temp, AsciiPixel{
- grayscaleValue: r1,
- rgbValue: []uint32{r2, g2, b2},
+ charDepth: charDepth,
+ grayscaleValue: [3]uint32{r1, g1, b1},
+ rgbValue: [3]uint32{r2, g2, b2},
})
}
@@ -166,7 +172,3 @@ func reverse(imgSet [][]AsciiPixel, flipX, flipY bool) [][]AsciiPixel {
return imgSet
}
-
-func calculateDimensions() {
-
-}
diff --git a/snapcraft.yaml b/snapcraft.yaml
index 8238fda..56459ba 100644
--- a/snapcraft.yaml
+++ b/snapcraft.yaml
@@ -1,7 +1,7 @@
name: ascii-image-converter
base: core18
-version: "1.4.0"
-summary: Converts images and gifs into ascii art
+version: "1.4.1"
+summary: Convert images and gifs into ascii art
description: |
This tool converts images and gifs into ascii format and prints them onto the terminal window.
Supported input formats are JPEG/JPG, PNG, WEBP, BMP TIFF/TIF and GIF. Further configuration can be managed by flags.