tags:

views:

104

answers:

3

I am communicating with an instrument (remote controlling it) and
one of the things I need to do is to draw the instrument screen.

In order to get the screen I issue a command and the instrument
replies with an array of bytes that represents the screen.

Below is what the instrument manual has to say about converting the response to the actual screen:

The command retrieves the framebuffer data used for the display.
It is 19200 bytes in size, 2-bits per pixel, 4 pixels per byte arranged as
320x240 characteres.
The data is sent in RLE encoded form.
To convert this data into a BMP for use in Windows, it needs to be
turned into a 4BPP. Also note that BMP files are upside down relative
to this data, i.e. the top display line is the last line in the BMP.

I managed to unpack the data, but now I am stuck on how to actually
go from the unpacked byte array to a bitmap.

My background on this is pretty close to zero and my searches
have not revealed much either.

I am looking for directions and/or articles I could use to help me
undestand how to get this done.

Any code or even pseudo code would also help. :-)

So, just to summarize it all:

How to convert a byte array of 19200 bytes in size, where
each byte represents 4 pixels (2 bits per pixel),
to a bitmap arranged as 320x240 characters.

Thanks in advance.

+1  A: 

If you have two bits per pixel, for each pixel you have 4 different possible colors. Probably the colors are indexed or just hardcoded (i.e. 0 means black, 1 white, etc).

Don't know if this is of much help ( I don't know what bitmap object you are using, but perhaps it has a regular RGB or ARGB scheme with 1 byte per channel), but in pseudo-actionscript, I think you should do something like this.

//  80 -> 320 / 4
for(var x:int = 0; x < 80; x++) {
    for(var y:int = 0; y < 240; y++) {
        var byteVal:int = readByte();

        var px_1:int = (byteVal >> 6) & 0x03;
        var px_2:int = (byteVal >> 4) & 0x03;
        var px_3:int = (byteVal >> 2) & 0x03;
        var px_4:int = (byteVal) & 0x03;

        //  map your pixel value to ARGB 
        px_1 = getPixelValue(px_1);
        px_2 = getPixelValue(px_2);
        px_3 = getPixelValue(px_3);
        px_4 = getPixelValue(px_4);     
        //  assuming setPixel(x,y,pixelValue)
        setPixel((x * 4), y, px_1);
        setPixel((x * 4) + 1, y, px_2);
        setPixel((x * 4) + 2, y, px_3);
        setPixel((x * 4) + 3, y, px_4);


    }
}

function getPixelValue(idx:int):uint {
    //   just an example...
    switch(idx) {
        case 0:     return 0xff000000;  //  black
        case 1:     return 0xffffffff;  //  white
        case 2:     return 0xffff0000;  //  red
        case 3:     return 0xff0000ff;  //  blue
    }
}

The above code, suffice it to say, is just to give you an idea (hopefully!) and is based on some assumptions like how these four pixels are stored in a byte.

Hope it makes sense.

Juan Pablo Califano
Your code makes a lot of sense.
Klinger
+2  A: 

To do something like this, you'll want a routine like this:

Bitmap ConvertToBitmap(byte[] data, int width, int height)
{
    Bitmap bm = new Bitmap(width, height, PixelFormat.Format24bppRgb);
    for (int y=0; y < height; y++) {
        for (int x=0; x < width; x++) {
            int value = ReadPixelValue(data, x, y, width);
            Color c = ConvertValToColor(value);
            bm.SetPixel(x, y, c);
        }
    }
    return bm;
}

from here, you need ReadPixelValue and ConvertValToColor.

static int ReadPixelValue(byte[] data, int x, int y, width)
{
    int pixelsPerByte = 4;
    // added the % pixelsPerByte to deal with width not being a multiple of pixelsPerByte,
    // which won't happen in your case, but will in the general case
    int bytesPerLine = width / pixelsPerByte + (width % pixelsPerByte != 0 ? 1 : 0);
    int index = y * bytesPerLine + (x / pixelsPerByte);
    byte b = data[index];

    int pixelIndex = (x % pixelsPerByte) * 2;
    // if every 4 pixels are reversed, try this:
    // int pixelIndex = 8 - (x % pixelsPerByte) * 2;
    return ((int b) >> pixelIndex) & 0x3;        
}

Basically, I pull each set of two bits out of each byte and return it as an int.

As for converting to color that's up to you how to make heads or tail of the 4 values that come back. Most likely you can do something like this:

static Color[] _colors = new Color[] { Color.Black, Color.Red, Color.Blue, Color.White };
static Color ConvertValToColor(int val)
{
    if (val < 0 || val > _colors.Length)
        throw new ArgumentOutOfRangeException("val");
    return _colors[val];
}
plinth
Thanks a bunch plinth. It worked.I did have to use the "reversed" code.It's all black magic to me and so I have some learning to do. :-)But, it works, and that's just great.
Klinger
A: 

I dont know if this helps, I use this for data I got from a rare old hardware:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Drawing;
using System.IO;

namespace ConsoleApplication1
{
    class Program
    {
        static void Main(string[] args)
        {
            byte[] imageBytes = new byte[19201];
            //(Fill it with the data from the unit before doing the rest).
            Bitmap bmp_workarea = new Bitmap(320, 240, System.Drawing.Imaging.PixelFormat.Format4bppIndexed);
            Image newImage = Image.FromStream(new MemoryStream(imageBytes));
            using (Graphics gr = Graphics.FromImage(bmp_workarea))
            {
                gr.DrawImage(newImage, new Rectangle(0, 0, bmp_workarea.Width, bmp_workarea.Height));
            }
            //now you can use newImage, for example picturebox1.image=newimage

        }
    }
}
Stefan
When I see the other answers, maybe I have misunderstand the question...
Stefan
His data is 2 bits per pixel, not 4 bits per pixel so this will fail.
plinth