使用image/jpeg进行编码会导致图像饱和度过高或像素错误。

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

Encoding with image/jpeg cause image saturation / wrong pixels

问题

我已经有一段时间遇到这个问题了:我正在创建一个处理图像的模块,其中一个函数是遍历图像的每个像素并反转颜色。当对.png图像进行编码时,该函数返回预期的结果,但是对于.jpeg/jpg图像,它会"饱和"。

处理.png图像的示例(正确):https://i.stack.imgur.com/0r0Go.png

处理.jpeg图像的示例(错误):https://i.stack.imgur.com/I7hNM.jpg

我进行了一些研究,找到了与我的问题最接近的一个问题,尽管它不是一个答案,这是来自Go存储库的问题:https://github.com/golang/go/issues/23936

// 反转颜色函数

func InvertColors(img image.Image) image.Image {
	bounds := img.Bounds()
	width := bounds.Max.X
	height := bounds.Max.Y
	inverted := image.NewRGBA(bounds)

	for y := bounds.Min.Y; y < height; y++ {
		for x := bounds.Min.X; x < width; x++ {
			r, g, b, a := img.At(x, y).RGBA()
			c := color.RGBA{uint8(255 - r), uint8(255 - g), uint8(255 - b), uint8(a)}

			inverted.SetRGBA(x, y, c)
		}
	}

	return inverted
}

// 主函数示例
func main() {
	link := "https://i.imgur.com/n5hsdl4.jpg"
	img, err := GetImageFromURL(link)
	if err != nil {
		panic(err)
	}

	buf := new(bytes.Buffer)
	Encode(buf, img, "jpg")
	ioutil.WriteFile("temp.jpg", buf.Bytes(), 0666)

	invImg := InvertColors(img)
	buf = new(bytes.Buffer)
	Encode(buf, invImg, "jpg")
	ioutil.WriteFile("temp2.jpg", buf.Bytes(), 0666)
}

// 从URL获取图像
func GetImageFromURL(link string) (image.Image, error) {
	_, format, err := ParseURL(link)
	if err != nil {
		return nil, err
	}

	req, err := http.NewRequest("GET", link, nil)
	if err != nil {
		return nil, err
	}

	// 必须进行请求。
	req.Close = true
	req.Header.Set("Content-Type", "image/"+format)

	res, err := http.DefaultClient.Do(req)
	if err != nil {
		return nil, err
	}
	defer res.Body.Close()

	b, err := ioutil.ReadAll(res.Body)
	if err != nil {
		return nil, err
	}

	img, err := Decode(bytes.NewReader(b), format)
	if err != nil {
		return nil, err
	}

	return img, nil
}

func ParseURL(link string) (u *url.URL, format string, err error) {
	u, err = url.Parse(link)
	if err != nil {
		return u, "", err
	}

	format = u.Path[len(u.Path)-4:]
	if strings.Contains(format, ".") {
		format = strings.Split(format, ".")[1]
	}

	if format != "png" && format != "jpg" && format != "jpeg" {
		return u, "", fmt.Errorf("不支持的格式:%s", format)
	}

	return u, format, nil
}

func Decode(r io.Reader, format string) (img image.Image, err error) {
	if format == "png" {
		img, err = png.Decode(r)
		if err != nil {
			return nil, err
		}

	} else if format == "jpg" || format == "jpeg" {
		img, err = jpeg.Decode(r)
		if err != nil {
			return nil, err
		}

	} else {
		return nil, fmt.Errorf("不支持的格式:%s", format)
	}

	return img, nil
}
英文:

I have been having this problem for some time: I'm creating a module to process images, one of my functions is to go through each pixel of an image and invert colors. The function returns the expected results when encoding .png images, but it "saturates" .jpeg/jpg images.

Example when processing a .png image (correct): https://i.stack.imgur.com/0r0Go.png

Example when processing a .jpeg image (error): https://i.stack.imgur.com/I7hNM.jpg

I was researching and the closest I found to my problem, although it's not an answer, is this issue from the Go repository: https://github.com/golang/go/issues/23936

// InvertColors function

func InvertColors(img image.Image) image.Image {
	bounds := img.Bounds()
	width := bounds.Max.X
	height := bounds.Max.Y
	inverted := image.NewRGBA(bounds)

	for y := bounds.Min.Y; y &lt; height; y++ {
		for x := bounds.Min.X; x &lt; width; x++ {
			r, g, b, a := img.At(x, y).RGBA()
			c := color.RGBA{uint8(255 - r), uint8(255 - g), uint8(255 - b), uint8(a)}

			inverted.SetRGBA(x, y, c)
		}
	}

	return inverted
}

