This commit is contained in:
andri lim 2017-11-14 20:24:05 +07:00
parent 8926a6cea3
commit 333122b9f3
1 changed files with 106 additions and 21 deletions

View File

@ -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):