add openArray API

This commit is contained in:
jangko 2020-06-11 20:58:37 +07:00
parent a3d104068b
commit e91cfff314
No known key found for this signature in database
GPG Key ID: 31702AE10541E6B9
6 changed files with 255 additions and 185 deletions

View File

@ -30,6 +30,8 @@ import nimPNG/[buffer, nimz, filters, results]
import strutils
export typetraits, results
const
NIM_PNG_VERSION = "0.3.0"
@ -254,7 +256,7 @@ proc `$`*(tag: PNGChunkType): string =
result[2] = chr(uint32(t shr 8) and 0xFF)
result[3] = chr(uint32(t) and 0xFF)
proc `==`(a, b: PNGChunkType): bool = int(a) == int(b)
proc `==`*(a, b: PNGChunkType): bool = int(a) == int(b)
#proc isAncillary(a: PNGChunkType): bool = (int(a) and (32 shl 24)) != 0
#proc isPrivate(a: PNGChunkType): bool = (int(a) and (32 shl 16)) != 0
#proc isSafeToCopy(a: PNGChunkType): bool = (int(a) and 32) != 0
@ -271,6 +273,18 @@ proc crc32(crc: uint32, buf: string): uint32 =
result = not crcu32
template newStorage[T](size: int): T =
when T is string:
newString(size)
else:
newSeq[uint8](size)
template newStorageOfCap[T](size: int): T =
when T is string:
newStringOfCap(size)
else:
newSeqOfCap[uint8](size)
const
PNGSignature = signatureMaker()
IHDR = makeChunkType("IHDR")
@ -381,7 +395,7 @@ proc readByte(s: PNGChunk): int =
inc s.pos
template readEnum(s: PNGChunk, T: type): untyped =
let typ = s.readByte.int
let typ = readByte(s).int
if typ < low(T).int or typ > high(T).int:
raise PNGFatal("Wrong " & T.name & " value " & $typ)
T(typ)
@ -949,7 +963,8 @@ proc parsePNG[T](s: Stream, settings: PNGDecoder): PNG[T] =
if not idat.validateChunk(png): raise PNGFatal("bad IDAT")
result = png
proc postProcessScanLines[T](png: PNG; header: PNGHeader, w, h: int; input, output: var openArray[T]) =
proc postProcessScanLines[T, A, B](png: PNG[T]; header: PNGHeader, w, h: int; input: var openArray[A],
output: var openArray[B]) =
# This function converts the filtered-padded-interlaced data
# into pure 2D image buffer with the PNG's colorType.
# Steps:
@ -993,25 +1008,25 @@ proc postProcessScanLines[T](png: PNG; header: PNGHeader, w, h: int; input, outp
adam7Deinterlace(output, input, w, h, bpp)
proc postProcessScanLines(png: PNG) =
proc postProcessScanLines[T](png: PNG[T]) =
var header = PNGHeader(png.getChunk(IHDR))
let w = header.width
let h = header.height
var idat = PNGData(png.getChunk(IDAT))
png.pixels = newString(idatRawSize(header.width, header.height, header))
png.pixels = newStorage[T](idatRawSize(header.width, header.height, header))
png.postProcessScanLines(header, w, h,
idat.idat.toOpenArray(0, idat.idat.len-1), # input
png.pixels.toOpenArray(0, png.pixels.len-1) # output
)
proc postProcessScanLines(png: PNG, ctl: APNGFrameControl, data: var string) =
proc postProcessScanLines[T](png: PNG[T], ctl: APNGFrameControl, data: var string) =
# we use var string here to avoid realloc
# coz we use the input as output too
var header = PNGHeader(png.getChunk(IHDR))
let w = ctl.width
let h = ctl.height
png.apngPixels.add newString(idatRawSize(ctl.width, ctl.height, header))
png.apngPixels.add newStorage[T](idatRawSize(ctl.width, ctl.height, header))
png.postProcessScanLines(header, w, h,
data.toOpenArray(0, data.len-1), # input
@ -1112,9 +1127,9 @@ proc RGBFromRGB16[T](output: var openArray[T], input: openArray[T], numPixels: i
for i in 0..<numPixels:
let x = i * 3
let y = i * 6
output[x] = input[y]
output[x+1] = input[y+2]
output[x+2] = input[y+4]
output[x] = T(input[y])
output[x+1] = T(input[y+2])
output[x+2] = T(input[y+4])
proc RGBFromPalette8[T](output: var openArray[T], input: openArray[T], numPixels: int, mode: PNGColorMode) =
for i in 0..<numPixels:
@ -1123,13 +1138,13 @@ proc RGBFromPalette8[T](output: var openArray[T], input: openArray[T], numPixels
if index >= mode.paletteSize:
# This is an error according to the PNG spec, but most PNG decoders make it black instead.
# Done here too, slightly faster due to no error handling needed.
output[x] = chr(0)
output[x+1] = chr(0)
output[x+2] = chr(0)
output[x] = T(0)
output[x+1] = T(0)
output[x+2] = T(0)
else:
output[x] = mode.palette[index].r
output[x+1] = mode.palette[index].g
output[x+2] = mode.palette[index].b
output[x] = T(mode.palette[index].r)
output[x+1] = T(mode.palette[index].g)
output[x+2] = T(mode.palette[index].b)
proc RGBFromPalette124[T](output: var openArray[T], input: openArray[T], numPixels: int, mode: PNGColorMode) =
var obp = 0
@ -1139,13 +1154,13 @@ proc RGBFromPalette124[T](output: var openArray[T], input: openArray[T], numPixe
if index >= mode.paletteSize:
# This is an error according to the PNG spec, but most PNG decoders make it black instead.
# Done here too, slightly faster due to no error handling needed.
output[x] = chr(0)
output[x+1] = chr(0)
output[x+2] = chr(0)
output[x] = T(0)
output[x+1] = T(0)
output[x+2] = T(0)
else:
output[x] = mode.palette[index].r
output[x+1] = mode.palette[index].g
output[x+2] = mode.palette[index].b
output[x] = T(mode.palette[index].r)
output[x+1] = T(mode.palette[index].g)
output[x+2] = T(mode.palette[index].b)
proc RGBFromGreyAlpha8[T](output: var openArray[T], input: openArray[T], numPixels: int, mode: PNGColorMode) =
for i in 0..<numPixels:
@ -1185,8 +1200,8 @@ proc RGBAFromGrey8[T](output: var openArray[T], input: openArray[T], numPixels:
output[x] = input[i]
output[x+1] = input[i]
output[x+2] = input[i]
if mode.keyDefined and (ord(input[i]) == mode.keyR): output[x+3] = chr(0)
else: output[x+3] = chr(255)
if mode.keyDefined and (ord(input[i]) == mode.keyR): output[x+3] = T(0)
else: output[x+3] = T(255)
proc RGBAFromGrey16[T](output: var openArray[T], input: openArray[T], numPixels: int, mode: PNGColorMode) =
for i in 0..<numPixels:
@ -1196,21 +1211,21 @@ proc RGBAFromGrey16[T](output: var openArray[T], input: openArray[T], numPixels:
output[x+1] = input[y]
output[x+2] = input[y]
let keyR = 256 * ord(input[y + 0]) + ord(input[y + 1])
if mode.keyDefined and (keyR == mode.keyR): output[x+3] = chr(0)
else: output[x+3] = chr(255)
if mode.keyDefined and (keyR == mode.keyR): output[x+3] = T(0)
else: output[x+3] = T(255)
proc RGBAFromGrey124[T](output: var openArray[T], input: openArray[T], numPixels: int, mode: PNGColorMode) =
var highest = ((1 shl mode.bitDepth) - 1) #highest possible value for this bit depth
var obp = 0
for i in 0..<numPixels:
let val = readBitsFromReversedStream(obp, input, mode.bitDepth)
let value = chr((val * 255) div highest)
let value = T((val * 255) div highest)
let x = i * 4
output[x] = value
output[x+1] = value
output[x+2] = value
if mode.keyDefined and (ord(val) == mode.keyR): output[x+3] = chr(0)
else: output[x+3] = chr(255)
output[x] = T(value)
output[x+1] = T(value)
output[x+2] = T(value)
if mode.keyDefined and (ord(val) == mode.keyR): output[x+3] = T(0)
else: output[x+3] = T(255)
proc RGBAFromRGB8[T](output: var openArray[T], input: openArray[T], numPixels: int, mode: PNGColorMode) =
for i in 0..<numPixels:
@ -1220,8 +1235,8 @@ proc RGBAFromRGB8[T](output: var openArray[T], input: openArray[T], numPixels: i
output[x+1] = input[y+1]
output[x+2] = input[y+2]
if mode.keyDefined and (mode.keyR == ord(input[y])) and
(mode.keyG == ord(input[y+1])) and (mode.keyB == ord(input[y+2])): output[x+3] = chr(0)
else: output[x+3] = chr(255)
(mode.keyG == ord(input[y+1])) and (mode.keyB == ord(input[y+2])): output[x+3] = T(0)
else: output[x+3] = T(255)
proc RGBAFromRGB16[T](output: var openArray[T], input: openArray[T], numPixels: int, mode: PNGColorMode) =
for i in 0..<numPixels:
@ -1234,8 +1249,8 @@ proc RGBAFromRGB16[T](output: var openArray[T], input: openArray[T], numPixels:
let keyG = 256 * ord(input[y+2]) + ord(input[y+3])
let keyB = 256 * ord(input[y+4]) + ord(input[y+5])
if mode.keyDefined and (mode.keyR == keyR) and
(mode.keyG == keyG) and (mode.keyB == keyB): output[x+3] = chr(0)
else: output[x+3] = chr(255)
(mode.keyG == keyG) and (mode.keyB == keyB): output[x+3] = T(0)
else: output[x+3] = T(255)
proc RGBAFromPalette8[T](output: var openArray[T], input: openArray[T], numPixels: int, mode: PNGColorMode) =
for i in 0..<numPixels:
@ -1244,15 +1259,15 @@ proc RGBAFromPalette8[T](output: var openArray[T], input: openArray[T], numPixel
if index >= mode.paletteSize:
# This is an error according to the PNG spec, but most PNG decoders make it black instead.
# Done here too, slightly faster due to no error handling needed.
output[x] = chr(0)
output[x+1] = chr(0)
output[x+2] = chr(0)
output[x+3] = chr(0)
output[x] = T(0)
output[x+1] = T(0)
output[x+2] = T(0)
output[x+3] = T(0)
else:
output[x] = mode.palette[index].r
output[x+1] = mode.palette[index].g
output[x+2] = mode.palette[index].b
output[x+3] = mode.palette[index].a
output[x] = T(mode.palette[index].r)
output[x+1] = T(mode.palette[index].g)
output[x+2] = T(mode.palette[index].b)
output[x+3] = T(mode.palette[index].a)
proc RGBAFromPalette124[T](output: var openArray[T], input: openArray[T], numPixels: int, mode: PNGColorMode) =
var obp = 0
@ -1262,15 +1277,15 @@ proc RGBAFromPalette124[T](output: var openArray[T], input: openArray[T], numPix
if index >= mode.paletteSize:
# This is an error according to the PNG spec, but most PNG decoders make it black instead.
# Done here too, slightly faster due to no error handling needed.
output[x] = chr(0)
output[x+1] = chr(0)
output[x+2] = chr(0)
output[x+3] = chr(0)
output[x] = T(0)
output[x+1] = T(0)
output[x+2] = T(0)
output[x+3] = T(0)
else:
output[x] = mode.palette[index].r
output[x+1] = mode.palette[index].g
output[x+2] = mode.palette[index].b
output[x+3] = mode.palette[index].a
output[x] = T(mode.palette[index].r)
output[x+1] = T(mode.palette[index].g)
output[x+2] = T(mode.palette[index].b)
output[x+3] = T(mode.palette[index].a)
proc RGBAFromGreyAlpha8[T](output: var openArray[T], input: openArray[T], numPixels: int, mode: PNGColorMode) =
for i in 0..<numPixels:
@ -1324,52 +1339,52 @@ proc hash*(c: RGBA8): Hash =
result = !$(h)
proc RGBA8FromGrey8[T](p: var RGBA8, input: openArray[T], px: int, mode: PNGColorMode) =
p.r = input[px]
p.g = input[px]
p.b = input[px]
if mode.keyDefined and (ord(p.r) == mode.keyR): p.a = chr(0)
else: p.a = chr(255)
p.r = char(input[px])
p.g = char(input[px])
p.b = char(input[px])
if mode.keyDefined and (ord(p.r) == mode.keyR): p.a = char(0)
else: p.a = char(255)
proc RGBA8FromGrey16[T](p: var RGBA8, input: openArray[T], px: int, mode: PNGColorMode) =
let i = px * 2
let keyR = 256 * ord(input[i]) + ord(input[i + 1])
p.r = input[i]
p.g = input[i]
p.b = input[i]
if mode.keyDefined and (keyR == mode.keyR): p.a = chr(0)
else: p.a = chr(255)
p.r = char(input[i])
p.g = char(input[i])
p.b = char(input[i])
if mode.keyDefined and (keyR == mode.keyR): p.a = char(0)
else: p.a = char(255)
proc RGBA8FromGrey124[T](p: var RGBA8, input: openArray[T], px: int, mode: PNGColorMode) =
let highest = ((1 shl mode.bitDepth) - 1) #highest possible value for this bit depth
var obp = px * mode.bitDepth
let val = readBitsFromReversedStream(obp, input, mode.bitDepth)
let value = chr((val * 255) div highest)
let value = char((val * 255) div highest)
p.r = value
p.g = value
p.b = value
if mode.keyDefined and (ord(val) == mode.keyR): p.a = chr(0)
else: p.a = chr(255)
if mode.keyDefined and (ord(val) == mode.keyR): p.a = char(0)
else: p.a = char(255)
proc RGBA8FromRGB8[T](p: var RGBA8, input: openArray[T], px: int, mode: PNGColorMode) =
let y = px * 3
p.r = input[y]
p.g = input[y+1]
p.b = input[y+2]
p.r = char(input[y])
p.g = char(input[y+1])
p.b = char(input[y+2])
if mode.keyDefined and (mode.keyR == ord(input[y])) and
(mode.keyG == ord(input[y+1])) and (mode.keyB == ord(input[y+2])): p.a = chr(0)
else: p.a = chr(255)
(mode.keyG == ord(input[y+1])) and (mode.keyB == ord(input[y+2])): p.a = char(0)
else: p.a = char(255)
proc RGBA8FromRGB16[T](p: var RGBA8, input: openArray[T], px: int, mode: PNGColorMode) =
let y = px * 6
p.r = input[y]
p.g = input[y+2]
p.b = input[y+4]
p.r = char(input[y])
p.g = char(input[y+2])
p.b = char(input[y+4])
let keyR = 256 * ord(input[y]) + ord(input[y+1])
let keyG = 256 * ord(input[y+2]) + ord(input[y+3])
let keyB = 256 * ord(input[y+4]) + ord(input[y+5])
if mode.keyDefined and (mode.keyR == keyR) and
(mode.keyG == keyG) and (mode.keyB == keyB): p.a = chr(0)
else: p.a = chr(255)
(mode.keyG == keyG) and (mode.keyB == keyB): p.a = char(0)
else: p.a = char(255)
proc RGBA8FromPalette8[T](p: var RGBA8, input: openArray[T], px: int, mode: PNGColorMode) =
let index = ord(input[px])
@ -1377,10 +1392,10 @@ proc RGBA8FromPalette8[T](p: var RGBA8, input: openArray[T], px: int, mode: PNGC
# This is an error according to the PNG spec,
# but common PNG decoders make it black instead.
# Done here too, slightly faster due to no error handling needed.
p.r = chr(0)
p.g = chr(0)
p.b = chr(0)
p.a = chr(255)
p.r = char(0)
p.g = char(0)
p.b = char(0)
p.a = char(255)
else:
p.r = mode.palette[index].r
p.g = mode.palette[index].g
@ -1394,10 +1409,10 @@ proc RGBA8FromPalette124[T](p: var RGBA8, input: openArray[T], px: int, mode: PN
# This is an error according to the PNG spec,
# but common PNG decoders make it black instead.
# Done here too, slightly faster due to no error handling needed.
p.r = chr(0)
p.g = chr(0)
p.b = chr(0)
p.a = chr(255)
p.r = char(0)
p.g = char(0)
p.b = char(0)
p.a = char(255)
else:
p.r = mode.palette[index].r
p.g = mode.palette[index].g
@ -1406,33 +1421,33 @@ proc RGBA8FromPalette124[T](p: var RGBA8, input: openArray[T], px: int, mode: PN
proc RGBA8FromGreyAlpha8[T](p: var RGBA8, input: openArray[T], px: int, mode: PNGColorMode) =
let i = px * 2
let val = input[i]
let val = char(input[i])
p.r = val
p.g = val
p.b = val
p.a = input[i+1]
p.a = char(input[i+1])
proc RGBA8FromGreyAlpha16[T](p: var RGBA8, input: openArray[T], px: int, mode: PNGColorMode) =
let i = px * 4
let val = input[i]
let val = char(input[i])
p.r = val
p.g = val
p.b = val
p.a = input[i+2]
p.a = char(input[i+2])
proc RGBA8FromRGBA8[T](p: var RGBA8, input: openArray[T], px: int, mode: PNGColorMode) =
let i = px * 4
p.r = input[i]
p.g = input[i+1]
p.b = input[i+2]
p.a = input[i+3]
p.r = char(input[i])
p.g = char(input[i+1])
p.b = char(input[i+2])
p.a = char(input[i+3])
proc RGBA8FromRGBA16[T](p: var RGBA8, input: openArray[T], px: int, mode: PNGColorMode) =
let i = px * 8
p.r = input[i]
p.g = input[i+2]
p.b = input[i+4]
p.a = input[i+6]
p.r = chaR(input[i])
p.g = chaR(input[i+2])
p.b = chaR(input[i+4])
p.a = chaR(input[i+6])
proc RGBA16FromGrey[T](p: var RGBA16, input: openArray[T], px: int, mode: PNGColorMode) =
let i = px * 2
@ -1468,12 +1483,12 @@ proc RGBA16FromRGBA[T](p: var RGBA16, input: openArray[T], px: int, mode: PNGCol
p.a = 256'u16 * uint16(input[i+6]) + uint16(input[i+7])
proc RGBA8ToGrey8[T](p: RGBA8, output: var openArray[T], px: int, mode: PNGColorMode, ct: ColorTree8) =
output[px] = p.r
output[px] = T(p.r)
proc RGBA8ToGrey16[T](p: RGBA8, output: var openArray[T], px: int, mode: PNGColorMode, ct: ColorTree8) =
let i = px * 2
output[i] = p.r
output[i+1] = p.r
output[i] = T(p.r)
output[i+1] = T(p.r)
proc RGBA8ToGrey124[T](p: RGBA8, output: var openArray[T], px: int, mode: PNGColorMode, ct: ColorTree8) =
# take the most significant bits of grey
@ -1482,18 +1497,18 @@ proc RGBA8ToGrey124[T](p: RGBA8, output: var openArray[T], px: int, mode: PNGCol
proc RGBA8ToRGB8[T](p: RGBA8, output: var openArray[T], px: int, mode: PNGColorMode, ct: ColorTree8) =
let i = px * 3
output[i] = p.r
output[i+1] = p.g
output[i+2] = p.b
output[i] = T(p.r)
output[i+1] = T(p.g)
output[i+2] = T(p.b)
proc RGBA8ToRGB16[T](p: RGBA8, output: var openArray[T], px: int, mode: PNGColorMode, ct: ColorTree8) =
let i = px * 6
output[i] = p.r
output[i+2] = p.g
output[i+4] = p.b
output[i+1] = p.r
output[i+3] = p.g
output[i+5] = p.b
output[i] = T(p.r)
output[i+2] = T(p.g)
output[i+4] = T(p.b)
output[i+1] = T(p.r)
output[i+3] = T(p.g)
output[i+5] = T(p.b)
proc RGBA8ToPalette8[T](p: RGBA8, output: var openArray[T], px: int, mode: PNGColorMode, ct: ColorTree8) =
output[px] = T(ct[p])
@ -1503,37 +1518,37 @@ proc RGBA8ToPalette124[T](p: RGBA8, output: var openArray[T], px: int, mode: PNG
proc RGBA8ToGreyAlpha8[T](p: RGBA8, output: var openArray[T], px: int, mode: PNGColorMode, ct: ColorTree8) =
let i = px * 2
output[i] = p.r
output[i+1] = p.a
output[i] = T(p.r)
output[i+1] = T(p.a)
proc RGBA8ToGreyAlpha16[T](p: RGBA8, output: var openArray[T], px: int, mode: PNGColorMode, ct: ColorTree8) =
let i = px * 4
output[i] = p.r
output[i+1] = p.r
output[i+2] = p.a
output[i+3] = p.a
output[i] = T(p.r)
output[i+1] = T(p.r)
output[i+2] = T(p.a)
output[i+3] = T(p.a)
proc RGBA8ToRGBA8[T](p: RGBA8, output: var openArray[T], px: int, mode: PNGColorMode, ct: ColorTree8) =
let i = px * 4
output[i] = p.r
output[i+1] = p.g
output[i+2] = p.b
output[i+3] = p.a
output[i] = T(p.r)
output[i+1] = T(p.g)
output[i+2] = T(p.b)
output[i+3] = T(p.a)
proc RGBA8ToRGBA16[T](p: RGBA8, output: var openArray[T], px: int, mode: PNGColorMode, ct: ColorTree8) =
let i = px * 8
output[i] = p.r
output[i+2] = p.g
output[i+4] = p.b
output[i+6] = p.a
output[i+1] = p.r
output[i+3] = p.g
output[i+5] = p.b
output[i+7] = p.a
output[i] = T(p.r)
output[i+2] = T(p.g)
output[i+4] = T(p.b)
output[i+6] = T(p.a)
output[i+1] = T(p.r)
output[i+3] = T(p.g)
output[i+5] = T(p.b)
output[i+7] = T(p.a)
proc RGBA16ToGrey[T](p: RGBA16, output: var openArray[T], px: int, mode: PNGColorMode) =
let i = px * 2
output[i] = T((p.r shr 8) and 255)
output[i] = T((p.r shr 8) and 255)
output[i+1] = T(p.r and 255)
proc RGBA16ToRGB[T](p: RGBA16, output: var openArray[T], px: int, mode: PNGColorMode) =
@ -1691,7 +1706,7 @@ proc convertImpl*[T](output: var openArray[T], input: openArray[T], modeOut, mod
let cvt = getColorRGBA8[T](modeIn)
let pxl = getPixelRGBA8[T](modeOut)
for px in 0..<numPixels:
var p = RGBA8(r:chr(0), g:chr(0), b:chr(0), a:chr(0))
var p = RGBA8(r:char(0), g:char(0), b:char(0), a:char(0))
cvt(p, input, px, modeIn)
pxl(p, output, px, modeOut, tree)
@ -1710,10 +1725,7 @@ proc convert*[T](png: PNG[T], colorType: PNGColorType, bitDepth: int): PNGResult
new(result)
result.width = header.width
result.height = header.height
when T is string:
result.data = newString(size)
else:
result.data = newSeq[uint8](size)
result.data = newStorage[T](size)
if modeOut == modeIn:
result.data = png.pixels
@ -1723,7 +1735,7 @@ proc convert*[T](png: PNG[T], colorType: PNGColorType, bitDepth: int): PNGResult
png.pixels.toOpenArray(0, png.pixels.len-1),
modeOut, modeIn, numPixels)
proc convert*[T](png: PNG[T], colorType: PNGColorType, bitDepth: int, ctl: APNGFrameControl, data: string): APNGFrame[T] =
proc convert*[T](png: PNG[T], colorType: PNGColorType, bitDepth: int, ctl: APNGFrameControl, data: T): APNGFrame[T] =
let modeIn = png.getColorMode()
let modeOut = newColorMode(colorType, bitDepth)
let size = getRawSize(ctl.width, ctl.height, modeOut)
@ -1731,10 +1743,7 @@ proc convert*[T](png: PNG[T], colorType: PNGColorType, bitDepth: int, ctl: APNGF
new(result)
result.ctl = ctl
when T is string:
result.data = newString(size)
else:
result.data = newSeq[uint8](size)
result.data = newStorage[T](size)
if modeOut == modeIn:
result.data = data
@ -1744,10 +1753,10 @@ proc convert*[T](png: PNG[T], colorType: PNGColorType, bitDepth: int, ctl: APNGF
data.toOpenArray(0, data.len-1),
modeOut, modeIn, numPixels)
proc toString(chunk: APNGFrameData): string =
proc toStorage[T](chunk: APNGFrameData): T =
let fdatLen = chunk.data.len - chunk.frameDataPos
let fdatAddr = chunk.data[chunk.frameDataPos].addr
result = newString(fdatLen)
result = newStorage[T](fdatLen)
copyMem(result[0].addr, fdatAddr, fdatLen)
type
@ -1768,7 +1777,10 @@ proc processingAPNG[T](apng: APNG[T], colorType: PNGColorType, bitDepth: int) =
if apng.png.firstFrameIsDefaultImage:
start = 1
# IDAT already processed, so we add a dummy here
frameData.add ""
when T is string:
frameData.add ""
else:
frameData.add @[]
for x in apng.png.apngChunks:
if x.chunkType == fcTL:
@ -1778,9 +1790,9 @@ proc processingAPNG[T](apng: APNG[T], colorType: PNGColorType, bitDepth: int) =
else:
let y = APNGFrameData(x)
if lastChunkType == fdAT:
frameData[^1].add y.toString()
frameData[^1].add toStorage[T](y)
else:
frameData.add y.toString()
frameData.add toStorage[T](y)
lastChunkType = fdAT
if actl.numFrames == 0 or actl.numFrames != numFrames or actl.numFrames != frameData.len:
@ -1806,7 +1818,10 @@ proc processingAPNG[T](apng: APNG[T], colorType: PNGColorType, bitDepth: int) =
for i in start..<numFrames:
let ctl = frameControl[i]
var nz = nzInflateInit(frameData[i])
when T is string:
var nz = nzInflateInit(frameData[i])
else:
var nz = nzInflateInit(cast[string](frameData[i]))
nz.ignoreAdler32 = PNGDecoder(apng.png.settings).ignoreAdler32
var idat = zlib_decompress(nz)
apng.png.postProcessScanLines(ctl, idat)
@ -1860,7 +1875,7 @@ when not defined(js):
if s == nil:
result.err("cannot open input stream")
return
result.ok(s.decodePNG(colorType, bitDepth, settings))
result.ok(decodePNG(T, s, colorType, bitDepth, settings))
s.close()
except PNGError, IOError, NZError:
result.err(getCurrentExceptionMsg())
@ -1880,7 +1895,7 @@ proc decodePNG32Impl*[T](input: T, settings = PNGDecoder(nil)): PNGRes[T] =
if s == nil:
result.err("cannot open input stream")
return
result.ok(s.decodePNG(LCT_RGBA, 8, settings))
result.ok(decodePNG(T, s, LCT_RGBA, 8, settings))
except PNGError, IOError, NZError:
result.err(getCurrentExceptionMsg())
@ -1893,7 +1908,7 @@ proc decodePNG24Impl*[T](input: T, settings = PNGDecoder(nil)): PNGRes[T] =
if s == nil:
result.err("cannot open input stream")
return
result.ok(s.decodePNG(LCT_RGB, 8, settings))
result.ok(decodePNG(T, s, LCT_RGB, 8, settings))
except PNGError, IOError, NZError:
result.err(getCurrentExceptionMsg())
@ -1933,12 +1948,16 @@ proc decodePNG24Legacy*(input: string, settings = PNGDecoder(nil)): PNGResult[st
template decodePNG32*[T](input: T, settings = PNGDecoder(nil)): untyped =
when T is string:
decodePNG32Legacy(input, settings)
elif T is openArray:
decodePNG32Impl(@(input), settings)
else:
decodePNG32Impl(input, settings)
template decodePNG24*[T](input: T, settings = PNGDecoder(nil)): untyped =
when T is string:
decodePNG24Legacy(input, settings)
elif T is openArray:
decodePNG24Impl(@(input), settings)
else:
decodePNG24Impl(input, settings)
@ -2325,7 +2344,7 @@ proc writeChunk(chunk: APNGFrameControl, png: PNG): bool =
proc writeChunk(chunk: APNGFrameData, png: PNG): bool =
chunk.writeInt32(chunk.sequenceNumber)
var nz = nzDeflateInit(png.apngPixels[chunk.frameDataPos])
var nz = nzDeflateInit(cast[string](png.apngPixels[chunk.frameDataPos]))
chunk.writeString zlib_compress(nz)
result = true
@ -2384,7 +2403,7 @@ proc differ(p: RGBA16): bool =
if (p.a and 255) != ((p.a shr 8) and 255): return true
result = false
proc calculateColorProfile(input: string, w, h: int, mode: PNGColorMode, prof: PNGColorProfile, tree: var Table[RGBA8, int]) =
proc calculateColorProfile[T](input: openArray[T], w, h: int, mode: PNGColorMode, prof: PNGColorProfile, tree: var Table[RGBA8, int]) =
let
numPixels = w * h
bpp = getBPP(mode)
@ -2406,7 +2425,7 @@ proc calculateColorProfile(input: string, w, h: int, mode: PNGColorMode, prof: P
#Check if the 16-bit input is truly 16-bit
if mode.bitDepth == 16:
let cvt = getColorRGBA16[char](mode)
let cvt = getColorRGBA16[T](mode)
var p = RGBA16(r:0, g:0, b:0, a:0)
for px in 0..<numPixels:
cvt(p, input.toOpenArray(0, input.len-1), px, mode)
@ -2415,7 +2434,7 @@ proc calculateColorProfile(input: string, w, h: int, mode: PNGColorMode, prof: P
break
if sixteen:
let cvt = getColorRGBA16[char](mode)
let cvt = getColorRGBA16[T](mode)
var p = RGBA16(r:0, g:0, b:0, a:0)
prof.bits = 16
#counting colors no longer useful, palette doesn't support 16-bit
@ -2448,7 +2467,7 @@ proc calculateColorProfile(input: string, w, h: int, mode: PNGColorMode, prof: P
if alphaDone and numColorsDone and coloredDone and bitsDone: break
else: # < 16-bit
let cvt = getColorRGBA8[char](mode)
let cvt = getColorRGBA8[T](mode)
for px in 0..<numPixels:
var p = RGBA8(r:chr(0), g:chr(0), b:chr(0), a:chr(0))
cvt(p, input.toOpenArray(0, input.len-1), px, mode)
@ -2500,12 +2519,13 @@ proc getColorProfile(png: PNG, mode: PNGColorMode): PNGColorProfile =
prof = makeColorProfile()
tree = initTable[RGBA8, int]()
calculateColorProfile(png.pixels, png.width, png.height, mode, prof, tree)
calculateColorProfile(png.pixels.toOpenArray(0, png.pixels.len-1), png.width, png.height, mode, prof, tree)
if png.isAPNG:
for i in 1..<png.apngChunks.len:
var ctl = APNGFrameControl(png.apngChunks[i])
calculateColorProfile(png.apngPixels[i], ctl.width, ctl.height, mode, prof, tree)
let len = png.apngPixels[i].len
calculateColorProfile(png.apngPixels[i].toOpenArray(0, len-1), ctl.width, ctl.height, mode, prof, tree)
result = prof
@ -2598,7 +2618,7 @@ proc filter[T](output: var openArray[T], input: openArray[T], w, h: int, modeOut
of LFS_BRUTE_FORCE: filterBruteForce(output, input, w, h, bpp)
of LFS_PREDEFINED: filterPredefined(output, input, w, h, bpp, state.predefinedFilters)
proc preProcessScanLines[T](png: PNG, input: openArray[T], frameNo, w, h: int, modeOut: PNGColorMode, state: PNGEncoder) =
proc preProcessScanLines[T, B](png: PNG[T], input: openArray[B], frameNo, w, h: int, modeOut: PNGColorMode, state: PNGEncoder) =
# This function converts the pure 2D image with the PNG's colorType, into filtered-padded-interlaced data. Steps:
# if no Adam7: 1) add padding bits (= posible extra bits per scanLine if bpp < 8) 2) filter
# if adam7: 1) Adam7_interlace 2) 7x add padding bits 3) 7x filter
@ -2608,11 +2628,11 @@ proc preProcessScanLines[T](png: PNG, input: openArray[T], frameNo, w, h: int, m
# image size plus an extra byte per scanLine + possible padding bits
let scanLen = (w * bpp + 7) div 8
let outSize = h + (h * scanLen)
var output = newString(outSize)
var output = newStorage[T](outSize)
# non multiple of 8 bits per scanLine, padding bits needed per scanLine
if(bpp < 8) and ((w * bpp) != (scanLen * 8)):
var padded = newString(h * scanLen)
var padded = newStorage[T](h * scanLen)
addPaddingBits(padded.toOpenArray(0, padded.len-1), input, scanLen * 8, w * bpp, h)
filter(output.toOpenArray(0, output.len-1),
@ -2630,14 +2650,14 @@ proc preProcessScanLines[T](png: PNG, input: openArray[T], frameNo, w, h: int, m
adam7PassValues(pass, w, h, bpp)
let outSize = pass.filterStart[7]
var output = newString(outSize)
var adam7 = newString(pass.start[7])
var output = newStorage[T](outSize)
var adam7 = newStorage[T](pass.start[7])
adam7Interlace(adam7.toOpenArray(0, adam7.len-1),
input, w, h, bpp)
for i in 0..6:
if bpp < 8:
var padding = newString(pass.paddedStart[i + 1] - pass.paddedStart[i])
var padding = newStorage[T](pass.paddedStart[i + 1] - pass.paddedStart[i])
addPaddingBits(padding.toOpenArray(0, padding.len-1),
adam7.toOpenArray(pass.start[i], adam7.len-1),
@ -2736,7 +2756,7 @@ proc addChunkpHYs(png: PNG, state: PNGEncoder) =
proc addChunkIDAT(png: PNG, state: PNGEncoder) =
var chunk = make[PNGData](IDAT, 0)
chunk.idat = png.pixels
chunk.idat = cast[string](png.pixels)
png.chunks.add chunk
proc addChunktIME(png: PNG, state: PNGEncoder) =
@ -2779,7 +2799,7 @@ proc addChunkacTL(png: PNG, numFrames, numPlays: int) =
chunk.numPlays = numPlays
png.chunks.add chunk
proc addChunkfcTL(png: PNG, chunk: APNGFrameControl, sequenceNumber: int) =
proc addChunkfcTL[T](png: PNG[T], chunk: APNGFrameControl, sequenceNumber: int) =
chunk.chunkType = fcTL
if chunk.data == "":
chunk.data = newStringOfCap(26)
@ -2799,7 +2819,7 @@ proc frameConvert[T](png: PNG[T], modeIn, modeOut: PNGColorMode, w, h, frameNo:
let size = (w * h * getBPP(modeOut) + 7) div 8
let numPixels = w * h
var converted = newString(size)
var converted = newStorage[T](size)
# although in preProcessScanLines png.pixels is reinitialized, it is ok
# because initBuffer(png.pixels) share the ownership
convertImpl(converted.toOpenArray(0, converted.len-1),
@ -2843,7 +2863,8 @@ proc encoderCore[T](png: PNG[T]) =
if not bitDepthAllowed(modeOut.colorType, modeOut.bitDepth):
raise PNGFatal("colorType and bitDepth combination not allowed")
if not png.isAPNG: png.apngPixels = @[""]
if not png.isAPNG: png.apngPixels = @[newStorage[T](0)]
shallowCopy(png.apngPixels[0], png.pixels)
frameConvert[T](png, modeIn, modeOut, png.width, png.height, 0, state)
shallowCopy(png.pixels, png.apngPixels[0])

View File

@ -299,7 +299,7 @@ proc filterBruteForce*[T](output: var openArray[T], input: openArray[T], w, h, b
for x in 0..lineTs-1:
output[y * (lineTs + 1) + 1 + x] = attempt[bestType][x]
proc unfilterScanline*[T](output: var openArray[T], input: openArray[T], byteWidth, len: int, filterType: PNGFilter) =
proc unfilterScanline*[A, B](output: var openArray[A], input: openArray[B], byteWidth, len: int, filterType: PNGFilter) =
# When the pixels are smaller than 1 T, the filter works T per T (byteWidth = 1)
# the incoming inputs do NOT include the filtertype T, that one is given in the parameter filterType instead
# output and input MAY be the same memory address! output must be disjoint.
@ -310,28 +310,28 @@ proc unfilterScanline*[T](output: var openArray[T], input: openArray[T], byteWid
case filterType
of FLT_NONE:
for i in 0..<len:
output[i] = input[i]
output[i] = A(input[i])
of FLT_SUB:
for i in 0..<byteWidth:
output[i] = input[i]
output[i] = A(input[i])
for i in byteWidth..<len:
output[i] = T((currPix(i) + prevPix(i)) and 0xFF)
output[i] = A((currPix(i) + prevPix(i)) and 0xFF)
of FLT_UP:
for i in 0..<len:
output[i] = input[i]
output[i] = A(input[i])
of FLT_AVERAGE:
for i in 0..<byteWidth:
output[i] = input[i]
output[i] = A(input[i])
for i in byteWidth..<len:
output[i] = T((currPix(i) + (prevPix(i) div 2)) and 0xFF)
output[i] = A((currPix(i) + (prevPix(i) div 2)) and 0xFF)
of FLT_PAETH:
for i in 0..<byteWidth:
output[i] = input[i]
output[i] = A(input[i])
for i in byteWidth..<len:
# paethPredictor(prevPix, 0, 0) is always prevPix
output[i] = T((currPix(i) + prevPix(i)) and 0xFF)
output[i] = A((currPix(i) + prevPix(i)) and 0xFF)
proc unfilterScanline*[T](output: var openArray[T], input, prevLine: openArray[T], byteWidth, len: int, filterType: PNGFilter) =
proc unfilterScanline*[A, B](output: var openArray[A], input: openArray[B], prevLine: openArray[A], byteWidth, len: int, filterType: PNGFilter) =
# For PNG filter method 0
# unfilter a PNG image input by input. when the pixels are smaller than 1 T,
# the filter works T per T (byteWidth = 1)
@ -349,28 +349,28 @@ proc unfilterScanline*[T](output: var openArray[T], input, prevLine: openArray[T
case filterType
of FLT_NONE:
for i in 0..<len:
output[i] = input[i]
output[i] = A(input[i])
of FLT_SUB:
for i in 0..<byteWidth:
output[i] = input[i]
output[i] = A(input[i])
for i in byteWidth..<len:
output[i] = T((currPix(i) + prevPix(i)) and 0xFF)
output[i] = A((currPix(i) + prevPix(i)) and 0xFF)
of FLT_UP:
for i in 0..<len:
output[i] = T((currPix(i) + upPix(i)) and 0xFF)
output[i] = A((currPix(i) + upPix(i)) and 0xFF)
of FLT_AVERAGE:
for i in 0..<byteWidth:
output[i] = T((currPix(i) + upPix(i) div 2) and 0xFF)
output[i] = A((currPix(i) + upPix(i) div 2) and 0xFF)
for i in byteWidth..<len:
output[i] = T((currPix(i) + ((prevPix(i) + upPix(i)) div 2)) and 0xFF)
output[i] = A((currPix(i) + ((prevPix(i) + upPix(i)) div 2)) and 0xFF)
of FLT_PAETH:
for i in 0..<byteWidth:
# paethPredictor(0, upPix, 0) is always upPix
output[i] = T((currPix(i) + upPix(i)) and 0xFF)
output[i] = A((currPix(i) + upPix(i)) and 0xFF)
for i in byteWidth..<len:
output[i] = T((currPix(i) + paethPredictor(prevPixI(i), upPixI(i), prevUpPix(i))) and 0xFF)
output[i] = A((currPix(i) + paethPredictor(prevPixI(i), upPixI(i), prevUpPix(i))) and 0xFF)
proc unfilter*[T](output: var openArray[T], input: openArray[T], w, h, bpp: int) =
proc unfilter*[A, B](output: var openArray[A], input: openArray[B], w, h, bpp: int) =
# For PNG filter method 0
# this function unfilters a single image (e.g. without interlacing this is called once, with Adam7 seven times)
# output must have enough Ts allocated already, input must have the scanLines + 1 filtertype T per scanLine
@ -430,7 +430,7 @@ proc setBitOfReversedStream*[T](bitptr: var int, bitstream: var openArray[T], bi
else: bitstream[bitptr shr 3] |= cast[T](1 shl (7 - (bitptr and 0x7)))
inc bitptr
proc removePaddingBits*[T](output: var openArray[T], input: openArray[T], olinebits, ilinebits, h: int) =
proc removePaddingBits*[A, B](output: var openArray[A], input: openArray[B], olinebits, ilinebits, h: int) =
# After filtering there are still padding bits if scanLines have non multiple of 8 bit amounts. They need
# to be removed (except at last scanLine of (Adam7-reduced) image) before working with pure image buffers
# for the Adam7 code, the color convert code and the output to the user.
@ -495,7 +495,7 @@ proc adam7PassValues*(pass: var PNGPass, w, h, bpp: int) =
# output must be big enough AND must be 0 everywhere if bpp < 8 in the current implementation
# (because that's likely a little bit faster)
# NOTE: comments about padding bits are only relevant if bpp < 8
proc adam7Deinterlace*[T](output: var openArray[T], input: openArray[T], w, h, bpp: int) =
proc adam7Deinterlace*[A, B](output: var openArray[A], input: openArray[B], w, h, bpp: int) =
var pass: PNGPass
adam7PassValues(pass, w, h, bpp)
@ -507,7 +507,7 @@ proc adam7Deinterlace*[T](output: var openArray[T], input: openArray[T], w, h, b
let inStart = pass.start[i] + (y * pass.w[i] + x) * byteWidth
let outStart = ((ADAM7_IY[i] + y * ADAM7_DY[i]) * w + ADAM7_IX[i] + x * ADAM7_DX[i]) * byteWidth
for b in 0..<byteWidth:
output[outStart + b] = input[inStart + b]
output[outStart + b] = A(input[inStart + b])
else: # bpp < 8: Adam7 with pixels < 8 bit is a bit trickier: with bit pointers
for i in 0..6:
let ilinebits = bpp * pass.w[i]

View File

@ -4,4 +4,5 @@ import
test_suite,
test_nimz,
test_filters,
test_crash
test_crash,
test_api

BIN
tests/misc/sample24.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 535 KiB

BIN
tests/misc/sample32.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 624 KiB

48
tests/test_api.nim Normal file
View File

@ -0,0 +1,48 @@
import ../nimPNG, unittest, os
proc main() =
const subject = "tests" / "misc" / "sample.png"
const subject24 = "tests" / "misc" / "sample24.png"
const subject32 = "tests" / "misc" / "sample32.png"
let data = cast[string](readFile(subject))
suite "test API":
test "decodePNG/encodePNG/savePNG string":
let png1 = decodePNG24(data)
let png2 = decodePNG32(data)
let im1 = encodePNG24(png1.data, png1.width, png1.height)
let im2 = encodePNG32(png2.data, png2.width, png2.height)
check savePNG24(subject24, png1.data, png1.width, png1.height) == true
check savePNG32(subject32, png2.data, png2.width, png2.height) == true
test "decodePNG/encodePNG/savePNG seq[uint8]":
let res1 = decodePNG24(cast[seq[uint8]](data))
let res2 = decodePNG32(cast[seq[uint8]](data))
check res1.isOk() == true
check res2.isOk() == true
let png1 = res1.get()
let png2 = res2.get()
let im1 = encodePNG24(png1.data, png1.width, png1.height)
let im2 = encodePNG32(png2.data, png2.width, png2.height)
check savePNG24(subject24, png1.data, png1.width, png1.height).isOk() == true
check savePNG32(subject32, png2.data, png2.width, png2.height).isOk() == true
test "decodePNG openArray[uint8]":
let png1 = decodePNG24(data.toOpenArrayByte(0, data.len-1))
let png2 = decodePNG32(data.toOpenArrayByte(0, data.len-1))
test "loadPNG string":
let png1 = loadPNG32(string, subject)
let png2 = loadPNG24(string, subject)
test "loadPNG string":
let png1 = loadPNG32(seq[uint8], subject)
let png2 = loadPNG24(seq[uint8], subject)
main()