Using Go’s standard image library to load Cube Maps

Golang’s image library offers an impressive amount of functionalities for modifying images, but also in obtaining segments of an image for use in isolation. A cube map typically is a T-shaped image where each segment accounts for a part of the cube that can then be fed into a graphical pipeline for rendering 3D output as a skybox or as a refractored image projected onto an object.

To start, we’ll begin with a portion of code that involves loading an image into a buffer that is delimited by RGBA values:

img, _, err := image.Decode(imgFile)

if err != nil {
    panic(err)
}

rgba := image.NewNRGBA(img.Bounds())

if rgba.Stride != rgba.Rect.Size().X * 4 {
    panic(fmt.Errorf("unsupported stride"))
}

draw.Draw(rgba, rgba.Bounds(), img, image.Point{X: 0, Y: 0}, draw.Src)

Here, we decode image data from the contents of imgFile and construct an RGBA buffer that will hold all this data for later processing.

Through which we can now point to different segments of an image. Assuming an image like this:

img
From the top-left to the bottom-right, we can fathom there are twelve segments in this single image, including the blank segments. We’re interested in only the 2nd, 5th, 6th, 7th, 8th and 10th segments of this image to construct our cube map. Because each segment has an equal width and height, we can say that there are 3 segments in a single height; and 4 segments in a single width. Thus:

width := img.Rect.Size().X
height := img.Rect.Size().Y

wdiv := width / 4
hdiv := height / 3

We’ve now obtained an equally segmented width and height, which we can now use to tell Golang which segments we would like to grab to construct our cube map. Since each segment is a subimage, we will use the subImage function to obtain a new image based on the location it is in. Thus:

sub2 := img.SubImage(image.Rect(wdiv, 0, wdiv * 2, hdiv))
sub5 := img.SubImage(image.Rect(0, hdiv, wdiv, hdiv * 2))
sub6 := img.SubImage(image.Rect(wdiv, hdiv, wdiv * 2, hdiv * 2))
sub7 := img.SubImage(image.Rect(wdiv * 2, hdiv, wdiv * 3, hdiv * 2))
sub8 := img.SubImage(image.Rect(wdiv * 3, hdiv, wdiv * 4, hdiv * 2))
sub10 := img.SubImage(image.Rect(wdiv, hdiv * 2, wdiv * 2, hdiv * 3))

For each subimage, we are using a Rect to highlight which part of the image we want to use. For example, assuming that the T-shaped cube map image is 1280x1280 pixels, dividing the width by 4 and the height by 3 will yield 320x427 pixels. Taking the second segment as an example, we know it starts in (320, 0) and ends in (640, 427) because each segment is only 320x427, and since this is just one segment we’re only incrementing the image’s width by another 320 to compute one single width for that segment.

We also know that the height of each segment is 427 pixels, and because we’re in the first row, we’ll also add 427 to the final rectangle’s Y position to complete the rectangle that will surround the part of the image that we will use as our subimage. Indeed, it seems complicated because up until now we’ve only been applying image-segmentation optically through our eyes, rather than numerically which a computer can understand.

Each subimage returns a new image, that you can then add to a list in order to either write to a file with a unique name, or to be fed into the GPU for skybox or refactored image processing. Assuming the above segments are in a slice called images, you can then do something like so:

for i, img := range images {
gl.TexImage2D(gl.TEXTURE_CUBE_MAP_POSITIVE_X + uint32(i), 0, 
    gl.RGBA, int32(img.Image.Rect.Size().X), int32(image.Rect.Size().Y),0, 
        gl.RGBA, gl.UNSIGNED_BYTE, gl.Ptr(img.Image.Pix))
}

With the correct shaders, you should then be able to render the cube map as you see fit.