Added grayscale flag

This commit is contained in:
Zoraiz 2021-07-25 23:27:16 +05:00
parent c06c4ef1c6
commit df31045bae
9 changed files with 82 additions and 44 deletions

View File

@ -188,6 +188,16 @@ ascii-image-converter [image paths/urls] -m " .-=+#@"
<img src="https://raw.githubusercontent.com/TheZoraiz/ascii-image-converter/master/example_gifs/map.gif"> <img src="https://raw.githubusercontent.com/TheZoraiz/ascii-image-converter/master/example_gifs/map.gif">
</p> </p>
#### --grayscale OR -g
Display ascii art in grayscale colors. This is the same as --color flag, except each character will be encoded with a grayscale RGB value.
```
ascii-image-converter [image paths/urls] -g
# Or
ascii-image-converter [image paths/urls] --grayscale
```
#### --negative OR -n #### --negative OR -n
Display ascii art in negative colors. Works with both uncolored and colored text from --color flag. 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.SaveImagePath = "." // Save generated PNG image in same directory
flags.SaveGifPath = "." // If gif was provided, save ascii art gif 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.Negative = true // Ascii art will have negative color-depth
flags.Colored = true // Keep colors from original image flags.Colored = true // Keep colors from original image. This overrides flags.Grayscale
flags.CustomMap = " .-=+#@" // Starting from darkest to brightest shades. This overrites "complex" flag 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.FlipX = true // Flips ascii art horizontally
flags.FlipY = true // Flips ascii art vertically 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 // For an image
asciiArt, err := aic_package.Convert(filePath, flags) asciiArt, err := aic_package.Convert(filePath, flags)

View File

