Home > database >  Is img.At() or rgba.Pix() faster/better for retrieving pixel data from a Golang image.Image?
Is img.At() or rgba.Pix() faster/better for retrieving pixel data from a Golang image.Image?

Time:01-08

Reading the answers from Get a pixel array from from golang image.Image, I saw that there were two methods of pixel RGBA retrieval, via img.At() and rgba.Pix().

Which is better to use? Should one always be used, or are there cases where one should be used over the other and vice versa?

CodePudding user response:

If your program will be conducting a computation where you need a majority, if not all of the pixel data, then rgba.Pix() significantly outperforms img.At(). If you only need the pixel data of a single or few pixels in an image, use img.At() (the overhead of computing the rgba.Pix() prerequisite is too high in such cases).

Here are the results of various test loads, the durations of which are averaged over 10 samples each.

Method 1x1 1000x667 3840x2160 1000x667 computation 1000x667 only 5x5 accessed
img.At() 195ns 30.211071ms 294.885396ms 853.345043ms 42.431μs
rgba.Pix() 719ns 7.786029ms 77.700552ms 836.480063ms 6.791461ms

We can see how for the tiny 1x1 image and the image where we limit our for loops to upper bounds of 5, using img.At() results in faster execution time. However, for use cases where every pixel is fetched, rgba.Pix() results in better performance. This improvement in performance is less evident the more computation we do with every pixel, as the total time increases and the difference between img.At() and rgba.Pix() becomes less obvious, as seen in "1000x667 computation" in the table above.

Here is the test code used:

func main() {
    resp, err := http.Get("IMAGE URL GOES HERE")
    if err != nil {
        panic(err)
    }
    defer resp.Body.Close()

    img, _, err := image.Decode(resp.Body)
    if err != nil {
        panic(err)
    }

    var start time.Time
    var duration time.Duration
    samples := 10
    var sum time.Duration

    fmt.Println("Samples: ", samples)
    sum = time.Duration(0)
    for i := 0; i < samples; i   {
        start = time.Now()
        usingAt(img)
        duration = time.Since(start)
        sum  = duration
    }
    fmt.Println("*** At avg: ", sum/time.Duration(samples))

    sum = time.Duration(0)
    for i := 0; i < samples; i   {
        start = time.Now()
        usingPix(img)
        duration = time.Since(start)
        sum  = duration
    }
    fmt.Println("*** Pix avg: ", sum/time.Duration(samples))
}

func usingAt(img image.Image) {
    bounds := img.Bounds()
    width, height := bounds.Max.X, bounds.Max.Y

    for y := 0; y < height; y   {
        for x := 0; x < width; x   {
            r, g, b, _ := img.At(x, y).RGBA()
            _ = uint8(r >> 8)
            _ = uint8(g >> 8)
            _ = uint8(b >> 8)
        }
    }
}

func usingPix(img image.Image, targetColor colorful.Color) {
    bounds := img.Bounds()
    width, height := bounds.Max.X, bounds.Max.Y

    rgba := image.NewRGBA(bounds)
    draw.Draw(rgba, bounds, img, bounds.Min, draw.Src)
    for y := 0; y < height; y   {
        for x := 0; x < width; x   {
            index := (y*width   x) * 4
            pix := rgba.Pix[index : index 4]
            _ = pix[0]
            _ = pix[1]
            _ = pix[2]
        }
    }
}

1000x667 only 5x5 accessed replaced the height and width in the for loops with 5 and 5, limiting the number of pixels accessed.

1000x667 computation actually used the RGB values by comparing each pixel's color distance from a target color with go-colorful's DE2000 calculation.

  •  Tags:  
  • Related