英文:
Golang - Swap PNG channels of a picture using Image and Image/PNG
问题
我正在尝试编写一个简短的程序,它将读取一个PNG文件,并将其中一个通道与另一个通道交换(R、G、B是可能的选择)。
然而,我无法找出如何从image.At(x, y)返回的color.Color对象中提取整数。一旦我能构造出交换通道的新RGBA颜色,使用image.Set(x, y, color)将其写回可能会更容易。
这是我现在的代码(你可以直接跳到最后的循环):
package main
import (
"flag"
"fmt"
//"image"
"image/color"
"image/png"
"os"
)
type Choice struct {
value string
valid bool
}
func (c *Choice) validate() {
goodchoices := []string{"R", "G", "B"}
for _, v := range goodchoices {
if c.value == v {
c.valid = true
}
}
}
func main() {
var fname string
var c1 Choice
var c2 Choice
flag.StringVar(&c1.value, "c1", "", "The color channel to swap - R or G or B")
flag.StringVar(&c2.value, "c2", "", "The color channel to swap with - R or G or B")
flag.StringVar(&fname, "f", "", "A .png image (normal map)")
flag.Parse()
c1.validate()
c2.validate()
if c1.valid == true && c2.valid == true {
fmt.Println("We could proceed..")
fmt.Println("Swapping channels:", c1.value, "<->", c2.value, "In", fname) //for testing
} else {
fmt.Println("Invalid channel... Please use R, G or B.")
return
}
file, err := os.Open(fname)
if err != nil {
fmt.Println(err)
return
}
defer file.Close()
pic, err := png.Decode(file)
if err != nil {
fmt.Fprintf(os.Stderr, "%s: %v\n", fname, err)
return
}
b := pic.Bounds()
for y := b.Min.Y; y < b.Max.Y; y++ {
for x := b.Min.X; x < b.Max.X; x++ {
col := pic.At(x, y)
???? 如何交换col中的通道?????
}
}
}
英文:
I'm trying to write a short one, which will read a PNG file, and swap one channel with the other (R,G,B) being the possible choices.
I can't find out however, how to extract the integer from the color.Color object returned by image.At(x,y) . Writing it back would probably easier with image.Set(x,y,color) once i can construct the new RGBA color with the swapped channels.
Here I am now (you can pretty much skip to the last loop):
package main
import (
"flag"
"fmt"
//"image"
"image/color"
"image/png"
"os"
)
type Choice struct {
value string
valid bool
}
func (c *Choice) validate() {
goodchoices := []string{"R", "G", "B"}
for _, v := range goodchoices {
if c.value == v {
c.valid = true
}
}
}
func main() {
var fname string
var c1 Choice
var c2 Choice
flag.StringVar(&c1.value, "c1", "", "The color channel to swap - R or G or B ")
flag.StringVar(&c2.value, "c2", "", "The color channel to swap with - R or G or B ")
flag.StringVar(&fname, "f", "", "A .png image (normal map)")
flag.Parse()
c1.validate()
c2.validate()
if c1.valid == true && c2.valid == true {
fmt.Println("We could proceed..")
fmt.Println("Swapping channels:", c1.value, "<->", c2.value, "In", fname) //for testing
} else {
fmt.Println("Invalid channel... Please use R, G or B.")
return
}
file, err := os.Open(fname)
if err != nil {
fmt.Println(err)
return
}
defer file.Close()
pic, err := png.Decode(file)
if err != nil {
fmt.Fprintf(os.Stderr, "%s: %v\n", fname, err)
return
}
b := pic.Bounds()
for y := b.Min.Y; y < b.Max.Y; y++ {
for x := b.Min.X; x < b.Max.X; x++ {
col := pic.At(x, y)
???? How do I swap the channels in col ????
}
}
}
I'm really new to Go and programming in general, so please consider it in your answer. Thank You.
答案1
得分: 7
Hmmm,这比我想象的要难一些 - 我想知道是否有人能想出一个更好的主意!
问题是你不知道png.Decode
返回的具体类型 - 它可能返回任何图像类型。你只有一个image.Image
接口,它没有Set
方法。
为了解决这个问题,首先定义一个接口,所有可以设置像素的图像类型都要满足这个接口:
type ImageSet interface {
Set(x, y int, c color.Color)
}
接下来看看pic
是否实现了这个接口(如果没有,Go会引发恐慌 - 如果这让你困扰,可以使用picSet, ok
的形式):
// 获取一个可以设置像素的接口
picSet := pic.(ImageSet)
现在你的循环看起来像这样 - 我只是交换了红色和绿色,这样你就可以看到这个想法。
for y := b.Min.Y; y < b.Max.Y; y++ {
for x := b.Min.X; x < b.Max.X; x++ {
col := pic.At(x, y)
r, g, b, a := col.RGBA()
// 交换绿色和红色
newCol := color.RGBA{uint8(g>>8), uint8(r>>8), uint8(b>>8), uint8(a>>8)}
picSet.Set(x, y, newCol)
}
}
我怀疑,一个高性能的版本可能需要使用类型切换来确定它是哪种图像类型,然后为每种类型编写定制代码,对于24位图像使用uint8,对于48位图像使用uint16等等。
这是一个完整的工作示例,如果你想试一试的话。不过在playground中无法运行 - 你需要下载它。
更新:刚刚注意到你的评论。如果你知道你有一个RGBA图像,那么你可以使用类型断言来获取底层图像,这样事情就变得容易得多了。
// 如果是RGBA图像,则获取一个image.RGBA
rgba, ok := pic.(*image.RGBA)
if !ok {
fmt.Println("这不是一个RGBA图像!")
return
}
for y := b.Min.Y; y < b.Max.Y; y++ {
for x := b.Min.X; x < b.Max.X; x++ {
// 注意使用类型断言来获取color.RGBA
col := rgba.At(x, y).(color.RGBA)
// 交换绿色和红色
col.G, col.R = col.R, col.G
rgba.Set(x, y, col)
}
}
英文:
Hmmm, that was harder than I thought it would be - I wonder if anyone can come up with a better idea!
The problem is that you don't know the concrete type that png.Decode
returns - it may return any of the image types. You only have an image.Image
interface which doesn't have a Set method.
To get round that, first define an interface which all the Image types which can set pixels satisfies
type ImageSet interface {
Set(x, y int, c color.Color)
}
Next see whether pic
implements that interface (go will panic if it doesn't - use the picSet, ok form if that bothers you)
// Get an interface which can set pixels
picSet := pic.(ImageSet)
Now your loop looks like this - I only swapped red and green so you can see the idea.
for y := b.Min.Y; y < b.Max.Y; y++ {
for x := b.Min.X; x < b.Max.X; x++ {
col := pic.At(x, y)
r, g, b, a := col.RGBA()
// Swap green and red
newCol := color.RGBA{uint8(g>>8), uint8(r>>8), uint8(b>>8), uint8(a>>8)}
picSet.Set(x, y, newCol)
}
}
I suspect that a high performing version of this would have to use a type switch to determine which image type it was, then have a customized code for each one with uint8s for 24 bit images and uint16s for 48 bit images etc.
Here is the complete working example if you want to have a go. It doesn't work in the playground though - you'll have to download it.
Update: Just noticed your comment. If you know that you have an RGBA image, then you can use a type assertion to get the underlying image which makes things a whole lot easier.
// Get an image.RGBA if it is one
rgba, ok := pic.(*image.RGBA)
if !ok {
fmt.Println("That wasn't an RGBA!")
return
}
for y := b.Min.Y; y < b.Max.Y; y++ {
for x := b.Min.X; x < b.Max.X; x++ {
// Note type assertion to get a color.RGBA
col := rgba.At(x, y).(color.RGBA)
// Swap green and red
col.G, col.R = col.R, col.G
rgba.Set(x, y, col)
}
}
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论