Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

image/png: info loss when encoding non-alpha-premultiplied that's not NRGBA (e.g., NRGBA64, NYCbCrA) #26001

Closed
rbxb opened this issue Jun 21, 2018 · 3 comments
Labels
FrozenDueToAge NeedsInvestigation Someone must examine and confirm this is a valid issue and not a duplicate of an existing one.
Milestone

Comments

@rbxb
Copy link

rbxb commented Jun 21, 2018

Edit: solution below

Go Version:
1.10.3 windows/amd64 (latest)

I'm not sure if this is an error or if it is by design.
I think this is a problem with the webp and/or vp8 packages, but it could be a problem with my code. I don't know all the ins and outs of the image package.

When decoding WebPs (and converting to PNG), color data is lost for pixels that are completely transparent. I don't think that this should be happening.
The WebPs affected are structured like so:

  • VP8X
  • ALPH
  • VP8

This loss does not occur when I decode the same WebP using Google's WebP tools.

Here is my Go code:

if extension == ".webp" {

	//open the WebP file
	webpFile, _ := os.OpenFile(filepath, os.O_RDWR, 0777)
	defer webpFile.Close()

	//create a reader
	reader := bufio.NewReader(webpFile)

	//decode the WebP
	imageData, _ := webp.Decode(reader)
	webpFile.Close()

	//create the new PNG file
	pngFile, _ := os.Create(newFilePath + ".png")
	defer pngFile.Close()

	//write to the PNG file
	png.Encode(pngFile, imageData)

}

I reproduced the problem with an example image:
I'm linking to them because they don't display correctly through github

The original PNG before it was encoded to WebP; the Golang logo but completely transparent:
.png

I drew on only the alpha channel of the original so you can see that it still retains color:
.png

Here is the WebP encoded from the original PNG
.webp

Now here is where the problem occurs:
Again, I drew on only the alpha channel so you can see if the color is retained.

Decoded using Google's WebP tool (What I expected to happen):
.png
.png

Decoded using my code (What happened):
.png
.png

Does anyone know why this is happening? Is it a bug/intentional in the WebP packages? Is it my code?

Edit: Here is the solution I implemented.
It handles NYCbCrA and NRGBA images specially and reformats them to a color.RGBA to give to the png encoder. The RGBA pixel is just set to be completely opaque since I don't need the A values.

func ConvertNYCbCrA(Color color.NYCbCrA) color.RGBA {
	r,g,b := color.YCbCrToRGB(Color.Y,Color.Cb,Color.Cr)
	return color.RGBA{r,g,b,255}
}

func ConvertNRGBA(Color color.NRGBA) color.RGBA {
	return color.RGBA{
		Color.R,
		Color.G,
		Color.B,
		255,
	}
}

/************************/

if extension == ".webp" {

	//open the WebP file
	webpFile, _ := os.OpenFile(filepath, os.O_RDWR, 0777)
	defer webpFile.Close()

	//create a reader
	reader := bufio.NewReader(webpFile)

	//decode the WebP
	imageData, _ := webp.Decode(reader)
	webpFile.Close()

	//create new image
	newImage := image.NewRGBA(imageData.Bounds())

	//fill new image with pixels
	for y := imageData.Bounds().Min.Y; y < imageData.Bounds().Max.Y; y++ {
		for x := imageData.Bounds().Min.X; x < imageData.Bounds().Max.X; x++ {

			//get pixel from imageData
			pixel := imageData.At(x,y)

			//convert pixel to RGBA
			var RGBApixel color.RGBA
			switch imageData.ColorModel() {

				case color.NYCbCrAModel:
					RGBApixel = ConvertNYCbCrA(pixel.(color.NYCbCrA))
	
				case color.NRGBAModel:
					RGBApixel = ConvertNRGBA(pixel.(color.NRGBA))
	
				default:
					RGBApixel = color.RGBAModel.Convert(pixel).(color.RGBA)
					RGBApixel.A = 255

			}

			//set new pixel in new image
			newImage.Set(x,y,RGBApixel)

		}
	}

	//create the new PNG file
	pngFile, _ := os.Create(newFilePath + ".png")
	defer pngFile.Close()

	//write to the PNG file
	png.Encode(pngFile, newImage)

}

Here is the result:
.png

@gopherbot gopherbot added this to the Unreleased milestone Jun 21, 2018
@ianlancetaylor ianlancetaylor added the NeedsInvestigation Someone must examine and confirm this is a valid issue and not a duplicate of an existing one. label Jun 21, 2018
@ianlancetaylor
Copy link
Contributor

CC @nigeltao

@nigeltao
Copy link
Contributor

It's by design. The Go image and color model treats "transparent red" and "transparent blue" equivalently, as they're both transparent. In other words, it works primarily with alpha-premultiplied values. The documentation for the fundamental interface, https://golang.org/pkg/image/color/#Color, says (emphasis added):

type Color interface {
        // RGBA returns the *alpha-premultiplied* etc.
        RGBA() (r, g, b, a uint32)
}

Decoding the WebP image is giving you an image.NYCbCrA, because that's the natural format of transparent VP8. That N means non-alpha-premultiplied, and the PNG format allows for non-alpha-premultiplied values, but the image/png package doesn't treat image.NYCbCrA values especially. Instead, it goes through the general-purpose image.Image interface and the previously mentioned color.Color interface, which enforces alpha-premultiplication, which converts both of "transparent red" and "transparent blue" to the same "transparent".

It's arguably a bug in the image/png package, which should special case encoding an image.NYCbCrA the same way it special cases image.NRGBA. Similarly, it's probably a bug that it doesn't special case image.NRGBA64.

It's not a trivial thing to implement, though, especially with chroma subsampling.

I also don't have much spare time to work on this myself. Sorry.

@nigeltao nigeltao changed the title x/image/webp: Losing color data on transparent pixels when decoding image/png: info loss when encoding non-alpha-premultiplied that's not NRGBA (e.g., NRGBA64, NYCbCrA) Jun 22, 2018
@rbxb
Copy link
Author

rbxb commented Jun 23, 2018

@nigeltao thanks for the information.

@rbxb rbxb closed this as completed Jun 24, 2018
@golang golang locked and limited conversation to collaborators Jun 24, 2019
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
FrozenDueToAge NeedsInvestigation Someone must examine and confirm this is a valid issue and not a duplicate of an existing one.
Projects
None yet
Development

No branches or pull requests

4 participants