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.