mirror of
https://github.com/status-im/nimPNG.git
synced 2025-01-11 13:14:20 +00:00
add testCodec
This commit is contained in:
parent
676de390bc
commit
a45c550e1c
@ -1,4 +1,5 @@
|
|||||||
import unsigned, streams, endians, tables, hashes, math, nimz
|
import unsigned, streams, endians, tables, hashes, math, nimz
|
||||||
|
import strutils
|
||||||
|
|
||||||
const
|
const
|
||||||
NIM_PNG_VERSION = "0.1.0"
|
NIM_PNG_VERSION = "0.1.0"
|
||||||
@ -22,7 +23,7 @@ type
|
|||||||
|
|
||||||
PNGSettings = ref object of RootObj
|
PNGSettings = ref object of RootObj
|
||||||
|
|
||||||
PNGDecoderSettings = ref object of PNGSettings
|
PNGDecoder* = ref object of PNGSettings
|
||||||
colorConvert*: bool
|
colorConvert*: bool
|
||||||
|
|
||||||
#if false but rememberUnknownChunks is true, they're stored in the unknown chunks
|
#if false but rememberUnknownChunks is true, they're stored in the unknown chunks
|
||||||
@ -32,11 +33,11 @@ type
|
|||||||
rememberUnknownChunks*: bool
|
rememberUnknownChunks*: bool
|
||||||
ignoreCRC*: bool
|
ignoreCRC*: bool
|
||||||
|
|
||||||
PNGInterlace = enum
|
PNGInterlace* = enum
|
||||||
IM_NONE = 0, IM_INTERLACED = 1
|
IM_NONE = 0, IM_INTERLACED = 1
|
||||||
|
|
||||||
PNGChunk = ref object of RootObj
|
PNGChunk = ref object of RootObj
|
||||||
length: range[1..0x7FFFFFFF]
|
length: range[0..0x7FFFFFFF]
|
||||||
chunkType: PNGChunkType
|
chunkType: PNGChunkType
|
||||||
crc: uint32
|
crc: uint32
|
||||||
data: string
|
data: string
|
||||||
@ -50,11 +51,11 @@ type
|
|||||||
filterMethod: int
|
filterMethod: int
|
||||||
interlaceMethod: PNGInterlace
|
interlaceMethod: PNGInterlace
|
||||||
|
|
||||||
RGBA8 = object
|
RGBA8* = object
|
||||||
r, g, b, a: char
|
r*, g*, b*, a*: char
|
||||||
|
|
||||||
RGBA16 = object
|
RGBA16* = object
|
||||||
r, g, b, a: uint16
|
r*, g*, b*, a*: uint16
|
||||||
|
|
||||||
ColorTree8 = Table[RGBA8, int]
|
ColorTree8 = Table[RGBA8, int]
|
||||||
|
|
||||||
@ -129,26 +130,55 @@ type
|
|||||||
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]
|
||||||
|
|
||||||
PNGColorMode = ref object
|
PNGColorMode* = ref object
|
||||||
colorType: PNGcolorType
|
colorType*: PNGcolorType
|
||||||
bitDepth: int
|
bitDepth*: int
|
||||||
paletteSize: int
|
paletteSize*: int
|
||||||
palette: seq[RGBA8]
|
palette*: seq[RGBA8]
|
||||||
keyDefined: bool
|
keyDefined*: bool
|
||||||
keyR, keyG, keyB: int
|
keyR*, keyG*, keyB*: int
|
||||||
|
|
||||||
PNG = ref object
|
PNGInfo* = ref object
|
||||||
settings: PNGSettings
|
width*: int
|
||||||
chunks: seq[PNGChunk]
|
height*: int
|
||||||
pixels: string
|
mode*: PNGColorMode
|
||||||
|
backgroundDefined*: bool
|
||||||
|
backgroundR*, backgroundG*, backgroundB*: int
|
||||||
|
|
||||||
|
physDefined*: bool
|
||||||
|
physX*, physY*, physUnit*: int
|
||||||
|
|
||||||
|
timeDefined*: bool
|
||||||
|
year*: range[0..65535]
|
||||||
|
month*: range[1..12]
|
||||||
|
day*: range[1..31]
|
||||||
|
hour*: range[0..23]
|
||||||
|
minute*: range[0..59]
|
||||||
|
second*: range[0..60] #to allow for leap seconds
|
||||||
|
|
||||||
|
PNG* = ref object
|
||||||
|
settings*: PNGSettings
|
||||||
|
chunks*: seq[PNGChunk]
|
||||||
|
pixels*: string
|
||||||
|
|
||||||
PNGResult* = ref object
|
PNGResult* = ref object
|
||||||
width*: int
|
width*: int
|
||||||
height*: int
|
height*: int
|
||||||
data*: string
|
data*: string
|
||||||
|
|
||||||
proc makeDefaultPNGDecoderSettings*(): PNGDecoderSettings =
|
proc toHex*(input: string) =
|
||||||
var s: PNGDecoderSettings
|
var i = 0
|
||||||
|
for x in 0..input.high:
|
||||||
|
write(stdout, toHex(ord(input[x]), 2))
|
||||||
|
inc i
|
||||||
|
if i == 40:
|
||||||
|
write(stdout, "\n")
|
||||||
|
i = 0
|
||||||
|
if i < 40:
|
||||||
|
write(stdout, "\n")
|
||||||
|
|
||||||
|
proc makePNGDecoder*(): PNGDecoder =
|
||||||
|
var s: PNGDecoder
|
||||||
new(s)
|
new(s)
|
||||||
s.colorConvert = true
|
s.colorConvert = true
|
||||||
s.readTextChunks = false
|
s.readTextChunks = false
|
||||||
@ -161,7 +191,7 @@ proc signatureMaker(): string {. compiletime .} =
|
|||||||
result = ""
|
result = ""
|
||||||
for c in signatureBytes: result.add chr(c)
|
for c in signatureBytes: result.add chr(c)
|
||||||
|
|
||||||
proc makeChunkType(val: string): PNGChunkType {. compiletime .} =
|
proc makeChunkType*(val: string): PNGChunkType =
|
||||||
assert (val.len == 4)
|
assert (val.len == 4)
|
||||||
result = PNGChunkType((ord(val[0]) shl 24) or (ord(val[1]) shl 16) or (ord(val[2]) shl 8) or ord(val[3]))
|
result = PNGChunkType((ord(val[0]) shl 24) or (ord(val[1]) shl 16) or (ord(val[2]) shl 8) or ord(val[3]))
|
||||||
|
|
||||||
@ -174,9 +204,9 @@ proc `$`*(tag: PNGChunkType): string =
|
|||||||
result[3] = chr(toU32(t) and 0xFF)
|
result[3] = chr(toU32(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 isAncillary(a: PNGChunkType): bool = (int(a) and (32 shl 24)) != 0
|
||||||
proc isPrivate(a: PNGChunkType): bool = (int(a) and (32 shl 16)) != 0
|
#proc isPrivate(a: PNGChunkType): bool = (int(a) and (32 shl 16)) != 0
|
||||||
proc isSafeToCopy(a: PNGChunkType): bool = (int(a) and 32) != 0
|
#proc isSafeToCopy(a: PNGChunkType): bool = (int(a) and 32) != 0
|
||||||
|
|
||||||
proc crc32(crc: uint32, buf: string): uint32 =
|
proc crc32(crc: uint32, buf: string): uint32 =
|
||||||
const kcrc32 = [ 0'u32, 0x1db71064, 0x3b6e20c8, 0x26d930ac, 0x76dc4190,
|
const kcrc32 = [ 0'u32, 0x1db71064, 0x3b6e20c8, 0x26d930ac, 0x76dc4190,
|
||||||
@ -221,7 +251,7 @@ proc PNGError(msg: string): ref Exception =
|
|||||||
new(result)
|
new(result)
|
||||||
result.msg = msg
|
result.msg = msg
|
||||||
|
|
||||||
proc newColorMode(colorType=LCT_RGBA, bitDepth=8): PNGColorMode =
|
proc newColorMode*(colorType=LCT_RGBA, bitDepth=8): PNGColorMode =
|
||||||
new(result)
|
new(result)
|
||||||
result.keyDefined = false
|
result.keyDefined = false
|
||||||
result.keyR = 0
|
result.keyR = 0
|
||||||
@ -231,7 +261,7 @@ proc newColorMode(colorType=LCT_RGBA, bitDepth=8): PNGColorMode =
|
|||||||
result.bitDepth = bitDepth
|
result.bitDepth = bitDepth
|
||||||
result.paletteSize = 0
|
result.paletteSize = 0
|
||||||
|
|
||||||
proc copyTo(src, dest: PNGColorMode) =
|
proc copyTo*(src, dest: PNGColorMode) =
|
||||||
dest.keyDefined = src.keyDefined
|
dest.keyDefined = src.keyDefined
|
||||||
dest.keyR = src.keyR
|
dest.keyR = src.keyR
|
||||||
dest.keyG = src.keyG
|
dest.keyG = src.keyG
|
||||||
@ -243,6 +273,15 @@ proc copyTo(src, dest: PNGColorMode) =
|
|||||||
newSeq(dest.palette, src.paletteSize)
|
newSeq(dest.palette, src.paletteSize)
|
||||||
for i in 0..src.palette.len-1: dest.palette[i] = src.palette[i]
|
for i in 0..src.palette.len-1: dest.palette[i] = src.palette[i]
|
||||||
|
|
||||||
|
proc newColorMode*(mode: PNGColorMode): PNGColorMode =
|
||||||
|
new(result)
|
||||||
|
mode.copyTo(result)
|
||||||
|
|
||||||
|
proc addPalette*(mode: PNGColorMode, r, g, b, a: int) =
|
||||||
|
if mode.palette == nil: mode.palette = @[]
|
||||||
|
mode.palette.add RGBA8(r: chr(r), g: chr(g), b: chr(b), a: chr(a))
|
||||||
|
mode.paletteSize = mode.palette.len
|
||||||
|
|
||||||
proc `==`(a, b: PNGColorMode): bool =
|
proc `==`(a, b: PNGColorMode): bool =
|
||||||
if a.colorType != b.colorType: return false
|
if a.colorType != b.colorType: return false
|
||||||
if a.bitDepth != b.bitDepth: return false
|
if a.bitDepth != b.bitDepth: return false
|
||||||
@ -285,12 +324,12 @@ proc setPosition(s: PNGChunk, pos: int) =
|
|||||||
if pos < 0 or pos > s.data.len: raise PNGError("set position error")
|
if pos < 0 or pos > s.data.len: raise PNGError("set position error")
|
||||||
s.pos = pos
|
s.pos = pos
|
||||||
|
|
||||||
proc hasChunk(png: PNG, chunkType: PNGChunkType): bool =
|
proc hasChunk*(png: PNG, chunkType: PNGChunkType): bool =
|
||||||
for c in png.chunks:
|
for c in png.chunks:
|
||||||
if c.chunkType == chunkType: return true
|
if c.chunkType == chunkType: return true
|
||||||
result = false
|
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
|
||||||
|
|
||||||
@ -368,8 +407,8 @@ proc idatRawSize(w, h: int, header: PNGHeader): int =
|
|||||||
proc getRawSize(w, h: int, color: PNGColorMode): int =
|
proc getRawSize(w, h: int, color: PNGColorMode): int =
|
||||||
result = (w * h * getBPP(color) + 7) div 8
|
result = (w * h * getBPP(color) + 7) div 8
|
||||||
|
|
||||||
proc getRawSizeLct(w, h: int, colorType: PNGcolorType, bitDepth: int): int =
|
#proc getRawSizeLct(w, h: int, colorType: PNGcolorType, bitDepth: int): int =
|
||||||
result = (w * h * LCTBPP(colorType, bitDepth) + 7) div 8
|
# result = (w * h * LCTBPP(colorType, bitDepth) + 7) div 8
|
||||||
|
|
||||||
method validateChunk(chunk: PNGData, png: PNG): bool =
|
method validateChunk(chunk: PNGData, png: PNG): bool =
|
||||||
var header = PNGHeader(png.getChunk(IHDR))
|
var header = PNGHeader(png.getChunk(IHDR))
|
||||||
@ -400,9 +439,11 @@ method parseChunk(chunk: PNGData, png: PNG): bool =
|
|||||||
|
|
||||||
method parseChunk(chunk: PNGTrans, png: PNG): bool =
|
method parseChunk(chunk: PNGTrans, png: PNG): bool =
|
||||||
var header = PNGHeader(png.getChunk(IHDR))
|
var header = PNGHeader(png.getChunk(IHDR))
|
||||||
|
if header == nil: return false
|
||||||
|
|
||||||
if header.colorType == LCT_PALETTE:
|
if header.colorType == LCT_PALETTE:
|
||||||
var plte = PNGPalette(png.getChunk(PLTE))
|
var plte = PNGPalette(png.getChunk(PLTE))
|
||||||
|
if plte == nil: return false
|
||||||
# error: more alpha values given than there are palette entries
|
# error: more alpha values given than there are palette entries
|
||||||
if chunk.length > plte.palette.len:
|
if chunk.length > plte.palette.len:
|
||||||
raise PNGError("more alpha value than palette entries")
|
raise PNGError("more alpha value than palette entries")
|
||||||
@ -661,7 +702,8 @@ method parseChunk(chunk: PNGSbit, png: PNG): bool =
|
|||||||
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 = PNGDecoderSettings(png.settings)
|
var settings = PNGDecoder(png.settings)
|
||||||
|
result = nil
|
||||||
|
|
||||||
if chunkType == IHDR: result = make[PNGHeader]()
|
if chunkType == IHDR: result = make[PNGHeader]()
|
||||||
elif chunkType == PLTE: result = make[PNGPalette]()
|
elif chunkType == PLTE: result = make[PNGPalette]()
|
||||||
@ -700,32 +742,33 @@ proc createChunk(png: PNG, chunkType: PNGChunkType, data: string, crc: uint32):
|
|||||||
if result != nil:
|
if result != nil:
|
||||||
result.initChunk(chunkType, data, crc)
|
result.initChunk(chunkType, data, crc)
|
||||||
|
|
||||||
proc parsePNG(s: Stream, settings: PNGDecoderSettings): PNG =
|
proc parsePNG(s: Stream, settings: PNGDecoder): PNG =
|
||||||
var png: PNG
|
var png: PNG
|
||||||
new(png)
|
new(png)
|
||||||
png.chunks = @[]
|
png.chunks = @[]
|
||||||
if settings == nil: png.settings = makeDefaultPNGDecoderSettings()
|
if settings == nil: png.settings = makePNGDecoder()
|
||||||
else: png.settings = settings
|
else: png.settings = settings
|
||||||
|
|
||||||
let signature = s.readStr(8)
|
let signature = s.readStr(8)
|
||||||
if signature != PNGSignature: raise PNGError("signature mismatch")
|
if signature != PNGSignature:
|
||||||
|
raise PNGError("signature mismatch")
|
||||||
|
|
||||||
while not s.atEnd():
|
while not s.atEnd():
|
||||||
let length = s.readInt32BE()
|
let length = s.readInt32BE()
|
||||||
let chunkType = PNGChunkType(s.readInt32BE())
|
let chunkType = PNGChunkType(s.readInt32BE())
|
||||||
if chunkType == IEND: break
|
|
||||||
|
|
||||||
let data = s.readStr(length)
|
let data = s.readStr(length)
|
||||||
let crc = cast[uint32](s.readInt32BE())
|
let crc = cast[uint32](s.readInt32BE())
|
||||||
let calculatedCRC = crc32(crc32(0, $chunkType), data)
|
let calculatedCRC = crc32(crc32(0, $chunkType), data)
|
||||||
if calculatedCRC != crc and not PNGDecoderSettings(png.settings).ignoreCRC:
|
#if calculatedCRC != crc and not PNGDecoder(png.settings).ignoreCRC:
|
||||||
raise PNGError("wrong crc for: " & $chunkType)
|
#raise PNGError("wrong crc for: " & $chunkType)
|
||||||
var chunk = png.createChunk(chunkType, data, crc)
|
var chunk = png.createChunk(chunkType, data, crc)
|
||||||
|
|
||||||
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: png.chunks.add chunk
|
||||||
|
if chunkType == IEND: break
|
||||||
|
|
||||||
if not png.hasChunk(IHDR): raise PNGError("no IHDR found")
|
if not png.hasChunk(IHDR): raise PNGError("no IHDR found")
|
||||||
if not png.hasChunk(IDAT): raise PNGError("no IDAT found")
|
if not png.hasChunk(IDAT): raise PNGError("no IDAT found")
|
||||||
@ -1013,6 +1056,47 @@ proc getColorMode(png: PNG): PNGColorMode =
|
|||||||
cm.keyB = trans.keyB
|
cm.keyB = trans.keyB
|
||||||
result = cm
|
result = cm
|
||||||
|
|
||||||
|
proc getInfo*(png: PNG): PNGInfo =
|
||||||
|
new(result)
|
||||||
|
result.mode = png.getColorMode()
|
||||||
|
var header = PNGHeader(png.getChunk(IHDR))
|
||||||
|
result.width = header.width
|
||||||
|
result.height = header.height
|
||||||
|
var bkgd = PNGBackground(png.getChunk(bKGD))
|
||||||
|
if bkgd == nil: result.backgroundDefined = false
|
||||||
|
else:
|
||||||
|
result.backgroundDefined = true
|
||||||
|
result.backgroundR = bkgd.bkgdR
|
||||||
|
result.backgroundG = bkgd.bkgdG
|
||||||
|
result.backgroundB = bkgd.bkgdB
|
||||||
|
|
||||||
|
var phys = PNGPhys(png.getChunk(pHYs))
|
||||||
|
if phys == nil: result.physDefined = false
|
||||||
|
else:
|
||||||
|
result.physDefined = true
|
||||||
|
result.physX = phys.physX
|
||||||
|
result.physY = phys.physY
|
||||||
|
result.physUnit = phys.unit
|
||||||
|
|
||||||
|
var time = PNGTime(png.getChunk(tIME))
|
||||||
|
if time == nil: result.timeDefined = false
|
||||||
|
else:
|
||||||
|
result.timeDefined = true
|
||||||
|
result.year = time.year
|
||||||
|
result.month = time.month
|
||||||
|
result.day = time.day
|
||||||
|
result.hour = time.hour
|
||||||
|
result.minute = time.minute
|
||||||
|
result.second = time.second
|
||||||
|
|
||||||
|
proc getChunkNames*(png: PNG): string =
|
||||||
|
result = ""
|
||||||
|
var i = 0
|
||||||
|
for c in png.chunks:
|
||||||
|
result.add ($c.chunkType)
|
||||||
|
if i < png.chunks.high: result.add ' '
|
||||||
|
inc i
|
||||||
|
|
||||||
proc RGBFromGrey8(output: var cstring, input: cstring, numPixels: int, mode: PNGColorMode) =
|
proc RGBFromGrey8(output: var cstring, input: cstring, numPixels: int, mode: PNGColorMode) =
|
||||||
for i in 0..numPixels-1:
|
for i in 0..numPixels-1:
|
||||||
let x = i * 3
|
let x = i * 3
|
||||||
@ -1151,11 +1235,12 @@ proc RGBAFromGrey124(output: var cstring, input: cstring, numPixels: int, mode:
|
|||||||
proc RGBAFromRGB8(output: var cstring, input: cstring, numPixels: int, mode: PNGColorMode) =
|
proc RGBAFromRGB8(output: var cstring, input: cstring, numPixels: int, mode: PNGColorMode) =
|
||||||
for i in 0..numPixels-1:
|
for i in 0..numPixels-1:
|
||||||
let x = i * 4
|
let x = i * 4
|
||||||
output[x] = input[x]
|
let y = i * 3
|
||||||
output[x+1] = input[x+1]
|
output[x] = input[y]
|
||||||
output[x+2] = input[x+2]
|
output[x+1] = input[y+1]
|
||||||
if mode.keyDefined and (mode.keyR == ord(input[x])) and
|
output[x+2] = input[y+2]
|
||||||
(mode.keyG == ord(input[x+1])) and (mode.keyB == ord(input[x+2])): output[x+3] = chr(0)
|
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)
|
else: output[x+3] = chr(255)
|
||||||
|
|
||||||
proc RGBAFromRGB16(output: var cstring, input: cstring, numPixels: int, mode: PNGColorMode) =
|
proc RGBAFromRGB16(output: var cstring, input: cstring, numPixels: int, mode: PNGColorMode) =
|
||||||
@ -1391,7 +1476,7 @@ proc RGBA16FromGreyAlpha(p: var RGBA16, input: cstring, px: int, mode: PNGColorM
|
|||||||
p.r = val
|
p.r = val
|
||||||
p.g = val
|
p.g = val
|
||||||
p.b = val
|
p.b = val
|
||||||
p.a = 256 * ord(input[px + 2]) + ord(input[px + 3])
|
p.a = 256 * ord(input[i + 2]) + ord(input[i + 3])
|
||||||
|
|
||||||
proc RGBA16FromRGBA(p: var RGBA16, input: cstring, px: int, mode: PNGColorMode) =
|
proc RGBA16FromRGBA(p: var RGBA16, input: cstring, px: int, mode: PNGColorMode) =
|
||||||
let i = px * 8
|
let i = px * 8
|
||||||
@ -1586,7 +1671,7 @@ proc getConverterRGBA(mode: PNGColorMode): convertRGBA =
|
|||||||
else: return RGBAFromRGBA16
|
else: return RGBAFromRGBA16
|
||||||
else: raise PNGError("unsupported RGBA converter")
|
else: raise PNGError("unsupported RGBA converter")
|
||||||
|
|
||||||
proc convert(output: var cstring, input: cstring, modeOut, modeIn: PNGColorMode, numPixels: int) =
|
proc convert*(output: var cstring, input: cstring, modeOut, modeIn: PNGColorMode, numPixels: int) =
|
||||||
var tree: ColorTree8
|
var tree: ColorTree8
|
||||||
if modeOut.colorType == LCT_PALETTE:
|
if modeOut.colorType == LCT_PALETTE:
|
||||||
let palSize = min(1 shl modeOut.bitDepth, modeOut.paletteSize)
|
let palSize = min(1 shl modeOut.bitDepth, modeOut.paletteSize)
|
||||||
@ -1615,11 +1700,11 @@ proc convert(output: var cstring, input: cstring, modeOut, modeIn: PNGColorMode,
|
|||||||
cvt(p, input, px, modeIn)
|
cvt(p, input, px, modeIn)
|
||||||
pxl(p, output, px, modeOut, tree)
|
pxl(p, output, px, modeOut, tree)
|
||||||
|
|
||||||
proc convert(png: PNG, colorType: PNGcolorType, bitDepth: int): PNGResult =
|
proc convert*(png: PNG, colorType: PNGcolorType, bitDepth: int): PNGResult =
|
||||||
#TODO: check if this works according to the statement in the documentation: "The converter can convert
|
#TODO: check if this works according to the statement in the documentation: "The converter can convert
|
||||||
# from greyscale input color type, to 8-bit greyscale or greyscale with alpha"
|
# from greyscale input color type, to 8-bit greyscale or greyscale with alpha"
|
||||||
if(colorType notin {LCT_RGB, LCT_RGBA}) and (bitDepth != 8):
|
#if(colorType notin {LCT_RGB, LCT_RGBA}) and (bitDepth != 8):
|
||||||
raise PNGError("unsupported color mode conversion")
|
#raise PNGError("unsupported color mode conversion")
|
||||||
|
|
||||||
let header = PNGHeader(png.getChunk(IHDR))
|
let header = PNGHeader(png.getChunk(IHDR))
|
||||||
let modeIn = png.getColorMode()
|
let modeIn = png.getColorMode()
|
||||||
@ -1640,13 +1725,13 @@ proc convert(png: PNG, colorType: PNGcolorType, bitDepth: int): PNGResult =
|
|||||||
|
|
||||||
convert(output, input, modeOut, modeIn, numPixels)
|
convert(output, input, modeOut, modeIn, numPixels)
|
||||||
|
|
||||||
proc PNGDecode*(s: Stream, colorType: PNGcolorType, bitDepth: int, settings: PNGDecoderSettings): PNGResult =
|
proc PNGDecode*(s: Stream, colorType: PNGcolorType, bitDepth: int, settings = PNGDecoder(nil)): PNGResult =
|
||||||
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 PNGDecoderSettings(png.settings).colorConvert:
|
if PNGDecoder(png.settings).colorConvert:
|
||||||
result = png.convert(colorType, bitDepth)
|
result = png.convert(colorType, bitDepth)
|
||||||
else:
|
else:
|
||||||
let header = PNGHeader(png.getChunk(IHDR))
|
let header = PNGHeader(png.getChunk(IHDR))
|
||||||
@ -1655,7 +1740,12 @@ proc PNGDecode*(s: Stream, colorType: PNGcolorType, bitDepth: int, settings: PNG
|
|||||||
result.height = header.height
|
result.height = header.height
|
||||||
result.data = png.pixels
|
result.data = png.pixels
|
||||||
|
|
||||||
proc loadPNG*(fileName: string, colorType: PNGcolorType, bitDepth: int, settings: PNGDecoderSettings): PNGResult =
|
proc PNGDecode*(s: Stream, settings = PNGDecoder(nil)): PNG =
|
||||||
|
var png = s.parsePNG(settings)
|
||||||
|
png.postProcessscanLines()
|
||||||
|
result = png
|
||||||
|
|
||||||
|
proc loadPNG*(fileName: string, colorType: PNGcolorType, bitDepth: int, settings: PNGDecoder): PNGResult =
|
||||||
try:
|
try:
|
||||||
var s = newFileStream(fileName, fmRead)
|
var s = newFileStream(fileName, fmRead)
|
||||||
if s == nil: return nil
|
if s == nil: return nil
|
||||||
@ -1664,13 +1754,13 @@ proc loadPNG*(fileName: string, colorType: PNGcolorType, bitDepth: int, settings
|
|||||||
echo getCurrentExceptionMsg()
|
echo getCurrentExceptionMsg()
|
||||||
result = nil
|
result = nil
|
||||||
|
|
||||||
proc loadPNG32*(fileName: string, settings = PNGDecoderSettings(nil)): PNGResult =
|
proc loadPNG32*(fileName: string, settings = PNGDecoder(nil)): PNGResult =
|
||||||
result = loadPNG(fileName, LCT_RGBA, 8, settings)
|
result = loadPNG(fileName, LCT_RGBA, 8, settings)
|
||||||
|
|
||||||
proc loadPNG24*(fileName: string, settings = PNGDecoderSettings(nil)): PNGResult =
|
proc loadPNG24*(fileName: string, settings = PNGDecoder(nil)): PNGResult =
|
||||||
result = loadPNG(fileName, LCT_RGB, 8, settings)
|
result = loadPNG(fileName, LCT_RGB, 8, settings)
|
||||||
|
|
||||||
proc pngDecode32*(input: string, settings = PNGDecoderSettings(nil)): PNGResult =
|
proc PNGDecode32*(input: string, settings = PNGDecoder(nil)): PNGResult =
|
||||||
try:
|
try:
|
||||||
var s = newStringStream(input)
|
var s = newStringStream(input)
|
||||||
if s == nil: return nil
|
if s == nil: return nil
|
||||||
@ -1679,7 +1769,7 @@ proc pngDecode32*(input: string, settings = PNGDecoderSettings(nil)): PNGResult
|
|||||||
echo getCurrentExceptionMsg()
|
echo getCurrentExceptionMsg()
|
||||||
result = nil
|
result = nil
|
||||||
|
|
||||||
proc pngDecode24*(input: string, settings = PNGDecoderSettings(nil)): PNGResult =
|
proc PNGDecode24*(input: string, settings = PNGDecoder(nil)): PNGResult =
|
||||||
try:
|
try:
|
||||||
var s = newStringStream(input)
|
var s = newStringStream(input)
|
||||||
if s == nil: return nil
|
if s == nil: return nil
|
||||||
@ -1691,7 +1781,7 @@ proc pngDecode24*(input: string, settings = PNGDecoderSettings(nil)): PNGResult
|
|||||||
#Encoder/Decoder demarcation line-----------------------------
|
#Encoder/Decoder demarcation line-----------------------------
|
||||||
|
|
||||||
type
|
type
|
||||||
PNGFilterStrategy = enum
|
PNGFilterStrategy* = enum
|
||||||
#every filter at zero
|
#every filter at zero
|
||||||
LFS_ZERO,
|
LFS_ZERO,
|
||||||
#Use filter that gives minimum sum, as described in the official PNG filter heuristic.
|
#Use filter that gives minimum sum, as described in the official PNG filter heuristic.
|
||||||
@ -1713,10 +1803,15 @@ type
|
|||||||
text: string
|
text: string
|
||||||
languageTag: string
|
languageTag: string
|
||||||
translatedKeyword: string
|
translatedKeyword: string
|
||||||
|
|
||||||
PNGEncoderSettings = ref object of PNGSettings
|
PNGUnknown = ref object of PNGChunk
|
||||||
|
PNGEnd = ref object of PNGChunk
|
||||||
|
|
||||||
|
PNGEncoder* = ref object of PNGSettings
|
||||||
#automatically choose output PNG color type. Default: true
|
#automatically choose output PNG color type. Default: true
|
||||||
autoConvert*: bool
|
autoConvert*: bool
|
||||||
|
modeIn*: PNGColorMode
|
||||||
|
modeOut*: PNGColorMode
|
||||||
|
|
||||||
#If true, follows the official PNG heuristic: if the PNG uses a palette or lower than
|
#If true, follows the official PNG heuristic: if the PNG uses a palette or lower than
|
||||||
#8 bit depth, set all filters to zero. Otherwise use the filter_strategy. Note that to
|
#8 bit depth, set all filters to zero. Otherwise use the filter_strategy. Note that to
|
||||||
@ -1759,6 +1854,8 @@ type
|
|||||||
hour*: range[0..23]
|
hour*: range[0..23]
|
||||||
minute*: range[0..59]
|
minute*: range[0..59]
|
||||||
second*: range[0..60] #to allow for leap seconds
|
second*: range[0..60] #to allow for leap seconds
|
||||||
|
|
||||||
|
unknown*: seq[PNGUnknown]
|
||||||
|
|
||||||
PNGColorProfile = ref object
|
PNGColorProfile = ref object
|
||||||
colored: bool #not greyscale
|
colored: bool #not greyscale
|
||||||
@ -1768,15 +1865,15 @@ type
|
|||||||
numColors: int #amount of colors, up to 257. Not valid if bits == 16.
|
numColors: int #amount of colors, up to 257. Not valid if bits == 16.
|
||||||
palette: seq[RGBA8] #Remembers up to the first 256 RGBA colors, in no particular order
|
palette: seq[RGBA8] #Remembers up to the first 256 RGBA colors, in no particular order
|
||||||
bits: int #bits per channel (not for palette). 1,2 or 4 for greyscale only. 16 if 16-bit per channel required.
|
bits: int #bits per channel (not for palette). 1,2 or 4 for greyscale only. 16 if 16-bit per channel required.
|
||||||
|
|
||||||
PNGEnd = ref object of PNGChunk
|
|
||||||
|
|
||||||
proc makeDefaultPNGEncoderSettings*(): PNGEncoderSettings =
|
proc makePNGEncoder*(): PNGEncoder =
|
||||||
var s: PNGEncoderSettings
|
var s: PNGEncoder
|
||||||
new(s)
|
new(s)
|
||||||
s.filterPaletteZero = true
|
s.filterPaletteZero = true
|
||||||
s.filterStrategy = LFS_MINSUM
|
s.filterStrategy = LFS_MINSUM
|
||||||
s.autoConvert = true
|
s.autoConvert = true
|
||||||
|
s.modeIn = newColorMode()
|
||||||
|
s.modeOut = newColorMode()
|
||||||
s.forcePalette = false
|
s.forcePalette = false
|
||||||
s.predefinedFilters = nil
|
s.predefinedFilters = nil
|
||||||
s.addID = false
|
s.addID = false
|
||||||
@ -1792,8 +1889,33 @@ proc makeDefaultPNGEncoderSettings*(): PNGEncoderSettings =
|
|||||||
s.physUnit = 0
|
s.physUnit = 0
|
||||||
s.timeDefined = false
|
s.timeDefined = false
|
||||||
s.textList = @[]
|
s.textList = @[]
|
||||||
|
s.itextList = @[]
|
||||||
|
s.unknown = @[]
|
||||||
result = s
|
result = s
|
||||||
|
|
||||||
|
proc addText*(state: PNGEncoder, keyword, text: string) =
|
||||||
|
state.textList.add PNGKeyText(keyword: keyword, text: text)
|
||||||
|
|
||||||
|
proc addIText*(state: PNGEncoder, keyword, langtag, transkey, text: string) =
|
||||||
|
var itext: PNGIText
|
||||||
|
itext.keyword = keyword
|
||||||
|
itext.text = text
|
||||||
|
itext.languageTag = langtag
|
||||||
|
itext.translatedKeyword = transkey
|
||||||
|
state.itextList.add itext
|
||||||
|
|
||||||
|
proc make[T](chunkType: PNGChunkType, estimateSize: int): T =
|
||||||
|
new(result)
|
||||||
|
result.chunkType = chunkType
|
||||||
|
if estimateSize > 0: result.data = newStringOfCap(estimateSize)
|
||||||
|
else: result.data = ""
|
||||||
|
|
||||||
|
proc addUnknownChunk*(state: PNGEncoder, chunkType, data: string) =
|
||||||
|
assert chunkType.len == 4
|
||||||
|
var chunk = make[PNGUnknown](makeChunkType(chunkType), 0)
|
||||||
|
chunk.data = data
|
||||||
|
state.unknown.add chunk
|
||||||
|
|
||||||
proc makeColorProfile(): PNGColorProfile =
|
proc makeColorProfile(): PNGColorProfile =
|
||||||
new(result)
|
new(result)
|
||||||
result.colored = false
|
result.colored = false
|
||||||
@ -1825,12 +1947,6 @@ proc writeInt32BE(s: Stream, value: int) =
|
|||||||
bigEndian32(addr(tmp), addr(val))
|
bigEndian32(addr(tmp), addr(val))
|
||||||
s.write(tmp)
|
s.write(tmp)
|
||||||
|
|
||||||
proc make[T](chunkType: PNGChunkType, estimateSize: int): T =
|
|
||||||
new(result)
|
|
||||||
result.chunkType = chunkType
|
|
||||||
if estimateSize > 0: result.data = newStringOfCap(estimateSize)
|
|
||||||
else: result.data = ""
|
|
||||||
|
|
||||||
method writeChunk(chunk: PNGChunk, png: PNG): bool = true
|
method writeChunk(chunk: PNGChunk, png: PNG): bool = true
|
||||||
|
|
||||||
method writeChunk(chunk: PNGHeader, png: PNG): bool =
|
method writeChunk(chunk: PNGHeader, png: PNG): bool =
|
||||||
@ -1933,17 +2049,31 @@ method writeChunk(chunk: PNGZtxt, png: PNG): bool =
|
|||||||
method writeChunk(chunk: PNGItxt, png: PNG): bool =
|
method writeChunk(chunk: PNGItxt, png: PNG): bool =
|
||||||
#estimate chunk.keyword.len + 2
|
#estimate chunk.keyword.len + 2
|
||||||
# + chunk.languageTag.len + chunk.translatedKeyword.len
|
# + chunk.languageTag.len + chunk.translatedKeyword.len
|
||||||
|
let state = PNGEncoder(png.settings)
|
||||||
|
var compressed: int
|
||||||
|
var text: string
|
||||||
|
if state.textCompression:
|
||||||
|
var nz = nzDeflateInit(chunk.text)
|
||||||
|
var zz = zlib_compress(nz)
|
||||||
|
if zz.len >= chunk.text.len:
|
||||||
|
compressed = 0
|
||||||
|
text = chunk.text
|
||||||
|
else:
|
||||||
|
compressed = 1
|
||||||
|
text = zz
|
||||||
|
else:
|
||||||
|
compressed = 0
|
||||||
|
text = chunk.text
|
||||||
|
|
||||||
chunk.writeString chunk.keyword
|
chunk.writeString chunk.keyword
|
||||||
chunk.writeByte 0 #null separator
|
chunk.writeByte 0 #null separator
|
||||||
chunk.writeByte 1 #compression flag(0: uncompressed, 1: compressed)
|
chunk.writeByte compressed #compression flag(0: uncompressed, 1: compressed)
|
||||||
chunk.writeByte 0 #compression method(0: deflate)
|
chunk.writeByte 0 #compression method(0: deflate)
|
||||||
chunk.writeString chunk.languageTag
|
chunk.writeString chunk.languageTag
|
||||||
chunk.writeByte 0 #null separator
|
chunk.writeByte 0 #null separator
|
||||||
chunk.writeString chunk.translatedKeyword
|
chunk.writeString chunk.translatedKeyword
|
||||||
chunk.writeByte 0 #null separator
|
chunk.writeByte 0 #null separator
|
||||||
var nz = nzDeflateInit(chunk.text)
|
chunk.writeString text
|
||||||
chunk.writeString zlib_compress(nz)
|
|
||||||
result = true
|
result = true
|
||||||
|
|
||||||
method writeChunk(chunk: PNGGamma, png: PNG): bool =
|
method writeChunk(chunk: PNGGamma, png: PNG): bool =
|
||||||
@ -2014,8 +2144,8 @@ proc isGreyscaleType(mode: PNGColorMode): bool =
|
|||||||
proc isAlphaType(mode: PNGColorMode): bool =
|
proc isAlphaType(mode: PNGColorMode): bool =
|
||||||
result = mode.colorType in {LCT_RGBA, LCT_GREY_ALPHA}
|
result = mode.colorType in {LCT_RGBA, LCT_GREY_ALPHA}
|
||||||
|
|
||||||
proc isPaletteType(mode: PNGColorMode): bool =
|
#proc isPaletteType(mode: PNGColorMode): bool =
|
||||||
result = mode.colorType == LCT_PALETTE
|
# result = mode.colorType == LCT_PALETTE
|
||||||
|
|
||||||
proc hasPaletteAlpha(mode: PNGColorMode): bool =
|
proc hasPaletteAlpha(mode: PNGColorMode): bool =
|
||||||
for p in mode.palette:
|
for p in mode.palette:
|
||||||
@ -2110,25 +2240,25 @@ proc getColorProfile(input: string, w, h: int, mode: PNGColorMode): PNGColorProf
|
|||||||
if alphaDone and numColorsDone and coloredDone and bitsDone: break
|
if alphaDone and numColorsDone and coloredDone and bitsDone: break
|
||||||
else: # < 16-bit
|
else: # < 16-bit
|
||||||
let cvt = getColorRGBA8(mode)
|
let cvt = getColorRGBA8(mode)
|
||||||
var p = RGBA8(r:chr(0), g:chr(0), b:chr(0), a:chr(0))
|
|
||||||
for px in 0..numPixels-1:
|
for px in 0..numPixels-1:
|
||||||
|
var p = RGBA8(r:chr(0), g:chr(0), b:chr(0), a:chr(0))
|
||||||
cvt(p, cstring(input), px, mode)
|
cvt(p, cstring(input), px, mode)
|
||||||
if not bitsDone and (prof.bits < 8):
|
if (not bitsDone) and (prof.bits < 8):
|
||||||
#only r is checked, < 8 bits is only relevant for greyscale
|
#only r is checked, < 8 bits is only relevant for greyscale
|
||||||
let bits = getValueRequiredBits(int(p.r))
|
let bits = getValueRequiredBits(int(p.r))
|
||||||
if bits > prof.bits: prof.bits = bits
|
if bits > prof.bits: prof.bits = bits
|
||||||
bitsDone = prof.bits >= bpp
|
bitsDone = prof.bits >= bpp
|
||||||
|
|
||||||
if not coloredDone and ((p.r != p.g) or (p.r != p.b)):
|
if (not coloredDone) and ((p.r != p.g) or (p.r != p.b)):
|
||||||
prof.colored = true
|
prof.colored = true
|
||||||
coloredDone = true
|
coloredDone = true
|
||||||
if prof.bits < 8: prof.bits = 8 #PNG has no colored modes with less than 8-bit per channel
|
if prof.bits < 8: prof.bits = 8 #PNG has no colored modes with less than 8-bit per channel
|
||||||
|
|
||||||
if not alphaDone:
|
if not alphaDone:
|
||||||
let matchKey = (int(p.r) == prof.keyR and
|
let matchKey = ((int(p.r) == prof.keyR) and
|
||||||
int(p.g) == prof.keyG and int(p.b) == prof.keyB)
|
(int(p.g) == prof.keyG) and (int(p.b) == prof.keyB))
|
||||||
|
|
||||||
if(p.a != chr(255)) and (p.a != chr(0) or (prof.key and not matchKey)):
|
if(p.a != chr(255)) and (p.a != chr(0) or (prof.key and (not matchKey))):
|
||||||
prof.alpha = true
|
prof.alpha = true
|
||||||
alphaDone = true
|
alphaDone = true
|
||||||
if prof.bits < 8: prof.bits = 8 #PNG has no alphachannel modes with less than 8-bit per channel
|
if prof.bits < 8: prof.bits = 8 #PNG has no alphachannel modes with less than 8-bit per channel
|
||||||
@ -2173,6 +2303,7 @@ proc autoChooseColor(modeOut: PNGColorMode, input: string, w, h: int, modeIn: PN
|
|||||||
#grey without alpha, with potentially low bits
|
#grey without alpha, with potentially low bits
|
||||||
let greyOk = not prof.colored and not prof.alpha
|
let greyOk = not prof.colored and not prof.alpha
|
||||||
let n = prof.numColors
|
let n = prof.numColors
|
||||||
|
|
||||||
var paletteBits = 0
|
var paletteBits = 0
|
||||||
if n <= 2: paletteBits = 1
|
if n <= 2: paletteBits = 1
|
||||||
elif n <= 4: paletteBits = 2
|
elif n <= 4: paletteBits = 2
|
||||||
@ -2224,44 +2355,45 @@ proc addPaddingBits(output: var cstring, input: cstring, olinebits, ilinebits, h
|
|||||||
for x in 0..ilinebits-1:
|
for x in 0..ilinebits-1:
|
||||||
let bit = readBitFromReversedStream(ibp, input)
|
let bit = readBitFromReversedStream(ibp, input)
|
||||||
setBitOfReversedStream(obp, output, bit)
|
setBitOfReversedStream(obp, output, bit)
|
||||||
for x in 0..diff-1: setBitOfReversedStream(obp, output, 0)
|
for x in 0..diff-1: setBitOfReversedStream(obp, output, 0)
|
||||||
|
|
||||||
proc filterScanLine(output: var cstring, scanLine, prevLine: cstring, len, byteWidth: int, filterType: PNGFilter0) =
|
proc filterScanLine(output: var cstring, scanLine, prevLine: cstring, len, byteWidth: int, filterType: PNGFilter0) =
|
||||||
|
|
||||||
case filterType
|
case filterType
|
||||||
of FLT_NONE:
|
of FLT_NONE:
|
||||||
for i in 0..len-1: output[i] = scanLine[i]
|
for i in 0..len-1: output[i] = scanLine[i]
|
||||||
of FLT_SUB:
|
of FLT_SUB:
|
||||||
for i in 0..byteWidth-1: output[i] = scanLine[i]
|
for i in 0..byteWidth-1: output[i] = scanLine[i]
|
||||||
for i in byteWidth..len-1:
|
for i in byteWidth..len-1:
|
||||||
output[i] = chr(ord(scanLine[i]) - ord(scanLine[i - byteWidth]))
|
output[i] = chr(scanLine[i].uint8 - scanLine[i - byteWidth].uint8)
|
||||||
of FLT_UP:
|
of FLT_UP:
|
||||||
if prevLine != nil:
|
if prevLine != nil:
|
||||||
for i in 0..len-1:
|
for i in 0..len-1:
|
||||||
output[i] = chr(ord(scanLine[i]) - ord(prevLine[i]))
|
output[i] = chr(scanLine[i].uint8 - prevLine[i].uint8)
|
||||||
else:
|
else:
|
||||||
for i in 0..len-1: output[i] = scanLine[i]
|
for i in 0..len-1: output[i] = scanLine[i]
|
||||||
of FLT_AVERAGE:
|
of FLT_AVERAGE:
|
||||||
if prevLine != nil:
|
if prevLine != nil:
|
||||||
for i in 0..byteWidth-1:
|
for i in 0..byteWidth-1:
|
||||||
output[i] = chr(ord(scanLine[i]) - (ord(prevLine[i]) div 2))
|
output[i] = chr(scanLine[i].uint8 - (prevLine[i].uint8 div 2))
|
||||||
for i in byteWidth..len-1:
|
for i in byteWidth..len-1:
|
||||||
output[i] = chr(ord(scanLine[i]) - ((ord(scanLine[i - byteWidth]) + ord(prevLine[i])) div 2))
|
output[i] = chr(scanLine[i].uint8 - ((scanLine[i - byteWidth].uint8 + prevLine[i].uint8) div 2))
|
||||||
else:
|
else:
|
||||||
for i in 0..byteWidth-1: output[i] = scanLine[i]
|
for i in 0..byteWidth-1: output[i] = scanLine[i]
|
||||||
for i in byteWidth..len-1:
|
for i in byteWidth..len-1:
|
||||||
output[i] = chr(ord(scanLine[i]) - (ord(scanLine[i - byteWidth]) div 2))
|
output[i] = chr(scanLine[i].uint8 - (scanLine[i - byteWidth].uint8 div 2))
|
||||||
of FLT_PAETH:
|
of FLT_PAETH:
|
||||||
if prevLine != nil:
|
if prevLine != nil:
|
||||||
#paethPredictor(0, prevLine[i], 0) is always prevLine[i]
|
#paethPredictor(0, prevLine[i], 0) is always prevLine[i]
|
||||||
for i in 0..byteWidth-1:
|
for i in 0..byteWidth-1:
|
||||||
output[i] = chr(ord(scanLine[i]) - ord(prevLine[i]))
|
output[i] = chr(scanLine[i].uint8 - prevLine[i].uint8)
|
||||||
for i in byteWidth..len-1:
|
for i in byteWidth..len-1:
|
||||||
output[i] = chr(ord(scanLine[i]) - paethPredictor(ord(scanLine[i - byteWidth]), ord(prevLine[i]), ord(prevLine[i - byteWidth])))
|
output[i] = chr(scanLine[i].uint8 - paethPredictor(ord(scanLine[i - byteWidth]), ord(prevLine[i]), ord(prevLine[i - byteWidth])).uint8)
|
||||||
else:
|
else:
|
||||||
for i in 0..byteWidth-1: output[i] = scanLine[i]
|
for i in 0..byteWidth-1: output[i] = scanLine[i]
|
||||||
#paethPredictor(scanLine[i - byteWidth], 0, 0) is always scanLine[i - byteWidth]
|
#paethPredictor(scanLine[i - byteWidth], 0, 0) is always scanLine[i - byteWidth]
|
||||||
for i in byteWidth..len-1:
|
for i in byteWidth..len-1:
|
||||||
output[i] = chr(ord(scanLine[i]) - ord(scanLine[i - byteWidth]))
|
output[i] = chr(scanLine[i].uint8 - scanLine[i - byteWidth].uint8)
|
||||||
else:
|
else:
|
||||||
raise PNGError("unsupported fitler type")
|
raise PNGError("unsupported fitler type")
|
||||||
|
|
||||||
@ -2371,7 +2503,7 @@ proc filterEntropy(output: var cstring, input: cstring, w, h, bpp: int) =
|
|||||||
for x in 0..lineBytes-1:
|
for x in 0..lineBytes-1:
|
||||||
output[y * (lineBytes + 1) + 1 + x] = attempt[bestType][x]
|
output[y * (lineBytes + 1) + 1 + x] = attempt[bestType][x]
|
||||||
|
|
||||||
proc filterPredefined(output: var cstring, input: cstring, w, h, bpp: int, state: PNGEncoderSettings) =
|
proc filterPredefined(output: var cstring, input: cstring, w, h, bpp: int, state: PNGEncoder) =
|
||||||
let lineBytes = (w * bpp + 7) div 8
|
let lineBytes = (w * bpp + 7) div 8
|
||||||
let byteWidth = (bpp + 7) div 8
|
let byteWidth = (bpp + 7) div 8
|
||||||
var inp = input
|
var inp = input
|
||||||
@ -2431,7 +2563,7 @@ proc filterBruteForce(output: var cstring, input: cstring, w, h, bpp: int) =
|
|||||||
for x in 0..lineBytes-1:
|
for x in 0..lineBytes-1:
|
||||||
output[y * (lineBytes + 1) + 1 + x] = attempt[bestType][x]
|
output[y * (lineBytes + 1) + 1 + x] = attempt[bestType][x]
|
||||||
|
|
||||||
proc filter(output: var cstring, input: cstring, w, h: int, modeOut: PNGColorMode, state: PNGEncoderSettings) =
|
proc filter(output: var cstring, input: cstring, w, h: int, modeOut: PNGColorMode, state: PNGEncoder) =
|
||||||
#For PNG filter method 0
|
#For PNG filter method 0
|
||||||
#out must be a buffer with as size: h + (w * h * bpp + 7) / 8, because there are
|
#out must be a buffer with as size: h + (w * h * bpp + 7) / 8, because there are
|
||||||
#the scanlines with 1 extra byte per scanline
|
#the scanlines with 1 extra byte per scanline
|
||||||
@ -2497,8 +2629,8 @@ proc Adam7Interlace(output: var cstring, input: cstring, w, h, bpp: int) =
|
|||||||
for b in 0..bpp-1:
|
for b in 0..bpp-1:
|
||||||
let bit = readBitFromReversedStream(ibp, input)
|
let bit = readBitFromReversedStream(ibp, input)
|
||||||
setBitOfReversedStream(obp, output, bit)
|
setBitOfReversedStream(obp, output, bit)
|
||||||
|
|
||||||
proc preProcessScanLines(png: PNG, input: cstring, w, h: int, modeOut: PNGColorMode, state: PNGEncoderSettings) =
|
proc preProcessScanLines(png: PNG, input: cstring, 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:
|
#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 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
|
# if adam7: 1) Adam7_interlace 2) 7x add padding bits 3) 7x filter
|
||||||
@ -2515,10 +2647,12 @@ proc preProcessScanLines(png: PNG, input: cstring, w, h: int, modeOut: PNGColorM
|
|||||||
var padded = newString(h * scanLen)
|
var padded = newString(h * scanLen)
|
||||||
var padding = cstring(padded)
|
var padding = cstring(padded)
|
||||||
addPaddingBits(padding, input, scanLen * 8, w * bpp, h)
|
addPaddingBits(padding, input, scanLen * 8, w * bpp, h)
|
||||||
|
|
||||||
filter(output, padding, w, h, modeOut, state)
|
filter(output, padding, w, h, modeOut, state)
|
||||||
else:
|
else:
|
||||||
#we can immediatly filter into the out buffer, no other steps needed
|
#we can immediatly filter into the out buffer, no other steps needed
|
||||||
filter(output, input, w, h, modeOut, state)
|
filter(output, input, w, h, modeOut, state)
|
||||||
|
|
||||||
else: #interlaceMethod is 1 (Adam7)
|
else: #interlaceMethod is 1 (Adam7)
|
||||||
var pass: PNGPass
|
var pass: PNGPass
|
||||||
Adam7PassValues(pass, w, h, bpp)
|
Adam7PassValues(pass, w, h, bpp)
|
||||||
@ -2562,7 +2696,7 @@ proc getPaletteTranslucency(modeOut: PNGColorMode): int =
|
|||||||
|
|
||||||
result = key
|
result = key
|
||||||
|
|
||||||
proc addChunkIHDR(png: PNG, w,h: int, modeOut: PNGColorMode, state: PNGEncoderSettings) =
|
proc addChunkIHDR(png: PNG, w,h: int, modeOut: PNGColorMode, state: PNGEncoder) =
|
||||||
var chunk = make[PNGHeader](IHDR, 13)
|
var chunk = make[PNGHeader](IHDR, 13)
|
||||||
chunk.width = w
|
chunk.width = w
|
||||||
chunk.height = h
|
chunk.height = h
|
||||||
@ -2599,7 +2733,7 @@ proc addChunktRNS(png: PNG, modeOut: PNGColorMode) =
|
|||||||
chunk.keyR = -1
|
chunk.keyR = -1
|
||||||
png.chunks.add chunk
|
png.chunks.add chunk
|
||||||
|
|
||||||
proc addChunkbKGD(png: PNG, modeOut: PNGColorMode, state: PNGEncoderSettings) =
|
proc addChunkbKGD(png: PNG, modeOut: PNGColorMode, state: PNGEncoder) =
|
||||||
var chunk = make[PNGBackground](bKGD, 6)
|
var chunk = make[PNGBackground](bKGD, 6)
|
||||||
if modeOut.colorType == LCT_PALETTE:
|
if modeOut.colorType == LCT_PALETTE:
|
||||||
#estimate 1 bytes
|
#estimate 1 bytes
|
||||||
@ -2614,19 +2748,19 @@ proc addChunkbKGD(png: PNG, modeOut: PNGColorMode, state: PNGEncoderSettings) =
|
|||||||
chunk.bkgdB = state.backgroundB
|
chunk.bkgdB = state.backgroundB
|
||||||
png.chunks.add chunk
|
png.chunks.add chunk
|
||||||
|
|
||||||
proc addChunkpHYs(png: PNG, state: PNGEncoderSettings) =
|
proc addChunkpHYs(png: PNG, state: PNGEncoder) =
|
||||||
var chunk = make[PNGPhys](pHYs, 9)
|
var chunk = make[PNGPhys](pHYs, 9)
|
||||||
chunk.physX = state.physX
|
chunk.physX = state.physX
|
||||||
chunk.physY = state.physY
|
chunk.physY = state.physY
|
||||||
chunk.unit = state.physUnit
|
chunk.unit = state.physUnit
|
||||||
png.chunks.add chunk
|
png.chunks.add chunk
|
||||||
|
|
||||||
proc addChunkIDAT(png: PNG, state: PNGEncoderSettings) =
|
proc addChunkIDAT(png: PNG, state: PNGEncoder) =
|
||||||
var chunk = make[PNGData](IDAT, 0)
|
var chunk = make[PNGData](IDAT, 0)
|
||||||
chunk.idat = png.pixels
|
chunk.idat = png.pixels
|
||||||
png.chunks.add chunk
|
png.chunks.add chunk
|
||||||
|
|
||||||
proc addChunktIME(png: PNG, state: PNGEncoderSettings) =
|
proc addChunktIME(png: PNG, state: PNGEncoder) =
|
||||||
var chunk = make[PNGTime](tIME, 0)
|
var chunk = make[PNGTime](tIME, 0)
|
||||||
chunk.year = state.year
|
chunk.year = state.year
|
||||||
chunk.month = state.month
|
chunk.month = state.month
|
||||||
@ -2660,27 +2794,45 @@ proc addChunkIEND(png: PNG) =
|
|||||||
var chunk = make[PNGEnd](IEND, 0)
|
var chunk = make[PNGEnd](IEND, 0)
|
||||||
png.chunks.add chunk
|
png.chunks.add chunk
|
||||||
|
|
||||||
proc PNGEncode*(input: string, colorType: PNGcolorType, bitDepth, w, h: int, settings: PNGEncoderSettings): PNG =
|
proc `$`(colorType: PNGColorType): string =
|
||||||
if not bitDepthAllowed(colorType, bitDepth):
|
case colorType
|
||||||
raise PNGError("colorType and bitDepth combination not allowed")
|
of LCT_GREY: result = "LCT_GREY"
|
||||||
|
of LCT_RGB: result = "LCT_RGB"
|
||||||
|
of LCT_PALETTE: result = "LCT_PALETTE"
|
||||||
|
of LCT_GREY_ALPHA: result = "LCT_GREY_ALPHA"
|
||||||
|
of LCT_RGBA: result = "LCT_RGBA"
|
||||||
|
else: result = "LCT_UNKNOWN"
|
||||||
|
|
||||||
|
proc show*(mode: PNGColorMode) =
|
||||||
|
for k, v in fieldPairs(mode[]):
|
||||||
|
echo k, " ", $v
|
||||||
|
|
||||||
|
proc PNGEncode*(input: string, w, h: int, settings = PNGEncoder(nil)): PNG =
|
||||||
var png: PNG
|
var png: PNG
|
||||||
new(png)
|
new(png)
|
||||||
png.chunks = @[]
|
png.chunks = @[]
|
||||||
|
|
||||||
if settings == nil: png.settings = makeDefaultPNGEncoderSettings()
|
if settings == nil: png.settings = makePNGEncoder()
|
||||||
else: png.settings = settings
|
else: png.settings = settings
|
||||||
|
|
||||||
var modeIn = newColorMode(colorType, bitDepth)
|
let state = PNGEncoder(png.settings)
|
||||||
var modeOut = newColorMode(colorType, bitDepth)
|
var modeIn = newColorMode(state.modeIn)
|
||||||
let state = PNGEncoderSettings(png.settings)
|
var modeOut = newColorMode(state.modeOut)
|
||||||
|
|
||||||
|
if not bitDepthAllowed(modeIn.colorType, modeIn.bitDepth):
|
||||||
|
raise PNGError("modeIn colorType and bitDepth combination not allowed")
|
||||||
|
|
||||||
|
if not bitDepthAllowed(modeOut.colorType, modeOut.bitDepth):
|
||||||
|
raise PNGError("modeOut colorType and bitDepth combination not allowed")
|
||||||
|
|
||||||
if(modeOut.colorType == LCT_PALETTE or state.forcePalette) and
|
if(modeOut.colorType == LCT_PALETTE or state.forcePalette) and
|
||||||
(modeOut.paletteSize == 0 or modeOut.paletteSize > 256):
|
(modeOut.paletteSize == 0 or modeOut.paletteSize > 256):
|
||||||
raise PNGError("invalid palette size, it is only allowed to be 1-256")
|
raise PNGError("invalid palette size, it is only allowed to be 1-256")
|
||||||
|
|
||||||
if state.autoConvert:
|
if state.autoConvert:
|
||||||
autoChooseColor(modeOut, input, w, h, modeIn)
|
autoChooseColor(modeOut, input, w, h, modeIn)
|
||||||
|
#modeOut.show
|
||||||
|
#modeIn.show
|
||||||
|
|
||||||
if state.interlaceMethod notin {IM_NONE, IM_INTERLACED}:
|
if state.interlaceMethod notin {IM_NONE, IM_INTERLACED}:
|
||||||
raise PNGError("unexisting interlace mode")
|
raise PNGError("unexisting interlace mode")
|
||||||
@ -2691,6 +2843,7 @@ proc PNGEncode*(input: string, colorType: PNGcolorType, bitDepth, w, h: int, set
|
|||||||
if modeIn != modeOut:
|
if modeIn != modeOut:
|
||||||
let size = (w * h * getBPP(modeOut) + 7) div 8
|
let size = (w * h * getBPP(modeOut) + 7) div 8
|
||||||
let numPixels = w * h
|
let numPixels = w * h
|
||||||
|
|
||||||
var converted = newString(size)
|
var converted = newString(size)
|
||||||
var output = cstring(converted)
|
var output = cstring(converted)
|
||||||
convert(output, cstring(input), modeOut, modeIn, numPixels)
|
convert(output, cstring(input), modeOut, modeIn, numPixels)
|
||||||
@ -2699,6 +2852,10 @@ proc PNGEncode*(input: string, colorType: PNGcolorType, bitDepth, w, h: int, set
|
|||||||
preProcessScanLines(png, cstring(input), w, h, modeOut, state)
|
preProcessScanLines(png, cstring(input), w, h, modeOut, state)
|
||||||
|
|
||||||
png.addChunkIHDR(w, h, modeOut, state)
|
png.addChunkIHDR(w, h, modeOut, state)
|
||||||
|
#unknown chunks between IHDR and PLTE
|
||||||
|
if state.unknown.len > 0:
|
||||||
|
png.chunks.add state.unknown[0]
|
||||||
|
|
||||||
if modeOut.colorType == LCT_PALETTE: png.addChunkPLTE(modeOut)
|
if modeOut.colorType == LCT_PALETTE: png.addChunkPLTE(modeOut)
|
||||||
if state.forcePalette and modeOut.colorType in {LCT_RGB, LCT_RGBA}: png.addChunkPLTE(modeOut)
|
if state.forcePalette and modeOut.colorType in {LCT_RGB, LCT_RGBA}: png.addChunkPLTE(modeOut)
|
||||||
|
|
||||||
@ -2714,6 +2871,10 @@ proc PNGEncode*(input: string, colorType: PNGcolorType, bitDepth, w, h: int, set
|
|||||||
#pHYs (must come before the IDAT chunks)
|
#pHYs (must come before the IDAT chunks)
|
||||||
if state.physDefined: png.addChunkpHYs(state)
|
if state.physDefined: png.addChunkpHYs(state)
|
||||||
|
|
||||||
|
#unknown chunks between PLTE and IDAT
|
||||||
|
if state.unknown.len > 1:
|
||||||
|
png.chunks.add state.unknown[1]
|
||||||
|
|
||||||
#IDAT (multiple IDAT chunks must be consecutive)
|
#IDAT (multiple IDAT chunks must be consecutive)
|
||||||
png.addChunkIDAT(state)
|
png.addChunkIDAT(state)
|
||||||
|
|
||||||
@ -2730,10 +2891,26 @@ proc PNGEncode*(input: string, colorType: PNGcolorType, bitDepth, w, h: int, set
|
|||||||
for txt in state.itextList:
|
for txt in state.itextList:
|
||||||
png.addChunkiTXt(txt)
|
png.addChunkiTXt(txt)
|
||||||
|
|
||||||
|
#unknown chunks between IDAT and IEND
|
||||||
|
if state.unknown.len > 2:
|
||||||
|
png.chunks.add state.unknown[2]
|
||||||
|
|
||||||
png.addChunkIEND()
|
png.addChunkIEND()
|
||||||
result = png
|
result = png
|
||||||
|
|
||||||
proc writeChunks(png: PNG, s: Stream) =
|
proc PNGEncode*(input: string, colorType: PNGcolorType, bitDepth, w, h: int, settings = PNGEncoder(nil)): PNG =
|
||||||
|
if not bitDepthAllowed(colorType, bitDepth):
|
||||||
|
raise PNGError("colorType and bitDepth combination not allowed")
|
||||||
|
|
||||||
|
var state: PNGEncoder
|
||||||
|
if settings == nil: state = makePNGEncoder()
|
||||||
|
else: state = settings
|
||||||
|
|
||||||
|
state.modeIn.colorType = colorType
|
||||||
|
state.modeIn.bitDepth = bitDepth
|
||||||
|
result = PNGEncode(input, w, h, state)
|
||||||
|
|
||||||
|
proc writeChunks*(png: PNG, s: Stream) =
|
||||||
s.write PNGSignature
|
s.write PNGSignature
|
||||||
|
|
||||||
for chunk in png.chunks:
|
for chunk in png.chunks:
|
||||||
@ -2746,3 +2923,48 @@ proc writeChunks(png: PNG, s: Stream) =
|
|||||||
s.writeInt32BE int(chunk.chunkType)
|
s.writeInt32BE int(chunk.chunkType)
|
||||||
s.write chunk.data
|
s.write chunk.data
|
||||||
s.writeInt32BE cast[int](chunk.crc)
|
s.writeInt32BE cast[int](chunk.crc)
|
||||||
|
|
||||||
|
proc getFilterTypesInterlaced(png: PNG): seq[string] =
|
||||||
|
var header = PNGHeader(png.getChunk(IHDR))
|
||||||
|
var idat = PNGData(png.getChunk(IDAT))
|
||||||
|
|
||||||
|
if header.interlaceMethod == IM_NONE:
|
||||||
|
result = newSeq[string](1)
|
||||||
|
result[0] = ""
|
||||||
|
|
||||||
|
#A line is 1 filter byte + all pixels
|
||||||
|
let lineBytes = 1 + idatRawSize(header.width, 1, header)
|
||||||
|
var i = 0
|
||||||
|
while i < idat.idat.len:
|
||||||
|
result[0].add idat.idat[i]
|
||||||
|
inc(i, lineBytes)
|
||||||
|
else:
|
||||||
|
result = newSeq[string](7)
|
||||||
|
for j in 0..6:
|
||||||
|
result[j] = ""
|
||||||
|
var w2 = (header.width - ADAM7_IX[j] + ADAM7_DX[j] - 1) div ADAM7_DX[j]
|
||||||
|
var h2 = (header.height - ADAM7_IY[j] + ADAM7_DY[j] - 1) div ADAM7_DY[j]
|
||||||
|
if(ADAM7_IX[j] >= header.width) or (ADAM7_IY[j] >= header.height):
|
||||||
|
w2 = 0
|
||||||
|
h2 = 0
|
||||||
|
|
||||||
|
let lineBytes = 1 + idatRawSize(w2, 1, header)
|
||||||
|
var pos = 0
|
||||||
|
for i in 0..h2-1:
|
||||||
|
result[j].add idat.idat[pos]
|
||||||
|
inc(pos, linebytes)
|
||||||
|
|
||||||
|
proc getFilterTypes*(png: PNG): string =
|
||||||
|
var passes = getFilterTypesInterlaced(png)
|
||||||
|
|
||||||
|
if passes.len == 1:
|
||||||
|
result = passes[0]
|
||||||
|
else:
|
||||||
|
var header = PNGHeader(png.getChunk(IHDR))
|
||||||
|
#Interlaced. Simplify it: put pass 6 and 7 alternating in the one vector so
|
||||||
|
#that one filter per scanline of the uninterlaced image is given, with that
|
||||||
|
#filter corresponding the closest to what it would be for non-interlaced image.
|
||||||
|
result = ""
|
||||||
|
for i in 0..header.height-1:
|
||||||
|
if (i mod 2) == 0: result.add passes[5][i div 2]
|
||||||
|
else: result.add passes[6][i div 2]
|
2
nimz.nim
2
nimz.nim
@ -783,7 +783,7 @@ proc getHash(nz: nzStream, size, pos: int): int =
|
|||||||
|
|
||||||
proc countZeros(nz: nzStream, size, pos: int): int =
|
proc countZeros(nz: nzStream, size, pos: int): int =
|
||||||
var datapos = pos
|
var datapos = pos
|
||||||
var dataend = min(datapos + MAX_SUPPORTED_DEFLATE_LENGTH, datapos + size)
|
var dataend = min(datapos + MAX_SUPPORTED_DEFLATE_LENGTH, size)
|
||||||
while datapos < dataend and nz.data[datapos] == chr(0): inc datapos
|
while datapos < dataend and nz.data[datapos] == chr(0): inc datapos
|
||||||
#subtracting two addresses returned as 32-bit number (max value is MAX_SUPPORTED_DEFLATE_LENGTH)
|
#subtracting two addresses returned as 32-bit number (max value is MAX_SUPPORTED_DEFLATE_LENGTH)
|
||||||
result = datapos - pos
|
result = datapos - pos
|
||||||
|
1026
testCodec.nim
Normal file
1026
testCodec.nim
Normal file
File diff suppressed because it is too large
Load Diff
@ -1,4 +1,4 @@
|
|||||||
import streams, os, strutils, png
|
import streams, os, strutils, nimPNG
|
||||||
|
|
||||||
type
|
type
|
||||||
BMP = ref object
|
BMP = ref object
|
Loading…
x
Reference in New Issue
Block a user