Refactor code, added colors flag, negative flag, image formats flag

This commit is contained in:
Zoraiz 2021-05-24 16:17:21 +05:00
parent f91513f00b
commit a7eebb45d0
6 changed files with 193 additions and 100 deletions

View File

@ -3,11 +3,11 @@
ascii-image-converter is a command-line tool that converts images into ascii art and prints them out onto the console. It is cross-platform so both Windows and Linux distributions are supported ascii-image-converter is a command-line tool that converts images into ascii art and prints them out onto the console. It is cross-platform so both Windows and Linux distributions are supported
Image formats currently supported: Image formats currently supported:
* PNG
* JPEG/JPG * JPEG/JPG
* PNG
* WEBP * WEBP
* BMP * BMP
* TIFF * TIFF/TIF
<br> <br>
@ -79,7 +79,7 @@ ascii-image-converter [path to image] --complex
#### --dimensions OR -d #### --dimensions OR -d
Set the width and height of the printed ascii image in character lengths. Set the width and height for ascii art in CHARACTER lengths. (Don't immediately append another flag with -d)
``` ```
ascii-image-converter [path to image] -d <width>,<height> ascii-image-converter [path to image] -d <width>,<height>
# Or # Or
@ -90,18 +90,45 @@ Example:
ascii-image-converter [path to image] -d 100,30 ascii-image-converter [path to image] -d 100,30
``` ```
#### --save OR -S #### --color OR -C
Save the image ascii art in a file ascii-image.txt in the same directory Display ascii art with the colors from original image. Works with the -n flag as well.
``` ```
ascii-image-converter [path to image] --save ascii-image-converter [path to image] -C
# Or # Or
ascii-image-converter [path to image] -S ascii-image-converter [path to image] --color
``` ```
#### --negative OR -n
Display ascii art in negative colors. Works with both uncolored and colored text from -C flag.
```
ascii-image-converter [path to image] -n
# Or
ascii-image-converter [path to image] --negative
```
#### --save OR -s
Save the printed ascii art in a file ascii-image.txt in the directory passed alongside. (Don't immediately append another flag with -s)
Example for current directory:
```
ascii-image-converter [path to image] --save ./
# Or
ascii-image-converter [path to image] -s ./
```
#### --formats OR -f
Display supported image formats.
```
ascii-image-converter [path to image] --formats
# Or
ascii-image-converter [path to image] -f
```
<br> <br>
You can combine commands as well You can combine flags as well. Following command outputs colored and negative ascii art, with complex characters, fixed 100 by 30 character dimensions and saves the output in current directory as well.
``` ```
ascii-image-converter [path to image] -Scd 100,30 ascii-image-converter [path to image] -Ccnd 100,30 -s ./
``` ```
<br> <br>
@ -122,6 +149,8 @@ You can fork the project and implement any changes you want for a pull request.
[github.com/nfnt/resize](https://github.com/nfnt/resize) [github.com/nfnt/resize](https://github.com/nfnt/resize)
[github.com/gookit/color](https://github.com/gookit/color)
## License ## License
[Apache-2.0](https://github.com/TheZoraiz/ascii-image-converter/blob/master/LICENSE) [Apache-2.0](https://github.com/TheZoraiz/ascii-image-converter/blob/master/LICENSE)

View File

@ -40,29 +40,45 @@ import (
) )
var ( var (
// Flags
cfgFile string cfgFile string
compl bool compl bool
dimensions []int dimensions []int
save bool savePath string
negative bool
formatsTrue bool
colored bool
// Root commands
rootCmd = &cobra.Command{ rootCmd = &cobra.Command{
Use: "ascii-image-converter [image path]", Use: "ascii-image-converter [image path]",
Short: "Converts images into ascii format", Short: "Converts images into ascii format",
Long: `ascii-image-converter converts images into ascii format and prints them onto the terminal window. Further configuration can be managed with flags`, Long: `This tool converts images into ascii format and prints them onto the terminal window. Further configuration can be managed with flags`,
Args: cobra.ExactArgs(1),
RunE: func(cmd *cobra.Command, args []string) error { RunE: func(cmd *cobra.Command, args []string) error {
if formatsTrue {
fmt.Println("Supported image formats: JPEG/JPG, PNG, WEBP, BMP, TIFF/TIF")
return nil
}
if len(args) != 1 {
return fmt.Errorf("Requires 1 image path, got %v", len(args))
}
numberOfDimensions := len(dimensions) numberOfDimensions := len(dimensions)
if dimensions != nil && numberOfDimensions != 2 { if dimensions != nil && numberOfDimensions != 2 {
return fmt.Errorf("-d requires two dimensions got %v", numberOfDimensions) return fmt.Errorf("-d requires 2 dimensions, got %v", numberOfDimensions)
} }
imagePath := args[0] imagePath := args[0]
return convertPicture(imagePath, compl, dimensions, save)
return convertImage(imagePath)
}, },
} }
) )
func convertPicture(imagePath string, isComplex bool, dimensions []int, save bool) error { func convertImage(imagePath string) error {
pic, err := os.Open(imagePath) pic, err := os.Open(imagePath)
if err != nil { if err != nil {
return fmt.Errorf("Unable to open file: %w", err) return fmt.Errorf("Unable to open file: %w", err)
@ -74,32 +90,46 @@ func convertPicture(imagePath string, isComplex bool, dimensions []int, save boo
return fmt.Errorf("Unable to decode file: %w", err) return fmt.Errorf("Unable to decode file: %w", err)
} }
imgSet := imgMani.ConvertToTerminalSizedSlices(imData, dimensions) imgSet, err := imgMani.ConvertToAsciiPixels(imData, dimensions)
var asciiSet [][]string if err != nil {
return err
if isComplex {
asciiSet = imgMani.ConvertToAsciiDetailed(imgSet)
} else {
asciiSet = imgMani.ConvertToAsciiSimple(imgSet)
} }
ascii := flattenAscii(asciiSet) var asciiSet [][]imgMani.AsciiChar
asciiSet = imgMani.ConvertToAscii(imgSet, negative, colored, compl)
var ascii []string
ascii = flattenAscii(asciiSet, colored)
for _, line := range ascii { for _, line := range ascii {
fmt.Println(line) fmt.Println(line)
} }
if save { if savePath != "" {
return ioutil.WriteFile("ascii-image.txt", []byte(strings.Join(ascii, "\n")), 0777) // To make sure uncolored ascii art is the one saved
saveAscii := flattenAscii(asciiSet, false)
if savePath == "." {
savePath = "./"
}
return ioutil.WriteFile(savePath+"ascii-image.txt", []byte(strings.Join(saveAscii, "\n")), 0777)
} }
return nil return nil
} }
// flattenAscii flattens a two-dimensional grid of ascii characters into a one dimension // flattenAscii flattens a two-dimensional grid of ascii characters into a one dimension
// of lines of ascii // of lines of ascii
func flattenAscii(asciiSet [][]string) []string { func flattenAscii(asciiSet [][]imgMani.AsciiChar, color bool) []string {
var ascii []string var ascii []string
for _, line := range asciiSet { for _, line := range asciiSet {
ascii = append(ascii, strings.Join(line, "")) var tempAscii []string
for i := 0; i < len(line); i++ {
if color {
tempAscii = append(tempAscii, line[i].Colored)
} else {
tempAscii = append(tempAscii, line[i].Simple)
}
}
ascii = append(ascii, strings.Join(tempAscii, ""))
} }
return ascii return ascii
} }
@ -117,9 +147,12 @@ func init() {
cobra.OnInitialize(initConfig) cobra.OnInitialize(initConfig)
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(&compl, "complex", "c", false, "Prints ascii characters in a larger range, may result in higher quality") rootCmd.PersistentFlags().BoolVarP(&colored, "color", "C", false, "Display ascii art with the colors from original image (Can work with the -n flag)")
rootCmd.PersistentFlags().BoolVarP(&compl, "complex", "c", false, "Display ascii characters in a larger range, may result in higher quality")
rootCmd.PersistentFlags().IntSliceVarP(&dimensions, "dimensions", "d", nil, "Set width and height for ascii art in CHARACTER length e.g. 100,30 (defaults to terminal size)") rootCmd.PersistentFlags().IntSliceVarP(&dimensions, "dimensions", "d", nil, "Set width and height for ascii art in CHARACTER length e.g. 100,30 (defaults to terminal size)")
rootCmd.PersistentFlags().BoolVarP(&save, "save", "S", false, "Save ascii text in current directory in an ascii-image.txt file") rootCmd.PersistentFlags().BoolVarP(&formatsTrue, "formats", "f", false, "Display supported image formats")
rootCmd.PersistentFlags().BoolVarP(&negative, "negative", "n", false, "Display ascii art in negative colors (Can work with the -C flag)")
rootCmd.PersistentFlags().StringVarP(&savePath, "save", "s", "", "Save ascii art in an ascii-image.txt file in a given path (pass ./ for current directory)")
} }
// initConfig reads in config file and ENV variables if set. // initConfig reads in config file and ENV variables if set.

1
go.mod
View File

@ -4,6 +4,7 @@ go 1.16
require ( require (
github.com/fsnotify/fsnotify v1.4.9 // indirect github.com/fsnotify/fsnotify v1.4.9 // indirect
github.com/gookit/color v1.4.2 // indirect
github.com/magiconair/properties v1.8.5 // indirect github.com/magiconair/properties v1.8.5 // indirect
github.com/mitchellh/go-homedir v1.1.0 github.com/mitchellh/go-homedir v1.1.0
github.com/mitchellh/mapstructure v1.4.1 // indirect github.com/mitchellh/mapstructure v1.4.1 // indirect

7
go.sum
View File

@ -66,6 +66,8 @@ github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OI
github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI=
github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg= github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg=
github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk= github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk=
github.com/gookit/color v1.4.2 h1:tXy44JFSFkKnELV6WaMo/lLfu/meqITX3iAV52do7lk=
github.com/gookit/color v1.4.2/go.mod h1:fqRyamkC1W8uxl+lxCQxOT09l/vYfZ+QeiX3rKQHCoQ=
github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1 h1:EGx4pi6eqNxGaHF6qqu48+N2wcFQ5qg5FXgOdqsJ5d8= github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1 h1:EGx4pi6eqNxGaHF6qqu48+N2wcFQ5qg5FXgOdqsJ5d8=
github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
@ -192,10 +194,13 @@ github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXf
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/stretchr/testify v1.4.0 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJyk= github.com/stretchr/testify v1.4.0 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJyk=
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/subosito/gotenv v1.2.0 h1:Slr1R9HxAlEKefgq5jn9U+DnETlIUa6HfgEzj0g5d7s= github.com/subosito/gotenv v1.2.0 h1:Slr1R9HxAlEKefgq5jn9U+DnETlIUa6HfgEzj0g5d7s=
github.com/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69rRypqCw= github.com/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69rRypqCw=
github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U= github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U=
github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU= github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU=
github.com/xo/terminfo v0.0.0-20210125001918-ca9a967f8778 h1:QldyIu/L63oPpyvQmHgvgickp1Yw510KJOqX7H24mg8=
github.com/xo/terminfo v0.0.0-20210125001918-ca9a967f8778/go.mod h1:2MuV+tbUrU1zIOPMxZ5EncGwgmMJsa+9ucAQZXxsObs=
go.etcd.io/bbolt v1.3.2/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU= go.etcd.io/bbolt v1.3.2/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU=
go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU= go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU=
go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8= go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8=
@ -264,6 +269,7 @@ golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7w
golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210511113859-b0526f3d8744 h1:yhBbb4IRs2HS9PPlAg6DMC6mUOKexJBNsLf4Z+6En1Q= golang.org/x/sys v0.0.0-20210511113859-b0526f3d8744 h1:yhBbb4IRs2HS9PPlAg6DMC6mUOKexJBNsLf4Z+6En1Q=
golang.org/x/sys v0.0.0-20210511113859-b0526f3d8744/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210511113859-b0526f3d8744/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
@ -328,6 +334,7 @@ gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=

View File

@ -16,6 +16,12 @@ limitations under the License.
package image_conversions package image_conversions
import (
"strconv"
"github.com/gookit/color"
)
// Reference taken from http://paulbourke.net/dataformats/asciiart/ // Reference taken from http://paulbourke.net/dataformats/asciiart/
var asciiTableSimple = map[int]string{ var asciiTableSimple = map[int]string{
0: " ", 0: " ",
@ -106,68 +112,71 @@ 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 float32 = 65535
// Converts the 2D uint32 slice of image data (each value representing each pixel of image) type AsciiChar struct {
// to a 2D string slice with each string having an ASCII character corresponding to Colored string
// the original uint32 value. Simple string
}
// Converts the 2D AsciiPixel slice of image data (each instance representing each pixel of original image)
// to a 2D AsciiChar slice with each colored and simple string having an ASCII character corresponding to
// the original grayscale and RGB values in AsciiPixel.
// //
// Values are compared to 69 ASCII characters // If complex parameter is true, values are compared to 69 levels of color density in ASCII characters.
func ConvertToAsciiDetailed(imgSet [][]uint32) [][]string { // Otherwise, values are compared to 10 levels of color density in ASCII characters.
func ConvertToAscii(imgSet [][]AsciiPixel, negative bool, colored bool, complex bool) [][]AsciiChar {
height := len(imgSet) height := len(imgSet)
width := len(imgSet[0]) width := len(imgSet[0])
result := make([][]string, height) var chosenTable map[int]string
if complex {
chosenTable = asciiTableDetailed
} else {
chosenTable = asciiTableSimple
}
result := make([][]AsciiChar, height)
for i := range result { for i := range result {
result[i] = make([]string, width) result[i] = make([]AsciiChar, width)
} }
for i := 0; i < height; i++ { for i := 0; i < height; i++ {
var tempSlice []string
var tempSlice []AsciiChar
for j := 0; j < width; j++ { for j := 0; j < width; j++ {
value := float32(imgSet[i][j].grayscaleValue)
value := float32(imgSet[i][j]) // Gets appropriate string index from asciiTableSimple by percentage comparisons with its length
tempFloat := (value / MAX_VAL) * float32(len(chosenTable))
tempFloat := (value / MAX_VAL) * float32(len(asciiTableDetailed))
if value == MAX_VAL { if value == MAX_VAL {
tempFloat = float32(len(asciiTableDetailed) - 1) tempFloat = float32(len(chosenTable) - 1)
} }
tempInt := int(tempFloat) tempInt := int(tempFloat)
tempSlice = append(tempSlice, asciiTableDetailed[tempInt]) r := int(imgSet[i][j].rgbValue[0])
} g := int(imgSet[i][j].rgbValue[1])
result[i] = tempSlice b := int(imgSet[i][j].rgbValue[2])
}
if negative {
return result // Select character from opposite side of table as well as turn pixels negative
} r = 255 - r
g = 255 - g
// Converts the 2D uint32 slice of image data (each value representing each pixel of image) b = 255 - b
// to a 2D string slice with each string having an ASCII character corresponding to
// the original uint32 value. tempInt = (len(chosenTable) - 1) - tempInt
// }
// Values are compared to 10 ASCII characters
func ConvertToAsciiSimple(imgSet [][]uint32) [][]string { rStr := strconv.Itoa(r)
gStr := strconv.Itoa(g)
height := len(imgSet) bStr := strconv.Itoa(b)
width := len(imgSet[0])
var char AsciiChar
result := make([][]string, height)
for i := range result { char.Colored = color.Sprintf("<fg="+rStr+","+gStr+","+bStr+">%v</>", chosenTable[tempInt])
result[i] = make([]string, width) char.Simple = chosenTable[tempInt]
}
tempSlice = append(tempSlice, char)
for i := 0; i < height; i++ {
var tempSlice []string
for j := 0; j < width; j++ {
value := float32(imgSet[i][j])
tempFloat := (value / MAX_VAL) * float32(len(asciiTableSimple))
if value == MAX_VAL {
tempFloat = float32(len(asciiTableSimple) - 1)
}
tempInt := int(tempFloat)
tempSlice = append(tempSlice, asciiTableSimple[tempInt])
} }
result[i] = tempSlice result[i] = tempSlice
} }

View File

@ -20,19 +20,23 @@ import (
"fmt" "fmt"
"image" "image"
"image/color" "image/color"
"os"
"github.com/nathan-fiscaletti/consolesize-go" "github.com/nathan-fiscaletti/consolesize-go"
"github.com/nfnt/resize" "github.com/nfnt/resize"
) )
type AsciiPixel struct {
grayscaleValue uint32
rgbValue []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
// size if none are passed. It turns each pixel into grayscale to simplify getting numeric // size if none are passed. Stores each pixel's grayscale and RGB values in an AsciiPixel
// data for ASCII character comparison. // instance to simplify getting numeric data for ASCII character comparison.
// //
// The returned 2D uint32 slice contains each corresponding pixel's value ranging from // The returned 2D AsciiPixel slice contains each corresponding pixel's values. Grayscale value
// 0 to 65535. // ranges from 0 to 65535, while RGB values are separate.
func ConvertToTerminalSizedSlices(img image.Image, dimensions []int) [][]uint32 { func ConvertToAsciiPixels(img image.Image, dimensions []int) ([][]AsciiPixel, error) {
var terminalWidth, terminalHeight int var terminalWidth, terminalHeight int
@ -46,12 +50,12 @@ func ConvertToTerminalSizedSlices(img image.Image, dimensions []int) [][]uint32
// Sometimes full length outputs print empty lines between ascii art // Sometimes full length outputs print empty lines between ascii art
terminalWidth -= 1 terminalWidth -= 1
// Passing 0 in place of height keeps the original image's aspect ratio
smallImg = resize.Resize(uint(terminalWidth), 0, img, resize.Lanczos3) smallImg = resize.Resize(uint(terminalWidth), 0, img, resize.Lanczos3)
terminalHeight = smallImg.Bounds().Max.Y terminalHeight = smallImg.Bounds().Max.Y - smallImg.Bounds().Min.Y
// To fix height ratio // To fix height ratio in eventual ascii art
terminalHeight -= terminalHeight / 2 terminalHeight = int(0.5 * float32(terminalHeight))
terminalHeight -= terminalHeight / 5
smallImg = resize.Resize(uint(terminalWidth), uint(terminalHeight), img, resize.Lanczos3) smallImg = resize.Resize(uint(terminalWidth), uint(terminalHeight), img, resize.Lanczos3)
@ -61,39 +65,49 @@ func ConvertToTerminalSizedSlices(img image.Image, dimensions []int) [][]uint32
smallImg = resize.Resize(uint(terminalWidth), uint(terminalHeight), img, resize.Lanczos3) smallImg = resize.Resize(uint(terminalWidth), uint(terminalHeight), img, resize.Lanczos3)
} }
// If there are passed dimensions, check whether the width exceeds terminal width
if len(dimensions) > 0 { if len(dimensions) > 0 {
defaultTermWidth, _ := consolesize.GetConsoleSize() defaultTermWidth, _ := consolesize.GetConsoleSize()
defaultTermWidth -= 1 defaultTermWidth -= 1
if dimensions[0] > defaultTermWidth { if dimensions[0] > defaultTermWidth {
fmt.Println("Error: Set width is larger than terminal width") return nil, fmt.Errorf("Set width is larger than terminal width")
os.Exit(1)
} }
} }
// initialize imgSet 2D slice // Initialize imgSet 2D slice
imgSet := make([][]uint32, terminalHeight) imgSet := make([][]AsciiPixel, terminalHeight)
for i := range imgSet { for i := range imgSet {
imgSet[i] = make([]uint32, terminalWidth) imgSet[i] = make([]AsciiPixel, terminalWidth)
} }
// smallImg := resize.Resize(uint(terminalWidth), 0, img, resize.Lanczos3)
b := smallImg.Bounds() b := smallImg.Bounds()
// These nested loops iterate through each pixel of resized image and get an AsciiPixel instance
for y := b.Min.Y; y < b.Max.Y; y++ { for y := b.Min.Y; y < b.Max.Y; y++ {
var temp []uint32 var temp []AsciiPixel
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) pixel := color.GrayModel.Convert(oldPixel)
// We only need Red from Red, Green, Blue 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
r, _, _, _ := pixel.RGBA() r1, _, _, _ := pixel.RGBA()
temp = append(temp, r)
// Get colored 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},
})
} }
imgSet[y] = temp imgSet[y] = temp
} }
return imgSet return imgSet, nil
} }