mirror of https://github.com/status-im/nimPNG.git
WIP apng
This commit is contained in:
parent
8926a6cea3
commit
333122b9f3
127
nimPNG.nim
127
nimPNG.nim
|
@ -155,6 +155,35 @@ type
|
||||||
|
|
||||||
PNGSbit = ref object of PNGChunk
|
PNGSbit = ref object of PNGChunk
|
||||||
|
|
||||||
|
APNGAnimationControl = ref object of PNGChunk
|
||||||
|
numFrames: int
|
||||||
|
numPlays: int
|
||||||
|
|
||||||
|
APNG_DISPOSE_OP* = enum
|
||||||
|
APNG_DISPOSE_OP_NONE
|
||||||
|
APNG_DISPOSE_OP_BACKGROUND
|
||||||
|
APNG_DISPOSE_OP_PREVIOUS
|
||||||
|
|
||||||
|
APNG_BLEND_OP* = enum
|
||||||
|
APNG_BLEND_OP_SOURCE
|
||||||
|
APNG_BLEND_OP_OVER
|
||||||
|
|
||||||
|
APNGFrameChunk = ref object of PNGChunk
|
||||||
|
sequenceNumber: int
|
||||||
|
|
||||||
|
APNGFrameControl = ref object of APNGFrameChunk
|
||||||
|
width: int
|
||||||
|
height: int
|
||||||
|
xOffset: int
|
||||||
|
yOffset: int
|
||||||
|
delayNum: int
|
||||||
|
delayDen: int
|
||||||
|
disposeOp: APNG_DISPOSE_OP
|
||||||
|
blendOp: APNG_BLEND_OP
|
||||||
|
|
||||||
|
APNGFrameData = ref object of APNGFrameChunk
|
||||||
|
frameDataPos: int
|
||||||
|
|
||||||
PNGPass = object
|
PNGPass = object
|
||||||
w, h: array[0..6, int]
|
w, h: array[0..6, int]
|
||||||
filterStart, paddedStart, start: array[0..7, int]
|
filterStart, paddedStart, start: array[0..7, int]
|
||||||
|
@ -189,11 +218,19 @@ type
|
||||||
settings*: PNGSettings
|
settings*: PNGSettings
|
||||||
chunks*: seq[PNGChunk]
|
chunks*: seq[PNGChunk]
|
||||||
pixels*: string
|
pixels*: string
|
||||||
|
apngChunks*: seq[APNGFrameChunk]
|
||||||
|
firstFrameIsDefaultImage*: bool
|
||||||
|
|
||||||
|
APNGFrame* = ref object
|
||||||
|
width*: int
|
||||||
|
height*: int
|
||||||
|
data*: string
|
||||||
|
|
||||||
PNGResult* = ref object
|
PNGResult* = ref object
|
||||||
width*: int
|
width*: int
|
||||||
height*: int
|
height*: int
|
||||||
data*: string
|
data*: string
|
||||||
|
frames*: seq[APNGFrame]
|
||||||
|
|
||||||
DataBuf = Buffer[string]
|
DataBuf = Buffer[string]
|
||||||
|
|
||||||
|
@ -252,6 +289,11 @@ const
|
||||||
sPLT = makeChunkType("sPLT")
|
sPLT = makeChunkType("sPLT")
|
||||||
hIST = makeChunkType("hIST")
|
hIST = makeChunkType("hIST")
|
||||||
|
|
||||||
|
# APNG chunks
|
||||||
|
acTL = makeChunkType("acTL")
|
||||||
|
fcTL = makeChunkType("fcTL")
|
||||||
|
fdAT = makeChunkType("fdAT")
|
||||||
|
|
||||||
# shared values used by multiple Adam7 related functions
|
# shared values used by multiple Adam7 related functions
|
||||||
ADAM7_IX = [ 0, 4, 0, 2, 0, 1, 0 ] # x start values
|
ADAM7_IX = [ 0, 4, 0, 2, 0, 1, 0 ] # x start values
|
||||||
ADAM7_IY = [ 0, 0, 4, 0, 2, 0, 1 ] # y start values
|
ADAM7_IY = [ 0, 0, 4, 0, 2, 0, 1 ] # y start values
|
||||||
|
@ -353,6 +395,11 @@ proc hasChunk*(png: PNG, chunkType: PNGChunkType): bool =
|
||||||
if c.chunkType == chunkType: return true
|
if c.chunkType == chunkType: return true
|
||||||
result = false
|
result = false
|
||||||
|
|
||||||
|
proc apngHasChunk*(png: PNG, chunkType: PNGChunkType): bool =
|
||||||
|
for c in png.apngChunks:
|
||||||
|
if c.chunkType == chunkType: return true
|
||||||
|
result = false
|
||||||
|
|
||||||
proc getChunk*(png: PNG, chunkType: PNGChunkType): PNGChunk =
|
proc getChunk*(png: PNG, chunkType: PNGChunkType): PNGChunk =
|
||||||
for c in png.chunks:
|
for c in png.chunks:
|
||||||
if c.chunkType == chunkType: return c
|
if c.chunkType == chunkType: return c
|
||||||
|
@ -727,43 +774,77 @@ method parseChunk(chunk: PNGSbit, png: PNG): bool =
|
||||||
|
|
||||||
result = true
|
result = true
|
||||||
|
|
||||||
|
method parseChunk(chunk: APNGAnimationControl, png: PNG): bool =
|
||||||
|
chunk.numFrames = chunk.readInt32()
|
||||||
|
chunk.numPlays = chunk.readInt32()
|
||||||
|
|
||||||
|
method parseChunk(chunk: APNGFrameControl, png: PNG): bool =
|
||||||
|
chunk.sequenceNumber = chunk.readInt32()
|
||||||
|
chunk.width = chunk.readInt32()
|
||||||
|
chunk.height = chunk.readInt32()
|
||||||
|
chunk.xOffset = chunk.readInt32()
|
||||||
|
chunk.yOffset = chunk.readInt32()
|
||||||
|
chunk.delayNum = chunk.readInt16()
|
||||||
|
chunk.delayDen = chunk.readInt16()
|
||||||
|
chunk.disposeOp = chunk.readByte().APNG_DISPOSE_OP
|
||||||
|
chunk.blendOp = chunk.readByte().APNG_BLEND_OP
|
||||||
|
|
||||||
|
method validateChunk(chunk: APNGFrameControl, png: PNG): bool =
|
||||||
|
let header = PNGHEader(png.getChunk(IHDR))
|
||||||
|
result = true
|
||||||
|
result = result and (chunk.xOffset >= 0)
|
||||||
|
result = result and (chunk.yOffset >= 0)
|
||||||
|
result = result and (chunk.width > 0)
|
||||||
|
result = result and (chunk.height > 0)
|
||||||
|
result = result and (chunk.xOffset + chunk.width <= header.width)
|
||||||
|
result = result and (chunk.yOffset + chunk.height <= header.height)
|
||||||
|
|
||||||
|
method parseChunk(chunk: APNGFrameData, png: PNG): bool =
|
||||||
|
chunk.sequenceNumber = chunk.readInt32()
|
||||||
|
chunk.frameDataPos = chunk.pos
|
||||||
|
|
||||||
proc make[T](): T = new(result)
|
proc make[T](): T = new(result)
|
||||||
|
|
||||||
proc createChunk(png: PNG, chunkType: PNGChunkType, data: string, crc: uint32): PNGChunk =
|
proc createChunk(png: PNG, chunkType: PNGChunkType, data: string, crc: uint32): PNGChunk =
|
||||||
var settings = PNGDecoder(png.settings)
|
var settings = PNGDecoder(png.settings)
|
||||||
result = nil
|
result = nil
|
||||||
|
|
||||||
if chunkType == IHDR: result = make[PNGHeader]()
|
case chunkType
|
||||||
elif chunkType == PLTE: result = make[PNGPalette]()
|
of IHDR: result = make[PNGHeader]()
|
||||||
elif chunkType == IDAT:
|
of PLTE: result = make[PNGPalette]()
|
||||||
|
of IDAT:
|
||||||
|
if png.apngHasChunk(fcTL): png.firstFrameIsDefaultImage = true
|
||||||
if not png.hasChunk(IDAT): result = make[PNGData]()
|
if not png.hasChunk(IDAT): result = make[PNGData]()
|
||||||
else:
|
else:
|
||||||
var idat = PNGData(png.getChunk(IDAT))
|
var idat = PNGData(png.getChunk(IDAT))
|
||||||
idat.data.add data
|
idat.data.add data
|
||||||
return idat
|
return idat
|
||||||
elif chunkType == tRNS: result = make[PNGTrans]()
|
of tRNS: result = make[PNGTrans]()
|
||||||
elif chunkType == bKGD: result = make[PNGBackground]()
|
of bKGD: result = make[PNGBackground]()
|
||||||
elif chunkType == tIME: result = make[PNGTime]()
|
of tIME: result = make[PNGTime]()
|
||||||
elif chunkType == pHYs: result = make[PNGPhys]()
|
of pHYs: result = make[PNGPhys]()
|
||||||
elif chunkType == tEXt:
|
of tEXt:
|
||||||
if settings.readTextChunks: result = make[PNGTExt]()
|
if settings.readTextChunks: result = make[PNGTExt]()
|
||||||
else:
|
else:
|
||||||
if settings.rememberUnknownChunks: new(result)
|
if settings.rememberUnknownChunks: new(result)
|
||||||
elif chunkType == zTXt:
|
of zTXt:
|
||||||
if settings.readTextChunks: result = make[PNGZtxt]()
|
if settings.readTextChunks: result = make[PNGZtxt]()
|
||||||
else:
|
else:
|
||||||
if settings.rememberUnknownChunks: new(result)
|
if settings.rememberUnknownChunks: new(result)
|
||||||
elif chunkType == iTXt:
|
of iTXt:
|
||||||
if settings.readTextChunks: result = make[PNGItxt]()
|
if settings.readTextChunks: result = make[PNGItxt]()
|
||||||
else:
|
else:
|
||||||
if settings.rememberUnknownChunks: new(result)
|
if settings.rememberUnknownChunks: new(result)
|
||||||
elif chunkType == gAMA: result = make[PNGGamma]()
|
of gAMA: result = make[PNGGamma]()
|
||||||
elif chunkType == cHRM: result = make[PNGChroma]()
|
of cHRM: result = make[PNGChroma]()
|
||||||
elif chunkType == iCCP: result = make[PNGICCProfile]()
|
of iCCP: result = make[PNGICCProfile]()
|
||||||
elif chunkType == sRGB: result = make[PNGStandarRGB]()
|
of sRGB: result = make[PNGStandarRGB]()
|
||||||
elif chunkType == sPLT: result = make[PNGSPalette]()
|
of sPLT: result = make[PNGSPalette]()
|
||||||
elif chunkType == hIST: result = make[PNGHist]()
|
of hIST: result = make[PNGHist]()
|
||||||
elif chunkType == sBIT: result = make[PNGSbit]()
|
of sBIT: result = make[PNGSbit]()
|
||||||
|
of acTL: result = make[APNGAnimationControl]()
|
||||||
|
of fcTL: result = make[APNGFrameControl]()
|
||||||
|
of fdAT: result = make[APNGFrameData]()
|
||||||
else:
|
else:
|
||||||
if settings.rememberUnknownChunks: new(result)
|
if settings.rememberUnknownChunks: new(result)
|
||||||
|
|
||||||
|
@ -784,6 +865,7 @@ proc parsePNG(s: Stream, settings: PNGDecoder): PNG =
|
||||||
var png: PNG
|
var png: PNG
|
||||||
new(png)
|
new(png)
|
||||||
png.chunks = @[]
|
png.chunks = @[]
|
||||||
|
png.apngChunks = @[]
|
||||||
if settings == nil: png.settings = makePNGDecoder()
|
if settings == nil: png.settings = makePNGDecoder()
|
||||||
else: png.settings = settings
|
else: png.settings = settings
|
||||||
|
|
||||||
|
@ -805,7 +887,10 @@ proc parsePNG(s: Stream, settings: PNGDecoder): PNG =
|
||||||
if chunkType != IDAT and chunk != nil:
|
if chunkType != IDAT and chunk != nil:
|
||||||
if not chunk.parseChunk(png): raise PNGError("error parse chunk: " & $chunkType)
|
if not chunk.parseChunk(png): raise PNGError("error parse chunk: " & $chunkType)
|
||||||
if not chunk.validateChunk(png): raise PNGError("invalid chunk: " & $chunkType)
|
if not chunk.validateChunk(png): raise PNGError("invalid chunk: " & $chunkType)
|
||||||
if chunk != nil: png.chunks.add chunk
|
if chunk != nil:
|
||||||
|
if chunkType == fcTL or chunkType == fdAT:
|
||||||
|
png.apngChunks.add APNGFrameChunk(chunk)
|
||||||
|
else: png.chunks.add chunk
|
||||||
if chunkType == IEND: break
|
if chunkType == IEND: break
|
||||||
|
|
||||||
if not png.hasChunk(IHDR): raise PNGError("no IHDR found")
|
if not png.hasChunk(IHDR): raise PNGError("no IHDR found")
|
||||||
|
@ -1029,7 +1114,7 @@ proc Adam7Deinterlace(output: var DataBuf, input: DataBuf, w, h, bpp: int) =
|
||||||
# note that this function assumes the out buffer is completely 0, use setBitOfReversedStream otherwise
|
# note that this function assumes the out buffer is completely 0, use setBitOfReversedStream otherwise
|
||||||
setBitOfReversedStream0(obp, output, bit)
|
setBitOfReversedStream0(obp, output, bit)
|
||||||
|
|
||||||
proc postProcessscanLines(png: PNG) =
|
proc postProcessScanLines(png: PNG) =
|
||||||
# This function converts the filtered-padded-interlaced data
|
# This function converts the filtered-padded-interlaced data
|
||||||
# into pure 2D image buffer with the PNG's colorType.
|
# into pure 2D image buffer with the PNG's colorType.
|
||||||
# Steps:
|
# Steps:
|
||||||
|
@ -1781,7 +1866,7 @@ proc decodePNG*(s: Stream, colorType: PNGcolorType, bitDepth: int, settings = PN
|
||||||
if not bitDepthAllowed(colorType, bitDepth):
|
if not bitDepthAllowed(colorType, bitDepth):
|
||||||
raise PNGError("colorType and bitDepth combination not allowed")
|
raise PNGError("colorType and bitDepth combination not allowed")
|
||||||
var png = s.parsePNG(settings)
|
var png = s.parsePNG(settings)
|
||||||
png.postProcessscanLines()
|
png.postProcessScanLines()
|
||||||
|
|
||||||
if PNGDecoder(png.settings).colorConvert:
|
if PNGDecoder(png.settings).colorConvert:
|
||||||
result = png.convert(colorType, bitDepth)
|
result = png.convert(colorType, bitDepth)
|
||||||
|
@ -1794,7 +1879,7 @@ proc decodePNG*(s: Stream, colorType: PNGcolorType, bitDepth: int, settings = PN
|
||||||
|
|
||||||
proc decodePNG*(s: Stream, settings = PNGDecoder(nil)): PNG =
|
proc decodePNG*(s: Stream, settings = PNGDecoder(nil)): PNG =
|
||||||
var png = s.parsePNG(settings)
|
var png = s.parsePNG(settings)
|
||||||
png.postProcessscanLines()
|
png.postProcessScanLines()
|
||||||
result = png
|
result = png
|
||||||
|
|
||||||
when not defined(js):
|
when not defined(js):
|
||||||
|
|
Loading…
Reference in New Issue