Merge pull request #6 from guzba/master

add more png support + tests
This commit is contained in:
treeform 2020-11-21 10:21:46 -08:00 committed by GitHub
commit 67ac0d4f38
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
3 changed files with 248 additions and 83 deletions

View file

@ -7,7 +7,7 @@ const
type
ChunkCounts = object
PLTE, IDAT: uint8
PLTE, IDAT, tRNS: uint8
PngHeader = object
width, height: int
@ -146,7 +146,9 @@ proc unfilter(
result[unfiteredIdx(x, y)] = value
proc parseImageData(
header: PngHeader, palette: seq[array[3, uint8]], data: seq[uint8]
header: PngHeader,
palette: seq[array[3, uint8]],
transparency, data: seq[uint8]
): seq[ColorRGBA] =
result.setLen(header.width * header.height)
@ -178,6 +180,7 @@ proc parseImageData(
case header.colorType:
of 0:
let special = if transparency.len == 2: transparency[1].int else: -1
var bytePos, bitPos: int
for y in 0 ..< header.height:
for x in 0 ..< header.width:
@ -204,8 +207,9 @@ proc parseImageData(
inc bytePos
bitPos = 0
let alpha = if value.int == special: 0 else: 255
result[x + y * header.width] = ColorRGBA(
r: value, g: value, b: value, a: 255
r: value, g: value, b: value, a: alpha.uint8
)
# If we move to a new row, skip to the next full byte
@ -213,13 +217,21 @@ proc parseImageData(
inc bytePos
bitPos = 0
of 2:
let special =
if transparency.len == 6:
ColorRGBA(
r: transparency[1], g: transparency[3], b: transparency[5], a: 255
)
else:
ColorRGBA()
var bytePos: int
for y in 0 ..< header.height:
for x in 0 ..< header.width:
let rgb = cast[ptr array[3, uint8]](unfiltered[bytePos].unsafeAddr)[]
result[x + y * header.width] = ColorRGBA(
r: rgb[0], g: rgb[1], b: rgb[2], a: 255
)
var rgba = ColorRGBA(r: rgb[0], g: rgb[1], b: rgb[2], a: 255)
if rgba == special:
rgba.a = 0
result[x + y * header.width] = rgba
bytePos += 3
of 3:
var bytePos, bitPos: int
@ -247,9 +259,15 @@ proc parseImageData(
if value.int >= palette.len:
failInvalid()
let rgb = palette[value]
let
rgb = palette[value]
transparency =
if transparency.len > value.int:
transparency[value]
else:
255
result[x + y * header.width] = ColorRGBA(
r: rgb[0], g: rgb[1], b: rgb[2], a: 255
r: rgb[0], g: rgb[1], b: rgb[2], a: transparency
)
# If we move to a new row, skip to the next full byte
@ -297,7 +315,7 @@ proc decodePng*(data: seq[uint8]): Image =
counts = ChunkCounts()
header: PngHeader
palette: seq[array[3, uint8]]
imageData: seq[uint8]
transparency, imageData: seq[uint8]
prevChunkType: string
# First chunk must be IHDR
@ -333,9 +351,26 @@ proc decodePng*(data: seq[uint8]): Image =
failInvalid()
of "PLTE":
inc counts.PLTE
if counts.PLTE > 1 or counts.IDAT > 0:
if counts.PLTE > 1 or counts.IDAT > 0 or counts.tRNS > 0:
failInvalid()
palette = parsePalette(data[pos ..< pos + chunkLen])
of "tRNS":
inc counts.tRNS
if counts.tRNS > 1 or counts.IDAT > 0:
failInvalid()
transparency = data[pos ..< pos + chunkLen]
case header.colorType:
of 0:
if transparency.len != 2:
failInvalid()
of 2:
if transparency.len != 6:
failInvalid()
of 3:
if transparency.len > palette.len:
failInvalid()
else:
failInvalid()
of "IDAT":
inc counts.IDAT
if counts.IDAT > 1 and prevChunkType != "IDAT":
@ -372,7 +407,7 @@ proc decodePng*(data: seq[uint8]): Image =
result = Image()
result.width = header.width
result.height = header.height
result.data = parseImageData(header, palette, imageData)
result.data = parseImageData(header, palette, transparency, imageData)
proc decodePng*(data: string): Image {.inline.} =
decodePng(cast[seq[uint8]](data))

View file

@ -1,73 +1,196 @@
const pngSuiteFiles* = [
# Basic
"basn0g01", # black & white
"basn0g02", # 2 bit (4 level) grayscale
"basn0g04", # 4 bit (16 level) grayscale
"basn0g08", # 8 bit (256 level) grayscale
# "basn0g16", # 16 bit (64k level) grayscale
"basn2c08", # 3x8 bits rgb color
# "basn2c16", # 3x16 bits rgb color
"basn3p01", # 1 bit (2 color) paletted
"basn3p02", # 2 bit (4 color) paletted
"basn3p04", # 4 bit (16 color) paletted
"basn3p08", # 8 bit (256 color) paletted
"basn4a08", # 8 bit grayscale + 8 bit alpha-channel
# "basn4a16", # 16 bit grayscale + 16 bit alpha-channel
"basn6a08", # 3x8 bits rgb color + 8 bit alpha-channel
# "basn6a16", # 3x16 bits rgb color + 16 bit alpha-channel
# http://www.schaik.com/pngsuite/
# Interlaced
# "basi0g01", # black & white
# "basi0g02", # 2 bit (4 level) grayscale
# "basi0g04", # 4 bit (16 level) grayscale
# "basi0g08", # 8 bit (256 level) grayscale
# "basi0g16", # 16 bit (64k level) grayscale
# "basi2c08", # 3x8 bits rgb color
# "basi2c16", # 3x16 bits rgb color
# "basi3p01", # 1 bit (2 color) paletted
# "basi3p02", # 2 bit (4 color) paletted
# "basi3p04", # 4 bit (16 color) paletted
# "basi3p08", # 8 bit (256 color) paletted
# "basi4a08", # 8 bit grayscale + 8 bit alpha-channel
# "basi4a16", # 16 bit grayscale + 16 bit alpha-channel
# "basi6a08", # 3x8 bits rgb color + 8 bit alpha-channel
# "basi6a16", # 3x16 bits rgb color + 16 bit alpha-channel
const
pngSuiteFiles* = [
# Basic
"basn0g01", # black & white
"basn0g02", # 2 bit (4 level) grayscale
"basn0g04", # 4 bit (16 level) grayscale
"basn0g08", # 8 bit (256 level) grayscale
# "basn0g16", # 16 bit (64k level) grayscale
"basn2c08", # 3x8 bits rgb color
# "basn2c16", # 3x16 bits rgb color
"basn3p01", # 1 bit (2 color) paletted
"basn3p02", # 2 bit (4 color) paletted
"basn3p04", # 4 bit (16 color) paletted
"basn3p08", # 8 bit (256 color) paletted
"basn4a08", # 8 bit grayscale + 8 bit alpha-channel
# "basn4a16", # 16 bit grayscale + 16 bit alpha-channel
"basn6a08", # 3x8 bits rgb color + 8 bit alpha-channel
# "basn6a16", # 3x16 bits rgb color + 16 bit alpha-channel
# Odd sizes
# "s01i3p01", # 1x1 paletted file, interlaced
"s01n3p01", # 1x1 paletted file, no interlacing
# "s02i3p01", # 2x2 paletted file, interlaced
"s02n3p01", # 2x2 paletted file, no interlacing
# "s03i3p01", # 3x3 paletted file, interlaced
"s03n3p01", # 3x3 paletted file, no interlacing
# "s04i3p01", # 4x4 paletted file, interlaced
"s04n3p01", # 4x4 paletted file, no interlacing
# "s05i3p02", # 5x5 paletted file, interlaced
"s05n3p02", # 5x5 paletted file, no interlacing
# "s06i3p02", # 6x6 paletted file, interlaced
"s06n3p02", # 6x6 paletted file, no interlacing
# "s07i3p02", # 7x7 paletted file, interlaced
"s07n3p02", # 7x7 paletted file, no interlacing
# "s08i3p02", # 8x8 paletted file, interlaced
"s08n3p02", # 8x8 paletted file, no interlacing
# "s09i3p02", # 9x9 paletted file, interlaced
"s09n3p02", # 9x9 paletted file, no interlacing
# "s32i3p04", # 32x32 paletted file, interlaced
"s32n3p04", # 32x32 paletted file, no interlacing
# "s33i3p04", # 33x33 paletted file, interlaced
"s33n3p04", # 33x33 paletted file, no interlacing
# "s34i3p04", # 34x34 paletted file, interlaced
"s34n3p04", # 34x34 paletted file, no interlacing
# "s35i3p04", # 35x35 paletted file, interlaced
"s35n3p04", # 35x35 paletted file, no interlacing
# "s36i3p04", # 36x36 paletted file, interlaced
"s36n3p04", # 36x36 paletted file, no interlacing
# "s37i3p04", # 37x37 paletted file, interlaced
"s37n3p04", # 37x37 paletted file, no interlacing
# "s38i3p04", # 38x38 paletted file, interlaced
"s38n3p04", # 38x38 paletted file, no interlacing
# "s39i3p04", # 39x39 paletted file, interlaced
"s39n3p04", # 39x39 paletted file, no interlacing
# "s40i3p04", # 40x40 paletted file, interlaced
"s40n3p04", # 40x40 paletted file, no interlacing
]
# Interlaced
# "basi0g01", # black & white
# "basi0g02", # 2 bit (4 level) grayscale
# "basi0g04", # 4 bit (16 level) grayscale
# "basi0g08", # 8 bit (256 level) grayscale
# "basi0g16", # 16 bit (64k level) grayscale
# "basi2c08", # 3x8 bits rgb color
# "basi2c16", # 3x16 bits rgb color
# "basi3p01", # 1 bit (2 color) paletted
# "basi3p02", # 2 bit (4 color) paletted
# "basi3p04", # 4 bit (16 color) paletted
# "basi3p08", # 8 bit (256 color) paletted
# "basi4a08", # 8 bit grayscale + 8 bit alpha-channel
# "basi4a16", # 16 bit grayscale + 16 bit alpha-channel
# "basi6a08", # 3x8 bits rgb color + 8 bit alpha-channel
# "basi6a16", # 3x16 bits rgb color + 16 bit alpha-channel
# Odd sizes
# "s01i3p01", # 1x1 paletted file, interlaced
"s01n3p01", # 1x1 paletted file, no interlacing
# "s02i3p01", # 2x2 paletted file, interlaced
"s02n3p01", # 2x2 paletted file, no interlacing
# "s03i3p01", # 3x3 paletted file, interlaced
"s03n3p01", # 3x3 paletted file, no interlacing
# "s04i3p01", # 4x4 paletted file, interlaced
"s04n3p01", # 4x4 paletted file, no interlacing
# "s05i3p02", # 5x5 paletted file, interlaced
"s05n3p02", # 5x5 paletted file, no interlacing
# "s06i3p02", # 6x6 paletted file, interlaced
"s06n3p02", # 6x6 paletted file, no interlacing
# "s07i3p02", # 7x7 paletted file, interlaced
"s07n3p02", # 7x7 paletted file, no interlacing
# "s08i3p02", # 8x8 paletted file, interlaced
"s08n3p02", # 8x8 paletted file, no interlacing
# "s09i3p02", # 9x9 paletted file, interlaced
"s09n3p02", # 9x9 paletted file, no interlacing
# "s32i3p04", # 32x32 paletted file, interlaced
"s32n3p04", # 32x32 paletted file, no interlacing
# "s33i3p04", # 33x33 paletted file, interlaced
"s33n3p04", # 33x33 paletted file, no interlacing
# "s34i3p04", # 34x34 paletted file, interlaced
"s34n3p04", # 34x34 paletted file, no interlacing
# "s35i3p04", # 35x35 paletted file, interlaced
"s35n3p04", # 35x35 paletted file, no interlacing
# "s36i3p04", # 36x36 paletted file, interlaced
"s36n3p04", # 36x36 paletted file, no interlacing
# "s37i3p04", # 37x37 paletted file, interlaced
"s37n3p04", # 37x37 paletted file, no interlacing
# "s38i3p04", # 38x38 paletted file, interlaced
"s38n3p04", # 38x38 paletted file, no interlacing
# "s39i3p04", # 39x39 paletted file, interlaced
"s39n3p04", # 39x39 paletted file, no interlacing
# "s40i3p04", # 40x40 paletted file, interlaced
"s40n3p04", # 40x40 paletted file, no interlacing
# "bgai4a08", # 8 bit grayscale, alpha, no background chunk, interlaced
# "bgai4a16", # 16 bit grayscale, alpha, no background chunk, interlaced
"bgan6a08", # 3x8 bits rgb color, alpha, no background chunk
# "bgan6a16", # 3x16 bits rgb color, alpha, no background chunk
"bgbn4a08", # 8 bit grayscale, alpha, black background chunk
# "bggn4a16", # 16 bit grayscale, alpha, gray background chunk
"bgwn6a08", # 3x8 bits rgb color, alpha, white background chunk
# "bgyn6a16", # 3x16 bits rgb color, alpha, yellow background chunk
# "tbbn0g04", # transparent, black background chunk
# # "tbbn2c16", # transparent, blue background chunk
"tbbn3p08", # transparent, black background chunk
# # "tbgn2c16", # transparent, green background chunk
"tbgn3p08", # transparent, light-gray background chunk
"tbrn2c08", # transparent, red background chunk
# # "tbwn0g16", # transparent, white background chunk
"tbwn3p08", # transparent, white background chunk
"tbyn3p08", # transparent, yellow background chunk
"tp0n0g08", # not transparent for reference (logo on gray)
"tp0n2c08", # not transparent for reference (logo on gray)
"tp0n3p08", # not transparent for reference (logo on gray)
"tp1n3p08", # transparent, but no background chunk
"tm3n3p02", # multiple levels of transparency, 3 entries
# "g03n0g16", # grayscale, file-gamma = 0.35
"g03n2c08", # color, file-gamma = 0.35
"g03n3p04", # paletted, file-gamma = 0.35
# "g04n0g16", # grayscale, file-gamma = 0.45
"g04n2c08", # color, file-gamma = 0.45
"g04n3p04", # paletted, file-gamma = 0.45
# "g05n0g16", # grayscale, file-gamma = 0.55
"g05n2c08", # color, file-gamma = 0.55
"g05n3p04", # paletted, file-gamma = 0.55
# "g07n0g16", # grayscale, file-gamma = 0.70
"g07n2c08", # color, file-gamma = 0.70
"g07n3p04", # paletted, file-gamma = 0.70
# "g10n0g16", # grayscale, file-gamma = 1.00
"g10n2c08", # color, file-gamma = 1.00
"g10n3p04", # paletted, file-gamma = 1.00
# "g25n0g16", # grayscale, file-gamma = 2.50
"g25n2c08", # color, file-gamma = 2.50
"g25n3p04", # paletted, file-gamma = 2.50
"f00n0g08", # grayscale, no interlacing, filter-type 0
"f00n2c08", # color, no interlacing, filter-type 0
"f01n0g08", # grayscale, no interlacing, filter-type 1
"f01n2c08", # color, no interlacing, filter-type 1
"f02n0g08", # grayscale, no interlacing, filter-type 2
"f02n2c08", # color, no interlacing, filter-type 2
"f03n0g08", # grayscale, no interlacing, filter-type 3
"f03n2c08", # color, no interlacing, filter-type 3
"f04n0g08", # grayscale, no interlacing, filter-type 4
"f04n2c08", # color, no interlacing, filter-type 4
"f99n0g04", # bit-depth 4, filter changing per scanline
# "pp0n2c16", # six-cube palette-chunk in true-color image
"pp0n6a08", # six-cube palette-chunk in true-color+alpha image
"ps1n0g08", # six-cube suggested palette (1 byte) in grayscale image
# "ps1n2c16", # six-cube suggested palette (1 byte) in true-color image
"ps2n0g08", # six-cube suggested palette (2 bytes) in grayscale image
# "ps2n2c16", # six-cube suggested palette (2 bytes) in true-color image
"ccwn2c08", # chroma chunk w:0.3127,0.3290 r:0.64,0.33 g:0.30,0.60 b:0.15,0.06
"ccwn3p08", # chroma chunk w:0.3127,0.3290 r:0.64,0.33 g:0.30,0.60 b:0.15,0.06
"cdfn2c08", # physical pixel dimensions, 8x32 flat pixels
"cdhn2c08", # physical pixel dimensions, 32x8 high pixels
"cdsn2c08", # physical pixel dimensions, 8x8 square pixels
"cdun2c08", # physical pixel dimensions, 1000 pixels per 1 meter
"ch1n3p04", # histogram 15 colors
"ch2n3p08", # histogram 256 colors
"cm0n0g04", # modification time, 01-jan-2000 12:34:56
"cm7n0g04", # modification time, 01-jan-1970 00:00:00
"cm9n0g04", # modification time, 31-dec-1999 23:59:59
# "cs3n2c16", # color, 13 significant bits
"cs3n3p08", # paletted, 3 significant bits
"cs5n2c08", # color, 5 significant bits
"cs5n3p08", # paletted, 5 significant bits
"cs8n2c08", # color, 8 significant bits (reference)
"cs8n3p08", # paletted, 8 significant bits (reference)
"ct0n0g04", # no textual data
"ct1n0g04", # with textual data
"ctzn0g04", # with compressed textual data
"cten0g04", # international UTF-8, english
"ctfn0g04", # international UTF-8, finnish
"ctgn0g04", # international UTF-8, greek
"cthn0g04", # international UTF-8, hindi
"ctjn0g04", # international UTF-8, japanese
"exif2c08", # chunk with jpeg exif data
# "oi1n0g16", # grayscale mother image with 1 idat-chunk
# "oi1n2c16", # color mother image with 1 idat-chunk
# "oi2n0g16", # grayscale image with 2 idat-chunks
# "oi2n2c16", # color image with 2 idat-chunks
# "oi4n0g16", # grayscale image with 4 unequal sized idat-chunks
# "oi4n2c16", # color image with 4 unequal sized idat-chunks
# "oi9n0g16", # grayscale image with all idat-chunks length one
# "oi9n2c16", # color image with all idat-chunks length one
"z00n2c08", # color, no interlacing, compression level 0 (none)
"z03n2c08", # color, no interlacing, compression level 3
"z06n2c08", # color, no interlacing, compression level 6 (default)
"z09n2c08", # color, no interlacing, compression level 9 (maximum)
]
pngSuiteCorruptedFiles* = [
"xs1n0g01", # signature byte 1 MSBit reset to zero
"xs2n0g01", # signature byte 2 is a 'Q'
"xs4n0g01", # signature byte 4 lowercase
"xs7n0g01", # 7th byte a space instead of control-Z
"xcrn0g04", # added cr bytes
"xlfn0g04", # added lf bytes
# "xhdn0g08", # incorrect IHDR checksum
"xc1n0g08", # color type 1
"xc9n2c08", # color type 9
"xd0n2c08", # bit-depth 0
"xd3n2c08", # bit-depth 3
"xd9n2c08", # bit-depth 99
"xdtn0g01", # missing IDAT chunk
# "xcsn0g01" # incorrect IDAT checksum
]

View file

@ -1,4 +1,4 @@
import pixie/fileformats/png, strformat, pngsuite
import pixie/fileformats/png, strformat, pngsuite, pixie/common
for file in pngSuiteFiles:
let
@ -20,3 +20,10 @@ for channels in 1 .. 4:
components[i] = (x * 16).uint8
data.add(components)
let encoded = encodePng(16, 16, channels, data[0].addr, data.len)
for file in pngSuiteCorruptedFiles:
try:
discard decodePng(readFile(&"tests/data/pngsuite/{file}.png"))
doAssert false
except PixieError:
discard