Deduplicação de objetos de desenho em arquivos gráficos SVG / vetoriais

0

Eu tenho uma imagem exportada que consiste em ~ 35000 objetos (a única maneira de obtê-la do aplicativo de origem era como um "metarquivo" copiado e colado). Colando no desenho do libreoffice e convertendo em polígonos eu tenho algo que eu posso editar. Cada linha visível no arquivo é uma pilha de 8 linhas, portanto há 8x tantos objetos quanto deveria existir. No momento, ele quebra o inkscape, seja como um arquivo .pdf ou .svg, mas eu posso editar no libreoffice (é dolorosamente lento fazer qualquer coisa como pan ou zoom, e a dedução manual seria de 10 a 20 horas de trabalho pelo meu cálculo aproximado).

Existe alguma coisa lá fora que pode ler um arquivo SVG, encontrar objetos que são duplicados uns dos outros e manter apenas uma das cópias? A grande maioria dos meus objetos são linhas, por isso, se ele pudesse lidar apenas com linhas que não fossem polígonos, colocaria meu arquivo em um tamanho utilizável.

Como um SVG, o arquivo tem 20MB, obviamente .odg e .pdf são compactados.

Eu tenho Linux ou WinXP (VM, não pergunte!) à minha disposição.

    
por Chris H 14.04.2014 / 17:38

1 resposta

1

Bem, acontece que trabalhar com XML em python é bem fácil. O código a seguir recursivamente (para lidar com grupos aninhados) é executado sobre o arquivo. Um hash de cada caminho (alguns dos caminhos são bem longos) é armazenado, a menos que o hash seja uma duplicata, caso em que o nó é marcado para exclusão. Exclusão real acontece antes de voltar para o pai, uma vez que parece quebrar a iteração sobre os filhos, se feito na mosca. A exclusão de objetos preenchidos com branco também está lá.

import xml.etree.ElementTree as ET
import hashlib as hash

def iter_groups(group):
    global hashlist
    global count
    rem=[]
    for child in group:
        if child.tag==rtag+'g' :#we have a group
            iter_groups(child)
        else:
            if child.tag==rtag+'path':
                h=hash.md5(str(child.attrib)).hexdigest()
                print h
                if h in hashlist:
                    rem.append(child)
                    print "removing ",child.tag, "in",group.tag,group.attrib," -- duplicate"
                    count+=1
                else:
                    try:
                        print child.attrib['fill']
                    except KeyError:
                        print 'no fill'
                        #no fill attribute
                    else:
                        if ("rgb(255,255,255)") in child.attrib['fill']:
                            rem.append(child)
                            print "removing ",child.tag, "in",group.tag,group.attrib," -- white"
                        else:
                            hashlist.append(h)
    for r in rem: group.remove(r)

#main#
hashlist=[] 
count=0 
tree = ET.parse('imgtest.svg')
root = tree.getroot()
rtag= root.tag.split('}')[0]+'}'
iter_groups(root)       
tree.write('imgtest_out.svg',encoding="us-ascii", xml_declaration=True, default_namespace="", method="xml")

Problemas:

  • Por algum motivo, todas as tags na saída são prefixadas com "ns0:" - localizar e substituir as correções que
  • Você pode ficar com muitos grupos vazios e ids não referenciados - executando vasculhando o arquivo depois (com --enable-id-stripping ) é uma boa ideia.

Resultados: arquivo inicial: 20,030KB após este código: 8,555KB após a lavagem: 4.545KB isso é praticamente viável no inkscape.

Existem ainda algumas duplicatas visuais produzidas por código marginalmente diferente e ainda alguns grupos funcionalmente vazios.

Editar o código acima tinha vários bugs, não apenas que ele não removesse os objetos brancos. Eu também hackeei algo para lidar com grupos vazios e grupos contendo apenas 1 elemento. É feio, mas aqui está mesmo assim.

import xml.etree.cElementTree as ET
import hashlib as hash
import copy

def get_attr(obj,attr):
    try:
        return obj.attrib[attr]
    except KeyError:
        return None
    else:
        return None

def iter_groups(group):
    global hashlist
    global count
    rem=[]
    for child in group:
        if child.tag==rtag+'g' :#we have a group
            iter_groups(child)
        else:
            if child.tag==rtag+'path':
                h=hash.md5(str(child.attrib)).hexdigest()
                print h
                if h in hashlist:
                    rem.append(child)
                    print "removing ",child.tag, "in",group.tag,group.attrib," -- duplicate"
                    count+=1
                else:   
                    if get_attr(child,'fill')!=None:
                        if ("rgb(255,255,255)") in child.attrib['fill']:
                            print "removing ",get_attr(child,'id'), "in",group.tag,group.attrib," -- white"
                            rem.append(child)
                        else:
                            hashlist.append(h)
    for r in rem: 
        print "about to remove",r.attrib
        group.remove(r)
    rem=[]
    for child in group:
        if child.tag==rtag+'g' :#we have a group
            if len(child.findall('*'))==0:
                print "removing ",child.tag, "in",group.tag,group.attrib," -- empty"
                rem.append(child)
    for r in rem: group.remove(r)


def ungroup_singles(group):
    global count
    for child in group:
        #print child.tag,rtag
        if child.tag==rtag+'g' :#we have a group
            print "len(group",get_attr(child,'id'),")",len(child)
            if len(child)>1:
                ungroup_singles(child)
            else :
                if len(child)==1:
                    if (len(child[0])>=1)or(child[0].tag<>rtag+'g'):
                        print "about to promote",child[0].tag,get_attr(child[0],'id'),get_attr(child[0],'class')
                        print len(child[0])
                        moveelem=copy.deepcopy(child[0])
                        group.append(moveelem)
                        group.remove(child)
                        count+=1
                    else:
                        print "about to remove",child[0].tag,get_attr(child[0],'id'),get_attr(child[0],'class')
                    child.remove(child[0])
                    count+=1
                else:#i.e. len(child)==0
                    print "about to remove",child.tag,get_attr(child,'id'),get_attr(child,'class')
                    group.remove(child)
                    count+=1
        #else:
            # if gl==1:#and not clipped?
                #moveelem= ET.copy.deepcopy(child)

#main#
hashlist=[] 
count=0 
ET.register_namespace("","http://www.w3.org/2000/svg")
tree = ET.parse('imgtest_l.svg')
root = tree.getroot()
rtag= root.tag.split('}')[0]+'}'
iter_groups(root)
print "A", count," elements removed"
lcount=1
while True:
    count=0
    ungroup_singles(root)
    print lcount,":",count," empty groups removed / single elements promoted from groups"
    lcount+=1
    if count==0 or lcount>10:
        break

tree.write('imgtest_out.svg',encoding="us-ascii", xml_declaration=True, default_namespace="", method="xml")
    
por 17.04.2014 / 16:49