mingrammer / diagrams

:art: Diagram as Code for prototyping cloud system architectures
https://diagrams.mingrammer.com
MIT License
36.79k stars 2.39k forks source link

Embed images into svg output #26

Open zjfroot opened 4 years ago

zjfroot commented 4 years ago

When choosing svg as the output type, the generated svg file references images to python site-packages installation folder. For example:

<image xlink:href="/Users/adam/.virtualenvs/py3.8/lib/python3.8/site-packages/resources/azure/compute/vm.png".../>

When sharing the generated svg files, the receiver doesn't always have diagrams package installed, or installed at a different location.

Can we make it configurable so that we can embed the images in the generated svg file?

mingrammer commented 4 years ago

Graphviz does not support embedded SVG as default. I'm looking for a solution to this issue.

Same issue: #8

sharepointoscar commented 4 years ago

Yep same thing happened to me. I'll monitor this issue. My idea is that even if I have to move the icons into my site and change the reference path of the icons, that's fine. But I don't see the icons now either in that path /lib/python3.8/site-packages/

sharepointoscar commented 4 years ago

Yep same thing happened to me. I'll monitor this issue. My idea is that even if I have to move the icons into my site and change the reference path of the icons, that's fine. But I don't see the icons now either in that path /lib/python3.8/site-packages/

I ended up moving the resources folder into my website and pointed to that, then all images are shown on the svg diagram.

FFengIll commented 4 years ago

Could an extra process become an alternative solution?

in svg xlink:href="data:image/png;base64, with base64 code can work well, and we can process svg as xml.

FFengIll commented 4 years ago

@mingrammer if nobody WIP and the solution is accepted, I will try to PR in 1~2 weeks.

Here is a demo code. But I imported lxml to support some svg process (like xml).

qvistgaard commented 3 years ago

Is there any updates on this issue?

dpar39 commented 1 year ago

I wrote this script until this feature is supported:

import base64
import os
import sys
import mimetypes
from xml.dom import minidom

def embed_images(svg_file, svg_file_out=None):
    doc = minidom.parse(svg_file)
    images = doc.getElementsByTagName("image")
    for img in images:
        if img.hasAttribute("xlink:href"):
            resource_file = img.getAttribute("xlink:href")
            if os.path.isfile(resource_file):
                mime_type = mimetypes.guess_type(resource_file)
                with open(resource_file, "rb") as image_file:
                    encoded = base64.b64encode(image_file.read()).decode()
                    attr = f"data:{mime_type};base64," + encoded
                    img.setAttribute("xlink:href", attr)
    if not svg_file_out:
        p, ext = os.path.splitext(svg_file)
        svg_file_out = p + "_out" + ext
    with open(svg_file_out, "w") as f:
        f.write(doc.toxml())

if __name__ == "__main__":
    svg_file = sys.argv[1] if len(sys.argv) == 2 else "my_diagram.svg"
    embed_images(svg_file) # outputs my_diagram_out.svg with base64 encoded data URLs for the images

Could be improved by making sure that the same image is never embedded more than once.

couling commented 1 year ago

I'm wondering about the best (most acceptable) way to structure this.

The post processing idea is not bad, but embedding is not necessarily ideal in all circumstances. Based of the code dpar39 suggested, another solution might be a post processing script that:

The advantage to this approach is that it doesn't bloat the SVGs. Running the same post-processing script on many SVGs will result on only a single copy of each used image. So the result would be both portable and as slim as possible.

N.B.: I'm investigating this because I want to embed diagrams in a MKDocs site and want to autogenerate diagrams from python using mkdocs-gen-files. I can generate the SVGs this way but end up with missing images, as others have discovered.

radupotop commented 1 year ago

@dpar39 That works very well, except that guess_type returns a tuple. So that line should be:

            mime_type = mimetypes.guess_type(resource_file)[0]