Creating a custom SVG masked RGB LED

Does anyone off hand have a reference for an LED widget (ideally RGB) where part of the widget is completely opaque (as defined by an SVG e.g. above). Also let’s assume the opaqing SVG is complex enough to make reproducing with custom nvg commands impossible. I’ve needed to do this in a few different contexts previously and always struggled. As a hardware example of the sort of thing I’m talking about see Caster & Pollux (below) (this is not actually what I’m working on just an example :sweat_smile:). What I’ve attempted is creating an SVG with transparency where I want light to pass like so:

image

I’m aware of these threads but am struggling to make anything work. Make a SvgSwitch widget glow in the dark and How to use custom lights - #16 by Ahornberg.

For example the following code produces the results below:


 struct MyLED : TSvgLight<RedGreenBlueLight> {
    MyLED() {
        this->setSvg(Svg::load(asset::plugin(pluginInstance, "res/components/led.svg")));
    }
    
    void drawLayer(const DrawArgs& args, int layer) override {

        if (layer == 1) {
            std::shared_ptr<window::Svg> svg = sw->svg;
            if (!svg) return;
            rack::window::svgDraw(args.vg, svg->handle);            
        }

        LightWidget::drawLayer(args, layer);
    }
    
};

Left: On (green), Right: Off

image

Is it to do with how I contruct the masking SVG? Is it to do with the blending mode somehow? Any help/code references greatly appreciated. :pray:

Have you tried to call LightWidget::drawLayer before drawing your SVG file to place it on top of the light?

    void drawLayer(const DrawArgs& args, int layer) override {

        LightWidget::drawLayer(args, layer);

        if (layer == 1) {
            std::shared_ptr<window::Svg> svg = sw->svg;
            if (!svg) return;
            rack::window::svgDraw(args.vg, svg->handle);            
        }
        
    }

I did and have a similar problem with that approach (although star appears lighter not darker):

If the LED has two states, the way I obtain similar results to what I think you want is by switching frames. (Hydra and Gegenees).

If you want RGB, the way I did it for my Dungeon module (that has a massive multicolored and gradiented moon that changes color according to the held voltage) and the way I think Instruo did it for their S&H module is to draw the SVG yourself. You can use gradients to make it prettier and you can even halo it, if you desire and you can use any and all rgb colors you want. It may not be the best approach but it works for me. I guess another approach would be this:

  • The light is composed of two layers: a background and a foreground.

  • The background is the “off” or dark part (and is drawn on the regular draw call).

  • The foreground is an svg without the parts you want to be dark, so the background shows and is, in effect not glowing, the foreground is drawn in drawLayer.

So, the mask is inverted: you draw what you want lit and use that to mask what you want unlit in the background. This approach can be haloed as well.

If the first two approaches make sense, you can look here at SanguineLightUpSwitch and SanguineMultiColoredShapedLight. SanguineShapedLight kind of cheats like the third approach above: the “off” state is part of the faceplate.

SanguineLightUpSwitch is used quite extensively in the modules in that collection, for example superswitch18.cpp. The big light up moon is in use in dungeon.cpp.

I hope some of this helps.

For reference: how it looks

For testing purpose: May you share your SVG file here?

Sure, thanks for taking a look. I just threw together something arbitrary to illustrate the point.

led

1 Like

Amazing thanks for this, I’ll take a look and assuming I can generalise to my case I’ll report back with results :slight_smile:

No problem, I hope it helps :slight_smile:

So if I rip out svgDraw from Rack/src/window/Svg.cpp, and replace the part that fills the SVG with the SVG defined color with a fill to use the Light object’s color, then it works! In the following, mySvgDraw is a modified version of Rack/src/window/Svg.cpp svgDraw.

struct MyLED : TSvgLight<RedGreenBlueLight> {

	MyLED() {
		this->setSvg(Svg::load(asset::plugin(pluginInstance, "res/components/led_inv.svg")));

	}

	void draw(const DrawArgs& args) override {}
	void drawLayer(const DrawArgs& args, int layer) override {
		if (layer == 1) {
			//From SvgWidget::draw()
			if (!sw->svg)
				return;
			if (module && !module->isBypassed()) {

				nvgGlobalCompositeBlendFunc(args.vg, NVG_ONE_MINUS_DST_COLOR, NVG_ONE);
				mySvgDraw(args.vg, sw->svg->handle, color);

				// TODO: optional halo here
			}
		}
		Widget::drawLayer(args, layer);
	}
};

That’s a solid flag in the ground, and great to have something working. However I’m keen to find a more cleaner / more idomatic way to do this so I’ll keep looking and report in this thread if I find it. Thanks all so far. :slight_smile:

I’m glad it’s working, if you find a better way, let us know :slight_smile:

Is the question how to dynamically change the color of an svg in the fly? I didn’t read that closely but I think so.

The old surge modules used to do that. The logo was an svg with a white fill and at draw time we replace the color

There’s the code if that helps. Sorry if I’m off track / in that case just ignore!

1 Like

As I was thinking about this in the shower, I realised modifying SVG colour on the fly would make things much easier and lo and behold when I came back you posted exact code, thanks!

1 Like

That reminded me of Jane’s Addiction’s ‘Standing in the shower, thinking…’ song

For completeness, this is what the final thing looks like (stock halo will be round but that’s fine for my use case). This naturally uses the color as defined for the RGB light (including the alpha channel, which the surge example didn’t need to use). For my use case, I have the background (led off state) on the panel itself though would be easy to generalise to include in the struct itself.

struct MyLed : TSvgLight<RedGreenBlueLight> {

	MyLed() {		
		this->setSvg(Svg::load(asset::plugin(pluginInstance, "res/components/my_led.svg")));		

	}

	void draw(const DrawArgs& args) override {}
	void drawLayer(const DrawArgs& args, int layer) override {
		if (layer == 1) {

			if (!sw->svg)
				return;

			if (module && !module->isBypassed()) {

				for (auto s = sw->svg->handle->shapes; s; s = s->next) {
					s->fill.color = ((int)(color.a * 255) << 24) + (((int)(color.b * 255)) << 16) + (((int)(color.g * 255)) << 8) + (int)(color.r * 255);
					s->fill.type = NSVG_PAINT_COLOR;
				}

				nvgGlobalCompositeBlendFunc(args.vg, NVG_ONE_MINUS_DST_COLOR, NVG_ONE);
				svgDraw(args.vg, sw->svg->handle);
				drawHalo(args);
			}
		}
		Widget::drawLayer(args, layer);
	}
};

Thanks all for the help!

5 Likes