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
2 changes: 1 addition & 1 deletion pixie.nimble
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ srcDir = "src"
requires "nim >= 1.4.8"
requires "vmath >= 1.1.4"
requires "chroma >= 0.2.5"
requires "zippy >= 0.9.11"
requires "zippy >= 0.10.0"
requires "flatty >= 0.3.4"
requires "nimsimd >= 1.0.0"
requires "bumpy >= 1.1.1"
Expand Down
6 changes: 3 additions & 3 deletions src/pixie.nim
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ proc decodeImageDimensions*(
proc decodeImage*(data: string): Image {.raises: [PixieError].} =
## Loads an image from memory.
if data.len > 8 and data.readUint64(0) == cast[uint64](pngSignature):
newImage(decodePng(data))
decodePng(data).convertToImage()
elif data.len > 2 and data.readUint16(0) == cast[uint16](jpegStartOfImage):
decodeJpeg(data)
elif data.len > 2 and data.readStr(0, 2) == bmpSignature:
Expand All @@ -51,7 +51,7 @@ proc decodeImage*(data: string): Image {.raises: [PixieError].} =
elif data.len > 6 and data.readStr(0, 6) in gifSignatures:
decodeGif(data)
elif data.len > (14+8) and data.readStr(0, 4) == qoiSignature:
newImage(decodeQoi(data))
decodeQoi(data).convertToImage()
elif data.len > 9 and data.readStr(0, 2) in ppmSignatures:
decodePpm(data)
else:
Expand All @@ -60,7 +60,7 @@ proc decodeImage*(data: string): Image {.raises: [PixieError].} =
proc decodeMask*(data: string): Mask {.raises: [PixieError].} =
## Loads a mask from memory.
if data.len > 8 and data.readUint64(0) == cast[uint64](pngSignature):
newMask(newImage(decodePng(data)))
newMask(decodePng(data).convertToImage())
else:
raise newException(PixieError, "Unsupported mask file format")

Expand Down
39 changes: 28 additions & 11 deletions src/pixie/fileformats/png.nim
Original file line number Diff line number Diff line change
Expand Up @@ -21,8 +21,8 @@ type
template failInvalid() =
raise newException(PixieError, "Invalid PNG buffer, unable to load")

# template failCRC() =
# raise newException(PixieError, "CRC check failed")
template failCRC() =
raise newException(PixieError, "CRC check failed")

when defined(release):
{.push checks: off.}
Expand Down Expand Up @@ -338,10 +338,24 @@ proc decodeImageData(
discard # Not possible, parseHeader validates

proc newImage*(png: Png): Image {.raises: [PixieError].} =
## Creates a new Image from the PNG.
result = newImage(png.width, png.height)
copyMem(result.data[0].addr, png.data[0].addr, png.data.len * 4)
result.data.toPremultipliedAlpha()

proc convertToImage*(png: Png): Image {.raises: [].} =
## Converts a PNG into an Image by moving the data. This is faster but can
## only be done once.
type Movable = ref object
width, height, channels: int
data: seq[ColorRGBX]

result = Image()
result.width = png.width
result.height = png.height
result.data = move cast[Movable](png).data
result.data.toPremultipliedAlpha()

proc decodePngDimensions*(
data: pointer, len: int
): ImageDimensions {.raises: [PixieError].} =
Expand Down Expand Up @@ -400,8 +414,9 @@ proc decodePng*(data: pointer, len: int): Png {.raises: [PixieError].} =
prevChunkType = "IHDR"
inc(pos, 13)

# if crc32(data[pos - 17 ..< pos]) != read32be(data, pos):
# failCRC()
let headerCrc = crc32(data[pos - 17].addr, 17)
if headerCrc != data.readUint32(pos).swap():
failCRC()
inc(pos, 4) # CRC

while true:
Expand Down Expand Up @@ -462,8 +477,9 @@ proc decodePng*(data: pointer, len: int): Png {.raises: [PixieError].} =

inc(pos, chunkLen)

# if crc32(data[pos - chunkLen - 4 ..< pos]) != read32be(data, pos):
# failCRC()
let chunkCrc = crc32(data[pos - chunkLen - 4].addr, chunkLen + 4)
if chunkCrc != data.readUint32(pos).swap():
failCRC()
inc(pos, 4) # CRC

prevChunkType = chunkType
Expand Down Expand Up @@ -523,7 +539,7 @@ proc encodePng*(
result.add(0.char)
result.add(0.char)
result.add(0.char)
result.addUint32(crc32(result[result.len - 17 ..< result.len]).swap())
result.addUint32(crc32(result[result.len - 17].addr, 17).swap())

# Add IDAT
# Add room for 1 byte before each row for the filter type.
Expand Down Expand Up @@ -556,14 +572,15 @@ proc encodePng*(
result.addUint32(compressed.len.uint32.swap())
result.add("IDAT")
result.add(compressed)
result.addUint32(
crc32(result[result.len - compressed.len - 4 ..< result.len]).swap()
)
result.addUint32(crc32(
result[result.len - compressed.len - 4].addr,
compressed.len + 4
).swap())

# Add IEND
result.addUint32(0)
result.add("IEND")
result.addUint32(crc32(result[result.len - 4 ..< result.len]).swap())
result.addUint32(crc32(result[result.len - 4].addr, 4).swap())

proc encodePng*(png: Png): string {.raises: [PixieError].} =
encodePng(png.width, png.height, 4, png.data[0].addr, png.data.len * 4)
Expand Down
16 changes: 15 additions & 1 deletion src/pixie/fileformats/qoi.nim
Original file line number Diff line number Diff line change
Expand Up @@ -30,11 +30,25 @@ proc hash(p: ColorRGBA): int =
(p.r.int * 3 + p.g.int * 5 + p.b.int * 7 + p.a.int * 11) mod indexLen

proc newImage*(qoi: Qoi): Image =
## Converts raw QOI data to `Image`.
## Creates a new Image from the QOI.
result = newImage(qoi.width, qoi.height)
copyMem(result.data[0].addr, qoi.data[0].addr, qoi.data.len * 4)
result.data.toPremultipliedAlpha()

proc convertToImage*(qoi: Qoi): Image {.raises: [].} =
## Converts a QOI into an Image by moving the data. This is faster but can
## only be done once.
type Movable = ref object
width, height, channels: int
colorspace: Colorspace
data: seq[ColorRGBX]

result = Image()
result.width = qoi.width
result.height = qoi.height
result.data = move cast[Movable](qoi).data
result.data.toPremultipliedAlpha()

proc decodeQoi*(data: string): Qoi {.raises: [PixieError].} =
## Decompress QOI file format data.
if data.len <= 14 or data[0 .. 3] != qoiSignature:
Expand Down
3 changes: 2 additions & 1 deletion src/pixie/masks.nim
Original file line number Diff line number Diff line change
Expand Up @@ -254,7 +254,8 @@ proc spread*(mask: Mask, spread: float32) {.raises: [PixieError].} =
let spread = round(spread).int
if spread == 0:
return
elif spread > 0:

if spread > 0:

# Spread in the X direction. Store with dimensions swapped for reading later.
let spreadX = newMask(mask.height, mask.width)
Expand Down
4 changes: 2 additions & 2 deletions src/pixie/paths.nim
Original file line number Diff line number Diff line change
Expand Up @@ -1320,9 +1320,9 @@ proc clearUnsafe(target: Image | Mask, startX, startY, toX, toY: int) =
start = target.dataIndex(startX, startY)
len = target.dataIndex(toX, toY) - start
when type(target) is Image:
target.data.fillUnsafe(rgbx(0, 0, 0, 0), start, len)
fillUnsafe(target.data, rgbx(0, 0, 0, 0), start, len)
else: # target is Mask
target.data.fillUnsafe(0, start, len)
fillUnsafe(target.data, 0, start, len)

proc fillCoverage(
image: Image,
Expand Down
10 changes: 5 additions & 5 deletions tests/benchmark_png.nim
Original file line number Diff line number Diff line change
Expand Up @@ -13,12 +13,12 @@ block:
timeIt "pixie decode":
discard decodePng(data)

timeIt "pixie decode + alpha":
discard decodePng(data).convertToImage()

timeIt "pixie encode":
discard encodePng(decodedPng)

timeIt "pixie decode + alpha":
discard newImage(decodePng(data))

timeIt "pixie encode + alpha":
discard encodePng(decodedImage)

Expand Down Expand Up @@ -55,9 +55,9 @@ block:

block:
timeIt "cairo decode":
discard imageSurfaceCreateFromPng(filePath)
discard imageSurfaceCreateFromPng(filePath.cstring)

let decoded = imageSurfaceCreateFromPng(filePath)
let decoded = imageSurfaceCreateFromPng(filePath.cstring)
timeIt "cairo encode":
var write: WriteFunc =
proc(closure: pointer, data: cstring, len: int32): Status {.cdecl.} =
Expand Down