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

huangapple go评论89阅读模式
英文:

Change color of a single pixel - Golang image

问题

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

有人可以解释一下如何做到这一点吗?非常感谢。

英文:

I want to open jpeg image file, encode it, change some pixel colors, and then save it back as it was.

I'd like to do something like this

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)

I just can't come up with a working solution, since default image type that I get after encoding image-file doesn't have Set method.

Can anyone explain how to do this? Thanks a lot.

答案1

得分: 23

成功解码后(包括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是Changeable类型,可以调用它的Set()方法(在其上绘制)
	cimg.Set(0, 0, color.RGBA{85, 165, 34, 255})
	cimg.Set(0, 1, color.RGBA{255, 0, 0, 255})
	// 完成后,像往常一样保存img
} else {
	// 没有成功...参考下面的选项
}

如果图像没有Set()方法,你可以选择通过实现一个自定义类型来“覆盖其视图”,该类型实现了image.Image接口,但在其At(x, y int) color.Color方法中(返回/提供像素的颜色)返回你将要设置的新颜色,对于不更改图像的像素,返回原始图像的像素。

实现image.Image接口最简单的方法是利用_嵌入_,这样你只需要实现你想要的更改。下面是如何实现的:

type MyImg struct {
	// 嵌入image.Image,这样MyImg将实现image.Image接口
	// 因为Image的字段和方法将被提升:
	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)
}

使用方法:

// 像往常一样加载图像,然后

my := NewMyImg(img)
my.Set(0, 0, color.RGBA{85, 165, 34, 1})
my.Set(0, 1, color.RGBA{255, 0, 0, 255})

// 保存时,当然要保存'my'而不是原始图像:
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()方法)
cimg.Set(0, 0, color.RGBA{85, 165, 34, 255})
cimg.Set(0, 1, color.RGBA{255, 0, 0, 255})

// 保存时,当然要保存'cimg':
jpeg.Encode(outFile, cimg, nil)

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

英文:

On successful decoding image.Decode() (and also specific decoding functions like jpeg.Decode()) return a value of image.Image. image.Image is an interface which defines a read-only view of an image: it does not provide methods to change / draw on the image.

The image package provides several image.Image implementations which allow you to change / draw on the image, usually with a Set(x, y int, c color.Color) method.

image.Decode() however does not give you any guarantee that the returned image will be any of the image types defined in the image package, or even that the dynamic type of the image has a Set() method (it may, but no guarantee). Registered custom image decoders may return you an image.Image value being a custom implementation (meaning not an image type defined in the image package).

If the (dynamic type of the) image does have a Set() method, you may use type assertion and use its Set() method to draw on it. This is how it can be done:

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
}

If the image does not have a Set() method, you may choose to "override its view" by implementing a custom type which implements image.Image, but in its At(x, y int) color.Color method (which returns / supplies the colors of pixels) you return the new colors that you would set if the image would be changeable, and return the pixels of the original image where you would not change the image.

Implementing the image.Image interface is easiest done by utilizing embedding, so you only need to implement the changes you want. This is how it can be done:

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

Using it: extremely simple. Load the image as you did, but when saving, provide a value of our MyImg type which will take care of providing the altered image content (colors) when it is asked by the encoder:

jpeg.Encode(outFile, &MyImg{img}, nil)

If you have to change many pixels, it's not practical to include all in the At() method. For that we can extend our MyImg to have our Set() implementation which stores the pixels that we want to change. Example implementation:

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

Using it:

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

If you have to change many pixels, then it might be more profitable to just create a new image which supports changing its pixels, e.g. image.RGBA, draw the original image on it and then proceed to change pixels you want to.

To draw an image onto another, you can use the image/draw package.

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)

The above code is just for demonstration. In "real-life" images Image.Bounds() may return a rectangle that does not start at (0;0) point, in which case some adjustment would be needed to make it work.

答案2

得分: 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)
英文:

The image decode returns an image interface which has a Bounds method to obtain the image pixel width and height.

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

Once you have the width and height you can iterate over the pixels with two nested for loops (one for the x and one for y coordinate).

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

Once your are done with the image manipulation you can encode the file. But because image.Image does not have Set method, you can create a new RGBA image, which returns an RGBA struct, on which you can use the Set method.

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

答案3

得分: 0

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

如果你将其类型转换为draw.Image,那么你就可以使用Set方法。

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

image.Image is immutable by default, but draw.Image is mutable.

If you do a type conversion to draw.Image, that should give you a Set method

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

huangapple
  • 本文由 发表于 2016年4月12日 20:33:04
  • 转载请务必保留本文链接:https://go.coder-hub.com/36573413.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定