diff --git a/pixie.nimble b/pixie.nimble index d3d2114..94fa28e 100644 --- a/pixie.nimble +++ b/pixie.nimble @@ -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" diff --git a/src/pixie.nim b/src/pixie.nim index 3b8c5dd..c98d950 100644 --- a/src/pixie.nim +++ b/src/pixie.nim @@ -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: @@ -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: @@ -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") diff --git a/src/pixie/fileformats/png.nim b/src/pixie/fileformats/png.nim index 79e3a14..7cc98e3 100644 --- a/src/pixie/fileformats/png.nim +++ b/src/pixie/fileformats/png.nim @@ -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.} @@ -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].} = @@ -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: @@ -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 @@ -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. @@ -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) diff --git a/src/pixie/fileformats/qoi.nim b/src/pixie/fileformats/qoi.nim index 1d6fa4e..5395f6b 100644 --- a/src/pixie/fileformats/qoi.nim +++ b/src/pixie/fileformats/qoi.nim @@ -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: diff --git a/src/pixie/masks.nim b/src/pixie/masks.nim index 3af7cf7..aa42526 100644 --- a/src/pixie/masks.nim +++ b/src/pixie/masks.nim @@ -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) diff --git a/src/pixie/paths.nim b/src/pixie/paths.nim index 6d0eba7..e098980 100644 --- a/src/pixie/paths.nim +++ b/src/pixie/paths.nim @@ -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, diff --git a/tests/benchmark_png.nim b/tests/benchmark_png.nim index 81fcf76..39f2bbc 100644 --- a/tests/benchmark_png.nim +++ b/tests/benchmark_png.nim @@ -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) @@ -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.} =