Still celebrating National IT Professionals Day with 3 months of free Premium Membership. Use Code ITDAY17

x
Solved

# Details from the Anatomy of a BitMap (from the .Net BitMap Class)

Posted on 2013-01-09
Medium Priority
627 Views
Howdy, Experts!

Here's the situation: it has been discovered that - to properly send a graphic image to a Zebra ZPL printer - all one really needs to know is its [Width], its [Height], and have a byte array of the image data to send to the printer.  Nothing more esoteric than that.

The data that is you need to send isn't a PNG. You need to take the image and convert it to black and white and send the data to the printer. For example, if you have an image that is 40x50px you would take the image, and strip out the color information so you would have a total of 2000 bits of data. Then send your ZPL down like ^GFB,250,250,5,{2000 bits of data}.

I got 250 by taking 2000 bits / 8 (bits / byte) to get 250 bytes. I got 5 by dividing 40 by 8. The number of rows will be calculated automatically.

Something that I find useful when dealing with Zebra printers is to think in terms of bits. All graphics are done on a bit level.

Remember that the image you are going to send down will change size depending on the DPI of the printer. A 203 DPI printer will show my example at about .2in x .25in. On a 300 DPI printer it will show at about .13in by .16in. This is because the printer will just place raw data onto the format and the number of px is the number of dots the image will be.

References: [1] ZPL Manual on page 208 (^GF page2).

...until I decided to accomplish this from an interface in my class/object that accepts a BitMap.  Makes sense, right?  Send the class a BitMap, and it will handle getting it over to the printer, get it printed.  Only it's not that easy.

