Background Image from DrawContext?

I tried building a background image with a DrawContext for a widget, but it looks like it only sort-of works. Anything I draw on my context comes across as solid black, regardless of my fill/stroke color.

	let backgroundContext = new DrawContext()
	backgroundContext.opaque = false
	backgroundContext.size = new Size(300,300)
	backgroundContext.setFillColor = new Color("#ff0000",0.3)
	backgroundContext.setStrokeColor = new Color("#ff0000",0.3)
	backgroundContext.setLineWidth(5)
	backgroundContext.fillEllipse(new Rect(0,0,75,100))
	backgroundContext.strokeRect(new Rect(20,20,100,100))
	w.backgroundImage = backgroundContext.getImage()

I’m having a similar problem with trying to set a widget background image with an image rendered from a DrawContext.

Here’s the simplified script I’m working with:

let widget = new ListWidget()
widget.setPadding(0, 0, 0, 0);
widget.backgroundColor = new Color("#4a525a")

// Traditional widget text -- works
// let titleTxt = widget.addText('Hello world')
// titleTxt.font = Font.boldSystemFont(24)
// titleTxt.textColor = new Color("#eeeeee")  // almost white
// End test drawing

// Drawing in a graphics context
let dc = new DrawContext();
dc.size = new Size(282, 282);
dc.opaque = true; // true = solid white; false = solid widget background color

dc.setFont(Font.boldSystemFont(24));
dc.setTextColor(new Color('#eeeeee'));
dc.drawText('Hello draw context', new Point(30, 30));

widget.backgroundImage = dc.getImage();
// End DC drawing

// Present widget, or queue for Home screen
if ( ! config.runsInWidget) {
    await widget.presentSmall()
}
Script.setWidget(widget)
Script.complete()

When I set DrawContext.opaque to true, the widget renders solid white. When I set it to false, I get the #4a525a gray that’s set as the widget.backgroundColor. In neither case is the text rendered.

Looking at the DrawContext docs, there’s mention of a call to “finish” the drawing, endDrawing():

When you are done drawing your image, you should call endDrawing().

It’s mentioned in several places in the DrawContext docs. However, there’s no doc for that function, and when I add it to the script, I get an error that Exception Occurred, endDrawing() is not a function, is undefined.

I have scripts from other folks that use DrawContext, and which seem to make it work. But there’s some magic sauce I’m not seeing in their code, and even when I reduce it to limited amounts of drawing, it still works. But I can’t figure out why those work, but my simple code does not.

OK, I’ve figured out that at least part of the problem with the code above is that my DrawContext is sized for a small widget, but I was presenting in Medium format (now fixed, above). That apparently throws the coordinate system off, and the text I was looking for was effectively offscreen.

But it would be nice to have more documentation about how to use DrawContext. There’s no details of what the different “layers” are, across the widget (three different background properties) and DC. There’s no details of the effects of opaque are, in relation to different variations of the widget. There’s no description of how drawing state (colors, etc.) is managed. And so on.

It’s not hard to write a lot of test code to experiment with the behavior, and figure it out by deduction.

But it sure would be nice to just read about it, and see some examples, maybe in a cookbook format.

Truly interesting. Your code works. I wonder why my was working oddly. I guess I should have thrown together a simple example like you have.

The effect of opaque seems pretty straightforward. I would assume that opaque = true is pretty much like filling a white rect, the size of the drawContext. I suppose it just removes the step of setting a fill, and filling a rect.

The widget size issue seems odd though, I tried small, medium, and large and as expected it stretched to fill the area, except for medium, which didn’t work at all, perhaps that’s a bug?

There’s a lot going on with DrawContext in relation to widgets. I left out the complexity of Device.screenSize(), Device.screenResolution(), and Device.screenScale().

screenResolution = screenSize x screenScale, and that means you need to do a lot of scaling math to support more than one device. When I adjusted the size for one of the weather widgets folks are iterating on, the whole chart shrunk down to ¼ size. Blurry, or tiny, or a lot of scaling, those are the choices.

(Or, use the built-in widget layout and drawing features, which limit flexibility in return for being a lot easier to use. I don’t mean to sound like the cranky old man that I actually am. Scriptable is amazing for what it does. But that doesn’t mean perfect…)

Graphics are a big weakness in my programming skillset, so I’m exercising new muscles… But I’m abandoning the effort for this weekend. I do hope to return the next time I have a few hours to concentrate on the problem.

Oh, and as far as opaque goes, yes, in a vacuum, it’s easy enough to understand.

But, to know what effect it will have on the whole rendered widget, you have to know what layers exist, and in what order. You have to figure out how it’ll blend with other layers, if you set transparency somewhere. You can’t set an actual background on the DrawContext (though apparently you used to be able to, based on code in an older post), so what’s the default if opaque=true? (It’s white.) And so on.

My experiments were not helped by the other, unrelated problem where I was drawing my graphics offscreen due to the size problem. So I wasn’t seeing the effects of each change, just seeing multiple problems without understanding how my changes affected them.

Now that I’m past that, the rest should be easy enough to figure out.

Still. It would be nice if there were better docs. The existing docs are fine as reference docs, but they are not useful for learning concepts, which actually comes first in the learning process.

Figured I’d do some testing. All done with a small widget (just tested with a “presentSmall()”, not an actual widget on the screen). Certainly some oddities.

It appears as though the order is backgroundColor at the bottom, then backgroundImage in the middle, then backgroundGradient at the top (which kind of makes sense).

I agree about the learning concepts. Maybe it’s up to the community to put together better examples!

1 Like