~/Building a BMP Encoder in Golang

Sep 17, 2019


To build a BMP image encoder in Go, you need to follow the BMP file format specification that details header sizes and pixel arrangements.

A BMP file starts with a 14 byte file header, a 40 byte DIB header, and then pixel data. BMP often stores pixel data in bottom-up order, with each row padded to a multiple of 4 bytes.

Below is a minimal BMP encoder for 24bpp (RGB, no alpha), suitable for small images. For larger or more robust usage, use the image and image/png packages as references.

Code:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
package main

import (
	"encoding/binary"
	"os"
)

func WriteBMP24(width, height int, pixels []byte, filename string) error {
	rowSize := ((24*width + 31) / 32) * 4
	imgSize := rowSize * height
	fileSize := 14 + 40 + imgSize

	f, err := os.Create(filename)
	if err != nil {
		return err
	}
	defer f.Close()

	// BITMAPFILEHEADER
	f.Write([]byte{'B', 'M'})
	binary.Write(f, binary.LittleEndian, uint32(fileSize))
	binary.Write(f, binary.LittleEndian, uint16(0))
	binary.Write(f, binary.LittleEndian, uint16(0))
	binary.Write(f, binary.LittleEndian, uint32(54)) // offset

	// BITMAPINFOHEADER
	binary.Write(f, binary.LittleEndian, uint32(40)) // DIB header size
	binary.Write(f, binary.LittleEndian, int32(width))
	binary.Write(f, binary.LittleEndian, int32(height))
	binary.Write(f, binary.LittleEndian, uint16(1)) // planes
	binary.Write(f, binary.LittleEndian, uint16(24)) // bits per pixel
	binary.Write(f, binary.LittleEndian, uint32(0))  // compression
	binary.Write(f, binary.LittleEndian, uint32(imgSize))
	binary.Write(f, binary.LittleEndian, int32(0)) // xppm
	binary.Write(f, binary.LittleEndian, int32(0)) // yppm
	binary.Write(f, binary.LittleEndian, uint32(0)) // clr used
	binary.Write(f, binary.LittleEndian, uint32(0)) // clr important

	// Pixel data
	padding := make([]byte, rowSize-width*3)
	// BMP stores bottom to top
	for y := height - 1; y >= 0; y-- {
		row := pixels[y*width*3 : (y+1)*width*3]
		f.Write(row)
		f.Write(padding)
	}
	return nil
}

Use like this:

1
2
3
pixels := make([]byte, 3*2*2) // 2x2 image, RGB
// fill pixel data in BGR order per BMP spec
WriteBMP24(2, 2, pixels, "test.bmp")

For real images, fill pixels so every 3 bytes represent BGR for one pixel. Learn more from Wikipedia BMP file format. For more details see Golang binary encoding.

Tags: [golang] [bmp] [image]