From 43d5548237f8a7ba6e6190855904592d93ea90ea Mon Sep 17 00:00:00 2001 From: andri lim Date: Mon, 13 Apr 2020 18:36:40 +0700 Subject: [PATCH 01/11] gc:arc refactor 'filterScanline' and add tests --- nimPNG.nimble | 3 + nimPNG/filters.nim | 310 +++++++++++++++++++++++++++++++++++++++++ tests/randutils.nim | 58 ++++++++ tests/test_filters.nim | 97 +++++++++++++ 4 files changed, 468 insertions(+) create mode 100644 nimPNG/filters.nim create mode 100644 tests/randutils.nim create mode 100644 tests/test_filters.nim diff --git a/nimPNG.nimble b/nimPNG.nimble index 609f0d6..36ff277 100644 --- a/nimPNG.nimble +++ b/nimPNG.nimble @@ -13,10 +13,13 @@ task tests, "Run tests": exec "nim c -r tests/test_codec.nim" exec "nim c -r tests/test_suite.nim" exec "nim c -r tests/test_nimz.nim" + exec "nim c -r tests/test_filters.nim" exec "nim c -r -d:release tests/test_apng.nim" exec "nim c -r -d:release tests/test_codec.nim" exec "nim c -r -d:release tests/test_suite.nim" exec "nim c -r -d:release tests/test_nimz.nim" + exec "nim c -r -d:release tests/test_filters.nim" exec "nim c -r --gc:arc -d:release tests/test_nimz.nim" + exec "nim c -r --gc:arc -d:release tests/test_filters.nim" diff --git a/nimPNG/filters.nim b/nimPNG/filters.nim new file mode 100644 index 0000000..ae215ef --- /dev/null +++ b/nimPNG/filters.nim @@ -0,0 +1,310 @@ +type + PNGFilter* = enum + FLT_NONE, + FLT_SUB, + FLT_UP, + FLT_AVERAGE, + FLT_PAETH + +# Paeth predicter, used by PNG filter type 4 +proc paethPredictor(a, b, c: int): uint = + let pa = abs(b - c) + let pb = abs(a - c) + let pc = abs(a + b - c - c) + + if(pc < pa) and (pc < pb): return c.uint + elif pb < pa: return b.uint + result = a.uint + +proc filterScanline*(output: var openArray[byte], input: openArray[byte], byteWidth, len: int, filterType: PNGFilter) = + template currPix: untyped = input[i].uint + template prevPix: untyped = input[i - byteWidth].uint + + case filterType + of FLT_NONE: + for i in 0.. Date: Mon, 13 Apr 2020 20:55:50 +0700 Subject: [PATCH 02/11] gc:arc refactor add LFS_ZERO, LFS_PREDEFINED, LFS_ENTROPY filter and tests --- nimPNG/filters.nim | 157 +++++++++++++++++++++++++++++------------ tests/randutils.nim | 2 +- tests/test_filters.nim | 73 +++++++++++++++++-- 3 files changed, 181 insertions(+), 51 deletions(-) diff --git a/nimPNG/filters.nim b/nimPNG/filters.nim index ae215ef..e60d6aa 100644 --- a/nimPNG/filters.nim +++ b/nimPNG/filters.nim @@ -1,3 +1,5 @@ +import math + type PNGFilter* = enum FLT_NONE, @@ -40,7 +42,7 @@ proc filterScanline*(output: var openArray[byte], input: openArray[byte], byteWi of FLT_PAETH: for i in 0.. 0: + output[0] = byte(FLT_NONE) # filterType byte + filterScanline(output.toOpenArray(1, output.len-1), # skip filterType + input, byteWidth, lineBytes, FLT_NONE) + + # next line start from 1 + var prevIndex = 0 + for y in 1.. 0: + output[0] = byte(predefinedFilters[0]) # filterType byte + filterScanline(output.toOpenArray(1, output.len-1), # skip filterType + input, byteWidth, lineBytes, predefinedFilters[0]) + + # next line start from 1 + var prevIndex = 0 + for y in 1.. 0: + unfilterScanLine(output, + input.toOpenArray(1, input.len-1), # skip the filterType + byteWidth, lineBytes, + PNGFilter(input[0])) + + # next line start from 1 + var prevIndex = 0 + for y in 1.. Date: Mon, 13 Apr 2020 20:56:40 +0700 Subject: [PATCH 03/11] add png_types.nim --- nimPNG/png_types.nim | 14 ++++++++++++++ 1 file changed, 14 insertions(+) create mode 100644 nimPNG/png_types.nim diff --git a/nimPNG/png_types.nim b/nimPNG/png_types.nim new file mode 100644 index 0000000..9a687df --- /dev/null +++ b/nimPNG/png_types.nim @@ -0,0 +1,14 @@ +type + PNGFilterStrategy* = enum + #every filter at zero + LFS_ZERO, + #Use filter that gives minimum sum, as described in the official PNG filter heuristic. + LFS_MINSUM, + #Use the filter type that gives smallest Shannon entropy for this scanLine. Depending + #on the image, this is better or worse than minsum. + LFS_ENTROPY, + #Brute-force-search PNG filters by compressing each filter for each scanLine. + #Experimental, very slow, and only rarely gives better compression than MINSUM. + LFS_BRUTE_FORCE, + #use predefined_filters buffer: you specify the filter type for each scanLine + LFS_PREDEFINED \ No newline at end of file From c490027c55911a92e5627251a5ae78a29afaf519 Mon Sep 17 00:00:00 2001 From: andri lim Date: Mon, 13 Apr 2020 21:00:17 +0700 Subject: [PATCH 04/11] add exception error if predefinedFilters not enough --- nimPNG.nim | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/nimPNG.nim b/nimPNG.nim index 8a00502..b226d30 100644 --- a/nimPNG.nim +++ b/nimPNG.nim @@ -3150,6 +3150,10 @@ proc encoderCore(png: PNG) = (modeOut.paletteSize == 0 or modeOut.paletteSize > 256): raise PNGError("invalid palette size, it is only allowed to be 1-256") + if state.filterStrategy == LFS_PREDEFINED: + if state.predefinedFilters.len < png.width: + raise PNGError("predefinedFilters contains not enough filterType compared to image height") + let inputSize = getRawSize(png.width, png.height, modeIn) if png.pixels.len < inputSize: raise PNGError("not enough input to encode") From 7304e5e28c9cd5816aa4bb233322dbf8ffa8bc60 Mon Sep 17 00:00:00 2001 From: andri lim Date: Mon, 13 Apr 2020 21:28:55 +0700 Subject: [PATCH 05/11] gc:arc refactor add LFS_MINSUM, LFS_BRUTEFORCE and add tests --- nimPNG/filters.nim | 116 +++++++++++++++++++++++------------------ nimPNG/nimz.nim | 5 +- tests/test_filters.nim | 19 ++++++- 3 files changed, 87 insertions(+), 53 deletions(-) diff --git a/nimPNG/filters.nim b/nimPNG/filters.nim index e60d6aa..81107ed 100644 --- a/nimPNG/filters.nim +++ b/nimPNG/filters.nim @@ -1,4 +1,4 @@ -import math +import math, ../nimPNG/nimz type PNGFilter* = enum @@ -101,55 +101,64 @@ proc filterZero*(output: var openArray[byte], input: openArray[byte], w, h, bpp: input.toOpenArray(prevIndex, input.len-1), byteWidth, lineBytes, FLT_NONE) prevIndex = inIndex - -#[ + + proc filterMinsum*(output: var openArray[byte], input: openArray[byte], w, h, bpp: int) = let lineBytes = (w * bpp + 7) div 8 let byteWidth = (bpp + 7) div 8 #adaptive filtering - var sum = [0, 0, 0, 0, 0] - var smallest = 0 - - #five filtering attempts, one for each filter type - var attempt: array[0..4, string] - var bestType = 0 - var prevLine: DataBuf + var + sum = [0, 0, 0, 0, 0] + smallest = 0 + # five filtering attempts, one for each filter type + attempt: array[0..4, seq[byte]] + bestType = 0 + prevIndex = 0 for i in 0..attempt.high: - attempt[i] = newString(lineBytes) + attempt[i] = newSeq[byte](lineBytes) - for y in 0..h-1: - #try the 5 filter types + for y in 0.. Date: Tue, 14 Apr 2020 09:57:50 +0700 Subject: [PATCH 06/11] gc:arc refactor adam7interlace --- nimPNG/filters.nim | 167 ++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 166 insertions(+), 1 deletion(-) diff --git a/nimPNG/filters.nim b/nimPNG/filters.nim index 81107ed..ade7c2f 100644 --- a/nimPNG/filters.nim +++ b/nimPNG/filters.nim @@ -8,6 +8,17 @@ type FLT_AVERAGE, FLT_PAETH + PNGPass* = object + w*, h*: array[0..6, int] + filterStart*, paddedStart*, start*: array[0..7, int] + +const + # 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 + ADAM7_DX = [ 8, 8, 4, 4, 2, 2, 1 ] # x delta values + ADAM7_DY = [ 8, 8, 8, 4, 4, 2, 2 ] # y delta values + # Paeth predicter, used by PNG filter type 4 proc paethPredictor(a, b, c: int): uint = let pa = abs(b - c) @@ -102,7 +113,6 @@ proc filterZero*(output: var openArray[byte], input: openArray[byte], w, h, bpp: byteWidth, lineBytes, FLT_NONE) prevIndex = inIndex - proc filterMinsum*(output: var openArray[byte], input: openArray[byte], w, h, bpp: int) = let lineBytes = (w * bpp + 7) div 8 let byteWidth = (bpp + 7) div 8 @@ -389,3 +399,158 @@ proc unfilter*(output: var openArray[byte], input: openArray[byte], w, h, bpp: i output.toOpenArray(prevIndex, output.len-1), # prevLine byteWidth, lineBytes, filterType) prevIndex = outIndex + +proc readBitFromReversedStream(bitptr: var int, bitstream: openArray[byte]): int = + result = ((int(bitstream[bitptr shr 3]) shr (7 - (bitptr and 0x7))) and 1) + inc bitptr + +proc readBitsFromReversedStream(bitptr: var int, bitstream: openArray[byte], nbits: int): int = + result = 0 + var i = nbits - 1 + while i > -1: + result += readBitFromReversedStream(bitptr, bitstream) shl i + dec i + +proc `&=`(a: var byte, b: byte) = + a = byte(int(a) and int(b)) + +proc `|=`(a: var byte, b: byte) = + a = byte(int(a) or int(b)) + +proc setBitOfReversedStream0(bitptr: var int, bitstream: var openArray[byte], bit: int) = + # the current bit in bitstream must be 0 for this to work + if bit != 0: + # earlier bit of huffman code is in a lesser significant bit of an earlier byte + bitstream[bitptr shr 3] |= byte(bit shl (7 - (bitptr and 0x7))) + inc bitptr + +proc setBitOfReversedStream(bitptr: var int, bitstream: var openArray[byte], bit: int) = + # the current bit in bitstream may be 0 or 1 for this to work + if bit == 0: bitstream[bitptr shr 3] &= byte(not (1 shl (7 - (bitptr and 0x7)))) + else: bitstream[bitptr shr 3] |= byte(1 shl (7 - (bitptr and 0x7))) + inc bitptr + +proc removePaddingBits(output: var openArray[byte], input: openArray[byte], 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. + # in and out are allowed to be the same buffer, in may also be higher but still overlapping; in must + # have >= ilinebits*h bits, out must have >= olinebits*h bits, olinebits must be <= ilinebits + # also used to move bits after earlier such operations happened, e.g. in a sequence of reduced images from Adam7 + # only useful if (ilinebits - olinebits) is a value in the range 1..7 + + let diff = ilinebits - olinebits + var + ibp = 0 + obp = 0 # input and output bit pointers + for y in 0..h-1: + for x in 0..olinebits-1: + var bit = readBitFromReversedStream(ibp, input) + setBitOfReversedStream(obp, output, bit) + inc(ibp, diff) + +# Outputs various dimensions and positions in the image related to the Adam7 reduced images. +# passw: output containing the width of the 7 passes +# passh: output containing the height of the 7 passes +# filter_passstart: output containing the index of the start and end of each +# reduced image with filter bytes +# padded_passstart output containing the index of the start and end of each +# reduced image when without filter bytes but with padded scanLines +# passstart: output containing the index of the start and end of each reduced +# image without padding between scanLines, but still padding between the images +# w, h: width and height of non-interlaced image +# bpp: bits per pixel +# "padded" is only relevant if bpp is less than 8 and a scanLine or image does not +# end at a full byte +proc adam7PassValues*(pass: var PNGPass, w, h, bpp: int) = + # the passstart values have 8 values: + # the 8th one indicates the byte after the end of the 7th (= last) pass + + # calculate width and height in pixels of each pass + for i in 0..6: + pass.w[i] = (w + ADAM7_DX[i] - ADAM7_IX[i] - 1) div ADAM7_DX[i] + pass.h[i] = (h + ADAM7_DY[i] - ADAM7_IY[i] - 1) div ADAM7_DY[i] + if pass.w[i] == 0: pass.h[i] = 0 + if pass.h[i] == 0: pass.w[i] = 0 + + pass.filterStart[0] = 0 + pass.paddedStart[0] = 0 + pass.start[0] = 0 + for i in 0..6: + # if passw[i] is 0, it's 0 bytes, not 1 (no filtertype-byte) + pass.filterStart[i + 1] = pass.filterStart[i] + if (pass.w[i] != 0) and (pass.h[i] != 0): + pass.filterStart[i + 1] += pass.h[i] * (1 + (pass.w[i] * bpp + 7) div 8) + # bits padded if needed to fill full byte at end of each scanLine + pass.paddedStart[i + 1] = pass.paddedStart[i] + pass.h[i] * ((pass.w[i] * bpp + 7) div 8) + # only padded at end of reduced image + pass.start[i + 1] = pass.start[i] + (pass.h[i] * pass.w[i] * bpp + 7) div 8 + +# input: Adam7 interlaced image, with no padding bits between scanLines, but between +# reduced images so that each reduced image starts at a byte. +# output: the same pixels, but re-ordered so that they're now a non-interlaced image with size w*h +# bpp: bits per pixel +# output has the following size in bits: w * h * bpp. +# input is possibly bigger due to padding bits between reduced images. +# 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*(output: var openArray[byte], input: openArray[byte], w, h, bpp: int) = + var pass: PNGPass + adam7PassValues(pass, w, h, bpp) + + if bpp >= 8: + for i in 0..6: + let byteWidth = bpp div 8 + for y in 0..= 8: + for i in 0..6: + let byteWidth = bpp div 8 + for y in 0.. Date: Tue, 14 Apr 2020 10:18:05 +0700 Subject: [PATCH 07/11] refactor filters functions into generics accepting char or uint8 or byte openarray --- nimPNG/filters.nim | 336 ++++++++++++++++++++--------------------- nimPNG/nimz.nim | 3 + tests/randutils.nim | 7 +- tests/test_filters.nim | 34 +++-- 4 files changed, 194 insertions(+), 186 deletions(-) diff --git a/nimPNG/filters.nim b/nimPNG/filters.nim index ade7c2f..ab8af8e 100644 --- a/nimPNG/filters.nim +++ b/nimPNG/filters.nim @@ -29,129 +29,129 @@ proc paethPredictor(a, b, c: int): uint = elif pb < pa: return b.uint result = a.uint -proc filterScanline*(output: var openArray[byte], input: openArray[byte], byteWidth, len: int, filterType: PNGFilter) = +proc filterScanline*[T](output: var openArray[T], input: openArray[T], TWidth, len: int, filterType: PNGFilter) = template currPix: untyped = input[i].uint - template prevPix: untyped = input[i - byteWidth].uint + template prevPix: untyped = input[i - TWidth].uint case filterType of FLT_NONE: for i in 0.. 0: - output[0] = byte(FLT_NONE) # filterType byte + output[0] = T(FLT_NONE) # filterType T filterScanline(output.toOpenArray(1, output.len-1), # skip filterType - input, byteWidth, lineBytes, FLT_NONE) + input, TWidth, lineTs, FLT_NONE) # next line start from 1 var prevIndex = 0 for y in 1.. 0: - output[0] = byte(predefinedFilters[0]) # filterType byte + output[0] = T(predefinedFilters[0]) # filterType T filterScanline(output.toOpenArray(1, output.len-1), # skip filterType - input, byteWidth, lineBytes, predefinedFilters[0]) + input, TWidth, lineTs, predefinedFilters[0]) # next line start from 1 var prevIndex = 0 for y in 1.. 0: unfilterScanLine(output, input.toOpenArray(1, input.len-1), # skip the filterType - byteWidth, lineBytes, + TWidth, lineTs, PNGFilter(input[0])) # next line start from 1 var prevIndex = 0 for y in 1.. -1: result += readBitFromReversedStream(bitptr, bitstream) shl i dec i -proc `&=`(a: var byte, b: byte) = - a = byte(int(a) and int(b)) +proc `&=`[T](a: var T, b: T) = + a = T(int(a) and int(b)) -proc `|=`(a: var byte, b: byte) = - a = byte(int(a) or int(b)) +proc `|=`[T](a: var T, b: T) = + a = T(int(a) or int(b)) -proc setBitOfReversedStream0(bitptr: var int, bitstream: var openArray[byte], bit: int) = +proc setBitOfReversedStream0[T](bitptr: var int, bitstream: var openArray[T], bit: int) = # the current bit in bitstream must be 0 for this to work if bit != 0: - # earlier bit of huffman code is in a lesser significant bit of an earlier byte - bitstream[bitptr shr 3] |= byte(bit shl (7 - (bitptr and 0x7))) + # earlier bit of huffman code is in a lesser significant bit of an earlier T + bitstream[bitptr shr 3] |= T(bit shl (7 - (bitptr and 0x7))) inc bitptr -proc setBitOfReversedStream(bitptr: var int, bitstream: var openArray[byte], bit: int) = +proc setBitOfReversedStream[T](bitptr: var int, bitstream: var openArray[T], bit: int) = # the current bit in bitstream may be 0 or 1 for this to work - if bit == 0: bitstream[bitptr shr 3] &= byte(not (1 shl (7 - (bitptr and 0x7)))) - else: bitstream[bitptr shr 3] |= byte(1 shl (7 - (bitptr and 0x7))) + if bit == 0: bitstream[bitptr shr 3] &= T(not (1 shl (7 - (bitptr and 0x7)))) + else: bitstream[bitptr shr 3] |= T(1 shl (7 - (bitptr and 0x7))) inc bitptr -proc removePaddingBits(output: var openArray[byte], input: openArray[byte], olinebits, ilinebits, h: int) = +proc removePaddingBits[T](output: var openArray[T], input: openArray[T], 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. @@ -453,18 +453,18 @@ proc removePaddingBits(output: var openArray[byte], input: openArray[byte], olin # passw: output containing the width of the 7 passes # passh: output containing the height of the 7 passes # filter_passstart: output containing the index of the start and end of each -# reduced image with filter bytes +# reduced image with filter Ts # padded_passstart output containing the index of the start and end of each -# reduced image when without filter bytes but with padded scanLines +# reduced image when without filter Ts but with padded scanLines # passstart: output containing the index of the start and end of each reduced # image without padding between scanLines, but still padding between the images # w, h: width and height of non-interlaced image # bpp: bits per pixel # "padded" is only relevant if bpp is less than 8 and a scanLine or image does not -# end at a full byte -proc adam7PassValues*(pass: var PNGPass, w, h, bpp: int) = +# end at a full T +proc adam7PassValues(pass: var PNGPass, w, h, bpp: int) = # the passstart values have 8 values: - # the 8th one indicates the byte after the end of the 7th (= last) pass + # the 8th one indicates the T after the end of the 7th (= last) pass # calculate width and height in pixels of each pass for i in 0..6: @@ -477,17 +477,17 @@ proc adam7PassValues*(pass: var PNGPass, w, h, bpp: int) = pass.paddedStart[0] = 0 pass.start[0] = 0 for i in 0..6: - # if passw[i] is 0, it's 0 bytes, not 1 (no filtertype-byte) + # if passw[i] is 0, it's 0 Ts, not 1 (no filtertype-T) pass.filterStart[i + 1] = pass.filterStart[i] if (pass.w[i] != 0) and (pass.h[i] != 0): pass.filterStart[i + 1] += pass.h[i] * (1 + (pass.w[i] * bpp + 7) div 8) - # bits padded if needed to fill full byte at end of each scanLine + # bits padded if needed to fill full T at end of each scanLine pass.paddedStart[i + 1] = pass.paddedStart[i] + pass.h[i] * ((pass.w[i] * bpp + 7) div 8) # only padded at end of reduced image pass.start[i + 1] = pass.start[i] + (pass.h[i] * pass.w[i] * bpp + 7) div 8 # input: Adam7 interlaced image, with no padding bits between scanLines, but between -# reduced images so that each reduced image starts at a byte. +# reduced images so that each reduced image starts at a T. # output: the same pixels, but re-ordered so that they're now a non-interlaced image with size w*h # bpp: bits per pixel # output has the following size in bits: w * h * bpp. @@ -495,18 +495,18 @@ 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*(output: var openArray[byte], input: openArray[byte], w, h, bpp: int) = +proc adam7Deinterlace*[T](output: var openArray[T], input: openArray[T], w, h, bpp: int) = var pass: PNGPass adam7PassValues(pass, w, h, bpp) if bpp >= 8: for i in 0..6: - let byteWidth = bpp div 8 + let TWidth = bpp div 8 for y in 0..= 8: for i in 0..6: - let byteWidth = bpp div 8 + let TWidth = bpp div 8 for y in 0.. Date: Tue, 14 Apr 2020 11:49:37 +0700 Subject: [PATCH 08/11] gc:arc refactor nimPNG encoder/decoder --- nimPNG.nim | 1031 ++++++++++++------------------------------ nimPNG/filters.nim | 55 ++- tests/test_codec.nim | 35 +- 3 files changed, 348 insertions(+), 773 deletions(-) diff --git a/nimPNG.nim b/nimPNG.nim index b226d30..8420f1d 100644 --- a/nimPNG.nim +++ b/nimPNG.nim @@ -26,7 +26,7 @@ #------------------------------------- import streams, endians, tables, hashes, math -import nimPNG/[buffer, nimz] +import nimPNG/[buffer, nimz, filters] const NIM_PNG_VERSION = "0.2.4" @@ -41,13 +41,6 @@ type LCT_GREY_ALPHA = 4, # greyscale with alpha: 8,16 bit LCT_RGBA = 6 # RGB with alpha: 8,16 bit - PNGFilter* = enum - FLT_NONE, - FLT_SUB, - FLT_UP, - FLT_AVERAGE, - FLT_PAETH - PNGSettings = ref object of RootObj PNGDecoder* = ref object of PNGSettings @@ -185,10 +178,6 @@ type # during encoding frameDataPos points to png.apngPixels[pos] and png.apngChunks[pos] frameDataPos: int - PNGPass = object - w, h: array[0..6, int] - filterStart, paddedStart, start: array[0..7, int] - PNGColorMode* = ref object colorType*: PNGcolorType bitDepth*: int @@ -302,12 +291,6 @@ const 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 - ADAM7_DX = [ 8, 8, 4, 4, 2, 2, 1 ] # x delta values - ADAM7_DY = [ 8, 8, 8, 4, 4, 2, 2 ] # y delta values - proc PNGError(msg: string): ref Exception = new(result) result.msg = msg @@ -922,216 +905,7 @@ proc parsePNG(s: Stream, settings: PNGDecoder): PNG = if not idat.validateChunk(png): raise PNGError("bad IDAT") result = png -# Paeth predicter, used by PNG filter type 4 -proc paethPredictor(a, b, c: int): int = - let pa = abs(b - c) - let pb = abs(a - c) - let pc = abs(a + b - c - c) - - if(pc < pa) and (pc < pb): return c - elif pb < pa: return b - result = a - -proc readBitFromReversedStream(bitptr: var int, bitstream: DataBuf): int = - result = ((ord(bitstream[bitptr shr 3]) shr (7 - (bitptr and 0x7))) and 1) - inc bitptr - -proc readBitsFromReversedStream(bitptr: var int, bitstream: DataBuf, nbits: int): int = - result = 0 - var i = nbits - 1 - while i > -1: - result += readBitFromReversedStream(bitptr, bitstream) shl i - dec i - -proc `&=`(a: var char, b: char) = - a = chr(ord(a) and ord(b)) - -proc `|=`(a: var char, b: char) = - a = chr(ord(a) or ord(b)) - -proc setBitOfReversedStream0(bitptr: var int, bitstream: var DataBuf, bit: int) = - # the current bit in bitstream must be 0 for this to work - if bit != 0: - # earlier bit of huffman code is in a lesser significant bit of an earlier byte - bitstream[bitptr shr 3] |= cast[char](bit shl (7 - (bitptr and 0x7))) - inc bitptr - -proc setBitOfReversedStream(bitptr: var int, bitstream: var DataBuf, bit: int) = - #the current bit in bitstream may be 0 or 1 for this to work - if bit == 0: bitstream[bitptr shr 3] &= cast[char](not (1 shl (7 - (bitptr and 0x7)))) - else: bitstream[bitptr shr 3] |= cast[char](1 shl (7 - (bitptr and 0x7))) - inc bitptr - -# index: bitgroup index, bits: bitgroup size(1, 2 or 4), in: bitgroup value, out: octet array to add bits to -proc addColorBits(output: var DataBuf, index, bits, input: int) = - var m = 1 - if bits == 1: m = 7 - elif bits == 2: m = 3 - # p = the partial index in the byte, e.g. with 4 palettebits it is 0 for first half or 1 for second half - let p = index and m - - var val = input and ((1 shl bits) - 1) #filter out any other bits of the input value - val = val shl (bits * (m - p)) - let idx = index * bits div 8 - if p == 0: output[idx] = chr(val) - else: output[idx] = chr(ord(output[idx]) or val) - -proc unfilterScanLine(recon: var DataBuf, scanLine, precon: DataBuf, byteWidth, len: int, filterType: PNGFilter) = - # For PNG filter method 0 - # unfilter a PNG image scanLine by scanLine. when the pixels are smaller than 1 byte, - # the filter works byte per byte (byteWidth = 1) - # precon is the previous unfiltered scanLine, recon the result, scanLine the current one - # the incoming scanLines do NOT include the filtertype byte, that one is given in the parameter filterType instead - # recon and scanLine MAY be the same memory address! precon must be disjoint. - - case filterType - of FLT_NONE: - for i in 0..len-1: recon[i] = scanLine[i] - of FLT_SUB: - for i in 0..byteWidth-1: recon[i] = scanLine[i] - for i in byteWidth..len-1: recon[i] = chr((ord(scanLine[i]) + ord(recon[i - byteWidth])) mod 256) - of FLT_UP: - if not precon.isNil: - for i in 0..len-1: recon[i] = chr((ord(scanLine[i]) + ord(precon[i])) mod 256) - else: - for i in 0..len-1: recon[i] = scanLine[i] - of FLT_AVERAGE: - if not precon.isNil: - for i in 0..byteWidth-1: - recon[i] = chr((ord(scanLine[i]) + ord(precon[i]) div 2) mod 256) - for i in byteWidth..len-1: - recon[i] = chr((ord(scanLine[i]) + ((ord(recon[i - byteWidth]) + ord(precon[i])) div 2)) mod 256) - else: - for i in 0..byteWidth-1: recon[i] = scanLine[i] - for i in byteWidth..len-1: - recon[i] = chr((ord(scanLine[i]) + ord(recon[i - byteWidth]) div 2) mod 256) - of FLT_PAETH: - if not precon.isNil: - for i in 0..byteWidth-1: - recon[i] = chr((ord(scanLine[i]) + ord(precon[i])) mod 256) #paethPredictor(0, precon[i], 0) is always precon[i] - for i in byteWidth..len-1: - recon[i] = chr((ord(scanLine[i]) + paethPredictor(ord(recon[i - byteWidth]), ord(precon[i]), ord(precon[i - byteWidth]))) mod 256) - else: - for i in 0..byteWidth-1: recon[i] = scanLine[i] - for i in byteWidth..len-1: - # paethPredictor(recon[i - byteWidth], 0, 0) is always recon[i - byteWidth] - recon[i] = chr((ord(scanLine[i]) + ord(recon[i - byteWidth])) mod 256) - -proc unfilter(output: var DataBuf, input: DataBuf, 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 bytes allocated already, input must have the scanLines + 1 filtertype byte per scanLine - # w and h are image dimensions or dimensions of reduced image, bpp is bits per pixel - # input and output are allowed to be the same memory address (but aren't the same size since in has the extra filter bytes) - - var prevLine : DataBuf - - # byteWidth is used for filtering, is 1 when bpp < 8, number of bytes per pixel otherwise - let byteWidth = (bpp + 7) div 8 - let lineBytes = (w * bpp + 7) div 8 - - for y in 0..h-1: - let outIndex = lineBytes * y - let inIndex = (1 + lineBytes) * y # the extra filterbyte added to each row - let filterType = PNGFilter(input[inindex]) - let scanLine = input.subbuffer(inIndex + 1) - var outp = output.subbuffer(outIndex) - unfilterScanLine(outp, scanLine, prevLine, byteWidth, lineBytes, filterType) - prevLine = output.subbuffer(outIndex) - -proc removePaddingBits(output: var DataBuf, input: DataBuf, 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. - # in and out are allowed to be the same buffer, in may also be higher but still overlapping; in must - # have >= ilinebits*h bits, out must have >= olinebits*h bits, olinebits must be <= ilinebits - # also used to move bits after earlier such operations happened, e.g. in a sequence of reduced images from Adam7 - # only useful if (ilinebits - olinebits) is a value in the range 1..7 - - let diff = ilinebits - olinebits - var - ibp = 0 - obp = 0 # input and output bit pointers - for y in 0..h-1: - for x in 0..olinebits-1: - var bit = readBitFromReversedStream(ibp, input) - setBitOfReversedStream(obp, output, bit) - inc(ibp, diff) - -# Outputs various dimensions and positions in the image related to the Adam7 reduced images. -# passw: output containing the width of the 7 passes -# passh: output containing the height of the 7 passes -# filter_passstart: output containing the index of the start and end of each -# reduced image with filter bytes -# padded_passstart output containing the index of the start and end of each -# reduced image when without filter bytes but with padded scanLines -# passstart: output containing the index of the start and end of each reduced -# image without padding between scanLines, but still padding between the images -# w, h: width and height of non-interlaced image -# bpp: bits per pixel -# "padded" is only relevant if bpp is less than 8 and a scanLine or image does not -# end at a full byte -proc Adam7PassValues(pass: var PNGPass, w, h, bpp: int) = - #the passstart values have 8 values: - # the 8th one indicates the byte after the end of the 7th (= last) pass - - # calculate width and height in pixels of each pass - for i in 0..6: - pass.w[i] = (w + ADAM7_DX[i] - ADAM7_IX[i] - 1) div ADAM7_DX[i] - pass.h[i] = (h + ADAM7_DY[i] - ADAM7_IY[i] - 1) div ADAM7_DY[i] - if pass.w[i] == 0: pass.h[i] = 0 - if pass.h[i] == 0: pass.w[i] = 0 - - pass.filterStart[0] = 0 - pass.paddedStart[0] = 0 - pass.start[0] = 0 - for i in 0..6: - # if passw[i] is 0, it's 0 bytes, not 1 (no filtertype-byte) - pass.filterStart[i + 1] = pass.filterStart[i] - if (pass.w[i] != 0) and (pass.h[i] != 0): - pass.filterStart[i + 1] += pass.h[i] * (1 + (pass.w[i] * bpp + 7) div 8) - # bits padded if needed to fill full byte at end of each scanLine - pass.paddedStart[i + 1] = pass.paddedStart[i] + pass.h[i] * ((pass.w[i] * bpp + 7) div 8) - # only padded at end of reduced image - pass.start[i + 1] = pass.start[i] + (pass.h[i] * pass.w[i] * bpp + 7) div 8 - -# input: Adam7 interlaced image, with no padding bits between scanLines, but between -# reduced images so that each reduced image starts at a byte. -# output: the same pixels, but re-ordered so that they're now a non-interlaced image with size w*h -# bpp: bits per pixel -# output has the following size in bits: w * h * bpp. -# input is possibly bigger due to padding bits between reduced images. -# 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(output: var DataBuf, input: DataBuf, w, h, bpp: int) = - var pass: PNGPass - Adam7PassValues(pass, w, h, bpp) - - if bpp >= 8: - for i in 0..6: - var byteWidth = bpp div 8 - for y in 0..pass.h[i]-1: - for x in 0..pass.w[i]-1: - var inStart = pass.start[i] + (y * pass.w[i] + x) * byteWidth - var outStart = ((ADAM7_IY[i] + y * ADAM7_DY[i]) * w + ADAM7_IX[i] + x * ADAM7_DX[i]) * byteWidth - for b in 0..byteWidth-1: - output[outStart + b] = input[inStart + b] - else: # bpp < 8: Adam7 with pixels < 8 bit is a bit trickier: with bit pointers - for i in 0..6: - var ilinebits = bpp * pass.w[i] - var olinebits = bpp * w - for y in 0..pass.h[i]-1: - for x in 0..pass.w[i]-1: - var ibp = (8 * pass.start[i]) + (y * ilinebits + x * bpp) - var obp = (ADAM7_IY[i] + y * ADAM7_DY[i]) * olinebits + (ADAM7_IX[i] + x * ADAM7_DX[i]) * bpp - for b in 0..bpp-1: - var bit = readBitFromReversedStream(ibp, input) - # note that this function assumes the out buffer is completely 0, use setBitOfReversedStream otherwise - setBitOfReversedStream0(obp, output, bit) - -proc postProcessScanLines(png: PNG; header: PNGHeader, w, h: int; input, output: var DataBuf) = +proc postProcessScanLines[T](png: PNG; header: PNGHeader, w, h: int; input, output: var openArray[T]) = # This function converts the filtered-padded-interlaced data # into pure 2D image buffer with the PNG's colorType. # Steps: @@ -1147,16 +921,19 @@ proc postProcessScanLines(png: PNG; header: PNGHeader, w, h: int; input, output: if(bpp < 8) and (bitsPerLine != bitsPerPaddedLine): unfilter(input, input, w, h, bpp) removePaddingBits(output, input, bitsPerLine, bitsPerPaddedLine, h) - # we can immediatly filter into the out buffer, no other steps needed - else: unfilter(output, input, w, h, bpp) + else: + # we can immediatly filter into the out buffer, no other steps needed + unfilter(output, input, w, h, bpp) + else: # interlace_method is 1 (Adam7) var pass: PNGPass - Adam7PassValues(pass, w, h, bpp) + adam7PassValues(pass, w, h, bpp) for i in 0..6: - var outp = input.subbuffer(pass.paddedStart[i]) - var inp = input.subbuffer(pass.filterStart[i]) - unfilter(outp, inp, pass.w[i], pass.h[i], bpp) + unfilter(input.toOpenArray(pass.paddedStart[i], input.len-1), + input.toOpenArray(pass.filterStart[i], input.len-1), + pass.w[i], pass.h[i], bpp + ) # TODO: possible efficiency improvement: # if in this reduced image the bits fit nicely in 1 scanLine, @@ -1164,11 +941,13 @@ proc postProcessScanLines(png: PNG; header: PNGHeader, w, h: int; input, output: if bpp < 8: # remove padding bits in scanLines; after this there still may be padding # bits between the different reduced images: each reduced image still starts nicely at a byte - outp = input.subbuffer(pass.start[i]) - inp = input.subbuffer(pass.paddedStart[i]) - removePaddingBits(outp, inp, pass.w[i] * bpp, ((pass.w[i] * bpp + 7) div 8) * 8, pass.h[i]) + removePaddingBits( + input.toOpenArray(pass.start[i], input.len-1), + input.toOpenArray(pass.paddedStart[i], input.len-1), + pass.w[i] * bpp, ((pass.w[i] * bpp + 7) div 8) * 8, pass.h[i] + ) - Adam7Deinterlace(output, input, w, h, bpp) + adam7Deinterlace(output, input, w, h, bpp) proc postProcessScanLines(png: PNG) = var header = PNGHeader(png.getChunk(IHDR)) @@ -1176,22 +955,24 @@ proc postProcessScanLines(png: PNG) = let h = header.height var idat = PNGData(png.getChunk(IDAT)) png.pixels = newString(idatRawSize(header.width, header.height, header)) - var input = initBuffer(idat.idat) - var output = initBuffer(png.pixels) - zeroMem(output) - png.postProcessScanLines(header, w, h, input, output) + 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: string) = +proc postProcessScanLines(png: PNG, 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)) - var input = initBuffer(data) - var output = initBuffer(png.apngPixels[^1]) - zeroMem(output) - png.postProcessScanLines(header, w, h, input, output) + png.postProcessScanLines(header, w, h, + data.toOpenArray(0, data.len-1), # input + png.apngPixels[^1].toOpenArray(0, png.apngPixels[^1].len-1) + ) proc getColorMode(png: PNG): PNGColorMode = var header = PNGHeader(png.getChunk(IHDR)) @@ -1251,48 +1032,48 @@ proc getChunkNames*(png: PNG): string = if i < png.chunks.high: result.add ' ' inc i -proc RGBFromGrey8(output: var DataBuf, input: DataBuf, numPixels: int, mode: PNGColorMode) = - for i in 0..numPixels-1: +proc RGBFromGrey8[T](output: var openArray[T], input: openArray[T], numPixels: int, mode: PNGColorMode) = + for i in 0..= mode.paletteSize: @@ -1306,9 +1087,9 @@ proc RGBFromPalette8(output: var DataBuf, input: DataBuf, numPixels: int, mode: output[x+1] = mode.palette[index].g output[x+2] = mode.palette[index].b -proc RGBFromPalette124(output: var DataBuf, input: DataBuf, numPixels: int, mode: PNGColorMode) = +proc RGBFromPalette124[T](output: var openArray[T], input: openArray[T], numPixels: int, mode: PNGColorMode) = var obp = 0 - for i in 0..numPixels-1: + for i in 0..= mode.paletteSize: @@ -1322,40 +1103,40 @@ proc RGBFromPalette124(output: var DataBuf, input: DataBuf, numPixels: int, mode output[x+1] = mode.palette[index].g output[x+2] = mode.palette[index].b -proc RGBFromGreyAlpha8(output: var DataBuf, input: DataBuf, numPixels: int, mode: PNGColorMode) = - for i in 0..numPixels-1: +proc RGBFromGreyAlpha8[T](output: var openArray[T], input: openArray[T], numPixels: int, mode: PNGColorMode) = + for i in 0..= mode.paletteSize: @@ -1429,9 +1210,9 @@ proc RGBAFromPalette8(output: var DataBuf, input: DataBuf, numPixels: int, mode: output[x+2] = mode.palette[index].b output[x+3] = mode.palette[index].a -proc RGBAFromPalette124(output: var DataBuf, input: DataBuf, numPixels: int, mode: PNGColorMode) = +proc RGBAFromPalette124[T](output: var openArray[T], input: openArray[T], numPixels: int, mode: PNGColorMode) = var obp = 0 - for i in 0..numPixels-1: + for i in 0..= mode.paletteSize: @@ -1447,8 +1228,8 @@ proc RGBAFromPalette124(output: var DataBuf, input: DataBuf, numPixels: int, mod output[x+2] = mode.palette[index].b output[x+3] = mode.palette[index].a -proc RGBAFromGreyAlpha8(output: var DataBuf, input: DataBuf, numPixels: int, mode: PNGColorMode) = - for i in 0..numPixels-1: +proc RGBAFromGreyAlpha8[T](output: var openArray[T], input: openArray[T], numPixels: int, mode: PNGColorMode) = + for i in 0..= mode.paletteSize: # This is an error according to the PNG spec, @@ -1561,7 +1342,7 @@ proc RGBA8FromPalette8(p: var RGBA8, input: DataBuf, px: int, mode: PNGColorMode p.b = mode.palette[index].b p.a = mode.palette[index].a -proc RGBA8FromPalette124(p: var RGBA8, input: DataBuf, px: int, mode: PNGColorMode) = +proc RGBA8FromPalette124[T](p: var RGBA8, input: openArray[T], px: int, mode: PNGColorMode) = var obp = px * mode.bitDepth let index = readBitsFromReversedStream(obp, input, mode.bitDepth) if index >= mode.paletteSize: @@ -1578,7 +1359,7 @@ proc RGBA8FromPalette124(p: var RGBA8, input: DataBuf, px: int, mode: PNGColorMo p.b = mode.palette[index].b p.a = mode.palette[index].a -proc RGBA8FromGreyAlpha8(p: var RGBA8, input: DataBuf, px: int, mode: PNGColorMode) = +proc RGBA8FromGreyAlpha8[T](p: var RGBA8, input: openArray[T], px: int, mode: PNGColorMode) = let i = px * 2 let val = input[i] p.r = val @@ -1586,7 +1367,7 @@ proc RGBA8FromGreyAlpha8(p: var RGBA8, input: DataBuf, px: int, mode: PNGColorMo p.b = val p.a = input[i+1] -proc RGBA8FromGreyAlpha16(p: var RGBA8, input: DataBuf, px: int, mode: PNGColorMode) = +proc RGBA8FromGreyAlpha16[T](p: var RGBA8, input: openArray[T], px: int, mode: PNGColorMode) = let i = px * 4 let val = input[i] p.r = val @@ -1594,21 +1375,21 @@ proc RGBA8FromGreyAlpha16(p: var RGBA8, input: DataBuf, px: int, mode: PNGColorM p.b = val p.a = input[i+2] -proc RGBA8FromRGBA8(p: var RGBA8, input: DataBuf, px: int, mode: PNGColorMode) = +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] -proc RGBA8FromRGBA16(p: var RGBA8, input: DataBuf, px: int, mode: PNGColorMode) = +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] -proc RGBA16FromGrey(p: var RGBA16, input: DataBuf, px: int, mode: PNGColorMode) = +proc RGBA16FromGrey[T](p: var RGBA16, input: openArray[T], px: int, mode: PNGColorMode) = let i = px * 2 let val = 256'u16 * uint16(input[i]) + uint16(input[i + 1]) p.r = val @@ -1617,7 +1398,7 @@ proc RGBA16FromGrey(p: var RGBA16, input: DataBuf, px: int, mode: PNGColorMode) if mode.keyDefined and (val.int == mode.keyR): p.a = 0 else: p.a = 65535 -proc RGBA16FromRGB(p: var RGBA16, input: DataBuf, px: int, mode: PNGColorMode) = +proc RGBA16FromRGB[T](p: var RGBA16, input: openArray[T], px: int, mode: PNGColorMode) = let i = px * 6 p.r = 256'u16 * uint16(input[i]) + uint16(input[i+1]) p.g = 256'u16 * uint16(input[i+2]) + uint16(input[i+3]) @@ -1626,7 +1407,7 @@ proc RGBA16FromRGB(p: var RGBA16, input: DataBuf, px: int, mode: PNGColorMode) = (int(p.g) == mode.keyG) and (int(p.b) == mode.keyB): p.a = 0 else: p.a = 65535 -proc RGBA16FromGreyAlpha(p: var RGBA16, input: DataBuf, px: int, mode: PNGColorMode) = +proc RGBA16FromGreyAlpha[T](p: var RGBA16, input: openArray[T], px: int, mode: PNGColorMode) = let i = px * 4 let val = 256'u16 * uint16(input[i]) + uint16(input[i + 1]) p.r = val @@ -1634,33 +1415,33 @@ proc RGBA16FromGreyAlpha(p: var RGBA16, input: DataBuf, px: int, mode: PNGColorM p.b = val p.a = 256'u16 * uint16(input[i + 2]) + uint16(input[i + 3]) -proc RGBA16FromRGBA(p: var RGBA16, input: DataBuf, px: int, mode: PNGColorMode) = +proc RGBA16FromRGBA[T](p: var RGBA16, input: openArray[T], px: int, mode: PNGColorMode) = let i = px * 8 p.r = 256'u16 * uint16(input[i]) + uint16(input[i+1]) p.g = 256'u16 * uint16(input[i+2]) + uint16(input[i+3]) p.b = 256'u16 * uint16(input[i+4]) + uint16(input[i+5]) p.a = 256'u16 * uint16(input[i+6]) + uint16(input[i+7]) -proc RGBA8ToGrey8(p: RGBA8, output: var DataBuf, px: int, mode: PNGColorMode, ct: ColorTree8) = +proc RGBA8ToGrey8[T](p: RGBA8, output: var openArray[T], px: int, mode: PNGColorMode, ct: ColorTree8) = output[px] = p.r -proc RGBA8ToGrey16(p: RGBA8, output: var DataBuf, px: int, mode: PNGColorMode, ct: ColorTree8) = +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 -proc RGBA8ToGrey124(p: RGBA8, output: var DataBuf, px: int, mode: PNGColorMode, ct: ColorTree8) = +proc RGBA8ToGrey124[T](p: RGBA8, output: var openArray[T], px: int, mode: PNGColorMode, ct: ColorTree8) = # take the most significant bits of grey - let grey = (ord(p.r) shr (8 - mode.bitDepth)) and ((1 shl mode.bitDepth) - 1) + let grey = (int(p.r) shr (8 - mode.bitDepth)) and ((1 shl mode.bitDepth) - 1) addColorBits(output, px, mode.bitDepth, grey) -proc RGBA8ToRGB8(p: RGBA8, output: var DataBuf, px: int, mode: PNGColorMode, ct: ColorTree8) = +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 -proc RGBA8ToRGB16(p: RGBA8, output: var DataBuf, px: int, mode: PNGColorMode, ct: ColorTree8) = +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 @@ -1669,32 +1450,32 @@ proc RGBA8ToRGB16(p: RGBA8, output: var DataBuf, px: int, mode: PNGColorMode, ct output[i+3] = p.g output[i+5] = p.b -proc RGBA8ToPalette8(p: RGBA8, output: var DataBuf, px: int, mode: PNGColorMode, ct: ColorTree8) = - output[px] = chr(ct[p]) +proc RGBA8ToPalette8[T](p: RGBA8, output: var openArray[T], px: int, mode: PNGColorMode, ct: ColorTree8) = + output[px] = T(ct[p]) -proc RGBA8ToPalette124(p: RGBA8, output: var DataBuf, px: int, mode: PNGColorMode, ct: ColorTree8) = +proc RGBA8ToPalette124[T](p: RGBA8, output: var openArray[T], px: int, mode: PNGColorMode, ct: ColorTree8) = addColorBits(output, px, mode.bitDepth, ct[p]) -proc RGBA8ToGreyAlpha8(p: RGBA8, output: var DataBuf, px: int, mode: PNGColorMode, ct: ColorTree8) = +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 -proc RGBA8ToGreyAlpha16(p: RGBA8, output: var DataBuf, px: int, mode: PNGColorMode, ct: ColorTree8) = +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 -proc RGBA8ToRGBA8(p: RGBA8, output: var DataBuf, px: int, mode: PNGColorMode, ct: ColorTree8) = +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 -proc RGBA8ToRGBA16(p: RGBA8, output: var DataBuf, px: int, mode: PNGColorMode, ct: ColorTree8) = +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 @@ -1705,129 +1486,129 @@ proc RGBA8ToRGBA16(p: RGBA8, output: var DataBuf, px: int, mode: PNGColorMode, c output[i+5] = p.b output[i+7] = p.a -proc RGBA16ToGrey(p: RGBA16, output: var DataBuf, px: int, mode: PNGColorMode) = +proc RGBA16ToGrey[T](p: RGBA16, output: var openArray[T], px: int, mode: PNGColorMode) = let i = px * 2 - output[i] = char((p.r shr 8) and 255) - output[i+1] = char(p.r and 255) + output[i] = T((p.r shr 8) and 255) + output[i+1] = T(p.r and 255) -proc RGBA16ToRGB(p: RGBA16, output: var DataBuf, px: int, mode: PNGColorMode) = +proc RGBA16ToRGB[T](p: RGBA16, output: var openArray[T], px: int, mode: PNGColorMode) = let i = px * 6 - output[i] = char((p.r shr 8) and 255) - output[i+1] = char(p.r and 255) - output[i+2] = char((p.g shr 8) and 255) - output[i+3] = char(p.g and 255) - output[i+4] = char((p.b shr 8) and 255) - output[i+5] = char(p.b and 255) - -proc RGBA16ToGreyAlpha(p: RGBA16, output: var DataBuf, px: int, mode: PNGColorMode) = + output[i] = T((p.r shr 8) and 255) + output[i+1] = T(p.r and 255) + output[i+2] = T((p.g shr 8) and 255) + output[i+3] = T(p.g and 255) + output[i+4] = T((p.b shr 8) and 255) + output[i+5] = T(p.b and 255) + +proc RGBA16ToGreyAlpha[T](p: RGBA16, output: var openArray[T], px: int, mode: PNGColorMode) = let i = px * 4 - output[i] = char((p.r shr 8) and 255) - output[i+1] = char(p.r and 255) - output[i+2] = char((p.a shr 8) and 255) - output[i+3] = char(p.a and 255) + output[i] = T((p.r shr 8) and 255) + output[i+1] = T(p.r and 255) + output[i+2] = T((p.a shr 8) and 255) + output[i+3] = T(p.a and 255) -proc RGBA16ToRGBA(p: RGBA16, output: var DataBuf, px: int, mode: PNGColorMode) = +proc RGBA16ToRGBA[T](p: RGBA16, output: var openArray[T], px: int, mode: PNGColorMode) = let i = px * 8 - output[i] = char((p.r shr 8) and 255) - output[i+1] = char(p.r and 255) - output[i+2] = char((p.g shr 8) and 255) - output[i+3] = char(p.g and 255) - output[i+4] = char((p.b shr 8) and 255) - output[i+5] = char(p.b and 255) - output[i+6] = char((p.a shr 8) and 255) - output[i+7] = char(p.a and 255) - -proc getColorRGBA16(mode: PNGColorMode): convertRGBA16 = - if mode.colorType == LCT_GREY: return RGBA16FromGrey - elif mode.colorType == LCT_RGB: return RGBA16FromRGB - elif mode.colorType == LCT_GREY_ALPHA: return RGBA16FromGreyAlpha - elif mode.colorType == LCT_RGBA: return RGBA16FromRGBA + output[i] = T((p.r shr 8) and 255) + output[i+1] = T(p.r and 255) + output[i+2] = T((p.g shr 8) and 255) + output[i+3] = T(p.g and 255) + output[i+4] = T((p.b shr 8) and 255) + output[i+5] = T(p.b and 255) + output[i+6] = T((p.a shr 8) and 255) + output[i+7] = T(p.a and 255) + +proc getColorRGBA16[T](mode: PNGColorMode): convertRGBA16[T] = + if mode.colorType == LCT_GREY: return RGBA16FromGrey[T] + elif mode.colorType == LCT_RGB: return RGBA16FromRGB[T] + elif mode.colorType == LCT_GREY_ALPHA: return RGBA16FromGreyAlpha[T] + elif mode.colorType == LCT_RGBA: return RGBA16FromRGBA[T] else: raise PNGError("unsupported converter16") -proc getPixelRGBA16(mode: PNGColorMode): pixelRGBA16 = - if mode.colorType == LCT_GREY: return RGBA16ToGrey - elif mode.colorType == LCT_RGB: return RGBA16ToRGB - elif mode.colorType == LCT_GREY_ALPHA: return RGBA16ToGreyAlpha - elif mode.colorType == LCT_RGBA: return RGBA16ToRGBA +proc getPixelRGBA16[T](mode: PNGColorMode): pixelRGBA16[T] = + if mode.colorType == LCT_GREY: return RGBA16ToGrey[T] + elif mode.colorType == LCT_RGB: return RGBA16ToRGB[T] + elif mode.colorType == LCT_GREY_ALPHA: return RGBA16ToGreyAlpha[T] + elif mode.colorType == LCT_RGBA: return RGBA16ToRGBA[T] else: raise PNGError("unsupported pixel16 converter") -proc getColorRGBA8(mode: PNGColorMode): convertRGBA8 = +proc getColorRGBA8[T](mode: PNGColorMode): convertRGBA8[T] = if mode.colorType == LCT_GREY: - if mode.bitDepth == 8: return RGBA8FromGrey8 - elif mode.bitDepth == 16: return RGBA8FromGrey16 - else: return RGBA8FromGrey124 + if mode.bitDepth == 8: return RGBA8FromGrey8[T] + elif mode.bitDepth == 16: return RGBA8FromGrey16[T] + else: return RGBA8FromGrey124[T] elif mode.colorType == LCT_RGB: - if mode.bitDepth == 8: return RGBA8FromRGB8 - else: return RGBA8FromRGB16 + if mode.bitDepth == 8: return RGBA8FromRGB8[T] + else: return RGBA8FromRGB16[T] elif mode.colorType == LCT_PALETTE: - if mode.bitDepth == 8: return RGBA8FromPalette8 - else: return RGBA8FromPalette124 + if mode.bitDepth == 8: return RGBA8FromPalette8[T] + else: return RGBA8FromPalette124[T] elif mode.colorType == LCT_GREY_ALPHA: - if mode.bitDepth == 8: return RGBA8FromGreyAlpha8 - else: return RGBA8FromGreyAlpha16 + if mode.bitDepth == 8: return RGBA8FromGreyAlpha8[T] + else: return RGBA8FromGreyAlpha16[T] elif mode.colorType == LCT_RGBA: - if mode.bitDepth == 8: return RGBA8FromRGBA8 - else: return RGBA8FromRGBA16 + if mode.bitDepth == 8: return RGBA8FromRGBA8[T] + else: return RGBA8FromRGBA16[T] else: raise PNGError("unsupported converter8") -proc getPixelRGBA8(mode: PNGColorMode): pixelRGBA8 = +proc getPixelRGBA8[T](mode: PNGColorMode): pixelRGBA8[T] = if mode.colorType == LCT_GREY: - if mode.bitDepth == 8: return RGBA8ToGrey8 - elif mode.bitDepth == 16: return RGBA8ToGrey16 - else: return RGBA8ToGrey124 + if mode.bitDepth == 8: return RGBA8ToGrey8[T] + elif mode.bitDepth == 16: return RGBA8ToGrey16[T] + else: return RGBA8ToGrey124[T] elif mode.colorType == LCT_RGB: - if mode.bitDepth == 8: return RGBA8ToRGB8 - else: return RGBA8ToRGB16 + if mode.bitDepth == 8: return RGBA8ToRGB8[T] + else: return RGBA8ToRGB16[T] elif mode.colorType == LCT_PALETTE: - if mode.bitDepth == 8: return RGBA8ToPalette8 - else: return RGBA8ToPalette124 + if mode.bitDepth == 8: return RGBA8ToPalette8[T] + else: return RGBA8ToPalette124[T] elif mode.colorType == LCT_GREY_ALPHA: - if mode.bitDepth == 8: return RGBA8ToGreyAlpha8 - else: return RGBA8ToGreyAlpha16 + if mode.bitDepth == 8: return RGBA8ToGreyAlpha8[T] + else: return RGBA8ToGreyAlpha16[T] elif mode.colorType == LCT_RGBA: - if mode.bitDepth == 8: return RGBA8ToRGBA8 - else: return RGBA8ToRGBA16 + if mode.bitDepth == 8: return RGBA8ToRGBA8[T] + else: return RGBA8ToRGBA16[T] else: raise PNGError("unsupported pixel8 converter") -proc getConverterRGB(mode: PNGColorMode): convertRGBA = +proc getConverterRGB[T](mode: PNGColorMode): convertRGBA[T] = if mode.colorType == LCT_GREY: - if mode.bitDepth == 8: return RGBFromGrey8 - elif mode.bitDepth == 16: return RGBFromGrey16 - else: return RGBFromGrey124 + if mode.bitDepth == 8: return RGBFromGrey8[T] + elif mode.bitDepth == 16: return RGBFromGrey16[T] + else: return RGBFromGrey124[T] elif mode.colorType == LCT_RGB: - if mode.bitDepth == 8: return RGBFromRGB8 - else: return RGBFromRGB16 + if mode.bitDepth == 8: return RGBFromRGB8[T] + else: return RGBFromRGB16[T] elif mode.colorType == LCT_PALETTE: - if mode.bitDepth == 8: return RGBFromPalette8 - else: return RGBFromPalette124 + if mode.bitDepth == 8: return RGBFromPalette8[T] + else: return RGBFromPalette124[T] elif mode.colorType == LCT_GREY_ALPHA: - if mode.bitDepth == 8: return RGBFromGreyAlpha8 - else: return RGBFromGreyAlpha16 + if mode.bitDepth == 8: return RGBFromGreyAlpha8[T] + else: return RGBFromGreyAlpha16[T] elif mode.colorType == LCT_RGBA: - if mode.bitDepth == 8: return RGBFromRGBA8 - else: return RGBFromRGBA16 + if mode.bitDepth == 8: return RGBFromRGBA8[T] + else: return RGBFromRGBA16[T] else: raise PNGError("unsupported RGB converter") -proc getConverterRGBA(mode: PNGColorMode): convertRGBA = +proc getConverterRGBA[T](mode: PNGColorMode): convertRGBA[T] = if mode.colorType == LCT_GREY: - if mode.bitDepth == 8: return RGBAFromGrey8 - elif mode.bitDepth == 16: return RGBAFromGrey16 - else: return RGBAFromGrey124 + if mode.bitDepth == 8: return RGBAFromGrey8[T] + elif mode.bitDepth == 16: return RGBAFromGrey16[T] + else: return RGBAFromGrey124[T] elif mode.colorType == LCT_RGB: - if mode.bitDepth == 8: return RGBAFromRGB8 - else: return RGBAFromRGB16 + if mode.bitDepth == 8: return RGBAFromRGB8[T] + else: return RGBAFromRGB16[T] elif mode.colorType == LCT_PALETTE: - if mode.bitDepth == 8: return RGBAFromPalette8 - else: return RGBAFromPalette124 + if mode.bitDepth == 8: return RGBAFromPalette8[T] + else: return RGBAFromPalette124[T] elif mode.colorType == LCT_GREY_ALPHA: - if mode.bitDepth == 8: return RGBAFromGreyAlpha8 - else: return RGBAFromGreyAlpha16 + if mode.bitDepth == 8: return RGBAFromGreyAlpha8[T] + else: return RGBAFromGreyAlpha16[T] elif mode.colorType == LCT_RGBA: - if mode.bitDepth == 8: return RGBAFromRGBA8 - else: return RGBAFromRGBA16 + if mode.bitDepth == 8: return RGBAFromRGBA8[T] + else: return RGBAFromRGBA16[T] else: raise PNGError("unsupported RGBA converter") -proc convert*(output: var DataBuf, input: DataBuf, modeOut, modeIn: PNGColorMode, numPixels: int) = +proc convert*[T](output: var openArray[T], input: openArray[T], modeOut, modeIn: PNGColorMode, numPixels: int) = var tree: ColorTree8 if modeOut.colorType == LCT_PALETTE: var @@ -1849,68 +1630,68 @@ proc convert*(output: var DataBuf, input: DataBuf, modeOut, modeIn: PNGColorMode tree[palette[i]] = i if(modeIn.bitDepth == 16) and (modeOut.bitDepth == 16): - let cvt = getColorRGBA16(modeIn) - let pxl = getPixelRGBA16(modeOut) - for px in 0..numPixels-1: + let cvt = getColorRGBA16[T](modeIn) + let pxl = getPixelRGBA16[T](modeOut) + for px in 0..= modeOut.palettesize) and (modeIn.bitdepth == modeOut.bitdepth): - #If input should have same palette colors, keep original to preserve its order and prevent conversion + # If input should have same palette colors, keep original to preserve its order and prevent conversion modeIn.copyTo(modeOut) - else: #8-bit or 16-bit per channel + else: # 8-bit or 16-bit per channel modeOut.bitDepth = prof.bits if prof.alpha: if prof.colored: modeOut.colorType = LCT_RGBA @@ -2653,245 +2429,32 @@ proc autoChooseColor(png: PNG, modeOut, modeIn: PNGColorMode) = else: modeOut.colorType = LCT_GREY if prof.key and not prof.alpha: - #profile always uses 16-bit, mask converts it + # profile always uses 16-bit, mask converts it let mask = (1 shl modeOut.bitDepth) - 1 modeOut.keyR = prof.keyR and mask modeOut.keyG = prof.keyG and mask modeOut.keyB = prof.keyB and mask modeOut.keyDefined = true -proc addPaddingBits(output: var DataBuf, input: DataBuf, olinebits, ilinebits, h: int) = - #The opposite of the removePaddingBits function - #olinebits must be >= ilinebits - - let diff = olinebits - ilinebits - var - obp = 0 - ibp = 0 #bit pointers - - for y in 0..h-1: - for x in 0..ilinebits-1: - let bit = readBitFromReversedStream(ibp, input) - setBitOfReversedStream(obp, output, bit) - for x in 0..diff-1: setBitOfReversedStream(obp, output, 0) - -proc filterScanLine(output: var DataBuf, scanLine, prevLine: DataBuf, len, byteWidth: int, filterType: PNGFilter) = - case filterType - of FLT_NONE: - for i in 0..len-1: output[i] = scanLine[i] - of FLT_SUB: - for i in 0..byteWidth-1: output[i] = scanLine[i] - for i in byteWidth..len-1: - output[i] = chr((scanLine[i].uint - scanLine[i - byteWidth].uint) and 0xFF) - of FLT_UP: - if not prevLine.isNil: - for i in 0..len-1: - output[i] = chr((scanLine[i].uint - prevLine[i].uint) and 0xFF) - else: - for i in 0..len-1: output[i] = scanLine[i] - of FLT_AVERAGE: - if not prevLine.isNil: - for i in 0..byteWidth-1: - output[i] = chr((scanLine[i].uint - (prevLine[i].uint div 2)) and 0xFF) - for i in byteWidth..len-1: - output[i] = chr((scanLine[i].uint - ((scanLine[i - byteWidth].uint + prevLine[i].uint) div 2)) and 0xFF) - else: - for i in 0..byteWidth-1: output[i] = scanLine[i] - for i in byteWidth..len-1: - output[i] = chr((scanLine[i].uint - (scanLine[i - byteWidth].uint div 2)) and 0xFF) - of FLT_PAETH: - if not prevLine.isNil: - #paethPredictor(0, prevLine[i], 0) is always prevLine[i] - for i in 0..byteWidth-1: - output[i] = chr((scanLine[i].uint - prevLine[i].uint) and 0xFF) - for i in byteWidth..len-1: - output[i] = chr((scanLine[i].uint - paethPredictor(ord(scanLine[i - byteWidth]), ord(prevLine[i]), ord(prevLine[i - byteWidth])).uint) and 0xFF) - else: - for i in 0..byteWidth-1: output[i] = scanLine[i] - #paethPredictor(scanLine[i - byteWidth], 0, 0) is always scanLine[i - byteWidth] - for i in byteWidth..len-1: - output[i] = chr((scanLine[i].uint - scanLine[i - byteWidth].uint) and 0xFF) - -proc filterZero(output: var DataBuf, input: DataBuf, w, h, bpp: int) = - #the width of a scanline in bytes, not including the filter type - let lineBytes = (w * bpp + 7) div 8 - #byteWidth is used for filtering, is 1 when bpp < 8, number of bytes per pixel otherwise - let byteWidth = (bpp + 7) div 8 - var prevLine: DataBuf - - for y in 0..h-1: - let outindex = (1 + lineBytes) * y #the extra filterbyte added to each row - let inindex = lineBytes * y - output[outindex] = chr(int(FLT_NONE)) #filter type byte - var outp = output.subbuffer(outindex + 1) - let scanLine = input.subbuffer(inindex) - filterScanLine(outp, scanLine, prevLine, lineBytes, byteWidth, FLT_NONE) - prevLine = input.subbuffer(inindex) - -proc filterMinsum(output: var DataBuf, input: DataBuf, w, h, bpp: int) = - let lineBytes = (w * bpp + 7) div 8 - let byteWidth = (bpp + 7) div 8 - - #adaptive filtering - var sum = [0, 0, 0, 0, 0] - var smallest = 0 - - #five filtering attempts, one for each filter type - var attempt: array[0..4, string] - var bestType = 0 - var prevLine: DataBuf - - for i in 0..attempt.high: - attempt[i] = newString(lineBytes) - - for y in 0..h-1: - #try the 5 filter types - for fType in 0..4: - var outp = initBuffer(attempt[fType]) - filterScanLine(outp, input.subbuffer(y * lineBytes), prevLine, lineBytes, byteWidth, PNGFilter(fType)) - #calculate the sum of the result - sum[fType] = 0 - if fType == 0: - for x in 0..lineBytes-1: - sum[fType] += ord(attempt[fType][x]) - else: - for x in 0..lineBytes-1: - #For differences, each byte should be treated as signed, values above 127 are negative - #(converted to signed char). Filtertype 0 isn't a difference though, so use unsigned there. - #This means filtertype 0 is almost never chosen, but that is justified. - let s = ord(attempt[fType][x]) - if s < 128: sum[fType] += s - else: sum[fType] += (255 - s) - - #check if this is smallest sum (or if type == 0 it's the first case so always store the values)*/ - if(fType == 0) or (sum[fType] < smallest): - bestType = fType - smallest = sum[fType] - - prevLine = input.subbuffer(y * lineBytes) - #now fill the out values - #the first byte of a scanline will be the filter type - output[y * (lineBytes + 1)] = chr(bestType) - for x in 0..lineBytes-1: - output[y * (lineBytes + 1) + 1 + x] = attempt[bestType][x] - -proc filterEntropy(output: var DataBuf, input: DataBuf, w, h, bpp: int) = - let lineBytes = (w * bpp + 7) div 8 - let byteWidth = (bpp + 7) div 8 - var prevLine: DataBuf - - var sum: array[0..4, float] - var smallest = 0.0 - var bestType = 0 - var attempt: array[0..4, string] - var count: array[0..255, int] - - for i in 0..attempt.high: - attempt[i] = newString(lineBytes) - - for y in 0..h-1: - #try the 5 filter types - for fType in 0..4: - var outp = initBuffer(attempt[fType]) - filterScanLine(outp, input.subbuffer(y * lineBytes), prevLine, lineBytes, byteWidth, PNGFilter(fType)) - for x in 0..255: count[x] = 0 - for x in 0..lineBytes-1: - inc count[ord(attempt[fType][x])] - inc count[fType] #the filter type itself is part of the scanline - sum[fType] = 0 - for x in 0..255: - let p = float(count[x]) / float(lineBytes + 1) - if count[x] != 0: sum[fType] += log2(1 / p) * p - - #check if this is smallest sum (or if type == 0 it's the first case so always store the values) - if (fType == 0) or (sum[fType] < smallest): - bestType = fType - smallest = sum[fType] - - prevLine = input.subbuffer(y * lineBytes) - #now fill the out values*/ - #the first byte of a scanline will be the filter type - output[y * (lineBytes + 1)] = chr(bestType) - for x in 0..lineBytes-1: - output[y * (lineBytes + 1) + 1 + x] = attempt[bestType][x] - -proc filterPredefined(output: var DataBuf, input: DataBuf, w, h, bpp: int, state: PNGEncoder) = - let lineBytes = (w * bpp + 7) div 8 - let byteWidth = (bpp + 7) div 8 - var prevLine: DataBuf - - for y in 0..h-1: - let outindex = (1 + lineBytes) * y #the extra filterbyte added to each row - let inindex = lineBytes * y - let fType = ord(state.predefinedFilters[y]) - output[outindex] = chr(fType) #filter type byte - var outp = output.subbuffer(outindex + 1) - filterScanLine(outp, input.subbuffer(inindex), prevLine, lineBytes, byteWidth, PNGFilter(fType)) - prevLine = input.subbuffer(inindex) - -proc filterBruteForce(output: var DataBuf, input: DataBuf, w, h, bpp: int) = - let lineBytes = (w * bpp + 7) div 8 - let byteWidth = (bpp + 7) div 8 - var prevLine: DataBuf - - #brute force filter chooser. - #deflate the scanline after every filter attempt to see which one deflates best. - #This is very slow and gives only slightly smaller, sometimes even larger, result*/ - - var size: array[0..4, int] - var attempt: array[0..4, string] #five filtering attempts, one for each filter type - var smallest = 0 - var bestType = 0 - - #use fixed tree on the attempts so that the tree is not adapted to the filtertype on purpose, - #to simulate the true case where the tree is the same for the whole image. Sometimes it gives - #better result with dynamic tree anyway. Using the fixed tree sometimes gives worse, but in rare - #cases better compression. It does make this a bit less slow, so it's worth doing this. - - for i in 0..attempt.high: - attempt[i] = newString(lineBytes) - - for y in 0..h-1: - #try the 5 filter types - for fType in 0..4: - #let testSize = attempt[fType].len - var outp = initBuffer(attempt[fType]) - filterScanline(outp, input.subbuffer(y * lineBytes), prevLine, lineBytes, byteWidth, PNGFilter(fType)) - size[fType] = 0 - - var nz = nzDeflateInit(attempt[fType]) - let data = zlib_compress(nz) - size[fType] = data.len - - #check if this is smallest size (or if type == 0 it's the first case so always store the values) - if(fType == 0) or (size[fType] < smallest): - bestType = fType - smallest = size[fType] - - prevLine = input.subbuffer(y * lineBytes) - output[y * (lineBytes + 1)] = chr(bestType) #the first byte of a scanline will be the filter type - for x in 0..lineBytes-1: - output[y * (lineBytes + 1) + 1 + x] = attempt[bestType][x] - -proc filter(output: var DataBuf, input: DataBuf, w, h: int, modeOut: PNGColorMode, state: PNGEncoder) = - #For PNG filter method 0 - #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 +proc filter[T](output: var openArray[T], input: openArray[T], w, h: int, modeOut: PNGColorMode, state: PNGEncoder) = + # For PNG filter method 0 + # 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 let bpp = getBPP(modeOut) var strategy = state.filterStrategy - #There is a heuristic called the minimum sum of absolute differences heuristic, suggested by the PNG standard: + # There is a heuristic called the minimum sum of absolute differences heuristic, suggested by the PNG standard: # * If the image type is Palette, or the bit depth is smaller than 8, then do not filter the image (i.e. # use fixed filtering, with the filter None). # * (The other case) If the image type is Grayscale or RGB (with or without Alpha), and the bit depth is # not smaller than 8, then use adaptive filtering heuristic as follows: independently for each row, apply # all five filters and select the filter that produces the smallest sum of absolute values per row. - #This heuristic is used if filter strategy is LFS_MINSUM and filter_palette_zero is true. + # This heuristic is used if filter strategy is LFS_MINSUM and filter_palette_zero is true. - #If filter_palette_zero is true and filter_strategy is not LFS_MINSUM, the above heuristic is followed, - #but for "the other case", whatever strategy filter_strategy is set to instead of the minimum sum - #heuristic is used. + # If filter_palette_zero is true and filter_strategy is not LFS_MINSUM, the above heuristic is followed, + # but for "the other case", whatever strategy filter_strategy is set to instead of the minimum sum + # heuristic is used. if state.filterPaletteZero and (modeOut.colorType == LCT_PALETTE or modeOut.bitDepth < 8): strategy = LFS_ZERO @@ -2903,83 +2466,59 @@ proc filter(output: var DataBuf, input: DataBuf, w, h: int, modeOut: PNGColorMod of LFS_MINSUM: filterMinsum(output, input, w, h, bpp) of LFS_ENTROPY: filterEntropy(output, input, w, h, bpp) of LFS_BRUTE_FORCE: filterBruteForce(output, input, w, h, bpp) - of LFS_PREDEFINED: filterPredefined(output, input, w, h, bpp, state) - -#input: non-interlaced image with size w*h -#output: the same pixels, but re-ordered according to PNG's Adam7 interlacing, with -# no padding bits between scanlines, but between reduced images so that each -# reduced image starts at a byte. -#bpp: bits per pixel -#there are no padding bits, not between scanlines, not between reduced images -#in has the following size in bits: w * h * bpp. -#output is possibly bigger due to padding bits between reduced images -#NOTE: comments about padding bits are only relevant if bpp < 8 -proc Adam7Interlace(output: var DataBuf, input: DataBuf, w, h, bpp: int) = - var pass: PNGPass - Adam7PassValues(pass, w, h, bpp) - - if bpp >= 8: - for i in 0..6: - let byteWidth = bpp div 8 - for y in 0..pass.h[i]-1: - for x in 0..pass.w[i]-1: - let inStart = ((ADAM7_IY[i] + y * ADAM7_DY[i]) * w + ADAM7_IX[i] + x * ADAM7_DX[i]) * byteWidth - let outStart = pass.start[i] + (y * pass.w[i] + x) * byteWidth - for b in 0..byteWidth-1: - output[outStart + b] = 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] - let olinebits = bpp * w - var obp, ibp: int #bit pointers (for out and in buffer) - for y in 0..pass.h[i]-1: - for x in 0..pass.w[i]-1: - ibp = (ADAM7_IY[i] + y * ADAM7_DY[i]) * olinebits + (ADAM7_IX[i] + x * ADAM7_DX[i]) * bpp - obp = (8 * pass.start[i]) + (y * ilinebits + x * bpp) - for b in 0..bpp-1: - let bit = readBitFromReversedStream(ibp, input) - setBitOfReversedStream(obp, output, bit) - -proc preProcessScanLines(png: PNG, input: DataBuf, 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 + 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) = + # 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 let bpp = getBPP(modeOut) + template output: untyped = png.apngPixels[frameNo] if state.interlaceMethod == IM_NONE: - #image size plus an extra byte per scanLine + possible padding bits + # image size plus an extra byte per scanLine + possible padding bits let scanLen = (w * bpp + 7) div 8 let outSize = h + (h * scanLen) png.apngPixels[frameNo] = newString(outSize) - var output = initBuffer(png.apngPixels[frameNo]) - #non multiple of 8 bits per scanLine, padding bits needed per scanLine + + # non multiple of 8 bits per scanLine, padding bits needed per scanLine if(bpp < 8) and ((w * bpp) != (scanLen * 8)): - var padded = initBuffer(newString(h * scanLen)) - addPaddingBits(padded, input, scanLen * 8, w * bpp, h) + var padded = newString(h * scanLen) + addPaddingBits(padded.toOpenArray(0, padded.len-1), input, scanLen * 8, w * bpp, h) - filter(output, padded, w, h, modeOut, state) + filter(output.toOpenArray(0, output.len-1), + padded.toOpenArray(0, padded.len-1), + w, h, modeOut, state) else: - #we can immediatly filter into the out buffer, no other steps needed - filter(output, input, w, h, modeOut, state) + # we can immediatly filter into the out buffer, no other steps needed + filter(output.toOpenArray(0, output.len-1), + input, w, h, modeOut, state) else: #interlaceMethod is 1 (Adam7) var pass: PNGPass - Adam7PassValues(pass, w, h, bpp) + adam7PassValues(pass, w, h, bpp) let outSize = pass.filterStart[7] + png.apngPixels[frameNo] = newString(outSize) - var adam7 = initBuffer(newString(pass.start[7])) - var output = initBuffer(png.apngPixels[frameNo]) + var adam7 = newString(pass.start[7]) - Adam7Interlace(adam7, input, w, h, bpp) + adam7Interlace(adam7.toOpenArray(0, adam7.len-1), + input, w, h, bpp) for i in 0..6: if bpp < 8: - var padding = initBuffer(newString(pass.paddedStart[i + 1] - pass.paddedStart[i])) - addPaddingBits(padding, adam7.subbuffer(pass.start[i]), ((pass.w[i] * bpp + 7) div 8) * 8, pass.w[i] * bpp, pass.h[i]) - var outp = output.subbuffer(pass.filterStart[i]) - filter(outp, padding, pass.w[i], pass.h[i], modeOut, state) + var padding = newString(pass.paddedStart[i + 1] - pass.paddedStart[i]) + + addPaddingBits(padding.toOpenArray(0, padding.len-1), + adam7.toOpenArray(pass.start[i], adam7.len-1), + ((pass.w[i] * bpp + 7) div 8) * 8, pass.w[i] * bpp, pass.h[i]) + + filter(output.toOpenArray(pass.filterStart[i], output.len-1), + padding.toOpenArray(0, padding.len-1), + pass.w[i], pass.h[i], modeOut, state) else: - var outp = output.subbuffer(pass.filterStart[i]) - filter(outp, adam7.subbuffer(pass.paddedStart[i]), pass.w[i], pass.h[i], modeOut, state) + filter(output.toOpenArray(pass.filterStart[i], output.len-1), + adam7.toOpenArray(pass.paddedStart[i], adam7.len-1), + pass.w[i], pass.h[i], modeOut, state) #palette must have 4 * palettesize bytes allocated, and given in format RGBARGBARGBARGBA... #returns 0 if the palette is opaque, @@ -3121,18 +2660,22 @@ proc addChunkfdAT(png: PNG, sequenceNumber, frameDataPos: int) = png.chunks.add chunk proc frameConvert(png: PNG, modeIn, modeOut: PNGColorMode, w, h, frameNo: int, state: PNGEncoder) = + template input: untyped = png.apngPixels[frameNo] + if modeIn != modeOut: let size = (w * h * getBPP(modeOut) + 7) div 8 let numPixels = w * h var converted = newString(size) - var output = initBuffer(converted) # although in preProcessScanLines png.pixels is reinitialized, it is ok # because initBuffer(png.pixels) share the ownership - convert(output, initBuffer(png.apngPixels[frameNo]), modeOut, modeIn, numPixels) - preProcessScanLines(png, initBuffer(converted), frameNo, w, h, modeOut, state) + convert(converted.toOpenArray(0, converted.len-1), + input.toOpenArray(0, input.len-1), + modeOut, modeIn, numPixels) + + preProcessScanLines(png, converted.toOpenArray(0, converted.len-1), frameNo, w, h, modeOut, state) else: - preProcessScanLines(png, initBuffer(png.apngPixels[frameNo]), frameNo, w, h, modeOut, state) + preProcessScanLines(png, input.toOpenArray(0, input.len-1), frameNo, w, h, modeOut, state) proc encoderCore(png: PNG) = let state = PNGEncoder(png.settings) diff --git a/nimPNG/filters.nim b/nimPNG/filters.nim index ab8af8e..35591cb 100644 --- a/nimPNG/filters.nim +++ b/nimPNG/filters.nim @@ -14,10 +14,10 @@ type const # 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 - ADAM7_DX = [ 8, 8, 4, 4, 2, 2, 1 ] # x delta values - ADAM7_DY = [ 8, 8, 8, 4, 4, 2, 2 ] # y delta 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_DX* = [ 8, 8, 4, 4, 2, 2, 1 ] # x delta values + ADAM7_DY* = [ 8, 8, 8, 4, 4, 2, 2 ] # y delta values # Paeth predicter, used by PNG filter type 4 proc paethPredictor(a, b, c: int): uint = @@ -400,11 +400,11 @@ proc unfilter*[T](output: var openArray[T], input: openArray[T], w, h, bpp: int) TWidth, lineTs, filterType) prevIndex = outIndex -proc readBitFromReversedStream[T](bitptr: var int, bitstream: openArray[T]): int = +proc readBitFromReversedStream*[T](bitptr: var int, bitstream: openArray[T]): int = result = ((int(bitstream[bitptr shr 3]) shr (7 - (bitptr and 0x7))) and 1) inc bitptr -proc readBitsFromReversedStream[T](bitptr: var int, bitstream: openArray[T], nbits: int): int = +proc readBitsFromReversedStream*[T](bitptr: var int, bitstream: openArray[T], nbits: int): int = result = 0 var i = nbits - 1 while i > -1: @@ -417,20 +417,20 @@ proc `&=`[T](a: var T, b: T) = proc `|=`[T](a: var T, b: T) = a = T(int(a) or int(b)) -proc setBitOfReversedStream0[T](bitptr: var int, bitstream: var openArray[T], bit: int) = +proc setBitOfReversedStream0*[T](bitptr: var int, bitstream: var openArray[T], bit: int) = # the current bit in bitstream must be 0 for this to work if bit != 0: # earlier bit of huffman code is in a lesser significant bit of an earlier T - bitstream[bitptr shr 3] |= T(bit shl (7 - (bitptr and 0x7))) + bitstream[bitptr shr 3] |= cast[T](bit shl (7 - (bitptr and 0x7))) inc bitptr -proc setBitOfReversedStream[T](bitptr: var int, bitstream: var openArray[T], bit: int) = +proc setBitOfReversedStream*[T](bitptr: var int, bitstream: var openArray[T], bit: int) = # the current bit in bitstream may be 0 or 1 for this to work - if bit == 0: bitstream[bitptr shr 3] &= T(not (1 shl (7 - (bitptr and 0x7)))) - else: bitstream[bitptr shr 3] |= T(1 shl (7 - (bitptr and 0x7))) + if bit == 0: bitstream[bitptr shr 3] &= cast[T](not (1 shl (7 - (bitptr and 0x7)))) + 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*[T](output: var openArray[T], input: openArray[T], 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. @@ -462,7 +462,7 @@ proc removePaddingBits[T](output: var openArray[T], input: openArray[T], olinebi # bpp: bits per pixel # "padded" is only relevant if bpp is less than 8 and a scanLine or image does not # end at a full T -proc adam7PassValues(pass: var PNGPass, w, h, bpp: int) = +proc adam7PassValues*(pass: var PNGPass, w, h, bpp: int) = # the passstart values have 8 values: # the 8th one indicates the T after the end of the 7th (= last) pass @@ -554,3 +554,32 @@ proc adam7Interlace*[T](output: var openArray[T], input: openArray[T], w, h, bpp for b in 0..= ilinebits + + let diff = olinebits - ilinebits + var + obp = 0 + ibp = 0 #bit pointers + + for y in 0..h-1: + for x in 0..ilinebits-1: + let bit = readBitFromReversedStream(ibp, input) + setBitOfReversedStream(obp, output, bit) + for x in 0..diff-1: setBitOfReversedStream(obp, output, 0) diff --git a/tests/test_codec.nim b/tests/test_codec.nim index f8a204e..6632eb6 100644 --- a/tests/test_codec.nim +++ b/tests/test_codec.nim @@ -1,5 +1,5 @@ import ../nimPNG, streams, math, strutils, tables, base64, os -import ../nimPNG/buffer +import ../nimPNG/[buffer, filters] type Image = ref object @@ -485,12 +485,12 @@ proc testPredefinedFilters() = var state = makePNGEncoder() state.filterStrategy = LFS_PREDEFINED state.filterPaletteZero = false - + # everything to filter type 'FLT_AVERAGE' state.predefinedFilters = newSeq[PNGFilter](h) for i in 0.. Date: Tue, 14 Apr 2020 13:58:50 +0700 Subject: [PATCH 09/11] gc:arc fix preprocessScanline --- nimPNG.nim | 13 +++-- nimPNG.nimble | 16 +----- nimPNG/filters.nim | 132 +++++++++++++++++++++---------------------- tests/all_tests.nim | 6 ++ tests/test_codec.nim | 6 ++ tests/test_nimz.nim | 31 +++++----- 6 files changed, 106 insertions(+), 98 deletions(-) create mode 100644 tests/all_tests.nim diff --git a/nimPNG.nim b/nimPNG.nim index 8420f1d..ab23a64 100644 --- a/nimPNG.nim +++ b/nimPNG.nim @@ -28,8 +28,10 @@ import streams, endians, tables, hashes, math import nimPNG/[buffer, nimz, filters] +import strutils + const - NIM_PNG_VERSION = "0.2.4" + NIM_PNG_VERSION = "0.2.6" type PNGChunkType = distinct int32 @@ -2473,13 +2475,12 @@ proc preProcessScanLines[T](png: PNG, input: openArray[T], frameNo, w, h: int, m # 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 let bpp = getBPP(modeOut) - template output: untyped = png.apngPixels[frameNo] if state.interlaceMethod == IM_NONE: # image size plus an extra byte per scanLine + possible padding bits let scanLen = (w * bpp + 7) div 8 let outSize = h + (h * scanLen) - png.apngPixels[frameNo] = newString(outSize) + var output = newString(outSize) # non multiple of 8 bits per scanLine, padding bits needed per scanLine if(bpp < 8) and ((w * bpp) != (scanLen * 8)): @@ -2494,12 +2495,14 @@ proc preProcessScanLines[T](png: PNG, input: openArray[T], frameNo, w, h: int, m filter(output.toOpenArray(0, output.len-1), input, w, h, modeOut, state) + shallowCopy(png.apngPixels[frameNo], output) + else: #interlaceMethod is 1 (Adam7) var pass: PNGPass adam7PassValues(pass, w, h, bpp) let outSize = pass.filterStart[7] - png.apngPixels[frameNo] = newString(outSize) + var output = newString(outSize) var adam7 = newString(pass.start[7]) adam7Interlace(adam7.toOpenArray(0, adam7.len-1), @@ -2520,6 +2523,8 @@ proc preProcessScanLines[T](png: PNG, input: openArray[T], frameNo, w, h: int, m adam7.toOpenArray(pass.paddedStart[i], adam7.len-1), pass.w[i], pass.h[i], modeOut, state) + shallowCopy(png.apngPixels[frameNo], output) + #palette must have 4 * palettesize bytes allocated, and given in format RGBARGBARGBARGBA... #returns 0 if the palette is opaque, #returns 1 if the palette has a single color with alpha 0 ==> color key diff --git a/nimPNG.nimble b/nimPNG.nimble index 36ff277..574f6f7 100644 --- a/nimPNG.nimble +++ b/nimPNG.nimble @@ -9,17 +9,5 @@ skipDirs = @["tests", "docs"] requires "nim >= 0.19.0" task tests, "Run tests": - exec "nim c -r tests/test_apng.nim" - exec "nim c -r tests/test_codec.nim" - exec "nim c -r tests/test_suite.nim" - exec "nim c -r tests/test_nimz.nim" - exec "nim c -r tests/test_filters.nim" - - exec "nim c -r -d:release tests/test_apng.nim" - exec "nim c -r -d:release tests/test_codec.nim" - exec "nim c -r -d:release tests/test_suite.nim" - exec "nim c -r -d:release tests/test_nimz.nim" - exec "nim c -r -d:release tests/test_filters.nim" - - exec "nim c -r --gc:arc -d:release tests/test_nimz.nim" - exec "nim c -r --gc:arc -d:release tests/test_filters.nim" + exec "nim c -r -d:release tests/all_tests" + exec "nim c -r --gc:arc -d:release tests/all_tests" diff --git a/nimPNG/filters.nim b/nimPNG/filters.nim index 35591cb..9c97c73 100644 --- a/nimPNG/filters.nim +++ b/nimPNG/filters.nim @@ -29,77 +29,77 @@ proc paethPredictor(a, b, c: int): uint = elif pb < pa: return b.uint result = a.uint -proc filterScanline*[T](output: var openArray[T], input: openArray[T], TWidth, len: int, filterType: PNGFilter) = +proc filterScanline*[T](output: var openArray[T], input: openArray[T], byteWidth, len: int, filterType: PNGFilter) = template currPix: untyped = input[i].uint - template prevPix: untyped = input[i - TWidth].uint + template prevPix: untyped = input[i - byteWidth].uint case filterType of FLT_NONE: for i in 0.. 0: output[0] = T(FLT_NONE) # filterType T filterScanline(output.toOpenArray(1, output.len-1), # skip filterType - input, TWidth, lineTs, FLT_NONE) + input, byteWidth, lineTs, FLT_NONE) # next line start from 1 var prevIndex = 0 @@ -110,12 +110,12 @@ proc filterZero*[T](output: var openArray[T], input: openArray[T], w, h, bpp: in filterScanline(output.toOpenArray(outIndex + 1, output.len-1), # skip filterType input.toOpenArray(inIndex, input.len-1), input.toOpenArray(prevIndex, input.len-1), - TWidth, lineTs, FLT_NONE) + byteWidth, lineTs, FLT_NONE) prevIndex = inIndex proc filterMinsum*[T](output: var openArray[T], input: openArray[T], w, h, bpp: int) = let lineTs = (w * bpp + 7) div 8 - let TWidth = (bpp + 7) div 8 + let byteWidth = (bpp + 7) div 8 #adaptive filtering var @@ -137,12 +137,12 @@ proc filterMinsum*[T](output: var openArray[T], input: openArray[T], w, h, bpp: if y == 0: filterScanline(attempt[fType], input.toOpenArray(inIndex, input.len-1), - TWidth, lineTs, PNGFilter(fType)) + byteWidth, lineTs, PNGFilter(fType)) else: filterScanline(attempt[fType], input.toOpenArray(inIndex, input.len-1), input.toOpenArray(prevIndex, input.len-1), - TWidth, lineTs, PNGFilter(fType)) + byteWidth, lineTs, PNGFilter(fType)) # calculate the sum of the result sum[fType] = 0 @@ -172,7 +172,7 @@ proc filterMinsum*[T](output: var openArray[T], input: openArray[T], w, h, bpp: proc filterEntropy*[T](output: var openArray[T], input: openArray[T], w, h, bpp: int) = let lineTs = (w * bpp + 7) div 8 - let TWidth = (bpp + 7) div 8 + let byteWidth = (bpp + 7) div 8 var sum: array[0..4, float] @@ -192,12 +192,12 @@ proc filterEntropy*[T](output: var openArray[T], input: openArray[T], w, h, bpp: if y == 0: filterScanline(attempt[fType], input.toOpenArray(inIndex, input.len-1), - TWidth, lineTs, PNGFilter(fType)) + byteWidth, lineTs, PNGFilter(fType)) else: filterScanline(attempt[fType], input.toOpenArray(inIndex, input.len-1), input.toOpenArray(prevIndex, input.len-1), - TWidth, lineTs, PNGFilter(fType)) + byteWidth, lineTs, PNGFilter(fType)) for x in 0..255: count[x] = 0 for x in 0..lineTs-1: @@ -225,13 +225,13 @@ proc filterPredefined*[T](output: var openArray[T], input: openArray[T], w, h, bpp: int, predefinedFilters: openArray[PNGFilter]) = let lineTs = (w * bpp + 7) div 8 - let TWidth = (bpp + 7) div 8 + let byteWidth = (bpp + 7) div 8 # line 0 if h > 0: output[0] = T(predefinedFilters[0]) # filterType T filterScanline(output.toOpenArray(1, output.len-1), # skip filterType - input, TWidth, lineTs, predefinedFilters[0]) + input, byteWidth, lineTs, predefinedFilters[0]) # next line start from 1 var prevIndex = 0 @@ -243,12 +243,12 @@ proc filterPredefined*[T](output: var openArray[T], input: openArray[T], filterScanline(output.toOpenArray(outIndex + 1, output.len-1), # skip filterType input.toOpenArray(inIndex, input.len-1), input.toOpenArray(prevIndex, input.len-1), - TWidth, lineTs, PNGFilter(fType)) + byteWidth, lineTs, PNGFilter(fType)) prevIndex = inIndex proc filterBruteForce*[T](output: var openArray[T], input: openArray[T], w, h, bpp: int) = let lineTs = (w * bpp + 7) div 8 - let TWidth = (bpp + 7) div 8 + let byteWidth = (bpp + 7) div 8 # brute force filter chooser. # deflate the input after every filter attempt to see which one deflates best. @@ -277,12 +277,12 @@ proc filterBruteForce*[T](output: var openArray[T], input: openArray[T], w, h, b if y == 0: filterScanline(attempt[fType], input.toOpenArray(inIndex, input.len-1), - TWidth, lineTs, PNGFilter(fType)) + byteWidth, lineTs, PNGFilter(fType)) else: filterScanline(attempt[fType], input.toOpenArray(inIndex, input.len-1), input.toOpenArray(prevIndex, input.len-1), - TWidth, lineTs, PNGFilter(fType)) + byteWidth, lineTs, PNGFilter(fType)) size[fType] = 0 var nz = nzCompressInit(attempt[fType]) @@ -299,75 +299,75 @@ 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], TWidth, len: int, filterType: PNGFilter) = - # When the pixels are smaller than 1 T, the filter works T per T (TWidth = 1) +proc unfilterScanline*[T](output: var openArray[T], input: openArray[T], 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. template currPix: untyped = input[i].uint - template prevPix: untyped = output[i - TWidth].uint + template prevPix: untyped = output[i - byteWidth].uint case filterType of FLT_NONE: for i in 0.. 0: unfilterScanLine(output, input.toOpenArray(1, input.len-1), # skip the filterType - TWidth, lineTs, + byteWidth, lineTs, PNGFilter(input[0])) # next line start from 1 @@ -397,7 +397,7 @@ proc unfilter*[T](output: var openArray[T], input: openArray[T], w, h, bpp: int) unfilterScanLine(output.toOpenArray(outIndex, output.len-1), input.toOpenArray(inIndex + 1, input.len-1), # skip the filterType output.toOpenArray(prevIndex, output.len-1), # prevLine - TWidth, lineTs, filterType) + byteWidth, lineTs, filterType) prevIndex = outIndex proc readBitFromReversedStream*[T](bitptr: var int, bitstream: openArray[T]): int = @@ -501,12 +501,12 @@ proc adam7Deinterlace*[T](output: var openArray[T], input: openArray[T], w, h, b if bpp >= 8: for i in 0..6: - let TWidth = bpp div 8 + let byteWidth = bpp div 8 for y in 0..= 8: for i in 0..6: - let TWidth = bpp div 8 + let byteWidth = bpp div 8 for y in 0.. 512: #assertTrue(s.data.len < image.data.len, "compressed size") + #debugEcho "PNG LEN: ", s.getPosition() + #debugEcho "PNG DATA: ", s.data.toHex + s.setPosition 0 var decoded = s.decodePNG(image.colorType, image.bitDepth) + #debugEcho "DECODED LEN: ", decoded.data.len + #debugEcho "DECODED DATA: ", decoded.data.toHex + assertEquals(image.width, decoded.width) assertEquals(image.height, decoded.height) diff --git a/tests/test_nimz.nim b/tests/test_nimz.nim index 1b3c712..aca80d0 100644 --- a/tests/test_nimz.nim +++ b/tests/test_nimz.nim @@ -12,17 +12,20 @@ template check_roundtrip(source) = if uncomp != input: check false -suite "nimz": - check_roundtrip("alice29.txt") - check_roundtrip("house.jpg") - check_roundtrip("html") - check_roundtrip("urls.10K") - check_roundtrip("fireworks.jpeg") - check_roundtrip("paper-100k.pdf") - check_roundtrip("html_x_4") - check_roundtrip("asyoulik.txt") - check_roundtrip("lcet10.txt") - check_roundtrip("plrabn12.txt") - check_roundtrip("geo.protodata") - check_roundtrip("kppkn.gtb") - check_roundtrip("Mark.Twain-Tom.Sawyer.txt") +proc main() = + suite "nimz": + check_roundtrip("alice29.txt") + check_roundtrip("house.jpg") + check_roundtrip("html") + check_roundtrip("urls.10K") + check_roundtrip("fireworks.jpeg") + check_roundtrip("paper-100k.pdf") + check_roundtrip("html_x_4") + check_roundtrip("asyoulik.txt") + check_roundtrip("lcet10.txt") + check_roundtrip("plrabn12.txt") + check_roundtrip("geo.protodata") + check_roundtrip("kppkn.gtb") + check_roundtrip("Mark.Twain-Tom.Sawyer.txt") + +main() From c00186c64a0bd2bb97e5de271512abbabf720d56 Mon Sep 17 00:00:00 2001 From: andri lim Date: Tue, 14 Apr 2020 14:09:37 +0700 Subject: [PATCH 10/11] show nim version in nimPNG.nimble --- nimPNG.nimble | 1 + 1 file changed, 1 insertion(+) diff --git a/nimPNG.nimble b/nimPNG.nimble index 574f6f7..3beeb0e 100644 --- a/nimPNG.nimble +++ b/nimPNG.nimble @@ -9,5 +9,6 @@ skipDirs = @["tests", "docs"] requires "nim >= 0.19.0" task tests, "Run tests": + exec "nim -v" exec "nim c -r -d:release tests/all_tests" exec "nim c -r --gc:arc -d:release tests/all_tests" From e2277ddf8bde6d0d215ecbd44d48ae484e42d9ee Mon Sep 17 00:00:00 2001 From: andri lim Date: Tue, 14 Apr 2020 15:13:02 +0700 Subject: [PATCH 11/11] fix 32 bit untyped template issue --- nimPNG/filters.nim | 68 +++++++++++++++++++++++----------------------- 1 file changed, 34 insertions(+), 34 deletions(-) diff --git a/nimPNG/filters.nim b/nimPNG/filters.nim index 9c97c73..cf3afc8 100644 --- a/nimPNG/filters.nim +++ b/nimPNG/filters.nim @@ -30,8 +30,8 @@ proc paethPredictor(a, b, c: int): uint = result = a.uint proc filterScanline*[T](output: var openArray[T], input: openArray[T], byteWidth, len: int, filterType: PNGFilter) = - template currPix: untyped = input[i].uint - template prevPix: untyped = input[i - byteWidth].uint + template currPix(i): untyped = input[i].uint + template prevPix(i): untyped = input[i - byteWidth].uint case filterType of FLT_NONE: @@ -41,7 +41,7 @@ proc filterScanline*[T](output: var openArray[T], input: openArray[T], byteWidth for i in 0..