# HG changeset patch # User Sam # Date 1683825210 -25200 # Node ID ecf2d75f06d8e68d715224aa9cdde0b92112ddc6 # Parent 22122b64f09fd4b598460cd48b551e444ffa6197 add: BMP loader diff -r 22122b64f09f -r ecf2d75f06d8 src/semicongine/core/imagetypes.nim --- a/src/semicongine/core/imagetypes.nim Thu May 11 00:30:44 2023 +0700 +++ b/src/semicongine/core/imagetypes.nim Fri May 12 00:13:30 2023 +0700 @@ -8,9 +8,10 @@ proc newImage*(width, height: uint32, imagedata: seq[Pixel] = @[]): Image = assert width > 0 and height > 0 + result = new Image + result.imagedata = (if imagedata.len == 0: newSeq[Pixel](width * height) else: imagedata) assert width * height == uint32(result.imagedata.len) - result = new Image result.width = width result.height = height diff -r 22122b64f09f -r ecf2d75f06d8 src/semicongine/resources/image.nim --- a/src/semicongine/resources/image.nim Thu May 11 00:30:44 2023 +0700 +++ b/src/semicongine/resources/image.nim Fri May 12 00:13:30 2023 +0700 @@ -1,2 +1,105 @@ +import std/streams +import std/bitops +import std/algorithm +import ../core/imagetypes +const COMPRESSION_BI_RGB = 0'u32 +const COMPRESSION_BI_BITFIELDS = 3'u32 +const COMPRESSION_BI_ALPHABITFIELDS = 6'u32 +type + BitmapFileHeader = object + magicbytes: array[2, char] + filesize: uint32 + reserved1: uint16 + reserved2: uint16 + dataStart: uint32 + DIBHeader = object + headersize: uint32 + width: int32 + height: int32 + colorPlanes: uint16 + bitsPerPixel: uint16 + compression: uint32 + imageDataSize: uint32 # unused + resolutionX: int32 # unused + resolutionY: int32 # unused + nColors: uint32 # unused + nImportantColors: uint32 # unused + bitMaskRed: uint32 + bitMaskGreen: uint32 + bitMaskBlue: uint32 + bitMaskAlpha: uint32 + colorSpace: array[4, char] # not used yet + colorSpaceEndpoints: array[36, uint8] # unused + gammaRed: uint32 # not used yet + gammaGreen: uint32 # not used yet + gammaBlue: uint32 # not used yet + +proc loadBitmap(stream: Stream): Image = + var + bitmapFileHeader: BitmapFileHeader + dibHeader: DIBHeader + + for name, value in fieldPairs(bitmapFileHeader): + stream.read(value) + for name, value in fieldPairs(dibHeader): + + when name in ["bitMaskRed", "bitMaskGreen", "bitMaskBlue"]: + if dibHeader.compression in [COMPRESSION_BI_BITFIELDS, COMPRESSION_BI_ALPHABITFIELDS]: + stream.read(value) + elif name == "bitMaskAlpha": + if dibHeader.compression == COMPRESSION_BI_ALPHABITFIELDS: + stream.read(value) + else: + stream.read(value) + + when name == "headersize": + if value != 124: + raise newException(Exception, "Cannot open image, only BITMAPV5 supported") + elif name == "colorPlanes": + assert value == 1 + elif name == "bitsPerPixel": + if not (value in [24'u16, 32'u16]): + raise newException(Exception, "Cannot open image, only depth of 24 and 32 supported") + elif name == "compression": + if not (value in [0'u32, 3'u32]): + raise newException(Exception, "Cannot open image, only BI_RGB and BI_BITFIELDS are supported compressions") + elif name == "colorSpace": + swap(value[0], value[3]) + swap(value[1], value[2]) + stream.setPosition(int(bitmapFileHeader.dataStart)) + var + padding = ((int32(dibHeader.bitsPerPixel div 8)) * dibHeader.width) mod 4 + data = newSeq[Pixel](dibHeader.width * abs(dibHeader.height)) + if padding > 0: + padding = 4 - padding + for row in 0 ..< abs(dibHeader.height): + for col in 0 ..< dibHeader.width: + + var pixel: Pixel = [0'u8, 0'u8, 0'u8, 255'u8] + # if we got channeld bitmasks + if dibHeader.compression in [COMPRESSION_BI_BITFIELDS, COMPRESSION_BI_ALPHABITFIELDS]: + var value = stream.readUint32() + pixel[0] = uint8((value and dibHeader.bitMaskRed) shr dibHeader.bitMaskRed.countTrailingZeroBits) + pixel[1] = uint8((value and dibHeader.bitMaskGreen) shr dibHeader.bitMaskGreen.countTrailingZeroBits) + pixel[2] = uint8((value and dibHeader.bitMaskBlue) shr dibHeader.bitMaskBlue.countTrailingZeroBits) + if dibHeader.compression == COMPRESSION_BI_ALPHABITFIELDS: + pixel[3] = uint8((value and dibHeader.bitMaskAlpha) shr dibHeader.bitMaskAlpha.countTrailingZeroBits) + # if we got plain RGB(A), using little endian + elif dibHeader.compression == COMPRESSION_BI_RGB: + let nChannels = int(dibHeader.bitsPerPixel) div 8 + for i in 1 .. nChannels: + stream.read(pixel[nChannels - i]) + else: + raise newException(Exception, "Cannot open image, only BI_RGB and BI_BITFIELDS are supported compressions") + + # determine whether we read top-to-bottom or bottom-to-top + var row_mult: int = (if dibHeader.height < 0: row else: dibHeader.height - row - 1) + data[row_mult * dibHeader.width + col]= pixel + stream.setPosition(stream.getPosition() + padding) + + result = newImage(width=uint32(dibHeader.width), height=uint32(abs(dibHeader.height)), imagedata=data) + +proc loadBitmap(file: string): Image = + loadBitmap(newFileStream(file, fmRead))