Writing a BMP to a Nokia 3310 Screen - Getting Frustrated

I picked up a shield with a Nokia 3310 screen on it and have been playing around with it using the driver found at Arduino Playground - PCD8544. I've managed to convert the menu into a multi level menu to suit my needs but am getting frustrated with the LCD_3310_draw_bmp_pixel function. It's not critical to what I'm building but I'm so frustrated by it that I can't let it go. When I first started playing with it a few months back I found a site or a program that generated the char array for any bitmaps you imported and got some to work but I can no longer figure out how I did that. Right now I'm trying to generate the code through a C# app I wrote but I'm failing miserably.

The example app uses this char array to generate an AVR logo when passed into the LCD_3310_draw_bmp_pixel function:

unsigned char AVR_bmp[]= {
 0x00,0x00,0x00,0x00,0x80,0xE0,0xFC,0xFF,0xFF,0xFF,0x7F,0xFF,0xFE,0xFC,0xF0,0xC1,
 0x0F,0x7F,0xFF,0xFF,0xFE,0xF0,0xC0,0x00,0x00,0x00,0xC0,0xF8,0xFE,0xFF,0xFF,0x3F,
 0x07,0xC1,0xF0,0xFE,0xFF,0xFF,0xFF,0x1F,0x07,0x8F,0xCF,0xFF,0xFF,0xFF,0xFE,0xFC,
 0x00,0x80,0xF0,0xFC,0xFF,0xFF,0xFF,0x7F,0x7F,0x78,0x78,0x79,0x7F,0x7F,0xFF,0xFF,
 0xFC,0xF0,0xC1,0x07,0x1F,0xFF,0xFF,0xFE,0xFC,0xFF,0xFF,0xFF,0x1F,0x07,0xC1,0xF0,
 0xFE,0xFF,0xFF,0x3F,0x0F,0x0F,0x7F,0xFF,0xFF,0xFF,0xFF,0xE7,0x07,0x03,0x01,0x00,
 0x02,0x03,0x03,0x03,0x03,0x03,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x03,
 0x03,0x03,0x03,0x03,0x00,0x00,0x03,0x1F,0x3F,0x1F,0x07,0x00,0x00,0x02,0x03,0x03,
 0x03,0x03,0x01,0x00,0x00,0x00,0x00,0x03,0x03,0x03,0x03,0x03,0x03,0x00,0x00,0x00
 };

As you can see its 48x24 pixels, yet the data in it is 16x9 bytes for a total of 144 bytes. I would suspect that if each byte simply mapped out to a pixel and set it on or off then you'd have 1152 bytes in that array, so that theory is out.

In my C# app I've tried converting the whole BMP to an array but it again is far too massive. I've also tried stripping the headers and getting just the individual pixel data and building a map of that even though I knew it wouldn't work. Here's my code for that:

public String convertToByteArray(String filePath)
        {
            Bitmap bmp2 = null;
            // Make sure the bitmap is loaded into memory
            if (bmp == null)
            {
                //bmp = new Bitmap(filePath);
                bmp = new Bitmap(filePath); // Create 8bpp bitmap
                bmp2 = CopyToBpp(bmp, 1); // Convert to a 1bpp bitmap (monochrome)
            }

            byteArrayLen = bmp.Height * bmp.Width;                      
            String byteArrayString = "";
            String byteTemp = "";
            int i = 0;
            if (byteArrayLen > 0)
            {
                // Start building the string
                byteArrayString = byteArrayPrefix;
                int x = 0;
                int y = 0;

                // Build the middle 
                for (i = 0; i < byteArrayLen - 1; i++)
                {
                    // Calculate x and y positions
                    if ((i > 0) && (i % (byteArrayY - 1)== 0))
                    {
                        x++;
                        y = 0;
                    }
                    if (x == 84)
                    {
                        break;
                    }

                    // Add the current byte
                    try
                    {
                        byteTemp = bmp.GetPixel(x, y).ToString().Substring(16, 3);
                        if (byteTemp == "255")
                        {
                            byteArrayString += "0xFF";
                        }
                        else
                        {
                            byteArrayString += "0x00";
                        }
                    } 
                    catch (Exception err)
                    {
                        Console.WriteLine(err.ToString());
                    }

                    // Add separator
                    if (i < byteArrayLen - 1)
                    {
                        byteArrayString += byteArrayseparator;
                        if ((i + 1) % 16 == 0)
                        {
                            byteArrayString += "\r\n";
                        }
                    }
                    else
                    {
                        byteArrayString += "";
                    }

                    y++;
                }

                // Finish off string
                byteArrayString += byteArraySuffix;
            }
            else
            {
                byteArrayString = null;
            }

            return byteArrayString;
        } //convertToByteArray()