// main example
func main() {
	link := &quot;https://i.imgur.com/n5hsdl4.jpg&quot;
	img, err := GetImageFromURL(link)
	if err != nil {
		panic(err)
	}

	buf := new(bytes.Buffer)
	Encode(buf, img, &quot;jpg&quot;)
	ioutil.WriteFile(&quot;temp.jpg&quot;, buf.Bytes(), 0666)

	invImg := InvertColors(img)
	buf = new(bytes.Buffer)
	Encode(buf, invImg, &quot;jpg&quot;)
	ioutil.WriteFile(&quot;temp2.jpg&quot;, buf.Bytes(), 0666)
}

// GetImageFromURL 
func GetImageFromURL(link string) (image.Image, error) {
	_, format, err := ParseURL(link)
	if err != nil {
		return nil, err
	}

	req, err := http.NewRequest(&quot;GET&quot;, link, nil)
	if err != nil {
		return nil, err
	}

	// Required to make a request.
	req.Close = true
	req.Header.Set(&quot;Content-Type&quot;, &quot;image/&quot;+format)

	res, err := http.DefaultClient.Do(req)
	if err != nil {
		return nil, err
	}
	defer res.Body.Close()

	b, err := ioutil.ReadAll(res.Body)
	if err != nil {
		return nil, err
	}

	img, err := Decode(bytes.NewReader(b), format)
	if err != nil {
		return nil, err
	}

	return img, nil
}

func ParseURL(link string) (u *url.URL, format string, err error) {
	u, err = url.Parse(link)
	if err != nil {
		return u, &quot;&quot;, err
	}

	format = u.Path[len(u.Path)-4:]
	if strings.Contains(format, &quot;.&quot;) {
		format = strings.Split(format, &quot;.&quot;)[1]
	}

	if format != &quot;png&quot; &amp;&amp; format != &quot;jpg&quot; &amp;&amp; format != &quot;jpeg&quot; {
		return u, &quot;&quot;, fmt.Errorf(&quot;Unsupported format: %s&quot;, format)
	}

	return u, format, nil
}

func Decode(r io.Reader, format string) (img image.Image, err error) {
	if format == &quot;png&quot; {
		img, err = png.Decode(r)
		if err != nil {
			return nil, err
		}

	} else if format == &quot;jpg&quot; || format == &quot;jpeg&quot; {
		img, err = jpeg.Decode(r)
		if err != nil {
			return nil, err
		}

	} else {
		return nil, fmt.Errorf(&quot;Unsupported format: %s&quot;, format)
	}

	return img, nil
}

答案1

得分: 4

你的代码中有两个与颜色处理相关的错误(第二个可能与此无关)。

首先,RGBA() 方法返回的是 16 位的 R、G、B、A 值,但你将它们当作了 8 位的值处理。

其次,color.RGBA 值是经过 alpha 预乘的,所以 (R, G, B, A) 的反色应该是 (A-R, A-G, A-B, A) 而不是 (MAX-R, MAX-G, MAX-B, A)。这可能与你的图片没有明显的 alpha 通道有关。

修复代码的一种方法是将这段代码替换为:

r, g, b, a := img.At(x, y).RGBA()
c := color.RGBA{uint8((a - r)>>8), uint8((a - g)>>8), uint8((a - b)>>8), uint8(a>>8)}

(注意,你可能会发现,先将图像转换为 image.NRGBA(如果它还不是)然后迭代存储图像的(非 alpha 预乘)RGBA 通道的底层字节切片,比使用 imagecolor 包提供的更抽象的接口要快得多。)

英文:

You have two bugs in your code related to color-handling (the second of which is probably not relevant).

First, the RGBA() method returns 16-bit R, G, B, A, but you're treating them like 8-bit values.

Second, color.RGBA values are alpha-premultiplied, so the inverse of (R, G, B, A) is (A-R, A-G, A-B, A) and not (MAX-R, MAX-G, MAX-B, A). This is probably not relevant, because it looks like your picture does not have any significant alpha.

One way to fix to your code is to replace this:

r, g, b, a := img.At(x, y).RGBA()
c := color.RGBA{uint8(255 - r), uint8(255 - g), uint8(255 - b), uint8(a)}

with this:

r, g, b, a := img.At(x, y).RGBA()
c := color.RGBA{uint8((a - r)&gt;&gt;8), uint8((a - g)&gt;&gt;8), uint8((a - b)&gt;&gt;8), uint8(a&gt;&gt;8)}

(Note, that you may find that first converting your image to image.NRGBA (if it's not already) and then iterating over underlying byte slice that stores the (non-alpha-premultiplied) RGBA channels for the image is much faster than using the more abstract interfaces provided by the image and color packages.)

huangapple
  • 本文由 发表于 2022年7月10日 14:21:49
  • 转载请务必保留本文链接:https://go.coder-hub.com/72926524.html
匿名

发表评论

匿名网友

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

确定