英文:
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 < 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)
}
}
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("changed.jpg")
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})
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论