Bitmap Transformation

Same again. 6.2.0 introduces new features, but no examples. Everyone has to reinvent the wheel (except senior SW architects around, but I can imagine, also they have difficulties in case of Bitmap Tranformation feature). Bitmap Tranformation seems to be very interesting. Scalable Fonts are only avalable on some devices, so not interesting (only in some years if majority of devices in the field are supporting).

  • I did put together a really simple sample for scalable fonts.  It doesn't do much except draw "ABC" in the center of the display with the height being 40% of the display height.  I'll look at doing something similar for the new stuff with transformations..(Which will only be available on newer devices, like scalable fonts from what I see)

    import Toybox.Graphics;
    import Toybox.WatchUi;
    
    class scaleView extends WatchUi.View {
        var height,width;
        var font;
    
        function initialize() {
            View.initialize();
        }
    
        // Load your resources here
        function onLayout(dc as Dc) as Void {
            height=dc.getHeight();
            width=dc.getWidth();
            font=Graphics.FONT_LARGE;
    
            //Try scaleable font RobotoItalic
            var sz=(height*.4).toNumber();
            var nf=Graphics.getVectorFont({:face=>"RobotoItalic", :size=>sz});
            if(nf!=null) {font=nf;}
        }
    
        // Called when this View is brought to the foreground. Restore
        // the state of this View and prepare it to be shown. This includes
        // loading resources into memory.
        function onShow() as Void {
        }
    
        // Update the view
        function onUpdate(dc as Dc) as Void {
            dc.setColor(Graphics.COLOR_BLACK,Graphics.COLOR_BLACK);
            dc.clear();
            dc.setColor(Graphics.COLOR_WHITE,Graphics.COLOR_BLACK);
        
            dc.drawText(width/2,height/2,font,"ABC",Graphics.TEXT_JUSTIFY_CENTER|Graphics.TEXT_JUSTIFY_VCENTER);
        }
    
        // Called when this View is removed from the screen. Save the
        // state of this View here. This includes freeing resources from
        // memory.
        function onHide() as Void {
        }
    
    }
    

    Sim screenshot with the 265s:

  • I added a few lines of code to get a smaller (and different) scalable font for " drawRadialText() and drawAngledText().  Here's it in the sim for the 965. (I reduced the height of "ABC" a bit)

  • to be honest, but I talked about Bitmap Transformation example, this feature is available to many current devices (Vector fonts I don't care at the moment, because limited to some devices, maybe in some months, if majority of devices in the field is supporting)

  • Looks to me that Bitmap Transformation is also limited to newer devices 'Since Api Level 4.2.2" in the Core Topics doc.

  • With the AffineTransformation stuff, I got to admit I don't even really understand what the basic calls are about.

  • Following API spec:

    - AfineTransform -> 4.2.0 (most of current devices supported) ... but used with drawBitmap2 which is 4.2.1 ??

    - drawBitmap2 -> 4.2.1 (only some latest devices)

    - vector fonts -> 4.2.1 (only some latest devices)

    maybe one of the rare cases where spec is wrong *g* ... anyhow ... thats why I wanted to check on devices ... but no examples ...

  • For what's worth, the announcement says that slightly more devices (now) support Bitmap Translation than Scalable Fonts.

    https://forums.garmin.com/developer/connect-iq/b/news-announcements/posts/new-connect-iq-for-you-in-system-6-2

    I don't know if that will change in the future.

    The announcement also states that DrawBitmap2 is available for API level 4.2.2, while the docs says 4.2.1, so there's already a discrepancy here.

    https://developer.garmin.com/connect-iq/api-docs/Toybox/Graphics/Dc.html#drawBitmap2-instance_function

  • With the AffineTransformation stuff, I got to admit I don't even really understand what the basic calls are about.

    TL;DR it's using matrix math to transform images (e.g. translate, rotate, shear and scale.) You can also apply a custom transformation by setting the transformation matrix values manually. But I don't think you really need to understand the math behind it to use the predefined transforms. I do think the documentation could be improved and examples would be helpful, but what else is new?

    For example, suppose you want to scale a bitmap by 110% in both directions, then rotate it by 90 degrees.

    var transform = new Graphics.AffineTransform();
    transform.scale(1.1, 1.1);
    transform.rotate(Math.PI / 2); // 90 degrees
    dc.drawBitmap2(x, y, bitmap, { :transform => transform });


    This is the math behind it, if you're interested.

    https://people.computing.clemson.edu/~dhouse/courses/401/notes/affines-matrices.pdf

    [https://en.wikipedia.org/wiki/Affine_transformation#Image_transformation]

    Every point (x,y) in a 2D image can be represented as a 3D vector with the 3rd coordinate set to 1:

    | x |
    | y |
    | 1 |

    To apply an affine transformation (*) to an image, each point is mapped to the result of the product of a 3x3 affine transformation matrix and the point's 3D vector, using matrix multiplication.

    (*)

    In Euclidean geometry, an affine transformation or affinity (from the Latin, affinis, "connected with") is a geometric transformation that preserves lines and parallelism, but not necessarily Euclidean distances and angles.

    For our purposes:

    Affine transformation matrix X point = transformed point

    | a1 a2 a3 |   | x |   | a1*x+a2*y+a3*z |
    | b1 b2 b3 | X | y | = | b1*x+b2*y+b3*z |
    | c1 c2 c3 |   | z |   | c1*x+c2*z+c3*z |

    (Note that the result of multiplying a 3x3 matrix with a 3x1 matrix is a 3x1 matrix. i.e. Applying an affine transform matrix to a 3D vector results in another 3D vector.)

    That's the general form of the multiplication (just to illustrate how you'd multiply any old 3x3 and 3x1 matrices), but since we know the 3rd component of each point is 1, and the 3rd row of each transformation matrix is (0 0 1), we actually have:

    | a1 a2 a3 |   | x |   | a1*x+a2*y+a3 |
    | b1 b2 b3 | X | y | = | b1*x+b2*y+b3 |
    | 0  0  1  |   | 1 |   | 1            |


    For example, the identity matrix (returned by AffineTransform.initialize()) is:

    | 1 0 0 |
    | 0 1 0 |
    | 0 0 1 |

    This matrix maps every point to itself. i.e.

    | 1 0 0 |   | x |   | x |
    | 0 1 0 | X | y | = | y |
    | 0 0 1 |   | 1 |   | 1 |

    The other affine transformation matrices implement operations such as scale, shear, translate and rotate, when applied to every point in an image.


    For example, the translation matrix (AffineTransform.setToTranslation()) is:

    | 1 0 tx |
    | 0 1 ty |
    | 0 0 1  |

    tx and ty are the number of pixels to move each point in the x and y directions, respectively.

    Suppose we have an image with the points (1,2) and (3,5), and we want to move each point 5 pixels to the right and 3 pixels up (tx = 5 and ty = -3.)

    That gives us the following affine transformation matrix:

    | 1 0  5 |
    | 0 1 -3 |
    | 0 0  1 |

    Applying the transformation matrix to each point in the image results in a new image translated 5 pixels right and 3 pixels up:

    | 1 0  5 |   | 1 |   | 1*1+0*2+5*1 |   |  6 |
    | 0 1 -3 | X | 2 | = | 0*1+1*2-3*1 | = | -1 |
    | 0 0  1 |   | 1 |   | 0*1+0*2+1*1 |   |  1 |

    (1,2) => (6,-1)

    | 1 0  5 |   | 3 |   | 1*3+0*5+5*1 |   | 8 |
    | 0 1 -3 | X | 5 | = | 0*3+1*5-3*1 | = | 2 |
    | 0 0  1 |   | 1 |   | 0*3+0*5+1*1 |   | 1 |

    (3,5) => (8,2)

    (That's obviously a very simple example and not as interesting as something like rotation or shearing.)


    If you want to do multiple transformations, you would multiply the corresponding matrices together, which is why the AffineTransform methods without set in the name (such as shear(), rotate(), etc.) work by applying (multiplying) the corresponding transform matrix to an existing transform matrix. e.g. If transform T is currently a translation, and you call T.rotate(), T is now a translation followed by a rotation.

    In contrast, AffineTransform.set*() functions (such as setShear(), setRotate(), etc.) replace the current transform with a new transform.

  • Can you  please show the code for rotating font?

  • Ok, you need to use a scalable font, so you need to load one and set it's height.

    font=Graphics.getVectorFont({:face=>"RobotoCondensedRegular", :size=>height*.2};
    Here I useRobotoCondensedRegular, and set it to be 20% of the screen height (height is from dc.getHeight())
    After that, it's just making the proper call to display it
    dc.drawRadialText(width/2, height/2, font, "Battery", Graphics.TEXT_JUSTIFY_CENTER, 0, height/2, Graphics.RADIAL_TEXT_DIRECTION_CLOCKWISE);
    Here I draw "Battery", on the right edge of the screen, in the center.  Check the API doc for what's passed to drawRadialText()
    The code I use in the following is a bit different as I'm checking out different sizes, and need to leave space for the orange arc that is showing the actual battery level.