rodekruis / sims_maps_qgis_plugin

QGIS plugin for creating Red Cross Red Crescent maps
GNU General Public License v2.0
6 stars 4 forks source link

Store layer/map layout resources as base64 strings in project variables #38

Closed arongergely closed 3 years ago

arongergely commented 4 years ago

Raymond came across this possibility used for svg images: https://gis.stackexchange.com/questions/374223/using-embedded-svg-symbol-in-multiple-layers/374238#374238

This could allow us to embed map layout resources to the QGIS project file. Then project files would not depend on the SIMS plugin's resources anymore e.g. a QGIS user without the SIMS plugin could open the project file and all resources would be present on the map layout (no missing logo, etc).

Need to test whether this works for JPG, PNG, SVG logos. Base64 strings may be too long for the raster formats, variable max length may not be long enough?

arongergely commented 4 years ago

Tested the above idea: I encoded an SVG logo to base64 and added it to a layout variable. In the layout canvas I added an image layout item and set the variable as image source. The layout item expects binary and can not read base64, so I wrapped the variable in the from_base64() expression.

On QGIS latest release (3.14.16) this works but with limitations:

On QGIS LTR (3.10.x) this does not work, the from_base64() expression is not available there. May be possible to encode to binary directly from the SVG string without a custom expression function, but have to find this out.

arongergely commented 3 years ago

In QGIS 3.16 it is possible to embed SVG images in the layout editor (for the layout 'picture' item) via the GUI. it works via base64 encoded strings behind the scenes. But it is not stored as a project variable (presumably no constraint on the length of the string, which was the showstopper of https://github.com/rodekruis/sims_maps_qgis_plugin/issues/38#issuecomment-694801170)

Made below script to test the idea, works fine on 3.16. With large SVGs as well. This may be the right solution.

Note to self:

# QGIS script to add a base64 encoded svg image to a print layout
import base64 

# get the layout manager of the current project
project = QgsProject.instance()
manager = project.layoutManager()
layoutName = 'svg_layout'

# remove pre-existing layout with same name
for l in manager.printLayouts():
    if layoutName == l.name():
        manager.removeLayout(l)

# create layout and add it to the project
myLayout = QgsPrintLayout(project)
myLayout.initializeDefaults()
myLayout.setName(layoutName)
manager.addLayout(myLayout)

# add a picture item to the layout
picture = QgsLayoutItemPicture(myLayout)
picture.attemptResize(QgsLayoutSize(100, 100)) # default units are millimeters
myLayout.addLayoutItem(picture)

# read in SVG file and create a base64 encoded string representation
# NOTE: qgis needs the 'base64:' prefix to recognize it.
imagePath = '/path/to/qgis-icon128.svg'
with open(imagePath, 'rb') as svgfile:
    svg = svgfile.read()

svg_base64 = 'base64:' + base64.b64encode(svg).decode('utf-8')

# set the base64 encoded string as picture path
picture.setPicturePath(svg_base64, QgsLayoutItemPicture.FormatSVG)

# show the filepath of the svg. it shall indeed be the base64 string.
# print(picture.evaluatedPath())

# write an uncompressed project file. The base64 string should be embedded
project.write('/path/to/outproj.qgs')