Cabbage Logo
Back to Cabbage Site

Image as base64

Hi, is there a way to embed images data as base64 data into a plugin instead of using .png external files ?

I’m afraid not, but it’s something I could look into. If you work something out I’d be happy to accept a pull request :+1:

Btw @tharos, I think the best approach here might be to allow users to pass a text file with a base64 string in place of a png or jpeg? I guess they could also go into the csd file?

@rorywalsh I thought about something like this

image bounds(30, 190, 23, 23) base64(“iVBORw0KGgAAAX … oXK7hxsDU5CYII=”)

by introducing a new property called for example base64 which would contain the base64 encoded string

I’m using an online service to convert png image to base64 like this one https://www.base64-image.de/

From my tests I implemented the base64 property in cabbage files by declaring all necessary code in various files.

In CabbageImage.cpp I’m trying to load the image with this code but it doesn’t work, even if the string is well loaded from the base64 property (verified in debug mode)

String ImageBase64 = CabbageWidgetData::getStringProp(wData, CabbageIdentifierIds::base64);
if(ImageBase64.isNotEmpty()) {
    MemoryOutputStream out;
    bool result = Base64::convertFromBase64(out, ImageBase64);
    if(result) {
        img = ImageFileFormat::loadFrom(out.getData(), out.getDataSize());
    }
}

PS: thanks for your answer in JUCE forum (I suppose it’s you)
PS2: Sorry if sometimes my english is approximative: not my native language :slight_smile:

I’m pretty sure it’s because a valid image file is not being passed to the widget. If you pass a valid file() to it, it should work. You see this in the paint() method:

   if (imgFile.existsAsFile())
    {
        if (imgFile.hasFileExtension (".svg"))
        {
            CabbageLookAndFeel2::drawFromSVG (g, imgFile, 0, 0, getWidth(), getHeight(), AffineTransform());
        }
        else
        {
            g.drawImage (img, 0, 0, getWidth(), getHeight(), cropx, cropy,
                         cropwidth == 0 ? img.getWidth() : cropwidth,
                         cropheight == 0 ? img.getHeight() : cropheight);
        }
    } 

If imgFile does not exist, it won’t draw img. I wonder we should just permit the file() identifier to take a base64 string? If the files exists, we know it’s an image on disk and it should behave as normal. If File::existsAsFile() returns false we check if it’s a base64 string. We also need to make sure the GUI editor doesn’t wipe out the strings each time a user edits the widgets using the drag and drop editor.

It was I and I’m happy to help. I’ve also no problems whatsoever with your English :+1: I read you loud and clear :wink:

I went ahead and added that. Sorry, I realised afterwards that perhaps you were enjoying implementing this :laughing: Sorry, I may have spoiled your fun! It works fine with JUCE encoded strings, but I’m just going to check now for strings produced by that website you referenced…

[edit] yes it works fine. You just need to remove the header stuff at the start of the string. So

file("data:image/png;base64,iVBORw0KGg....")

becomes:

file("iVBORw0KGg.......")

You can pull from the dev branch now to try out the changes. I’d suggest users only use this when they are ready to publish their plugins. It’s a little unnerving having such long strings in your .csd file :grimacing:

@rorywalsh I really really thank you. In fact my code works like a charm. The problem wasn’t with it. I didn’t see how g.drawImage is called in the paint() method and the various if conditions around it.

So basically, it’s possible to embed directly images in the .csd file in the section instead of using external .png image by using this kind of syntax

image bounds(30, 190, 23, 23) base64(“iVBORw0KGgAAAX … oXK7hxsDU5CYII=”)

Now next step could be to see how to extend this technic to other widgets that use the “file” property to load and display image.

Don’t worry. I’m very pleased to contribute.

Yes. data:image/png;base64, must be removed

I think our posts got crossed there. I think we can use the same methods I just added for other widgets that use the file property too. Please take a look over my code and see if you can spot any problems or potential issues moving forward.

@rorywalsh I checked the modifications you pushed on the dev branch

In CabbageImage.cpp you can not write:

widgetData.addListener (this);

