I can't make drawing into a buffered bitmap work

[Edit: this seems to have been some kind of build corruption. After upgrading to sdk 4.0.10, I can no longer repro, even when I downgrade to 4.0.9 again]

I've been trying to draw into a BufferedBitmap, and then copy that to the screen, and I was getting very strange results, so I created a new DataField project from the template, and replaced onUpdate with this:

    function onUpdate(dc as Dc) as Void {
        dc.setColor(Graphics.COLOR_BLACK, Graphics.COLOR_WHITE);
        dc.clear();

        var w = dc.getWidth() / 4;
        var h = dc.getHeight() / 4;
        drawAt(
            dc, w, h, false, [
                Graphics.COLOR_BLACK,
                Graphics.COLOR_WHITE
            ] as Array<Graphics.ColorValue>
        );

        drawAt(
            dc, w * 2, h, false, [
                Graphics.COLOR_BLACK,
                Graphics.COLOR_WHITE,
                Graphics.COLOR_RED,
                Graphics.COLOR_BLUE
            ] as Array<Graphics.ColorValue>
        );

        drawAt(
            dc, w * 3, h, false, [
                Graphics.COLOR_RED,
                Graphics.COLOR_BLUE,
                Graphics.COLOR_BLACK,
                Graphics.COLOR_WHITE,
            ] as Array<Graphics.ColorValue>
        );

        drawAt(
            dc, w, h * 2, false, [
                0x000000,
                0x000055,
                0x0000AA,
                0x0000FF,
                0x005500,
                0x005555,
                0x0055AA,
                0x0055FF,
                0x00AA00,
                0x00AA55,
                0x00AAAA,
                0x00AAFF,
                0x00FF00,
                0x00FF55,
                0x00FFAA,
                0x00FFFF,
                0x550000,
                0x550055,
                0x5500AA,
                0x5500FF,
                0x555500,
                0x555555,
                0x5555AA,
                0x5555FF,
                0x55AA00,
                0x55AA55,
                0x55AAAA,
                0x55AAFF,
                0x55FF00,
                0x55FF55,
                0x55FFAA,
                0x55FFFF,
                0xAA0000,
                0xAA0055,
                0xAA00AA,
                0xAA00FF,
                0xAA5500,
                0xAA5555,
                0xAA55AA,
                0xAA55FF,
                0xAAAA00,
                0xAAAA55,
                0xAAAAAA,
                0xAAAAFF,
                0xAAFF00,
                0xAAFF55,
                0xAAFFAA,
                0xAAFFFF,
                0xFF0000,
                0xFF0055,
                0xFF00AA,
                0xFF00FF,
                0xFF5500,
                0xFF5555,
                0xFF55AA,
                0xFF55FF,
                0xFFAA00,
                0xFFAA55,
                0xFFAAAA,
                0xFFAAFF,
                0xFFFF00,
                0xFFFF55,
                0xFFFFAA,
                0xFFFFFF
                ] as Array<Graphics.ColorValue>
        );

        drawAt(
            dc, w * 2, h * 2, false, [
            0xFFFFFF,
            0xAAAAAA,
            0x555555,
            0x000000,
            0xFF0000,
            0xAA0000,
            0xFF5500,
            0xFF00FF,
            0xFFAA00,
            0xAA00FF,
            0x00FF00,
            0x00AA00,
            0x00AAFF,
            0x0000FF
            ] as Array<Graphics.ColorValue>
        );

        drawAt(
            dc, w * 3, h * 2, false, null
        );

        drawAt(
            dc, w, h * 3, true, [
                Graphics.COLOR_BLACK,
                Graphics.COLOR_WHITE
            ] as Array<Graphics.ColorValue>
        );

        drawAt(
            dc, w * 2, h * 3, true, [
                Graphics.COLOR_BLACK,
                Graphics.COLOR_WHITE,
                Graphics.COLOR_RED,
                Graphics.COLOR_BLUE
            ] as Array<Graphics.ColorValue>
        );

        drawAt(
            dc, w * 3, h * 3, true, [
                Graphics.COLOR_RED,
                Graphics.COLOR_BLUE,
                Graphics.COLOR_BLACK,
                Graphics.COLOR_WHITE,
            ] as Array<Graphics.ColorValue>
        );

        // dc.setColor(Graphics.COLOR_BLACK, Graphics.COLOR_YELLOW);
        // dc.drawText(dc.getWidth()/2, dc.getHeight()/2, Graphics.FONT_MEDIUM,
        //            value.toString(), Graphics.TEXT_JUSTIFY_CENTER + Graphics.TEXT_JUSTIFY_VCENTER);
    }

    function drawAt(dc as Dc, x as Number, y as Number, fillRect as Boolean, palette as Array<Graphics.ColorValue>?) {
        x -= 25;
        y -= 25;
        var bitmapOpts = {
            :width => 50,
            :height => 50
        };
        if (palette != null) {
            bitmapOpts[:palette] = palette;
        }
        var bitmap = Graphics has :createBufferedBitmap ?
            Graphics.createBufferedBitmap(bitmapOpts).get() as BufferedBitmap :
            new Graphics.BufferedBitmap(bitmapOpts);

        bitmap.getDc().clearClip();
        bitmap.getDc().setColor(Graphics.COLOR_BLACK, Graphics.COLOR_BLACK);
        bitmap.getDc().clear();
        if (fillRect) { bitmap.getDc().fillRectangle(0, 0, 50, 50); }
        bitmap.getDc().setColor(Graphics.COLOR_WHITE, Graphics.COLOR_WHITE);
        bitmap.getDc().fillEllipse(25, 25, 15, 20);

        dc.drawBitmap(x, y, bitmap);
    }

