~dricottone/image2ascii

6ee682ae1c41bfd64968d71334d72b52c8d29ca3 — qeesung 6 years ago 3481ed7
add parallel convert
2 files changed, 119 insertions(+), 5 deletions(-)

M convert/convert.go
M convert/convert_test.go
M convert/convert.go => convert/convert.go +100 -5
@@ 6,6 6,7 @@ import (
	"github.com/qeesung/image2ascii/ascii"
	"image"
	"image/color"
	"runtime"
	// Support decode jpeg image
	_ "image/jpeg"
	// Support deocde the png image


@@ 14,6 15,13 @@ import (
	"os"
)

type Pixel struct {
	i              int
	j              int
	color          color.Color
	convertedChars string
}

// Options to convert the image to ASCII
type Options struct {
	Ratio          float64


@@ 37,11 45,11 @@ func Image2ASCIIMatrix(image image.Image, imageConvertOptions *Options) []string
	// Resize the convert first
	newImage := ScaleImage(image, imageConvertOptions)
	sz := newImage.Bounds()
	newWidth := sz.Max.Y
	newHeight := sz.Max.X
	rawCharValues := make([]string, 0, int(newWidth*newHeight+newWidth))
	for i := 0; i < int(newWidth); i++ {
		for j := 0; j < int(newHeight); j++ {
	newWidth := sz.Max.X
	newHeight := sz.Max.Y
	rawCharValues := make([]string, 0, int(newWidth*newHeight+newHeight))
	for i := 0; i < int(newHeight); i++ {
		for j := 0; j < int(newWidth); j++ {
			pixel := color.NRGBAModel.Convert(newImage.At(j, i))
			// Convert the pixel to ascii char
			pixelConvertOptions := ascii.NewOptions()


@@ 54,6 62,65 @@ func Image2ASCIIMatrix(image image.Image, imageConvertOptions *Options) []string
	return rawCharValues
}

func Image2ASCIIMatrixParallel(image image.Image, imageConvertOptions *Options) []string {
	// Resize the convert first
	newImage := ScaleImage(image, imageConvertOptions)
	sz := newImage.Bounds()
	newWidth := sz.Max.X
	newHeight := sz.Max.Y
	cap := int(newWidth*newHeight + newHeight)
	rawCharValues := make([]string, cap, cap)

	// set the new line string
	for i := 0; i < newHeight; i++ {
		newLineIndex := (i+1)*(newWidth+1) - 1
		rawCharValues[newLineIndex] = "\n"
	}

	pixelChannel := make(chan Pixel, newWidth)
	charChannel := make(chan Pixel, newWidth)
	defer close(pixelChannel)
	defer close(charChannel)

	// schedule the convert task
	go func() {
		for i := 0; i < int(newHeight); i++ {
			for j := 0; j < int(newWidth); j++ {
				pixelColor := color.NRGBAModel.Convert(newImage.At(j, i))
				pixelChannel <- Pixel{
					i:              i,
					j:              j,
					color:          pixelColor,
					convertedChars: "",
				}
			}
		}
	}()

	// convert the pixel
	pixelConvertOptions := ascii.NewOptions()
	pixelConvertOptions.Colored = imageConvertOptions.Colored
	coreCount := runtime.NumCPU()
	for i := 0; i < coreCount; i++ {
		go func() {
			for pixel := range pixelChannel {
				rawChar := ascii.ConvertPixelToASCII(pixel.color, &pixelConvertOptions)
				pixel.convertedChars = rawChar
				charChannel <- pixel
			}
		}()
	}

	// get the results
	for k := 0; k < int(newWidth)*int(newHeight); k++ {
		convertedPixel := <-charChannel
		i, j := convertedPixel.i, convertedPixel.j
		index := i*int(newWidth+1) + j
		rawCharValues[index] = convertedPixel.convertedChars
	}
	return rawCharValues
}

// Image2ASCIIString converts a image to ascii matrix, and the join the matrix to a string
func Image2ASCIIString(image image.Image, options *Options) string {
	convertedPixelASCII := Image2ASCIIMatrix(image, options)


@@ 65,6 132,16 @@ func Image2ASCIIString(image image.Image, options *Options) string {
	return buffer.String()
}

func Image2ASCIIStringParallel(image image.Image, options *Options) string {
	convertedPixelASCII := Image2ASCIIMatrixParallel(image, options)
	var buffer bytes.Buffer

	for i := 0; i < len(convertedPixelASCII); i++ {
		buffer.WriteString(convertedPixelASCII[i])
	}
	return buffer.String()
}

// ImageFile2ASCIIMatrix converts a image file to ascii matrix
func ImageFile2ASCIIMatrix(imageFilename string, option *Options) []string {
	img, err := OpenImageFile(imageFilename)


@@ 74,6 151,15 @@ func ImageFile2ASCIIMatrix(imageFilename string, option *Options) []string {
	return Image2ASCIIMatrix(img, option)
}

// ImageFile2ASCIIMatrix converts a image file to ascii matrix
func ImageFile2ASCIIMatrixParallel(imageFilename string, option *Options) []string {
	img, err := OpenImageFile(imageFilename)
	if err != nil {
		log.Fatal("open image failed : " + err.Error())
	}
	return Image2ASCIIMatrixParallel(img, option)
}

// ImageFile2ASCIIString converts a image file to ascii string
func ImageFile2ASCIIString(imageFilename string, option *Options) string {
	img, err := OpenImageFile(imageFilename)


@@ 83,6 169,15 @@ func ImageFile2ASCIIString(imageFilename string, option *Options) string {
	return Image2ASCIIString(img, option)
}

// ImageFile2ASCIIString converts a image file to ascii string
func ImageFile2ASCIIStringParallel(imageFilename string, option *Options) string {
	img, err := OpenImageFile(imageFilename)
	if err != nil {
		log.Fatal("open image failed : " + err.Error())
	}
	return Image2ASCIIStringParallel(img, option)
}

// OpenImageFile open a image and return a image object
func OpenImageFile(imageFilename string) (image.Image, error) {
	f, err := os.Open(imageFilename)

M convert/convert_test.go => convert/convert_test.go +19 -0
@@ 64,6 64,12 @@ func TestImage2ASCIIMatrix(t *testing.T) {
				t.Errorf("image %s convert expected to %+v, but get %+v",
					tt.imageFilename, tt.asciiMatrix, matrix)
			}

			matrixParallel := ImageFile2ASCIIMatrixParallel(tt.imageFilename, &convertOptions)
			if !reflect.DeepEqual(matrixParallel, tt.asciiMatrix) {
				t.Errorf("image %s convert expected to %+v, but get %+v",
					tt.imageFilename, tt.asciiMatrix, matrixParallel)
			}
		})
	}
}


@@ 107,6 113,19 @@ func BenchmarkBigImage2ASCIIMatrix(b *testing.B) {
	}
}

// BenchmarkBigImage2ASCIIMatrix benchmark convert big image to ascii
func BenchmarkBigImage2ASCIIMatrixParallel(b *testing.B) {
	convertOptions := DefaultOptions
	convertOptions.FitScreen = false
	convertOptions.Colored = false
	convertOptions.ExpectedWidth = 200
	convertOptions.ExpectedHeight = 200

	for i:=0; i< b.N; i++ {
		_ = ImageFile2ASCIIMatrixParallel("testdata/cat_2000x1500.jpg", &convertOptions)
	}
}

// BenchmarkSmallImage2ASCIIMatrix benchmark convert small image to ascii
func BenchmarkSmallImage2ASCIIMatrix(b *testing.B) {
	convertOptions := DefaultOptions