I need to somehow figure out where the header ends and the (raw?) image data begins - perhaps somehow making sure that the image data is 1BPP monochrome (a thermal printer doesn't print greyscales very well, yet...) - only I can't seem to figure out this information from the BitMap class.  Frustrating!

The hitch: this thing has to operate from the BitMap class: no files included.  Think about it: what if the image being "fed" comes from a resource file?  Or was 'punched in' to a PictureBox from a user-selected image, perhaps pasted off the clipboard?  Because these are all "realistic" scenarios, I am informed that it must perform its mastery in memory, preferrably with the BitMap which has been passed to it.  According to the powers that be, "it's enough information to put that image on a screen somewhere, so it should be more than plenty to send to the printer".  (An almost direct quote.)

So I think I've got a two-fold mission, here: figure out where the header ends in a given BitMap in order to (somehow) grab a byte array representing the 1BPP image data from that same BitMap, and (somehow) figure out if said BitMap is the correct format in the first place.

I'm not sure the BitMap PixelFormat can be trusted: I converted a PNG image to 1BPP color, and it (somehow) saved as a 16-color indexed format - yet the data was pure b/w image.  So I'm not sure if that's a realistic expectation, especially based on limitations of file formats.

So I'm more interested in figuring out the "dividing line" between the header and the raw image data, and how I might pluck that raw image data into a Byte Array for transmission to a ZPL printer for other nefarious purposes.

* I included the back information on the Zebra ZPL printers just in case some inquiring mind in similar straits can benefit from this question as well.  Sometimes it helps when you know what I plan to do with this information...      ...ultimately, when I am finished with this segment of the application, I will be able to grab an image, make sure it's 1BPP (or convert it/dither it until it is), get the size of it, and transfer just the 1's and 0's to the ZPL printer in order to cause it to properly display that image, somewhere on the printout.  I know how to send the data, I even know what data I need to send.  I just need to somehow distill the data from a BitMap class entity --- and that's not as easy as it seems.
0
Question by:LongFist
[X]
###### Welcome to Experts Exchange

Add your voice to the tech community where 5M+ people just like you are talking about what matters.

• Help others & share knowledge
• Earn cash & points
• Learn & ask questions
• 2
• 2

LVL 37

Accepted Solution

TommySzalapski earned 2000 total points
ID: 38760152
Since you likely aren't processing dozens of images per second, you could just use the height and width and the GetPixel method do extract all the data into your own array. This way you know what the data is and it doesn't really matter what format it is in. You can also force it to be monochrome by putting whatever values come out for the pixels through a threshold.
0

LVL 1

Author Comment

ID: 38760170
An interesting concept.  Not exactly what I had in mind, but incredibly efficient nonetheless.  That just might work.  Let me test that...

Thresholding.  Thresholding.  I'm going to have to see if I've already visited that particular issue before - I may have, once or twice before...

Thank you for your prompt response.  Let me see if I can make that work...
0

LVL 37

Expert Comment

ID: 38760336
By threshold, I just mean that you convert any image to monochrome. For example, say you have one byte per pixel greyscale image. If GetPixel returns something > 128 use white else use black. Or if 128 isn't the best threshold value for you, use something else.

Also if you are converting everything to bits, you can't use the raw bitmap data even if you could find it. You'll have to use bit operators.

Would look something like this in C++
``````const int THRESHOLD = 128;
// Get the size (in bits)
int size = bmp.Height*bmp.Width;
// Convert to bytes
int buffer_size = (size + 7)/8; //+7 to make sure it rounds up.

// Create the array and zero it out
char* buffer = new buffer[buffer_size];
memset(buffer, 0, buffer_size);

for(int i = 0; i < size; ++i)
{
// i/width gives the row, i&width gives the column
if(bmp.GetPixel(i/bmp.Width, i%bmp.Width) > THRESHOLD)
{
buffer[i/8] |= 1 << (7-i%8);
}
}

SendBufferToZebraPrinterHere

delete[] buffer;
``````

Breaking down buffer[i/8] |= 1 << (7-i%8);
i/8 goes from the pixel to the byte so you are changing the correct byte
|= will make whatever bits are set on the right side to be set on the left side as well
<< is bit shift (1<<5 gives this in binary 00100000)
So for the third pixel, i = 2 so (7-i%8) is 5 so it sets the third bit in that byte.
0

LVL 16

Expert Comment

ID: 38762932
A bitmap file consists of 4 structures or data blocks one followed by the other:

BITMAPINFO
RGB Info
Scan Lines

If you're talking about a bitmap in memory, there is only the last three structures (i.e. no BITMAPFILEHEADER).

Years ago I wrote a subroutine that would decode all this stuff for me.  I defined my own structure call 'BitmapScanLines' consisting of the pertanent data about the bitmap I (and likly you) wanted.  The subroutine takes a pointer to 'BitmapScanLines' and a pointer to a BITMAPINFO and decodes the data in BITMAPINFO to populate 'BitmapScanLines'.  The code is written in C, but you should be able to decode it for C#.

structure
``````typedef struct                    //All the pertenent data needed to efficiently process a Bitmap
{
//The Entire Bitmap
BITMAPINFO* pBitmap;

//Cached Data
long RGBCount;              //Number of Colors in RGB table
unsigned char* pImage;      //Pointer to the Image Data (Scan Lines)
long Width;                 //Width of Bitmap (FullBytesPerScanLine * 8 + LastByteBitCount)
long Height;                //Number os Scanlines in a Bitmap
long DPIX;                  //Approximate DPI in X direction
long DPIY;                  //Approximage DPI in Y direction

//Calculated ScanLine Data
long FullBytesPerScanLine;  //Number of Bytes per scan line that all 8 bits contain Image Data
long LastByteBitCount;      //Number of Bits in the last Byte of each scan line that contains ImageData
//    (Significant bits in Byte#     FullBytesPerScanLine + 1)(can be zero if Width is evenly divisable by 8)
long ImageBytesPerScanLine; //Number of Bytes with ANY Image Data (excludes Extra Bytes)
long ExtraBytesPerScanLine; //Number of unused Bytes at the end of a scan line (Scan Lines must end on DWORD boundary)
long BytesPerScanLine;      //Number of Bytes per scan line (including Extra Bytes)
long BytesPerImage;         //Number of Bytes for the entire image (including all Extra Bytes)
} BitmapScanLines;
``````
Subroutine
``````//Given a BitmapInfo and a BitmapScanLines Structure
//    Initialize the BitmapScanLines Structure with data from BitmapInfo
//    Return 0 on success, a Negative Number on Failure
long __declspec(dllexport) FAR PASCAL LoadBitmapScanLines( BitmapScanLines* pScanLines, BITMAPINFO* pBitmap )
{

//Validate Compression
if( pHeader->biCompression != BI_RGB )
{
return -1;    //Only Uncompressed RGB bitmaps are currently supported
}

//Store location of Bitmap
pScanLines->pBitmap = pBitmap;

//Find Raw Image Data by finding number of RGB values
{
case 0:  return -2;    //JPEG and PNG formats are not currently supported
case 1:  pScanLines->RGBCount = 2;                                                                                break;
case 4:  pScanLines->RGBCount = pHeader->biClrUsed; if( pScanLines->RGBCount == 0 ) pScanLines->RGBCount = 16;    break;
case 8:  pScanLines->RGBCount = pHeader->biClrUsed; if( pScanLines->RGBCount == 0 ) pScanLines->RGBCount = 256;   break;
case 16: pScanLines->RGBCount = pHeader->biClrUsed; if( pScanLines->RGBCount == 0 ) pScanLines->RGBCount = 65536; break;
case 24: pScanLines->RGBCount = 0; /* No Color Pallet because each pixel is an RGB color */                       break;
case 32: pScanLines->RGBCount = 0; /* No Color Pallet because each pixel is an RGB color */                       break;
default: return -3;    //Unknown BitCount
}
pScanLines->pImage = (unsigned char*) pBitmap + sizeof(BITMAPINFOHEADER) + sizeof(RGBQUAD) * pScanLines->RGBCount;

pScanLines->DPIX   = (pHeader->biXPelsPerMeter * 254 + 5000) / 10000;   //+5000 to force Rounding
pScanLines->DPIY   = (pHeader->biYPelsPerMeter * 254 + 5000) / 10000;    //+5000 to force Rounding

//Load Calculated ScanLine Data
pScanLines->FullBytesPerScanLine = pScanLines->Width / 8;
pScanLines->LastByteBitCount = pScanLines->Width - pScanLines->FullBytesPerScanLine * 8;
pScanLines->ImageBytesPerScanLine = pScanLines->FullBytesPerScanLine + (pScanLines->LastByteBitCount ? 1 : 0);
pScanLines->ExtraBytesPerScanLine = 4 - pScanLines->ImageBytesPerScanLine % 4;
if( pScanLines->ExtraBytesPerScanLine == 4 ) pScanLines->ExtraBytesPerScanLine = 0;
pScanLines->BytesPerScanLine = pScanLines->ImageBytesPerScanLine + pScanLines->ExtraBytesPerScanLine;
pScanLines->BytesPerImage = pScanLines->Height * pScanLines->BytesPerScanLine;

return 0;
}
``````
0

LVL 1

Author Closing Comment

ID: 38783756
Sometimes the simplest answers are the best.  Processing the image down to 1BPP, then running through it for b/w (0/1) values was the answer.  As we are only sending 1 graphic label at a time, and the application is not time-intensive, this solution works best.

Took some time to get it working properly, but it's pretty bullet-proof.  Thank you!
0

## Featured Post

Question has a verified solution.

If you are experiencing a similar issue, please ask a related question

Although it can be difficult to imagine, someday your child will have a career of his or her own. He or she will likely start a family, buy a home and start having their own children. So, while being a kid is still extremely important, it’s also …
The article shows the basic steps of integrating an HTML theme template into an ASP.NET MVC project
Simple Linear Regression
Starting up a Project
###### Suggested Courses
Course of the Month6 days, 2 hours left to enroll

#### 688 members asked questions and received personalized solutions in the past 7 days.

Join the community of 500,000 technology professionals and ask your questions.