~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))