Add support for decoding images from a pointer and a length instead of a string (TODO: qoi, ppm)
Some checks failed
Github Actions / build (1.4.x, ubuntu-latest) (push) Has been cancelled
Github Actions / build (1.4.x, windows-latest) (push) Has been cancelled
Github Actions / build (stable, ubuntu-latest) (push) Has been cancelled
Github Actions / build (stable, windows-latest) (push) Has been cancelled

This commit is contained in:
Alberto Torres 2024-08-17 17:31:56 +02:00
parent 89ac8dfbe6
commit 8458981007
4 changed files with 75 additions and 20 deletions

View file

@ -49,8 +49,39 @@ proc decodeImageDimensions*(
## Decodes an image's dimensions from memory. ## Decodes an image's dimensions from memory.
decodeImageDimensions(data.cstring, data.len) decodeImageDimensions(data.cstring, data.len)
template compare_as(T: typedesc, p,q: pointer): bool =
cast[ptr T](p)[] == cast[ptr T](q)[]
proc decodeImage*(data: pointer, len: int): Image {.raises: [PixieError].} =
## Loads an image from memory, from a pointer and a length.
if len > 8 and compare_as(uint64, data, pngSignature.addr):
decodePng(data, len).convertToImage()
elif len > 2 and compare_as(uint16, data, jpegStartOfImage.addr):
decodeJpeg(data, len)
elif len > 2 and compare_as(array[2,char], data, bmpSignature.cstring):
decodeBmp(data, len)
elif len > 5 and
compare_as(array[5,char], data, xmlSignature.cstring) or
compare_as(array[4,char], data, svgSignature.cstring):
# TODO: avoid allocating/initializing string
var s = newStringOfCap(len)
s.setLen len
copyMem(s.cstring, data, len)
newImage(parseSvg(s))
elif len > 6 and compare_as(array[6,char], data, gifSignatures[0].cstring) or
compare_as(array[6,char], data, gifSignatures[1].cstring):
newImage(decodeGif(data, len))
# TODO
# elif len > (14+8) and compare_as(array[4,char], data, qoiSignature.cstring):
# decodeQoi(data, len).convertToImage()
# elif len > 9 and compare_as(array[2,char], data, ppmSignatures[0].cstring) or
# compare_as(array[2,char], data, ppmSignatures[1].cstring):
# decodePpm(data, len)
else:
raise newException(PixieError, "Unsupported image file format")
proc decodeImage*(data: string): Image {.raises: [PixieError].} = proc decodeImage*(data: string): Image {.raises: [PixieError].} =
## Loads an image from memory. ## Loads an image from memory as a string.
if data.len > 8 and data.readUint64(0) == cast[uint64](pngSignature): if data.len > 8 and data.readUint64(0) == cast[uint64](pngSignature):
decodePng(data).convertToImage() decodePng(data).convertToImage()
elif data.len > 2 and data.readUint16(0) == cast[uint16](jpegStartOfImage): elif data.len > 2 and data.readUint16(0) == cast[uint16](jpegStartOfImage):

View file

@ -227,6 +227,17 @@ proc decodeBmp*(data: string): Image {.raises: [PixieError].} =
decodeDib(data[14].unsafeAddr, data.len - 14) decodeDib(data[14].unsafeAddr, data.len - 14)
proc decodeBmp*(data: pointer, len: int): Image {.raises: [PixieError].} =
## Decodes bitmap data into an image.
if len < 14:
failInvalid()
# BMP Header
if cast[ptr int16](data)[] != cast[ptr int16]("BM".cstring)[]:
failInvalid()
decodeDib(cast[ptr UncheckedArray[char]](data)[14].addr, len - 14)
proc decodeBmpDimensions*( proc decodeBmpDimensions*(
data: pointer, len: int data: pointer, len: int
): ImageDimensions {.raises: [PixieError].} = ): ImageDimensions {.raises: [PixieError].} =

View file

@ -22,12 +22,18 @@ template failInvalid() =
when defined(release): when defined(release):
{.push checks: off.} {.push checks: off.}
proc decodeGif*(data: string): Gif {.raises: [PixieError].} = template compare_as(T: typedesc, p,q: pointer): bool =
cast[ptr T](p)[] == cast[ptr T](q)[]
proc decodeGif*(data: pointer, len: int): Gif {.raises: [PixieError].} =
let data = cast[ptr UncheckedArray[uint8]](data)
## Decodes GIF data. ## Decodes GIF data.
if data.len < 13: if len < 13:
failInvalid() failInvalid()
if data[0 .. 5] notin gifSignatures: if not (len > 6 and
compare_as(array[6,char], data, gifSignatures[0].cstring) or
compare_as(array[6,char], data, gifSignatures[1].cstring)):
raise newException(PixieError, "Invalid GIF file signature") raise newException(PixieError, "Invalid GIF file signature")
result = Gif() result = Gif()
@ -49,7 +55,7 @@ proc decodeGif*(data: string): Gif {.raises: [PixieError].} =
var pos = 13 var pos = 13
if pos + globalColorTableSize * 3 > data.len: if pos + globalColorTableSize * 3 > len:
failInvalid() failInvalid()
var var
@ -69,7 +75,7 @@ proc decodeGif*(data: string): Gif {.raises: [PixieError].} =
proc skipSubBlocks() = proc skipSubBlocks() =
while true: # Skip data sub-blocks while true: # Skip data sub-blocks
if pos + 1 > data.len: if pos + 1 > len:
failInvalid() failInvalid()
let subBlockSize = data.readUint8(pos).int let subBlockSize = data.readUint8(pos).int
@ -82,7 +88,7 @@ proc decodeGif*(data: string): Gif {.raises: [PixieError].} =
var controlExtension: ControlExtension var controlExtension: ControlExtension
while true: while true:
if pos + 1 > data.len: if pos + 1 > len:
failInvalid() failInvalid()
let blockType = data.readUint8(pos) let blockType = data.readUint8(pos)
@ -90,7 +96,7 @@ proc decodeGif*(data: string): Gif {.raises: [PixieError].} =
case blockType: case blockType:
of 0x2c: # Image of 0x2c: # Image
if pos + 9 > data.len: if pos + 9 > len:
failInvalid() failInvalid()
let let
@ -108,7 +114,7 @@ proc decodeGif*(data: string): Gif {.raises: [PixieError].} =
if imageWidth > screenWidth or imageHeight > screenHeight: if imageWidth > screenWidth or imageHeight > screenHeight:
raise newException(PixieError, "Invalid GIF frame dimensions") raise newException(PixieError, "Invalid GIF frame dimensions")
if pos + localColorTableSize * 3 > data.len: if pos + localColorTableSize * 3 > len:
failInvalid() failInvalid()
var localColorTable: seq[ColorRGBX] var localColorTable: seq[ColorRGBX]
@ -123,7 +129,7 @@ proc decodeGif*(data: string): Gif {.raises: [PixieError].} =
) )
pos += 3 pos += 3
if pos + 1 > data.len: if pos + 1 > len:
failInvalid() failInvalid()
let minCodeSize = data.readUint8(pos).int let minCodeSize = data.readUint8(pos).int
@ -135,7 +141,7 @@ proc decodeGif*(data: string): Gif {.raises: [PixieError].} =
# The image data is contained in a sequence of sub-blocks # The image data is contained in a sequence of sub-blocks
var lzwDataBlocks: seq[(int, int)] # (offset, len) var lzwDataBlocks: seq[(int, int)] # (offset, len)
while true: while true:
if pos + 1 > data.len: if pos + 1 > len:
failInvalid() failInvalid()
let subBlockSize = data.readUint8(pos).int let subBlockSize = data.readUint8(pos).int
@ -144,7 +150,7 @@ proc decodeGif*(data: string): Gif {.raises: [PixieError].} =
if subBlockSize == 0: if subBlockSize == 0:
break break
if pos + subBlockSize > data.len: if pos + subBlockSize > len:
failInvalid() failInvalid()
lzwDataBlocks.add((pos, subBlockSize)) lzwDataBlocks.add((pos, subBlockSize))
@ -307,7 +313,7 @@ proc decodeGif*(data: string): Gif {.raises: [PixieError].} =
controlExtension = ControlExtension() controlExtension = ControlExtension()
of 0x21: # Extension of 0x21: # Extension
if pos + 1 > data.len: if pos + 1 > len:
failInvalid() failInvalid()
let extensionType = data.readUint8(pos + 0) let extensionType = data.readUint8(pos + 0)
@ -316,7 +322,7 @@ proc decodeGif*(data: string): Gif {.raises: [PixieError].} =
case extensionType: case extensionType:
of 0xf9: of 0xf9:
# Graphic Control Extension # Graphic Control Extension
if pos + 1 > data.len: if pos + 1 > len:
failInvalid() failInvalid()
let blockSize = data.readUint8(pos).int let blockSize = data.readUint8(pos).int
@ -325,7 +331,7 @@ proc decodeGif*(data: string): Gif {.raises: [PixieError].} =
if blockSize != 4: if blockSize != 4:
failInvalid() failInvalid()
if pos + blockSize > data.len: if pos + blockSize > len:
failInvalid() failInvalid()
controlExtension.fields = data.readUint8(pos + 0) controlExtension.fields = data.readUint8(pos + 0)
@ -344,7 +350,7 @@ proc decodeGif*(data: string): Gif {.raises: [PixieError].} =
of 0xff: of 0xff:
# Application Specific # Application Specific
if pos + 1 > data.len: if pos + 1 > len:
failInvalid() failInvalid()
let blockSize = data.readUint8(pos).int let blockSize = data.readUint8(pos).int
@ -353,7 +359,7 @@ proc decodeGif*(data: string): Gif {.raises: [PixieError].} =
if blockSize != 11: if blockSize != 11:
failInvalid() failInvalid()
if pos + blockSize > data.len: if pos + blockSize > len:
failInvalid() failInvalid()
pos += blockSize pos += blockSize
@ -378,6 +384,9 @@ proc decodeGif*(data: string): Gif {.raises: [PixieError].} =
for interval in result.intervals: for interval in result.intervals:
result.duration += interval result.duration += interval
proc decodeGif*(data: string): Gif {.raises: [PixieError].} =
return decodeGif(data.cstring, data.len)
proc decodeGifDimensions*( proc decodeGifDimensions*(
data: pointer, len: int data: pointer, len: int
): ImageDimensions {.raises: [PixieError].} = ): ImageDimensions {.raises: [PixieError].} =

