using tft.pushColor to display PROGMEM rgb565 bitmaps

Yes, it will just be the 16-bit pixel values. There is no way to store the geometry.
A common convention is to mangle the filename e.g. mpg_24x25. Or place all the 32x32 images in a raw32x32 directory and all the 18x27 images in a raw18x27 directory.

With a BMP, this information is stored in the header.

Personally, I would do the development with regular BMP files that you have produced on the PC. Render with a regular BMP algorithm.
When you have the design settled, you just need to convert to RAW images and change to the faster rendering function.

Incidentally, how did you do the anti-aliasing?

David.

@carguy

I've had some time this evening to look at your library in post #48 of this thread again. I can see multiple hurdles to overcome in getting it to work...

As I have done this sort of thing many times I have created the sketch attached which plots integers and floats to the screen using your bitmaps from post #48. It works fine on my displays but you will have to adapt it if you use a different library. Adaptions should be limited to calling up the right library and then adapting setup() and drawIcon() functions. It prints the following numbers to screen

0
12345678
90000000
xx.x where this is a random float value that changes every second.

With the TFT_ST7735 library I used it plots each digit in 1.8ms, so it is quite usable because number updates visually appear "instant".

The plotIcon() function can also be used to plot any bitmap, see how it plots the first 0 on the display.

Give it a go, and then migrate to a library implementation later.

Edit: See post #65 for latest version of the code

@bodmer:

I know it's not going to be easy. I haven't written any libraries before for Arduino; did a little bit of it with PHP, but compared to what I've got planned here, small PHP libraries can be a ridiculously easy affair. Also, it's really been a few years since I coded in PHP at all.

I've got no real time constraints for this project, so I am willing to learn how to do it properly, even if it'll take me a few weeks.

@david_prentice:

If it's no trouble, could you modify the welcome.c file for me that I posted, so that it'll be in a format that will be directly useable for the display? I think I've understood now how you've described it... but just to be sure...


Anti-aliasing is pretty much done automatically when you work with a more complex graphics program, such as GIMP or Photoshop, or even CorelDraw or what-have-you. Whenever you draw shapes and other things in one of them, the default setting (which can be turned off) for them will be "anti-aliased".

I created my icons the way they are now with GIMP. It's free, you can download it here.

It's a bit difficult to get the hang of for a novice, and it can be a right nightmare if, like me, you're more used to Photoshop, but it is actually a really very potent graphics program. (and how does the old saying go... a bad workman blames his tools... :wink: )

OK

There is an example here that I wrote some time ago that pulls a file off an SD Card and plots it to the TFT.

At the start of the main sketch it even tells you how to create the raw 565 colour file.

To render the file the sketch calls the drawRaw() function which pulls chunks of the raw image file off the SD Card and pipes it to the display.

This should give you some ideas on how it can be done.

TTFN

@carguy: FYI, I have written a console app that converts 24bit bmp to 16bit 565 format while it keeps the bmp header so you have the information about image dimensions there. Here's a sketch that renders such images.
Alternatively I have another console app that converts a 24bit bmp to a 16bit 565 array and stores it in a .h file. Here's a sketch that renders such images.
Obivously this only works with ILI9341_due lib out of the box but you could adapt to your needs.

I fixed a bug in the example originally attached in post #61 where the plotIcon() function would not plot bitmaps bigger than 127 pixels wide or high.

New version is attached.

This new version also renders the 128x128 pixel welcome screen from FLASH memory at start up to test the drawing speed. The welcome image had to be split into two parts to avoid the 32768 byte limit for an array.

This one also by default uses the Adafruit_GFX library, this is about 4x slower when rendering bitmaps but it is still very usable on small displays. There is an option built in to use other libraries that support buffered pushColor() member functions.

That’s it for now. Will continue to answer questions as needed. TTFN

GFX_Plot_Bitmap_numbers.zip (21.9 KB)

bodmer:

Thanks, that seems easy enough to implement :slight_smile:

I'll try to get that to work tonight...

I just had an idea - what if, in the first two bytes of the .raw file, I simply define the height and width of the image, and then just let my function read and recognize those first two bytes as width and height definitions, and then move on to reading the rest of the data as pixel color information?

Couldn't you save yourself two function parameters that way, plus the extra step of having to know what size your image is?

(most of my small icons will be 25x25, but there are two or three that I had to do in 27x25, to make them look better)

...and over the weekend, I'll also have another crack at getting my head around how to compose libraries with Arduino, and writing an anti-aliased font library like the one I have in mind. That's probably going to take a week or two just to figure out how to do that in the first place, but in the end, I think it will be worth doing it "the hard way".

