M convert/convert.go => convert/convert.go +14 -12
@@ 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
M convert/convert_test.go => convert/convert_test.go +4 -4
@@ 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)
M convert/resize.go => convert/resize.go +80 -10
@@ 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() {
M convert/resize_test.go => convert/resize_test.go +10 -10
@@ 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)
M image2ascii.go => image2ascii.go +44 -15
@@ 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
}
M image2ascii_test.go => image2ascii_test.go +4 -4
@@ 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) {