Search code examples
gopng

Problems with Alpha channel(PNG) and Golang


I'm having a simple problem with my images in golang. I'm painting a png image with a color, but the result is not what I want.

In the pixels with the lowest number in Alpha, another color is painted. I'm using alphaChannel = false

/* return new image with new color
 * alphaChannel = true get AlphaChannel from given color
 * alphaChannel = false get AlphaChannel from image (x,y) point
 */
func PaintPngImage(img image.Image, cl color.Color, alphaChannel bool) image.Image {
    R, G, B, A := cl.RGBA()
    composeImage := image.NewRGBA(img.Bounds())

    // paint image over a new image
    draw.Draw(composeImage, composeImage.Bounds(), img, image.Point{}, draw.Over)

    // paint new color over the image
    bounds := composeImage.Bounds()
    w, h := bounds.Max.X, bounds.Max.Y

    for x := 0; x < w; x++ {
        for y := 0; y < h; y++ {
            _, _, _, aa := composeImage.At(x, y).RGBA()
            if !alphaChannel {
                A = aa
            }
            realColor := color.RGBA{R: uint8(R),G: uint8(G),B: uint8(B),A: uint8(A)}
            if aa != 0 {
                composeImage.Set(x, y, realColor)
            }
        }
    }

    return composeImage
}

colorLayer := PaintPngImage(layerImage, cl, false)
out, err := os.Create("./output/test.png")
utils.ShowFatalError(err)
err = png.Encode(out, colorLayer)
utils.CloseFile(out) // close file os.Close
utils.ShowFatalError(err) // Show panic log if err != nil

Final: [1]

If I use jpeg.Decode instead of png.Decode the image has not strange colors.


Solution

  • Color.RGBA() returns color components that are in the range of 0..0xffff, not in 0..0xff:

    // RGBA returns the alpha-premultiplied red, green, blue and alpha values
    // for the color. Each value ranges within [0, 0xffff], but is represented
    // by a uint32 so that multiplying by a blend factor up to 0xffff will not
    // overflow.
    

    So when constructing the color to draw with, you have to right shift all 16-bit components (by 8) and not just convert to uint8 because that conversion keeps the lowest 8 bits which might be "random" compared to the 16-bit value, and you need the higher 8 bits:

    realColor := color.RGBA{
        R: uint8(R>>8),
        G: uint8(G>>8),
        B: uint8(B>>8),
        A: uint8(A>>8),
    }