Basically, drawAt creates a bitmap with an optional palette, clears it to black using dc.clear(), optionally clears it to black again using dc.fillRectangle() (this should be a no-op, but strangely wasn't), then fills an ellipse with white in the center of the bitmap. Then it draws the bitmap centered on the given location.

onUpdate then calls this function 9 times.

The first three (the top row) give 3 different palettes; the first is black and white; the second is black, white, red and blue; the third is red, blue, black and white (but note that in each case, the only colors I draw in are black and white).

The second three (middle row) give the 64 entry fenix5xplus palette (as extracted from Garmin/ConnectIQ/Devices/fenix5xplus/compiler.json), the 14 entry fr230 palette (obtained similarly), and no palette at all.

The third three are the same as the top row, except that in addition to dc.clear(), it also calls dc.fillRectangle() to fill the background with black (this shouldn't change anything).

So we should have nine identical black squares, each with a white ellipse carved out of the center. What I get in the simulator (for fenix5xplus) is this:

The only one that came out correct is the one with the fenix5xplus palette. Even the one with no palette (which is supposed to use the system palette) failed. Also note that I clear the background to black, and then draw in white... there should be no way for other colors to get in there. And where did the stripes come from?

I tried with the venu2 (choosing a watch that doesn't have a system palette), and got:

And this time, the only one thats correct is the one with the fr230 palette.

Finally if I run it on the device I get solid black squares except for the top right and bottom right, which are red, and the center which is white (or maybe transparent).

I see from the discussions that people are using BufferedBitmaps for their apps, so presumably they're not really this broken... what am I doing wrong?

  • ... and I saw that sdk-4.0.10 was out, so I switched to that, and everything started working. But after going back to 4.0.9 again, it continues to work, and I can't reproduce the earlier results. So I'm at a loss.

  • I haven't analyse your code/text thoroughly but

    1. read this

    https://forums.garmin.com/developer/connect-iq/i/bug-reports/graphic-performance

    2. in my opinion use buffered bitmap only if you can't draw it yourself (for example by loading ready to use picture from web/resource). So draw this directly on screen.

    3. the first colour of palette determine colour of bitmap after clear() so if you change order of colours in palette after clear() you will have different background.

    4. If you don't see something it simple means no colour in palette.

  • Thanks for the link. Fortunately, performance isn't an issue here; this is to help draw an IconMenuItem in a Menu2, which will only be used by the DataField's getSettingsView(). Also, I only have to draw the bitmap once, and then use it many times.

    The idea is that I have some code that draws the icon, but I only want the parts inside an elliptical region to be visible. Only drawing those parts is hard (eg how do you fill the top right quadrant of an ellipse with color - since there's no fillArc primitive). There doesn't seem to be a direct way to do that, so instead I draw a white ellipse on a black background into the BufferedBitmap, and then change the palette to have transparent and "background" instead of white and black. Then I do my drawing, and then draw the bitmap over the top, so that the borders overwrite anything that spilled out of the ellipse. So now I can fill a rectangle that covers the top right quadrant of the ellipse, and after stamping the bitmap on top, its just the quadrant that shows.

    Once I upgraded to 4.0.10 sdk, it all started working (both my demo above, and the code in my original project). Since it continued to work after downgrading to 4.0.9 again, I have no idea what was going on...

  • You can

    1. use clip(x,y,w,h) to this region than fill ellipse and only this region will be filled.

    2. You can use dc.setPenWidth(10); and draw ellipse/arc instead of filling

  • 1. doesn't work because I want to fill/clear everything *outside* the ellipse, leaving everything inside it untouched.

    2. I tried that, but its hard to reliably hit exactly the pixels you want, without also hitting pixels you don't want (ones inside the ellipse).

    And the BufferedBitMap approach works perfectly, so...

  • 1. clip and bitmap has rectangular shape so effect has to be the same

    2, you can also use clip before drawing arc/ellipse so the edges will be good.

    and I know about some devices that buffered bitmaps crush (maybe it was already fixed maybe no}. so i have 2 kind of drawing unfortunately. but now, after performance tests I'll resign from buffered at all. wasting battery and memory (and there is no much memory for data fields)