~cpp
#format python

#---------------- pngtest.py -------------------------------------------
# -*- coding: utf-8 -*-

import unittest
import zlib
import Image
import misc

from ThomPNG import *


class TestPngFormat(unittest.TestCase):
	def setUp(self):
		self.png = ThomPNG('tmp.png')
	
	def tearDown(self):
		self.png.close()

	def testFirstEightBytes(self):
		#eightBytesFromFile = self.png.firstEightBytes()

		eightBytes = [137,80,78,71,13,10,26,10]

		eightBytesFromFile = self.png.firstEightBytes()
		
		for i in range(8):
			self.assertEqual( chr(eightBytes[i]), eightBytesFromFile[i] )

	def testChunkLength(self):
		chunkInfo = self.png.nextChunk()

		self.assertEqual(13, chunkInfo[0])	# 0 = chunkLen
	
	def testImageIHDR(self):
		im = Image.open(self.png.f.name)

		chunkInfo = self.png.nextChunk()
		
		# Width
		imageWidth = ord(chunkInfo[2][0]) * (256**3)
		imageWidth += ord(chunkInfo[2][1]) * (256**2)
		imageWidth += ord(chunkInfo[2][2]) * (256**1)
		imageWidth += ord(chunkInfo[2][3])

		self.assertEqual(im.size[0], imageWidth)

		# Height
		imageHeight = ord(chunkInfo[2][4]) * (256**3)
		imageHeight += ord(chunkInfo[2][5]) * (256**2)
		imageHeight += ord(chunkInfo[2][6]) * (256**1)
		imageHeight += ord(chunkInfo[2][7])

		self.assertEqual(im.size[1], imageHeight)

		# BitDepth
		bitDepth = ord(chunkInfo[2][8])
		self.assertEqual(True, bitDepth in [1,2,4,8,16])

		# ColorType
		colorType = ord(chunkInfo[2][9])
		self.assertEqual(True, colorType in [0,2,3,4,6])

		# CompressionMethod
		compressionMethod = ord(chunkInfo[2][10])

		# Filter Method
		filterMethod = ord(chunkInfo[2][11])

		# InterlaceMethod
		interlaceMethod = ord(chunkInfo[2][12])

		print '\nBitDepth, ColorType, CompressionMethod, FilterMethod = (%d, %d, %d, %d, %d)' \
				% (bitDepth, colorType, compressionMethod, filterMethod, interlaceMethod)

	
	def testImageDataSize(self):
		self.png.nextChunk()	# IHDR 부분 건너뛰기

		totalSize = 0
		loop=0
				
		while True:
			loop+=1

			chunkInfo = self.png.nextChunk()

			if chunkInfo[1] != 'IDAT':
				self.assertEqual('IEND', chunkInfo[1])
				self.assertEqual(0, chunkInfo[0])
				break
			
			totalSize += chunkInfo[0]

		# 전체 파일 크기와 비교.
		#self.assertEqual(224975, 8+4+4+13+4+(12*loop)+totalSize)

	def testRGB(self):
		rgb = RGB(1,1,1)
		self.assertEqual((1,1,1), rgb)


	# CRC 테스트
	def testCRC(self):
		chunkInfo = self.png.nextChunk()
		fileCRC = chunkInfo[3]
		computedCRC = self.png.computeCRC(chunkInfo[1]+chunkInfo[2])

		expectedCRC = [(computedCRC&0xff000000)>>24,(computedCRC&0x00ff0000)>>16,(computedCRC&0x0000ff00)>>8,(computedCRC&0x000000ff)]
		fileCRCInNum = [ord(fileCRC[0]), ord(fileCRC[1]), ord(fileCRC[2]), ord(fileCRC[3])]

		self.assertEqual(expectedCRC, fileCRCInNum)

	def testPaeth(self):
		self.assertEqual(10, self.png.paethDictor(10,11,12))
		self.assertEqual(87, self.png.paethDictor(0,87,0))


	# PIL과 비교. scanline by scanline 으로 비교해야 한다.
	def testCompareWithPIL(self):
		im = Image.open(self.png.f.name)

		zlibdata = self.png.getData()
		base = [ord(zlibdata[1]), ord(zlibdata[2]), ord(zlibdata[3])]

		rgbmap = []

		for i in range(im.size[1]):
			idx = self.png.getIdx(0,i)-1
			scanline = self.png.makeScanline(base, i, zlibdata, rgbmap )
			rgbmap.append(scanline)
			self.assertTrue(len(rgbmap)>0)

		for j in range(im.size[1]):
			for i in range(im.size[0]) :
				self.assertTrue(rgbmap[j][i].compare(im.getpixel((i,j)) ) )




