更改单个像素的颜色 - Golang图像

时间:2016-04-12 12:33:05

标签: image go draw

我想打开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方法。

任何人都可以解释如何做到这一点?非常感谢。

3 个答案:

答案 0 :(得分:14)

成功解码image.Decode()(以及jpeg.Decode()等特定解码函数)返回值image.Imageimage.Image是一个定义图像只读视图的界面:它不提供更改/绘制图像的方法。

image包提供了多种image.Image实现,允许您更改/绘制图像,通常使用Set(x, y int, c color.Color)方法。

image.Decode()但是并不保证返回的图像将是image包中定义的任何图像类型,或者甚至图像的动态类型具有{{1}方法(它可能,但不能保证)。注册的自定义图像解码器可能会返回Set()值作为自定义实现(意味着不是image.Image包中定义的图像类型)。

如果(动态类型)图片确实有image方法,您可以使用type assertion并使用其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接口,因此您只需要实现所需的更改。这是如何做到的:

image.Image

使用它:非常简单。像你一样加载图像,但是在保存时,提供我们的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

如果您必须更改许多像素,则在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 {
    // 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)
}

如果你必须改变很多像素,那么创建一个支持改变其像素的新图像可能更有利可图,例如image.RGBA,在其上绘制原始图像,然后继续更改您想要的像素。

要将图像绘制到另一个图像上,您可以使用image/draw包。

// 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)

以上代码仅供演示。在“真实”图像中,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()点开始的矩形,在这种情况下,需要进行一些调整才能使其正常工作。

答案 1 :(得分:2)

图像解码返回image interface,其Bounds方法可获取图像像素的宽度和高度。

img, _, err := image.Decode(imgfile)
if err != nil {
    fmt.Println(err.Error())
}
size := img.Bounds().Size()

一旦你有了宽度和高度,就可以用两个嵌套的for循环迭代像素(一个用于x,一个用于y坐标)。

for x := 0; x < size.X; x++ {
    for y := 0; y < size.Y; y++ {
        color := color.RGBA{
            uint8(255 * x / size.X),
            uint8(255 * y / size.Y),
            55,
            255}
        m.Set(x, y, color)
    }
}

完成图像处理后,您可以对文件进行编码。但由于image.Image没有Set方法,您可以创建一个新的RGBA图像,该图像返回RGBA结构,您可以使用Set方法。

m := image.NewRGBA(image.Rect(0, 0, width, height))
outFile, err := os.Create("changed.jpg")
if err != nil {
    log.Fatal(err)
}
defer outFile.Close()
png.Encode(outFile, m)

答案 2 :(得分:0)

image.Image默认是不可变的,但draw.Image是可变的。

如果您对draw.Image进行类型转换,则应该为您提供Set方法

img.(draw.Image).Set(0,0, color.RGBA{85, 165, 34, 1})
相关问题