C: Processing Windows Bitmaps Or DIBs With The Propeller?

idbruceidbruce Posts: 5,515
edited April 3 in Propeller 1 Vote Up0Vote Down
Hello Everyone

I am looking for existing code or a processing scheme to read the bits of Windows bitmaps or DIBs with the Propeller. It has been a while since I have messed with the bitmap file structures, so I know I will have to do a little studying, but not much.

Anyhow, the bitmaps will always be monochrome, and they are currently being saved as *.bmp, with a bit depth of 1, and a maximum file size of 572kb.

I can easily create a Windows program, which will send the color of each pixel serially to the Propeller, however I would prefer not to be tied to a PC. Although, the original bitmaps will need a little preprocessing, so I will be writing a Windows program for this, and I can also do additional modifications to the files during the preprocessing stage, if necessary.

My thoughts are to save the *.bmp files to an SD card and read the pixel information off of the card with the Propeller, however I do not yet know if this is possible or if there is already existing code to perform this task.

To sum it up, I want to be able to read a bmp file from an SD card and represent black and white colors with 1s and 0s, or equivalency thereof.

Bruce





Novel Solutions - http://www.novelsolutionsonline.com/ - Machinery Design • - • Product Development
"Necessity is the mother of invention." - Author unknown.

«134

Comments

  • 94 Comments sorted by Date Added Votes
  • kwinnkwinn Posts: 7,494
    edited April 3 Vote Up0Vote Down
    idbruce wrote: »

    LOL "represent black and white colors with 1s and 0s, or equivalency thereof."

    Couldn't help chuckling at this. Sounds a lot like the difficulty I have putting ideas I can picture so clearly into words others can understand.

    In science there is no authority. There is only experiment.
    Life is unpredictable. Eat dessert first.
  • kwinn

    :)

    Even though I confuse myself at times, with my thoughts, I make a worthy attempt, to make my thoughts clear to others, so that they may be confused also :)


    Novel Solutions - http://www.novelsolutionsonline.com/ - Machinery Design • - • Product Development
    "Necessity is the mother of invention." - Author unknown.

  • If you know the bitmaps resolution in advance, after skipping the header 1bpp BMP file are simple planar bitmaps.

    Suppose your image is 640x480: the raw bitmap is then 38400 bytes.

    Now, to find out the size of the header, save one empty 640x480x1 image using Paint or any other app (I always do this because I'm not sure if there are optional fields in the header, so size might differ depending on the program used).

    Suppose you get 38520 bytes.

    So you simply open the file, seek +120 from start, start reading from there (80 bytes per row in the example above).

    Be warned that row order is bottom-to-top.

    Hope this helps.
    Alessandro
  • idbruceidbruce Posts: 5,515
    edited April 3 Vote Up0Vote Down
    Earlier today, I was looking at the PBM file format, and tossed the idea aside. After creating this thread, yeti sent me a message, to suggest the PBM file format, and so I took another look at the file format.

    Upon taking a second look and thinking about it a bit, I came to the conclusion that it should be quite easy to convert a BMP file to PBM file format.

    With a PBM file saved to an SD card, it should be quite easy for the Propeller to parse this file and determine whether a pixel should be white or black.

    Solution in the works :)


    Novel Solutions - http://www.novelsolutionsonline.com/ - Machinery Design • - • Product Development
    "Necessity is the mother of invention." - Author unknown.

  • idbruceidbruce Posts: 5,515
    edited April 5 Vote Up0Vote Down
    Well here is a screen shot of my program in it's current state. First I worked on a large portion of the programming, and then I decided to create a simple interface for adding more options. At this point, it has the ability to work on huge *.bmp files, which need to be left aligned, with or without alternating scan lines.

    I will finish programming the other available options later this evening, such as mirrored images, mirrored images with right hand alignment, and mirrored images with right hand alignment and alternating scan lines :)

    The output files contain three characters, which are 0, 1, and LF, with a *.plt file extension.

    I can now see where this program could be very handy for LED displays.
    629 x 687 - 86K


    Novel Solutions - http://www.novelsolutionsonline.com/ - Machinery Design • - • Product Development
    "Necessity is the mother of invention." - Author unknown.

  • If you don't have the windows bitmap file structure info already,
    you can find it here:

    https://msdn.microsoft.com/en-us/library/dd183391(v=vs.85).aspx

  • JasonDorieJasonDorie Posts: 1,851
    edited April 5 Vote Up0Vote Down
    For speed and compactness, if you know that your images are always going to be 1-bit, you could write, in binary:
    Header:
    (8 bits each): Width_Lo, Width_Hi, Height_Lo, Height_Hi  (order of bytes isn't specifically important, as long as you pick one)
    
    Body:
    (8 bits per 8 pixels) x (Width rounded up to 8 pixels) x (Height)
    

    That gives you about 8:1 compression, would reduce the time to process the data (because reading from an SD is slow) and is still pretty trivial to display.
  • @JaanDoh
    If you don't have the windows bitmap file structure info already,
    you can find it here:

    Hmmmm...... I wouldn't be able to process bitmap files like I am, if I didn't already have that information. But thanks anyway.

    @JasonDorie

    My intent was to get the programming done as quick as possible. I am using a very old version of Visual C++ 6.0 to write the program and taking full advantage of the MFC classes, but more specifically, I am using the CString class for writing to the files and string reversal.

    Basically, I am much better at working with strings, which makes it much faster for me to write the program this way, and that is why I went this route. The characters will eventually have to be compared and converted to a number, which will slow things down quite a bit, but my thoughts are to read and compare the SD characters in a separate cog, and pump numbers into a queue for processing.

    However, I am always open to new and different ideas, and perhaps I could do a CString to binary conversion, after all the bitmap processing is completed. Or once the text file is written, could I then convert that text file to binary, because at that point, the data would already be formatted in the proper order?



    Novel Solutions - http://www.novelsolutionsonline.com/ - Machinery Design • - • Product Development
    "Necessity is the mother of invention." - Author unknown.

  • The code to queue up stream of bits for output to a file should be pretty simple.

    This is C-ish pseudocode, and assumes "myString" is your string of 0's and 1's, presumably for the current line of pixels:
    char BitRack, BitCount;
    
    BitCount = BitRack = 0;
    for( int i=0; i<length(myString); i++ )
    {
      char stringChar = myString.GetCharAt( i );  // get the i'th character
    
      if( stringChar == '1' )
        BitRack |= (1 << BitCount);    // mask in a 1 bit (if stringChar was '0' it leaves the target bit as 0 too
    
      BitCount++;
      if( BitCount == 8 )  // once we accumulate 8 bits...
      {
        outputFile.WriteChar( BitRack );
        BitRack = 0;
        BitCount = 0;
      }
    }
    
    // write any remaining bits (rounds to nearest 8 bits)
    if( BitCount != 0 ) {
      outputFile.WriteChar( BitRack );
    }
    

    Decoding them at runtime would be similar. In fact, you could access any individual bit in a horizontal line with:
    int GetBit( int N, char * ArrayOfBits )
    {
      // slower version
      int bytePos = N / 8;  // which byte contains our bit?
      int bitPos = N % 8;  // position of the bit in a particular byte
    
      // faster version
      int bytePos = N >> 3;  // shift by 3 is divide by 8
      int bitPos = N & 7;  // AND 7 is remainder/modulo of divide by 8
    
      // grab the right byte, shift the bit into place, AND it with 1 to return it by itself
      return (ArrayOfBits[bytePos] >> bitPos) & 1;
    }
    
  • @JasonDorie

    That's very cool and thank you very much! I should have no problem inserting that code into my program. It looks pretty cut and dry. In fact, I will just provide program options to create either file type or both.

    The beauty of your code is that it will utilize my formatted strings, which makes it very easy on me at this point.

    I just thought of something. What is the best way to incorporate the LF into your code? I am using LF to either indicate the advancement to the next scan line or a movement of the Y axis.


    Novel Solutions - http://www.novelsolutionsonline.com/ - Machinery Design • - • Product Development
    "Necessity is the mother of invention." - Author unknown.

  • JasonDorieJasonDorie Posts: 1,851
    edited April 6 Vote Up0Vote Down
    The above code assumes that you know in advance how long the lines are when you're decoding at runtime, which is why I suggested putting the line length / image height into a header.

    Files written in ASCII / text mode don't like having raw 0 bytes written into them - that's usually interpreted as an end-of-file marker, and the above code that packs bits together will end up emitting 0 bytes when you have 8 sequential clear pixels, so you should only use it when writing to a binary file. I wrote it to allow you to still use your strings right up to the point where you write the file.

    So your runtime, using the GetBit() function above, would look something like this:
      int width = bytes[0] + (bytes[1] << 8 );
      int height = bytes[2] + (bytes[3] << 8 );
    
      char * lineBytes = (bytes + 4);  // start of the actual pixel data is 4 bytes in
    
      for( int y=0; y<height; y++ )
      {
        for( int x=0; x<width; x++ )
        {
          int pixelValue = GetBit( x, lineBytes );
    
          // do that voodoo that you do (use the pixel here)
        }
    
        lineBytes += width >> 3;  // move past this line of bits
        if( (width & 7) != 0 )    // if the line is not an integer multiple of 8 pixels...
          lineBytes++;            // move another byte (this is the round-to-next-8 part)
    }
    

    Hopefully that's fairly easy for you. If you're more comfortable with a text-based file just do that - the only reason I suggested this was that it would likely be faster to get onto the display from the SD card. If your display is mono you might even be able to just blast the bytes directly to it.
  • @JasonDorie
    The above code assumes that you know in advance how long the lines are when you're decoding at runtime, which is why I suggested putting the line length / image height into a header.

    :)

    Okay, I see your point about the header. However, I really didn't need it with my previous plan. However, as you know, I like speed, so I will add a header and your code.
    I wrote it to allow you to still use your strings right up to the point where you write the file.

    I love it! It will fit right in.

    Thanks again Jason

    Controlling Propeller pins with this data at runtime should be interesting :)


    Novel Solutions - http://www.novelsolutionsonline.com/ - Machinery Design • - • Product Development
    "Necessity is the mother of invention." - Author unknown.

  • With all the mirrors, flips, and alternating lines, this programming effort was just a tiny bit confusing, but I believe I got it all figured out, and the text version should be ready. :)

    I am now onto Jason's version. Hopefully I can figure this all out. Everything seems fairly easy, except the header.

    I really should know how to do this already :(


    Novel Solutions - http://www.novelsolutionsonline.com/ - Machinery Design • - • Product Development
    "Necessity is the mother of invention." - Author unknown.

  • If the code is actually in C/C++, you can likely just do this:
      // Should use a full path here, and check that "fileHandle" is not null on return
      FILE * fileHandle = fopen( "MyImageFile", "wb" );  // open for writing binary data, overwrite if file exists
    
      fputc( width & 255, fileHandle );          // write the low byte of the width
      fputc( (width >> 8 ) & 255, fileHandle );  // write the high byte of the width
      fputc( height & 255, fileHandle );         // ... ditto for the height
      fputc( (height >> 8 ) & 255, fileHandle );
    
      // output code for pixels goes here
    
      fclose( fileHandle );  // close the file
    
  • idbruceidbruce Posts: 5,515
    edited April 7 Vote Up0Vote Down
    Jason

    After translating your code to the following code below, I kept getting CFileException's until I added the & symbol before BitRack, to make it &BiitRack. 'DOH'

    I can now write the bits to a file. :)

    The header code also helps tremendously. I would have spent hours trying to figure that out.

    I am now onto translating your header code to MFC.

    Thank you very much Jason!!!
    void CBrd2PltDlg::WriteBitmapsBits(CString* strBitmapBitArray, CFile* outputFile)
    {
    	char BitRack;
    	char BitCount;
    
    	int nStringLength;
    
    	BitCount = 0;
    	BitRack = 0;
    
    	nStringLength = strBitmapBitArray->GetLength();	
    
    	for(int i = 0; i < nStringLength; i++)
    	{
    		// Get the i'th character
    		char stringChar = strBitmapBitArray->GetAt(i);
    
    		if(stringChar == '1')
    		{
    
    			// If stringChar is a '1', mask in a 1 bit
    			// otherwise if stringChar is a '0', it leaves
    			// the target bit as a 0
    			BitRack |= (1 << BitCount);
    		}
    
    		BitCount++;
    
    		// Accumulate 8 bits before writing to the output file
    		if(BitCount == 8)
    		{
    			outputFile->Write(&BitRack, sizeof(BitRack));
    
    			BitRack = 0;
    			BitCount = 0;
    		}
    	}
    
    	// Write any remaining bits rounded to the nearest 8 bits
    	if(BitCount != 0)
    	{
    		outputFile->Write(&BitRack, sizeof(BitRack));
    	}
    }
    


    Novel Solutions - http://www.novelsolutionsonline.com/ - Machinery Design • - • Product Development
    "Necessity is the mother of invention." - Author unknown.

  • Wow - I'm surprised the syntax was as close as it was.

    If your strBitmapBitArray represents a single horizontal row then the code you have is fine as it is. If the function is intended to output the whole bitmap at once, I'd suggest reworking it a little, like this, to get the per-scanline padding right. (it won't actually matter if the image is a multiple of 8 pixels wide):
    void CBrd2PltDlg::WriteBitmapsBits(CString* strBitmapBitArray, int width, int height, CFile* outputFile)
    {
    	char BitRack;
    	char BitCount;
    
    	int nStringLength;
    
    	BitCount = 0;
    	BitRack = 0;
    
    	nStringLength = strBitmapBitArray->GetLength();
    	assert( nStringLength == width * height );	// just to double check...
    
    	// write the width / height header here...
    	char val;
    	val = (char)width;
    	outputFile->write( &val, sizeof(char));
    	val = (char)(width >> 8);
    	outputFile->write( &val, sizeof(char));
    
    	val = (char)height;
    	outputFile->write( &val, sizeof(char));
    	val = (char)(height >> 8);
    	outputFile->write( &val, sizeof(char));
    
    	// now write the scanline data...
    
    	int charIndex = 0;	// keep track of which string character we're outputting...
    	for( int y=0; y<height; y++ )
    	{
    		for(int x = 0; x<width; x++ )
    		{
    			// Get the i'th character
    			char stringChar = strBitmapBitArray->GetAt(charIndex);
    
    			if(stringChar == '1')
    			{
    
    				// If stringChar is a '1', mask in a 1 bit
    				// otherwise if stringChar is a '0', it leaves
    				// the target bit as a 0
    				BitRack |= (1 << BitCount);
    			}
    
    			BitCount++;
    
    			// Accumulate 8 bits before writing to the output file
    			if(BitCount == 8)
    			{
    				outputFile->Write(&BitRack, sizeof(BitRack));
    
    				BitRack = 0;
    				BitCount = 0;
    			}
    
    			charIndex++;	// move on to the next image byte in the source
    		}
    
    		// Write any remaining bits rounded to the nearest 8 bits  **per horizontal row**
    		if(BitCount != 0)
    		{
    			outputFile->Write(&BitRack, sizeof(BitRack));
    		}
    	}
    }
    
  • idbruceidbruce Posts: 5,515
    edited April 7 Vote Up0Vote Down
    Jason
    Wow - I'm surprised the syntax was as close as it was.

    Yea, you were pretty much right on the money :) Certainly made it easy on me :)
    If your strBitmapBitArray represents a single horizontal row then the code you have is fine as it is.

    Yep... That code is just meant to write a single line. It was a lot easier to do that way then to start rewriting a bunch of code. I now basically have two versions, separated into two main functions, with each version controlled by a separate button, one for text and one for binary output.

    In the text version, I was using the CStdioFile class for writing to the files, because of the simplicity provided by the class member function WriteString. However, for the binary version, I switched over to the CFile class for writing to the files. Of course, these two different classes have similar, but different file openers, which is the main reason behind separate functions. Since the CFile class does not have a WriteString class member function, I decided to write it as shown above. By doing so, I could basically just copy the text version, remove all references to line feeds, and replace all calls to WriteString with calls to WriteBitmapsBits(..., ...) shown above.

    Easy, peasy, Japaneasy (With your help of course)


    Novel Solutions - http://www.novelsolutionsonline.com/ - Machinery Design • - • Product Development
    "Necessity is the mother of invention." - Author unknown.

  • idbruceidbruce Posts: 5,515
    edited April 8 Vote Up0Vote Down
    Jason

    You said...
    For speed and compactness, if you know that your images are always going to be 1-bit, you could write, in binary:........That gives you about 8:1 compression

    For a 1795 X 1622 bitmap image, when stored on a Windows OS, the text version weighs in at 2847 KB, and the binary version weighs in at 357 KB.

    2847 KB / 357 KB = 7.97 (rounded) compression

    WOW... You're good!!! You seen it all, right from the start.

    Anyhow, I stole the header portion out of your last snippet (rewritten to my style) and added it to the following function. This is the function that calls the previous function that I posted. Between the two functions, you can clearly see how I utilized the help and code that you provided.
    void CBrd2PltDlg::OnCreateBinaryPlot() 
    {
    	CBitmap bitmap;
    	HBITMAP hBitmap;
    	HBITMAP hInvertedBitmap;
    
    	BOOL bLeftHandAlternate;
    	BOOL bMirror;
    	BOOL bMirrorRightHandAligned;
    	BOOL bRightHandAlternate;
    
    	BOOL bBitmapAttached;
    
    	// Set initial value of this flag
    	bBitmapAttached = FALSE;
    
    	// Check all check boxes and set the necessary flags
    	if(m_CkLeftHandAlternate.GetCheck() == 1)
    	{
    		bLeftHandAlternate = TRUE;
    	}
    	else
    	{
    		bLeftHandAlternate = FALSE;
    	}
    
    	if(m_CkMirror.GetCheck() == 1)
    	{
    		bMirror = TRUE;
    	}
    	else
    	{
    		bMirror = FALSE;
    	}
    
    	if(m_CkMirrorRightHandAligned.GetCheck() == 1)
    	{
    		bMirrorRightHandAligned = TRUE;
    	}
    	else
    	{
    		bMirrorRightHandAligned = FALSE;
    	}
    
    	if(m_CkRightHandAlternate.GetCheck() == 1)
    	{
    		bRightHandAlternate = TRUE;
    	}
    	else
    	{
    		bRightHandAlternate = FALSE;
    	}
    
    	// Load the bitmap indicated by the user
    	hBitmap = (HBITMAP)LoadImage(NULL, strBmpFilePath, 
    		IMAGE_BITMAP, 0, 0, LR_LOADFROMFILE | LR_CREATEDIBSECTION);
    
    	// If the mirror flag has been set, then set the direction of the mirror,
    	// create a mirrored bitmap, and attach it to a CBitmap.  Otherwise attach
    	// an uninverted bitmap to a CBitmap.  Either way, set a flag to indicate success
    	if(bMirror == TRUE && bMirrorRightHandAligned == FALSE)
    	{
    		hInvertedBitmap = GetInvertedBitmap(hBitmap, FALSE);
    
    		if(bitmap.Attach(hInvertedBitmap) != 0)
    		{
    			bBitmapAttached = TRUE;
    		}
    	}
    	else if(bMirror == TRUE && bMirrorRightHandAligned == TRUE)
    	{
    		hInvertedBitmap = GetInvertedBitmap(hBitmap, TRUE);
    
    		if(bitmap.Attach(hInvertedBitmap) != 0)
    		{
    			bBitmapAttached = TRUE;
    		}
    	}
    	else
    	{
    		if(bitmap.Attach(hBitmap) != 0)
    		{
    			bBitmapAttached = TRUE;
    		}
    	}
    
    	if(bBitmapAttached == TRUE)
    	{
    		
    		int nColors;
    
    		BITMAP bmp;		
    
    		bitmap.GetBitmap(&bmp);		
    
    		nColors = 1 << (bmp.bmPlanes * bmp.bmBitsPixel);
    
    		if(nColors > 2)
    		{
    			MessageBox("Monochrome bitmaps only", "ERROR", MB_ICONEXCLAMATION);
    		}
    		else
    		{
    			int bmWidth;
    			int bmHeight;
    			int nX;
    			int nY;
    
    			char chWidth;
    			char chHeight;
    
    			CDC memDC;
    			CBitmap* pOldBmp;
    
    			CFile BinaryPlotFile;
    			BOOL bOpenBinaryPlotFile;
    
    			CString strPltImageFile;
    
    			bmWidth = bmp.bmWidth;
    			bmHeight = bmp.bmHeight;			
    
    			memDC.CreateCompatibleDC(NULL);
    			pOldBmp = memDC.SelectObject(&bitmap);
    
    			bOpenBinaryPlotFile = BinaryPlotFile.Open(strBinPltFilePath,
    				CFile::modeReadWrite | CFile::modeCreate, NULL);
    
    			if(bOpenBinaryPlotFile != 0)
    			{
    				// Write the bitmap header here
    				//////////////////////////////////////////
    				// First the bitmap width
    				chWidth = (char)bmWidth;
    				BinaryPlotFile.Write(&chWidth, sizeof(chWidth));
    
    				chWidth = (char)(bmWidth >> 8);
    				BinaryPlotFile.Write(&chWidth, sizeof(chWidth));
    
    				// then the bitmap height
    				chHeight = (char)bmHeight;
    				BinaryPlotFile.Write(&chHeight, sizeof(chHeight));
    
    				chHeight = (char)(bmHeight >> 8);
    				BinaryPlotFile.Write(&chHeight, sizeof(chHeight));
    				//////////////////////////////////////////
    			
    				for(nY = 0; nY < bmHeight; ++nY)
    				{
    					strPltImageFile.Empty();
    
    					for(nX = 0; nX < bmWidth; ++nX)
    					{
    						COLORREF clrPixel;
    
    						clrPixel = memDC.GetPixel(nX, nY);
    
    						if(clrPixel == RGB(0, 0, 0))
    						{
    							strPltImageFile += "1";
    						}
    						else
    						{
    							strPltImageFile += "0";
    						}
    					}
    
    					// Begin comparisons and arrange according to check box flags
    					
    					//Bottom Image Or Top Image Left Hand Aligned Without Alternating Lines
    					if((bLeftHandAlternate == FALSE && bMirror == TRUE && bMirrorRightHandAligned == FALSE)
    						|| (bLeftHandAlternate == FALSE && bMirror == FALSE))
    					{
    						WriteBitmapsBits(&strPltImageFile, &BinaryPlotFile);
    					}
    
    					//Bottom Image Or Top Image Left Hand Aligned With Alternating Lines
    					if((bLeftHandAlternate == TRUE && bMirror == TRUE) ||
    						(bLeftHandAlternate == TRUE && bMirror == FALSE))
    					{					
    						if((nY % 2) != 0)
    						{
    							strPltImageFile.MakeReverse();
    						}
    
    						WriteBitmapsBits(&strPltImageFile, &BinaryPlotFile);
    					}
    
    					//Bottom Image Right Hand Aligned Without Alternating Lines
    					if(bMirror == TRUE && bMirrorRightHandAligned == TRUE &&
    						bRightHandAlternate == FALSE)
    					{
    						strPltImageFile.MakeReverse();
    
    						WriteBitmapsBits(&strPltImageFile, &BinaryPlotFile);
    					}
    
    					//Bottom Image Right Hand Aligned With Alternating Lines
    					if(bMirror == TRUE && bMirrorRightHandAligned == TRUE &&
    						bRightHandAlternate == TRUE)
    					{
    						if((nY % 2) == 0)
    						{
    							strPltImageFile.MakeReverse();
    						}
    
    						WriteBitmapsBits(&strPltImageFile, &BinaryPlotFile);
    					}
    					// End comparisons
    				}
    
    				memDC.SelectObject(pOldBmp);			
    				
    				BinaryPlotFile.Close();
    			}
    			else
    			{
    				MessageBox("*.plt file could not be created", "ERROR", MB_ICONERROR);
    			}
    		}
    
    		DeleteObject(bitmap.Detach());	
    
    		MessageBox("The Plot File Is Ready", "File Created", MB_ICONINFORMATION);
    
    		SetDlgItemText(IDC_FILE_PATH, "");
    
    		m_CkLeftHandAlternate.EnableWindow(TRUE);
    		m_CkLeftHandAlternate.SetCheck(0);
    
    		m_CkMirror.EnableWindow(TRUE);
    		m_CkMirror.SetCheck(0);
    
    		m_CkMirrorRightHandAligned.EnableWindow(FALSE);
    		m_CkMirrorRightHandAligned.SetCheck(0);
    
    		m_CkRightHandAlternate.EnableWindow(FALSE);
    		m_CkRightHandAlternate.SetCheck(0);
    	}
    	else
    	{
    		MessageBox("A processing error has occured", "ERROR", MB_ICONERROR);
    	}
    }
    


    Novel Solutions - http://www.novelsolutionsonline.com/ - Machinery Design • - • Product Development
    "Necessity is the mother of invention." - Author unknown.

  • I assume this is for a display module that has its own memory? That 357kb would be a little large for the prop. :)

    And, happy to help.
  • idbruceidbruce Posts: 5,515
    edited April 8 Vote Up0Vote Down
    Jason
    I assume this is for a display module that has its own memory?
    

    :)

    I might be saying a little too much.... but....

    Com'on... Have you ever known me to really talk about displays or LEDs? Although the code could be used for displays, as I said, but you know me as the one who always talks about CNC. This code is for machine control.

    As I said earlier, the bits will be dumped into a queue, small portions at a time.


    Novel Solutions - http://www.novelsolutionsonline.com/ - Machinery Design • - • Product Development
    "Necessity is the mother of invention." - Author unknown.

  • idbruceidbruce Posts: 5,515
    edited April 8 Vote Up0Vote Down
    Jason

    Here's one for you....

    As mentioned in the previous post, the intent is machine control. However, the output generated could theoretically control displays.

    While working on both your version and mine, I have been tossing this idea around in my head (although I am certain that I am not the first to come up with it), of describing a monochrome bitmap with moves (white pixels), lines (black pixels), and next scan lines, to be machine specific. Well I decided to experiment with the idea this morning.
    For a 1795 X 1622 bitmap image, when stored on a Windows OS, the text version weighs in at 2847 KB, and the binary version weighs in at 357 KB.

    2847 KB / 357 KB = 7.97 (rounded) compression

    Here is a portion of my new machine code :)
    MNM48L1600M1646L1745MNM48L1599M1647L1745MNM48L1598M1648L1745MNM48L1597M1649L1745MNM48L1595M1650L1745MNM48L1594M1651L1745MNM48L1593M1652L1745MNM48L1592M1653L1745MNM48L1591M1654L1745MNM48L1590M1655L1745MNM48L1589M1656L1745MNM48L1588M1657L1745MNM48L1588M1658L1745MNM48L1586M1659L1745MNM48L1584M1660L1745MNM48L1583M1661L1745MNM48L1582M1662L1745MNM48L1581M1663L1745MNM48L1580M1664L1745MNM48L1579M1665L1745MNM48L1368M1393L1579M1665L1745MNM48L1364M1397L1577M1665L1745MNM48L1360M1400L1576M1665L1745MNM48L444M591L1356M1404L1575M1665L1745MNM48L198M222L441M595L1352M1408L1574M1665L1745MNM48L194M226L439M598L1349M1411L1573M1665L1745MNM48L191M229L438M599L1346M1414L1572M1665L1745MNM48L187M233L437M600L1345M1416L1571M1665L1745MNM48L184M236L437M601L1343M1418L1571M1665L1745MNM48L181M239L435M602L1341M1419L1569M1665L1745MNM48L178M242L434M603L1339M1421L1567M1665L1745MNM48L177M243L433M604L1337M1423L1566M1665L1745MNM48L175M245L432M605L1335M1425L1565M1665L1745MNM48L173M247L431M606L1334M1427L1564M1665L1745MNM48L172M202L219M248L430M607L1332M1372L1389M1429L1563M1665L1745MNM48L170M196L225M250L429M608L1330M1366L1395M1431L1562M1665L1745MNM48L168M192L229M252L428M609L1330M1362L1399M1431L1562M1665L1745MNM48L166M189L232M254L427M447L588M610L1328M1359L1402M1433L1560M1665L1745MNM48L165M186L235M255L426M445L590M611L1327M1356L1405M1434L1558M1665L1745MNM48L163M184L237M257L425M444L591M612L1326M1354L1407M1435L1557M1665L1745MNM48L162M181L240M258L424M443L592M613L1325M1351L1410M1436L1556M1665L1745MNM48L161M179L242
    

    There is still a couple flaws, but utilizing the same file as the size tests above, the complete file size for this version currently weighs in at 135 KB

    EDIT: However, perhaps parsing will take longer.
    EDIT: Coordinates are relative to current position


    Novel Solutions - http://www.novelsolutionsonline.com/ - Machinery Design • - • Product Development
    "Necessity is the mother of invention." - Author unknown.

  • Your version could be made smaller with few changes. Your moves look like they could all fit into a 12 bit number (0 to 4095), and you only have 3 commands.

    Make a sequence of shorts (16 bits) where the upper 2 bits are:
    00 = MN
    01 = M
    10 = L
    11= end-of-stream
    The next 14 bits are your move/line distance.

    If you're actually storing the numbers in your file as ascii, you'll have to "reassemble" then into numbers at runtime. Encoding them into shorts just means doing an & 0x3fff at runtime to get the numbers out. As text, your commands are, at largest, 1 character (move/line) + 4 chars (length), whereas the shorts version would be always 2 chars per command + length, so about 2/5 the size, best case. Not as easily human readable, but much easier to work with from the Prop, and smaller.
  • Also, if you ever encounter a case where your move distance is larger than you can encode, you can just pack two of them in a row.
  • idbruceidbruce Posts: 5,515
    edited April 8 Vote Up0Vote Down
    Jason

    I really like the shorts idea :)

    Actually the MN was one of the flaws I was referring to, although it got me pondering whether it would be useful or not. It occurred because of a missing accumulation in the main processing loop, and it represents an entire scan line of white pixels. In theory, there could have also been an LN, which would have represented an entire scan line of black pixels, however the test image had a white border.

    My thoughts are that if I attempt to turn these flaws into something useful, then I will have to keep count during run time, which I believe would be painful, as compared to just fixing the loop and letting it run.

    Once again, in theory, every M should contain at least one pixel and every L should contain at least one pixel.

    Thanks for the idea about the shorts :)


    Novel Solutions - http://www.novelsolutionsonline.com/ - Machinery Design • - • Product Development
    "Necessity is the mother of invention." - Author unknown.

  • @idbruce,

    I was somehow missing your posts, lurking here, and thinking what he is up to now?

    So really good to see you back!

    It took me a while to understand your reason to mirror odd lines in the image, until I read '*.plt", that gave you away!

    Sure when plotting (or cutting) a bitmap on a x-y table, that reversal makes a lot of sense.
    Once again, in theory, every M should contain at least one pixel and every L should contain at least one pixel

    You could use a count of 0 to signal a complete black line or a complete white line. But I think with the short version you can just add a couple of instruction and will not lose much speed or gain much space in your file doing that.

    On the other hand , not allowing a count of 0 while creating the file, makes a lot of sense. Can not happen when you just create the output file from a bitmap of the right size. But if you want to put in scaling, of the bitmap before outputting to the CNC - then it might prevent a lot of unused not-movements.

    Enjoy!

    Mike


    I am just another Code Monkey.

    A determined coder can write COBOL programs in any language. -- Author unknown.

    The key words "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT", "SHOULD", "SHOULD NOT", "RECOMMENDED", "MAY", and "OPTIONAL" in this post are to be interpreted as described in RFC 2119.
  • idbruceidbruce Posts: 5,515
    edited April 8 Vote Up0Vote Down
    Mike
    You could use a count of 0 to signal a complete black line or a complete white line.

    That is really quite a good idea, providing I decide to add the extra command.
    then it might prevent a lot of unused not-movements.

    Which is one of the pros for M0, or MN.

    However the same result could be achieved by adding a header and comparing the very beginning of each new line against bitmap width. For example, let's assume a bitmap width of 10 pixels.

    If an entire scan line is represented by M10N, the M10 would be the first command parsed for that scan line
    if((command == 'M') && (command_value == bitmap_width)
    {
         // Disregard the current command and value, and grab the next short, which will always be N
    


    Novel Solutions - http://www.novelsolutionsonline.com/ - Machinery Design • - • Product Development
    "Necessity is the mother of invention." - Author unknown.

  • On the other hand, it may be beneficial to keep count of the processed pixels. For example
    if((command == 'M')
    {
         if(M_command_value == bitmap_width)
         {
              // Disregard the current command and value, and grab the next short, which will always be N
              GrabNextShort()
         }
         else if(M_command_value + processed_pixels == bitmap_width)
              // Disregard the current command and value, and grab the next short, which will always be N
              GrabNextShort()
         }
    }
    

    This would or should eliminate a lot of unnecessary movement.


    Novel Solutions - http://www.novelsolutionsonline.com/ - Machinery Design • - • Product Development
    "Necessity is the mother of invention." - Author unknown.

  • idbruceidbruce Posts: 5,515
    edited April 9 Vote Up0Vote Down
    The more I think about the new proposed route, the more I realize how complicated it will be. I had forgotten about the string reversals, which means that the pixel count is also being reversed and is currently not accurate when alternating scan lines or when aligning images to the right. The program will need some serious reorganization to make this work.

    Additionally, the above pseudo-code would not be useful for plot files with alternating scan lines.

    EDIT: Okay, I was over thinking this problem. Instead of reorganizing the code, all I really have to do is create my own string reversal function, which reverses the commands, but not the values associated with them.


    Novel Solutions - http://www.novelsolutionsonline.com/ - Machinery Design • - • Product Development
    "Necessity is the mother of invention." - Author unknown.

  • JasonDorieJasonDorie Posts: 1,851
    edited April 9 Vote Up0Vote Down
    You can easily expand the header to include some extra flags, like whether this image has alternating scan lines or not.

    If you need more commands, you can "steal" another bit or two from the short. For example:
    000 : 0000000000000
      |             |
      |             +----- Length of move (13 bits = 0 to 8191 pixels)
      |
      +--------------- Command: 000 = Move, 001 = Line
                                010 = skip rest of line, 011 = fill rest of line
                                100, 101, 110 (unused)
                                111 = end of file
    

    If you even need to encode more than 8191 on or off pixels in a row, you just make two of them in a row:
    000_1111111111 = move 8191
    000_0000001111 = move 15 more

    This means your entire stream just contains a series of binary short values, written to the file like this:

    If your lines are less than 8192 in length, you technically don't need "fill/skip" line commands, because a "draw or skip" of that many pixels accomplishes the same thing. You could save a couple command bits by doing that, and be able to make your lines longer (like up to 16383).
      short myShort = (command << 13) | moveLength;
      outFile.write( &myShort, sizeof(short) );  // this assumes they are in the right endian order for the prop
    

    If the endianness is reversed on the Prop (I can't remember for sure) you'd insert this line in between the above two, but only in the PC code:
      myShort = ((myShort & 0xff) << 8 ) | ((myShort >> 8 ) & 0xff);  // swap hi/low bytes
    

    Also, I **hate** that 8 ) is read as 8) even in 'code' blocks on this board.
  • On the decode side, it'd look like this:
    for( int y=0; y<height; y++)
    {
      int x = 0;
      while( x < width )
      {
        short cmd = GetNextShort();
        short moveLen = cmd & 8191;
        cmd = (cmd >> 13) & 7;
    
        switch( cmd )
        {
          case 0:  // this is a move
            x += moveLen;
            break;
    
          case 1:  // this is a fill
            plot( x , x + moveLen );  // plot a horizontal line from here to x + moveLen
            break;
    
          case 2:  // skip to end of line
            x = width;
            break;
    
          case 3:  // fill to end of line
            plot( x, width-1 );  // plot for here to the end of the line
            break;
    
          case 7;  // we're finished
             y = height;
             x = width;
        }
      }
    }
    
Sign In or Register to comment.