@bodmer:

I have to apologize to you for just now realizing that you’ve actually taken the time to pretty much put my entire idea into working code… I just tried out your posted sketch, and it looks as if it is PRECISELY giving me what I want… and in a neatly efficient way as well.

A very big “thank you” goes out to you.

You just may have taken weeks off the developing time for this project, as I was obviously struggling with how to implement this. Now I can finally move on to designing the menu screens…

EDIT:

Having had a look at the file size of the welcome screen, I think it might be better to load it as a .raw file from the SD card and not store it in PROGMEM. And if the SD card fails, then I will print out a slimmed down version of the welcome screen:

If I put the MG logo in PROGMEM as a 40x40 bitmap and the WELCOME sign as 128x30, I can save a considerable amount of flash memory. And the words “MG Driver Info System” and “Warning: SD Failure!” will then just be printed out in basic system font.

I am going to have to store three different number bitmap sets (large digital black, small digital black, large digital “MG red”), and there is a chance I will design a second set of them for “nighttime” operation, because I am toying with the idea of not only dimming the screen down at night, but giving it a darker background color to reduce glare. And then, still on top of that, I will store the 25x25 icons in PROGMEM as well. That’s already going to get me close to the 64KB threshold, so it might not be a good idea to cram a 128x128 bitmap into PROGMEM right out the gate that does nothing but welcome the user.

EDIT #2:

I’ve been having a little fun today with this bitmap plotting sketch, and I have created a mock analog odometer with it. Maybe this will be useful to somebody… I’ve attached the bitmaps file to this post…

hm… wonder if it’d be possible to implement some kind of “rolling over” effect as the digits change… by gradually replacing the pixels of one bitmap with another… but that’s just my imagination going wild again :slight_smile:

Bitmaps_analog.h (44.8 KB)

Cool odometer font.

Just for fun I created the attached that tries to emulate the classic odometer by scrolling the least significant digit when it increments.

GFX_Plot_Bitmap_numbers2.zip (26.7 KB)

That sounds pretty cool… I’ll have a look at it tomorrow afternoon and try it out on my display.

I’ve updated the font a bit… the numbers 5, 6 and 9 were a little too “edged” to look like odometer font… at least now they’re closer to the classic MG odometer font.

I’ve attached the updated bitmaps file to this post.

Bitmaps_analog.h (44.8 KB)

@bodmer:

I've just tested the sketch, and I am really kind of blown away by this... :slight_smile:

I think I will incorporate this in my CarDuino; as a kind of "alternate skin" to the seven-segment font.

I've just had a cursory look at the function that does the roll over effect... I am trying to think how it could be augmented so that a number can also "roll back" instead of forward... kind of the other way round... this could come in handy if the next value for a digit is smaller than that for the previous one.

As far as the welcome screen, yes, that's just too big for PROGMEM, considering that all it does is greet the user on startup. I'm just going to put a greatly slimmed down version of a 40x40 MG logo and the 128x30 "welcome" sign in PROGMEM, like I said above, and this will only load if the SD card fails. Otherwise, the 128x128 welcome screen will just be loaded from the SD card. The display is currently running on a 32GB MicroSDHC card from my old smartphone, so I might as well put that vast amount of memory to some use... :wink:

EDIT: All things combined, my display unit sketch will now have 62.42 KB of image data contained in PROGMEM. So it's pretty much up to the brim. Most of it stems from the idea that I want the display unit to be 90% functional even if the SD card fails. Hence, all the digit font data as well as the menu headers and small icons go into PROGMEM. The SD card will then only contain the 128x128 welcome screen and the full-color, 128x128 warning screens that will be displayed when sensors pick up faults.

I might use the SD card to also do some data logging, but I'm not quite sure yet. It's probably best to just store data in EEPROM, as I really only need things like the cumulated fuel consumption or mileage data etc. That could be done with just a handful of bytes in EEPROM, and then maybe update that data everytime the ignition is turned off again, to reduce wear on the EEPROM cells.

Which brings us to the next problem, how to make the unit notice that the ignition has been turned off. If I connect the Atmega circuit to the ignition, the power to it shuts off when the ignition is off. I will use a 5V power regulator for the Atmega, which will probably also go to 0V instantaneously. So maybe put a big enough cap over Vcc and GND so that the Atmega will notice via an input pin that the voltage is going down, but still have a few milliseconds to write into EEPROM before it too shuts down? Or is it better to connect the Atmega circuit straight to the battery and then have the Atmega sense if the ignition is on or off on an input pin?

@carguy