if __name__=='__main__':
	print '\n'
	print '----------------------------------------'
	print '          PNG Format TEST'
	print '----------------------------------------'
	print '\n'

	unittest.main(argv=('', '-v'))


#---------------- ThomPNG.py -------------------------------------------
# -*- coding: utf-8 -*-

import Image
import zlib

class ThomPNG:
	def __init__(self, filename):
		self.f = file(filename, 'r')

		self.leadingEightBytes = None
		self.firstEightBytes()
		self.crc = CRC()
		self.data = None; self.width = None; self.height = None; self.bitDepth = None

	def close(self):
		self.f.close()

	def firstEightBytes(self):
		if self.leadingEightBytes == None:
			self.leadingEightBytes = self.f.read(8)

		return self.leadingEightBytes

	def nextChunk(self):
		buff = self.f.read(8)
		chunkType = buff[4:]

		chunkLen = ord(buff[0]) * (256 ** 3 )
		chunkLen += ord(buff[1]) * (256 ** 2 )
		chunkLen += ord(buff[2]) * (256)
		chunkLen += ord(buff[3])

		chunk = self.f.read(chunkLen)

		if chunkType == 'IHDR':
			self.makeInfo(chunk)

		crc = self.f.read(4)

		return [chunkLen, chunkType, chunk, crc]


	def makeInfo(self, chunk):	#width, height 등의 정보 얻기
		# Width
		self.width = ord(chunk[0]) * (256**3)
		self.width += ord(chunk[1]) * (256**2)
		self.width += ord(chunk[2]) * (256**1)
		self.width += ord(chunk[3])
		# Height
		self.height = ord(chunk[4]) * (256**3)
		self.height += ord(chunk[5]) * (256**2)
		self.height += ord(chunk[6]) * (256**1)
		self.height += ord(chunk[7])

	def getData(self):
		if self.data != None:
			return self.data
		
		self.nextChunk()	# IHDR 읽기
	
		# PLTE 등은 아직..
		# ...

		self.data = ''

		while True:
			chunks = self.nextChunk()
			
			if chunks[1]!= 'IDAT':
				break
			
			self.data += chunks[2]
		
		self.data = zlib.decompress(self.data)
		return self.data
	

	def computeCRC(self, buf):
		return self.crc.getCRC(buf)

	def makeScanline(self, basepixel, ypos, stream, rgbmap):	# method 는 PNG filter type, stream은 zlib 으로 decomposite된 값들
		idx = self.getIdx(0, ypos)-1
		method = ord(stream[idx])

		if method == 0:
			pass
		elif method == 1:	# sub
			return self.makeScanlineBySub(basepixel, ypos, stream)
		elif method == 2:
			return self.makeScanlineByUp(basepixel, ypos, stream, rgbmap)
		elif method == 3:
			return self.makeScanlineByAverage(basepixel, ypos, stream, rgbmap)
		elif method == 4:	#paeth
			return self.makeScanlineByPaeth(basepixel, ypos, stream, rgbmap)
	
	def makeScanlineBySub(self, basepixel, ypos, stream):
		scanline = []
		for i in range(self.width):
			idx = self.getIdx(i, ypos)
			filteredR = ord(stream[idx])
			filteredG = ord(stream[idx+1])
			filteredB = ord(stream[idx+2])
			
			if i > 0 :	# 인덱스가 0이 아닐 경우는 옆칸에서 더한다.
				filteredR += scanline[i-1].r
				filteredG += scanline[i-1].g
				filteredB += scanline[i-1].b

			rgbs = self.pureRGB(filteredR, filteredG, filteredB)
			scanline.append(RGB(rgbs[0], rgbs[1], rgbs[2]) )

		return scanline

	def makeScanlineByUp(self, basepixel, ypos, stream, rgbmap):
		scanline = []
		for i in range(self.width):
			idx = self.getIdx(i, ypos)
			filteredR = ord(stream[idx])
			filteredG = ord(stream[idx+1])
			filteredB = ord(stream[idx+2])

			if ypos > 0 :	# y가 0 이면 윗칸에서 더할수 없지만 0보다 크면 더해야한다
				filteredR += rgbmap[-1][i].r
				filteredG += rgbmap[-1][i].g
				filteredB += rgbmap[-1][i].b
			
			rgbs = self.pureRGB(filteredR, filteredG, filteredB)
			scanline.append(RGB(rgbs[0], rgbs[1], rgbs[2]) )
		return scanline

	def makeScanlineByAverage(self, basepixel, ypos, stream, rgbmap):
		scanline = []
		for i in range(self.width):
			idx = self.getIdx(i, ypos)
			filteredR = ord(stream[idx])
			filteredG = ord(stream[idx+1])
			filteredB = ord(stream[idx+2])

			leftR = 0; leftG = 0; leftB = 0
			upR = 0; upG = 0; upB = 0

			if ypos > 0 :
				upR = rgbmap[-1][i].r
				upG = rgbmap[-1][i].g
				upB = rgbmap[-1][i].b
			if i > 0 :
				leftR = scanline[i-1].r
				leftG = scanline[i-1].g
				leftB = scanline[i-1].b

			filteredR += self.floor((leftR+upR)/2.0)
			filteredG += self.floor((leftG+upG)/2.0)
			filteredB += self.floor((leftB+upB)/2.0)

			rgbs = self.pureRGB(filteredR, filteredG, filteredB)
			scanline.append(RGB(rgbs[0], rgbs[1], rgbs[2]) )

		return scanline

	def makeScanlineByPaeth(self, basepixel, ypos, stream, rgbmap):
		scanline = []
		for i in range(self.width):
			idx = self.getIdx(i, ypos)
			filteredR = ord(stream[idx])
			filteredG = ord(stream[idx+1])
			filteredB = ord(stream[idx+2])
			
			if i == 0 and ypos > 0 :
				filteredR += self.paethDictor(0, rgbmap[-1][i].r, 0)
				filteredG += self.paethDictor(0, rgbmap[-1][i].g, 0)
				filteredB += self.paethDictor(0, rgbmap[-1][i].b, 0)
			else:
				if ypos == 0 :
					filteredR += self.paethDictor(scanline[i-1].r, 0, 0)
					filteredG += self.paethDictor(scanline[i-1].g, 0, 0)
					filteredB += self.paethDictor(scanline[i-1].b, 0, 0)
				else:
					filteredR += self.paethDictor(scanline[i-1].r, rgbmap[-1][i].r, rgbmap[-1][i-1].r)
					filteredG += self.paethDictor(scanline[i-1].g, rgbmap[-1][i].g, rgbmap[-1][i-1].g)
					filteredB += self.paethDictor(scanline[i-1].b, rgbmap[-1][i].b, rgbmap[-1][i-1].b)

			rgbs = self.pureRGB(filteredR, filteredG, filteredB)
			scanline.append(RGB(rgbs[0], rgbs[1], rgbs[2]) )

		return scanline

	def pureRGB(self, r, g, b):
		if r >= 256 : r -= 256
		if g >= 256 : g -= 256
		if b >= 256 : b -= 256
		if r < 0 : r += 256
		if g < 0 : g += 256
		if b < 0 : b += 256

		return (r, g, b)

	def paethDictor(self, a, b, c):
		p = a + b - c
		pa = abs(p-a)
		pb = abs(p-b)
		pc = abs(p-c)

		if pa<=pb and pa<=pc: return a
		elif pb<=pc : return b
		else : return c
		

	def getIdx(self, x,y):
		base = ( self.height * 3 * y ) +  (y +1)
		idx = base + 3 * x
		return idx

	def floor(self, x):
		if x %1.0  == 0.0:
			return int(round(x))
		else:
			return int(round(x+1))

	

