【问题标题】:Change color of a single pixel - Golang image更改单个像素的颜色 - Golang 图像
【发布时间】:2016-04-12 12:33:04
【问题描述】:

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

谁能解释一下如何做到这一点?非常感谢。

【问题讨论】:

  • 相关/可能与Draw a rectangle in golang?重复
  • 公平地说,我不明白那个答案。即使读了两遍,我也不知道如何使用 Set 方法。
  • 好的,请看下面我的回答。

标签: image go draw


【解决方案1】:

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

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

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

如果(动态类型的)图像确实具有Set() 方法,您可以使用type assertion 并使用其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)
}

使用它:

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

// 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) 点开始的矩形,在这种情况下,需要进行一些调整才能使其正常工作。

【讨论】:

  • 即使查看这些包的 go 源代码,我也不知道您是如何确定可以使用 Set 创建接口的。你是怎么得出这个结论的?
  • @JustinSelf image 包中不同的具体image.Image 实现都有一个Set(x, y int, c color.Color) 方法。因此,标准库的图像解码函数很可能会返回具有此Set() 方法的图像实现。所以这就是为什么我们应该先检查一下。如果图像没有Set() 方法,那么我们可以随意命名我们的自定义Set() 方法(只要我们会调用它就可以了),为了清楚起见,我只是使用了相同的方法签名.
【解决方案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)

【讨论】:

  • 我不能用这个。我仍然不能使用 Set 方法,因为我的编码图像是 image.Image 类型,它没有 Set 方法。循环遍历整个图像也不是我想做的,我只需要更改几个像素。无论如何感谢您的回答
  • 如何新建一个RGBA图像,返回RGBA接口,可以在上面使用Set方法m := image.NewRGBA(image.Rect(0, 0, width, height))
  • 好吧,我只是想我可以直接在我的编码图像上绘制像素。创建新图像并复制旧编码图像的内容只是为了改变几个像素对我来说似乎有点奇怪。
【解决方案3】:

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

如果你对 draw.Image 进行类型转换,那应该会给你一个 Set 方法

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

【讨论】:

  • 我之前尝试过这样做。我收到此错误:“ *image.YCbCr is not draw.Image: missing method Set”
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2017-09-20
  • 2016-07-03
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多