在成功解码image.Decode()
(以及特定的解码函数,如jpeg.Decode()
)后,会返回一个image.Image
值。 image.Image
是一个接口,它定义了图像的只读视图:不提供更改/绘制图像的方法。
image
包提供了几个image.Image
实现,允许您更改/绘制图像,通常使用Set(x, y int, c color.Color)
方法。
然而,image.Decode()
不能保证返回的图像将是image
包中定义的任何图像类型之一,甚至可能动态类型的图像没有Set()
方法(也可能有,但不保证)。已注册的自定义图像解码器可能会返回一个image.Image
值,它是一种自定义实现(意味着不是image
包中定义的图像类型)。
如果(动态类型的)图像确实具有Set()
方法,则可以使用类型断言并使用其Set()
方法进行绘制。以下是实现的方式:
type Changeable interface {
Set(x, y int, c color.Color)
}
imgfile, err := os.Open("unchanged.jpg")
if err != nil {
panic(err.Error())
}
defer imgfile.Close()
img, err := jpeg.Decode(imgfile)
if err != nil {
panic(err.Error())
}
if cimg, ok := img.(Changeable); ok {
cimg.Set(0, 0, color.RGBA{85, 165, 34, 255})
cimg.Set(0, 1, color.RGBA{255, 0, 0, 255})
} else {
}
如果图像没有
Set()
方法,你可以选择通过实现一个自定义类型来"覆盖其视图",该类型实现了
image.Image
接口,但在它的
At(x,y int) color.Color
方法中(返回/提供像素颜色),你要返回如果图像是可更改的所需设置的新颜色,并返回你不想更改图像的原始图像的像素。
最简单的实现
image.Image
接口的方法是利用
嵌入,这样你只需要实现你想要的更改。以下是如何完成这个过程:
type MyImg struct {
image.Image
}
func (m *MyImg) At(x, y int) color.Color {
switch {
case x == 0 && y == 0:
return color.RGBA{85, 165, 34, 255}
case x == 0 && y == 1:
return color.RGBA{255, 0, 0, 255}
}
return m.Image.At(x, y)
}
使用它非常简单。像往常一样加载图像,但在保存时,提供我们的MyImg
类型的值,它将负责在编码器请求时提供更改后的图像内容(颜色):
jpeg.Encode(outFile, &MyImg{img}, nil)
如果你需要改变很多像素,将所有的像素都包含在At()
方法里是不切实际的。为此,我们可以扩展MyImg
以拥有我们的Set()
实现,它存储我们想要更改的像素。例如实现:
type MyImg struct {
image.Image
custom map[image.Point]color.Color
}
func NewMyImg(img image.Image) *MyImg {
return &MyImg{img, map[image.Point]color.Color{}}
}
func (m *MyImg) Set(x, y int, c color.Color) {
m.custom[image.Point{x, y}] = c
}
func (m *MyImg) At(x, y int) color.Color {
if c := m.custom[image.Point{x, y}]; c != nil {
return c
}
return m.Image.At(x, y)
}
使用它:
// Load image as usual, then
my := NewMyImg(img)
my.Set(0, 0, color.RGBA{85, 165, 34, 1})
my.Set(0, 1, color.RGBA{255, 0, 0, 255})
// And when saving, save 'my' instead of the original:
jpeg.Encode(outFile, my, nil)
如果你需要改变许多像素,那么创建一个支持更改像素的新图像可能更具收益性,例如image.RGBA
,将原始图像绘制在其上,然后继续更改所需的像素。
要将一幅图像绘制到另一幅图像上,可以使用image/draw
包。
cimg := image.NewRGBA(img.Bounds())
draw.Draw(cimg, img.Bounds(), img, image.Point{}, draw.Over)
cimg.Set(0, 0, color.RGBA{85, 165, 34, 255})
cimg.Set(0, 1, color.RGBA{255, 0, 0, 255})
jpeg.Encode(outFile, cimg, nil)
上面的代码只是为了演示。在“现实生活”图像中,Image.Bounds()
可能返回一个不以 (0;0)
点开始的矩形,这种情况下可能需要进行一些调整才能使其正常工作。