class CRC:
	def __init__(self):
		self.crcTable = 0 
		self.makeCRCTable()

	def makeCRCTable(self):
		self.crcTable = [0L]*256

		for i in range(256):
			c = long(i)
			for k in range(8):
				if ( c & 1 ) :
					c = 0xedb88320L ^ (c>>1)
				else :
					c = c >> 1
			self.crcTable[i] = c

	def updateCRC(self, buf):
		c = 0xFFFFFFFFL

		for ch in buf:
			#c = crcTable[(c ^ ord(ch) ) & 0xff] ^ (c >> 8)
			c = self.crcTable[int ((c^ord(ch))&0xff) ] ^ (c >> 8)
		
		return long(c)

	def getCRC(self, buf):
		return long(self.updateCRC(buf) ^ 0xFFFFFFFFL )


class RGB:
	def __init__(self, r, g, b):
		self.r = r
		self.g = g
		self.b = b

	def compare(self, rgb):
		return self.r == rgb[0] and self.g == rgb[1] and self.b == rgb[2]

	def __eq__(self, rgb):
		return self.compare(rgb)

	def __str__(self):
		return str((self.r, self.g, self.b))

----
PNGFileFormat
Retrieved from http://wiki.zeropage.org/wiki.php/PNGFileFormat/FormatUnitTestInPythonLanguage
last modified 2021-02-07 05:23:58