~dricottone/image2ascii

596b61e18082520ed1a9c092fbec29819dcb2f88 — qeesung 6 years ago 90b8bfe
Add method to support convert image to pixelASCII matrix
3 files changed, 89 insertions(+), 3 deletions(-)

M ascii/ascii.go
M convert/convert.go
M convert/convert_test.go
M ascii/ascii.go => ascii/ascii.go +28 -3
@@ 10,6 10,14 @@ import (
	"reflect"
)

type PixelASCII struct {
	Char byte
	R    uint8
	G    uint8
	B    uint8
	A    uint8
}

// Options convert pixel to raw char
type Options struct {
	Pixels   []byte


@@ 46,14 54,15 @@ func NewPixelConverter() PixelConverter {
// PixelConverter define the convert pixel operation
type PixelConverter interface {
	ConvertPixelToASCII(pixel color.Color, options *Options) string
	ConvertPixelToPixelASCII(pixel color.Color, options *Options) PixelASCII
}

// PixelASCIIConverter responsible for pixel ascii conversion
type PixelASCIIConverter struct {
}

// ConvertPixelToASCII converts a pixel to a ASCII char string
func (converter PixelASCIIConverter) ConvertPixelToASCII(pixel color.Color, options *Options) string {
// ConvertPixelToPixelASCII convert a image pixel to PixelASCII
func (converter PixelASCIIConverter) ConvertPixelToPixelASCII(pixel color.Color, options *Options) PixelASCII {
	convertOptions := NewOptions()
	convertOptions.mergeOptions(options)



@@ 70,6 79,22 @@ func (converter PixelASCIIConverter) ConvertPixelToASCII(pixel color.Color, opti
	// Choose the char
	precision := float64(255 * 3 / (len(convertOptions.Pixels) - 1))
	rawChar := convertOptions.Pixels[converter.roundValue(float64(value)/precision)]
	return PixelASCII{
		Char: rawChar,
		R:    uint8(r),
		G:    uint8(g),
		B:    uint8(b),
		A:    uint8(a),
	}
}

// ConvertPixelToASCII converts a pixel to a ASCII char string
func (converter PixelASCIIConverter) ConvertPixelToASCII(pixel color.Color, options *Options) string {
	convertOptions := NewOptions()
	convertOptions.mergeOptions(options)

	pixelASCII := converter.ConvertPixelToPixelASCII(pixel, options)
	rawChar, r, g, b := pixelASCII.Char, pixelASCII.R, pixelASCII.G, pixelASCII.B
	if convertOptions.Colored {
		return converter.decorateWithColor(r, g, b, rawChar)
	}


@@ 93,7 118,7 @@ func (converter PixelASCIIConverter) intensity(r, g, b, a uint64) uint64 {
}

// decorateWithColor decorate the raw char with the color base on r,g,b value
func (converter PixelASCIIConverter) decorateWithColor(r, g, b uint64, rawChar byte) string {
func (converter PixelASCIIConverter) decorateWithColor(r, g, b uint8, rawChar byte) string {
	coloredChar := rgbterm.FgString(string([]byte{rawChar}), uint8(r), uint8(g), uint8(b))
	return coloredChar
}

M convert/convert.go => convert/convert.go +34 -0
@@ 50,6 50,8 @@ type Converter interface {
	Image2ASCIIString(image image.Image, options *Options) string
	ImageFile2ASCIIMatrix(imageFilename string, option *Options) []string
	ImageFile2ASCIIString(imageFilename string, option *Options) string
	Image2PixelASCIIMatrix(image image.Image, imageConvertOptions *Options) [][]ascii.PixelASCII
	ImageFile2PixelASCIIMatrix(image image.Image, imageConvertOptions *Options) [][]ascii.PixelASCII
}

// ImageConverter implement the Convert interface, and responsible


@@ 59,6 61,38 @@ type ImageConverter struct {
	pixelConverter ascii.PixelConverter
}

// Image2PixelASCIIMatrix convert a image to a pixel ascii matrix
func (converter *ImageConverter) Image2PixelASCIIMatrix(image image.Image, imageConvertOptions *Options) [][]ascii.PixelASCII {
	newImage := converter.resizeHandler.ScaleImage(image, imageConvertOptions)
	sz := newImage.Bounds()
	newWidth := sz.Max.X
	newHeight := sz.Max.Y
	pixelASCIIs := make([][]ascii.PixelASCII, 0, newHeight)
	for i := 0; i < int(newHeight); i++ {
		line := make([]ascii.PixelASCII, 0, newWidth)
		for j := 0; j < int(newWidth); j++ {
			pixel := color.NRGBAModel.Convert(newImage.At(j, i))
			// Convert the pixel to ascii char
			pixelConvertOptions := ascii.NewOptions()
			pixelConvertOptions.Colored = imageConvertOptions.Colored
			pixelConvertOptions.Reversed = imageConvertOptions.Reversed
			pixelASCII := converter.pixelConverter.ConvertPixelToPixelASCII(pixel, &pixelConvertOptions)
			line = append(line, pixelASCII)
		}
		pixelASCIIs = append(pixelASCIIs, line)
	}
	return pixelASCIIs
}

// Image2PixelASCIIMatrix convert a image to a pixel ascii matrix
func (converter *ImageConverter) ImageFile2PixelASCIIMatrix(imageFilename string, imageConvertOptions *Options) [][]ascii.PixelASCII {
	img, err := OpenImageFile(imageFilename)
	if err != nil {
		log.Fatal("open image failed : " + err.Error())
	}
	return converter.Image2PixelASCIIMatrix(img, imageConvertOptions)
}

// Image2ASCIIMatrix converts a image to ASCII matrix
func (converter *ImageConverter) Image2ASCIIMatrix(image image.Image, imageConvertOptions *Options) []string {
	// Resize the convert first

M convert/convert_test.go => convert/convert_test.go +27 -0
@@ 96,6 96,33 @@ func TestImageFile2ASCIIString(t *testing.T) {
	}
}

func TestImageFile2PixelASCIIMatrix(t *testing.T) {
	converter := NewImageConverter()
	imageTests := []struct {
		imageFilename string
		width         int
		height        int
	}{
		{"testdata/3x3_black.png", 3, 3},
		{"testdata/3x3_white.png", 3,3},
		{"testdata/8x3_multi_colors.png", 8, 3},
	}

	for _, tt := range imageTests {
		t.Run(tt.imageFilename, func(t *testing.T) {
			convertOptions := DefaultOptions
			convertOptions.FitScreen = false
			convertOptions.Colored = false

			matrix := converter.ImageFile2PixelASCIIMatrix(tt.imageFilename, &convertOptions)
			if len(matrix) != tt.height|| len(matrix[0]) != tt.width{
				t.Errorf("image %s convert expected to %+v, %+v, but get %+v",
					tt.imageFilename, tt.width, tt.height, matrix)
			}
		})
	}
}

func TestImage2ReversedASCIIString(t *testing.T) {
	converter := NewImageConverter()
	imageTests := []struct {