View file

@ -1092,12 +1092,12 @@ proc buildImage(state: var DecoderState): Image =
else: else:
failInvalid("invalid orientation") failInvalid("invalid orientation")
proc decodeJpeg*(data: string): Image {.raises: [PixieError].} = proc decodeJpeg*(data: pointer, len: int): Image {.raises: [PixieError].} =
## Decodes the JPEG into an Image. ## Decodes the JPEG into an Image.
var state = DecoderState() var state = DecoderState()
state.buffer = cast[ptr UncheckedArray[uint8]](data.cstring) state.buffer = cast[ptr UncheckedArray[uint8]](data)
state.len = data.len state.len = len
while true: while true:
if state.readUint8() != 0xFF: if state.readUint8() != 0xFF:
@ -1157,6 +1157,10 @@ proc decodeJpeg*(data: string): Image {.raises: [PixieError].} =
state.buildImage() state.buildImage()
proc decodeJpeg*(data: string): Image {.raises: [PixieError].} =
## Decodes the JPEG into an Image.
decodeJpeg(data.cstring, data.len)
proc decodeJpegDimensions*( proc decodeJpegDimensions*(
data: pointer, len: int data: pointer, len: int
): ImageDimensions {.raises: [PixieError].} = ): ImageDimensions {.raises: [PixieError].} =