Add on fonts, slow draw speed

Hi,

I'm new to the Arduino and C so please take it easy on me, I have some experience with Delphi(pascal) and VB basic. I have a here a project that uses an ATMega2560 and a mcfriend 3.5 tft. I would like to flash a line of text using a the seven segment font that comes with the mcfriend_kbv library, however there is a very noticeable slow draw with this and pretty much every other scalable font which makes it unacceptable for this application, the standard adafruit gfx fonts are fine. Now I may be just about to answer my own question here but is this because of the size of font? As far as I can tell the gfx are all small fonts and are scaled up to display, taking up less memory but the add on fonts are stored in the size they would be displayed at,if you see what I mean? Also tft.print() does this use drawChar() or is it totally unrelated?

All the font drawing is done by Adafruit_GFX. This plots pixel by pixel with the hardware drawPixel()

Yes, a Uno shield on a Mega2560 is inherently SLOW. (because the data bus maps to random port pins)
Buy a Mega shield if you want to use a Mega2560 and speed is important.
Yes, a larger font has larger glyphs with more pixels.

I could draw Fonts in hardware. This is fast (and easy) when you use a background colour. Transparent printing e.g. FreeFonts is not so easy.

I seldom use a Mega2560. A Uno is much more convenient.

The first optimisation to make is: Only print the digits that change. It is simple to keep a "previous" copy.

David.

Thanks for the reply, It's actually only four characters that I want to flash/blink, looks ok if I use the default font setFont(NULL) and set the size to 6 or seven but the seven segment number font displays a very noticeable redraw, this is just a quick sketch that I've been using to test, even with this it's way to slow. So with my limited knowledge there's nothing I can do except change the display? If this is the case is there one you would recommend? Maybe I should just forget about blinking text and find another way of alerting the operator that the equipment is in edit mode :frowning:

#include <MCUFRIEND_kbv.h>
MCUFRIEND_kbv tft;       // hard-wired for UNO shields anyway.
#include <TouchScreen.h>
#include <FreeDefaultFonts.h>

const int white =  0xFFFF;
const int green =  0x07E0;

int state;
void setup() {
 
 uint16_t id;
 
 tft.reset();
 id = tft.readID();
 tft.begin(id);
 tft.setRotation(1);//landscape
 tft.fillScreen(white);
 tft.setFont(&FreeSevenSegNumFont);
 tft.setCursor(20,20);

}

void loop() {

    if (state==LOW) {state=HIGH;} else {state=LOW;}
    
    if (state==LOW)  {tft.setCursor(20,100);
                      tft.setTextColor(white);
                      tft.print(8888); 
                      delay(300);}
      
    if (state==HIGH)  {tft.setCursor(20,100);
                       tft.setTextColor(green);
                       tft.print(8888); 
                       delay(300);}
      
}

Yes, it looks horrible. Try this. It shows the difference between hardware drawing and drawPixel().
You can copy-paste the penguin[ ] array from the icons.c file in the graphictest_kbv example.

#include <MCUFRIEND_kbv.h>
MCUFRIEND_kbv tft;       // hard-wired for UNO shields anyway.
#include <TouchScreen.h>

#include <FreeDefaultFonts.h>

const int white =  0xFFFF;
const int green =  0x07E0;

#include "penguin.h"    //or copy-paste array from icons.c 

void draw_icon(int x, int y, const uint8_t icon[], int w, int h)
{
    tft.setAddrWindow(x, y, x + w - 1, y + h - 1);
    tft.pushColors(icon, w * h, 1);
}

int state;
void setup() {

    uint16_t id;

    tft.reset();
    id = tft.readID();
    tft.begin(id);
    tft.setRotation(1);//landscape
    tft.fillScreen(white);
    tft.setFont(&FreeSevenSegNumFont);
    tft.setCursor(20, 20);

}

void loop() {

    if (state == LOW) {
        state = HIGH;
    } else {
        state = LOW;
    }

    if (state == LOW)  {
#if 1
        tft.fillRect(20, 60, 160, 40, TFT_BLUE);
#else
        tft.setCursor(20, 60);
        tft.setTextColor(white);
        tft.print(8888);
#endif
        delay(300);
    }

    if (state == HIGH)  {
#if 1
        for (int i = 0; i < 4; i++)
            draw_icon(20 + 40 * i, 60, penguin, 40, 40);
#else
        tft.setCursor(20, 100);
        tft.setTextColor(green);
        tft.print(8888);
#endif
        delay(300);
    }

}

