import ../nimPNG, streams, math, strutils, tables, base64, os import ../nimPNG/[buffer, filters] type Image = ref object data: string width, height: int colorType: PNGcolorType bitDepth: int proc fromBase64(input: string): string = result = base64.decode(input) proc assertEquals[T, U](expected: T, actual: U, message = "") = if expected != actual: echo "Error: Not equal! Expected ", expected, " got ", actual, ". ", "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 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.. 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) 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 image3.data[255 * 4 + 0] = 255.chr image3.data[255 * 4 + 1] = 255.chr image3.data[255 * 4 + 2] = 255.chr image3.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.decodePNG(LCT_RGBA, 8) assertEquals(w, decoded.width) assertEquals(h, decoded.height) assertEquals(r, decoded.data[0].int) assertEquals(g, decoded.data[1].int) assertEquals(b, decoded.data[2].int) assertEquals(a, decoded.data[3].int) var state = makePNGEncoder() state.autoConvert = false var png = encodePNG(decoded.data, LCT_RGBA, 8, w, h, state) s = newStringStream() png.writeChunks s s.setPosition 0 var decoded2 = s.decodePNG(LCT_RGBA, 8) for i in 0..decoded.data.high: assertEquals(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.decodePNG(LCT_RGBA, 8) var input2 = fromBase64(b64b) var s2 = newStringStream(input2) var decoded2 = s2.decodePNG(LCT_RGBA, 8) assertEquals(decoded1.height, decoded2.height) assertEquals(decoded1.width, decoded2.width) let size = decoded1.height * decoded1.width * 4 for i in 0.. 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 testFilter() = echo "test Filter" let input = "tfilter.png" let temp = "temp.png" let png = loadPNG32("tests" / "misc" / input) discard savePNG32("tests" / "misc" / temp, png.data, png.width, png.height) let png2 = loadPNG32("tests" / "misc" / temp) if png.data != png2.data: echo "testFilter failed" quit() proc testPaletteToPaletteDecode() = echo "testPaletteToPaletteDecode" # It's a bit big for a 2x2 image... but this tests needs one with 256 palette entries in it. let base64 = "iVBORw0KGgoAAAANSUhEUgAAAAIAAAACCAMAAABFaP0WAAAAA3NCSVQICAjb4U/gAAADAFBMVEUA" & "AAAAADMAAGYAAJkAAMwAAP8AMwAAMzMAM2YAM5kAM8wAM/8AZgAAZjMAZmYAZpkAZswAZv8AmQAA" & "mTMAmWYAmZkAmcwAmf8AzAAAzDMAzGYAzJkAzMwAzP8A/wAA/zMA/2YA/5kA/8wA//8zAAAzADMz" & "AGYzAJkzAMwzAP8zMwAzMzMzM2YzM5kzM8wzM/8zZgAzZjMzZmYzZpkzZswzZv8zmQAzmTMzmWYz" & "mZkzmcwzmf8zzAAzzDMzzGYzzJkzzMwzzP8z/wAz/zMz/2Yz/5kz/8wz//9mAABmADNmAGZmAJlm" & "AMxmAP9mMwBmMzNmM2ZmM5lmM8xmM/9mZgBmZjNmZmZmZplmZsxmZv9mmQBmmTNmmWZmmZlmmcxm" & "mf9mzABmzDNmzGZmzJlmzMxmzP9m/wBm/zNm/2Zm/5lm/8xm//+ZAACZADOZAGaZAJmZAMyZAP+Z" & "MwCZMzOZM2aZM5mZM8yZM/+ZZgCZZjOZZmaZZpmZZsyZZv+ZmQCZmTOZmWaZmZmZmcyZmf+ZzACZ" & "zDOZzGaZzJmZzMyZzP+Z/wCZ/zOZ/2aZ/5mZ/8yZ///MAADMADPMAGbMAJnMAMzMAP/MMwDMMzPM" & "M2bMM5nMM8zMM//MZgDMZjPMZmbMZpnMZszMZv/MmQDMmTPMmWbMmZnMmczMmf/MzADMzDPMzGbM" & "zJnMzMzMzP/M/wDM/zPM/2bM/5nM/8zM////AAD/ADP/AGb/AJn/AMz/AP//MwD/MzP/M2b/M5n/" & "M8z/M///ZgD/ZjP/Zmb/Zpn/Zsz/Zv//mQD/mTP/mWb/mZn/mcz/mf//zAD/zDP/zGb/zJn/zMz/" & "zP///wD//zP//2b//5n//8z///8AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" & "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" & "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABlenwdAAABAHRSTlP/////////////////////////" & "////////////////////////////////////////////////////////////////////////////" & "////////////////////////////////////////////////////////////////////////////" & "////////////////////////////////////////////////////////////////////////////" & "//////////////////////////////////8AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" & "AAAAAAAAAAAAG8mZagAAAAlwSFlzAAAOTQAADpwB3vacVwAAAA5JREFUCJlj2CLHwHodAATjAa+k" & "lTE5AAAAAElFTkSuQmCC" let png = fromBase64(base64) var s = newStringStream(png) let decoded = s.decodePNG(LCT_PALETTE, 8) assertEquals(2, decoded.width) assertEquals(2, decoded.height) assertEquals(180, decoded.data[0].int) assertEquals(30, decoded.data[1].int) assertEquals(5, decoded.data[2].int) assertEquals(215, decoded.data[3].int) # 2-bit palette proc testPaletteToPaletteDecode2() = echo "testPaletteToPaletteDecode2" let base64 = "iVBORw0KGgoAAAANSUhEUgAAACAAAAAgAgMAAAAOFJJnAAAADFBMVEX/AAAA/wAAAP/////7AGD2AAAAE0lEQVR4AWMQhAKG3VCALDIqAgDl2WYBCQHY9gAAAABJRU5ErkJggg==" let png = fromBase64(base64) var s = newStringStream(png) let decoded = s.decodePNG(LCT_PALETTE, 8) assertEquals(32, decoded.width) assertEquals(32, decoded.height) assertEquals(0, decoded.data[0].int) assertEquals(1, decoded.data[1].int) # Now add a user-specified output palette, that differs from the input palette. That should give error 82. #LodePNGState state; #lodepng_state_init(&state); #state.info_raw.colortype = LCT_PALETTE; #state.info_raw.bitdepth = 8; #lodepng_palette_add(&state.info_raw, 0, 0, 0, 255); #lodepng_palette_add(&state.info_raw, 1, 1, 1, 255); #lodepng_palette_add(&state.info_raw, 2, 2, 2, 255); #lodepng_palette_add(&state.info_raw, 3, 3, 3, 255); #unsigned char* image2 = 0; #unsigned error2 = lodepng_decode(&image2, &width, &height, &state, &png[0], png.size()); #ASSERT_EQUALS(82, error2); #lodepng_state_cleanup(&state); #free(image2); proc flipBit(c: uint8, bitpos: int): uint8 = result = c xor uint8(1 shl bitpos) # Test various broken inputs. Returned errors are not checked, what is tested is # that is doesn't crash, and, when run with valgrind, no memory warnings are # given. proc testFuzzing() = echo "testFuzzing" var png = createComplexPNG() broken = newString(png.len) errors = initTable[string, int]() copyMem(broken.cstring, png.cstring, png.len) var settings = makePNGDecoder() settings.ignoreCRC = true settings.ignoreAdler32 = true for i in 0.. 0: broken.setLen(broken.len - 1) try: var s = newStringStream(broken) discard s.decodePNG(settings) except Exception as ex: if errors.hasKey(ex.msg): inc errors[ex.msg] else: errors[ex.msg] = 0 echo GC_getStatistics() #For fun, print the number of each error echo "Fuzzing error code counts: " for key, val in pairs(errors): echo key, " : ", val proc doMain() = # PNG testPNGCodec() testPngSuiteTiny() testPaletteFilterTypesZero() testComplexPNG() testPredefinedFilters() testPaletteToPaletteDecode() testPaletteToPaletteDecode2() #testFuzzing() OOM # COLOR testFewColors() testColorKeyConvert() testColorConvert() testColorConvert2() testPaletteToPaletteConvert() testRGBToPaletteConvert() test16bitColorEndianness() testNoAutoConvert() testAutoColorModels() testFilter() doMain()