The Image Interface

The Image interface has three elements:

  • The color model
  • the dimensions
  • the color at each pixel

We can't create an Image itself, we can only create things that satisfy the Image interface. The actual struct that implements the Image interface is the RGBA type. Image is an interface that defines the minimum requirements for structs to have. We can use the RGBA anywhere an Image interface is accepted.

// This is just an abstract interface. We can't actually instantiate an image
// directly. We can only use or implement types that implement these functions
// and contain these data elements. By doing so we satisfy the interface,
// and we can use that type anywhere an Image interface is passed
type Image interface {
    // ColorModel returns the Image's color model.
    ColorModel() color.Model
 
    // Bounds returns the domain for which At can return non-zero color.
    // The bounds do not necessarily contain the point (0, 0).
    Bounds() Rectangle
 
    // At returns the color of the pixel at (x, y).
    // At(Bounds().Min.X, Bounds().Min.Y) returns the upper-left pixel of the grid.
    // At(Bounds().Max.X-1, Bounds().Max.Y-1) returns the lower-right one.
    At(x, y int) color.Color
}
 
// Here is the source for sub types:
 
//  Each one of those elements has its own details though.  Here they are.
type Model interface {
    // Model can convert any Color to one from its
    // own color model. The conversion may be lossy.
    Convert(c Color) Color
}
 
// Stores two (x,y) coordinates called Points
// Those points mark the two corners of the rectangle
type Rectangle struct {
    Min, Max Point
}
 
// Color stores an RGB value and an Alpha(transparency)
type Color interface {
    // 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.
    RGBA() (r, g, b, a uint32)
}

Generating an Image

package main
import (
    "fmt"
    "image"
)
func main() {
    // Create a blank image 10 pixels wide by 4 pixels tall
    myImage := image.NewRGBA(image.Rect(0, 0, 10, 4))
 
    // You can access the pixels through myImage.Pix[i]
    // One pixel takes up four bytes/uint8. One for each: RGBA
    // So the first pixel is controlled by the first 4 elements
    // Values for color are 0 black - 255 full color
    // Alpha value is 0 transparent - 255 opaque
    myImage.Pix[0] = 255 // 1st pixel red
    myImage.Pix[1] = 0 // 1st pixel green
    myImage.Pix[2] = 0 // 1st pixel blue
    myImage.Pix[3] = 255 // 1st pixel alpha
 
    // myImage.Pix contains all the pixels
    // in a one-dimensional slice
    fmt.Println(myImage.Pix)
 
    // Stride is how many bytes take up 1 row of the image
    // Since 4 bytes are used for each pixel, the stride is
    // equal to 4 times the width of the image
    // Since all the pixels are stored in a 1D slice,
    // we need this to calculate where pixels are on different rows.
    fmt.Println(myImage.Stride) // 40 for an image 10 pixels wide
}
</code >
 
==== Writing Image to File ====
The Encode() function accepts a writer so you could write it to any writer interface. That includes files, stdout, tcp sockets, or any custom one.
 
<code go>
package main
 
import (
    "image"
    "image/png"
    "os"
)
 
func main() {
    // Create a blank image 100x200 pixels
    myImage := image.NewRGBA(image.Rect(0, 0, 100, 200))
 
    // outputFile is a File type which satisfies Writer interface
    outputFile, err := os.Create("test.png")
    if err != nil {
        // Handle error
    }
 
    // Encode takes a writer interface and an image interface
    // We pass it the File and the RGBA
    png.Encode(outputFile, myImage)
 
    outputFile.Close()
}

Reading Image From File

package main
import (
    "fmt"
    "image"
    "image/png"
    "os"
)
func main() {
    // Read image from file that already exists
    existingImageFile, err := os.Open("test.png")
    if err != nil {
        // Handle error
    }
    defer existingImageFile.Close()
    // Calling the generic image.Decode() will tell give us the data
    // and type of image it is as a string. We expect "png"
    imageData, imageType, err := image.Decode(existingImageFile)
    if err != nil {
        // Handle error
    }
    fmt.Println(imageData)
    fmt.Println(imageType)
    // We only need this because we already read from the file
    // We have to reset the file pointer back to beginning
    existingImageFile.Seek(0, 0)
    // Alternatively, since we know it is a png already
    // we can call png.Decode() directly
    loadedImage, err := png.Decode(existingImageFile)
    if err != nil {
        // Handle error
    }
    fmt.Println(loadedImage)
}
</code >
 
==== Base64 Encoding Image ====
Instead of writing the image data to a file, we could base64 encode it and store it as a string.
 
<code go>
package main
import (
    "bytes"
    "encoding/base64"
    "fmt"
    "image"
    "image/png"
)
func main() {
    // Create a blank image 10x20 pixels
    myImage := image.NewRGBA(image.Rect(0, 0, 10, 20))
    // In-memory buffer to store PNG image
    // before we base 64 encode it
    var buff bytes.Buffer
    // The Buffer satisfies the Writer interface so we can use it with Encode
    // In previous example we encoded to a file, this time to a temp buffer
    png.Encode(&buff, myImage)
    // Encode the bytes in the buffer to a base64 string
    encodedString := base64.StdEncoding.EncodeToString(buff.Bytes())
    // You can embed it in an html doc with this string
    htmlImage := "<img src=\"data:image/png;base64," + encodedString + "\" />"
    fmt.Println(htmlImage)
}