I ran it on a SPI display (on Uno). The SPI is slower than MCUFRIEND_kbv but probably faster than your Mega2560.
If you really like the seven-segment '8' you can make an array like the penguin.

David.

Maybe I should keep just use the Penguins, it would certainly give the operator a laugh! So let's see if I've got this right..the gfx library fonts are all loaded into progmem as hex and then the selected font is "read" to the screen using writepixel? which is used by drawchar() which in turn is used by print()? So the more information that has to be read the slower it will render? I love doing all this kind of stuff but unfortunately it's taking up time that I should be using on the control logic :frowning: Thing is that writepixel seems to be a fast routine( as rendering the default font shows), certainly fast enough to render the default font to the speed I require. I'm going to have a go both ways, use the drawicon routine that you have showed me and a writepixel routine which I can "borrow" from the gfx library and see which one works best, guess that control logic will just have to wait! :slight_smile:

The GFX class provides all the graphics. MCUFRIEND_kbv just does the hardware i.e. the TFT controller.

The hardware can blit a full rectangle very quickly. Just look at fillScreen() or fillRect(). pushColors() works pretty fast too.

For some reason, Adafruit decided that the FreeFonts should only use transparent mode.
This means that you have to send the address of every pixel. Effectively 13 byte transfers for every 2-byte pixel.

Since you have chosen to run a Uno shield on a Mega platform, the extra traffic is compounded by the complex bit mangling required when writing a byte to random port pins on the Mega.

Do the penguins get drawn quick enough for you?
You can draw filled rectangles even faster.
There are many ways to irritate your users. Have you ever visited a Chinese website?
You can use invertDisplay() or swap Red and Blue. Both hardware methods take nanoseconds. Ok, milliseconds on your Mega.

You can create any icon or bitmap that you like on the PC. And there are tools that will create the necessary C array. Both full colour or monochrome.
Did you look at the drawBitmap_kbv example?

David.

