Merge pull request #240 from guzba/master

png always strings, updated deps, masters fix
This commit is contained in:
treeform 2021-06-29 22:39:34 -07:00 committed by GitHub
commit c6ddb73673
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
8 changed files with 60 additions and 70 deletions

View file

@ -8,8 +8,8 @@ srcDir = "src"
requires "nim >= 1.2.6" requires "nim >= 1.2.6"
requires "vmath >= 1.0.8" requires "vmath >= 1.0.8"
requires "chroma >= 0.2.5" requires "chroma >= 0.2.5"
requires "zippy >= 0.5.12" requires "zippy >= 0.6.0"
requires "flatty >= 0.1.3" requires "flatty >= 0.2.2"
requires "nimsimd >= 1.0.0" requires "nimsimd >= 1.0.0"
requires "bumpy >= 1.0.3" requires "bumpy >= 1.0.3"

View file

@ -101,7 +101,7 @@ proc decodeGif*(data: string): Image =
# Turn full lzw data into bit stream. # Turn full lzw data into bit stream.
var var
bs = initBitStream(cast[seq[uint8]](lzwData)) bs = initBitStream(lzwData)
bitSize = lzwMinBitSize + 1 bitSize = lzwMinBitSize + 1
currentCodeTableMax = (1 shl (bitSize)) - 1 currentCodeTableMax = (1 shl (bitSize)) - 1
codeLast: int = -1 codeLast: int = -1

View file

