Get a pixel array from from golang image.Image

2020-08-15 00:51发布

I need to get a pixel array in the form of []byte to be passed to the texImage2D method of a Contex from the /mobile/gl package.

It needs a pixel array where rgba values of each pixel is appended in the order of pixels left to right, top to bottom. Currently I have an image loaded from a file.

a, err := asset.Open("key.jpeg")
if err != nil {
    log.Fatal(err)
}
defer a.Close()

img, _, err := image.Decode(a)
if err != nil {
    log.Fatal(err)
}

I am looking for something like img.Pixels()

标签: go
3条回答
女痞
2楼-- · 2020-08-15 01:18

In my test, Pix method shows ~4 times faster than At, but still takes too long...

Here is my test script, not sure about the height / width order, but this works for me:

// test_image_time.go
package main

import (
    "os"
    "fmt"
    "image"
    _ "image/jpeg"
    "golang.org/x/image/draw"
    "time"
)

func main() {
    img_path:= "/path/to/image/.jpg"
    aa := time.Now()
    reader, _ := os.Open(img_path)
    m, _, _ := image.Decode(reader)
    bounds := m.Bounds()
    fmt.Println("Bounds: ", bounds.Min.Y, bounds.Max.Y, bounds.Min.X, bounds.Max.X)
    bb := time.Now()
    fmt.Println("Read file time: ", float64(bb.Nanosecond() - aa.Nanosecond()) / 1e9)

    aa = time.Now()
    _ = image_2_array_at(m)
    bb = time.Now()
    fmt.Println("At Time: ", float64(bb.Nanosecond() - aa.Nanosecond()) / 1e9)

    aa = time.Now()
    _ = image_2_array_pix(m)
    bb = time.Now()
    fmt.Println("Pix Time: ", float64(bb.Nanosecond() - aa.Nanosecond()) / 1e9)
}

func image_2_array_at(src image.Image) [][][3]float32 {
    bounds := src.Bounds()
    width, height := bounds.Max.X, bounds.Max.Y
    iaa := make([][][3]float32, height)

    for y := 0; y < height; y++ {
        row := make([][3]float32, width)
        for x := 0; x < width; x++ {
            r, g, b, _ := src.At(x, y).RGBA()
            // A color's RGBA method returns values in the range [0, 65535].
            // Shifting by 8 reduces this to the range [0, 255].
            row[x] = [3]float32{float32(r>>8), float32(g>>8), float32(b>>8)}
        }
        iaa[y] = row
    }

    return iaa
}

func image_2_array_pix(src image.Image) [][][3]float32 {
    bounds := src.Bounds()
    width, height := bounds.Max.X, bounds.Max.Y
    iaa := make([][][3]float32, height)
    src_rgba := image.NewRGBA(src.Bounds())
    draw.Copy(src_rgba, image.Point{}, src, src.Bounds(), draw.Src, nil)

    for y := 0; y < height; y++ {
        row := make([][3]float32, width)
        for x := 0; x < width; x++ {
            idx_s := (y * width + x) * 4
            pix := src_rgba.Pix[idx_s : idx_s + 4]
            row[x] = [3]float32{float32(pix[0]), float32(pix[1]), float32(pix[2])}
        }
        iaa[y] = row
    }

    return iaa
}

Run

$ go run test_image_time.go
Bounds:  0 976 0 1920
Read file time:  0.025212067
At Time:  0.069091218
Pix Time:  0.0165787
查看更多
老娘就宠你
3楼-- · 2020-08-15 01:19

This is what I ended up doing. I am using image/draw package's Draw function to refill an image.RGBA instance

rect := img.Bounds()
rgba := image.NewRGBA(rect)
draw.Draw(rgba, rect, img, rect.Min, draw.Src)

Now rgba.Pix contains the array I want and can be used in the TexImage2D method.

glctx.TexImage2D(gl.TEXTURE_2D, 0, rect.Max.X-rect.Min.X, rect.Max.Y-rect.Min.Y, gl.RGBA, gl.UNSIGNED_BYTE, rgba.Pix)

Alternately

Image instances contains an At method that returns a Color. So it is possible to loop through each pixel and and collect colors. But converting returned rgba values from the Color might be complex. Quoting documentation:

    // 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.
    //
    // An alpha-premultiplied color component c has been scaled by alpha (a),
    // so has valid values 0 <= c <= a.  
查看更多
乱世女痞
4楼-- · 2020-08-15 01:33

You can simply use img.At(x, y).RGBA() to get the RBGA values for a pixel, you just need to divide them by 257 to get the 8 bit representation. I'd recommend building your own bi-dimensional array of pixels. Here's a possible implementation, modify it as needed:

package main

import (
    "fmt"
    "image"
    "image/png"
    "os"
    "io"
    "net/http"
)

func main() {
    // You can register another format here
    image.RegisterFormat("png", "png", png.Decode, png.DecodeConfig)

    file, err := os.Open("./image.png")

    if err != nil {
        fmt.Println("Error: File could not be opened")
        os.Exit(1)
    }

    defer file.Close()

    pixels, err := getPixels(file)

    if err != nil {
        fmt.Println("Error: Image could not be decoded")
        os.Exit(1)
    }

    fmt.Println(pixels)
}

// Get the bi-dimensional pixel array
func getPixels(file io.Reader) ([][]Pixel, error) {
    img, _, err := image.Decode(file)

    if err != nil {
        return nil, err
    }

    bounds := img.Bounds()
    width, height := bounds.Max.X, bounds.Max.Y

    var pixels [][]Pixel
    for y := 0; y < height; y++ {
        var row []Pixel
        for x := 0; x < width; x++ {
            row = append(row, rgbaToPixel(img.At(x, y).RGBA()))
        }
        pixels = append(pixels, row)
    }

    return pixels, nil
}

// img.At(x, y).RGBA() returns four uint32 values; we want a Pixel
func rgbaToPixel(r uint32, g uint32, b uint32, a uint32) Pixel {
    return Pixel{int(r / 257), int(g / 257), int(b / 257), int(a / 257)}
}

// Pixel struct example
type Pixel struct {
    R int
    G int
    B int
    A int
}
查看更多
登录 后发表回答