This commit is contained in:
Ryan Oldenburg 2022-02-12 19:03:34 -06:00
parent ccccd883bb
commit 97515a3849

View file

@ -5,6 +5,9 @@ import bitops, chroma, flatty/binny, pixie/common, pixie/images
const bmpSignature* = "BM" const bmpSignature* = "BM"
template failInvalid() =
raise newException(PixieError, "Invalid BMP buffer, unable to load")
proc colorMaskShift(color: uint32, mask: uint32): uint8 = proc colorMaskShift(color: uint32, mask: uint32): uint8 =
((color and mask) shr (mask.firstSetBit() - 1)).uint8 ((color and mask) shr (mask.firstSetBit() - 1)).uint8
@ -12,11 +15,11 @@ proc decodeBmp*(data: string): Image {.raises: [PixieError].} =
## Decodes bitmap data into an Image. ## Decodes bitmap data into an Image.
if data.len < 48: if data.len < 48:
raise newException(PixieError, "Invalid BMP data") failInvalid()
# BMP Header # BMP Header
if data[0 .. 1] != "BM": if data[0 .. 1] != "BM":
raise newException(PixieError, "Invalid BMP data") failInvalid()
let let
bits = data.readUint16(28).int bits = data.readUint16(28).int
@ -36,16 +39,16 @@ proc decodeBmp*(data: string): Image {.raises: [PixieError].} =
flipVertical = false flipVertical = false
if numColors < 0 or numColors > 256: if numColors < 0 or numColors > 256:
raise newException(PixieError, "Invalid number of colors") failInvalid()
if dibHeader notin [40, 108]: if dibHeader notin [40, 108]:
raise newException(PixieError, "Invalid BMP data") failInvalid()
var var
colorTable = newSeq[ColorRGBA](numColors) colorTable = newSeq[ColorRGBA](numColors)
if dibHeader == 108: if dibHeader == 108:
if data.len < 14 + dibHeader: if data.len < 14 + dibHeader:
raise newException(PixieError, "Invalid BMP data") failInvalid()
redChannel = data.readUInt32(54) redChannel = data.readUInt32(54)
greenChannel = data.readUInt32(58) greenChannel = data.readUInt32(58)
@ -59,13 +62,13 @@ proc decodeBmp*(data: string): Image {.raises: [PixieError].} =
if numColors > 0: if numColors > 0:
if data.len < 14 + dibHeader + numColors * 4: if data.len < 14 + dibHeader + numColors * 4:
raise newException(PixieError, "Invalid BMP data") failInvalid()
var colorOffset = dibHeader + 14 var colorOffset = dibHeader + 14
for i in 0 ..< numColors: for i in 0 ..< numColors:
var rgba: ColorRGBA var rgba: ColorRGBA
if colorOffset + 3 > data.len - 2: if colorOffset + 3 > data.len - 2:
raise newException(PixieError, "Truncated BMP data") failInvalid()
rgba.r = data.readUint8(colorOffset + 2) rgba.r = data.readUint8(colorOffset + 2)
rgba.g = data.readUint8(colorOffset + 1) rgba.g = data.readUint8(colorOffset + 1)
rgba.b = data.readUint8(colorOffset + 0) rgba.b = data.readUint8(colorOffset + 0)
@ -75,7 +78,7 @@ proc decodeBmp*(data: string): Image {.raises: [PixieError].} =
if redChannel == 0 or greenChannel == 0 or if redChannel == 0 or greenChannel == 0 or
blueChannel == 0 or alphaChannel == 0: blueChannel == 0 or alphaChannel == 0:
raise newException(PixieError, "Unsupported 0 channel mask.") failInvalid()
if bits notin [1, 4, 8, 32, 24]: if bits notin [1, 4, 8, 32, 24]:
raise newException(PixieError, "Unsupported BMP data format") raise newException(PixieError, "Unsupported BMP data format")
@ -104,7 +107,7 @@ proc decodeBmp*(data: string): Image {.raises: [PixieError].} =
var rgba: ColorRGBA var rgba: ColorRGBA
if haveBits == 0: if haveBits == 0:
if offset >= data.len: if offset >= data.len:
raise newException(PixieError, "Truncated BMP data") failInvalid()
colorBits = data.readUint8(offset) colorBits = data.readUint8(offset)
haveBits = 8 haveBits = 8
offset += 1 offset += 1
@ -130,13 +133,13 @@ proc decodeBmp*(data: string): Image {.raises: [PixieError].} =
var rgba: ColorRGBA var rgba: ColorRGBA
if haveBits == 0: if haveBits == 0:
if offset >= data.len: if offset >= data.len:
raise newException(PixieError, "Truncated BMP data") failInvalid()
colorBits = data.readUint8(offset) colorBits = data.readUint8(offset)
haveBits = 8 haveBits = 8
offset += 1 offset += 1
let index = (colorBits and 0b1111_0000) shr 4 let index = (colorBits and 0b1111_0000) shr 4
if index.int >= numColors: if index.int >= numColors:
raise newException(PixieError, "Invalid BMP index") failInvalid()
rgba = colorTable[index] rgba = colorTable[index]
colorBits = colorBits shl 4 colorBits = colorBits shl 4
haveBits -= 4 haveBits -= 4
@ -150,12 +153,12 @@ proc decodeBmp*(data: string): Image {.raises: [PixieError].} =
offset += 4 - padding offset += 4 - padding
for x in 0 ..< result.width: for x in 0 ..< result.width:
if offset >= data.len: if offset >= data.len:
raise newException(PixieError, "Truncated BMP data") failInvalid()
var rgba: ColorRGBA var rgba: ColorRGBA
let index = data.readUint8(offset) let index = data.readUint8(offset)
offset += 1 offset += 1
if index.int >= numColors: if index.int >= numColors:
raise newException(PixieError, "Invalid BMP index") failInvalid()
rgba = colorTable[index] rgba = colorTable[index]
result[x, result.height - y - 1] = rgba.rgbx() result[x, result.height - y - 1] = rgba.rgbx()
@ -167,7 +170,7 @@ proc decodeBmp*(data: string): Image {.raises: [PixieError].} =
offset += 4 - padding offset += 4 - padding
for x in 0 ..< result.width: for x in 0 ..< result.width:
if offset + 2 >= data.len: if offset + 2 >= data.len:
raise newException(PixieError, "Truncated BMP data") failInvalid()
var rgba: ColorRGBA var rgba: ColorRGBA
rgba.r = data.readUint8(offset + 2) rgba.r = data.readUint8(offset + 2)
rgba.g = data.readUint8(offset + 1) rgba.g = data.readUint8(offset + 1)
@ -180,7 +183,7 @@ proc decodeBmp*(data: string): Image {.raises: [PixieError].} =
for y in 0 ..< result.height: for y in 0 ..< result.height:
for x in 0 ..< result.width: for x in 0 ..< result.width:
if offset + 3 >= data.len: if offset + 3 >= data.len:
raise newException(PixieError, "Truncated BMP data") failInvalid()
var rgba: ColorRGBA var rgba: ColorRGBA
let color = data.readUint32(offset) let color = data.readUint32(offset)
rgba.r = color.colorMaskShift(redChannel) rgba.r = color.colorMaskShift(redChannel)