From df5eafe9a3eba079dac8fb37652249ddae220eb7 Mon Sep 17 00:00:00 2001 From: Zoraiz Date: Sat, 11 Sep 2021 02:16:17 +0500 Subject: [PATCH] Added 8-bit color support (#9, #11) --- .goreleaser.yml | 1 - README.md | 4 +- aic_package/convert_gif.go | 15 ++++-- aic_package/convert_image.go | 7 ++- cmd/root.go | 4 +- image_manipulation/ascii_conversions.go | 66 +++++++++++++------------ image_manipulation/util.go | 23 +++++++++ snapcraft.yaml | 2 +- 8 files changed, 77 insertions(+), 45 deletions(-) diff --git a/.goreleaser.yml b/.goreleaser.yml index 1a204fe..efe973b 100644 --- a/.goreleaser.yml +++ b/.goreleaser.yml @@ -29,7 +29,6 @@ builds: # - mipsle # - mips64 # - mips64le - archives: - diff --git a/README.md b/README.md index 51bc180..b2d1a14 100644 --- a/README.md +++ b/README.md @@ -162,9 +162,9 @@ ascii-image-converter myImage.jpeg #### --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 diff --git a/aic_package/convert_gif.go b/aic_package/convert_gif.go index eb21f3b..9f16db0 100644 --- a/aic_package/convert_gif.go +++ b/aic_package/convert_gif.go @@ -91,7 +91,7 @@ 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 // positioned inside the original gif. This behavior isn't supported by this app 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") + fmt.Printf("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\n") os.Exit(0) } @@ -99,16 +99,21 @@ func pathIsGif(gifPath, urlImgName string, pathIsURl bool, urlImgBytes []byte, l imgSet, err = imgManip.ConvertToAsciiPixels(frameImage, dimensions, width, height, flipX, flipY, full, braille, dither, noTermSizeComparison) if err != nil { - fmt.Println("Error:", err) + fmt.Printf("Error: %v\n", err) os.Exit(0) } var asciiCharSet [][]imgManip.AsciiChar if braille { - asciiCharSet = imgManip.ConvertToBrailleChars(imgSet, negative, colored, colorBg, fontColor, threshold) + asciiCharSet, err = imgManip.ConvertToBrailleChars(imgSet, negative, colored, grayscale, colorBg, fontColor, threshold) } 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].delay = originalGif.Delay[i] @@ -187,7 +192,7 @@ func pathIsGif(gifPath, urlImgName string, pathIsURl bool, urlImgBytes []byte, l colored || grayscale, ) if err != nil { - fmt.Println("Error:", err) + fmt.Printf("Error: %v\n", err) os.Exit(0) } diff --git a/aic_package/convert_image.go b/aic_package/convert_image.go index f6015b3..398f825 100644 --- a/aic_package/convert_image.go +++ b/aic_package/convert_image.go @@ -51,9 +51,12 @@ func pathIsImage(imagePath, urlImgName string, pathIsURl bool, urlImgBytes []byt var asciiSet [][]imgManip.AsciiChar if braille { - asciiSet = imgManip.ConvertToBrailleChars(imgSet, negative, colored, colorBg, fontColor, threshold) + asciiSet, err = imgManip.ConvertToBrailleChars(imgSet, negative, colored, grayscale, colorBg, fontColor, threshold) } 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 diff --git a/cmd/root.go b/cmd/root.go index 38eb74d..63517b7 100644 --- a/cmd/root.go +++ b/cmd/root.go @@ -57,7 +57,7 @@ var ( rootCmd = &cobra.Command{ Use: "ascii-image-converter [image paths/urls]", Short: "Converts images and gifs into ascii art", - Version: "1.9.2", + Version: "1.10.0", 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 @@ -128,7 +128,7 @@ 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\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().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") diff --git a/image_manipulation/ascii_conversions.go b/image_manipulation/ascii_conversions.go index 4643d4a..786f6aa 100644 --- a/image_manipulation/ascii_conversions.go +++ b/image_manipulation/ascii_conversions.go @@ -16,12 +16,6 @@ limitations under the License. package image_conversions -import ( - "strconv" - - "github.com/gookit/color" -) - var ( // Reference taken from http://paulbourke.net/dataformats/asciiart/ 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. 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) width := len(imgSet[0]) @@ -128,29 +122,33 @@ func ConvertToAsciiChars(imgSet [][]AsciiPixel, negative, colored, complex, colo tempInt = (len(chosenTable) - 1) - tempInt } - rStr := strconv.Itoa(r) - gStr := strconv.Itoa(g) - bStr := strconv.Itoa(b) - var char AsciiChar char.Simple = chosenTable[tempInt] + + var err error if colorBg { - char.OriginalColor = color.Sprintf("%v", chosenTable[tempInt]) + char.OriginalColor, err = getColoredCharForTerm(uint8(r), uint8(g), uint8(b), chosenTable[tempInt], true) } else { - char.OriginalColor = color.Sprintf("%v", chosenTable[tempInt]) + char.OriginalColor, err = getColoredCharForTerm(uint8(r), uint8(g), uint8(b), chosenTable[tempInt], false) + } + if (colored || grayscale) && err != nil { + return nil, err } // If font color is not set, use a simple string. Otherwise, use True color if fontColor != [3]int{255, 255, 255} { - fcR := strconv.Itoa(fontColor[0]) - fcG := strconv.Itoa(fontColor[1]) - fcB := strconv.Itoa(fontColor[2]) + fcR := fontColor[0] + fcG := fontColor[1] + fcB := fontColor[2] if colorBg { - char.SetColor = color.Sprintf("%v", chosenTable[tempInt]) + char.SetColor, err = getColoredCharForTerm(uint8(fcR), uint8(fcG), uint8(fcB), chosenTable[tempInt], true) } else { - char.SetColor = color.Sprintf("%v", chosenTable[tempInt]) + char.SetColor, err = getColoredCharForTerm(uint8(fcR), uint8(fcG), uint8(fcB), chosenTable[tempInt], false) + } + if err != nil { + return nil, err } } @@ -165,7 +163,7 @@ func ConvertToAsciiChars(imgSet [][]AsciiPixel, negative, colored, complex, colo result = append(result, tempSlice) } - return result + return result, nil } /* @@ -174,7 +172,7 @@ to a 2D image_conversions.AsciiChar slice 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) @@ -216,29 +214,33 @@ func ConvertToBrailleChars(imgSet [][]AsciiPixel, negative, colored, colorBg boo } } - rStr := strconv.Itoa(r) - gStr := strconv.Itoa(g) - bStr := strconv.Itoa(b) - var char AsciiChar char.Simple = brailleChar + + var err error if colorBg { - char.OriginalColor = color.Sprintf("%v", brailleChar) + char.OriginalColor, err = getColoredCharForTerm(uint8(r), uint8(g), uint8(b), brailleChar, true) } else { - char.OriginalColor = color.Sprintf("%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 fontColor != [3]int{255, 255, 255} { - fcR := strconv.Itoa(fontColor[0]) - fcG := strconv.Itoa(fontColor[1]) - fcB := strconv.Itoa(fontColor[2]) + fcR := fontColor[0] + fcG := fontColor[1] + fcB := fontColor[2] if colorBg { - char.SetColor = color.Sprintf("%v", brailleChar) + char.SetColor, err = getColoredCharForTerm(uint8(fcR), uint8(fcG), uint8(fcB), brailleChar, true) } else { - char.SetColor = color.Sprintf("%v", brailleChar) + char.SetColor, err = getColoredCharForTerm(uint8(fcR), uint8(fcG), uint8(fcB), brailleChar, false) + } + if err != nil { + return nil, err } } @@ -254,7 +256,7 @@ func ConvertToBrailleChars(imgSet [][]AsciiPixel, negative, colored, colorBg boo result = append(result, tempSlice) } - return result + return result, nil } // Iterate through the BrailleStruct table to see which dots need to be highlighted diff --git a/image_manipulation/util.go b/image_manipulation/util.go index dc29dae..dd22d51 100644 --- a/image_manipulation/util.go +++ b/image_manipulation/util.go @@ -23,6 +23,7 @@ import ( "github.com/TheZoraiz/ascii-image-converter/aic_package/winsize" "github.com/disintegration/imaging" + gookitColor "github.com/gookit/color" "github.com/makeworld-the-better-one/dither/v2" ) @@ -200,3 +201,25 @@ func reverse(imgSet [][]AsciiPixel, flipX, flipY bool) [][]AsciiPixel { return imgSet } + +var termColorLevel string = gookitColor.TermColorLevel().String() + +// This functions calculates terminal color level between rgb colors, 256-colors, 16-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 +} diff --git a/snapcraft.yaml b/snapcraft.yaml index b5ebc44..98f9acb 100644 --- a/snapcraft.yaml +++ b/snapcraft.yaml @@ -1,6 +1,6 @@ name: ascii-image-converter base: core18 -version: "1.9.2" +version: "1.10.0" summary: Convert images and gifs into ascii art description: | ascii-image-converter is a command-line tool that converts images into ascii art and prints