Go语言GUI编程包-Ebiten

1. 前言

最近碰到一个题目,就是鼠标选择图片区域,然后把选中的区域进行图像处理,图像处理好说,调用opencv的库函数即可,关键是如何用鼠标选择图片区域,这就涉及到GUI编程了,因此我去了解一下如何使用Go语言的GUI编程。

Go语言没有官方GUI的包,但是有很多第三方的GUI包,比如walk,但是这个包只能在Windows下使用,不能跨平台,我是Linux系统,放弃了选择这个包。

我网上又听说了fyne这个包,我查了一下如何用fyne监听鼠标事件,结果Github上有人提出issue,似乎使用fyne监听鼠标事件挺麻烦的,我就放弃使用fyne了,但是提问者提到了一个叫做Ebiten的库了。

我看了下Ebiten库里面的方法,基本显示图片、监听事件都有,感觉不错,就打算使用它了。

因此我在这里也介绍一些Ebiten库一些简单用法,比如在窗口输出文字、在窗口显示图片、监听鼠标事件。

2. Ebiten库介绍

Ebiten官网:https://ebiten.org/

Ebiten官方API文档:https://pkg.go.dev/github.com/hajimehoshi/ebiten

Ebiten是一个使用Go语言编程库,用于2D游戏开发,可以跨平台。

3. Ebiten在窗口显示文字

不多废话,直接上一个简单的Demo,功能就是弹出一个窗口,输出Hello, World。

package main

import (
	"fmt"
	"log"

	"github.com/hajimehoshi/ebiten"
	"github.com/hajimehoshi/ebiten/ebitenutil"
)

/*
	空结构体,实现了ebiten.Game接口。
*/
type Game struct{}

/*
	Update()是一个成员函数,自动调用
	因为这是一个游戏开发的库,界面是需要实时更新的
	因此每一个周期,都会更新一次,也就是调用一次Update函数
	更新周期是1/60秒,也就是一秒会更新60次
*/
func (g *Game) Update() error {
	return nil
}

/*
	Draw()用于渲染界面,也是个会自动调用的函数
	这个函数的自动调用频率和你电脑显示器的刷新频率一样
	screen表示GUI窗口显示的对象,这里是在该窗口输出"Hello, World!"
*/
func (g *Game) Draw(screen *ebiten.Image) {
	ebitenutil.DebugPrint(screen, "Hello, World!")
}

/*
	Layout()函数的返回值表示显示窗口里面逻辑上屏幕的大小
	官网上说参数outsideWidth和outsideHeight是显示在桌面的窗口大小

	这里是固定大小640*480
*/
func (g *Game) Layout(outsideWidth, outsideHeight int) (screenWidth, screenHeight int) {
	return 640, 480
}

func main() {
	// 设置窗口大小是640*480
	ebiten.SetWindowSize(640, 480)
	// 设置窗口头部,显示Hello, World
	ebiten.SetWindowTitle("Hello, World!")
	// 运行游戏
	if err := ebiten.RunGame(&Game{}); err != nil {
		log.Fatal(err)
	}
}

显示效果如下图:

GameUpdate()Draw()Layout()main

4. Ebiten在窗口显示图片

Ebiten包提供了一个函数,用于展示一个图片,如下所示:

func (i *Image) DrawImage(img *Image, options *DrawImageOptions) error

参数简单说一下:

img *Image: 被展示的图片
option  *DrawImageOption: 展示选项,比如图片的位置,暂不设置

4.1 直接展示图片

比如我要显示下面的图片:

gopher.png

Draw()
func (g *Game) Draw(screen *ebiten.Image) {
	// 1. 读取图片文件
	f, err := os.Open("gopher.png")
	if err != nil {
		log.Fatal(err)
	}
	img, err := png.Decode(f)
	if err != nil {
		log.Fatal(err)
	}
	// 把Image文件转成ebiten.Image文件,用于展示
	eImg := ebiten.NewImageFromImage(img)
	// 在屏幕上展示出图片
	screen.DrawImage(eImg, nil)
}

显示效果如下图:

4.1 指定位置展示图片

比如我一个图片要放在屏幕的某个位置,怎样指定坐标呢?

DrawImage()
func (i *Image) DrawImage(img *Image, options *DrawImageOptions) error

但是此时需要指定第二个参数,比如我要把图片显示在窗口的(100,100)坐标处:

func (g *Game) Draw(screen *ebiten.Image) {
	// 1. 读取图片文件
	f, err := os.Open("gopher.png")
	if err != nil {
		log.Fatal(err)
	}
	img, err := png.Decode(f)
	if err != nil {
		log.Fatal(err)
	}
	// 把Image文件转成ebiten.Image文件,用于展示
	eImg := ebiten.NewImageFromImage(img)
	op := &ebiten.DrawImageOptions{}
	op.GeoM.Translate(float64(100), float64(100))
	// 在屏幕上展示出图片
	screen.DrawImage(eImg, op)
}

显示效果:

5. Ebiten监听鼠标事件

监听鼠标事件,这里使用一个简单的例子,刚开始是显示图片的,按下左键,图片消失,松开左键,图片显示。

Draw()
var flag bool

func (g *Game) Draw(screen *ebiten.Image) {

	// flag一开始默认是false,会显示图片
	if !flag {
		screen.DrawImage(eImg, nil)
	} else {
		screen.Clear()
	}
	// 监听鼠标事件,如果左键按下,false变成true,图片就会删除
	if inpututil.IsMouseButtonJustPressed(ebiten.MouseButtonLeft) {
		flag = !flag
	}
	// 监听鼠标左键松开事件
	if inpututil.IsMouseButtonJustReleased(ebiten.MouseButtonLeft) {
		flag = !flag
	}
}

演示效果如下gif动图所示:

6. 其他

ebiten还有很多功能,我没有提到,主要也就是几个函数的调用,可以自己去查询官方API文档:https://pkg.go.dev/github.com/hajimehoshi/ebiten 。

ebitenutil.DebugPrint(screen, "Hello, World!")

但是如何显示文字呢?比如更改显示文字的颜色、大小等?

但这个需求还真不好实现,这个问题有人在github上也提出了issue: https://github.com/hajimehoshi/ebiten/issues/280 :
image-20210417153130812

作者表示,ebiten库的设计原则是,万物皆Image,因此想要输出特定的文字,需要制作一张带有文字的Image,然后放到窗口中显示出来。。。