첨부 소스 코드는 나눔고딕코딩 폰트를 사용합니다.
728x90
반응형
728x170

TestProject.zip
9.65MB

▶ main.py

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

※ 명령 프롬프트에서 env 가상 환경을 활성화하고 아래와 같이 실행하거나

python.exe main.py IMAGE/gray.png > OUTPUT/gray.svg

 

※ 비주얼 스튜디오에서 아래와 같이 설정하고 실행한다.

728x90
반응형
그리드형(광고전용)
Posted by icodebroker

댓글을 달아 주세요