M ascii/ascii.go => ascii/ascii.go +20 -9
@@ 10,35 10,46 @@ import (
"reflect"
)
+func NewPixelConverter() PixelConverter {
+ return PixelASCIIConverter{}
+}
+
+type PixelConverter interface {
+ ConvertPixelToASCII(pixel color.Color, options *Options) string
+}
+
+type PixelASCIIConverter struct {
+}
+
// ConvertPixelToASCII converts a pixel to a ASCII char string
-func ConvertPixelToASCII(pixel color.Color, options *Options) string {
+func (converter PixelASCIIConverter) ConvertPixelToASCII(pixel color.Color, options *Options) string {
convertOptions := NewOptions()
convertOptions.mergeOptions(options)
if convertOptions.Reversed {
- convertOptions.Pixels = reverse(convertOptions.Pixels)
+ convertOptions.Pixels = converter.reverse(convertOptions.Pixels)
}
r := reflect.ValueOf(pixel).FieldByName("R").Uint()
g := reflect.ValueOf(pixel).FieldByName("G").Uint()
b := reflect.ValueOf(pixel).FieldByName("B").Uint()
a := reflect.ValueOf(pixel).FieldByName("A").Uint()
- value := intensity(r, g, b, a)
+ value := converter.intensity(r, g, b, a)
// Choose the char
precision := float64(255 * 3 / (len(convertOptions.Pixels) - 1))
- rawChar := convertOptions.Pixels[roundValue(float64(value)/precision)]
+ rawChar := convertOptions.Pixels[converter.roundValue(float64(value)/precision)]
if convertOptions.Colored {
- return decorateWithColor(r, g, b, rawChar)
+ return converter.decorateWithColor(r, g, b, rawChar)
}
return string([]byte{rawChar})
}
-func roundValue(value float64) int {
+func (converter PixelASCIIConverter) roundValue(value float64) int {
return int(math.Floor(value + 0.5))
}
-func reverse(numbers []byte) []byte {
+func (converter PixelASCIIConverter) reverse(numbers []byte) []byte {
for i := 0; i < len(numbers)/2; i++ {
j := len(numbers) - i - 1
numbers[i], numbers[j] = numbers[j], numbers[i]
@@ 46,12 57,12 @@ func reverse(numbers []byte) []byte {
return numbers
}
-func intensity(r, g, b, a uint64) uint64 {
+func (converter PixelASCIIConverter) intensity(r, g, b, a uint64) uint64 {
return (r + g + b) * a / 255
}
// decorateWithColor decorate the raw char with the color base on r,g,b value
-func decorateWithColor(r, g, b uint64, rawChar byte) string {
+func (converter PixelASCIIConverter) decorateWithColor(r, g, b uint64, rawChar byte) string {
coloredChar := rgbterm.FgString(string([]byte{rawChar}), uint8(r), uint8(g), uint8(b))
return coloredChar
}
M ascii/ascii_test.go => ascii/ascii_test.go +14 -9
@@ 30,6 30,7 @@ func TestMergeOptions(t *testing.T) {
// TestConvertPixelToASCIIWhiteColor convert a white image pixel to ascii string
func TestConvertPixelToASCIIWhiteColor(t *testing.T) {
+ converter := NewPixelConverter()
assertions := assert.New(t)
r, g, b, a := uint8(255), uint8(255), uint8(255), uint8(255)
pixel := color.RGBA{
@@ 41,14 42,14 @@ func TestConvertPixelToASCIIWhiteColor(t *testing.T) {
defaultOptions := NewOptions()
defaultOptions.Colored = false
- convertedChar := ConvertPixelToASCII(pixel, &defaultOptions)
+ convertedChar := converter.ConvertPixelToASCII(pixel, &defaultOptions)
lastPixelChar := defaultOptions.Pixels[len(defaultOptions.Pixels)-1]
assertions.Equal(convertedChar, string([]byte{lastPixelChar}),
fmt.Sprintf("White color chould be converted to %s", string([]byte{lastPixelChar})))
defaultOptions.Colored = false
defaultOptions.Reversed = true
- convertedChar = ConvertPixelToASCII(pixel, &defaultOptions)
+ convertedChar = converter.ConvertPixelToASCII(pixel, &defaultOptions)
firstPixelChar := defaultOptions.Pixels[0]
assertions.Equal(convertedChar, string([]byte{firstPixelChar}),
fmt.Sprintf("Reversed white color chould be converted to %s", string([]byte{firstPixelChar})))
@@ 56,6 57,7 @@ func TestConvertPixelToASCIIWhiteColor(t *testing.T) {
// TestConvertPixelToASCIIBlackColor convert a white image pixel to ascii string
func TestConvertPixelToASCIIBlackColor(t *testing.T) {
+ converter := NewPixelConverter()
assertions := assert.New(t)
r, g, b, a := uint8(0), uint8(0), uint8(0), uint8(0)
pixel := color.RGBA{
@@ 67,20 69,21 @@ func TestConvertPixelToASCIIBlackColor(t *testing.T) {
defaultOptions := NewOptions()
defaultOptions.Colored = false
- convertedChar := ConvertPixelToASCII(pixel, &defaultOptions)
+ convertedChar := converter.ConvertPixelToASCII(pixel, &defaultOptions)
firstPixelChar := defaultOptions.Pixels[0]
assertions.Equal(convertedChar, string([]byte{firstPixelChar}),
fmt.Sprintf("Black color chould be converted to %s", string([]byte{firstPixelChar})))
defaultOptions.Colored = false
defaultOptions.Reversed = true
- convertedChar = ConvertPixelToASCII(pixel, &defaultOptions)
+ convertedChar = converter.ConvertPixelToASCII(pixel, &defaultOptions)
lastPixelChar := defaultOptions.Pixels[len(defaultOptions.Pixels)-1]
assertions.Equal(convertedChar, string([]byte{lastPixelChar}),
fmt.Sprintf("Reversed Black color chould be converted to %s", string([]byte{lastPixelChar})))
}
func TestColoredASCIIChar(t *testing.T) {
+ converter := NewPixelConverter()
assertions := assert.New(t)
r, g, b, a := uint8(123), uint8(123), uint8(123), uint8(255)
pixel := color.RGBA{
@@ 91,20 94,21 @@ func TestColoredASCIIChar(t *testing.T) {
}
defaultOptions := NewOptions()
defaultOptions.Colored = true
- coloredChar := ConvertPixelToASCII(pixel, &defaultOptions)
+ coloredChar := converter.ConvertPixelToASCII(pixel, &defaultOptions)
assertions.True(len(coloredChar) > 1)
}
// TestReverseSlice test reverse a slice
func TestReverseSlice(t *testing.T) {
+ converter := PixelASCIIConverter{}
s := []byte{1, 2, 3, 4, 5}
- reversedSlice := reverse(s)
+ reversedSlice := converter.reverse(s)
expectedReversedSlice := []byte{5, 4, 3, 2, 1}
assert.True(t, reflect.DeepEqual(reversedSlice, expectedReversedSlice),
fmt.Sprintf("%+v reversed should equal to %+v", s, expectedReversedSlice))
s = []byte{1, 2, 3, 4}
- reversedSlice = reverse(s)
+ reversedSlice = converter.reverse(s)
expectedReversedSlice = []byte{4, 3, 2, 1}
assert.True(t, reflect.DeepEqual(reversedSlice, expectedReversedSlice),
fmt.Sprintf("%+v reversed should equal to %+v", s, expectedReversedSlice))
@@ 113,6 117,7 @@ func TestReverseSlice(t *testing.T) {
// ExampleConvertPixelToASCII is a example convert pixel to ascii char
func ExampleConvertPixelToASCII() {
+ converter := NewPixelConverter()
// Create the pixel
r, g, b, a := uint8(255), uint8(255), uint8(255), uint8(255)
pixel := color.RGBA{
@@ 125,7 130,7 @@ func ExampleConvertPixelToASCII() {
// Create the convert options
defaultOptions := NewOptions()
defaultOptions.Colored = false
- convertedChar := ConvertPixelToASCII(pixel, &defaultOptions)
+ convertedChar := converter.ConvertPixelToASCII(pixel, &defaultOptions)
fmt.Println(convertedChar)
// Output: @
-}>
\ No newline at end of file
+}
M convert/convert.go => convert/convert.go +28 -9
@@ 36,10 36,29 @@ var DefaultOptions = Options{
StretchedScreen: false,
}
+func NewImageConverter() *ImageConverter {
+ return &ImageConverter{
+ resizeHandler: NewResizeHandler(),
+ pixelConverter: ascii.NewPixelConverter(),
+ }
+}
+
+type Converter interface {
+ Image2ASCIIMatrix(image image.Image, imageConvertOptions *Options) []string
+ Image2ASCIIString(image image.Image, options *Options) string
+ ImageFile2ASCIIMatrix(imageFilename string, option *Options) []string
+ ImageFile2ASCIIString(imageFilename string, option *Options) string
+}
+
+type ImageConverter struct {
+ resizeHandler ResizeHandler
+ pixelConverter ascii.PixelConverter
+}
+
// Image2ASCIIMatrix converts a image to ASCII matrix
-func Image2ASCIIMatrix(image image.Image, imageConvertOptions *Options) []string {
+func (converter *ImageConverter) Image2ASCIIMatrix(image image.Image, imageConvertOptions *Options) []string {
// Resize the convert first
- newImage := ScaleImage(image, imageConvertOptions)
+ newImage := converter.resizeHandler.ScaleImage(image, imageConvertOptions)
sz := newImage.Bounds()
newWidth := sz.Max.X
newHeight := sz.Max.Y
@@ 51,7 70,7 @@ func Image2ASCIIMatrix(image image.Image, imageConvertOptions *Options) []string
pixelConvertOptions := ascii.NewOptions()
pixelConvertOptions.Colored = imageConvertOptions.Colored
pixelConvertOptions.Reversed = imageConvertOptions.Reversed
- rawChar := ascii.ConvertPixelToASCII(pixel, &pixelConvertOptions)
+ rawChar := converter.pixelConverter.ConvertPixelToASCII(pixel, &pixelConvertOptions)
rawCharValues = append(rawCharValues, rawChar)
}
rawCharValues = append(rawCharValues, "\n")
@@ 60,8 79,8 @@ func Image2ASCIIMatrix(image image.Image, imageConvertOptions *Options) []string
}
// 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)
+func (converter *ImageConverter) Image2ASCIIString(image image.Image, options *Options) string {
+ convertedPixelASCII := converter.Image2ASCIIMatrix(image, options)
var buffer bytes.Buffer
for i := 0; i < len(convertedPixelASCII); i++ {
@@ 71,21 90,21 @@ func Image2ASCIIString(image image.Image, options *Options) string {
}
// ImageFile2ASCIIMatrix converts a image file to ascii matrix
-func ImageFile2ASCIIMatrix(imageFilename string, option *Options) []string {
+func (converter *ImageConverter) ImageFile2ASCIIMatrix(imageFilename string, option *Options) []string {
img, err := OpenImageFile(imageFilename)
if err != nil {
log.Fatal("open image failed : " + err.Error())
}
- return Image2ASCIIMatrix(img, option)
+ return converter.Image2ASCIIMatrix(img, option)
}
// ImageFile2ASCIIString converts a image file to ascii string
-func ImageFile2ASCIIString(imageFilename string, option *Options) string {
+func (converter *ImageConverter) ImageFile2ASCIIString(imageFilename string, option *Options) string {
img, err := OpenImageFile(imageFilename)
if err != nil {
log.Fatal("open image failed : " + err.Error())
}
- return Image2ASCIIString(img, option)
+ return converter.Image2ASCIIString(img, option)
}
// OpenImageFile open a image and return a image object
M convert/convert_test.go => convert/convert_test.go +12 -6
@@ 35,6 35,7 @@ func TestOpenNotExistsFile(t *testing.T) {
// TestImage2ASCIIMatrix test convert a image to ascii matrix
func TestImage2ASCIIMatrix(t *testing.T) {
+ converter := NewImageConverter()
imageTests := []struct {
imageFilename string
asciiMatrix []string
@@ 60,7 61,7 @@ func TestImage2ASCIIMatrix(t *testing.T) {
convertOptions.FitScreen = false
convertOptions.Colored = false
- matrix := ImageFile2ASCIIMatrix(tt.imageFilename, &convertOptions)
+ matrix := converter.ImageFile2ASCIIMatrix(tt.imageFilename, &convertOptions)
if !reflect.DeepEqual(matrix, tt.asciiMatrix) {
t.Errorf("image %s convert expected to %+v, but get %+v",
tt.imageFilename, tt.asciiMatrix, matrix)
@@ 70,6 71,7 @@ func TestImage2ASCIIMatrix(t *testing.T) {
}
func TestImageFile2ASCIIString(t *testing.T) {
+ converter := NewImageConverter()
imageTests := []struct {
imageFilename string
asciiString string
@@ 85,7 87,7 @@ func TestImageFile2ASCIIString(t *testing.T) {
convertOptions.FitScreen = false
convertOptions.Colored = false
- charString := ImageFile2ASCIIString(tt.imageFilename, &convertOptions)
+ charString := converter.ImageFile2ASCIIString(tt.imageFilename, &convertOptions)
if charString != tt.asciiString {
t.Errorf("image %s convert expected to %+v, but get %+v",
tt.imageFilename, tt.asciiString, charString)
@@ 95,6 97,7 @@ func TestImageFile2ASCIIString(t *testing.T) {
}
func TestImage2ReversedASCIIString(t *testing.T) {
+ converter := NewImageConverter()
imageTests := []struct {
imageFilename string
asciiString string
@@ 110,7 113,7 @@ func TestImage2ReversedASCIIString(t *testing.T) {
convertOptions.Colored = false
convertOptions.Reversed = true
- charString := ImageFile2ASCIIString(tt.imageFilename, &convertOptions)
+ charString := converter.ImageFile2ASCIIString(tt.imageFilename, &convertOptions)
if charString != tt.asciiString {
t.Errorf("image %s convert expected to %+v, but get %+v",
tt.imageFilename, tt.asciiString, charString)
@@ 121,6 124,7 @@ func TestImage2ReversedASCIIString(t *testing.T) {
// BenchmarkBigImage2ASCIIMatrix benchmark convert big image to ascii
func BenchmarkBigImage2ASCIIMatrix(b *testing.B) {
+ converter := NewImageConverter()
convertOptions := DefaultOptions
convertOptions.FitScreen = false
convertOptions.Colored = false
@@ 128,12 132,13 @@ func BenchmarkBigImage2ASCIIMatrix(b *testing.B) {
convertOptions.FixedHeight = 200
for i := 0; i < b.N; i++ {
- _ = ImageFile2ASCIIMatrix("testdata/cat_2000x1500.jpg", &convertOptions)
+ _ = converter.ImageFile2ASCIIMatrix("testdata/cat_2000x1500.jpg", &convertOptions)
}
}
// BenchmarkSmallImage2ASCIIMatrix benchmark convert small image to ascii
func BenchmarkSmallImage2ASCIIMatrix(b *testing.B) {
+ converter := NewImageConverter()
convertOptions := DefaultOptions
convertOptions.FitScreen = false
convertOptions.Colored = false
@@ 141,17 146,18 @@ func BenchmarkSmallImage2ASCIIMatrix(b *testing.B) {
convertOptions.FixedHeight = 200
for i := 0; i < b.N; i++ {
- _ = ImageFile2ASCIIMatrix("testdata/husky_200x200.jpg", &convertOptions)
+ _ = converter.ImageFile2ASCIIMatrix("testdata/husky_200x200.jpg", &convertOptions)
}
}
// ExampleImage2ASCIIMatrix is example
func ExampleImage2ASCISString() {
+ converter := NewImageConverter()
imageFilename := "testdata/3x3_white.png"
convertOptions := DefaultOptions
convertOptions.FitScreen = false
convertOptions.Colored = false
- asciiString := ImageFile2ASCIIString(imageFilename, &convertOptions)
+ asciiString := converter.ImageFile2ASCIIString(imageFilename, &convertOptions)
fmt.Println(asciiString)
/* Output:
@@@
M convert/resize.go => convert/resize.go +35 -54
@@ 1,18 1,28 @@
package convert
import (
- "errors"
- "github.com/mattn/go-isatty"
"github.com/nfnt/resize"
- terminal "github.com/wayneashleyberry/terminal-dimensions"
+ "github.com/qeesung/image2ascii/terminal"
"image"
"log"
- "os"
- "runtime"
)
+func NewResizeHandler() ResizeHandler {
+ return &ImageResizeHandler{
+ terminal: terminal.NewTerminalAccessor(),
+ }
+}
+
+type ResizeHandler interface {
+ ScaleImage(image image.Image, options *Options) (newImage image.Image)
+}
+
+type ImageResizeHandler struct {
+ terminal terminal.Terminal
+}
+
// ScaleImage resize the convert to expected size base on the convert options
-func ScaleImage(image image.Image, options *Options) (newImage image.Image) {
+func (handler *ImageResizeHandler) ScaleImage(image image.Image, options *Options) (newImage image.Image) {
sz := image.Bounds()
ratio := options.Ratio
newHeight := sz.Max.Y
@@ 28,8 38,8 @@ func ScaleImage(image image.Image, options *Options) (newImage image.Image) {
// use the ratio the scale the image
if options.FixedHeight == -1 && options.FixedWidth == -1 && ratio != 1 {
- newWidth = ScaleWidthByRatio(float64(sz.Max.X), ratio)
- newHeight = ScaleHeightByRatio(float64(sz.Max.Y), ratio)
+ newWidth = handler.ScaleWidthByRatio(float64(sz.Max.X), ratio)
+ newHeight = handler.ScaleHeightByRatio(float64(sz.Max.Y), ratio)
}
// fit the screen
@@ 37,7 47,7 @@ func ScaleImage(image image.Image, options *Options) (newImage image.Image) {
options.FixedWidth == -1 &&
options.FixedHeight == -1 &&
options.FitScreen {
- fitWidth, fitHeight, err := CalcProportionalFittingScreenSize(image)
+ fitWidth, fitHeight, err := handler.CalcProportionalFittingScreenSize(image)
if err != nil {
log.Fatal(err)
}
@@ 51,7 61,7 @@ func ScaleImage(image image.Image, options *Options) (newImage image.Image) {
options.FixedHeight == -1 &&
!options.FitScreen &&
options.StretchedScreen {
- screenWidth, screenHeight, err := getTerminalScreenSize()
+ screenWidth, screenHeight, err := handler.terminal.ScreenSize()
if err != nil {
log.Fatal(err)
}
@@ 65,16 75,13 @@ func ScaleImage(image image.Image, options *Options) (newImage image.Image) {
// CalcProportionalFittingScreenSize proportional scale the image
// so that the terminal can just show the picture.
-func CalcProportionalFittingScreenSize(image image.Image) (newWidth, newHeight int, err error) {
- if !isatty.IsTerminal(os.Stdout.Fd()) && !isatty.IsCygwinTerminal(os.Stdout.Fd()) {
- return 0, 0,
- errors.New("can not detect the terminal, please disable the '-s fitScreen' option")
+func (handler *ImageResizeHandler) CalcProportionalFittingScreenSize(image image.Image) (newWidth, newHeight int, err error) {
+ screenWidth, screenHeight, err := handler.terminal.ScreenSize()
+ if err != nil {
+ log.Fatal(nil)
}
-
- screenWidth, _ := terminal.Width()
- screenHeight, _ := terminal.Height()
sz := image.Bounds()
- newWidth, newHeight = CalcFitSize(
+ newWidth, newHeight = handler.CalcFitSize(
float64(screenWidth),
float64(screenHeight),
float64(sz.Max.X),
@@ 88,13 95,13 @@ func CalcProportionalFittingScreenSize(image image.Image) (newWidth, newHeight i
// Either match the width first, then scale the length equally,
// or match the length first, then scale the height equally.
// More detail please check the example
-func CalcFitSizeRatio(width, height, imageWidth, imageHeight float64) (ratio float64) {
+func (handler *ImageResizeHandler) CalcFitSizeRatio(width, height, imageWidth, imageHeight float64) (ratio float64) {
ratio = 1.0
// try to fit the height
ratio = height / imageHeight
- scaledWidth := imageWidth * ratio / charWidth()
+ scaledWidth := imageWidth * ratio / handler.terminal.CharWidth()
if scaledWidth < width {
- return ratio / charWidth()
+ return ratio / handler.terminal.CharWidth()
}
// try to fit the width
@@ 105,43 112,17 @@ func CalcFitSizeRatio(width, height, imageWidth, imageHeight float64) (ratio flo
// CalcFitSize through the given length and width ,
// Calculation is able to match the length and width of
// the specified size, and is proportional scaling.
-func CalcFitSize(width, height, toBeFitWidth, toBeFitHeight float64) (fitWidth, fitHeight int) {
- ratio := CalcFitSizeRatio(width, height, toBeFitWidth, toBeFitHeight)
- fitWidth = ScaleWidthByRatio(toBeFitWidth, ratio)
- fitHeight = ScaleHeightByRatio(toBeFitHeight, ratio)
+func (handler *ImageResizeHandler) CalcFitSize(width, height, toBeFitWidth, toBeFitHeight float64) (fitWidth, fitHeight int) {
+ ratio := handler.CalcFitSizeRatio(width, height, toBeFitWidth, toBeFitHeight)
+ fitWidth = handler.ScaleWidthByRatio(toBeFitWidth, ratio)
+ fitHeight = handler.ScaleHeightByRatio(toBeFitHeight, ratio)
return
}
-func ScaleWidthByRatio(width float64, ratio float64) int {
+func (handler *ImageResizeHandler) ScaleWidthByRatio(width float64, ratio float64) int {
return int(width * ratio)
}
-func ScaleHeightByRatio(height float64, ratio float64) int {
- return int(height * ratio * charWidth())
-}
-
-// charWidth get the terminal char width on different system
-func charWidth() float64 {
- if isWindows() {
- return 0.714
- }
- return 0.5
-}
-
-// isWindows check if current system is windows
-func isWindows() bool {
- return runtime.GOOS == "windows"
-}
-
-// getTerminalScreenSize get the current terminal screen size
-func getTerminalScreenSize() (newWidth, newHeight uint, err error) {
- if !isatty.IsTerminal(os.Stdout.Fd()) && !isatty.IsCygwinTerminal(os.Stdout.Fd()) {
- return 0, 0,
- errors.New("can not detect the terminal, please disable the '-s fitScreen' option")
- }
-
- x, _ := terminal.Width()
- y, _ := terminal.Height()
-
- return x, y, nil
+func (handler *ImageResizeHandler) ScaleHeightByRatio(height float64, ratio float64) int {
+ return int(height * ratio * handler.terminal.CharWidth())
}
M convert/resize_test.go => convert/resize_test.go +46 -60
@@ 2,29 2,15 @@ package convert
import (
"fmt"
- "github.com/mattn/go-isatty"
+ terminal2 "github.com/qeesung/image2ascii/terminal"
"github.com/stretchr/testify/assert"
- "io/ioutil"
"log"
- "os"
- "os/exec"
- "strings"
"testing"
)
-// TestGetTerminalScreenSize test fetch the terminal screen size
-func TestGetTerminalScreenSize(t *testing.T) {
- assertions := assert.New(t)
- _, _, err := getTerminalScreenSize()
- if !isatty.IsTerminal(os.Stdout.Fd()) && !isatty.IsCygwinTerminal(os.Stdout.Fd()) {
- assertions.True(err != nil)
- } else {
- assertions.True(err == nil)
- }
-}
-
// TestScaleImageWithFixedHeight test scale the image by fixed height
func TestScaleImageWithFixedHeight(t *testing.T) {
+ handler := NewResizeHandler()
assertions := assert.New(t)
imageFilePath := "testdata/cat_2000x1500.jpg"
img, err := OpenImageFile(imageFilePath)
@@ 35,7 21,7 @@ func TestScaleImageWithFixedHeight(t *testing.T) {
options.Colored = false
options.FixedHeight = 100
- scaledImage := ScaleImage(img, &options)
+ scaledImage := handler.ScaleImage(img, &options)
sz := scaledImage.Bounds()
oldSz := img.Bounds()
assertions.Equal(100, sz.Max.Y, "scaled image height should be 100")
@@ 44,6 30,7 @@ func TestScaleImageWithFixedHeight(t *testing.T) {
// TestScaleImageWithFixedWidth test scale the image by fixed width
func TestScaleImageWithFixedWidth(t *testing.T) {
+ handler := NewResizeHandler()
assertions := assert.New(t)
imageFilePath := "testdata/cat_2000x1500.jpg"
img, err := OpenImageFile(imageFilePath)
@@ 54,7 41,7 @@ func TestScaleImageWithFixedWidth(t *testing.T) {
options.Colored = false
options.FixedWidth = 200
- scaledImage := ScaleImage(img, &options)
+ scaledImage := handler.ScaleImage(img, &options)
sz := scaledImage.Bounds()
oldSz := img.Bounds()
assertions.Equal(oldSz.Max.Y, sz.Max.Y, "scaled image height should be changed")
@@ 63,6 50,7 @@ func TestScaleImageWithFixedWidth(t *testing.T) {
// TestScaleImageWithFixedWidthHeight test scale the image by fixed width
func TestScaleImageWithFixedWidthHeight(t *testing.T) {
+ handler := NewResizeHandler()
assertions := assert.New(t)
imageFilePath := "testdata/cat_2000x1500.jpg"
img, err := OpenImageFile(imageFilePath)
@@ 74,7 62,7 @@ func TestScaleImageWithFixedWidthHeight(t *testing.T) {
options.FixedWidth = 200
options.FixedHeight = 100
- scaledImage := ScaleImage(img, &options)
+ scaledImage := handler.ScaleImage(img, &options)
sz := scaledImage.Bounds()
assertions.Equal(100, sz.Max.Y, "scaled image height should be 100")
assertions.Equal(200, sz.Max.X, "scaled image width should be 200")
@@ 82,6 70,8 @@ func TestScaleImageWithFixedWidthHeight(t *testing.T) {
// TestScaleImageByRatio test scale image by ratio
func TestScaleImageByRatio(t *testing.T) {
+ handler := NewResizeHandler()
+ terminal := terminal2.NewTerminalAccessor()
assertions := assert.New(t)
imageFilePath := "testdata/cat_2000x1500.jpg"
img, err := OpenImageFile(imageFilePath)
@@ 92,58 82,52 @@ func TestScaleImageByRatio(t *testing.T) {
options.Colored = false
options.Ratio = 0.5
- scaledImage := ScaleImage(img, &options)
+ scaledImage := handler.ScaleImage(img, &options)
sz := scaledImage.Bounds()
oldSz := img.Bounds()
- expectedHeight := int(float64(oldSz.Max.Y) * 0.5 * charWidth())
+ expectedHeight := int(float64(oldSz.Max.Y) * 0.5 * terminal.CharWidth())
expectedWidth := int(float64(oldSz.Max.X) * 0.5)
assertions.Equal(expectedHeight, sz.Max.Y, fmt.Sprintf("scaled image height should be %d", expectedHeight))
assertions.Equal(expectedWidth, sz.Max.X, fmt.Sprintf("scaled image width should be %d", expectedHeight))
}
-// TestScaleToFitTerminalSize test scale image to fit the terminal
-func TestScaleToFitTerminalSize(t *testing.T) {
- assertions := assert.New(t)
- imageFilePath := "testdata/cat_2000x1500.jpg"
- img, err := OpenImageFile(imageFilePath)
- assertions.True(img != nil)
- assertions.True(err == nil)
-
- options := DefaultOptions
- options.Colored = false
- options.FitScreen = true
-
- // not terminal
- if !isatty.IsTerminal(os.Stdout.Fd()) &&
- !isatty.IsCygwinTerminal(os.Stdout.Fd()) &&
- os.Getenv("BE_CRASHER") == "1" {
- ScaleImage(img, &options)
+// TestCalcFitSize test calc the fit size
+func TestCalcFitSize(t *testing.T) {
+ handler := ImageResizeHandler{
+ terminal: terminal2.NewTerminalAccessor(),
}
-
- cmd := exec.Command(os.Args[0], "-test.run=TestScaleToFitTerminalSize")
- cmd.Env = append(os.Environ(), "BE_CRASHER=1")
- stdout, _ := cmd.StderrPipe()
- if err := cmd.Start(); err != nil {
- t.Fatal(err)
- }
-
- // Check that the log fatal message is what we expected
- gotBytes, _ := ioutil.ReadAll(stdout)
- got := string(gotBytes)
- expected := "can not detect the terminal, please disable the '-s fitScreen' option"
- if !strings.HasSuffix(got[:len(got)-1], expected) {
- t.Fatalf("Unexpected log message. Got %s but should contain %s", got[:len(got)-1], expected)
+ fitSizeTests := []struct {
+ width int
+ height int
+ toBeFitWidth int
+ toBeFitHeight int
+ fitWidth int
+ fitHeight int
+ }{
+ {width: 100, height: 80, toBeFitWidth: 50, toBeFitHeight: 120, fitWidth: 66, fitHeight: 80},
+ {width: 100, height: 80, toBeFitWidth: 120, toBeFitHeight: 50, fitWidth: 100, fitHeight: 20},
}
-
- // Check that the program exited
- err = cmd.Wait()
- if e, ok := err.(*exec.ExitError); !ok || e.Success() {
- t.Fatalf("Process ran with err %v, want exit status 1", err)
+ for _, tt := range fitSizeTests {
+ t.Run(fmt.Sprintf("%d, %d -> %d, %d",
+ tt.width, tt.height, tt.toBeFitWidth, tt.toBeFitHeight), func(t *testing.T) {
+ fitWidth, fitHeight := handler.CalcFitSize(
+ float64(tt.width),
+ float64(tt.height),
+ float64(tt.toBeFitWidth),
+ float64(tt.toBeFitHeight))
+ if fitWidth != tt.fitWidth || fitHeight != tt.fitHeight {
+ t.Errorf("%d, %d -> %d, %d should be %d, %d, but get %d, %d",
+ tt.width, tt.height, tt.toBeFitWidth,
+ tt.toBeFitHeight, tt.fitWidth, tt.fitHeight,
+ fitWidth, fitHeight)
+ }
+ })
}
}
// ExampleScaleImage is scale image example
func ExampleScaleImage() {
+ handler := NewResizeHandler()
imageFilePath := "testdata/cat_2000x1500.jpg"
img, err := OpenImageFile(imageFilePath)
if err != nil {
@@ 155,7 139,7 @@ func ExampleScaleImage() {
options.FixedWidth = 200
options.FixedHeight = 100
- scaledImage := ScaleImage(img, &options)
+ scaledImage := handler.ScaleImage(img, &options)
sz := scaledImage.Bounds()
fmt.Print(sz.Max.X, sz.Max.Y)
// output: 200 100
@@ 163,6 147,7 @@ func ExampleScaleImage() {
// BenchmarkScaleImage benchmark scale big image
func BenchmarkScaleBigImage(b *testing.B) {
+ handler := NewResizeHandler()
imageFilePath := "testdata/cat_2000x1500.jpg"
img, err := OpenImageFile(imageFilePath)
if err != nil {
@@ 176,12 161,13 @@ func BenchmarkScaleBigImage(b *testing.B) {
options.FixedWidth = 100
for i := 0; i < b.N; i++ {
- _ = ScaleImage(img, &options)
+ _ = handler.ScaleImage(img, &options)
}
}
// BenchmarkScaleSmallImage benchmark scale small image
func BenchmarkScaleSmallImage(b *testing.B) {
+ handler := NewResizeHandler()
imageFilePath := "testdata/husky_200x200.jpg"
img, err := OpenImageFile(imageFilePath)
if err != nil {
@@ 195,6 181,6 @@ func BenchmarkScaleSmallImage(b *testing.B) {
options.FixedWidth = 100
for i := 0; i < b.N; i++ {
- _ = ScaleImage(img, &options)
+ _ = handler.ScaleImage(img, &options)
}
}
M image2ascii.go => image2ascii.go +3 -2
@@ 24,7 24,7 @@ var convertDefaultOptions = convert.DefaultOptions
func init() {
flag.StringVar(&imageFilename,
"f",
- "",
+ "docs/images/lufei.jpg",
"Image filename to be convert")
flag.Float64Var(&ratio,
"r",
@@ 60,7 60,8 @@ func init() {
func main() {
flag.Parse()
if convertOptions, err := parseOptions(); err == nil {
- fmt.Print(convert.ImageFile2ASCIIString(imageFilename, convertOptions))
+ converter := convert.NewImageConverter()
+ fmt.Print(converter.ImageFile2ASCIIString(imageFilename, convertOptions))
} else {
usage()
}
A terminal/terminal.go => terminal/terminal.go +51 -0
@@ 0,0 1,51 @@
+package terminal
+
+import (
+ "errors"
+ "github.com/mattn/go-isatty"
+ terminal "github.com/wayneashleyberry/terminal-dimensions"
+ "os"
+ "runtime"
+)
+
+const (
+ charWidthWindows = 0.714
+ charWidthOther = 0.5
+)
+
+func NewTerminalAccessor() Terminal {
+ return Accessor{}
+}
+
+// Terminal get the terminal basic information
+type Terminal interface {
+ CharWidth() float64
+ ScreenSize() (width, height int, err error)
+ IsWindows() bool
+}
+
+type Accessor struct {
+}
+
+func (accessor Accessor) CharWidth() float64 {
+ if accessor.IsWindows() {
+ return charWidthWindows
+ }
+ return charWidthOther
+}
+
+func (accessor Accessor) IsWindows() bool {
+ return runtime.GOOS == "windows"
+}
+
+func (accessor Accessor) ScreenSize() (newWidth, newHeight int, err error) {
+ if !isatty.IsTerminal(os.Stdout.Fd()) && !isatty.IsCygwinTerminal(os.Stdout.Fd()) {
+ return 0, 0,
+ errors.New("can not detect the terminal")
+ }
+
+ x, _ := terminal.Width()
+ y, _ := terminal.Height()
+
+ return int(x), int(y), nil
+}
A terminal/terminal_test.go => terminal/terminal_test.go +30 -0
@@ 0,0 1,30 @@
+package terminal
+
+import (
+ "github.com/stretchr/testify/assert"
+ "testing"
+)
+
+func TestNewTerminalAccessor(t *testing.T) {
+ assertions := assert.New(t)
+ accessor := NewTerminalAccessor()
+ assertions.True(accessor != nil)
+}
+
+func TestAccessor_CharWidth(t *testing.T) {
+ assertions := assert.New(t)
+ accessor := NewTerminalAccessor()
+ charWidth := accessor.CharWidth()
+ if accessor.IsWindows() {
+ assertions.Equal(charWidthWindows, charWidth)
+ } else {
+ assertions.Equal(charWidthOther, charWidth)
+ }
+}
+
+func TestAccessor_ScreenSize(t *testing.T) {
+ assertions := assert.New(t)
+ accessor := NewTerminalAccessor()
+ _, _, err := accessor.ScreenSize()
+ assertions.True(err != nil)
+}