diff --git a/src/pixie/fileformats/bmp.nim b/src/pixie/fileformats/bmp.nim new file mode 100644 index 0000000..793ecdf --- /dev/null +++ b/src/pixie/fileformats/bmp.nim @@ -0,0 +1,67 @@ +import ../images, flatty/binny, flatty/hexPrint, chroma, sequtils + +# See: https://en.wikipedia.org/wiki/BMP_file_format + +proc decodeBmp*(data: string): Image = + ## Decodes bitmap data into an Image. + + # BMP Header + doAssert data[0..1] == "BM" + let + width = data.readInt32(0x12).int + height = data.readInt32(0x16).int + # TODO: Handle masks. + var + offset = data.readUInt32(0xA).int + + result = newImage(width, height) + + for y in 0 ..< result.height: + for x in 0 ..< result.width: + var rgba: ColorRGBA + rgba.r = data.readUint8(offset+0) + rgba.g = data.readUint8(offset+1) + rgba.b = data.readUint8(offset+2) + rgba.a = data.readUint8(offset+3) + result[x, result.height - y - 1] = rgba + offset += 4 + +proc encodeBmp*(image: Image): string = + ## Encodes an image into bitmap data. + + # 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) diff --git a/src/pixie/images.nim b/src/pixie/images.nim index 225a57e..64d04e6 100644 --- a/src/pixie/images.nim +++ b/src/pixie/images.nim @@ -296,4 +296,8 @@ proc draw*( ## Thoughts ## single draw function that takes a matrix ## if matrix is simple integer translation -> fast pass -## if blend mode is copy -> even faster path +## if matrix is a simple flip -> fast path +## if blend mode is copy -> fast path +## +## Helper function that takes x,y +## Helper function that takes x,y and rotation. diff --git a/tests/images/bmp/test16x16.bmp b/tests/images/bmp/test16x16.bmp new file mode 100644 index 0000000..807a2d9 Binary files /dev/null and b/tests/images/bmp/test16x16.bmp differ diff --git a/tests/images/bmp/test4x2.bmp b/tests/images/bmp/test4x2.bmp new file mode 100644 index 0000000..354799b Binary files /dev/null and b/tests/images/bmp/test4x2.bmp differ diff --git a/tests/testbmp.nim b/tests/testbmp.nim new file mode 100644 index 0000000..0c02aaa --- /dev/null +++ b/tests/testbmp.nim @@ -0,0 +1,31 @@ +import pixie, pixie/fileformats/bmp, chroma, flatty/hexPrint + +block: + var image = newImage(4, 2) + + image[0, 0] = rgba(0, 0, 255, 255) + image[1, 0] = rgba(0, 255, 0, 255) + image[2, 0] = rgba(255, 0, 0, 255) + image[3, 0] = rgba(255, 255, 255, 255) + + image[0, 1] = rgba(0, 0, 255, 127) + image[1, 1] = rgba(0, 255, 0, 127) + image[2, 1] = rgba(255, 0, 0, 127) + image[3, 1] = rgba(255, 255, 255, 127) + + writeFile("images/bmp/test4x2.bmp", encodeBmp(image)) + + var image2 = decodeBmp(encodeBmp(image)) + doAssert image2.width == image.width + doAssert image2.height == image.height + doAssert image2.data == image.data + +block: + var image = newImage(16, 16) + image.fill(rgba(255, 0, 0, 127)) + writeFile("images/bmp/test16x16.bmp", encodeBmp(image)) + + var image2 = decodeBmp(encodeBmp(image)) + doAssert image2.width == image.width + doAssert image2.height == image.height + doAssert image2.data == image.data