Skip to content

Commit

Permalink
Added grayscale flag
Browse files Browse the repository at this point in the history
  • Loading branch information
TheZoraiz committed Jul 25, 2021
1 parent c06c4ef commit df31045
Show file tree
Hide file tree
Showing 9 changed files with 82 additions and 44 deletions.
17 changes: 14 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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">
</p>

#### --grayscale OR -g

Display ascii art in grayscale colors. This is the same as --color flag, except each character will be encoded with a grayscale RGB value.

```
ascii-image-converter [image paths/urls] -g
# Or
ascii-image-converter [image paths/urls] --grayscale
```

#### --negative OR -n

Display ascii art in negative colors. Works with both uncolored and colored text from --color flag.
Expand Down Expand Up @@ -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)
Expand Down
4 changes: 2 additions & 2 deletions aic_package/convert_gif.go
Original file line number Diff line number Diff line change
Expand Up @@ -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")

Expand Down Expand Up @@ -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)
Expand Down
4 changes: 2 additions & 2 deletions aic_package/convert_image.go
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -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
Expand Down
5 changes: 5 additions & 0 deletions aic_package/convert_root.go
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ type Flags struct {
SaveGifPath string
Negative bool
Colored bool
Grayscale bool
CustomMap string
FlipX bool
FlipY bool
Expand All @@ -55,6 +56,7 @@ var (
saveTxtPath string
saveImagePath string
saveGifPath string
grayscale bool
negative bool
colored bool
customMap string
Expand All @@ -74,6 +76,7 @@ func DefaultFlags() Flags {
SaveGifPath: "",
Negative: false,
Colored: false,
Grayscale: false,
CustomMap: "",
FlipX: false,
FlipY: false,
Expand All @@ -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
Expand All @@ -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
Expand Down
17 changes: 9 additions & 8 deletions aic_package/create_ascii_image.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand All @@ -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})
}

Expand Down
13 changes: 8 additions & 5 deletions cmd/root.go
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ var (
negative bool
formatsTrue bool
colored bool
grayscale bool
customMap string
flipX bool
flipY bool
Expand All @@ -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
Expand All @@ -66,6 +67,7 @@ var (
SaveGifPath: saveGifPath,
Negative: negative,
Colored: colored,
Grayscale: grayscale,
CustomMap: customMap,
FlipX: flipX,
FlipY: flipY,
Expand Down Expand Up @@ -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: <image-name>-ascii-art.png\nImage will be saved in passed path\n(pass . for current directory)\n")
Expand Down
38 changes: 27 additions & 11 deletions image_manipulation/ascii_conversions.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand All @@ -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])
Expand Down Expand Up @@ -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
Expand All @@ -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
}
Expand All @@ -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.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)
}
Expand Down
24 changes: 13 additions & 11 deletions image_manipulation/image_conversions.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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},
})

}
Expand Down Expand Up @@ -166,7 +172,3 @@ func reverse(imgSet [][]AsciiPixel, flipX, flipY bool) [][]AsciiPixel {

return imgSet
}

func calculateDimensions() {

}
4 changes: 2 additions & 2 deletions snapcraft.yaml
Original file line number Diff line number Diff line change
@@ -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.
Expand Down

0 comments on commit df31045

Please sign in to comment.