Reading monochrome bitmap pixel colors - c#

I don't know a better title, but I'll describe the problem.
A piece of hardware we use has the ability to display images.
It can display a black and white image with a resolution of 64 x 256.
The problem is the format of the image we have to send to the device.
It is not a standard bitmap format, but instead it is simply an array of
bytes representing each pixel of the image.
0 = black, 1 = white.
So if we had an image with the size: 4 x 4 the byte array might look something like:
1000 0100 0010 0001
And the image would look like:
Bitmap http://www.mediafire.com/imgbnc.php/6ee6a28148d0170708cb10ec7ce6512e4g.jpg
The problem is that we need to create this image by creating a monochrome bitmap
in C# and then convert it to the file format understood by the device.
For example, one might to display text on the device. In order to do so he would
have to create a bitmap and write text to it:
var bitmap = new Bitmap(256, 64);
using (var graphics = Graphics.FromImage(bitmap))
{
graphics.DrawString("Hello World", new Font("Courier", 10, FontStyle.Regular), new SolidBrush(Color.White), 1, 1);
}
There are 2 problems here:
The generated bitmap isn't monochrome
The generated bitmap has a different binary format
So I need a way to:
Generate a monochrome bitmap in .NET
Read the individual pixel colors for each pixel in the bitmap
I have found that you can set the pixel depth to 16, 24, or 32 bits, but haven't found monochrome and I have no idea how to read the pixel data.
Suggestions are welcome.
UPDATE: I cannot use Win32 PInvokes... has to be platform neutral!
FOLLOW UP: The following code works for me now. (Just in case anybody needs it)
private static byte[] GetLedBytes(Bitmap bitmap)
{
int threshold = 127;
int index = 0;
int dimensions = bitmap.Height * bitmap.Width;
BitArray bits = new BitArray(dimensions);
//Vertically
for (int y = 0; y < bitmap.Height; y++)
{
//Horizontally
for (int x = 0; x < bitmap.Width; x++)
{
Color c = bitmap.GetPixel(x, y);
int luminance = (int)(c.R * 0.3 + c.G * 0.59 + c.B * 0.11);
bits[index] = (luminance > threshold);
index++;
}
}
byte[] data = new byte[dimensions / 8];
bits.CopyTo(data, 0);
return data;
}

I'd compute the luminance of each pixel a then compare it to some threshold value.
y=0.3*R+0.59G*G+0.11*B
Say the threshold value is 127:
const int threshold = 127;
Bitmap bm = { some source bitmap };
byte[,] buffer = new byte[64,256];
for(int y=0;y<bm.Height;y++)
{
for(int x=0;x<bm.Width;x++)
{
Color c=source.GetPixel(x,y);
int luminance = (int)(c.R*0.3 + c.G*0.59+ c.B*0.11);
buffer[x,y] = (luminance > 127) ? 1 : 0;
}
}

I don't know C#. There are possibly many ways to do it. Here is a simple way.
Create a blank black bitmap image of size equal to your device requirement. Draw on it whatever you wish to draw like text, figures etc.
Now threshold the image i.e. set the pixel of image below an intensity value to zero else set it to. e.g. set all intensity values > 0 to 1.
Now convert to the format required by your device. Create a byte array of the size (64 * 256)/8. Set the corresponding bits to 1 where the corresponding pixel values in earlier bitmap are 1, else reset them to 0.
Edit: Step 3. Use bitwise operators to set the bits.

You shouldn't use GetPixel method of your bitmap to convert entire bitmap from one format to another! This will be ineffective. Instead you should use LockBits method to get access to a copy of image buffer and convert it into desired format. I'm not completely sure about converting it to monochrome but there is Format1bppIndexed value in PixelFormat enumeration which may help you.

You may try to supply a pixelformat in the constructor:
var bitmap = new Bitmap(256, 64, PixelFormat.Format1bppIndexed);
When I did draw monochrome bitmaps on other platforms I sometimes had
to disable antialiasing or the rendered text would not show up:
graphics.SmoothingMode=SmoothingMode.None;
YMMV.