@ -107,7 +107,7 @@ func pathIsGif(gifPath, urlImgName string, pathIsURl bool, urlImgBytes []byte, l
gifFramesSlice[i].asciiCharSet = asciiCharSet gifFramesSlice[i].asciiCharSet = asciiCharSet
gifFramesSlice[i].delay = originalGif.Delay[i] gifFramesSlice[i].delay = originalGif.Delay[i]
ascii := flattenAscii(asciiCharSet, colored) ascii := flattenAscii(asciiCharSet, colored || grayscale)
asciiArtSet[i] = strings.Join(ascii, "\n") asciiArtSet[i] = strings.Join(ascii, "\n")
@ -179,7 +179,7 @@ func pathIsGif(gifPath, urlImgName string, pathIsURl bool, urlImgBytes []byte, l
tempImg, err := createGifFrameToSave( tempImg, err := createGifFrameToSave(
gifFrame.asciiCharSet, gifFrame.asciiCharSet,
img, img,
colored, colored || grayscale,
) )
if err != nil { if err != nil {
fmt.Println(err) fmt.Println(err)

View File

@ -56,7 +56,7 @@ func pathIsImage(imagePath, urlImgName string, pathIsURl bool, urlImgBytes []byt
if saveImagePath != "" { if saveImagePath != "" {
if err := createImageToSave( if err := createImageToSave(
asciiSet, asciiSet,
colored, colored || grayscale,
saveImagePath, saveImagePath,
imagePath, imagePath,
urlImgName, 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") result := strings.Join(ascii, "\n")
return result, nil return result, nil

View File

@ -43,6 +43,7 @@ type Flags struct {
SaveGifPath string SaveGifPath string
Negative bool Negative bool
Colored bool Colored bool
Grayscale bool
CustomMap string CustomMap string
FlipX bool FlipX bool
FlipY bool FlipY bool
@ -55,6 +56,7 @@ var (
saveTxtPath string saveTxtPath string
saveImagePath string saveImagePath string
saveGifPath string saveGifPath string
grayscale bool
negative bool negative bool
colored bool colored bool
customMap string customMap string
@ -74,6 +76,7 @@ func DefaultFlags() Flags {
SaveGifPath: "", SaveGifPath: "",
Negative: false, Negative: false,
Colored: false, Colored: false,
Grayscale: false,
CustomMap: "", CustomMap: "",
FlipX: false, FlipX: false,
FlipY: 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 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 Negative: bool, // Pass true for negative color-depth ascii art
Colored: bool, // Pass true for returning colored ascii string 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. 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 FlipX: bool, // Pass true to return horizontally flipped ascii art
FlipY: bool, // Pass true to return vertically 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 saveGifPath = flags.SaveGifPath
negative = flags.Negative negative = flags.Negative
colored = flags.Colored colored = flags.Colored
grayscale = flags.Grayscale
customMap = flags.CustomMap customMap = flags.CustomMap
flipX = flags.FlipX flipX = flags.FlipX
flipY = flags.FlipY flipY = flags.FlipY

View File

@ -27,10 +27,17 @@ import (
"github.com/golang/freetype/truetype" "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 //go:embed RobotoMono-Bold.ttf
var embeddedFontFile []byte 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. 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 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) 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}) robotoBoldFontFace := truetype.NewFace(tempFont, &truetype.Options{Size: constant * 1.5})
dc.SetFontFace(robotoBoldFontFace) dc.SetFontFace(robotoBoldFontFace)
// Font color of text on picture is white by default // 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]) b := uint8(char.RgbValue[2])
if colored { 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}) dc.SetColor(color.RGBA{r, g, b, 255})
} }

View File

@ -39,6 +39,7 @@ var (
negative bool negative bool
formatsTrue bool formatsTrue bool
colored bool colored bool
grayscale bool
customMap string customMap string
flipX bool flipX bool
flipY bool flipY bool
@ -48,7 +49,7 @@ var (
rootCmd = &cobra.Command{ rootCmd = &cobra.Command{
Use: "ascii-image-converter [image paths/urls]", Use: "ascii-image-converter [image paths/urls]",
Short: "Converts images and gifs into ascii art", 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.", 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
@ -66,6 +67,7 @@ var (
SaveGifPath: saveGifPath, SaveGifPath: saveGifPath,
Negative: negative, Negative: negative,
Colored: colored, Colored: colored,
Grayscale: grayscale,
CustomMap: customMap, CustomMap: customMap,
FlipX: flipX, FlipX: flipX,
FlipY: flipY, FlipY: flipY,
@ -185,12 +187,13 @@ 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\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().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(&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(&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(Can work with the --color 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(&flipX, "flipX", "x", false, "Flip ascii art horizontally\n")
rootCmd.PersistentFlags().BoolVarP(&flipY, "flipY", "y", false, "Flip ascii art vertically\n") rootCmd.PersistentFlags().BoolVarP(&flipY, "flipY", "y", false, "Flip ascii art vertically\n")
rootCmd.PersistentFlags().StringVarP(&saveImagePath, "save-img", "s", "", "Save ascii art as a .png file\nFormat: <image-name>-ascii-art.png\nImage will be saved in passed path\n(pass . for current directory)\n") rootCmd.PersistentFlags().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")

View File

@ -110,12 +110,12 @@ var asciiTableDetailed = map[int]string{
} }
// For each individual element of imgSet in ConvertToASCIISlice() // For each individual element of imgSet in ConvertToASCIISlice()
const MAX_VAL float32 = 65535 const MAX_VAL float64 = 65535
type AsciiChar struct { type AsciiChar struct {
Colored string Colored string
Simple string Simple string
RgbValue []uint32 RgbValue [3]uint32
} }
// Converts the 2D AsciiPixel slice of image data (each instance representing each pixel of original image) // 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. // 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. // 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) height := len(imgSet)
width := len(imgSet[0]) width := len(imgSet[0])
@ -155,18 +155,26 @@ func ConvertToAscii(imgSet [][]AsciiPixel, negative bool, colored bool, complex
var tempSlice []AsciiChar var tempSlice []AsciiChar
for j := 0; j < width; j++ { 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 // 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 { if value == MAX_VAL {
tempFloat = float32(len(chosenTable) - 1) tempFloat = float64(len(chosenTable) - 1)
} }
tempInt := int(tempFloat) tempInt := int(tempFloat)
r := int(imgSet[i][j].rgbValue[0]) var r, g, b int
g := int(imgSet[i][j].rgbValue[1])
b := int(imgSet[i][j].rgbValue[2]) 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 { if negative {
// Select character from opposite side of table as well as turn pixels 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 b = 255 - b
// To preserve negative rgb values for saving png image later down the line, since it uses imgSet // 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 tempInt = (len(chosenTable) - 1) - tempInt
} }
@ -189,7 +201,11 @@ func ConvertToAscii(imgSet [][]AsciiPixel, negative bool, colored bool, complex
char.Colored = color.Sprintf("<fg="+rStr+","+gStr+","+bStr+">%v</>", chosenTable[tempInt]) char.Colored = color.Sprintf("<fg="+rStr+","+gStr+","+bStr+">%v</>", chosenTable[tempInt])
char.Simple = 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) tempSlice = append(tempSlice, char)
} }

View File

@ -26,8 +26,9 @@ import (
) )
type AsciiPixel struct { type AsciiPixel struct {
grayscaleValue uint32 charDepth uint32
rgbValue []uint32 grayscaleValue [3]uint32
rgbValue [3]uint32
} }
// This function shrinks the passed image according to passed dimensions or terminal // 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++ { for x := b.Min.X; x < b.Max.X; x++ {
oldPixel := smallImg.At(x, y) 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 // 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, g2, b2, _ := oldPixel.RGBA()
r2 = uint32(r2 / 257) r2 = uint32(r2 / 257)
g2 = uint32(g2 / 257) g2 = uint32(g2 / 257)
b2 = uint32(b2 / 257) b2 = uint32(b2 / 257)
temp = append(temp, AsciiPixel{ temp = append(temp, AsciiPixel{
grayscaleValue: r1, charDepth: charDepth,
rgbValue: []uint32{r2, g2, b2}, 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 return imgSet
} }
func calculateDimensions() {
}

View File

@ -1,7 +1,7 @@
name: ascii-image-converter name: ascii-image-converter
base: core18 base: core18
version: "1.4.0" version: "1.4.1"
summary: Converts images and gifs into ascii art summary: Convert images and gifs into ascii art
description: | description: |
This tool converts images and gifs into ascii format and prints them onto the terminal window. 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. Supported input formats are JPEG/JPG, PNG, WEBP, BMP TIFF/TIF and GIF. Further configuration can be managed by flags.