From 9e6742da409008fc764fa344fc324438ce7928eb Mon Sep 17 00:00:00 2001 From: qeesung <1245712564@qq.com> Date: Sun, 28 Oct 2018 20:05:22 +0800 Subject: [PATCH] add the fit options to scale the image to fit the screen --- convert/convert.go | 26 ++++++------ convert/convert_test.go | 8 ++-- convert/resize.go | 90 ++++++++++++++++++++++++++++++++++++----- convert/resize_test.go | 20 ++++----- image2ascii.go | 59 ++++++++++++++++++++------- image2ascii_test.go | 8 ++-- 6 files changed, 156 insertions(+), 55 deletions(-) diff --git a/convert/convert.go b/convert/convert.go index 2ae8c82..623ebd9 100644 --- a/convert/convert.go +++ b/convert/convert.go @@ -16,22 +16,24 @@ import ( // Options to convert the image to ASCII type Options struct { - Ratio float64 - ExpectedWidth int - ExpectedHeight int - FitScreen bool - Colored bool - Reversed bool + Ratio float64 + FixedWidth int + FixedHeight int + FitScreen bool // only work on terminal + StretchedScreen bool // only work on terminal + Colored bool // only work on terminal + Reversed bool } // DefaultOptions for convert image var DefaultOptions = Options{ - Ratio: 1, - ExpectedWidth: -1, - ExpectedHeight: -1, - FitScreen: true, - Colored: true, - Reversed: false, + Ratio: 1, + FixedWidth: -1, + FixedHeight: -1, + FitScreen: true, + Colored: true, + Reversed: false, + StretchedScreen: false, } // Image2ASCIIMatrix converts a image to ASCII matrix diff --git a/convert/convert_test.go b/convert/convert_test.go index 32e25f0..e25491e 100644 --- a/convert/convert_test.go +++ b/convert/convert_test.go @@ -124,8 +124,8 @@ func BenchmarkBigImage2ASCIIMatrix(b *testing.B) { convertOptions := DefaultOptions convertOptions.FitScreen = false convertOptions.Colored = false - convertOptions.ExpectedWidth = 200 - convertOptions.ExpectedHeight = 200 + convertOptions.FixedWidth = 200 + convertOptions.FixedHeight = 200 for i := 0; i < b.N; i++ { _ = ImageFile2ASCIIMatrix("testdata/cat_2000x1500.jpg", &convertOptions) @@ -137,8 +137,8 @@ func BenchmarkSmallImage2ASCIIMatrix(b *testing.B) { convertOptions := DefaultOptions convertOptions.FitScreen = false convertOptions.Colored = false - convertOptions.ExpectedWidth = 200 - convertOptions.ExpectedHeight = 200 + convertOptions.FixedWidth = 200 + convertOptions.FixedHeight = 200 for i := 0; i < b.N; i++ { _ = ImageFile2ASCIIMatrix("testdata/husky_200x200.jpg", &convertOptions) diff --git a/convert/resize.go b/convert/resize.go index 0129df3..e927049 100644 --- a/convert/resize.go +++ b/convert/resize.go @@ -18,26 +18,39 @@ func ScaleImage(image image.Image, options *Options) (newImage image.Image) { newHeight := sz.Max.Y newWidth := sz.Max.X - if options.ExpectedWidth != -1 { - newWidth = options.ExpectedWidth + if options.FixedWidth != -1 { + newWidth = options.FixedWidth } - if options.ExpectedHeight != -1 { - newHeight = options.ExpectedHeight + if options.FixedHeight != -1 { + newHeight = options.FixedHeight } // use the ratio the scale the image - if options.ExpectedHeight == -1 && options.ExpectedWidth == -1 && ratio != 1 { - newWidth = int(float64(sz.Max.X) * ratio) - newHeight = int(float64(sz.Max.Y) * ratio * charWidth()) + if options.FixedHeight == -1 && options.FixedWidth == -1 && ratio != 1 { + newWidth = ScaleWidthByRatio(float64(sz.Max.X), ratio) + newHeight = ScaleHeightByRatio(float64(sz.Max.Y), ratio) } // fit the screen - // get the fit the screen size if ratio == 1 && - options.ExpectedWidth == -1 && - options.ExpectedHeight == -1 && + options.FixedWidth == -1 && + options.FixedHeight == -1 && options.FitScreen { + fitWidth, fitHeight, err := CalcProportionalFittingScreenSize(image) + if err != nil { + log.Fatal(err) + } + newWidth = int(fitWidth) + newHeight = int(fitHeight) + } + + //Stretch the picture to overspread the terminal + if ratio == 1 && + options.FixedWidth == -1 && + options.FixedHeight == -1 && + !options.FitScreen && + options.StretchedScreen { screenWidth, screenHeight, err := getTerminalScreenSize() if err != nil { log.Fatal(err) @@ -50,6 +63,63 @@ func ScaleImage(image image.Image, options *Options) (newImage image.Image) { return } +// 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") + } + + screenWidth, _ := terminal.Width() + screenHeight, _ := terminal.Height() + sz := image.Bounds() + newWidth, newHeight = CalcFitSize( + float64(screenWidth), + float64(screenHeight), + float64(sz.Max.X), + float64(sz.Max.Y)) + return +} + +// CalcFitSizeRatio through the given length and width, +// the computation can match the optimal scaling ratio of the length and width. +// In other words, it is able to give a given size rectangle to contain pictures +// 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) { + ratio = 1.0 + // try to fit the height + ratio = height / imageHeight + scaledWidth := imageWidth * ratio / charWidth() + if scaledWidth < width { + return ratio / charWidth() + } + + // try to fit the width + ratio = width / imageWidth + return ratio +} + +// 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) + return +} + +func 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() { diff --git a/convert/resize_test.go b/convert/resize_test.go index 3820cd1..93c0756 100644 --- a/convert/resize_test.go +++ b/convert/resize_test.go @@ -33,7 +33,7 @@ func TestScaleImageWithFixedHeight(t *testing.T) { options := DefaultOptions options.Colored = false - options.ExpectedHeight = 100 + options.FixedHeight = 100 scaledImage := ScaleImage(img, &options) sz := scaledImage.Bounds() @@ -52,7 +52,7 @@ func TestScaleImageWithFixedWidth(t *testing.T) { options := DefaultOptions options.Colored = false - options.ExpectedWidth = 200 + options.FixedWidth = 200 scaledImage := ScaleImage(img, &options) sz := scaledImage.Bounds() @@ -71,8 +71,8 @@ func TestScaleImageWithFixedWidthHeight(t *testing.T) { options := DefaultOptions options.Colored = false - options.ExpectedWidth = 200 - options.ExpectedHeight = 100 + options.FixedWidth = 200 + options.FixedHeight = 100 scaledImage := ScaleImage(img, &options) sz := scaledImage.Bounds() @@ -152,8 +152,8 @@ func ExampleScaleImage() { options := DefaultOptions options.Colored = false - options.ExpectedWidth = 200 - options.ExpectedHeight = 100 + options.FixedWidth = 200 + options.FixedHeight = 100 scaledImage := ScaleImage(img, &options) sz := scaledImage.Bounds() @@ -172,8 +172,8 @@ func BenchmarkScaleBigImage(b *testing.B) { options := DefaultOptions options.Colored = false options.FitScreen = false - options.ExpectedHeight = 100 - options.ExpectedWidth = 100 + options.FixedHeight = 100 + options.FixedWidth = 100 for i := 0; i < b.N; i++ { _ = ScaleImage(img, &options) @@ -191,8 +191,8 @@ func BenchmarkScaleSmallImage(b *testing.B) { options := DefaultOptions options.Colored = false options.FitScreen = false - options.ExpectedHeight = 100 - options.ExpectedWidth = 100 + options.FixedHeight = 100 + options.FixedWidth = 100 for i := 0; i < b.N; i++ { _ = ScaleImage(img, &options) diff --git a/image2ascii.go b/image2ascii.go index 297ada1..4c4f16a 100644 --- a/image2ascii.go +++ b/image2ascii.go @@ -12,20 +12,48 @@ import ( var imageFilename string var ratio float64 -var expectedWidth int -var expectedHeight int +var fixedWidth int +var fixedHeight int var fitScreen bool +var stretchedScreen bool var colored bool var reversed bool +var convertDefaultOptions = convert.DefaultOptions + func init() { - flag.StringVar(&imageFilename, "f", "", "Image filename to be convert") - flag.Float64Var(&ratio, "r", 1, "Ratio to scale the image, ignored when use -w or -g") - flag.IntVar(&expectedWidth, "w", -1, "Expected image width, -1 for image default width") - flag.IntVar(&expectedHeight, "g", -1, "Expected image height, -1 for image default height") - flag.BoolVar(&fitScreen, "s", true, "Fit the terminal screen, ignored when use -w, -g, -r") - flag.BoolVar(&colored, "c", true, "Colored the ascii when output to the terminal") - flag.BoolVar(&reversed, "i", false, "Reversed the ascii when output to the terminal") + flag.StringVar(&imageFilename, + "f", + "", + "Image filename to be convert") + flag.Float64Var(&ratio, + "r", + convertDefaultOptions.Ratio, + "Ratio to scale the image, ignored when use -w or -g") + flag.IntVar(&fixedWidth, + "w", + convertDefaultOptions.FixedWidth, + "Expected image width, -1 for image default width") + flag.IntVar(&fixedHeight, + "g", + convertDefaultOptions.FixedHeight, + "Expected image height, -1 for image default height") + flag.BoolVar(&fitScreen, + "s", + convertDefaultOptions.FitScreen, + "Fit the terminal screen, ignored when use -w, -g, -r") + flag.BoolVar(&colored, + "c", + convertDefaultOptions.Colored, + "Colored the ascii when output to the terminal") + flag.BoolVar(&reversed, + "i", + convertDefaultOptions.Reversed, + "Reversed the ascii when output to the terminal") + flag.BoolVar(&stretchedScreen, + "t", + convertDefaultOptions.StretchedScreen, + "Stretch the picture to overspread the screen") flag.Usage = usage } @@ -44,12 +72,13 @@ func parseOptions() (*convert.Options, error) { } // config the options convertOptions := &convert.Options{ - Ratio: ratio, - ExpectedHeight: expectedHeight, - ExpectedWidth: expectedWidth, - FitScreen: fitScreen, - Colored: colored, - Reversed: reversed, + Ratio: ratio, + FixedWidth: fixedWidth, + FixedHeight: fixedHeight, + FitScreen: fitScreen, + StretchedScreen: stretchedScreen, + Colored: colored, + Reversed: reversed, } return convertOptions, nil } diff --git a/image2ascii_test.go b/image2ascii_test.go index af8060e..2b00724 100644 --- a/image2ascii_test.go +++ b/image2ascii_test.go @@ -18,15 +18,15 @@ func TestParseOptions(t *testing.T) { ratio = 0.5 fitScreen = false colored = false - expectedHeight = 100 - expectedWidth = 100 + fixedHeight = 100 + fixedWidth = 100 opt, err :=parseOptions() assertions.True(err == nil) assertions.Equal(ratio, opt.Ratio) assertions.False(fitScreen) assertions.False(colored) - assertions.Equal(expectedWidth, opt.ExpectedWidth) - assertions.Equal(expectedHeight, opt.ExpectedHeight) + assertions.Equal(fixedWidth, opt.FixedWidth) + assertions.Equal(fixedHeight, opt.FixedHeight) } func TestParseUsage(t *testing.T) { -- 2.45.2