Mercurial > games > semicongine
diff semiconginev2/old/resources/image.nim @ 1218:56781cc0fc7c compiletime-tests
did: renamge main package
author | sam <sam@basx.dev> |
---|---|
date | Wed, 17 Jul 2024 21:01:37 +0700 |
parents | semicongine/old/resources/image.nim@a3eb305bcac2 |
children |
line wrap: on
line diff
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/semiconginev2/old/resources/image.nim Wed Jul 17 21:01:37 2024 +0700 @@ -0,0 +1,163 @@ +import std/os +# import std/syncio +import std/streams +import std/bitops +import std/strformat + +import ../core/imagetypes +import ../core/utils + +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 ReadBMP*(stream: Stream): Image[RGBAPixel] = + var + bitmapFileHeader: BitmapFileHeader + dibHeader: DIBHeader + + for name, value in fieldPairs(bitmapFileHeader): + stream.read(value) + if bitmapFileHeader.magicbytes != ['B', 'M']: + raise newException(Exception, "Cannot open image, invalid magic bytes (is this really a BMP bitmap?)") + 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[RGBAPixel](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: RGBAPixel = [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 = dibHeader.width.uint32, height = abs(dibHeader.height).uint32, imagedata = data) + +{.compile: currentSourcePath.parentDir() & "/lodepng.c".} + +proc lodepng_decode32(out_data: ptr cstring, w: ptr cuint, h: ptr cuint, in_data: cstring, insize: csize_t): cuint {.importc.} +proc lodepng_encode_memory(out_data: ptr cstring, outsize: ptr csize_t, image: cstring, w: cuint, h: cuint, colorType: cint, bitdepth: cuint): cuint {.importc.} + +proc free(p: pointer) {.importc.} # for some reason the lodepng pointer can only properly be freed with the native free + +proc ReadPNG*(stream: Stream): Image[RGBAPixel] = + let indata = stream.readAll() + var w, h: cuint + var data: cstring + + if lodepng_decode32(out_data = addr data, w = addr w, h = addr h, in_data = cstring(indata), insize = csize_t(indata.len)) != 0: + raise newException(Exception, "An error occured while loading PNG file") + + let imagesize = w * h * 4 + var imagedata = newSeq[RGBAPixel](w * h) + copyMem(addr imagedata[0], data, imagesize) + + free(data) + + result = NewImage(width = w, height = h, imagedata = imagedata) + +proc ToPNG*[T: Pixel](image: Image[T]): seq[uint8] = + when T is GrayPixel: + let pngType = 0 # hardcoded in lodepng.h + else: + let pngType = 6 # hardcoded in lodepng.h + var + pngData: cstring + pngSize: csize_t + for y in 0 ..< image.height: + for x in 0 ..< image.width: + discard + let ret = lodepng_encode_memory( + addr pngData, + addr pngSize, + cast[cstring](image.imagedata.ToCPointer), + cuint(image.width), + cuint(image.height), + cint(pngType), + 8, + ) + assert ret == 0, &"There was an error with generating the PNG data for image {image}, result was: {ret}" + result = newSeq[uint8](pngSize) + for i in 0 ..< pngSize: + result[i] = uint8(pngData[i]) + free(pngData) + +proc WritePNG*[T: Pixel](image: Image[T], filename: string) = + let f = filename.open(mode = fmWrite) + let data = image.toPNG() + let written = f.writeBytes(data, 0, data.len) + assert written == data.len, &"There was an error while saving '{filename}': only {written} of {data.len} bytes were written" + f.close()