Merge pull request #2 from guzba/master

png
This commit is contained in:
treeform 2020-11-20 18:43:02 -08:00 committed by GitHub
commit 0458b4c6e5
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
188 changed files with 635 additions and 4 deletions

View file

@ -9,4 +9,4 @@ requires "nim >= 1.2.6"
requires "vmath >= 0.3.2"
requires "chroma >= 0.1.5"
requires "zippy >= 0.3.5"
requires "flatty >= 0.1.1"
requires "flatty >= 0.1.2"

View file

@ -1,7 +1,7 @@
## Public interface to you library.
import pixie/images, pixie/masks, pixie/paths
export images, masks, paths
import pixie/images, pixie/masks, pixie/paths, pixie/common
export images, masks, paths, PixieError
proc toMask*(image: Image): Mask =
## Converts an Image to a Mask.

View file

@ -0,0 +1,432 @@
import chroma, pixie/images, pixie/common, math, zippy, zippy/crc, flatty/binny
# See http://www.libpng.org/pub/png/spec/1.2/PNG-Contents.html
type
ChunkCounts = object
PLTE, IDAT: uint8
PngHeader = object
width, height: int
bitDepth, colorType, compressionMethod, filterMethod, interlaceMethod: uint8
template failInvalid() =
raise newException(PixieError, "Invalid PNG buffer, unable to load")
# template failCRC() =
# raise newException(PixieError, "CRC check failed")
when defined(release):
{.push checks: off.}
proc parseHeader(data: seq[uint8]): PngHeader =
result.width = data.readUint32(0).swap().int
result.height = data.readUint32(4).swap().int
result.bitDepth = data[8]
result.colorType = data[9]
result.compressionMethod = data[10]
result.filterMethod = data[11]
result.interlaceMethod = data[12]
if result.width == 0 or result.width > int32.high.int:
raise newException(PixieError, "Invalid PNG width")
if result.height == 0 or result.height > int32.high.int:
raise newException(PixieError, "Invalid PNG height")
template failInvalidCombo() =
raise newException(
PixieError, "Invalid PNG color type and bit depth combination"
)
case result.colorType:
of 0:
if result.bitDepth notin [1.uint8, 2, 4, 8, 16]:
failInvalidCombo()
of 2:
if result.bitDepth notin [8.uint8, 16]:
failInvalidCombo()
of 3: # PLTE chunk is required, sample depth is always 8 bits
if result.bitDepth notin [1.uint8, 2, 4, 8]:
failInvalidCombo()
of 4:
if result.bitDepth notin [8.uint8, 16]:
failInvalidCombo()
of 6:
if result.bitDepth notin [8.uint8, 16]:
failInvalidCombo()
else:
failInvalidCombo()
if result.compressionMethod != 0:
raise newException(PixieError, "Invalid PNG compression method")
if result.filterMethod != 0:
raise newException(PixieError, "Invalid PNG filter method")
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 yet supported")
if result.interlaceMethod != 0:
raise newException(PixieError, "Interlaced PNG not yet supported")
proc parsePalette(data: seq[uint8]): seq[array[3, uint8]] =
if data.len == 0 or data.len mod 3 != 0:
failInvalid()
result.setLen(data.len div 3)
for i in 0 ..< data.len div 3:
result[i] = cast[ptr array[3, uint8]](data[i * 3].unsafeAddr)[]
proc unfilter(
uncompressed: seq[uint8], height, rowBytes, bpp: int
): seq[uint8] =
result.setLen(uncompressed.len - height)
template uncompressedIdx(x, y: int): int =
x + y * (rowBytes + 1)
template unfiteredIdx(x, y: int): int =
x + y * rowBytes
# Unfilter the image data
for y in 0 ..< height:
let filterType = uncompressed[uncompressedIdx(0, y)]
for x in 0 ..< rowBytes:
var value = uncompressed[uncompressedIdx(x + 1, y)]
case filterType:
of 0: # None
discard
of 1: # Sub
if x - bpp >= 0:
value += result[unfiteredIdx(x - bpp, y)]
of 2: # Up
if y - 1 >= 0:
value += result[unfiteredIdx(x, y - 1)]
of 3: # Average
var left, up: int
if x - bpp >= 0:
left = result[unfiteredIdx(x - bpp, y)].int
if y - 1 >= 0:
up = result[unfiteredIdx(x, y - 1)].int
value += ((left + up) div 2).uint8
of 4: # Paeth
var left, up, upLeft: int
if x - bpp >= 0:
left = result[unfiteredIdx(x - bpp, y)].int
if y - 1 >= 0:
up = result[unfiteredIdx(x, y - 1)].int
if x - bpp >= 0 and y - 1 >= 0:
upLeft = result[unfiteredIdx(x - bpp, y - 1)].int
proc paethPredictor(a, b, c: int): int {.inline.} =
let
p = a + b - c
pa = abs(p - a)
pb = abs(p - b)
pc = abs(p - c)
if pa <= pb and pa <= pc:
a
elif pb <= pc:
b
else:
c
value += paethPredictor(up, left, upLeft).uint8
else:
discard # Not possible, parseHeader validates
result[unfiteredIdx(x, y)] = value
proc parseImageData(
header: PngHeader, palette: seq[array[3, uint8]], data: seq[uint8]
): seq[ColorRGBA] =
result.setLen(header.width * header.height)
let
uncompressed = try: uncompress(data) except ZippyError: failInvalid()
valuesPerPixel =
case header.colorType:
of 0: 1
of 2: 3
of 3: 1
of 4: 2
of 6: 4
else: 0 # Not possible, parseHeader validates
valuesPerByte = 8 div header.bitDepth.int
rowBytes = ceil((header.width.int * valuesPerPixel) / valuesPerByte).int
totalBytes = rowBytes * header.height.int
# Uncompressed image data should be the total bytes of pixel data plus
# a filter byte for each row.
if uncompressed.len != totalBytes + header.height.int:
failInvalid()
let unfiltered = unfilter(
uncompressed,
header.height,
rowBytes,
max(valuesPerPixel div valuesPerByte, 1)
)
var bytePos, bitPos: int
for y in 0 ..< header.height:
for x in 0 ..< header.width:
var rgba: array[4, uint8]
case header.colorType:
of 0:
var value = unfiltered[bytePos]
case header.bitDepth:
of 1:
value = (value shr (7 - bitPos)) and 1
value *= 255
inc bitPos
of 2:
value = (value shr (6 - bitPos)) and 3
value *= 85
inc(bitPos, 2)
of 4:
value = (value shr (4 - bitPos)) and 15
value *= 17
inc(bitPos, 4)
of 8:
inc bytePos
else:
discard # Not possible, parseHeader validates
if bitPos == 8:
inc bytePos
bitPos = 0
rgba = [value, value, value, 255]
of 2:
let rgb = cast[ptr array[3, uint8]](unfiltered[bytePos].unsafeAddr)[]
rgba = [rgb[0], rgb[1], rgb[2], 255]
inc(bytePos, 3)
of 3:
var value = unfiltered[bytePos]
case header.bitDepth:
of 1:
value = (value shr (7 - bitPos)) and 1
inc bitPos
of 2:
value = (value shr (6 - bitPos)) and 3
inc(bitPos, 2)
of 4:
value = (value shr (4 - bitPos)) and 15
inc(bitPos, 4)
of 8:
inc bytePos
else:
discard # Not possible, parseHeader validates
if bitPos == 8:
inc bytePos
bitPos = 0
if value.int >= palette.len:
failInvalid()
let rgb = palette[value]
rgba = [rgb[0], rgb[1], rgb[2], 255]
of 4:
rgba = [
unfiltered[bytePos],
unfiltered[bytePos],
unfiltered[bytePos],
unfiltered[bytePos + 1]
]
inc(bytePos, 2)
of 6:
rgba = cast[array[4, uint8]](unfiltered.readUint32(bytePos))
inc(bytePos, 4)
else:
discard # Not possible, parseHeader validates
result[x + y * header.width] = cast[ColorRGBA](rgba)
# If we move to a new row, skip to the next full byte
if bitPos > 0:
inc bytePos
bitPos = 0
proc decodePng*(data: seq[uint8]): Image =
## Decodes the PNG from the parameter buffer. Check png.channels and
## png.bitDepth to see how the png.data is formatted.
## The returned png.data is currently always RGBA (4 channels)
## with bitDepth of 8.
if data.len < (8 + (8 + 13 + 4) + 4): # Magic bytes + IHDR + IEND
failInvalid()
# PNG file signature
let signature = cast[array[8, uint8]](data.readUint64(0))
if signature != [137.uint8, 80, 78, 71, 13, 10, 26, 10]:
failInvalid()
var
pos = 8 # After signature
counts = ChunkCounts()
header: PngHeader
palette: seq[array[3, uint8]]
imageData: seq[uint8]
prevChunkType: string
# First chunk must be IHDR
if data.readUint32(pos).swap() != 13 or
data.readStr(pos + 4, 4) != "IHDR":
failInvalid()
inc(pos, 8)
header = parseHeader(data[pos ..< pos + 13])
prevChunkType = "IHDR"
inc(pos, 13)
# if crc32(data[pos - 17 ..< pos]) != read32be(data, pos):
# failCRC()
inc(pos, 4) # CRC
while true:
if pos + 8 > data.len:
failInvalid()
let
chunkLen = data.readUint32(pos).swap().int
chunkType = data.readStr(pos + 4, 4)
inc(pos, 8)
if chunkLen > high(int32).int:
failInvalid()
if pos + chunkLen + 4 > data.len:
failInvalid()
case chunkType:
of "IHDR":
failInvalid()
of "PLTE":
inc counts.PLTE
if counts.PLTE > 1 or counts.IDAT > 0:
failInvalid()
palette = parsePalette(data[pos ..< pos + chunkLen])
of "IDAT":
inc counts.IDAT
if counts.IDAT > 1 and prevChunkType != "IDAT":
failInvalid()
if header.colorType == 3 and counts.PLTE == 0:
failInvalid()
let op = imageData.len
imageData.setLen(imageData.len + chunkLen)
copyMem(imageData[op].addr, data[pos].unsafeAddr, chunkLen)
of "IEND":
if chunkLen != 0:
failInvalid()
else:
let bytes = cast[seq[uint8]](chunkType)
if (bytes[0] and 0b00100000) == 0:
raise newException(
PixieError, "Unrecognized PNG critical chunk " & chunkType
)
inc(pos, chunkLen)
# if crc32(data[pos - chunkLen - 4 ..< pos]) != read32be(data, pos):
# failCRC()
inc(pos, 4) # CRC
prevChunkType = chunkType
if pos == data.len:
break
if prevChunkType != "IEND":
failInvalid()
result = Image()
result.width = header.width
result.height = header.height
result.data = parseImageData(header, palette, imageData)
proc encodePng*(
width, height, channels: int, data: pointer, len: int
): seq[uint8] =
## Encodes the image data into the PNG file format.
if width <= 0 or width > int32.high.int:
raise newException(PixieError, "Invalid PNG width")
if height <= 0 or height > int32.high.int:
raise newException(PixieError, "Invalid PNG height")
if len != width * height * channels:
raise newException(PixieError, "Invalid PNG data size")
let colorType = case channels:
of 1: 0.uint8
of 2: 4
of 3: 2
of 4: 6
else:
raise newException(PixieError, "Invalid PNG number of channels")
# Add the PNG file signature
result.add([137.uint8, 80, 78, 71, 13, 10, 26, 10])
# Add IHDR
result.addUint32(13.uint32.swap())
result.addStr("IHDR")
result.addUint32(width.uint32.swap())
result.addUint32(height.uint32.swap())
result.add(8.uint8)
result.add([colorType, 0, 0, 0])
result.addUint32(crc32(result[result.len - 17 ..< result.len]).swap())
# Add IDAT
# Add room for 1 byte before each row for the filter type.
var filtered = newSeq[uint8](width * height * channels + height)
for y in 0 ..< height:
filtered[y * width * channels + y] = 3 # Average
for x in 0 ..< width * channels:
# Move through the image data byte-by-byte
let
data = cast[ptr UncheckedArray[uint8]](data)
dataPos = y * width * channels + x
filteredPos = y * width * channels + y + 1 + x
var left, up: int
if x - channels >= 0:
left = data[dataPos - channels].int
if y - 1 >= 0:
up = data[(y - 1) * width * channels + x].int
let avg = ((left + up) div 2).uint8
filtered[filteredPos] = data[dataPos] - avg
let compressed =
try:
compress(filtered, DefaultCompression, dfZlib)
except ZippyError:
raise newException(
PixieError, "Unexpected error compressing PNG image data"
)
if compressed.len > int32.high.int:
raise newException(PixieError, "Compressed PNG image data too large")
result.addUint32(compressed.len.uint32.swap())
result.add(cast[seq[uint8]]("IDAT"))
result.add(compressed)
result.addUint32(
crc32(result[result.len - compressed.len - 4 ..< result.len]).swap()
)
# Add IEND
result.addUint32(0)
result.addStr("IEND")
result.addUint32(crc32(result[result.len - 4 ..< result.len]).swap())
proc encodePng*(image: Image): string =
## Encodes the image data into the PNG file format.
cast[string](encodePng(
image.width, image.height, 4, image.data[0].addr, image.data.len * 4
))
when defined(release):
{.pop.}

