pixie/src/pixie/fileformats/bmp.nim
2020-12-04 08:17:03 -08:00

92 lines
3.1 KiB
Nim

import flatty/binny, chroma, pixie/common, pixie/images
# See: https://en.wikipedia.org/wiki/BMP_file_format
const bmpSignature* = "BM"
proc decodeBmp*(data: string): Image =
## Decodes bitmap data into an Image.
# BMP Header
if data[0 .. 1] != "BM":
raise newException(PixieError, "Invalid BMP data")
let
width = data.readInt32(18).int
height = data.readInt32(22).int
bits = data.readUint16(28).int
compression = data.readUint32(30).int
var
offset = data.readUInt32(10).int
if bits notin [32, 24]:
raise newException(PixieError, "Unsupported BMP data format")
if compression notin [0, 3]:
raise newException(PixieError, "Unsupported BMP data format")
let channels = if bits == 32: 4 else: 3
if width * height * channels + offset > data.len:
raise newException(PixieError, "Invalid BMP data size")
result = newImage(width, height)
for y in 0 ..< result.height:
for x in 0 ..< result.width:
var rgba: ColorRGBA
if bits == 32:
rgba.r = data.readUint8(offset + 0)
rgba.g = data.readUint8(offset + 1)
rgba.b = data.readUint8(offset + 2)
rgba.a = data.readUint8(offset + 3)
offset += 4
elif bits == 24:
rgba.r = data.readUint8(offset + 2)
rgba.g = data.readUint8(offset + 1)
rgba.b = data.readUint8(offset + 0)
rgba.a = 255
offset += 3
result[x, result.height - y - 1] = rgba
proc decodeBmp*(data: seq[uint8]): Image {.inline.} =
decodeBmp(cast[string](data))
proc encodeBmp*(image: Image): string =
## Encodes an image into the BMP file format.
# BMP Header
result.add("BM") # The header field used to identify the BMP
result.addUint32(0) # The size of the BMP file in bytes.
result.addUint16(0) # Reserved.
result.addUint16(0) # Reserved.
result.addUint32(122) # The offset to the pixel array.
# DIB Header
result.addUint32(108) # Size of this header
result.addInt32(image.width.int32) # Signed integer.
result.addInt32(image.height.int32) # Signed integer.
result.addUint16(1) # Must be 1 (color planes).
result.addUint16(32) # Bits per pixels, only support RGBA.
result.addUint32(3) # BI_BITFIELDS, no pixel array compression used
result.addUint32(32) # Size of the raw bitmap data (including padding)
result.addUint32(2835) # Print resolution of the image
result.addUint32(2835) # Print resolution of the image
result.addUint32(0) # Number of colors in the palette
result.addUint32(0) # 0 means all colors are important
result.addUint32(uint32(0x000000FF)) # Red channel.
result.addUint32(uint32(0x0000FF00)) # Green channel.
result.addUint32(uint32(0x00FF0000)) # Blue channel.
result.addUint32(uint32(0xFF000000)) # Alpha channel.
result.add("Win ") # little-endian.
for i in 0 ..< 48:
result.addUint8(0) # Unused
for y in 0 ..< image.height:
for x in 0 ..< image.width:
let rgba = image[x, image.height - y - 1]
result.addUint8(rgba.r)
result.addUint8(rgba.g)
result.addUint8(rgba.b)
result.addUint8(rgba.a)
result.writeUInt32(2, result.len.uint32)