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
3 changes: 1 addition & 2 deletions pixie.nimble
Original file line number Diff line number Diff line change
Expand Up @@ -9,11 +9,10 @@ requires "nim >= 1.4.8"
requires "vmath >= 1.1.4"
requires "chroma >= 0.2.5"
requires "zippy >= 0.9.11"
requires "flatty >= 0.3.3"
requires "flatty >= 0.3.4"
requires "nimsimd >= 1.0.0"
requires "bumpy >= 1.1.1"


task bindings, "Generate bindings":

proc compile(libName: string, flags = "") =
Expand Down
2 changes: 1 addition & 1 deletion src/pixie.nim
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,7 @@ proc decodeMask*(data: string): Mask {.raises: [PixieError].} =
proc readImageDimensions*(
filePath: string
): ImageDimensions {.inline, raises: [PixieError].} =
## Loads an image from a file.
## Decodes an image's dimensions from a file.
try:
decodeImageDimensions(readFile(filePath))
except IOError as e:
Expand Down
57 changes: 36 additions & 21 deletions src/pixie/fileformats/png.nim
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,8 @@ template failInvalid() =
when defined(release):
{.push checks: off.}

proc decodeHeader(data: string): PngHeader =
proc decodeHeader(data: pointer): PngHeader =
let data = cast[ptr UncheckedArray[uint8]](data)
result.width = data.readUint32(0).swap().int
result.height = data.readUint32(4).swap().int
result.bitDepth = data.readUint8(8)
Expand Down Expand Up @@ -78,18 +79,19 @@ proc decodeHeader(data: string): PngHeader =
# Not yet supported:

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

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

proc decodePalette(data: string): seq[ColorRGB] =
if data.len == 0 or data.len mod 3 != 0:
proc decodePalette(data: pointer, len: int): seq[ColorRGB] =
if len == 0 or len mod 3 != 0:
failInvalid()

result.setLen(data.len div 3)
result.setLen(len div 3)

for i in 0 ..< data.len div 3:
let data = cast[ptr UncheckedArray[uint8]](data)
for i in 0 ..< len div 3:
copyMem(result[i].addr, data[i * 3].unsafeAddr, 3)

proc unfilter(
Expand Down Expand Up @@ -165,7 +167,7 @@ proc unfilter(
discard # Not possible, parseHeader validates

proc decodeImageData(
data: string,
data: ptr UncheckedArray[uint8],
header: PngHeader,
palette: seq[ColorRGB],
transparency: string,
Expand All @@ -183,7 +185,7 @@ proc decodeImageData(
for (start, len) in idats:
let op = imageData.len
imageData.setLen(imageData.len + len)
copyMem(imageData[op].addr, data[start].unsafeAddr, len)
copyMem(imageData[op].addr, data[start].addr, len)
try: uncompress(imageData) except ZippyError: failInvalid()
else:
let
Expand Down Expand Up @@ -268,7 +270,6 @@ proc decodeImageData(
result[i].a = 0
else:
# While we can read an extra byte safely, do so. Much faster.
# var rgba: ColorRGBA
for i in 0 ..< header.height * header.width - 1:
copyMem(result[i].addr, unfiltered[i * 3].unsafeAddr, 4)
result[i].a = 255
Expand Down Expand Up @@ -342,12 +343,14 @@ proc newImage*(png: Png): Image {.raises: [PixieError].} =
result.data.toPremultipliedAlpha()

proc decodePngDimensions*(
data: string
data: pointer, len: int
): ImageDimensions {.raises: [PixieError].} =
## Decodes the PNG dimensions.
if data.len < (8 + (8 + 13 + 4) + 4): # Magic bytes + IHDR + IEND
if len < (8 + (8 + 13 + 4) + 4): # Magic bytes + IHDR + IEND
failInvalid()

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

# PNG file signature
let signature = cast[array[8, uint8]](data.readUint64(0))
if signature != pngSignature:
Expand All @@ -357,15 +360,23 @@ proc decodePngDimensions*(
if data.readUint32(8).swap() != 13 or data.readStr(12, 4) != "IHDR":
failInvalid()

let header = decodeHeader(data[16 ..< 16 + 13])
let header = decodeHeader(data[16].addr)
result.width = header.width
result.height = header.height

proc decodePng*(data: string): Png {.raises: [PixieError].} =
proc decodePngDimensions*(
data: string
): ImageDimensions {.inline, raises: [PixieError].} =
## Decodes the PNG dimensions.
decodePngDimensions(data.cstring, data.len)

proc decodePng*(data: pointer, len: int): Png {.raises: [PixieError].} =
## Decodes the PNG data.
if data.len < (8 + (8 + 13 + 4) + 4): # Magic bytes + IHDR + IEND
if len < (8 + (8 + 13 + 4) + 4): # Magic bytes + IHDR + IEND
failInvalid()

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

# PNG file signature
let signature = cast[array[8, uint8]](data.readUint64(0))
if signature != pngSignature:
Expand All @@ -385,7 +396,7 @@ proc decodePng*(data: string): Png {.raises: [PixieError].} =
data.readStr(pos + 4, 4) != "IHDR":
failInvalid()
inc(pos, 8)
header = decodeHeader(data[pos ..< pos + 13])
header = decodeHeader(data[pos].addr)
prevChunkType = "IHDR"
inc(pos, 13)

Expand All @@ -394,7 +405,7 @@ proc decodePng*(data: string): Png {.raises: [PixieError].} =
inc(pos, 4) # CRC

while true:
if pos + 8 > data.len:
if pos + 8 > len:
failInvalid()

let
Expand All @@ -405,7 +416,7 @@ proc decodePng*(data: string): Png {.raises: [PixieError].} =
if chunkLen > high(int32).int:
failInvalid()

if pos + chunkLen + 4 > data.len:
if pos + chunkLen + 4 > len:
failInvalid()

case chunkType:
Expand All @@ -415,12 +426,12 @@ proc decodePng*(data: string): Png {.raises: [PixieError].} =
inc counts.PLTE
if counts.PLTE > 1 or counts.IDAT > 0 or counts.tRNS > 0:
failInvalid()
palette = decodePalette(data[pos ..< pos + chunkLen])
palette = decodePalette(data[pos].addr, chunkLen)
of "tRNS":
inc counts.tRNS
if counts.tRNS > 1 or counts.IDAT > 0:
failInvalid()
transparency = data[pos ..< pos + chunkLen]
transparency = data.readStr(pos, chunkLen)
case header.colorType:
of 0:
if transparency.len != 2:
Expand Down Expand Up @@ -457,7 +468,7 @@ proc decodePng*(data: string): Png {.raises: [PixieError].} =

prevChunkType = chunkType

if pos == data.len or prevChunkType == "IEND":
if pos == len or prevChunkType == "IEND":
break

if prevChunkType != "IEND":
Expand All @@ -469,6 +480,10 @@ proc decodePng*(data: string): Png {.raises: [PixieError].} =
result.channels = 4
result.data = decodeImageData(data, header, palette, transparency, idats)

proc decodePng*(data: string): Png {.inline, raises: [PixieError].} =
## Decodes the PNG data.
decodePng(data.cstring, data.len)

proc encodePng*(
width, height, channels: int, data: pointer, len: int
): string {.raises: [PixieError].} =
Expand Down