Bitmap has a GetPixel method that you can use. This will let you draw on the Bitmap and later convert it to the format that you need.
Bitmaps in Windows forms (ie, accessed through Graphics.FromImage) are 24 bpp (maybe 32? It's too early and I honestly forget). Nonetheless, GetPixel returns a Color object, so the bit depth of the bitmap is immaterial. I suggest you write your code like this:
MyBitmapFormat ToMyBitmap(Bitmap b)
{
MyBitmapFormat mine = new MyBitmapFormat(b.Width, b.Height);
for (int y=0; y < b.Height; y++) {
for (int x=0; x < b.Width; x++) {
mine.SetPixel(x, y, ColorIsBlackish(b.GetPixel(x, y)));
}
}
}
bool ColorIsBlackish(Color c)
{
return Luminance(c) < 128; // 128 is midline
}
int Luminance(c)
{
return (int)(0.299 * Color.Red + 0.587 * Color.Green + 0.114 * Color.Blue);
}
This process is called simple thresholding. It's braindead, but it will work as a first cut.

thanks for the above code - I'm trying to convert a monochrome image into a 2d array where 1-black 0-white however I'm having some trouble - I used your code to load an 8x8 bmp image, and am outputting its contents to a textbox by using
myGrid =GetLedBytes(myBmp);
for (int x = 1; x < 8; x++)
{
textBox1.Text = textBox1.Text + Convert.ToString(myGrid[x])+ " ";
}
however I get this as a result in the textbox:
225 231 231 231 231 129 255
how do I get it so it's 0's and 1's?

This chap has some code that creates a mono bitmap. The SaveImage sample is the one of interest.

Related

Plot values of a line from a grayscale image

I am trying to take a grayscale bitmap and extract a single line from it and then graph the gray values. I got something to work, but I'm not really happy with it. It just seems slow and tedious. I am sure someone has a better idea
WriteableBitmap someImg; //camera image
int imgWidth = someImg.PixelWidth;
int imgHeight = someImg.PixelHeight;
Int32Rect rectLine = new Int32Rect(0, imgHeight / 2, imgWidth, 1); //horizontal line half way down the image as a rectangle with height 1
//calculate stride and buffer size
int imgStride = (imgWidth * someImg.Format.BitsPerPixel + 7) / 8; // not sure I understand this part
byte[] buffer = new byte[imgStride * rectLine.Height];
//copy pixels to buffer
someImg.CopyPixels(rectLine, buffer, imgStride, 0);
const int xGraphHeight = 256;
WriteableBitmap xgraph = new WriteableBitmap(imgWidth, xGraphHeight, someImg.DpiX, someImg.DpiY, PixelFormats.Gray8, null);
//loop through pixels
for (int i = 0; i < imgWidth; i++)
{
Int32Rect dot = new Int32Rect(i, buffer[i], 1, 1); //1x1 rectangle
byte[] WhiteDotByte = { 255 }; //white
xgraph.WritePixels(dot, WhiteDotByte, imgStride, 0);//write pixel
}
You can see the image and the plot below the green line. I guess I am having some WPF issues that make it look funny but that's a problem for another post.
I assume the goal is to create a plot of the pixel value intensities of the selected line.
The first approach to consider it to use an actual plotting library. I have used oxyplot, it works fine, but is lacking in some aspects. Unless you have specific performance requirements this will likely be the most flexible approach to take.
If you actually want to render to an image you might be better of using unsafe code to access the pixel values directly. For example:
xgraph.Lock();
for (int y = 0; y < imgHeight; y++){
var rowPtr = (byte*)(xgraph.BackBuffer + y * xgraph.BackBufferStride);
for(int x = 0; x < imgWidth; x++){
rowPtr[x] = (byte)(y < buffer[i] ? 0 : 255);
}
}
self.Unlock(); // this should be placed in a finally statement
This should be faster than writing 1x1 rectangles. It should also write columns instead of single pixels, and that should help making the graph more visible. You might also consider allowing arbitrary image height and scale the comparison value.
If you want to plot the pixel values along an arbitrary line, and not just a horizontal one. You can take equidistant samples along the line, and use bilinear interpolation to sample the image.

How do I get a partial image from a raw byte array of colors?

So I want to grab a partial image from a byte array of colors. The image is a unity logo that is 64x64 pixels. I want to grab a third of the image (Unity Logo). How would I traverse the byte array to get this image?
Unity Byte Array
assuming each byte is a single pixel (which is only true for 8-bit depth images), the bytes 0-63 are the first row, 64-127 are the second row, etc etc.
meaning that to find out the position of a pixel in the one-dimensional array, based on its two-dimensional coordinates in the image itself, you do
int oneDimPos = (y*64) + x;
if each pixel were 3 bytes (24-bit color depth), the conversion from 2dimensional to 1dimensional coordinates would be:
int oneDimPos = (y * 64 * 3) + (x * 3);
(so the most generic equation is:
int oneDimPos = (y * imageWidth * colorDepth) + (x * colorDepth);
and you need to keep this in mind and adjust the code accordingly. or even better, use this most generic version, and actually read the image width and its color depth from the asset you're using as source.
BEWARE: if the image is anything else than 8bits per pixel, this equation will, naturally, only give you the first, starting bit belonging to that pixel, and you still need to take care to actually also read the other ones that belong to that pixel
i'm gonna finish the answer assuming 8bit color depth, for simplicity, as well as so that you can't just copypaste the answer, but also have to understand it and re-shape it according to your specific needs ;)
)
meaning you can now do classic two nested loops for x and y:
List<byte> result = new List(); //i'm going to use list so i can just .Add each byte instead of having to calculate and allocate the final size in advance, and having to mess around with recalculating the index from the source array into the destination one, because i'm lazy
for(int x=0; x < 22; x++){ //no way for you to grab precise third since that boundary is in the middle of a pixel for an image 64pixels wide
for(int y = 0; y < 64; y++){ //we go all the way to the bottom
result.Add(sourceAsset.bytes[(y*64) + x]);
}
}
//now just convert the list to actual byte array
byte[] resultBytes = result.ToArray();
The original issue that I was having was not exactly the same as the question. I wanted to simplify it by having a byte array that everyone could take a look at. The byte array from Unity's website wasn't exactly what I was getting.
So I have 3 x 1080p portrait screen (1080 x 1920 pixels) with RGBA channels. I grabbed a screenshot from this and got a 24,883,200 size byte array.
Note, 3 * width(1080) * height(1920) * channels(4) = 24,883,200.
byte[] colors = new byte[24883200]; // Screenshot of 3x1080p screen.
byte[] leftThird = new byte[colors.Length / 3];
Array.Copy(colors, 0, leftThird, 0, colors.Length / 3); // Grab the first third of array
This is an issue because the colors array is read from top to bottom, left to right. So instead, you should read a portion of the 3 x 1080 x 4 channels.
int width = 1080 * 4; // 4 channels of colors (RGBA)
int fullWidth = width * 3; // Three screens
int height = 1920;
byte[] leftScreen = new byte[screenShotByteArray.Length / 3];
for(int i = 0; i < height; i++)
{
Array.Copy(screenShotByteArray, (i * fullWidth) + (offset * 4), leftScreen, i * width, width);
}