View file

@ -1,4 +1,4 @@
import strformat, chroma, chroma/blends, vmath
import chroma, chroma/blends, vmath
type
Image* = ref object

22
tests/benchmark_png.nim Normal file
View file

@ -0,0 +1,22 @@
import pixie/fileformats/png, stb_image/read as stbi, fidget/opengl/perf, nimPNG
let data = readFile("tests/data/lenna.png")
timeIt "pixie":
for i in 0 ..< 100:
discard decodePng(cast[seq[uint8]](data))
timeIt "nimPNG":
for i in 0 ..< 100:
discard decodePNG32(data)
timeIt "stb_image":
for i in 0 ..< 100:
var width, height, channels: int
discard loadFromMemory(
cast[seq[byte]](data),
width,
height,
channels,
stbi.RGBA
)

BIN
tests/data/lenna.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 463 KiB

View file

@ -0,0 +1,9 @@
PngSuite
--------
Permission to use, copy, modify and distribute these images for any
purpose and without fee is hereby granted.
(c) Willem van Schaik, 1996, 2011

View file

@ -0,0 +1,25 @@
PNGSUITE
----------------
testset for PNG-(de)coders
created by Willem van Schaik
------------------------------------
This is a collection of graphics images created to test the png applications
like viewers, converters and editors. All (as far as that is possible)
formats supported by the PNG standard are represented.
The suite consists of the following files:
- PngSuite.README - this file
- PngSuite.LICENSE - the PngSuite is freeware
- PngSuite.png - image with PngSuite logo
- PngSuite.tgz - archive of all PNG testfiles
- PngSuite.zip - same in .zip format for PCs
--------
(c) Willem van Schaik
willem@schaik.com
Calgary, April 2011

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 217 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 154 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 247 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 254 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 299 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 315 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 595 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 132 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 193 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 327 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 214 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 361 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 164 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 104 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 145 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 138 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 167 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 145 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 302 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 112 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 146 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 216 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 126 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 184 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 214 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 184 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 140 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 202 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 404 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 344 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 232 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 724 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 258 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 292 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 292 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 292 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 214 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 259 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 186 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 271 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 149 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 256 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 273 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 792 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 742 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 716 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 941 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 753 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 319 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 321 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 355 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 389 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 269 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 985 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 426 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 345 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 370 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 214 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 363 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 377 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 219 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 339 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 350 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 206 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 321 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 340 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 207 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 262 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 285 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 214 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 383 B

Some files were not shown because too many files have changed in this diff Show more