I've extracted all the relevant data out of the library to try and determine how it works but to me it looks like it's just the pixel map I already determined it isn't:

void LCD_draw_bmp_pixel(unsigned char X,unsigned char Y,unsigned char *map,
                  unsigned char Pix_x,unsigned char Pix_y)
  {
    unsigned int i,n;
    unsigned char row;
    
    if (Pix_y%8==0) row=Pix_y/8;  
      else
        row=Pix_y/8+1;
    
    for (n=0;n<row;n++)
      {
      	LCD_set_XY(X,Y);
        for(i=0; i<Pix_x; i++)
          {
            LCD_write_byte(map[i+n*Pix_x], 1);
          }
        Y++;                       
      }      
  }
void Nokia_3310_lcd::LCD_3310_write_byte(unsigned char dat, unsigned char dat_type){
	LCD_write_byte(dat, dat_type);
}

  void LCD_set_XY(unsigned char X, unsigned char Y)
  {
    LCD_write_byte(0x40 | Y, 0);		// column
    LCD_write_byte(0x80 | X, 0);          	// row
  }

So does anyone have any clue how I generate an array to map out a bitmap like the AVR logo in the example? I've been at this all day and feel like I'm just getting further from the correct way of doing it.

Thanks in advance

As you can see its 48x24 pixels,

No, I can't see that. As that statement seems to form the basis of your troubles, perhaps you need to help me see that the 16 x 9 array represents 28 x 24 pixels.

48 x 24 = 1152.
16 x 9 x 8 = 1152

It's one bit per pixel.

The fact that there are 16 values on a row has no meaning.

The fact that there are 9 rows has no meaning.

One could put all 144 values on one line, or one value on each of 144 lines. The compiler would produce exactly the same output.

The fact that the data defines 1152 bits does not definitely imply monochrome (one bit per pixel) or any particular size. The image could have been 48 by 24 or 24 by 48 or 144 by 8 or 32 by 36 or many other sizes.

                    if (x == 84)

What is the significance of 84?

                        byteTemp = bmp.GetPixel(x, y).ToString().Substring(16, 3);

You are getting the data for one pixel, as a string. What does this string look like, when the bmp is a solid red? Why are you extracting only 3 characters from that string? What are the other characters?

                        if (byteTemp == "255")
                        {
                            byteArrayString += "0xFF";
                        }
                        else
                        {
                            byteArrayString += "0x00";
                        }

Your output array will consist of the string 0xFF or the string 0x00. The example array consists of lots of other values.

PaulS:

As you can see its 48x24 pixels,

No, I can't see that. As that statement seems to form the basis of your troubles, perhaps you need to help me see that the 16 x 9 array represents 28 x 24 pixels.

Sorry, should've posted this accompanying function call : lcd.LCD_3310_draw_bmp_pixel(0,0, AVR_bmp, 48,24);
The latter part specifies the dimensions of the image. There was also a comment above the array that said it was 48x24.

PeteC:
48 x 24 = 1152.
16 x 9 x 8 = 1152

It's one bit per pixel.

Thanks, that's what I had figured as 1bpp = monochrome. Didn't think to take the result of 16x9 and then multiply that by 8 again. Now the numbers make sense. Thanks.

PaulS:
The fact that there are 16 values on a row has no meaning.

The fact that there are 9 rows has no meaning.

