我想打开jpeg图像文件,对其进行编码,更改一些像素颜色,然后将其保存回原样。
我想做这样的事情
imgfile, err := os.Open("unchanged.jpeg") defer imgfile.Close() if err != nil { fmt.Println(err.Error()) } img,err := jpeg.Decode(imgfile) if err != nil { fmt.Println(err.Error()) } img.Set(0, 0, color.RGBA{85, 165, 34, 1}) img.Set(1,0,....) outFile, _ := os.Create("changed.jpeg") defer outFile.Close() jpeg.Encode(outFile, img, nil)
我只是想不出一个可行的解决方案,因为编码图像文件后获得的默认图像类型没有Set方法。
谁能解释该怎么做?非常感谢。
成功解码后image.Decode()(以及特定的解码功能,如jpeg.Decode()),返回的值image.Image。image.Image是一个接口,用于定义图像的只读视图:它不提供更改/绘制图像的方法。
image.Decode()
jpeg.Decode()
image.Image
该image软件包提供了几种image.Image实现方式,这些实现方式通常允许您使用一种Set(x, y int, c color.Color)方法来更改/绘制图像。
image
Set(x, y int, c color.Color)
image.Decode()但是,不能保证返回的图像将是image包中定义的任何图像类型,甚至Set()不能保证图像的动态类型具有方法(它可以,但不能保证)。注册的自定义图像解码器可能会返回一个image.Image值,该值是自定义实现(意味着不是image包中定义的图像类型)。
Set()
如果(图像的动态类型)确实具有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 is of type Changeable, you can call its Set() method (draw on it) cimg.Set(0, 0, color.RGBA{85, 165, 34, 255}) cimg.Set(0, 1, color.RGBA{255, 0, 0, 255}) // when done, save img as usual } else { // No luck... see your options below }
如果图像没有Set()方法,您可以选择通过实现一个实现的自定义类型来“覆盖其视图” image.Image,但是在其At(x, y int) color.Color方法(返回/提供像素的颜色)中,您将返回如果该图像将是可变的,并在不更改图像的情况下返回原始图像的像素。
At(x, y int) color.Color
image.Image利用 embedding 最容易实现接口,因此您只需要实现所需的更改即可。这是可以做到的:
type MyImg struct { // Embed image.Image so MyImg will implement image.Image // because fields and methods of Image will be promoted: image.Image } func (m *MyImg) At(x, y int) color.Color { // "Changed" part: custom colors for specific coordinates: 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} } // "Unchanged" part: the colors of the original image: return m.Image.At(x, y) }
使用它:非常简单。MyImg照原样加载图像,但是在保存时,请提供我们类型的值,该值将在编码器要求时提供更改后的图像内容(颜色):
MyImg
jpeg.Encode(outFile, &MyImg{img}, nil)
如果必须更改许多像素,则将所有像素都包含在该At()方法中是不切实际的。为此,我们可以扩展我们MyImg的Set()实现,以存储要更改的像素。示例实现:
At()
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 { // Explicitly changed part: custom colors of the changed pixels: if c := m.custom[image.Point{x, y}]; c != nil { return c } // Unchanged part: colors of the original image: 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.RGBA
要将图像绘制到另一个图像上,可以使用该image/draw包装。
image/draw
cimg := image.NewRGBA(img.Bounds()) draw.Draw(cimg, img.Bounds(), img, image.Point{}, draw.Over) // Now you have cimg which contains the original image and is changeable // (it has a Set() method) cimg.Set(0, 0, color.RGBA{85, 165, 34, 255}) cimg.Set(0, 1, color.RGBA{255, 0, 0, 255}) // And when saving, save 'cimg' of course: jpeg.Encode(outFile, cimg, nil)
上面的代码仅用于演示。在“现实生活”中,图像Image.Bounds()可能会返回一个矩形,该矩形并非始于该(0;0)点,在这种情况下,需要进行一些调整才能使其工作。
Image.Bounds()
(0;0)