Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
38 changes: 25 additions & 13 deletions src/pixie.nim
Original file line number Diff line number Diff line change
Expand Up @@ -19,24 +19,36 @@ converter autoPremultipliedAlpha*(c: ColorRGBA): ColorRGBX {.inline, raises: [].
c.rgbx()

proc decodeImageDimensions*(
data: string
data: pointer, len: int
): ImageDimensions {.raises: [PixieError].} =
## Decodes an image's dimensions from memory.
if data.len > 8 and data.readUint64(0) == cast[uint64](pngSignature):
decodePngDimensions(data)
elif data.len > 2 and data.readUint16(0) == cast[uint16](jpegStartOfImage):
decodeJpegDimensions(data)
elif data.len > 2 and data.readStr(0, 2) == bmpSignature:
decodeBmpDimensions(data)
elif data.len > 6 and data.readStr(0, 6) in gifSignatures:
decodeGifDimensions(data)
elif data.len > (14+8) and data.readStr(0, 4) == qoiSignature:
decodeQoiDimensions(data)
elif data.len > 9 and data.readStr(0, 2) in ppmSignatures:
decodePpmDimensions(data)
if len > 8 and equalMem(data, pngSignature[0].unsafeAddr, 8):
decodePngDimensions(data, len)
elif len > 2 and equalMem(data, jpegStartOfImage[0].unsafeAddr, 2):
decodeJpegDimensions(data, len)
elif len > 2 and equalMem(data, bmpSignature.cstring, 2):
decodeBmpDimensions(data, len)
elif len > 6 and (
equalMem(data, gifSignatures[0].cstring, 6) or
equalMem(data, gifSignatures[1].cstring, 6)
):
decodeGifDimensions(data, len)
elif len > (14 + 8) and equalMem(data, qoiSignature.cstring, 4):
decodeQoiDimensions(data, len)
elif len > 9 and (
equalMem(data, ppmSignatures[0].cstring, 2) or
equalMem(data, ppmSignatures[1].cstring, 2)
):
decodePpmDimensions(data, len)
else:
raise newException(PixieError, "Unsupported image file format")

proc decodeImageDimensions*(
data: string
): ImageDimensions {.raises: [PixieError].} =
## Decodes an image's dimensions from memory.
decodeImageDimensions(data.cstring, data.len)

proc decodeImage*(data: string): Image {.raises: [PixieError].} =
## Loads an image from memory.
if data.len > 8 and data.readUint64(0) == cast[uint64](pngSignature):
Expand Down
14 changes: 11 additions & 3 deletions src/pixie/fileformats/bmp.nim
Original file line number Diff line number Diff line change
Expand Up @@ -228,19 +228,27 @@ proc decodeBmp*(data: string): Image {.raises: [PixieError].} =
decodeDib(data[14].unsafeAddr, data.len - 14)

proc decodeBmpDimensions*(
data: string
data: pointer, len: int
): ImageDimensions {.raises: [PixieError].} =
## Decodes the BMP dimensions.
if data.len < 26:
if len < 26:
failInvalid()

let data = cast[ptr UncheckedArray[uint8]](data)

# BMP Header
if data[0 .. 1] != "BM":
if data[0].char != 'B' or data[1].char != 'M': # Must start with BM
failInvalid()

result.width = data.readInt32(18).int
result.height = abs(data.readInt32(22)).int

proc decodeBmpDimensions*(
data: string
): ImageDimensions {.raises: [PixieError].} =
## Decodes the BMP dimensions.
decodeBmpDimensions(data.cstring, data.len)

proc encodeDib*(image: Image): string {.raises: [].} =
## Encodes an image into a DIB.

Expand Down
17 changes: 14 additions & 3 deletions src/pixie/fileformats/gif.nim
Original file line number Diff line number Diff line change
Expand Up @@ -379,18 +379,29 @@ proc decodeGif*(data: string): Gif {.raises: [PixieError].} =
result.duration += interval

proc decodeGifDimensions*(
data: string
data: pointer, len: int
): ImageDimensions {.raises: [PixieError].} =
## Decodes the GIF dimensions.
if data.len < 10:
if len < 10:
failInvalid()

if data[0 .. 5] notin gifSignatures:
let data = cast[ptr UncheckedArray[uint8]](data)

let startsWithSignature =
equalMem(data, gifSignatures[0].cstring, 6) or
equalMem(data, gifSignatures[1].cstring, 6)

if not startsWithSignature:
raise newException(PixieError, "Invalid GIF file signature")

result.width = data.readInt16(6).int
result.height = data.readInt16(8).int

proc decodeGifDimensions*(
data: string
): ImageDimensions {.raises: [PixieError].} =
decodeGifDimensions(data.cstring, data.len)

proc newImage*(gif: Gif): Image {.raises: [].} =
gif.frames[0].copy()

Expand Down
68 changes: 49 additions & 19 deletions src/pixie/fileformats/jpeg.nim
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,6 @@ import chroma, flatty/binny, ../common, ../images, ../internal,

const
fastBits = 9
jpegStartOfImage* = [0xFF.uint8, 0xD8]
deZigZag = [
uint8 00, 01, 08, 16, 09, 02, 03, 10,
uint8 17, 24, 32, 25, 18, 11, 04, 05,
Expand All @@ -40,6 +39,9 @@ const
1023, 2047, 4095, 8191, 16383, 32767, 65535
]

let
jpegStartOfImage* = [0xFF.uint8, 0xD8]

type
Huffman = object
codes: array[256, uint16]
Expand Down Expand Up @@ -380,15 +382,16 @@ proc decodeSOF2(state: var DecoderState) =

proc decodeExif(state: var DecoderState) =
## Decode Exif header
let
len = state.readUint16be().int - 2
endOffset = state.pos + len
var len = state.readUint16be().int - 2

let exifHeader = state.readStr(6)

len -= 6

if exifHeader != "Exif\0\0":
# Happens with progressive images, just ignore instead of error.
# Skip to the end.
state.pos = endOffset
state.skipBytes(len)
return

# Read the endianess of the exif header
Expand All @@ -402,22 +405,40 @@ proc decodeExif(state: var DecoderState) =
else:
failInvalid("invalid Tiff header")

len -= 2

# Verify we got the endianess right.
if state.readUint16be().maybeSwap(littleEndian) != 0x002A.uint16:
failInvalid("invalid Tiff header endianess")

len -= 2

# Skip any other tiff header data.
let offsetToFirstIFD = state.readUint32be().maybeSwap(littleEndian).int

len -= 4

if offsetToFirstIFD < 8:
failInvalid("invalid Tiff offset")

state.skipBytes(offsetToFirstIFD - 8)

len -= (offsetToFirstIFD - 8)

# Read the IFD0 (main image) tags.
let numTags = state.readUint16be().maybeSwap(littleEndian).int

len -= 2

for i in 0 ..< numTags:
let
tagNumber = state.readUint16be().maybeSwap(littleEndian)
dataFormat = state.readUint16be().maybeSwap(littleEndian)
numberComponents = state.readUint32be().maybeSwap(littleEndian)
dataOffset = state.readUint32be().maybeSwap(littleEndian).int

len -= 12

# For now we only care about orientation tag.
case tagNumber:
of 0x0112: # Orientation
Expand All @@ -426,7 +447,7 @@ proc decodeExif(state: var DecoderState) =
discard

# Skip all of the data we do not want to read, IFD1, thumbnail, etc.
state.pos = endOffset
state.skipBytes(len) # Skip any remaining len

proc reset(state: var DecoderState) =
## Rests the decoder state need for restart markers.
Expand Down Expand Up @@ -1136,13 +1157,13 @@ proc decodeJpeg*(data: string): Image {.raises: [PixieError].} =
state.buildImage()

proc decodeJpegDimensions*(
data: string
data: pointer, len: int
): ImageDimensions {.raises: [PixieError].} =
## Decodes the JPEG dimensions.

var state = DecoderState()
state.buffer = cast[ptr UncheckedArray[uint8]](data.cstring)
state.len = data.len
state.buffer = cast[ptr UncheckedArray[uint8]](data)
state.len = len

while true:
if state.readUint8() != 0xFF:
Expand All @@ -1153,21 +1174,24 @@ proc decodeJpegDimensions*(
of 0xD8:
# SOI - Start of Image
discard
of 0xC0:
# Start Of Frame (Baseline DCT)
state.decodeSOF0()
of 0xC0, 0xC2:
# Start Of Frame (Baseline DCT or Progressive DCT)
discard state.readUint16be().int # Chunk len
discard state.readUint8() # Precision
state.imageHeight = state.readUint16be().int
state.imageWidth = state.readUint16be().int
break
of 0xC1:
# Start Of Frame (Extended sequential DCT)
state.decodeSOF1()
break
of 0xC2:
# Start Of Frame (Progressive DCT)
state.decodeSOF2()
break
failInvalid("unsupported extended sequential DCT format")
of 0xC4:
# Define Huffman Table
state.decodeDHT()
of 0xDB:
# Define Quantization Table(s)
state.skipChunk()
of 0xDD:
# Define Restart Interval
state.skipChunk()
of 0XE0:
# Application-specific
state.skipChunk()
Expand All @@ -1193,5 +1217,11 @@ proc decodeJpegDimensions*(
else:
failInvalid("invalid orientation")

proc decodeJpegDimensions*(
data: string
): ImageDimensions {.raises: [PixieError].} =
## Decodes the JPEG dimensions.
decodeJpegDimensions(data.cstring, data.len)

when defined(release):
{.pop.}
16 changes: 7 additions & 9 deletions src/pixie/fileformats/png.nim
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import chroma, flatty/binny, math, ../common, ../images, ../internal,

# See http://www.libpng.org/pub/png/spec/1.2/PNG-Contents.html

const
let
pngSignature* = [137.uint8, 80, 78, 71, 13, 10, 26, 10]

type
Expand Down Expand Up @@ -76,14 +76,6 @@ proc decodeHeader(data: pointer): PngHeader =
if result.interlaceMethod notin [0.uint8, 1]:
raise newException(PixieError, "Invalid PNG interlace method")

# Not yet supported:

if result.bitDepth == 16:
raise newException(PixieError, "PNG 16 bit depth not supported yet")

if result.interlaceMethod != 0:
raise newException(PixieError, "Interlaced PNG not supported yet")

proc decodePalette(data: pointer, len: int): seq[ColorRGB] =
if len == 0 or len mod 3 != 0:
failInvalid()
Expand Down Expand Up @@ -451,6 +443,12 @@ proc decodePng*(data: pointer, len: int): Png {.raises: [PixieError].} =
failCRC()
inc(pos, 4) # CRC

# Not yet supported:
if header.bitDepth == 16:
raise newException(PixieError, "PNG 16 bit depth not supported yet")
if header.interlaceMethod != 0:
raise newException(PixieError, "Interlaced PNG not supported yet")

while true:
if pos + 8 > len:
failInvalid()
Expand Down
28 changes: 20 additions & 8 deletions src/pixie/fileformats/ppm.nim
Original file line number Diff line number Diff line change
Expand Up @@ -12,16 +12,17 @@ type
template failInvalid() =
raise newException(PixieError, "Invalid PPM data")

proc decodeHeader(data: string): PpmHeader {.raises: [PixieError].} =
if data.len <= 10: # Each part + whitespace
raise newException(PixieError, "Invalid PPM file header")

proc decodeHeader(
data: ptr UncheckedArray[uint8], len: int
): PpmHeader {.raises: [PixieError].} =
var
commentMode, readWhitespace: bool
i, readFields: int
field: string
while readFields < 4:
let c = readUint8(data, i).char
if i >= len:
raise newException(PixieError, "Invalid PPM file header")
let c = data[i].char
if c == '#':
commentMode = true
elif c == '\n':
Expand Down Expand Up @@ -120,7 +121,10 @@ proc decodeP3Data(data: string, maxVal: int): seq[ColorRGBX] {.raises: [PixieErr
proc decodePpm*(data: string): Image {.raises: [PixieError].} =
## Decodes Portable Pixel Map data into an Image.

let header = decodeHeader(data)
let header = decodeHeader(
cast[ptr UncheckedArray[uint8]](data.cstring),
data.len
)

if not (header.version in ppmSignatures):
failInvalid()
Expand All @@ -136,13 +140,21 @@ proc decodePpm*(data: string): Image {.raises: [PixieError].} =
decodeP6Data(data[header.dataOffset .. ^1], header.maxVal)

proc decodePpmDimensions*(
data: string
data: pointer, len: int
): ImageDimensions {.raises: [PixieError].} =
## Decodes the PPM dimensions.
let header = decodeHeader(data)
let
data = cast[ptr UncheckedArray[uint8]](data)
header = decodeHeader(data, len)
result.width = header.width
result.height = header.height

proc decodePpmDimensions*(
data: string
): ImageDimensions {.raises: [PixieError].} =
## Decodes the PPM dimensions.
decodePpmDimensions(data.cstring, data.len)

proc encodePpm*(image: Image): string {.raises: [].} =
## Encodes an image into the PPM file format (version P6).

Expand Down
12 changes: 10 additions & 2 deletions src/pixie/fileformats/qoi.nim
Original file line number Diff line number Diff line change
Expand Up @@ -136,15 +136,23 @@ proc decodeQoi*(data: string): Qoi {.raises: [PixieError].} =
inc(p)

proc decodeQoiDimensions*(
data: string
data: pointer, len: int
): ImageDimensions {.raises: [PixieError].} =
## Decodes the QOI dimensions.
if data.len <= 12 or data[0 .. 3] != qoiSignature:
if len <= 12 or not equalMem(data, qoiSignature.cstring, 4):
raise newException(PixieError, "Invalid QOI header")

let data = cast[ptr UncheckedArray[uint8]](data)

result.width = data.readUint32(4).swap().int
result.height = data.readUint32(8).swap().int

proc decodeQoiDimensions*(
data: string
): ImageDimensions {.raises: [PixieError].} =
## Decodes the QOI dimensions.
decodeQoiDimensions(data.cstring, data.len)

proc encodeQoi*(qoi: Qoi): string {.raises: [PixieError].} =
## Encodes raw QOI pixels to the QOI file format.

Expand Down