更改单个像素的颜色 - Go lang图像

Mat*_*ády 11 image draw go

我想打开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)
Run Code Online (Sandbox Code Playgroud)

我只是无法想出一个有效的解决方案,因为我在编码图像文件后得到的默认图像类型没有Set方法.

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

icz*_*cza 15

成功解码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 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
}
Run Code Online (Sandbox Code Playgroud)

如果图像没有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)
}
Run Code Online (Sandbox Code Playgroud)

使用它:非常简单.像你一样加载图像,但是在保存时,提供我们MyImg类型的值,它将在编码器询问时提供改变的图像内容(颜色):

jpeg.Encode(outFile, &MyImg{img}, nil)
Run Code Online (Sandbox Code Playgroud)

如果必须更改许多像素,则在At()方法中包含all是不切实际的.为此,我们可以扩展我们MyImgSet()实现,以存储我们想要更改的像素.示例实现:

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)
}
Run Code Online (Sandbox Code Playgroud)

使用它:

// 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)
Run Code Online (Sandbox Code Playgroud)

如果你必须改变许多像素,那么创建一个支持改变其像素的新图像可能更有利可图,例如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)
Run Code Online (Sandbox Code Playgroud)

以上代码仅供演示.在"真实"中,图像Image.Bounds()可能会返回一个不从(0;0)点开始的矩形,在这种情况下,需要进行一些调整才能使其工作.