3

I can't gracefully get pixels of an image as array in general case.

f, err := os.Open(imgPath)
check(err)
defer f.Close()
img, _, err := image.Decode(bufio.NewReader(f))
check(err)
pixels, err := getPixels(img)
check(err)
// Logic with pixels.

Now function getPixels looks like this:

func getPixels(img image.Image) ([]uint8, error) {
    if i, ok := img.(*image.NRGBA); ok {
        return i.Pix, nil
    } else if i, ok := img.(*image.Alpha); ok {
        return i.Pix, nil
    } else if i, ok := img.(*image.Alpha16); ok {
        return i.Pix, nil
    } else if i, ok := img.(*image.CMYK); ok {
        return i.Pix, nil
    } else if i, ok := img.(*image.Gray); ok {
        return i.Pix, nil
    } else if i, ok := img.(*image.Gray16); ok {
        return i.Pix, nil
    } else if i, ok := img.(*image.NRGBA64); ok {
        return i.Pix, nil
    } else if i, ok := img.(*image.Paletted); ok {
        return i.Pix, nil
    } else if i, ok := img.(*image.RGBA); ok {
        return i.Pix, nil
    } else if i, ok := img.(*image.RGBA64); ok {
        return i.Pix, nil
    }
    return nil, fmt.Errorf("unknown image type %T", img)
}

But I think this is ugly. Golang knows type of image and I would prefer something like this:

func getPixels(img image.Image) ([]uint8, error) {
    if i, ok := img.(eval(fmt.Sprintf("%T", img))); ok {
        return i.Pix, nil
    }
    return nil, fmt.Errorf("unknown image type %T", img)
}

I also can't assert to reflect.TypeOf(img). Maybe there is a way to get type from reflect.Type interface?

Andrey
  • 1,495
  • 17
  • 14
  • 1
    What kind of processing are you trying to do with the `Pix` field? I'm curious because though the `Pix` field for different image types is of type `[]uint8`, the actual contents of the slice is not consistent. For example, the `CMYK` image type stores values for C, M, Y and K one after the other in the slice for each pixel, but `Alpha` stores just one value for each pixel. That's why the `image.Image` interface has an `At()` method returning a `color.Color` value for any given pixel. – svsd Sep 04 '18 at 12:30
  • Then I calculate average color. Getting color of every pixel with `img.At(x, y).RGBA()` is slower than processing `[]uint8`: ~530ms vs ~315ms on an image with resolution 3840x1200. I handle a lot if images so I have decided to use img.Pix. But Python with library "pillow" is faster: ~215ms :). – Andrey Sep 04 '18 at 12:47
  • Do you need that performance gain? If yes, are you sure that you're getting the right results without taking into account the image type? Especially for image types that have multiple components (eg. CMYK, NRGBA64 etc) where I assume you need to compute the average for each component. – svsd Sep 04 '18 at 12:52
  • I could do it without optimization, but why not for fun. Images are png, jpeg or tiff, colorful (4 channels) or grey (1 channel). Results checked with the help of imagemagick. – Andrey Sep 04 '18 at 12:59

1 Answers1

5

Your big if ... else structure could be simplified by using a type switch like this:

func getPixels(img image.Image) ([]uint8, error) {
    switch i := img.(type) {
    case *image.NRGBA:
        return i.Pix, nil
    case *image.Alpha:
        return i.Pix, nil
    case *image.Alpha16:
        return i.Pix, nil
    case *image.CMYK:
        return i.Pix, nil
        // ...
    }
    return nil, fmt.Errorf("unknown image type %T", img)
}

Where you still have to list all possible types, but it's nicer.

Since all image implementations are struct pointers having a field named Pix, you may use reflection to get that field. This implementation will handle future image implementations without any change (if they will also be structs with a Pix field).

This is how it would look like:

func getPix(img image.Image) ([]uint8, error) {
    v := reflect.ValueOf(img)
    if v.Kind() == reflect.Ptr {
        v = v.Elem()
    }

    if v.Kind() == reflect.Struct {
        pv := v.FieldByName("Pix")
        if pv.IsValid() {
            if pix, ok := pv.Interface().([]uint8); ok {
                return pix, nil
            }
        }
    }

    return nil, fmt.Errorf("unknown image type %T", img)
}

Testing it:

fmt.Println(getPix(&image.NRGBA{}))
fmt.Println(getPix(&image.RGBA{}))

type unknownImage struct{ image.Image }
fmt.Println(getPix(unknownImage{}))

Output (try it on the Go Playground):

[] <nil>
[] <nil>
[] unknown image type main.unknownImage
icza
  • 389,944
  • 63
  • 907
  • 827
  • Yes, `switch` slipped from my mind. Switch looks a bit nicer. But is it possible in golang to assert interface to its type ("%T")? In general case there may be tens of different types. – Andrey Sep 04 '18 at 11:02
  • 1
    @Andrey Go is a statically typed language. You can only type-assert to a concrete type or another interface type, but the type must be known at compile-time. Use the 2nd approach with reflection. – icza Sep 04 '18 at 11:13