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.imgur.com/DG35RsR.png
Example when processing a .jpeg image (error): https://i.imgur.com/DZQmxJ2.png
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 < 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
}
// main example
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)
}
// GetImageFromURL
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
}
// Required to make a request.
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("Unsupported format: %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("Unsupported format: %s", format)
}
return img, nil
}
CodePudding user response:
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)>>8), uint8((a - g)>>8), uint8((a - b)>>8), uint8(a>>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.)