使用Image和Image/PNG在Golang中交换图片的PNG通道

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

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 (
&quot;flag&quot;
&quot;fmt&quot;
//&quot;image&quot;
&quot;image/color&quot;
&quot;image/png&quot;
&quot;os&quot;
)

type Choice struct {
value string
valid bool
}

func (c *Choice) validate() {
goodchoices := []string{&quot;R&quot;, &quot;G&quot;, &quot;B&quot;}
for _, v := range goodchoices {
	if c.value == v {
		c.valid = true
	}
}
}

func main() {

var fname string
var c1 Choice
var c2 Choice

flag.StringVar(&amp;c1.value, &quot;c1&quot;, &quot;&quot;, &quot;The color channel to swap - R or G or B &quot;)
flag.StringVar(&amp;c2.value, &quot;c2&quot;, &quot;&quot;, &quot;The color channel to swap with - R or G or B &quot;)
flag.StringVar(&amp;fname, &quot;f&quot;, &quot;&quot;, &quot;A .png image (normal map)&quot;)
flag.Parse()

c1.validate()
c2.validate()

if c1.valid == true &amp;&amp; c2.valid == true {
	fmt.Println(&quot;We could proceed..&quot;)
	fmt.Println(&quot;Swapping channels:&quot;, c1.value, &quot;&lt;-&gt;&quot;, c2.value, &quot;In&quot;, fname) //for testing
} else {
	fmt.Println(&quot;Invalid channel... Please use R, G or B.&quot;)
	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, &quot;%s: %v\n&quot;, fname, err)
	return
}

b := pic.Bounds()

for y := b.Min.Y; y &lt; b.Max.Y; y++ {
	for x := b.Min.X; x &lt; 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 &lt; b.Max.Y; y++ {
	for x := b.Min.X; x &lt; b.Max.X; x++ {
		col := pic.At(x, y)
		r, g, b, a := col.RGBA()
		// Swap green and red
		newCol := color.RGBA{uint8(g&gt;&gt;8), uint8(r&gt;&gt;8), uint8(b&gt;&gt;8), uint8(a&gt;&gt;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(&quot;That wasn&#39;t an RGBA!&quot;)
	return
}

for y := b.Min.Y; y &lt; b.Max.Y; y++ {
	for x := b.Min.X; x &lt; 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)
	}
}

huangapple
  • 本文由 发表于 2013年2月2日 01:45:08
  • 转载请务必保留本文链接:https://go.coder-hub.com/14652060.html
匿名

发表评论

匿名网友

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

确定