import sys
import operator
import logging
import collections
import io
import optparse
from PIL import Image
################################################################################
# 공통
################################################################################
def AddTuple(tuple1, tuple2):
return tuple(map(operator.add, tuple1, tuple2))
def SubtractTuple(tuple1, tuple2):
return tuple(map(operator.sub, tuple1, tuple2))
def NegateTuple(tuple1):
return tuple(map(operator.neg, tuple1))
def CalculateDirection(edgeTupleTuple):
return SubtractTuple(edgeTupleTuple[1], edgeTupleTuple[0])
def CalculateMagnitude(a):
return int(pow(pow(a[0], 2) + pow(a[1], 2), .5))
def Normalize(tuple1):
magnitude = CalculateMagnitude(tuple1)
assert magnitude > 0, "Cannot normalize a zero-length vector"
return tuple(map(operator.truediv, tuple1, [magnitude] * len(tuple1)))
def GetSVGHeader(width, height):
return """<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg width="%d" height="%d" xmlns="http://www.w3.org/2000/svg" version="1.1">
""" % (width, height)
################################################################################
# SVG 데이터 구하기 1
################################################################################
def GetJoinedEdgeList(assortedEdgeDictionary, keepEveryPoint = False):
pieceListList = []
pieceList = []
directionDeque = collections.deque([(0, 1), (1, 0), (0, -1), (-1, 0)])
while assortedEdgeDictionary:
if not pieceList:
pieceList.append(assortedEdgeDictionary.pop())
currentDirectionTuple = Normalize(CalculateDirection(pieceList[-1]))
while currentDirectionTuple != directionDeque[2]:
directionDeque.rotate()
for i in range(1, 4):
nextEndTuple = AddTuple(pieceList[-1][1], directionDeque[i])
nextEdgeTuple = (pieceList[-1][1], nextEndTuple)
if nextEdgeTuple in assortedEdgeDictionary:
assortedEdgeDictionary.remove(nextEdgeTuple)
if i == 2 and not keepEveryPoint:
pieceList[-1] = (pieceList[-1][0], nextEdgeTuple[1])
else:
pieceList.append(nextEdgeTuple)
if pieceList[0][0] == pieceList[-1][1]:
if not keepEveryPoint and Normalize(CalculateDirection(pieceList[0])) == Normalize(CalculateDirection(pieceList[-1])):
pieceList[-1] = (pieceList[-1][0], pieceList.pop(0)[1])
pieceListList.append(pieceList)
pieceList = []
break
else:
raise Exception("Failed to find connecting edge")
return pieceListList
def GetSVGData1(image, opaque = None, keepEveryPoint = False):
adjacentOffsetTuple = ((1, 0), (0, 1), (-1, 0), (0, -1))
visitedImage = Image.new("1", image.size, 0)
colorTupleDictionary = {}
width, height = image.size
for x in range(width):
for y in range(height):
pointTuple = (x, y)
if visitedImage.getpixel(pointTuple):
continue
colorTuple = image.getpixel((x, y))
if opaque and not colorTuple[3]:
continue
pieceList = []
queueList = [pointTuple]
visitedImage.putpixel(pointTuple, 1)
while queueList:
pointTuple = queueList.pop()
for offsetTuple in adjacentOffsetTuple:
neighbourPointTuple = AddTuple(pointTuple, offsetTuple)
if not (0 <= neighbourPointTuple[0] < width) or not (0 <= neighbourPointTuple[1] < height):
continue
if visitedImage.getpixel(neighbourPointTuple):
continue
neighbourPointColorTuple = image.getpixel(neighbourPointTuple)
if neighbourPointColorTuple != colorTuple:
continue
queueList.append(neighbourPointTuple)
visitedImage.putpixel(neighbourPointTuple, 1)
pieceList.append(pointTuple)
if not colorTuple in colorTupleDictionary:
colorTupleDictionary[colorTuple] = []
colorTupleDictionary[colorTuple].append(pieceList)
del adjacentOffsetTuple
del visitedImage
edgeDictionray = \
{
(-1, 0) : ((0, 0), (0, 1)),
( 0, 1) : ((0, 1), (1, 1)),
( 1, 0) : ((1, 1), (1, 0)),
( 0, -1) : ((1, 0), (0, 0))
}
colorEdgeDictionary = {}
for colorTuple, pieceList in colorTupleDictionary.items():
for piecePixelList in pieceList:
edgeSet = set([])
for coordinateTuple in piecePixelList:
for offsetTuple, (startOffsetTuple, endOffsetTuple) in edgeDictionray.items():
neighbourPointTuple = AddTuple(coordinateTuple, offsetTuple )
startTuple = AddTuple(coordinateTuple, startOffsetTuple)
endTuple = AddTuple(coordinateTuple, endOffsetTuple )
edgeTuple = (startTuple, endTuple)
if neighbourPointTuple in piecePixelList:
continue
edgeSet.add(edgeTuple)
if not colorTuple in colorEdgeDictionary:
colorEdgeDictionary[colorTuple] = []
colorEdgeDictionary[colorTuple].append(edgeSet)
del colorTupleDictionary
del edgeDictionray
colorJoinedPieceDictionary = {}
for colorTuple, pieceList in colorEdgeDictionary.items():
colorJoinedPieceDictionary[colorTuple] = []
for assortedEdgeDictionary in pieceList:
colorJoinedPieceDictionary[colorTuple].append(GetJoinedEdgeList(assortedEdgeDictionary, keepEveryPoint))
stringIO = io.StringIO()
stringIO.write(GetSVGHeader(*image.size))
for colorTuple, shapeList in colorJoinedPieceDictionary.items():
for shape in shapeList:
stringIO.write(""" <path d=" """)
for subsidaryShape in shape:
pointTuple = subsidaryShape.pop(0)[0]
stringIO.write(""" M %d,%d """ % pointTuple)
for edgeTuple in subsidaryShape:
pointTuple = edgeTuple[0]
stringIO.write(""" L %d,%d """ % pointTuple)
stringIO.write(""" Z """)
stringIO.write(""" " style="fill:rgb%s; fill-opacity:%.3f; stroke:none;" />\n""" % (colorTuple[0:3], float(colorTuple[3]) / 255))
stringIO.write("""</svg>\n""")
return stringIO.getvalue()
################################################################################
# SVG 데이터 구하기 2
################################################################################
def GetSVGData2(image, opaque = None):
stringIO = io.StringIO()
stringIO.write(GetSVGHeader(*image.size))
width, height = image.size
for x in range(width):
for y in range(height):
pointTuple = (x, y)
colorTuple = image.getpixel(pointTuple)
if opaque and not colorTuple[3]:
continue
stringIO.write(""" <rect x="%d" y="%d" width="1" height="1" style="fill:rgb%s; fill-opacity:%.3f; stroke:none;" />\n"""\
% (x, y, colorTuple[0:3], float(colorTuple[3]) / 255))
stringIO.write("""</svg>\n""")
return stringIO.getvalue()
################################################################################
# SVG 파일 생성하기
################################################################################
def CreateSVGFile(pngFilePath, contiguous = None, opaque = None, keepEveryPoint = None):
try:
pngImageFile = Image.open(pngFilePath)
except IOError:
print("이미지 파일을 열 수가 없습니다 : %s\n" % pngFilePath)
sys.exit(1)
image = pngImageFile.convert("RGBA")
if contiguous:
return GetSVGData1(image, opaque, keepEveryPoint)
else:
return GetSVGData2(image, opaque)
################################################################################
# 프로그램 시작하기
################################################################################
logging.basicConfig()
log = logging.getLogger('png2svg')
if __name__ == "__main__":
parser = optparse.OptionParser()
parser.add_option(\
"-v",
"--verbose",
action = "store_true",
dest = "verbosity",
help = "Print verbose information for debugging",
default = None)
parser.add_option(\
"-q",
"--quiet",
action = "store_false",
dest = "verbosity",
help = "Suppress warnings",
default = None)
parser.add_option(\
"-p",
"--pixels",
action = "store_false",
dest = "contiguous",
help = "Generate a separate shape for each pixel; do not group pixels into contiguous areas of the same colour",
default = True)
parser.add_option(\
"-o",
"--opaque",
action = "store_true",
dest = "opaque",
help = "Opaque only; do not create shapes for fully transparent pixels.",
default = None)
parser.add_option(\
"-1",
"--one",
action = "store_true",
dest = "keeEveryPoint",
help = "1-pixel-width edges on contiguous shapes; default is to remove intermediate points on straight line edges.",
default = None)
(options, argumentList) = parser.parse_args()
if options.verbosity == True:
log.setLevel(logging.DEBUG)
elif options.verbosity == False:
log.setLevel(logging.ERROR)
print(CreateSVGFile(argumentList[0], contiguous = options.contiguous, opaque = options.opaque, keepEveryPoint = options.keeEveryPoint))