@ -23,14 +23,14 @@ template failInvalid() =
when defined(release): when defined(release):
{.push checks: off.} {.push checks: off.}
proc decodeHeader(data: seq[uint8]): PngHeader = proc decodeHeader(data: string): PngHeader =
result.width = data.readUint32(0).swap().int result.width = data.readUint32(0).swap().int
result.height = data.readUint32(4).swap().int result.height = data.readUint32(4).swap().int
result.bitDepth = data[8] result.bitDepth = data.readUint8(8)
result.colorType = data[9] result.colorType = data.readUint8(9)
result.compressionMethod = data[10] result.compressionMethod = data.readUint8(10)
result.filterMethod = data[11] result.filterMethod = data.readUint8(11)
result.interlaceMethod = data[12] result.interlaceMethod = data.readUint8(12)
if result.width == 0 or result.width > int32.high.int: if result.width == 0 or result.width > int32.high.int:
raise newException(PixieError, "Invalid PNG width") raise newException(PixieError, "Invalid PNG width")
@ -79,7 +79,7 @@ proc decodeHeader(data: seq[uint8]): PngHeader =
if result.interlaceMethod != 0: if result.interlaceMethod != 0:
raise newException(PixieError, "Interlaced PNG not yet supported") raise newException(PixieError, "Interlaced PNG not yet supported")
proc decodePalette(data: seq[uint8]): seq[ColorRGB] = proc decodePalette(data: string): seq[ColorRGB] =
if data.len == 0 or data.len mod 3 != 0: if data.len == 0 or data.len mod 3 != 0:
failInvalid() failInvalid()
@ -88,9 +88,7 @@ proc decodePalette(data: seq[uint8]): seq[ColorRGB] =
for i in 0 ..< data.len div 3: for i in 0 ..< data.len div 3:
result[i] = cast[ptr ColorRGB](data[i * 3].unsafeAddr)[] result[i] = cast[ptr ColorRGB](data[i * 3].unsafeAddr)[]
proc unfilter( proc unfilter(uncompressed: string, height, rowBytes, bpp: int): string =
uncompressed: seq[uint8], height, rowBytes, bpp: int
): seq[uint8] =
result.setLen(uncompressed.len - height) result.setLen(uncompressed.len - height)
template uncompressedIdx(x, y: int): int = template uncompressedIdx(x, y: int): int =
@ -101,7 +99,7 @@ proc unfilter(
# Unfilter the image data # Unfilter the image data
for y in 0 ..< height: for y in 0 ..< height:
let filterType = uncompressed[uncompressedIdx(0, y)] let filterType = uncompressed.readUint8(uncompressedIdx(0, y))
case filterType: case filterType:
of 0: # None of 0: # None
copyMem( copyMem(
@ -111,31 +109,31 @@ proc unfilter(
) )
of 1: # Sub of 1: # Sub
for x in 0 ..< rowBytes: for x in 0 ..< rowBytes:
var value = uncompressed[uncompressedIdx(x + 1, y)] var value = uncompressed.readUint8(uncompressedIdx(x + 1, y))
if x - bpp >= 0: if x - bpp >= 0:
value += result[unfiteredIdx(x - bpp, y)] value += result.readUint8(unfiteredIdx(x - bpp, y))
result[unfiteredIdx(x, y)] = value result[unfiteredIdx(x, y)] = value.char
of 2: # Up of 2: # Up
for x in 0 ..< rowBytes: for x in 0 ..< rowBytes:
var value = uncompressed[uncompressedIdx(x + 1, y)] var value = uncompressed.readUint8(uncompressedIdx(x + 1, y))
if y - 1 >= 0: if y - 1 >= 0:
value += result[unfiteredIdx(x, y - 1)] value += result.readUint8(unfiteredIdx(x, y - 1))
result[unfiteredIdx(x, y)] = value result[unfiteredIdx(x, y)] = value.char
of 3: # Average of 3: # Average
for x in 0 ..< rowBytes: for x in 0 ..< rowBytes:
var var
value = uncompressed[uncompressedIdx(x + 1, y)] value = uncompressed.readUint8(uncompressedIdx(x + 1, y))
left, up: int left, up: int
if x - bpp >= 0: if x - bpp >= 0:
left = result[unfiteredIdx(x - bpp, y)].int left = result[unfiteredIdx(x - bpp, y)].int
if y - 1 >= 0: if y - 1 >= 0:
up = result[unfiteredIdx(x, y - 1)].int up = result[unfiteredIdx(x, y - 1)].int
value += ((left + up) div 2).uint8 value += ((left + up) div 2).uint8
result[unfiteredIdx(x, y)] = value result[unfiteredIdx(x, y)] = value.char
of 4: # Paeth of 4: # Paeth
for x in 0 ..< rowBytes: for x in 0 ..< rowBytes:
var var
value = uncompressed[uncompressedIdx(x + 1, y)] value = uncompressed.readUint8(uncompressedIdx(x + 1, y))
left, up, upLeft: int left, up, upLeft: int
if x - bpp >= 0: if x - bpp >= 0:
left = result[unfiteredIdx(x - bpp, y)].int left = result[unfiteredIdx(x - bpp, y)].int
@ -156,14 +154,14 @@ proc unfilter(
else: else:
c c
value += paethPredictor(up, left, upLeft).uint8 value += paethPredictor(up, left, upLeft).uint8
result[unfiteredIdx(x, y)] = value result[unfiteredIdx(x, y)] = value.char
else: else:
discard # Not possible, parseHeader validates discard # Not possible, parseHeader validates
proc decodeImageData( proc decodeImageData(
header: PngHeader, header: PngHeader,
palette: seq[ColorRGB], palette: seq[ColorRGB],
transparency, data: seq[uint8] transparency, data: string
): seq[ColorRGBA] = ): seq[ColorRGBA] =
result.setLen(header.width * header.height) result.setLen(header.width * header.height)
@ -199,7 +197,7 @@ proc decodeImageData(
var bytePos, bitPos: int var bytePos, bitPos: int
for y in 0 ..< header.height: for y in 0 ..< header.height:
for x in 0 ..< header.width: for x in 0 ..< header.width:
var value = unfiltered[bytePos] var value = unfiltered.readUint8(bytePos)
case header.bitDepth: case header.bitDepth:
of 1: of 1:
value = (value shr (7 - bitPos)) and 1 value = (value shr (7 - bitPos)) and 1
@ -234,9 +232,9 @@ proc decodeImageData(
of 2: of 2:
var special: ColorRGBA var special: ColorRGBA
if transparency.len == 6: # Need to apply transparency check, slower. if transparency.len == 6: # Need to apply transparency check, slower.
special.r = transparency[1] special.r = transparency.readUint8(1)
special.g = transparency[3] special.g = transparency.readUint8(3)
special.b = transparency[5] special.b = transparency.readUint8(5)
special.a = 255 special.a = 255
# While we can read an extra byte safely, do so. Much faster. # While we can read an extra byte safely, do so. Much faster.
@ -264,7 +262,7 @@ proc decodeImageData(
var bytePos, bitPos: int var bytePos, bitPos: int
for y in 0 ..< header.height: for y in 0 ..< header.height:
for x in 0 ..< header.width: for x in 0 ..< header.width:
var value = unfiltered[bytePos] var value = unfiltered.readUint8(bytePos)
case header.bitDepth: case header.bitDepth:
of 1: of 1:
value = (value shr (7 - bitPos)) and 1 value = (value shr (7 - bitPos)) and 1
@ -290,7 +288,7 @@ proc decodeImageData(
rgb = palette[value] rgb = palette[value]
transparency = transparency =
if transparency.len > value.int: if transparency.len > value.int:
transparency[value] transparency.readUint8(value.int)
else: else:
255 255
result[x + y * header.width] = ColorRGBA( result[x + y * header.width] = ColorRGBA(
@ -305,10 +303,10 @@ proc decodeImageData(
for i in 0 ..< header.height * header.width: for i in 0 ..< header.height * header.width:
let bytePos = i * 2 let bytePos = i * 2
result[i] = ColorRGBA( result[i] = ColorRGBA(
r: unfiltered[bytePos], r: unfiltered.readUint8(bytePos),
g: unfiltered[bytePos], g: unfiltered.readUint8(bytePos),
b: unfiltered[bytePos], b: unfiltered.readUint8(bytePos),
a: unfiltered[bytePos + 1] a: unfiltered.readUint8(bytePos + 1)
) )
of 6: of 6:
for i in 0 ..< header.height * header.width: for i in 0 ..< header.height * header.width:
@ -316,7 +314,7 @@ proc decodeImageData(
else: else:
discard # Not possible, parseHeader validates discard # Not possible, parseHeader validates
proc decodePng*(data: seq[uint8]): Image = proc decodePng*(data: string): Image =
## Decodes the PNG data into an Image. ## Decodes the PNG data into an Image.
if data.len < (8 + (8 + 13 + 4) + 4): # Magic bytes + IHDR + IEND if data.len < (8 + (8 + 13 + 4) + 4): # Magic bytes + IHDR + IEND
@ -332,7 +330,7 @@ proc decodePng*(data: seq[uint8]): Image =
counts = ChunkCounts() counts = ChunkCounts()
header: PngHeader header: PngHeader
palette: seq[ColorRGB] palette: seq[ColorRGB]
transparency, imageData: seq[uint8] transparency, imageData: string
prevChunkType: string prevChunkType: string
# First chunk must be IHDR # First chunk must be IHDR
@ -401,8 +399,7 @@ proc decodePng*(data: seq[uint8]): Image =
if chunkLen != 0: if chunkLen != 0:
failInvalid() failInvalid()
else: else:
let bytes = cast[seq[uint8]](chunkType) if (chunkType.readUint8(0) and 0b00100000) == 0:
if (bytes[0] and 0b00100000) == 0:
raise newException( raise newException(
PixieError, "Unrecognized PNG critical chunk " & chunkType PixieError, "Unrecognized PNG critical chunk " & chunkType
) )
@ -429,13 +426,7 @@ proc decodePng*(data: seq[uint8]): Image =
result.height = header.height result.height = header.height
result.data = cast[seq[ColorRGBX]](pixels) result.data = cast[seq[ColorRGBX]](pixels)
proc decodePng*(data: string): Image {.inline.} = proc encodePng*(width, height, channels: int, data: pointer, len: int): string =
## Decodes the PNG data into an Image.
decodePng(cast[seq[uint8]](data))
proc encodePng*(
width, height, channels: int, data: pointer, len: int
): seq[uint8] =
## Encodes the image data into the PNG file format. ## Encodes the image data into the PNG file format.
## If data points to RGBA data, it is assumed to be straight alpha. ## If data points to RGBA data, it is assumed to be straight alpha.
@ -449,32 +440,36 @@ proc encodePng*(
raise newException(PixieError, "Invalid PNG data size") raise newException(PixieError, "Invalid PNG data size")
let colorType = case channels: let colorType = case channels:
of 1: 0.uint8 of 1: 0.char
of 2: 4 of 2: 4.char
of 3: 2 of 3: 2.char
of 4: 6 of 4: 6.char
else: else:
raise newException(PixieError, "Invalid PNG number of channels") raise newException(PixieError, "Invalid PNG number of channels")
let data = cast[ptr UncheckedArray[uint8]](data) let data = cast[ptr UncheckedArray[uint8]](data)
# Add the PNG file signature # Add the PNG file signature
result.add(pngSignature) for c in pngSignature:
result.add(c.char)
# Add IHDR # Add IHDR
result.addUint32(13.uint32.swap()) result.addUint32(13.uint32.swap())
result.addStr("IHDR") result.add("IHDR")
result.addUint32(width.uint32.swap()) result.addUint32(width.uint32.swap())
result.addUint32(height.uint32.swap()) result.addUint32(height.uint32.swap())
result.add(8.uint8) result.add(8.char)
result.add([colorType, 0, 0, 0]) result.add(colorType)
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 ..< result.len]).swap())
# Add IDAT # Add IDAT
# Add room for 1 byte before each row for the filter type. # Add room for 1 byte before each row for the filter type.
var filtered = newSeq[uint8](width * height * channels + height) var filtered = newString(width * height * channels + height)
for y in 0 ..< height: for y in 0 ..< height:
filtered[y * width * channels + y] = 3 # Average filtered[y * width * channels + y] = 3.char # Average
for x in 0 ..< width * channels: for x in 0 ..< width * channels:
# Move through the image data byte-by-byte # Move through the image data byte-by-byte
let let
@ -486,7 +481,7 @@ proc encodePng*(
if y - 1 >= 0: if y - 1 >= 0:
up = data[(y - 1) * width * channels + x].int up = data[(y - 1) * width * channels + x].int
let avg = ((left + up) div 2).uint8 let avg = ((left + up) div 2).uint8
filtered[filteredPos] = data[dataPos] - avg filtered[filteredPos] = (data[dataPos] - avg).char
let compressed = let compressed =
try: try:
@ -499,7 +494,7 @@ proc encodePng*(
raise newException(PixieError, "Compressed PNG image data too large") raise newException(PixieError, "Compressed PNG image data too large")
result.addUint32(compressed.len.uint32.swap()) result.addUint32(compressed.len.uint32.swap())
result.add(cast[seq[uint8]]("IDAT")) result.add("IDAT")
result.add(compressed) result.add(compressed)
result.addUint32( result.addUint32(
crc32(result[result.len - compressed.len - 4 ..< result.len]).swap() crc32(result[result.len - compressed.len - 4 ..< result.len]).swap()
@ -507,7 +502,7 @@ proc encodePng*(
# Add IEND # Add IEND
result.addUint32(0) result.addUint32(0)
result.addStr("IEND") result.add("IEND")
result.addUint32(crc32(result[result.len - 4 ..< result.len]).swap()) result.addUint32(crc32(result[result.len - 4 ..< result.len]).swap())
proc encodePng*(image: Image): string = proc encodePng*(image: Image): string =
@ -519,9 +514,7 @@ proc encodePng*(image: Image): string =
) )
var copy = image.data var copy = image.data
copy.toStraightAlpha() copy.toStraightAlpha()
cast[string](encodePng( encodePng(image.width, image.height, 4, copy[0].addr, copy.len * 4)
image.width, image.height, 4, copy[0].addr, copy.len * 4
))
proc encodePng*(mask: Mask): string = proc encodePng*(mask: Mask): string =
## Encodes the mask data into the PNG file format. ## Encodes the mask data into the PNG file format.
@ -530,9 +523,7 @@ proc encodePng*(mask: Mask): string =
PixieError, PixieError,
"Mask has no data (are height and width 0?)" "Mask has no data (are height and width 0?)"
) )
cast[string](encodePng( encodePng(mask.width, mask.height, 1, mask.data[0].addr, mask.data.len)
mask.width, mask.height, 1, mask.data[0].addr, mask.data.len
))
when defined(release): when defined(release):
{.pop.} {.pop.}

View file

@ -6,10 +6,10 @@ let
data = readFile(filePath) data = readFile(filePath)
timeIt "pixie decode": timeIt "pixie decode":
keep decodePng(cast[seq[uint8]](data)) keep decodePng(data)
timeIt "pixie encode": timeIt "pixie encode":
let decoded = decodePng(cast[seq[uint8]](data)) let decoded = decodePng(data)
keep encodePng(decoded).len keep encodePng(decoded).len
timeIt "nimPNG decode": timeIt "nimPNG decode":

Binary file not shown.

Before

Width:  |  Height:  |  Size: 29 KiB

After

Width:  |  Height:  |  Size: 30 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 13 KiB

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 14 KiB

After

Width:  |  Height:  |  Size: 13 KiB

View file

@ -45,9 +45,8 @@ block:
doAssert $path == "M1 2 L3 4 H5 V6 C0 0 0 0 0 0 Q1 1 1 1 T2 2 A7 7 7 7 7 7 7 Z" doAssert $path == "M1 2 L3 4 H5 V6 C0 0 0 0 0 0 Q1 1 1 1 T2 2 A7 7 7 7 7 7 7 Z"
block: block:
let let pathStr = "M 0.1E-10 0.1e10 L2+2 L3-3 L0.1E+10-1"
pathStr = "M 0.1E-10 0.1e10 L2+2 L3-3 L0.1E+10-1" discard parsePath(pathStr)
path = parsePath(pathStr)
block: block:
let let