Sadly said penguins not up to muster :frowning: the only thing, speed wise that is, is the awful default font! I didn't know when I started this project that the TFT shield was for a UNO :o , I think by "Chosen" you mean "In ignorance" :slight_smile: I bought the mega and shield as one item, I thought I would need a mega purely from a memory point of view as the UNO I originally purchased soon ran out of memory, also there are going to be quite a few ins and outs which the mega has in abundance. In VB and Delphi I used to use off screen drawing for animation and bitblt for bitmaps etc. So this is a bit of a curve. I've not managed to find a Mega 3.5 TFT display yet, they say they are but they look EXACTLY like the one I have(I think a lot of the sellers don't know what they are selling). It hadn't really occurred to me to use bitmaps as I thought I would find a font solution especially as the (after all I've said about it) default font was so close. If the bitmap route works I've then got write a routine to selected the right bitmap for the number, I guess not too hard now I know how it's done, AND don't get me started on irritating websites....

I could send you a class that draws FreeFonts in hardware. I can't believe that anyone would want a green flashing 8888. However fast it was rendered.

Sit down with a nice cup of tea. Think about what you actually want to do. Put some real numbers to it. e.g. draw 128x64 pixels in 20 ms.

Write dow your design requirements.

Someone might offer practical advice.

Engineering is about "making a practical solution with the tools available".

David.

"8888" is just a test number could have been anything, in reality it's anything between 0000 and 9999, a four digit read out. I know exactly what I want and what I want it to do, it needs to blink every 200/600 ms, sorry I thought that was clear. Size wise the font needs to be around 27x46 pixels which is the size of the seven segment num font. Possibly we've had crossed wires, sorry once again.

Ok, I have written you a couple of sketches.

this draws an 8 on the screen. then reads the screen to create a C array on the Serial Terminal

#include <MCUFRIEND_kbv.h>
MCUFRIEND_kbv tft;       // hard-wired for UNO shields anyway.

#include <FreeDefaultFonts.h>

// function reads an area of screen and creates C array.
void write_pixel_map(char *name, int x, int y, int w, int h)
{
    char buf[40];
    uint16_t pixel;
    sprintf(buf, "const uint16_t %s[%d * %d] PROGMEM = {", name, w, h);
    Serial.println(buf);
    int cnt = 0;
    for (int row = 0; row < h; row++) {
        for (int col = 0; col < w; col++) {
            pixel = tft.readPixel(x + col, y + row);
            sprintf(buf, "0x%04X%s", pixel, (++cnt & 7) ? "," : ",\n");
            Serial.print(buf);
        }
    }
    Serial.println("\n};");
}

void setup() 
{

    uint16_t id;

    tft.reset();
    id = tft.readID();
    tft.begin(id);
    tft.setRotation(1); //landscape
    tft.setFont(&FreeSevenSegNumFont);
    // pixel array will be printed on Serial Terminal 
    Serial.begin(9600);
    // just draw a single green "8" on black background
    tft.fillScreen(TFT_BLACK);
    tft.setTextColor(TFT_GREEN);
    int16_t x = 20, y = 56, x1, y1, w, h;
    tft.setCursor(x, y);
    tft.print("8");
    // calculate where the glyph is on screen
    tft.getTextBounds("8", x, y, &x1, &y1, &w, &h);
    // add a 1-pixel border around the glyph
    write_pixel_map("eight", x1 - 1, y1 - 1, w + 2, h + 2);
    // add a new Tab called "eight.h" to your project
    // copy-paste the Serial Terminal to the "eight.h"
}

void loop() 
{
}

and this one uses the "eight.h" file that you made earlier

#include <MCUFRIEND_kbv.h>
MCUFRIEND_kbv tft;       // hard-wired for UNO shields anyway.


#include "eight.h"

void draw_icon(int x, int y, const uint8_t icon[], int w, int h)
{
    tft.setAddrWindow(x, y, x + w - 1, y + h - 1);
    tft.pushColors(icon, w * h, 1);
}

int state;
void setup() 
{
    uint16_t id;
    id = tft.readID();
    tft.begin(id);
    tft.setRotation(1); //landscape
    tft.fillScreen(TFT_WHITE);
}

void loop() {

    if (state == LOW) {
        state = HIGH;
    } else {
        state = LOW;
    }

    if (state == LOW)  {
        tft.fillRect(20, 60, 29 * 4, 48, TFT_BLUE);
        delay(300);
    }

    if (state == HIGH)  {
        uint32_t t = micros();
        for (int i = 0; i < 4; i++) {
            //note that the uint16_t* is cast to uint8_t*
            draw_icon(20 + 29 * i, 60, (const uint8_t*)eight, 29, 48);
        }
        t = micros() - t;
        tft.setCursor(0, 120);
        tft.setTextColor(TFT_BLACK, TFT_WHITE);
        tft.print(0.001 * t);
        tft.print("ms "); 
        delay(300);
    }
}

I am gobsmacked by the result. The program took 4.10 ms to draw the 8888 on an SPI display.
But it took 79.86 ms on a MEGA2560..

Ok, you don't notice much difference because there is a 300 ms delay. You can just see the drawing process on a Mega. 80ms is well within your "spec".
It certainly looks "better" than your original Font version.

The first sketch shows how easy it is to generate a C array with the Arduino.
Of course you normally create the C array on a PC from a JPEG or whatever.

David.

Wow! I really was not expecting that, works a treat There is so much here that I need to figure out, really looking forward to pulling it all apart :slight_smile: Thank you David, it's much appreciated .Once the controller is finished I'll post up some videos of it in action.

Just thought I'd share this, I was halfway through this, based on some of your suggestions, before that last post. This may not be a very nice piece of code and I'm sure I could put all the cordinates in to an array, but it is just a rough idea, but it's surprising quick or a least my perception of it is, I can't detect any left to right rendering that I can with all the other techniques.

#include <MCUFRIEND_kbv.h>
MCUFRIEND_kbv tft;       // hard-wired for UNO shields anyway.


 const int white =  0xFFFF;
 const int mdgrey = 0xEF5D;


int state;
void setup() 
{
    uint16_t id;
    id = tft.readID();
    tft.begin(id);
    tft.setRotation(1); //landscape
    tft.fillScreen(white);
}

void loop() {

 int fc=mdgrey;
 

  if (state == LOW) {state = HIGH;} else {state = LOW;}

  if (state == LOW)  {tft.fillRect(20,20,120,44,white);delay(600);}
       
  if (state == HIGH) {display_82(20,20,fc);
                      display_82(50,20,fc);
                      display_82(80,20,fc);
                      display_82(110,20,fc);
                      display_8(20,20,fc);
                      display_8(50,20,fc);
                      display_8(80,20,fc);
                      display_8(110,20,fc);
                      delay(600);}
}



void display_82(int x,int y,int fc){

    tft.drawLine(x+6,y+39,x+6,y+42,fc);
    tft.drawLine(x+5,y+40,x+5,y+41,fc);
    
    tft.drawLine(x+20,y+39,x+20,y+42,fc);
    tft.drawLine(x+21,y+40,x+21,y+41,fc);
    
    tft.drawLine(x+1,y+5,x+4,y+5,fc);
    tft.drawLine(x+2,y+4,x+3,y+4,fc);
    
    tft.drawLine(x+1,y+19,x+4,y+19,fc);
    tft.drawLine(x+2,y+20,x+3,y+20,fc);
    
    tft.drawLine(x+22,y+5,x+25,y+5,fc);
    tft.drawLine(x+23,y+4,x+24,y+4,fc);
    
    tft.drawLine(x+22,y+19,x+25,y+19,fc);
    tft.drawLine(x+22,y+20,x+24,y+20,fc);
    
    tft.drawLine(x+1,y+24,x+4,y+24,fc);
    tft.drawLine(x+2,y+23,x+3,y+23,fc);
    
    tft.drawLine(x+1,y+38,x+4,y+38,fc);
    tft.drawLine(x+2,y+39,x+3,y+39,fc);
    
    tft.drawLine(x+22,y+24,x+25,y+24,fc);
    tft.drawLine(x+23,y+23,x+24,y+23,fc);
    
    tft.drawLine(x+22,y+38,x+25,y+38,fc);
    tft.drawLine(x+23,y+39,x+24,y+39,fc);
    
    tft.drawLine(x+6,y+1,x+6,y+4,fc);
    tft.drawLine(x+5,y+2,x+5,y+3,fc);
    
    tft.drawLine(x+20,y+1,x+20,y+4,fc);
    tft.drawLine(x+21,y+2,x+21,y+3,fc);
    
    tft.drawLine(x+6,y+20,x+6,y+23,fc);
    tft.drawLine(x+5,y+21,x+5,y+22,fc);
    
    tft.drawLine(x+20,y+20,x+20,y+23,fc);
    tft.drawLine(x+21,y+21,x+21,y+22,fc);

}

void display_8(int x,int y,int fc){
 
 //Top left
    tft.fillRect(x, y+6, 6, 13, fc);

 //Top right
    tft.fillRect(x+21, y+6, 6, 13, fc);

 //Bottom left
    tft.fillRect(x, y+25, 6, 13, fc);

 //Bottom right
    tft.fillRect(x+21,y+25, 6, 13, fc);

 //Horizontal Top
    tft.fillRect(x+7, y, 13, 6, fc);

 //Horizontal Middle
    tft.fillRect(x+7, y+19, 13, 6, fc);

 //Horizontal bottom
    tft.fillRect(x+7, y+38, 13, 6, fc);
}

The end of the segments are drawn first seems you don't notice them being drawn, if you try and draw the segment ends at the same time as the segment body it produces a nasty left to right render. I had to use drawline instead of filltriangle because they are not actually triangles

Yes, you can make it display faster if you only draw the specific segments.
Your example has two different shapes of segment i.e. horiz and vertical. a, d, g are horiz. b, c. e, f are vertical.

You just need two functions. Then draw them as required.

Calculating triangles is quite computational. It is probably easier to draw individual lines. Which seems to be exactly what you have done. Except that you have chosen non-intuitive names for the functions.

I added the "timing". And changed the colour so that a human could see the screen.

#include <MCUFRIEND_kbv.h>
MCUFRIEND_kbv tft;       // hard-wired for UNO shields anyway.


const int white =  0xFFFF;
const int mdgrey = 0xEF5D;


int state;
void setup()
{
    uint16_t id;
    id = tft.readID();
    tft.begin(id);
    tft.setRotation(1); //landscape
    tft.fillScreen(white);
}

void loop() {

    int fc = TFT_RED; //mdgrey;


    if (state == LOW) {
        state = HIGH;
    } else {
        state = LOW;
    }

    if (state == LOW)  {
        tft.fillRect(20, 20, 120, 44, white);
        delay(600);
    }

    if (state == HIGH) {
        uint32_t t = micros();
        display_82(20, 20, fc);
        display_82(50, 20, fc);
        display_82(80, 20, fc);
        display_82(110, 20, fc);
        display_8(20, 20, fc);
        display_8(50, 20, fc);
        display_8(80, 20, fc);
        display_8(110, 20, fc);
        t = micros() - t;
        tft.setCursor(0, 120);
        tft.setTextColor(TFT_BLACK, TFT_WHITE);
        tft.print(0.001 * t);
        tft.print("ms "); 
        delay(600);
    }
}



void display_82(int x, int y, int fc) {

    tft.drawLine(x + 6, y + 39, x + 6, y + 42, fc);
    tft.drawLine(x + 5, y + 40, x + 5, y + 41, fc);

    tft.drawLine(x + 20, y + 39, x + 20, y + 42, fc);
    tft.drawLine(x + 21, y + 40, x + 21, y + 41, fc);

    tft.drawLine(x + 1, y + 5, x + 4, y + 5, fc);
    tft.drawLine(x + 2, y + 4, x + 3, y + 4, fc);

    tft.drawLine(x + 1, y + 19, x + 4, y + 19, fc);
    tft.drawLine(x + 2, y + 20, x + 3, y + 20, fc);

    tft.drawLine(x + 22, y + 5, x + 25, y + 5, fc);
    tft.drawLine(x + 23, y + 4, x + 24, y + 4, fc);

    tft.drawLine(x + 22, y + 19, x + 25, y + 19, fc);
    tft.drawLine(x + 22, y + 20, x + 24, y + 20, fc);

    tft.drawLine(x + 1, y + 24, x + 4, y + 24, fc);
    tft.drawLine(x + 2, y + 23, x + 3, y + 23, fc);

    tft.drawLine(x + 1, y + 38, x + 4, y + 38, fc);
    tft.drawLine(x + 2, y + 39, x + 3, y + 39, fc);

    tft.drawLine(x + 22, y + 24, x + 25, y + 24, fc);
    tft.drawLine(x + 23, y + 23, x + 24, y + 23, fc);

    tft.drawLine(x + 22, y + 38, x + 25, y + 38, fc);
    tft.drawLine(x + 23, y + 39, x + 24, y + 39, fc);

    tft.drawLine(x + 6, y + 1, x + 6, y + 4, fc);
    tft.drawLine(x + 5, y + 2, x + 5, y + 3, fc);

    tft.drawLine(x + 20, y + 1, x + 20, y + 4, fc);
    tft.drawLine(x + 21, y + 2, x + 21, y + 3, fc);

    tft.drawLine(x + 6, y + 20, x + 6, y + 23, fc);
    tft.drawLine(x + 5, y + 21, x + 5, y + 22, fc);

    tft.drawLine(x + 20, y + 20, x + 20, y + 23, fc);
    tft.drawLine(x + 21, y + 21, x + 21, y + 22, fc);

}

void display_8(int x, int y, int fc) {

    //Top left
    tft.fillRect(x, y + 6, 6, 13, fc);

    //Top right
    tft.fillRect(x + 21, y + 6, 6, 13, fc);

    //Bottom left
    tft.fillRect(x, y + 25, 6, 13, fc);

    //Bottom right
    tft.fillRect(x + 21, y + 25, 6, 13, fc);

    //Horizontal Top
    tft.fillRect(x + 7, y, 13, 6, fc);

    //Horizontal Middle
    tft.fillRect(x + 7, y + 19, 13, 6, fc);

    //Horizontal bottom
    tft.fillRect(x + 7, y + 38, 13, 6, fc);
}

I was amazed to see that there was not much improvement in the time. 69.77ms on a Mega using the current Beta library.

David.

Edit. I was using a SSD1297 which is inherently slow.
If I use an ILI9341 I get 77.40ms for the bitmap and 26.54ms for your draw functions.

I got 63ms for the draw and, this is very odd, 7ms for the bitmap?, yet the draw routine displays a better redraw, incidentally originally my draw sketch was written like this

#include <MCUFRIEND_kbv.h>
MCUFRIEND_kbv tft;       // hard-wired for UNO shields anyway.


const int white =  0xFFFF;
const int mdgrey = 0xEF5D;


int state;
void setup()
{
    uint16_t id;
    id = tft.readID();
    tft.begin(id);
    tft.setRotation(1); //landscape
    tft.fillScreen(white);
}

void loop() {

    int fc = TFT_RED; //mdgrey;


    if (state == LOW) {
        state = HIGH;
    } else {
        state = LOW;
    }

    if (state == LOW)  {
        tft.fillRect(20, 20, 120, 44, white);
        delay(600);
    }

    if (state == HIGH) {
        uint32_t t = micros();
        display_8(20, 20, fc);
        display_8(50, 20, fc);
        display_8(80, 20, fc);
        display_8(110, 20, fc);
        t = micros() - t;
        tft.setCursor(0, 120);
        tft.setTextColor(TFT_BLACK, TFT_WHITE);
        tft.print(0.001 * t);
        tft.print("ms "); 
        delay(600);
    }
}





void display_8(int x, int y, int fc) {

    //Top left
    tft.fillRect(x, y + 6, 6, 13, fc);
    tft.drawLine(x + 1, y + 5, x + 4, y + 5, fc);
    tft.drawLine(x + 2, y + 4, x + 3, y + 4, fc);
    tft.drawLine(x + 1, y + 19, x + 4, y + 19, fc);
    tft.drawLine(x + 2, y + 20, x + 3, y + 20, fc);

    //Top right
    tft.fillRect(x + 21, y + 6, 6, 13, fc);
    tft.drawLine(x + 6, y + 39, x + 6, y + 42, fc);
    tft.drawLine(x + 5, y + 40, x + 5, y + 41, fc);
    tft.drawLine(x + 20, y + 39, x + 20, y + 42, fc);
    tft.drawLine(x + 21, y + 40, x + 21, y + 41, fc);

    //Bottom left
    tft.fillRect(x, y + 25, 6, 13, fc);
    tft.drawLine(x + 22, y + 5, x + 25, y + 5, fc);
    tft.drawLine(x + 23, y + 4, x + 24, y + 4, fc);
    tft.drawLine(x + 22, y + 19, x + 25, y + 19, fc);
    tft.drawLine(x + 22, y + 20, x + 24, y + 20, fc);


    //Bottom right
    tft.fillRect(x + 21, y + 25, 6, 13, fc);
    tft.drawLine(x + 1, y + 24, x + 4, y + 24, fc);
    tft.drawLine(x + 2, y + 23, x + 3, y + 23, fc);
    tft.drawLine(x + 1, y + 38, x + 4, y + 38, fc);
    tft.drawLine(x + 2, y + 39, x + 3, y + 39, fc);

    //Horizontal Top
    tft.fillRect(x + 7, y, 13, 6, fc);
    tft.drawLine(x + 22, y + 24, x + 25, y + 24, fc);
    tft.drawLine(x + 23, y + 23, x + 24, y + 23, fc);
    tft.drawLine(x + 22, y + 38, x + 25, y + 38, fc);
    tft.drawLine(x + 23, y + 39, x + 24, y + 39, fc);

    //Horizontal Middle
    tft.fillRect(x + 7, y + 19, 13, 6, fc);
    tft.drawLine(x + 6, y + 1, x + 6, y + 4, fc);
    tft.drawLine(x + 5, y + 2, x + 5, y + 3, fc);
    tft.drawLine(x + 20, y + 1, x + 20, y + 4, fc);
    tft.drawLine(x + 21, y + 2, x + 21, y + 3, fc);

    //Horizontal bottom
    tft.fillRect(x + 7, y + 38, 13, 6, fc);
    tft.drawLine(x + 6, y + 20, x + 6, y + 23, fc);
    tft.drawLine(x + 5, y + 21, x + 5, y + 22, fc);
    tft.drawLine(x + 20, y + 20, x + 20, y + 23, fc);
    tft.drawLine(x + 21, y + 21, x + 21, y + 22, fc);
}

so the fill rect and draw lines were in the same function but this produces a noticeable left to right redraw(can be seen on my setup but maybe not on yours) still takes the same amount of time but looks a lot worse. I rather like my colour scheme, looks like the old digital watch lcd panels.

The actual colours are cosmetic. I found it easier to see distinct colours for test purposes.

Yes, it is faster to draw the specific shapes than to blit the whole bitmap.
I would have thought that anything from 1.6ms to 70ms would be acceptable. (I can get 1.6ms with an overclocked Xmega)

Simple tricks like drawing the rectangles before tidying up with the triangles can make it look more attractive.
Engineering is making the best use of the components you have.

David.