nimPNG/testCodec.nim

1026 lines
36 KiB
Nim
Raw Normal View History

2015-09-02 20:15:44 +07:00
import nimPNG, streams, math, unsigned, strutils, tables
type
Image = ref object
data: string
width, height: int
colorType: PNGcolorType
bitDepth: int
proc fromBase64(v: int): int =
if(v >= ord('A')) and (v <= ord('Z')): return (v - ord('A'))
if(v >= ord('a')) and (v <= ord('z')): return (v - ord('a') + 26)
if(v >= ord('0')) and (v <= ord('9')): return (v - ord('0') + 52)
if v == ord('+'): return 62
if v == ord('/'): return 63
result = 0 #v == '='
proc fromBase64(input: string): string =
var i = 0
result = ""
while (i + 3) < input.len:
let v = 262144 * fromBase64(input[i].int) +
4096 * fromBase64(input[i + 1].int) +
64 * fromBase64(input[i + 2].int) + fromBase64(input[i + 3].int)
result.add chr((v shr 16) and 0xff)
if input[i + 3] != '=': result.add chr((v shr 8) and 0xff)
if input[i + 2] != '=': result.add chr((v shr 0) and 0xff)
inc(i, 4)
proc assertEquals[T, U](expected: T, actual: U, message = "") =
if expected != actual.T:
echo "Error: Not equal! Expected ", expected, " got ", actual.T, ". ",
"Message: ", message
quit()
proc assertTrue(value: bool, message = "") =
if not value:
echo "Error: expected true. Message: ", message
quit()
proc getNumColorChannels(colorType: PNGcolorType): int =
case colorType
of LCT_GREY: result = 1
of LCT_RGB: result = 3
of LCT_PALETTE: result = 1
of LCT_GREY_ALPHA: result = 2
of LCT_RGBA: result = 4
else: result = 0
proc generateTestImage(width, height: int, colorType = LCT_RGBA, bitDepth = 8): Image =
new(result)
result.width = width
result.height = height
result.colorType = colorType
result.bitDepth = bitDepth
let bits = bitDepth * getNumColorChannels(colorType)
let size = (width * height * bits + 7) div 8
result.data = newString(size)
var value = 128
for i in 0.. <size:
result.data[i] = chr(value mod 255)
value.inc
proc assertPixels(image: Image, decoded: string, message: string) =
for i in 0..image.data.high:
var byte_expected = ord(image.data[i])
var byte_actual = ord(decoded[i])
#last byte is special due to possible random padding bits which need not to be equal
if i == image.data.high:
let numbits = getNumColorChannels(image.colorType) * image.bitDepth * image.width * image.height
let padding = 8 - (numbits - 8 * (numbits div 8))
if padding != 8:
#set all padding bits of both to 0
for j in 0.. <padding:
byte_expected = (byte_expected and (not (1 shl j))) mod 256
byte_actual = (byte_actual and (not (1 shl j))) mod 256
assertEquals(byte_expected, byte_actual, message & " " & $i)
proc doCodecTest(image: Image, state: PNGEncoder) =
var png = PNGEncode(image.data, image.colorType, image.bitDepth, image.width, image.height, state)
var s = newStringStream()
png.writeChunks s
#if the image is large enough, compressing it should result in smaller size
#if image.data.len > 512:
#assertTrue(s.data.len < image.data.len, "compressed size")
s.setPosition 0
var decoded = s.PNGDecode(image.colorType, image.bitDepth)
assert image.width == decoded.width
assert image.height == decoded.height
if state == nil:
assertPixels(image, decoded.data, "Pixels")
else:
assertPixels(image, decoded.data, "Pixels Interlaced")
#Test PNG encoding and decoding the encoded result
proc doCodecTest(image: Image) =
doCodecTest(image, nil)
var state = makePNGEncoder()
state.interlaceMethod = IM_INTERLACED
doCodecTest(image, state)
#Test PNG encoding and decoding using some image generated with the given parameters
proc codecTest(width, height: int, colorType = LCT_RGBA, bitDepth = 8) =
echo "codec test ", width, " ", height
var image = generateTestImage(width, height, colorType, bitDepth)
image.doCodecTest()
proc testOtherPattern1() =
echo "codec other pattern 1"
var image: Image
new(image)
let w = 192
let h = 192
image.width = w
image.height = h
image.colorType = LCT_RGBA
image.bitDepth = 8
image.data = newString(w * h * 4)
for y in 0..h-1:
for x in 0..w-1:
image.data[4 * w * y + 4 * x + 0] = chr(int(127 * (1 + math.sin(float( x * x + y * y) / (float(w * h) / 8.0)))))
image.data[4 * w * y + 4 * x + 1] = chr(int(127 * (1 + math.sin(float((w - x - 1) * (w - x - 1) + y * y) / (float(w * h) / 8.0)))))
image.data[4 * w * y + 4 * x + 2] = chr(int(127 * (1 + math.sin(float( x * x + (h - y - 1) * (h - y - 1)) / (float(w * h) / 8.0)))))
image.data[4 * w * y + 4 * x + 3] = chr(int(127 * (1 + math.sin(float((w - x - 1) * (w - x - 1) + (h - y - 1) * (h - y - 1)) / (float(w * h) / 8.0)))))
doCodecTest(image)
proc testOtherPattern2() =
echo "codec other pattern 2"
var image: Image
new(image)
let w = 192
let h = 192
image.width = w
image.height = h
image.colorType = LCT_RGBA
image.bitDepth = 8
image.data = newString(w * h * 4)
for y in 0..h-1:
for x in 0..w-1:
image.data[4 * w * y + 4 * x + 0] = chr(255 * not (x and y) and 0xFF)
image.data[4 * w * y + 4 * x + 1] = chr((x xor y) and 0xFF)
image.data[4 * w * y + 4 * x + 2] = chr((x or y) and 0xFF)
image.data[4 * w * y + 4 * x + 3] = chr(255)
doCodecTest(image)
proc testSinglePixel(r, g, b, a: int) =
echo "codec single pixel " , r , " " , g , " " , b , " " , a
var pixel: Image
new(pixel)
pixel.width = 1
pixel.height = 1
pixel.colorType = LCT_RGBA
pixel.bitDepth = 8
pixel.data = newString(4)
pixel.data[0] = r.chr
pixel.data[1] = g.chr
pixel.data[2] = b.chr
pixel.data[3] = a.chr
doCodecTest(pixel)
proc testColor(r, g, b, a: int) =
echo "codec test color ", r , " " , g , " " , b , " " , a
var image: Image
new(image)
let w = 20
let h = 20
image.width = w
image.height = h
image.colorType = LCT_RGBA
image.bitDepth = 8
image.data = newString(w * h * 4)
for y in 0..h-1:
for x in 0..w-1:
image.data[20 * 4 * y + 4 * x + 0] = r.chr
image.data[20 * 4 * y + 4 * x + 0] = g.chr
image.data[20 * 4 * y + 4 * x + 0] = b.chr
image.data[20 * 4 * y + 4 * x + 0] = a.chr
doCodecTest(image)
image.data[3] = 0.chr #one fully transparent pixel
doCodecTest(image)
image.data[3] = 128.chr #one semi transparent pixel
doCodecTest(image)
var image3: Image
new(image3)
image3.width = image.width
image3.height = image.height
image3.colorType = image.colorType
image3.bitDepth = image.bitDepth
image3.data = image.data
#add 255 different colors
for i in 0..254:
image.data[i * 4 + 0] = i.chr
image.data[i * 4 + 1] = i.chr
image.data[i * 4 + 2] = i.chr
image.data[i * 4 + 3] = 255.chr
doCodecTest(image3)
#a 256th color
image.data[255 * 4 + 0] = 255.chr
image.data[255 * 4 + 1] = 255.chr
image.data[255 * 4 + 2] = 255.chr
image.data[255 * 4 + 3] = 255.chr
doCodecTest(image3)
testSinglePixel(r, g, b, a)
proc testSize(w, h: int) =
echo "codec test size ", w, " ", h
var image: Image
new(image)
image.width = w
image.height = h
image.colorType = LCT_RGBA
image.bitDepth = 8
image.data = newString(w * h * 4)
for y in 0..h-1:
for x in 0..w-1:
image.data[w * 4 * y + 4 * x + 0] = (x mod 256).chr
image.data[w * 4 * y + 4 * x + 0] = (y mod 256).chr
image.data[w * 4 * y + 4 * x + 0] = 255.chr
image.data[w * 4 * y + 4 * x + 0] = 255.chr
doCodecTest(image)
proc testPNGCodec() =
codecTest(1, 1)
codecTest(2, 2)
codecTest(1, 1, LCT_GREY, 1)
codecTest(7, 7, LCT_GREY, 1)
codecTest(127, 127)
codecTest(127, 127, LCT_GREY, 1)
testOtherPattern1()
testOtherPattern2()
testColor(255, 255, 255, 255)
testColor(0, 0, 0, 255)
testColor(1, 2, 3, 255)
testColor(255, 0, 0, 255)
testColor(0, 255, 0, 255)
testColor(0, 0, 255, 255)
testColor(0, 0, 0, 255)
testColor(1, 1, 1, 255)
testColor(1, 1, 1, 1)
testColor(0, 0, 0, 128)
testColor(255, 0, 0, 128)
testColor(127, 127, 127, 255)
testColor(128, 128, 128, 255)
testColor(127, 127, 127, 128)
testColor(128, 128, 128, 128)
#transparent single pixels
testColor(0, 0, 0, 0)
testColor(255, 0, 0, 0)
testColor(1, 2, 3, 0)
testColor(255, 255, 255, 0)
testColor(254, 254, 254, 0)
#This is mainly to test the Adam7 interlacing
for h in 1..11:
for w in 1..12:
testSize(w, h)
proc doPngSuiteTinyTest(b64: string, w, h, r, g, b, a: int) =
var input = fromBase64(b64)
var s = newStringStream(input)
var decoded = s.PNGDecode(LCT_RGBA, 8)
assert decoded.width == w
assert decoded.height == h
assert r == decoded.data[0].int
assert g == decoded.data[1].int
assert b == decoded.data[2].int
assert a == decoded.data[3].int
var state = makePNGEncoder()
state.autoConvert = false
var png = PNGEncode(decoded.data, LCT_RGBA, 8, w, h, state)
s = newStringStream()
png.writeChunks s
s.setPosition 0
var decoded2 = s.PNGDecode(LCT_RGBA, 8)
for i in 0..decoded.data.high:
assert decoded.data[i] == decoded2.data[i]
#checks that both png suite images have the exact same pixel content, e.g. to check that
#it decodes an interlaced and non-interlaced corresponding png suite image equally
proc doPngSuiteEqualTest(b64a, b64b: string) =
var input1 = fromBase64(b64a)
var s1 = newStringStream(input1)
var decoded1 = s1.PNGDecode(LCT_RGBA, 8)
var input2 = fromBase64(b64b)
var s2 = newStringStream(input2)
var decoded2 = s2.PNGDecode(LCT_RGBA, 8)
assert decoded1.height == decoded2.height
assert decoded1.width == decoded2.width
let size = decoded1.height * decoded1.width * 4
for i in 0.. <size:
if decoded1.data[i] != decoded2.data[i]:
echo "x: ", ((i div 4) mod decoded1.width), " y: ", ((i div 4) mod decoded1.width), " c: ", i mod 4
assert decoded1.data[i] == decoded2.data[i]
proc testPngSuiteTiny() =
echo "testPngSuiteTiny"
doPngSuiteTinyTest("iVBORw0KGgoAAAANSUhEUgAAAAEAAAABAQMAAAFS3GZcAAAABGdBTUEAAYagMeiWXwAAAANzQklU" &
"BAQEd/i1owAAAANQTFRFAAD/injSVwAAAApJREFUeJxjYAAAAAIAAUivpHEAAAAASUVORK5CYII=",
1, 1, 0, 0, 255, 255) #s01n3p01.png
doPngSuiteTinyTest("iVBORw0KGgoAAAANSUhEUgAAAAEAAAABAQMAAAAl21bKAAAABGdBTUEAAYagMeiWXwAAAANzQklU" &
"BAQEd/i1owAAAANQTFRFAAD/injSVwAAAApJREFUeJxjYAAAAAIAAUivpHEAAAAASUVORK5CYII=",
1, 1, 0, 0, 255, 255) #s01i3p01.png
doPngSuiteTinyTest("iVBORw0KGgoAAAANSUhEUgAAAAcAAAAHAgMAAAC5PL9AAAAABGdBTUEAAYagMeiWXwAAAANzQklU" &
"BAQEd/i1owAAAAxQTFRF/wB3AP93//8AAAD/G0OznAAAABpJREFUeJxj+P+H4WoMw605DDfmgEgg" &
"+/8fAHF5CrkeXW0HAAAAAElFTkSuQmCC",
7, 7, 0, 0, 255, 255) #s07n3p02.png
doPngSuiteTinyTest("iVBORw0KGgoAAAANSUhEUgAAAAcAAAAHAgMAAAHOO4/WAAAABGdBTUEAAYagMeiWXwAAAANzQklU" &
"BAQEd/i1owAAAAxQTFRF/wB3AP93//8AAAD/G0OznAAAACVJREFUeJxjOMBwgOEBwweGDQyvGf4z" &
"/GFIAcI/DFdjGG7MAZIAweMMgVWC+YkAAAAASUVORK5CYII=",
7, 7, 0, 0, 255, 255) #s07i3p02.png
doPngSuiteTinyTest("iVBORw0KGgoAAAANSUhEUgAAACAAAAAgAgMAAAAOFJJnAAAABGdBTUEAAYagMeiWXwAAAANzQklU" &
"AQEBfC53ggAAAAxQTFRFAP8A/wAA//8AAAD/ZT8rugAAACJJREFUeJxj+B+6igGEGfAw8MnBGKug" &
"LHwMqNL/+BiDzD0AvUl/geqJjhsAAAAASUVORK5CYII=",
32, 32, 0, 0, 255, 255) #basn3p02.png
doPngSuiteTinyTest("iVBORw0KGgoAAAANSUhEUgAAACAAAAAgAQMAAABJtOi3AAAABGdBTUEAAYagMeiWXwAAAAZQTFRF" &
"7v8iImb/bBrSJgAAABVJREFUeJxj4AcCBjTiAxCgEwOkDgC7Hz/Bk4JmWQAAAABJRU5ErkJggg==",
32, 32, 238, 255, 34, 255) #basn3p01.png
doPngSuiteTinyTest("iVBORw0KGgoAAAANSUhEUgAAACAAAAAgEAAAAAAGgflrAAAABGdBTUEAAYagMeiWXwAAAF5JREFU" &
"eJzV0jEKwDAMQ1E5W+9/xtygk8AoezLVKgSj2Y8/OICnuFcTE2OgOoJgHQiZAN2C9kDKBOgW3AZC" &
"JkC3oD2QMgG6BbeBkAnQLWgPpExgP28H7E/0GTjPfwAW2EvYX64rn9cAAAAASUVORK5CYII=",
32, 32, 0, 0, 0, 255) #basn0g16.png
doPngSuiteTinyTest("iVBORw0KGgoAAAANSUhEUgAAACAAAAAgEAAAAAFxhsn9AAAABGdBTUEAAYagMeiWXwAAAOJJREFU" &
"eJy1kTsOwjAQRMdJCqj4XYHD5DAcj1Okyg2okCyBRLOSC0BDERKCI7xJVmgaa/X8PFo7oESJEtka" &
"TeLDjdjjgCMe7eTE96FGd3AL7HvZsdNEaJMVo0GNGm775bgwW6Afj/SAjAY+JsYNXIHtz2xYxTXi" &
"UoOek4AbFcCnDYEK4NMGsgXcMrGHJytkBX5HIP8FAhVANIMVIBVANMPfgUAFEM3wAVyG5cxcecY5" &
"/dup3LVFa1HXmA61LY59f6Ygp1Eg1gZGQaBRILYGdxoFYmtAGgXx9YmCfPD+RMHwuuAFVpjuiRT/" &
"//4AAAAASUVORK5CYII=",
32, 32, 0, 0, 0, 255) #basi0g16.png
#s01n3p01.png s01i3p01.png
doPngSuiteEqualTest("iVBORw0KGgoAAAANSUhEUgAAAAEAAAABAQMAAAFS3GZcAAAABGdBTUEAAYagMeiWXwAAAANzQklU" &
"BAQEd/i1owAAAANQTFRFAAD/injSVwAAAApJREFUeJxjYAAAAAIAAUivpHEAAAAASUVORK5CYII=",
"iVBORw0KGgoAAAANSUhEUgAAAAEAAAABAQMAAAAl21bKAAAABGdBTUEAAYagMeiWXwAAAANzQklU" &
"BAQEd/i1owAAAANQTFRFAAD/injSVwAAAApJREFUeJxjYAAAAAIAAUivpHEAAAAASUVORK5CYII=")
#s07n3p02.png and s07i3p02.png
doPngSuiteEqualTest("iVBORw0KGgoAAAANSUhEUgAAAAcAAAAHAgMAAAC5PL9AAAAABGdBTUEAAYagMeiWXwAAAANzQklU" &
"BAQEd/i1owAAAAxQTFRF/wB3AP93//8AAAD/G0OznAAAABpJREFUeJxj+P+H4WoMw605DDfmgEgg" &
"+/8fAHF5CrkeXW0HAAAAAElFTkSuQmCC",
"iVBORw0KGgoAAAANSUhEUgAAAAcAAAAHAgMAAAHOO4/WAAAABGdBTUEAAYagMeiWXwAAAANzQklU" &
"BAQEd/i1owAAAAxQTFRF/wB3AP93//8AAAD/G0OznAAAACVJREFUeJxjOMBwgOEBwweGDQyvGf4z" &
"/GFIAcI/DFdjGG7MAZIAweMMgVWC+YkAAAAASUVORK5CYII=")
#basn0g16.png and basi0g16.png
doPngSuiteEqualTest("iVBORw0KGgoAAAANSUhEUgAAACAAAAAgEAAAAAAGgflrAAAABGdBTUEAAYagMeiWXwAAAF5JREFU" &
"eJzV0jEKwDAMQ1E5W+9/xtygk8AoezLVKgSj2Y8/OICnuFcTE2OgOoJgHQiZAN2C9kDKBOgW3AZC" &
"JkC3oD2QMgG6BbeBkAnQLWgPpExgP28H7E/0GTjPfwAW2EvYX64rn9cAAAAASUVORK5CYII=",
"iVBORw0KGgoAAAANSUhEUgAAACAAAAAgEAAAAAFxhsn9AAAABGdBTUEAAYagMeiWXwAAAOJJREFU" &
"eJy1kTsOwjAQRMdJCqj4XYHD5DAcj1Okyg2okCyBRLOSC0BDERKCI7xJVmgaa/X8PFo7oESJEtka" &
"TeLDjdjjgCMe7eTE96FGd3AL7HvZsdNEaJMVo0GNGm775bgwW6Afj/SAjAY+JsYNXIHtz2xYxTXi" &
"UoOek4AbFcCnDYEK4NMGsgXcMrGHJytkBX5HIP8FAhVANIMVIBVANMPfgUAFEM3wAVyG5cxcecY5" &
"/dup3LVFa1HXmA61LY59f6Ygp1Eg1gZGQaBRILYGdxoFYmtAGgXx9YmCfPD+RMHwuuAFVpjuiRT/" &
"//4AAAAASUVORK5CYII=")
#Create a PNG image with all known chunks (except only one of tEXt or zTXt) plus
#unknown chunks, and a palette.
proc createComplexPNG(): string =
let
w = 16
h = 17
var image = newString(w * h)
for i in 0..image.high:
image[i] = chr(i mod 256)
var state = makePNGEncoder()
state.modeIn.colorType = LCT_PALETTE
state.modeIn.bitDepth = 8
state.modeOut.colorType = LCT_PALETTE
state.modeOut.bitDepth = 8
state.autoConvert = false
state.textCompression = true
state.addID = true
for i in 0..255:
state.modeIn.addPalette(i, i, i ,i)
state.modeOut.addPalette(i, i, i ,i)
state.backgroundDefined = true
state.backgroundR = 127
state.addText("key0", "string0")
state.addText("key1", "string1")
state.addIText("ikey0", "ilangtag0", "itranskey0", "istring0")
state.addIText("ikey1", "ilangtag1", "itranskey1", "istring1")
state.timeDefined = true
state.year = 2012
state.month = 1
state.day = 2
state.hour = 3
state.minute = 4
state.second = 5
state.physDefined = true
state.physX = 1
state.physY = 2
state.physUnit = 1
state.addUnknownChunk("uNKa", "a01")
state.addUnknownChunk("uNKb", "b00")
state.addUnknownChunk("uNKc", "c00")
var png = PNGEncode(image, w, h, state)
var s = newStringStream()
png.writeChunks s
result = s.data
#test that, by default, it chooses filter type zero for all scanlines if the image has a palette
proc testPaletteFilterTypesZero() =
echo "testPaletteFilterTypesZero"
var raw = createComplexPNG()
var s = newStringStream(raw)
var png = s.PNGDecode()
var filterTypes = png.getFilterTypes()
assert 17 == filterTypes.len
for i in 0..16:
assert 0.chr == filterTypes[i]
proc testComplexPNG() =
echo "testComplexPNG"
var raw = createComplexPNG()
var s = newStringStream(raw)
var state = makePNGDecoder()
state.readTextChunks = true
state.rememberUnknownChunks = true
var png = s.PNGDecode(state)
var info = png.getInfo()
assert 16 == info.width
assert 17 == info.height
assert true == info.backgroundDefined
assert 127 == info.backgroundR
assert true == info.timeDefined
assert 2012 == info.year
assert 1 == info.month
assert 2 == info.day
assert 3 == info.hour
assert 4 == info.minute
assert 5 == info.second
assert true == info.physDefined
assert 1 == info.physX
assert 2 == info.physY
assert 1 == info.physUnit
let chunkNames = png.getChunkNames()
let expectedNames = "IHDR uNKa PLTE tRNS bKGD pHYs uNKb IDAT tIME zTXt zTXt tEXt iTXt iTXt uNKc IEND"
assert expectedNames == chunkNames
#TODO: test strings and unknown chunks too
proc testPredefinedFilters() =
let
w = 32
h = 32
echo "testPredefinedFilters"
var image = generateTestImage(w, h, LCT_RGBA, 8)
var state = makePNGEncoder()
state.filterStrategy = LFS_PREDEFINED
state.filterPaletteZero = false
state.predefinedFilters = repeat(chr(3), h) #everything to filter type '3'
var png = PNGEncode(image.data, w, h, state)
var outFilters = png.getFilterTypes()
assert outFilters.len == h
for i in 0.. <h:
assert chr(3) == outFilters[i]
proc testColorKeyConvert() =
echo "testColorKeyConvert"
let
w = 32
h = 32
var image = newString(w * h * 4)
let len = w*h
for i in 0..len-1:
image[i * 4 + 0] = chr(i mod 256)
image[i * 4 + 1] = chr(i div 256)
image[i * 4 + 2] = 0.chr
image[i * 4 + 3] = if i == 23: 0.chr else: 255.chr
var raw = PNGEncode(image, w, h)
var s = newStringStream()
raw.writeChunks s
s.setPosition 0
var png = s.PNGDecode()
var info = png.getInfo()
var image2 = png.convert(LCT_RGBA, 8)
assert 32 == info.width
assert 32 == info.height
assert true == info.mode.keyDefined
assert 23 == info.mode.keyR
assert 0 == info.mode.keyG
assert 0 == info.mode.keyB
assert image.len == image2.data.len
for i in 0..image.high:
assert image[i] == image2.data[i]
proc removeSpaces(input: string): string =
result = ""
for c in input:
if c != ' ': result.add c
proc bitStringToBytes(input: string): string =
let bits = removeSpaces(input)
result = newString((bits.len + 7) div 8)
for i in 0..bits.high:
let c = bits[i]
let j = i div 8
let k = i mod 8
if k == 0: result[j] = chr(0)
if c == '1': result[j] = chr(result[j].ord or (1 shl (7 - k)))
#test color convert on a single pixel. Testing palette and testing color keys is
#not supported by this function. Pixel values given using bits in an std::string
#of 0's and 1's.
proc colorConvertTest(bits_in: string, colorType_in: PNGcolorType, bitDepth_in: int,
bits_out: string, colorType_out: PNGcolorType, bitDepth_out: int) =
echo "color convert test ", bits_in, " - ", bits_out
let expected = bitStringToBytes(bits_out)
let image = bitStringToBytes(bits_in)
let modeIn = newColorMode(colorType_in, bitDepth_in)
let modeOut = newColorMode(colorType_out, bitDepth_out)
var actual = newString(expected.len)
var output = cstring(actual)
convert(output, image.cstring, modeOut, modeIn, 1)
for i in 0..expected.high:
assertEquals(expected[i].int, actual[i].int, "byte " & $i)
#Tests some specific color conversions with specific color bit combinations
proc testColorConvert() =
#test color conversions to RGBA8
colorConvertTest("1", LCT_GREY, 1, "11111111 11111111 11111111 11111111", LCT_RGBA, 8)
colorConvertTest("10", LCT_GREY, 2, "10101010 10101010 10101010 11111111", LCT_RGBA, 8)
colorConvertTest("1001", LCT_GREY, 4, "10011001 10011001 10011001 11111111", LCT_RGBA, 8)
colorConvertTest("10010101", LCT_GREY, 8, "10010101 10010101 10010101 11111111", LCT_RGBA, 8)
colorConvertTest("10010101 11111110", LCT_GREY_ALPHA, 8, "10010101 10010101 10010101 11111110", LCT_RGBA, 8)
colorConvertTest("10010101 00000001 11111110 00000001", LCT_GREY_ALPHA, 16, "10010101 10010101 10010101 11111110", LCT_RGBA, 8)
colorConvertTest("01010101 00000000 00110011", LCT_RGB, 8, "01010101 00000000 00110011 11111111", LCT_RGBA, 8)
colorConvertTest("01010101 00000000 00110011 10101010", LCT_RGBA, 8, "01010101 00000000 00110011 10101010", LCT_RGBA, 8)
colorConvertTest("10101010 01010101 11111111 00000000 11001100 00110011", LCT_RGB, 16, "10101010 11111111 11001100 11111111", LCT_RGBA, 8)
colorConvertTest("10101010 01010101 11111111 00000000 11001100 00110011 11100111 00011000", LCT_RGBA, 16, "10101010 11111111 11001100 11100111", LCT_RGBA, 8)
#test color conversions to RGB8
colorConvertTest("1", LCT_GREY, 1, "11111111 11111111 11111111", LCT_RGB, 8)
colorConvertTest("10", LCT_GREY, 2, "10101010 10101010 10101010", LCT_RGB, 8)
colorConvertTest("1001", LCT_GREY, 4, "10011001 10011001 10011001", LCT_RGB, 8)
colorConvertTest("10010101", LCT_GREY, 8, "10010101 10010101 10010101", LCT_RGB, 8)
colorConvertTest("10010101 11111110", LCT_GREY_ALPHA, 8, "10010101 10010101 10010101", LCT_RGB, 8)
colorConvertTest("10010101 00000001 11111110 00000001", LCT_GREY_ALPHA, 16, "10010101 10010101 10010101", LCT_RGB, 8)
colorConvertTest("01010101 00000000 00110011", LCT_RGB, 8, "01010101 00000000 00110011", LCT_RGB, 8)
colorConvertTest("01010101 00000000 00110011 10101010", LCT_RGBA, 8, "01010101 00000000 00110011", LCT_RGB, 8)
colorConvertTest("10101010 01010101 11111111 00000000 11001100 00110011", LCT_RGB, 16, "10101010 11111111 11001100", LCT_RGB, 8)
colorConvertTest("10101010 01010101 11111111 00000000 11001100 00110011 11100111 00011000", LCT_RGBA, 16, "10101010 11111111 11001100", LCT_RGB, 8)
#test color conversions to RGBA16
colorConvertTest("1", LCT_GREY, 1, "11111111 11111111 11111111 11111111 11111111 11111111 11111111 11111111", LCT_RGBA, 16)
colorConvertTest("10", LCT_GREY, 2, "10101010 10101010 10101010 10101010 10101010 10101010 11111111 11111111", LCT_RGBA, 16)
#test greyscale color conversions
colorConvertTest("1", LCT_GREY, 1, "11111111", LCT_GREY, 8)
colorConvertTest("1", LCT_GREY, 1, "1111111111111111", LCT_GREY, 16)
colorConvertTest("0", LCT_GREY, 1, "00000000", LCT_GREY, 8)
colorConvertTest("0", LCT_GREY, 1, "0000000000000000", LCT_GREY, 16)
colorConvertTest("11", LCT_GREY, 2, "11111111", LCT_GREY, 8)
colorConvertTest("11", LCT_GREY, 2, "1111111111111111", LCT_GREY, 16)
colorConvertTest("10", LCT_GREY, 2, "10101010", LCT_GREY, 8)
colorConvertTest("10", LCT_GREY, 2, "1010101010101010", LCT_GREY, 16)
colorConvertTest("1000", LCT_GREY, 4, "10001000", LCT_GREY, 8)
colorConvertTest("1000", LCT_GREY, 4, "1000100010001000", LCT_GREY, 16)
colorConvertTest("10110101", LCT_GREY, 8, "1011010110110101", LCT_GREY, 16)
colorConvertTest("1011010110110101", LCT_GREY, 16, "10110101", LCT_GREY, 8)
#others
colorConvertTest("11111111 11111111 11111111 00000000 00000000 00000000", LCT_RGB, 1, "10", LCT_GREY, 1)
#This tests color conversions from any color model to any color model, with any bit depth
#But it tests only with colors black and white, because that are the only colors every single model supports
proc testColorConvert2() =
echo "testColorConvert2"
proc toString(input: openArray[int]): string =
result = newString(input.len)
for i in 0..input.high:
result[i] = chr(input[i])
const
combos = [(colorType: LCT_GREY, bitDepth: 1),
(colorType: LCT_GREY, bitDepth: 2),
(colorType: LCT_GREY, bitDepth: 4),
(colorType: LCT_GREY, bitDepth: 8),
(colorType: LCT_GREY, bitDepth: 16),
(colorType: LCT_RGB, bitDepth: 8),
(colorType: LCT_RGB, bitDepth: 16),
(colorType: LCT_PALETTE, bitDepth: 1),
(colorType: LCT_PALETTE, bitDepth: 2),
(colorType: LCT_PALETTE, bitDepth: 4),
(colorType: LCT_PALETTE, bitDepth: 8),
(colorType: LCT_GREY_ALPHA, bitDepth: 8),
(colorType: LCT_GREY_ALPHA, bitDepth: 16),
(colorType: LCT_RGBA, bitDepth: 8),
(colorType: LCT_RGBA, bitDepth: 16)]
eight = [0,0,0,255, 255,255,255,255,
0,0,0,255, 255,255,255,255,
255,255,255,255, 0,0,0,255,
255,255,255,255, 255,255,255,255,
0,0,0,255].toString() #input in RGBA8
var
modeIn = newColorMode()
modeOut = newColorMode()
mode_8 = newColorMode()
input = newString(72)
output = newString(72)
inp = input.cstring
outp = output.cstring
eight2 = newString(36)
e2p = eight2.cstring
for i in 0..255:
let j = if i == 1: 255 else: i
modeIn.addPalette(j, j, j, 255)
modeOut.addPalette(j, j, j, 255)
for cma in combos:
modeIn.colorType = cma.colorType
modeIn.bitDepth = cma.bitDepth
for cmb in combos:
modeOut.colorType = cmb.colorType
modeOut.bitDepth = cmb.bitDepth
convert(inp, eight.cstring, modeIn, mode_8, 3 * 3)
convert(outp, inp, modeOut, modeIn, 3 * 3) #Test input to output type
convert(e2p, outp, mode_8, modeOut, 3 * 3)
assert eight == eight2
#tests that there are no crashes with auto color chooser in case of palettes with translucency etc...
proc testPaletteToPaletteConvert() =
echo "testPaletteToPaletteConvert"
let
w = 16
h = 16
var image = newString(w * h)
for i in 0..image.high: image[i] = chr(i mod 256)
var state = makePNGEncoder()
state.modeOut.colorType = LCT_PALETTE
state.modeIn.colorType = LCT_PALETTE
state.modeOut.bitDepth = 8
state.modeIn.bitDepth = 8
assert true == state.autoConvert
for i in 0..255:
state.modeIn.addPalette(i, i, i, i)
state.modeOut.addPalette(i, i, i, i)
discard PNGEncode(image, w, h, state)
#for this test, you have to choose palette colors that cause PNG to actually use a palette,
#so don't use all greyscale colors for example
proc doRGBAToPaletteTest(palette: openArray[int], expectedType = LCT_PALETTE) =
echo "testRGBToPaletteConvert ", palette.len
let
w = palette.len div 4
h = 257 #PNG encodes no palette if image is too small
var image = newString(w * h * 4)
for i in 0..image.high:
image[i] = palette[i mod palette.len].chr
var raw = PNGEncode(image, w, h)
var s = newStringStream()
raw.writeChunks s
s.setPosition 0
var png2 = s.PNGDecode()
var info = png2.getInfo()
var image2 = png2.convert(LCT_RGBA, 8)
assert image2.data == image
assert expectedType == info.mode.colorType
if expectedType == LCT_PALETTE:
assert ((palette.len div 4) == info.mode.paletteSize)
for i in 0..info.mode.palette.high:
assert info.mode.palette[i].r == image[i * 4 + 0]
assert info.mode.palette[i].g == image[i * 4 + 1]
assert info.mode.palette[i].b == image[i * 4 + 2]
assert info.mode.palette[i].a == image[i * 4 + 3]
proc testRGBToPaletteConvert() =
const
palette1 = [1,2,3,4]
palette2 = [1,2,3,4, 5,6,7,8]
palette3 = [1,1,1,255, 20,20,20,255, 20,20,21,255]
doRGBAToPaletteTest(palette1)
doRGBAToPaletteTest(palette2)
doRGBAToPaletteTest(palette3)
var palette: seq[int] = @[]
for i in 0..255:
palette.add(i)
palette.add(5)
palette.add(6)
palette.add(128)
doRGBAToPaletteTest(palette)
palette.add(5)
palette.add(6)
palette.add(7)
palette.add(8)
doRGBAToPaletteTest(palette, LCT_RGBA)
#Test that when decoding to 16-bit per channel, it always uses big endian consistently.
#It should always output big endian, the convention used inside of PNG, even though x86 CPU's are little endian.
proc test16bitColorEndianness() =
echo "test16bitColorEndianness"
#basn0g16.png from the PNG test suite
var base64 = "iVBORw0KGgoAAAANSUhEUgAAACAAAAAgEAAAAAAGgflrAAAABGdBTUEAAYagMeiWXwAAAF5JREFU" &
"eJzV0jEKwDAMQ1E5W+9/xtygk8AoezLVKgSj2Y8/OICnuFcTE2OgOoJgHQiZAN2C9kDKBOgW3AZC" &
"JkC3oD2QMgG6BbeBkAnQLWgPpExgP28H7E/0GTjPfwAW2EvYX64rn9cAAAAASUVORK5CYII="
var png = fromBase64(base64)
var s = newStringStream(png)
#Decode from 16-bit grey image to 16-bit per channel RGBA
var decoded = s.PNGDecode(LCT_RGBA, 16)
assert 0x09 == decoded.data[8].ord
assert 0x00 == decoded.data[9].ord
#Decode from 16-bit grey image to 16-bit grey raw image (no conversion)
var state = makePNGDecoder()
state.colorConvert = false
s.setPosition 0
var raw = s.PNGDecode(state)
assert 0x09 == raw.pixels[2].ord
assert 0x00 == raw.pixels[3].ord
#Decode from 16-bit per channel RGB image to 16-bit per channel RGBA
base64 = "iVBORw0KGgoAAAANSUhEUgAAACAAAAAgEAIAAACsiDHgAAAABGdBTUEAAYagMeiWXwAAAANzQklU" &
"DQ0N0DeNwQAAAH5JREFUeJztl8ENxEAIAwcJ6cpI+q8qKeNepAgelq2dCjz4AdQM1jRcf3WIDQ13" &
"qUNsiBBQZ1gR0cARUFIz3pug3586wo5+rOcfIaBOsCSggSOgpcB8D4D3R9DgfUyECIhDbAhp4Ajo" &
"KPD+CBq8P4IG72MiQkCdYUVEA0dAyQcwUyZpXH92ZwAAAABJRU5ErkJggg==" #cs3n2c16.png
png = fromBase64(base64)
s = newStringStream(png)
decoded = s.PNGDecode(LCT_RGBA, 16)
assert 0x1f == decoded.data[258].ord
assert 0xf9 == decoded.data[259].ord
#Decode from 16-bit per channel RGB image to 16-bit per channel RGBA raw image (no conversion)
s.setPosition 0
raw = s.PNGDecode(state)
assert 0x1f == raw.pixels[194].ord
assert 0xf9 == raw.pixels[195].ord
#Decode from palette image to 16-bit per channel RGBA
base64 = "iVBORw0KGgoAAAANSUhEUgAAAAcAAAAHAgMAAAC5PL9AAAAABGdBTUEAAYagMeiWXwAAAANzQklU" &
"BAQEd/i1owAAAAxQTFRF/wB3AP93//8AAAD/G0OznAAAABpJREFUeJxj+P+H4WoMw605DDfmgEgg" &
"+/8fAHF5CrkeXW0HAAAAAElFTkSuQmCC" #s07n3p02.png
png = fromBase64(base64)
s = newStringStream(png)
decoded = s.PNGDecode(LCT_RGBA, 16)
assert 0x77 == decoded.data[84].ord
assert 0x77 == decoded.data[85].ord
proc testNoAutoConvert() =
echo "testNoAutoConvert"
let
w = 32
h = 32
var image = newString(w * h * 4)
let len = w * h
for i in 0..len-1:
let c = if (i mod 2) != 0: 255.chr else: 0.chr
image[i * 4 + 0] = c
image[i * 4 + 1] = c
image[i * 4 + 2] = c
image[i * 4 + 3] = 0.chr
var state = makePNGEncoder()
state.modeOut.colorType = LCT_RGBA
state.modeOut.bitDepth = 8
state.autoConvert = false
var png = PNGEncode(image, w, h, state)
var s = newStringStream()
png.writeChunks s
s.setPosition 0
var raw = s.PNGDecode()
var info = raw.getInfo()
assert 32 == info.width
assert 32 == info.height
assert LCT_RGBA == info.mode.colorType
assert 8 == info.mode.bitDepth
assert image == raw.pixels
#colors is in RGBA, inbitDepth must be 8 or 16, the amount of bits per channel.
#colorType and bitDepth are the expected values. insize is amount of pixels. So the amount of bytes is insize * 4 * (inbitDepth / 8)
proc testAutoColorModel(colors: string, inbitDepth: int, colorType: PNGcolorType, bitDepth: int, key: bool) =
echo "testAutoColorModel ", inbitDepth, " ", colorType, " ", bitDepth, " ", key
let innum = colors.len div 4 * inbitDepth div 8
let num = max(innum, 65536) #Make image bigger so the convert doesn't avoid palette due to small image.
var colors2 = newString(num * 4 * (inbitDepth div 8))
for i in 0..colors2.high:
colors2[i] = colors[i mod colors.len]
var png = PNGEncode(colors2, LCT_RGBA, inbitDepth, num, 1)
var s = newStringStream()
png.writeChunks s
#now extract the color type it chose
s.setPosition 0
var raw = s.PNGDecode()
var info = raw.getInfo()
var decoded = png.convert(colorType, inbitDepth)
assert num == info.width
assert 1 == info.height
assert colorType == info.mode.colorType
assert bitDepth == info.mode.bitDepth
assert key == info.mode.keyDefined
if inbitDepth == 8:
colors.toHex
colors2.toHex
for i in 0..colors.high:
assert colors[i] == decoded.data[i]
else :
let len = colors.len div 2
for i in 0.. <len:
assert colors[i * 2] == decoded.data[i]
proc addColor(colors: var string, r, g, b, a: int) =
colors.add r.chr
colors.add g.chr
colors.add b.chr
colors.add a.chr
proc addColor16(colors: var string, r, g, b, a: int) =
colors.add chr(r and 255)
colors.add chr((r shr 8) and 255)
colors.add chr(g and 255)
colors.add chr((g shr 8) and 255)
colors.add chr(b and 255)
colors.add chr((b shr 8) and 255)
colors.add chr(a and 255)
colors.add chr((a shr 8) and 255)
proc testAutoColorModels() =
var grey1 = ""
for i in 0..1: addColor(grey1, i * 255, i * 255, i * 255, 255)
testAutoColorModel(grey1, 8, LCT_GREY, 1, false)
var grey2 = ""
for i in 0..3: addColor(grey2, i * 85, i * 85, i * 85, 255)
testAutoColorModel(grey2, 8, LCT_GREY, 2, false)
var grey4 = ""
for i in 0..15: addColor(grey4, i * 17, i * 17, i * 17, 255)
testAutoColorModel(grey4, 8, LCT_GREY, 4, false)
var grey8 = ""
for i in 0..255: addColor(grey8, i, i, i, 255)
testAutoColorModel(grey8, 8, LCT_GREY, 8, false)
var grey16 = ""
for i in 0..256: addColor16(grey16, i, i, i, 65535)
testAutoColorModel(grey16, 16, LCT_GREY, 16, false)
var palette = ""
addColor(palette, 0, 0, 1, 255)
testAutoColorModel(palette, 8, LCT_PALETTE, 1, false)
addColor(palette, 0, 0, 2, 255)
testAutoColorModel(palette, 8, LCT_PALETTE, 1, false)
for i in 3..4: addColor(palette, 0, 0, i, 255)
testAutoColorModel(palette, 8, LCT_PALETTE, 2, false)
for i in 5..7: addColor(palette, 0, 0, i, 255)
testAutoColorModel(palette, 8, LCT_PALETTE, 4, false)
for i in 8..17: addColor(palette, 0, 0, i, 255)
testAutoColorModel(palette, 8, LCT_PALETTE, 8, false)
addColor(palette, 0, 0, 18, 0) #transparent
testAutoColorModel(palette, 8, LCT_PALETTE, 8, false)
addColor(palette, 0, 0, 18, 1) #translucent
testAutoColorModel(palette, 8, LCT_PALETTE, 8, false)
var rgb = grey8
addColor(rgb, 255, 0, 0, 255)
testAutoColorModel(rgb, 8, LCT_RGB, 8, false)
var rgb_key = rgb
addColor(rgb_key, 128, 0, 0, 0)
testAutoColorModel(rgb_key, 8, LCT_RGB, 8, true)
var rgb_key2 = rgb_key
addColor(rgb_key2, 128, 0, 0, 255) #same color but opaque ==> no more key
testAutoColorModel(rgb_key2, 8, LCT_RGBA, 8, false)
var rgb_key3 = rgb_key
addColor(rgb_key3, 128, 0, 0, 255) #semi-translucent ==> no more key
testAutoColorModel(rgb_key3, 8, LCT_RGBA, 8, false)
var rgb_key4 = rgb_key
addColor(rgb_key4, 128, 0, 0, 255)
addColor(rgb_key4, 129, 0, 0, 255) #two different transparent colors ==> no more key
testAutoColorModel(rgb_key4, 8, LCT_RGBA, 8, false)
var grey1_key = grey1
grey1_key[7] = 0.chr
testAutoColorModel(grey1_key, 8, LCT_GREY, 1, true)
var grey2_key = grey2
grey2_key[7] = 0.chr
testAutoColorModel(grey2_key, 8, LCT_GREY, 2, true)
var grey4_key = grey4
grey4_key[7] = 0.chr
testAutoColorModel(grey4_key, 8, LCT_GREY, 4, true)
var grey8_key = grey8
grey8_key[7] = 0.chr
testAutoColorModel(grey8_key, 8, LCT_GREY, 8, true)
var small16 = ""
addColor16(small16, 1, 0, 0, 65535)
testAutoColorModel(small16, 16, LCT_RGB, 16, false)
var small16a = ""
addColor16(small16a, 1, 0, 0, 1)
testAutoColorModel(small16a, 16, LCT_RGBA, 16, false)
var not16 = ""
addColor16(not16, 257, 257, 257, 0)
testAutoColorModel(not16, 16, LCT_PALETTE, 1, false)
var alpha16 = ""
addColor16(alpha16, 257, 0, 0, 10000)
testAutoColorModel(alpha16, 16, LCT_RGBA, 16, false)
proc doMain() =
#testPNGCodec()
#testPngSuiteTiny()
#testPaletteFilterTypesZero()
#testComplexPNG()
#testPredefinedFilters()
#testColorKeyConvert()
#testColorConvert()
#testColorConvert2()
#testPaletteToPaletteConvert()
#testRGBToPaletteConvert()
#test16bitColorEndianness();
#testNoAutoConvert();
testAutoColorModels();
echo getTotalMem()
doMain()