One could put all 144 values on one line, or one value on each of 144 lines. The compiler would produce exactly the same output.

I never said the number of bytes on each line had any significance. It was simply a matter of length times width to figure out the number of bytes in the array. I could have just said 1152 but since I did the math in my head I typed it as I was thinking it.

PaulS:
The fact that the data defines 1152 bits does not definitely imply monochrome (one bit per pixel) or any particular size. The image could have been 48 by 24 or 24 by 48 or 144 by 8 or 32 by 36 or many other sizes.

It's monochrome.

PaulS:
What is the significance of 84?

The max resolution of the display is 84x48. It should have never hit that code and that was just a fail safe I tossed in in case I accidentally tried to load a 800x600 image or something much larger.

PaulS:
You are getting the data for one pixel, as a string. What does this string look like, when the bmp is a solid red? Why are you extracting only 3 characters from that string? What are the other characters?

Once again, we're working with monochrome. The values are either 255,255,255 (FFFFFF) or 0,0,0 (000000), so I just ignore the G and B channels. I'm sure there is a better way to do this and if I were attempting to convert images that weren't monochrome or a specific size I would've added more logic to account for that, but there was no need for it in this case. This was just rough code to see if this line of thinking was the correct one and then if it was successful I'd go back and beef it up.

Anyways, with all that said I had a bit of a eureka moment while working on it this morning. It is just a straight up bit map with no headers/footers or DIB data. There reason the size of the array wasn't making sense to me is because for some reason I was thinking a byte was representing each pixel which is silly because either it's on or off so it only needs a bit. So I tweaked the code to build a giant bit string and then afterwards convert that bit string into a byte array. So a 16x16 pixel display would need 256 bits (32 bytes) to represent it.

So right now I'm trying to convert this bitmap I made in paint (yeah I'm not a graphic designer... lol) which works out to this in hex:

AAAAAAAAAAAAAAAAAAAAAAAA00000000000A0000000000080000000000280000000000200000000000A00000000000801FFFFFFF02803FFFFFFE02007FFFFFFC0A00E00700000801C00E00002803801C0000200E00380000A01C00700000807000E0000280E001C0000201C00380000A0300070000080E001C0000281800300000203000600000A06000C0000080C00180000281800000000203000000000A0000000000080000000000280000000000200000000000A0000000000080000000000280000000000200000000000A0000000000080000000000280000000000200000000000A0000000000080000000000280000000000200000000000A00000000000800000000002800000000002AAAAAAAAAAAAAAAAAAAAAAA

Or expressed as a byte array I get:

unsigned char AVR_bmp []= {0xAA0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 
0x2A, 0x00, 0x00, 0x00, 0x00, 0x00, 0x28, 0x00, 0x00, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 
0x00, 0x00, 0x0A, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x80, 0x02, 0x00, 
0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0xA0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x20, 
0x00, 0x00, 0x00, 0x00, 0x00, 0x28, 0x00, 0x00, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x00, 
0x00, 0x0A, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x80, 0x02, 0x00, 0x00, 
0x00, 0x00, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0xA0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x20, 0x00, 
0x00, 0x00, 0x00, 0x00, 0x28, 0x00, 0x00, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 
0x0A, 0x00, 0x00, 0x00, 0x00, 0x03, 0x02, 0x00, 0x00, 0x00, 0x80, 0x81, 0x02, 0x00, 0x80, 0x01, 
0xC0, 0x80, 0x00, 0x00, 0xC0, 0x00, 0x60, 0xA0, 0x00, 0x00, 0x60, 0x00, 0x30, 0x20, 0x00, 0x00, 
0x30, 0x00, 0x18, 0x28, 0x00, 0x00, 0x1C, 0x00, 0x0E, 0x08, 0x00, 0x00, 0x07, 0x00, 0x03, 0x0A, 
0x00, 0x80, 0x03, 0xC0, 0x01, 0x02, 0x00, 0xC0, 0x01, 0xE0, 0x80, 0x02, 0x00, 0xE0, 0x00, 0x70, 
0x80, 0x00, 0x00, 0x70, 0x00, 0x1C, 0xA0, 0x00, 0x00, 0x38, 0x00, 0x0E, 0x20, 0x00, 0x00, 0x1C, 
0x80, 0x03, 0x28, 0x00, 0x00, 0x0E, 0xC0, 0x01, 0x08, 0x00, 0x00, 0x07, 0xE0, 0x00, 0x0A, 0xFC, 
0xFF, 0xFF, 0x7F, 0x00, 0x02, 0xFE, 0xFF, 0xFF, 0x3F, 0x80, 0x02, 0xFF, 0xFF, 0xFF, 0x1F, 0x80, 
0x00, 0x00, 0x00, 0x00, 0x00, 0xA0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00, 0x00, 
0x00, 0x28, 0x00, 0x00, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0A, 0x00, 0x00, 
0x00, 0x00, 0x00, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, };

