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
|
||||
|
||||
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
|
||||
w, h: array[0..6, int]
|
||||
filterStart, paddedStart, start: array[0..7, int]
|
||||
|
@ -189,11 +218,19 @@ type
|
|||
settings*: PNGSettings
|
||||
chunks*: seq[PNGChunk]
|
||||
pixels*: string
|
||||
apngChunks*: seq[APNGFrameChunk]
|
||||
firstFrameIsDefaultImage*: bool
|
||||
|
||||
APNGFrame* = ref object
|
||||
width*: int
|
||||
height*: int
|
||||
data*: string
|
||||
|
||||
PNGResult* = ref object
|
||||
width*: int
|
||||
height*: int
|
||||
data*: string
|
||||
frames*: seq[APNGFrame]
|
||||
|
||||
DataBuf = Buffer[string]
|
||||
|
||||
|
@ -252,6 +289,11 @@ const
|
|||
sPLT = makeChunkType("sPLT")
|
||||
hIST = makeChunkType("hIST")
|
||||
|
||||
# APNG chunks
|
||||
acTL = makeChunkType("acTL")
|
||||
fcTL = makeChunkType("fcTL")
|
||||
fdAT = makeChunkType("fdAT")
|
||||
|
||||
# shared values used by multiple Adam7 related functions
|
||||
ADAM7_IX = [ 0, 4, 0, 2, 0, 1, 0 ] # x 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
|
||||
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 =
|
||||
for c in png.chunks:
|
||||
if c.chunkType == chunkType: return c
|
||||
|
@ -727,43 +774,77 @@ method parseChunk(chunk: PNGSbit, png: PNG): bool =
|
|||
|
||||
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 createChunk(png: PNG, chunkType: PNGChunkType, data: string, crc: uint32): PNGChunk =
|
||||
var settings = PNGDecoder(png.settings)
|
||||
result = nil
|
||||
|
||||
if chunkType == IHDR: result = make[PNGHeader]()
|
||||
elif chunkType == PLTE: result = make[PNGPalette]()
|
||||
elif chunkType == IDAT:
|
||||
case chunkType
|
||||
of IHDR: result = make[PNGHeader]()
|
||||
of PLTE: result = make[PNGPalette]()
|
||||
of IDAT:
|
||||
if png.apngHasChunk(fcTL): png.firstFrameIsDefaultImage = true
|
||||
if not png.hasChunk(IDAT): result = make[PNGData]()
|
||||
else:
|
||||
var idat = PNGData(png.getChunk(IDAT))
|
||||
idat.data.add data
|
||||
return idat
|
||||
elif chunkType == tRNS: result = make[PNGTrans]()
|
||||
elif chunkType == bKGD: result = make[PNGBackground]()
|
||||
elif chunkType == tIME: result = make[PNGTime]()
|
||||
elif chunkType == pHYs: result = make[PNGPhys]()
|
||||
elif chunkType == tEXt:
|
||||
of tRNS: result = make[PNGTrans]()
|
||||
of bKGD: result = make[PNGBackground]()
|
||||
of tIME: result = make[PNGTime]()
|
||||
of pHYs: result = make[PNGPhys]()
|
||||
of tEXt:
|
||||
if settings.readTextChunks: result = make[PNGTExt]()
|
||||
else:
|
||||
if settings.rememberUnknownChunks: new(result)
|
||||
elif chunkType == zTXt:
|
||||
of zTXt:
|
||||
if settings.readTextChunks: result = make[PNGZtxt]()
|
||||
else:
|
||||
if settings.rememberUnknownChunks: new(result)
|
||||
elif chunkType == iTXt:
|
||||
of iTXt:
|
||||
if settings.readTextChunks: result = make[PNGItxt]()
|
||||
else:
|
||||
if settings.rememberUnknownChunks: new(result)
|
||||
elif chunkType == gAMA: result = make[PNGGamma]()
|
||||
elif chunkType == cHRM: result = make[PNGChroma]()
|
||||
elif chunkType == iCCP: result = make[PNGICCProfile]()
|
||||
elif chunkType == sRGB: result = make[PNGStandarRGB]()
|
||||
elif chunkType == sPLT: result = make[PNGSPalette]()
|
||||
elif chunkType == hIST: result = make[PNGHist]()
|
||||
elif chunkType == sBIT: result = make[PNGSbit]()
|
||||
of gAMA: result = make[PNGGamma]()
|
||||
of cHRM: result = make[PNGChroma]()
|
||||
of iCCP: result = make[PNGICCProfile]()
|
||||
of sRGB: result = make[PNGStandarRGB]()
|
||||
of sPLT: result = make[PNGSPalette]()
|
||||
of hIST: result = make[PNGHist]()
|
||||
of sBIT: result = make[PNGSbit]()
|
||||
of acTL: result = make[APNGAnimationControl]()
|
||||
of fcTL: result = make[APNGFrameControl]()
|
||||
of fdAT: result = make[APNGFrameData]()
|
||||
else:
|
||||
if settings.rememberUnknownChunks: new(result)
|
||||
|
||||
|
@ -784,6 +865,7 @@ proc parsePNG(s: Stream, settings: PNGDecoder): PNG =
|
|||
var png: PNG
|
||||
new(png)
|
||||
png.chunks = @[]
|
||||
png.apngChunks = @[]
|
||||
if settings == nil: png.settings = makePNGDecoder()
|
||||
else: png.settings = settings
|
||||
|
||||
|
@ -805,7 +887,10 @@ proc parsePNG(s: Stream, settings: PNGDecoder): PNG =
|
|||
if chunkType != IDAT and chunk != nil:
|
||||
if not chunk.parseChunk(png): raise PNGError("error parse 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 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
|
||||
setBitOfReversedStream0(obp, output, bit)
|
||||
|
||||
proc postProcessscanLines(png: PNG) =
|
||||
proc postProcessScanLines(png: PNG) =
|
||||
# This function converts the filtered-padded-interlaced data
|
||||
# into pure 2D image buffer with the PNG's colorType.
|
||||
# Steps:
|
||||
|
@ -1781,7 +1866,7 @@ proc decodePNG*(s: Stream, colorType: PNGcolorType, bitDepth: int, settings = PN
|
|||
if not bitDepthAllowed(colorType, bitDepth):
|
||||
raise PNGError("colorType and bitDepth combination not allowed")
|
||||
var png = s.parsePNG(settings)
|
||||
png.postProcessscanLines()
|
||||
png.postProcessScanLines()
|
||||
|
||||
if PNGDecoder(png.settings).colorConvert:
|
||||
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 =
|
||||
var png = s.parsePNG(settings)
|
||||
png.postProcessscanLines()
|
||||
png.postProcessScanLines()
|
||||
result = png
|
||||
|
||||
when not defined(js):
|
||||
|
|
Loading…
Reference in New Issue