(Solved) BufferedBitmap: Minimal working example? (SDK: 4.0.10)

Can anyone post a minimal working example of using a BufferedBitmap for off-screen drawing that works under SDK 4.0.10 (the most recent version?)

I have searched this forum for every mention of BufferedBitmap and BufferedBitmapReference. There are people who claim to have gotten a version that works (~10 months ago), but when I try any of the posted solutions today I still can't get anything to run without errors. I don't know if I'm missing something, or if Garmin is still messing with the SDK in minor updates.

Note: The SDK documentation for version 4.0 is still wrong: It claims that BufferedBitmap has a .getDc() method, which it no longer does. And that is the whole problem, I am unable to draw to a buffered bitmap without a Dc.

Many thanks!

import Toybox.Graphics;

//************************************************************
// Attempt #1:
function onUpdate(dc as Dc) as Void {
    var buffer = Graphics.createBufferedBitmap({:width => dc.getWidth, :height => dc.getHeight});
    buffer.setColor(Graphics.COLOR_BLACK, Graphics.COLOR_BLACK);
    buffer.clear();
}
// Error: Symbol Not Found Error
// Details: Could not find symbol 'setColor'

//************************************************************
// Attempt #2:
function onUpdate(dc as Dc) as Void {
    var buffer = Graphics.createBufferedBitmap({:width => dc.getWidth, :height => dc.getHeight});
    buffer = buffer.get();
    buffer.setColor(Graphics.COLOR_BLACK, Graphics.COLOR_BLACK);
    buffer.clear();
}
// Error: Unhandled Exception
// Exception: UnexpectedTypeException: Expected Number/Float/Boolean/Long/Double, given Method

  • Have you looked at the Analog sample in the SDK?

    createBuffered bitmap is only for devices with CIQ 4.x.  What target are you using?

  • Thanks for the suggestion, but the Analog example is definitely broken w/r/t SDK 4.x.  I am trying to build for an Epix Gen 2.

    The Analog example is (essentially) this:

    //************************************************************
    // Attempt #3:
    function onUpdate(dc as Dc) as Void {
        var buffer = new Graphics.BufferedBitmap({:width=>dc.getWidth(), :height=>dc.getHeight()});
        buffer = buffer.getDc();
        buffer.setColor(Graphics.COLOR_BLACK, Graphics.COLOR_BLACK);
        buffer.clear();
    }
    // Error: Symbol Not Found Error
    // Details: Failed invoking <symbol>
    // (Error occurs on line 4, but ignoring that it would break on line 5 also: getDc() does not exist!

  • line 4 should use creaetBufferedBitmap() on 4.x devices and then you want

    buffer=buffer.get();

    Without that you have a graphics pool reference to the buffered bitmap and not the buffered bitmap.

  • That's Attempt #2 above... doesn't work Slight frown

    Error: Unhandled Exception
    Exception: UnexpectedTypeException: Expected Number/Float/Boolean/Long/Double, given Method

    The error is stemming from buffer = buffer.get();

    SDK 4.0.10, build target is Epix Gen2.

  • Ugh, there was a stupid mistake in my code. In the original version it reads

    :width => dc.getWidth, :height => dc.getHeight

    It should be:

    :width => dc.getWidth(), :height => dc.getHeight()

    From there things work as you suggested. Thanks so much!

  • Yes, but isn't get() in this context a method of a weak reference?

    We should be able to check that it is stillAllve() as we don't control when the referenced object no longer exists in memory but buffer does not have that method and so crashes with symbol not found if you try to use this. 

    If createBufferedBitmap returns something that is not quite a weak reference lacking stillAlive() I assume that it is guaranteed to be valid? It would be useful to document this.

    Last point, shouldn't we check the value returned by createBufferedBitmap is not null.

    It all looks rather ragged to me, both the implementation and lack of documentation but what do I know?

    It would be helpful if Garmin emailed developers with any updates to the API.

    As for targeting particular versions, for me and as a single handed developer it would be inconvenient and labour intensive to maintain multiple versions of code to target differences in the API for the different x.x devices. I target as many as I can. I am not aware of any conditional compilation in Monkey C though I have seen workarounds (which consume memory).

    I have got this working now (though without testing the validity of the weak reference, if that is indeed the model).

    I now have to find out what 4.x has done to location (seems to vanish 15 minutes after an activity using gps) and some other strangeness that has been introduced that I have yet to investigate.

    I still find Garmin's whole approach to CIQ developers rather contemptuous and unsatisfactory. Most are independent, don't monetise and add value to Garmin products. They should be treated with more respect.

  • Keep an eye on "New and Announcements"

    Here for example is a whole post about graphics on CIQ 4 devices:

    https://forums.garmin.com/developer/connect-iq/b/news-announcements/posts/a-whole-new-world-of-graphics-with-connect-iq-4

    There are others, like for System 5 devices.

    The get() here has to to with the Graphics pool.  With the graphics pool, some things are loaded there vs in your app's memory, and what your app sees is a reference to the object in the graphics pool instead of the object 

  • Thanks for your code above. For those who are going to try and do this moving forward, I thought I would do a simple copy and paste between the old analog code and the new. Everything else stays the same.

    Old Code:

    offscreenBuffer = new Graphics.BufferedBitmap({
    :width=>dc.getWidth(),
    :height=>dc.getHeight(),
    :palette=> [
    Graphics.COLOR_DK_GRAY,
    Graphics.COLOR_LT_GRAY,
    Graphics.COLOR_BLACK,
    Graphics.COLOR_WHITE
    ]
    });

    // Allocate a buffer tall enough to draw the date into the full width of the
    // screen. This buffer is also used for blanking the second hand. This full
    // color buffer is needed because anti-aliased fonts cannot be drawn into
    // a buffer with a reduced color palette

    dateBuffer = new Graphics.BufferedBitmap({
    :width=>dc.getWidth(),
    :height=>Graphics.getFontHeight(Graphics.FONT_MEDIUM)
    });

    New Code:

                offscreenBuffer = Graphics.createBufferedBitmap({

                    :width=>dc.getWidth(),
                    :height=>dc.getHeight(),
                    :palette=> [
                        Graphics.COLOR_DK_GRAY,
                        Graphics.COLOR_LT_GRAY,
                        Graphics.COLOR_BLACK,
                        Graphics.COLOR_WHITE
                    ]
                });
                offscreenBuffer=offscreenBuffer.get();

                // Allocate a buffer tall enough to draw the date into the full width of the
                // screen. This buffer is also used for blanking the second hand. This full
                // color buffer is needed because anti-aliased fonts cannot be drawn into
                // a buffer with a reduced color palette
                dateBuffer = Graphics.createBufferedBitmap({
                    :width=>dc.getWidth(),
                    :height=>Graphics.getFontHeight(Graphics.FONT_MEDIUM)
                });
                dateBuffer=dateBuffer.get();
  • One other thing: It seems with using the new createBufferedBitmap call, the native text graphics are lacking pixels... I had to use resource fonts to get numbers that were readable... see the below picture. The numbers in the upper half that are loaded into the buffer are from fonts I created. The numbers in the lower half of the WF [calories, active minutes and heart rate] are from internal graphics (i.e. 

    dc.drawText(x-100, y+28 , Graphics.FONT_XTINY, calories, Graphics.TEXT_JUSTIFY_CENTER);

    And seem to be missing pixels...any thoughts?