Changing the rollDigit() function to roll down for a decremented count is quite easy, this should work:

void rollDigitDown(byte newDigit, int16_t x, int16_t y)
{
  byte oldDigit = (newDigit + 11) % 10; // Work out what the previous digit to scroll was
                                        // 0 rolls over to a newDigit value of 9

  byte bmpWidth  = pgm_read_byte( &aWidths[newDigit]);
  byte bmpHeight = pgm_read_byte( &aHeights[newDigit]) - 2; // Subtract 2 as we don't scroll to and bottom rows

  for (uint16_t row = bmpHeight; row >0 ; row--) // We skip row 0 and the last row
  {
    drawIcon(aBMPmap[newDigit]+row*bmpWidth, x, y + 1, bmpWidth, bmpHeight-row);
    drawIcon(aBMPmap[oldDigit] + bmpWidth, x, y + 1 + bmpHeight - row, bmpWidth, row);
    delay(ROLL_DELAY);
  }
}

Notice I have renamed the analog bitmaps to be the same as ones the sketch in post #68.

Cool… thanks for that…

Right now, I have gone back to trying to make a simple function work that will read raw image files from the SD card that contain the 16-bit pixel definitions.

So far, this is what I’ve got, and it’s not working at all, and gets the sketch stuck in an infinite loop:

void drawRawIcon(char* icon, int16_t x, int16_t y, uint8_t width, uint8_t height) {

  uint16_t count = 0;
  File rawFile = SD.open(icon);
  unsigned long pixelNum = width * height;
  char pixel[pixelNum];

  // Set up a window the right size to stream pixels into
  tft.setAddrWindow(x, y, x + width - 1, y + height - 1);

  for (uint16_t i = 0; i < height; i++) {
    for (uint16_t j = 0; j < width; j++) {

      pixel[count] =  read16(rawFile);
      tft.pushColor(pixel[count]);
      count++;
    }
  }
}

uint16_t read16(File f) {
  uint16_t result;
  ((uint8_t *)&result)[0] = f.read(); // LSB
  ((uint8_t *)&result)[1] = f.read(); // MSB
  return result;
}

This function is a mix between your drawIcon function and Adafruit’s own bmpDraw function (the “read16” part at the bottom).

I am guessing somehow I’ve got to put the pixels in a buffer before pushing them onto the screen, but I don’t quite know yet how to do that.

You don’t need a buffer. And you probably do not have enough Stack for a buffer anyway.

void drawRawIcon(char* icon, int16_t x, int16_t y, uint8_t width, uint8_t height) 
{
  File rawFile = SD.open(icon);
  uint16_t pixel;

  // Set up a window the right size to stream pixels into
  tft.setAddrWindow(x, y, x + width - 1, y + height - 1);

  for (uint16_t i = 0; i < height; i++) {
    for (uint16_t j = 0; j < width; j++) {

      pixel =  read16(rawFile);
      tft.pushColor(pixel);
    }
  }
}

uint16_t read16(File f) {
  uint16_t result;
  ((uint8_t *)&result)[0] = f.read(); // LSB
  ((uint8_t *)&result)[1] = f.read(); // MSB
  return result;
}

Some libraries have a pushColors() method. (mine do)
This would let you write a block of pixels in one go.

Bodmer has pushColor() which only does one pixel at a time.

Untested. (I don’t have much on this Laptop)

David.

ok so this is the most recent version of my function:

void drawRawIcon(char* icon, int16_t x, int16_t y, uint8_t width, uint8_t height) {

  File rawFile;
  uint16_t pixel;

  if ((rawFile = SD.open(icon)) == NULL) {
    Serial.print("File not found");
    readError = true;
    return;
  }

  else {
    // Set up a window the right size to stream pixels into
    tft.setAddrWindow(x, y, x + width - 1, y + height - 1);

    for (uint16_t i = 0; i < height; i++) {
      for (uint16_t j = 0; j < width; j++) {

        pixel =  read16(rawFile);
        tft.pushColor(pixel);
      }
    }
    readError = false;
  }
}

uint16_t read16(File f) {
  uint16_t result;
  ((uint8_t *)&result)[0] = f.read(); // LSB
  ((uint8_t *)&result)[1] = f.read(); // MSB
  return result;
}

It compiles, and it draws something on the screen, but that “something” isn’t a recognizable picture, just a bunch of pixels.

I’ve attached the .raw image file that I was trying to display (as a .txt file, because the forum won’t let you upload .raw files).

welcome.txt (113 KB)