This looks much closer to what I'm expecting, but still looks scrambled on the screen.

I did find the program I was originally using to convert these images, but I am still determined to get mine working now that I've put this much time into it. And unfortunately the guy didn't post an email address or any way to contact him so I cannot ask him for advice :frowning:

It did give me some pointers though, despite it's minimal interface. It made me realize that I hadn't considered processing the bits vertically (top to bottom) rather than horizontally (left to right) nor had I thought to play around with the endianness. In that software the settings that work are Byte Orientation: Vertical, Endianness: Big, Pixels/byte: 8, Size Included: No. So with that in mind I"m now about to go back to my C# code and get the same result. When I get it working I'll post an update.

You could switch over to native XBM bitmap format instead.
Tools like gimp can save directly in XBM format.

Once nice thing is that the bit format of the pixel data doesn't change from
the original format to the data array that is compiled into the embedded code.

It won't render as fast as a bitmap format that lines up with the native data format
of the glcd module which allows glcd page writes but in some cases it can be easier to work with.

Here is a code fragment that will extract the data from the XBM data array
(assuming you use progmem to push it into flash) and render it on the display.

This is a routine that will be in next revision of the glcd library.
It is fully tested and working.
You will have to adapt the SetDot() calls to whatever your graphic library uses.

void DrawBitmapXBM_P(uint8_t width, uint8_t height, uint8_t *xbmbits, 
			uint8_t x, uint8_t y, uint8_t fg_color, uint8_t bg_color)
{
uint8_t xbmx, xbmy;
uint8_t xbmdata;

	/*
	 * Traverse through the XBM data byte by byte and plot pixel by pixel
	 */
	for(xbmy = 0; xbmy < height; xbmy++)
	{
		for(xbmx = 0; xbmx < width; xbmx++)
		{
			if(!(xbmx & 7))	// read the flash data only once per byte
				xbmdata = ReadPgmData(xbmbits++);

			if(xbmdata & _BV((xbmx & 7)))
				SetDot(x+xbmx, y+xbmy, fg_color); // XBM 1 bits are fg color
			else
				SetDot(x+xbmx, y+xbmy, bg_color); // XBM 0 bits are bg color
		}
	}
}
uint8_t ReadPgmData(const uint8_t* ptr)
{
	return pgm_read_byte(ptr);
}

@bperrybap - Thanks, had I known about that from the get go I might not have started this whole project. However I'm still a bit determined to make what I've got work, as I'm not one to give up.

@PaulS - I found a better way to extract the colour information. Well, I'm assuming it's better as I know parsing out strings is typically slow so this would seem faster, I'll have to test it though.

// Get current pixel and store it as a colour, which gives me access to a method which extracts any colour values
Color c = bmp.GetPixel(x, y);
if (c.R == 255 || c.G == 255 || c.B == 255)  // if there's any colour to it at all, turn on the pixel
{
        // On
       byteArrayString += "1";
}
else 
{
        // Off
       byteArrayString += "0";
}

I've got it! Will be posting a solution soon on my site. Ended up finding a C# console app that had the logic. Don't quite understand some of what it's doing but I was able to make it work with the GUI I already had developed so I don't have to mess with the command line.

Thx to all those who offered suggestions.