Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Embed images into svg output #26

Open
zjfroot opened this issue Feb 18, 2020 · 10 comments
Open

Embed images into svg output #26

zjfroot opened this issue Feb 18, 2020 · 10 comments
Labels
bug Something isn't working kind/feature New feature or request

Comments

@zjfroot
Copy link

zjfroot commented Feb 18, 2020

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
Copy link
Owner

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

Same issue: #8

@mingrammer mingrammer added bug Something isn't working kind/feature New feature or request labels Feb 18, 2020
@sharepointoscar
Copy link

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
Copy link

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
Copy link

FFengIll commented Aug 13, 2020

Could an extra process become an alternative solution?

  • generate the svg as before
  • add an inline argument e.g. Diagram(inline=True)
    • if inline, we do an extra process on svg file which will do a embed process for all image resource.

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

@FFengIll
Copy link

FFengIll commented Aug 13, 2020

@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
Copy link

Is there any updates on this issue?

@dpar39
Copy link

dpar39 commented Dec 20, 2022

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
Copy link

couling commented Jan 4, 2023

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:

  • Makes a list of all required images
  • Copies those images to a resources directory close to the output file
  • Changes the absolute paths in the SVG to relative paths of the new resources directory.

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
Copy link

radupotop commented Mar 13, 2023

@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]

@sieldev
Copy link

sieldev commented Nov 8, 2024

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.

Thanks for that @dpar39 it was helpful.
However, this code generates broken images as well due to mimetypes.guess_type() that grabs 2 entries: image/png and None inserted in data attribute.

mime_type = mimetypes.guess_type(resource_file)

Need to be replaced by:

mime_type = [mime for mime in mimetypes.guess_type(resource_file) if mime != None])

In order to make a healthy svg file.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
bug Something isn't working kind/feature New feature or request
Projects
None yet
Development

No branches or pull requests

9 participants