No, I have not looked at your RAW file. But I bet that it is written in big-endian and the read16() is expecting little-endian or vice versa.

    pixel = (pixel << 8) | (pixel >> 8);

and see whether it makes everything better.

David.

david_prentice:
No, I have not looked at your RAW file. But I bet that it is written in big-endian and the read16() is expecting little-endian or vice versa.

    pixel = (pixel << 8) | (pixel >> 8);

and see whether it makes everything better.

David.

No, it hasn’t made it better, sadly… And I’ve just looked at the settings I used within “LCD Image Converter”, and it seems that it is giving out the image data as “little-endian”.

I have to confess I’ve got no idea what either of them means.

Well, I looked at your "Welcome.txt" and it appears to be an ascii text file with no line breaks or punctuation.

You either create a RAW binary file that you store on the SD card.
Or you create C source code. e.g.

const uint8_t PROGMEM penguin[3200] = {  /* 0X00,0X10,0X28,0X00,0X28,0X00,0X01,0X1B, */
    0XFF, 0XFF, 0XFF, 0XFF, 0XFF, 0XFF, 0XFF, 0XFF, 0XFF, 0XFF, 0XFF, 0XFF, 0XFF, 0XFF, 0XFF, 0XFF,
    0XFF, 0XFF, 0XFF, 0XFF, 0XFF, 0XFF, 0XFF, 0XFF, 0XFF, 0XFF, 0XFF, 0XFF, 0XBE, 0XF7, 0X7D, 0XEF,
    ...

or you do the same but in uint16_t e.g.

const uint16_t PROGMEM penguin[3200] = {  /* 0X00,0X10,0X28,0X00,0X28,0X00,0X01,0X1B, */
    0XFFFF, 0XFFFF, 0XFFFF, 0XFFFF, 0XFFFF, 0XFFFF, 0XFFFF, 0XFFFF,
    0XFFFF, 0XFFFF, 0XFFFF, 0XFFFF, 0XFFFF, 0XFFFF, 0XBEF7, 0X7DEF,
    ...

If you would care to attach the original Welcome.bmp file, I will convert it for you.

If you say where you got "LCD Image Converter" from, we could help you through the process of creating RAW files (or C files)

As I said earlier, you might just as well do all your development with regular BMP files on SD card.
When your design is finalised, we can show you how to reduce memory, speed things up, ...

The mega1284 is perfectly capable of producing a smart display. Do not worry about speed in the development phase.

David.

Hello, carguy.
Can you describe exactly image format that you are using? RAW binary pixels data? Or with header? Header data format?

This is where I got the LCD Image Converter from:

http://www.riuson.com/lcd-image-converter

So far, it has been working a treat in converting all my anti-aliased, full-color image files for PROGMEM use quite nicely. Not sure why it’s not working with the .raw files (yet).

I’ve attached the welcome.bmp file in a zip folder. From this file, I created a “welcome.raw” file, which my raw image file reading function was able to open on the MicroSD card, but beyond that point, things go pear shaped and I only get kind of a “chequered” pattern on the screen. So it’s loading something, but it’s not loading (or displaying) it the right way.

Although you’re probably right in pointing out that at this stage I shouldn’t worry about whether the files loaded from the MicroSD card are bmp or raw, this project is going to be very “picture heavy”, incorporating as many as twelve different warning/sensor fault screens alone, which will be loaded from the card when needed. So to get this out of the way early on might help me focus on other things, especially as Adafruit’s own bmpDraw() function which I am using at present seems slightly “clunky”, and I am not entirely sure which parts of it are superfluous:

 #define BUFFPIXEL 20

  void bmpDraw(char *filename, uint8_t x, uint8_t y) {

  File     bmpFile;
  int      bmpWidth, bmpHeight;   // W+H in pixels
  uint8_t  bmpDepth;              // Bit depth (currently must be 24)
  uint32_t bmpImageoffset;        // Start of image data in file
  uint32_t rowSize;               // Not always = bmpWidth; may have padding
  uint8_t  sdbuffer[3 * BUFFPIXEL]; // pixel buffer (R+G+B per pixel)
  uint8_t  buffidx = sizeof(sdbuffer); // Current position in sdbuffer
  boolean  goodBmp = false;       // Set to true on valid header parse
  boolean  flip    = true;        // BMP is stored bottom-to-top
  int      w, h, row, col;
  uint8_t  r, g, b;
  uint32_t pos = 0, startTime = millis();

  if ((x >= tft.width()) || (y >= tft.height())) return;

  Serial.println();
  Serial.print("Loading image '");
  Serial.print(filename);
  Serial.println('\'');

  // Open requested file on SD card
  if ((bmpFile = SD.open(filename)) == NULL) {
    Serial.print("File not found");
    return;
  }

  // Parse BMP header
  if (read16(bmpFile) == 0x4D42) { // BMP signature
    Serial.print("File size: "); Serial.println(read32(bmpFile));
    (void)read32(bmpFile); // Read & ignore creator bytes
    bmpImageoffset = read32(bmpFile); // Start of image data
    Serial.print("Image Offset: "); Serial.println(bmpImageoffset, DEC);
    // Read DIB header
    Serial.print("Header size: "); Serial.println(read32(bmpFile));
    bmpWidth  = read32(bmpFile);
    bmpHeight = read32(bmpFile);
    if (read16(bmpFile) == 1) { // # planes -- must be '1'
      bmpDepth = read16(bmpFile); // bits per pixel
      Serial.print("Bit Depth: "); Serial.println(bmpDepth);
      if ((bmpDepth == 24) && (read32(bmpFile) == 0)) { // 0 = uncompressed

        goodBmp = true; // Supported BMP format -- proceed!
        Serial.print("Image size: ");
        Serial.print(bmpWidth);
        Serial.print('x');
        Serial.println(bmpHeight);

        // BMP rows are padded (if needed) to 4-byte boundary
        rowSize = (bmpWidth * 3 + 3) & ~3;

        // If bmpHeight is negative, image is in top-down order.
        // This is not canon but has been observed in the wild.
        if (bmpHeight < 0) {
          bmpHeight = -bmpHeight;
          flip      = false;
        }

        // Crop area to be loaded
        w = bmpWidth;
        h = bmpHeight;
        if ((x + w - 1) >= tft.width())  w = tft.width()  - x;
        if ((y + h - 1) >= tft.height()) h = tft.height() - y;

        // Set TFT address window to clipped image bounds
        tft.setAddrWindow(x, y, x + w - 1, y + h - 1);

        for (row = 0; row < h; row++) { // For each scanline...

          // Seek to start of scan line.  It might seem labor-
          // intensive to be doing this on every line, but this
          // method covers a lot of gritty details like cropping
          // and scanline padding.  Also, the seek only takes
          // place if the file position actually needs to change
          // (avoids a lot of cluster math in SD library).
          if (flip) // Bitmap is stored bottom-to-top order (normal BMP)
            pos = bmpImageoffset + (bmpHeight - 1 - row) * rowSize;
          else     // Bitmap is stored top-to-bottom
            pos = bmpImageoffset + row * rowSize;
          if (bmpFile.position() != pos) { // Need seek?
            bmpFile.seek(pos);
            buffidx = sizeof(sdbuffer); // Force buffer reload
          }

          for (col = 0; col < w; col++) { // For each pixel...
            // Time to read more pixel data?
            if (buffidx >= sizeof(sdbuffer)) { // Indeed
              bmpFile.read(sdbuffer, sizeof(sdbuffer));
              buffidx = 0; // Set index to beginning
            }

            // Convert pixel from BMP to TFT format, push to display
            b = sdbuffer[buffidx++];
            g = sdbuffer[buffidx++];
            r = sdbuffer[buffidx++];
            tft.pushColor(tft.Color565(r, g, b));
          } // end pixel
        } // end scanline
        Serial.print("Loaded in ");
        Serial.print(millis() - startTime);
        Serial.println(" ms");
      } // end goodBmp
    }
  }

  bmpFile.close();
  if (!goodBmp) Serial.println("BMP format not recognized.");
  }

  // These read 16- and 32-bit types from the SD card file.
  // BMP data is stored little-endian, Arduino is little-endian too.
  // May need to reverse subscript order if porting elsewhere.

  uint16_t read16(File f) {
  uint16_t result;
  ((uint8_t *)&result)[0] = f.read(); // LSB
  ((uint8_t *)&result)[1] = f.read(); // MSB
  return result;
  }

  uint32_t read32(File f) {
  uint32_t result;
  ((uint8_t *)&result)[0] = f.read(); // LSB
  ((uint8_t *)&result)[1] = f.read();
  ((uint8_t *)&result)[2] = f.read();
  ((uint8_t *)&result)[3] = f.read(); // MSB
  return result;
  }

EDIT: One of you showed me a 1284 as an SMD variant on a small breakout PCB a while ago, which seemed pretty neat… can’t find the post at the moment… in the final product of my CarDuino, I could imagine using that one instead of the 1284P-PU, because it will be mounted either inside the instrument cluster or in the center console of my car, and both places don’t offer all that much space…

welcome.zip (25.1 KB)