How to find that all pixels in an image are grey scale or has R,G,B equal value for each individual pixel

Please dont quote this How can I check the color depth of a Bitmap? or How to check if a picture is in grayScale
Because an image can be 24/32 bit per pixel even if all unique colors are grey scale or less than 256 and each pixel can be 24 or 32 bit.
How do I find that the pixel in an image Bitmap.GetPixel(x, y) has equal R,G,B vales so that I can find out if all pixels in an image lie in grey scale range. Because the R,G,B value of a grey scale pixel is same. Or is there any better way to find if the image is in grey scale?
I am writing a code to compress the size of a 16/24/32 bit image such that if the image has 256 unique colors then change it to 8 bit image and save it.
First I count unique colors in an image that has higher than 8 its per pixel.
If the unique colors in the image are less than or equal to 256 then
If all the unique colors are in greyscale range then convert it to greyscale
Otherwise if any color is not greyscale then convert image to 8 BPP
uint UniqueColors(Bitmap Bitmap)
{
try
{
List<int> lstColors = new List<int>();
if (null == Bitmap)
return 0;
for (int iCount = 0; iCount < Bitmap.Height; iCount++)
for (int iCounter = 0; iCounter < Bitmap.Width; iCounter++)
if (!lstColors.Contains(Bitmap.GetPixel(iCounter, iCount).ToArgb()))
lstColors.Add(Bitmap.GetPixel(iCounter, iCount).ToArgb());
Bitmap.Dispose();
return Convert.ToUInt32(lstColors.Count);
}
catch (Exception)
{
Bitmap.Dispose();
return 0;
}
}
And then:
if (256 >= UniqueColors(new Bitmap(string ImagePath)))
{
if (Is_Greyscale(new Bitmap(ImagePath))
Convert_To_Greyscale(ImagePath);
else
Convert_To_8Bits(ImagePath);
}
now I am stuck how do I find if each unique color in the image lies in the greyscae rage. By which I mean that each unique color has equal (R, G, B) values. Like R=G=B. How can I find it in my code line
Bitmap.GetPixel(iCounter, iCount).ToArgb()
Bitmap.GetPixel() returns a Color structure which has fields R, G and B so you can just compare them however you want.
Do note that using GetPixel() is very slow, but if you don't need speed it will do.

Reduce the gray scale bit representation of a pixel in C#

I am sorry if the question in the header is not descriptive enough. But, basically what my problem is the following.
I am taking Bitmap and making it gray scale. It works nice if i do not reduce the number of bits and I still use 8 bits. However, the point of the hw I have is to show how the image changes when I reduce the number of bits holding the information. In the example bellow I am reducing the binary string to 4 bits and then rebuilding the image again. The problem is that the image becomes black. I think is because the image has mostly gray values (in the 80's range) and when I am reducing the binary string I am left with black image only. It seems to me that i heave to check for lower and high gray scale values and then make the more light-gray go to white and dark gray go to black. In the end with 1 bit representation I should only have black and white image.Any idea how can i do that separation?
Thanks
Bitmap bmpIn = (Bitmap)Bitmap.FromFile("c:\\test.jpg");
var grayscaleBmp = MakeGrayscale(bmpIn);
public Bitmap MakeGrayscale(Bitmap original)
{
//make an empty bitmap the same size as original
Bitmap newBitmap = new Bitmap(original.Width, original.Height);
for (int i = 0; i < original.Width; i++)
{
for (int j = 0; j < original.Height; j++)
{
//get the pixel from the original image
Color originalColor = original.GetPixel(i, j);
//create the grayscale version of the pixel
int grayScale = (int)((originalColor.R * .3) + (originalColor.G * .59)
+ (originalColor.B * .11));
//now turn it into binary and reduce the number of bits that hold information
byte test = (byte) grayScale;
string binary = Convert.ToString(test, 2).PadLeft(8, '0');
string cuted = binary.Remove(4);
var converted = Convert.ToInt32(cuted, 2);
//create the color object
Color newColor = Color.FromArgb(converted, converted, converted);
//set the new image's pixel to the grayscale version
newBitmap.SetPixel(i, j, newColor);
}
}
return newBitmap;
}
As mbeckish said, it is easier and much faster to use ImageAttributes.SetThreshold.
One way to do it manually is to get the median value of the grayscale pixels in the image, and use that for the threshold between black and white.

RGBA Image format (as used by Scintilla)

I'm working with Scintilla (not ScintillaNET), and I'm trying to set the images used in the auto complete list. Scintilla requires a string of bytes/pixels in the RGBA format.
Scintilla's description of this RGBA format: (Quote from documentation)
The RGBA format allows translucency with an alpha value for each
pixel. It is simpler than XPM and more capable.
The data is a sequence of 4 byte pixel values starting with the pixels
for the top line, with the leftmost pixel first, then continuing with
the pixels for subsequent lines. There is no gap between lines for
alignment reasons.
Each pixel consists of, in order, a red byte, a green byte, a blue
byte and an alpha byte. The colour bytes are not premultiplied by the
alpha value. That is, a fully red pixel that is 25% opaque will be
[FF, 00, 00, 3F]
I expect that I'm misunderstanding the format explained above, the documentation is not very clear.
My Conversion Method:
I wrote this function to convert a PNG into this string of bytes:
public static string ConvertFromPNG(Bitmap PNG)
{
string rgba = "";
int pixWidth = PNG.Width;
int pixHeight = PNG.Height;
for (var y = 0; y < pixHeight; y++)
{
for (var x = 0; x < pixWidth; x++)
{
Color pix = PNG.GetPixel(x, y);
rgba += pix.R.ToString("X2") + pix.G.ToString("X2") + pix.B.ToString("X2") + pix.A.ToString("X2");
}
}
return rgba;
}
The Resulting Image:
But Scintilla is just showing the image as a grey box:
.
The image in question is a copy of one of Microsoft's Babel images: .
I know that the set of bytes is correct because interestingly, if I format them in lines and zoom out on them I can see the outline of the image:
Bytes Generated:
00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000009F000000EC0000001B00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000815B0000DF9B0000F8000000B40000002F000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001B0000007F4E0000FDC80000FFFF0000FF720000FF1D0000B50000003E00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001613000085350000FFDE0000FFFF0000FFFF0000FFFF0000FF750000FF230000C4000000240000000000000000000000000000000000000000000000000000000000000000000000000000000000000085400000EADB0000FFF90000FFFF0000FFFF0000FFFF0000FFF60000FFA70000F5000000DA00000000000000000000000000000000000000000000000000000000000000000000000000000091810000EACA0000FFFF0000FFFF0000FFFF0000FFFF0000FFFF0000FFBB0000FF460000FF000000FF000000000000000000000000000000000000000000000000000000000000000000000000000000FF7E0000FFE80000FFFF0000FFFF0000FFFF0000FFFB0000FFB30000FF460000FF0C0000FF000000FF000000000000000000000000000000000000000000000000000000000000000000000000000000FF000000FF660000FFFF0000FFFF0000FFFF0000FFC70000FF0D0000FF590000FF7A0000FF000000FF000000000000000000000000000000000000000000000000000000000000000000000000000000FF8C0000FF7D0000FF2E0000FFFF0000FFBB0000FF1C0000FF510000FFB40000FF7B0000FF000000FF000000000000000000000000000000000000000000000000000000000000000000000000000000FFE20000FFE40000FF410000FF8B0000FF510000FF350000FFB60000FF8F0000FF3A0000EA00000091000000000000000000000000000000000000000000000000000000000000000000000000000000DA910000F5F30000FFFF0000FF0C0000FF270000FFC10000FF930000FF220000EA0000008500000000000000000000000000000000000000000000000000000000000000000000000000000000000000241D0000C4870000FFFF0000FF270000FF420000FF9E0000FF230000FF090000860000001600000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000003E2D0000B5610000FF120000FF1C0000FF370000FD0000007F0000001B000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002E000000B4000000F8000000DF00000081000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001B000000EC0000009F0000000000000000000000000000000000000000
You are formatting the bytes as hex values and placing them in a string. Scintilla just wants the bytes themselves.
I suspect you want to create a byte array rather than a string, and store into it the values of pix.R, pix.G, pix.B, and pix.A directly, rather than formatting them as strings.
After accepting JasonD's answer I've updated my code and it's working. In case it helps anyone else, here is the working code:
public static byte[] ConvertFromPNG(Bitmap PNG)
{
byte[] rgbaB = new byte[4 * (PNG.Width * PNG.Height)];
int i = 0;
for (var y = 0; y < PNG.Height; y++)
{
for (var x = 0; x < PNG.Width; x++)
{
Color pix = PNG.GetPixel(x, y);
rgbaB[i++] = pix.R;
rgbaB[i++] = pix.G;
rgbaB[i++] = pix.B;
rgbaB[i++] = pix.A;
}
}
return rgbaB;
}

Categories