The Startup
Published in

The Startup

Writing an Image Manipulation Library in Go — Part 1

Part 1 — Utility Functions

I have been interested in image manipulation for some time. Last year I worked on a small package that does simple image manipulation using Go’s standard image package: github.com/KorayGocmen/image

Let’s look at how to read an image from a provided file path and creating an image object. This is what the structs will look like.

// GrayscaleAverage, GrayscaleLuma, GrayscaleDesaturation
// are used by grayscale to choose between algorithms
const (
GrayscaleAverage = 0
GrayscaleLuma = 1
GrayscaleDesaturation = 2
)

// Pixel is a single pixel in 2d array
type Pixel struct {
R int
G int
B int
A int
}

// Image is the main object that holds information about the
// image file. Also is a wrapper around the decoded image
// from the standard image library.
type Image struct {
Pixels [][]Pixel
Width int
Height int
_Rect image.Rectangle
_Image image.Image
}

An image is basically a matrix of pixels. There are different image encoding techniques. I am using RGBA colour space, it stands for Red, Green, Blue and Alpha. Red, Green, Blue is pretty self-explanatory and Alpha is basically opacity of the pixel.

I am going to need 3 helper functions to quickly get/set a certain pixel and to transform the decoded pixel to my pixel format. This is how they are going to look:

// Get pixel value with key name
func (pix *Pixel) Get(keyName string) int {
switch keyName {
case "R":
return pix.R
case "G":
return pix.G
case "B":
return pix.B
case "A":
return pix.A
default:
return -1
}
}

// Set pixel value with key name and new value
func (pix *Pixel) Set(keyName string, val int) Pixel {
switch keyName {
case "R":
pix.R = val
case "G":
pix.G = val
case "B":
pix.B = val
case "A":
pix.A = val
}
return *pix
}

// rgbaToPixel alpha-premultiplied red, green, blue and alpha values
// to 8 bit red, green, blue and alpha values.
func rgbaToPixel(r uint32, g uint32, b uint32, a uint32) Pixel {
return Pixel{
R: int(r / 257),
G: int(g / 257),
B: int(b / 257),
A: int(a / 257),
}
}

`rgbaToPixel` function converts the pixels image package returns into pixels I defined. The RGBA values in standard package are uint32 type, I preferred int type. Therefore I need to divide the values by 257 and cast them as int.

Now, I am ready to read the image from a provided `filepath` and create my image object. This code can decode “jpeg”/”jpg” and “png” formats. I am going to keep the native image packages “img” object for future reference under my own image object via the key “_Image”. This function will return the image object or maybe an error.

// New reads an image from the given file path and return a
// new `Image` struct.
func New(filePath string) (*Image, error) {
s := strings.Split(filePath, ".")
imgType := s[len(s)-1]

switch imgType {
case "jpeg", "jpg":
image.RegisterFormat("jpeg", "jpeg", jpeg.Decode, jpeg.DecodeConfig)
case "png":
image.RegisterFormat("png", "png", png.Decode, png.DecodeConfig)
default:
return nil, errors.New("unknown image type")
}

imgReader, err := os.Open(filePath)
if err != nil {
return nil, err
}

img, _, err := image.Decode(imgReader)
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++ {
pixel := rgbaToPixel(img.At(x, y).RGBA())
row = append(row, pixel)
}
pixels = append(pixels, row)
}

return &Image{
Pixels: pixels,
Width: width,
Height: height,
_Rect: img.Bounds(),
_Image: img,
}, nil
}

Now I can read an image from a file but we also need to rewrite our manipulated image back to a file. This is pretty much doing everything I did in the `New` function in reverse as one might have guessed.

// WriteToFile writes iamges to the given filepath.
// Returns an error if it occurs.
func (img *Image) WriteToFile(outputPath string) error {
cimg := image.NewRGBA(img._Rect)
draw.Draw(cimg, img._Rect, img._Image, image.Point{}, draw.Over)

for y := 0; y < img.Height; y++ {
for x := 0; x < img.Width; x++ {
rowIndex, colIndex := y, x
pixel := img.Pixels[rowIndex][colIndex]
cimg.Set(x, y, color.RGBA{
uint8(pixel.R),
uint8(pixel.G),
uint8(pixel.B),
uint8(pixel.A),
})
}
}

s := strings.Split(outputPath, ".")
imgType := s[len(s)-1]

switch imgType {
case "jpeg", "jpg", "png":
fd, err := os.Create(outputPath)
if err != nil {
return err
}

switch imgType {
case "jpeg", "jpg":
jpeg.Encode(fd, cimg, nil)
case "png":
png.Encode(fd, cimg)
}
default:
return errors.New("unknown image type")
}

return nil
}

Alright, I have the utility functions ready, now it is time for the fun part.

--

--

--

Get smarter at building your thing. Follow to join The Startup’s +8 million monthly readers & +756K followers.

Recommended from Medium

Why we decided to launch Iraq’s first ever Summer Tech Fest

Couple of tips for K8s operator beginner developer

Things I Learned After 2 years of Building Software

Revisiting Algorithms with Khan Academy

A short & practical HOW-TO guide to scrape data from a website using Python

How to fix Valorant (Vanguard) disabling keyboard/mouse

Uncertainty in Elm — Part I

Solve 90% of Google Pagespeed Insights Issues in 30 Minutes

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store
Koray Göçmen

Koray Göçmen

University of Toronto, Computer Engineering, architected and implemented reliable infrastructures and worked as the lead developer for multiple startups.

More from Medium

Building a Simple REST API in Go With Gorilla/Mux

Build a REST API with Golang and MongoDB — Gin-gonic Version

The Proper Way To Start A Node Project

User Creation And Authentication in Golang Part 1