String fileBase64 = CabbageWidgetData::getStringProp(wData, CabbageIdentifierIds::file);
if (fileBase64.isNotEmpty()) {
	MemoryOutputStream out;
	bool result = Base64::convertFromBase64(out, fileBase64);
	if (result) 
	{
		img = ImageFileFormat::loadFrom(out.getData(), out.getDataSize());
	}
	else
	{
		imgFile = File(CabbageWidgetData::getStringProp(wData, CabbageIdentifierIds::csdfile)).getParentDirectory().getChildFile(CabbageWidgetData::getStringProp(wData, CabbageIdentifierIds::file));
		if (File(imgFile).existsAsFile())
			img = ImageFileFormat::loadFrom(imgFile);
	}
}

otherwise img will never be loaded from a file if fileBase64 is empty

Same issue in updateImage() method

But if the file string is empty how can we load an image? If it’s empty we draw a shape?

According to your code, if fileBase64 is empty, imgFile will be never loaded and then a shape will be always drawn even if the property file is filled in the widget

I’ll go with this correction

String fileBase64 = CabbageWidgetData::getStringProp(wData, CabbageIdentifierIds::file);
if (fileBase64.isNotEmpty())
{
	MemoryOutputStream out;
	bool result = Base64::convertFromBase64(out, fileBase64);
	if (result) 
	{
		img = ImageFileFormat::loadFrom(out.getData(), out.getDataSize());
	}
}
else 
{
    imgFile = File(CabbageWidgetData::getStringProp(wData, CabbageIdentifierIds::csdfile)).getParentDirectory().getChildFile(CabbageWidgetData::getStringProp(wData, CabbageIdentifierIds::file));
    if (File(imgFile).existsAsFile())
        img = ImageFileFormat::loadFrom(imgFile);

}

NB: maybe this code could be improved

	imgFile = File(CabbageWidgetData::getStringProp(wData, CabbageIdentifierIds::csdfile)).getParentDirectory().getChildFile(CabbageWidgetData::getStringProp(wData, CabbageIdentifierIds::file));
	if (File(imgFile).existsAsFile())
		img = ImageFileFormat::loadFrom(imgFile);

by replacing it by

String f = CabbageWidgetData::getStringProp(wData, CabbageIdentifierIds::file);
if (f.isNotEmpty()) 
{
    imgFile = File(CabbageWidgetData::getStringProp(wData, CabbageIdentifierIds::csdfile)).getParentDirectory().getChildFile(f);
    if (File(imgFile).existsAsFile())
        img = ImageFileFormat::loadFrom(imgFile);
}

An unnecessary I/O access disk is omit (better performance) if file property is empty or not present in a widget. No need to test if the file exists physically on the disk

I don’t follow the logic here. What use is the else block here if the file string is empty? There is little point in checking if a file exists when the filename we are checking is “”. You see my point?

Good point. I will update the code later. Btw, I just made a review of your PR on github. Maybe it’s best to continue the discussion there? It’s easier to quote code directly :wink:

I just double checked the code I pushed and I don’t see how this is the case. If the file string is not empty we assume it’s either a base64 string, or an image file.

String fileBase64 = CabbageWidgetData::getStringProp(wData, CabbageIdentifierIds::file);
	if (fileBase64.isNotEmpty()) {
		MemoryOutputStream out;
		bool result = Base64::convertFromBase64(out, fileBase64);
		if (result) 
		{
			img = ImageFileFormat::loadFrom(out.getData(), out.getDataSize());
		}
		else
		{
			imgFile = File(CabbageWidgetData::getStringProp(wData, CabbageIdentifierIds::csdfile)).getParentDirectory().getChildFile(CabbageWidgetData::getStringProp(wData, CabbageIdentifierIds::file));
			if (File(imgFile).existsAsFile())
				img = ImageFileFormat::loadFrom(imgFile);
		}
	}

Ok I understood your logic :slight_smile:
I didn’t see that the file property of the widget is used at once in your case to declare the physical file or a base64 string
If the test with convertFromBase64 fail then we assume it’s a file, not a base64 string
To sum up, ignore my pull request. Your approach is better than mine.

Great. Do you think we should support for base64 for all widgets that use the file identifier?

Yes I do. It would be fine for distributing plugin. If it’s possible to reduce external files it will be great

I will look into this. First I need to work out which widgets support files and then start adding support one by one. I guess I should start with the sliders as many people are using custom UIs for those. It would be quite handy if they could embed those resources into their .csd files.