From 6ee682ae1c41bfd64968d71334d72b52c8d29ca3 Mon Sep 17 00:00:00 2001 From: qeesung <1245712564@qq.com> Date: Sat, 27 Oct 2018 22:05:34 +0800 Subject: [PATCH] add parallel convert --- convert/convert.go | 105 ++++++++++++++++++++++++++++++++++++++-- convert/convert_test.go | 19 ++++++++ 2 files changed, 119 insertions(+), 5 deletions(-) diff --git a/convert/convert.go b/convert/convert.go index 3a4e2ce..302b8d1 100644 --- a/convert/convert.go +++ b/convert/convert.go @@ -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) diff --git a/convert/convert_test.go b/convert/convert_test.go index 6710b66..247db40 100644 --- a/convert/convert_test.go +++ b/convert/convert_test.go @@ -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 -- 2.45.2