Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
70 changes: 37 additions & 33 deletions src/pixie/fileformats/bmp.nim
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,9 @@ import bitops, chroma, flatty/binny, pixie/common, pixie/images
# https://stackoverflow.com/questions/61788908/windows-clipboard-getclipboarddata-for-cf-dibv5-causes-the-image-on-the-clip
# https://stackoverflow.com/questions/44177115/copying-from-and-to-clipboard-loses-image-transparency/46424800#46424800

const bmpSignature* = "BM"
const
bmpSignature* = "BM"
LCS_sRGB = 0x73524742

template failInvalid() =
raise newException(PixieError, "Invalid BMP buffer, unable to load")
Expand All @@ -17,8 +19,7 @@ proc colorMaskShift(color, mask: uint32): uint8 {.inline.} =
proc decodeDib*(
data: pointer, len: int, lpBitmapInfo = false
): Image {.raises: [PixieError].} =
## Decodes DIB data into an Image.

## Decodes DIB data into an image.
if len < 40:
failInvalid()

Expand Down Expand Up @@ -216,8 +217,7 @@ proc decodeDib*(
result.flipVertical()

proc decodeBmp*(data: string): Image {.raises: [PixieError].} =
## Decodes bitmap data into an Image.

## Decodes bitmap data into an image.
if data.len < 14:
failInvalid()

Expand All @@ -231,7 +231,6 @@ proc decodeBmpDimensions*(
data: string
): ImageDimensions {.raises: [PixieError].} =
## Decodes the BMP dimensions.

if data.len < 26:
failInvalid()

Expand All @@ -242,42 +241,47 @@ proc decodeBmpDimensions*(
result.width = data.readInt32(18).int
result.height = abs(data.readInt32(22)).int

proc encodeBmp*(image: Image): string {.raises: [].} =
## Encodes an image into the BMP file format.
proc encodeDib*(image: Image): string {.raises: [].} =
## Encodes an image into a DIB.

# 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.
# BITMAPINFO containing BITMAPV5HEADER
result.addUint32(124) # 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
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.addUint32(LCS_sRGB) # Color space
result.setLen(result.len + 64) # Unused
result.addUint32(0) # BITMAPINFO bmiColors 0
result.addUint32(0) # BITMAPINFO bmiColors 1
result.addUint32(0) # BITMAPINFO bmiColors 2

for y in 0 ..< image.height:
for x in 0 ..< image.width:
let rgba = image[x, image.height - y - 1].rgba()
result.addUint8(rgba.r)
result.addUint8(rgba.g)
result.addUint8(rgba.b)
result.addUint8(rgba.a)
result.addUint32(cast[uint32](rgba))

proc encodeBmp*(image: Image): string {.raises: [].} =
## 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(14 + 12 + 124) # The offset to the pixel array

# DIB
result.add(encodeDib(image))

result.writeUInt32(2, result.len.uint32)
result.writeUint32(2, result.len.uint32)
Binary file modified tests/fileformats/bmp/knight.24.bmp
Binary file not shown.
Binary file modified tests/fileformats/bmp/knight.32.bmp
Binary file not shown.
Binary file modified tests/fileformats/bmp/rgb.24.bmp
Binary file not shown.
12 changes: 11 additions & 1 deletion tests/test_bmp.nim
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import os, pixie/fileformats/bmp
import os, pixie, pixie/fileformats/bmp

# block:
# var image = newImage(4, 2)
Expand Down Expand Up @@ -52,3 +52,13 @@ block:
#image.writeFile(file.replace("bmpsuite", "output") & ".png")
doAssert image.width == dimensions.width
doAssert image.height == dimensions.height

block:
let image = newImage(100, 100)
image.fill(color(1, 0, 0, 1))

let
encoded = encodeDib(image)
decoded = decodeDib(encoded.cstring, encoded.len, true)

doAssert image.data == decoded.data