I made an Automatic image thresholding function, and wanna to save it as a bitmap file.
However, when I use the Bitmap.Save function of C# GDI+, although I set the ImageFormat as BMP, it always as the RGB color image file but not bitmap image file.
I must save it as the bitmap image file for the printer only can read the bitmap image file.
Maybe you will ask me what the bitmap image file is. I am not an expert of image processing and sorry about that can hardly explain clearly. But I can quote an example: in Photoshop, there are several color mode, such as RGB mode/CMYK mode/Index mode/Grayscale mode/Bitmap mode, I want to save the image as the Bitmap mode in C#.
Here is what Adobe explain about the Bitmap mode in their website:
Bitmap mode uses one of two color values (black or white) to represent the pixels in an image. Images in Bitmap mode are called bitmapped 1‑bit images because they have a bit depth of 1.
I googled but found nothing about this. How can I do it in C#? Thank you.
Here is my code:
Thread T = new Thread(() => {
Bitmap processedBitmap = new Bitmap(#"G:\\0001.jpg");
BitmapData bitmapData = processedBitmap.LockBits(new Rectangle(0, 0, processedBitmap.Width, processedBitmap.Height), ImageLockMode.ReadWrite, processedBitmap.PixelFormat);
int bytesPerPixel = Bitmap.GetPixelFormatSize(processedBitmap.PixelFormat) / 8;
int byteCount = bitmapData.Stride * processedBitmap.Height;
byte[] pixels = new byte[byteCount];
IntPtr ptrFirstPixel = bitmapData.Scan0;
Marshal.Copy(ptrFirstPixel, pixels, 0, pixels.Length);
int heightInPixels = bitmapData.Height;
int widthInBytes = bitmapData.Width * bytesPerPixel;
for (int y = 0; y < heightInPixels; y++)
{
int currentLine = y * bitmapData.Stride;
for (int x = 0; x < widthInBytes; x = x + bytesPerPixel)
{
int oldBlue = pixels[currentLine + x];
int oldGreen = pixels[currentLine + x + 1];
int oldRed = pixels[currentLine + x + 2];
double averageColor = (oldBlue + oldGreen + oldRed) / 3;
int NewC;
if (averageColor > 200)
{
NewC = 255;
}
else
{
NewC = 0;
}
// calculate new pixel value
pixels[currentLine + x] = (byte)NewC;
pixels[currentLine + x + 1] = (byte)NewC;
pixels[currentLine + x + 2] = (byte)NewC;
}
}
// copy modified bytes back
Marshal.Copy(pixels, 0, ptrFirstPixel, pixels.Length);
processedBitmap.UnlockBits(bitmapData);
processedBitmap.Save("G:\\aaa.bmp", ImageFormat.Bmp);
MessageBox.Show("Sucess!");
});
T.Start();
I believe the OP is referring to the last type of image in this adobe link
Bitmap is merely a container for data, the format of the data that you are storing is defined by the PixelFormat setting. As can be seen "Adobe" Bitmap mode is a 2 color format mode and corresponds to PixelFormat.Format1bppIndexed in C# Bitmap.
You have a couple of constructors for Bitmaps which have the PixelFormat as a parameter.
1.
public Bitmap (int width, int height, System.Drawing.Imaging.PixelFormat format);
2.
public Bitmap (int width, int height, int stride, System.Drawing.Imaging.PixelFormat format, IntPtr scan0);
With your source image you have a 24 bit image.
When you do your colour averaging, you're writing back to the image buffer with the following code:
pixels[currentLine + x] = (byte)NewC;
pixels[currentLine + x + 1] = (byte)NewC;
pixels[currentLine + x + 2] = (byte)NewC;
You're writing back 24 bits again.
So for example if your original values for RGB were (202, 203, 249), then NewC would be 218, and then you threshold it back to 255, so you write back (255,255,255) which is still an RGB value, it's just for white.
Then you save that image using
processedBitmap.Save("G:\\aaa.bmp", ImageFormat.Bmp);
The ImageFormat class just sets the type of image, like jpeg, png, etc.
And as you've discovered, you still have a 24 bit image being output.
So what you want is to save the image as a pure 1 bit per pixel black and white image.
To do this you need to specify the PixelFormat of the image you're saving, and specifically you want the PixelFormat Format1bppIndexed.
If you instead change the relevant bit of your code to:
...
Marshal.Copy(pixels, 0, ptrFirstPixel, pixels.Length);
processedBitmap.UnlockBits(bitmapData);
Bitmap clone = processedBitmap.Clone(new Rectangle(0, 0, processedBitmap.Width, processedBitmap.Height), PixelFormat.Format1bppIndexed);
clone.Save("G:\\aaa.bmp", ImageFormat.Bmp);
MessageBox.Show("Success!");
Now your output clone will be a 1bpp image.
However, you can simplify your code even more, because this clone function can actually do all the work for you, and you can reduce your code to just the following.
Bitmap processedBitmap = new Bitmap(#"G:\0001.jpg");
Bitmap clone = processedBitmap.Clone(new Rectangle(0, 0, processedBitmap.Width, processedBitmap.Height), PixelFormat.Format1bppIndexed);
clone.Save("G:\\aaa.bmp", ImageFormat.Bmp);
MessageBox.Show("Success!");
Be aware though that the output is slightly different.
Here are some test samples of the output.
This is my input image:
Output image with your thresholding code:
And output image using just the clone method:
To save a BMP object to a file all you have to do it this:
bmp.Save("c:\\Path\\To\\File\\image.bmp, ImageFormat.Bmp);
Are you doing anything else?
Related
I will be receiving some raw data that will be stored in a byte array, where each 2 bytes is a pixel value (16 bits/px). To start with, the array will contain 100x100*2 bytes (enough for a 100x100 pixel image). I would like to display this data in the Form window. Eventually, I would like to refresh the image with the new data to make it look like a video stream. No strict frame rate is required. How can this be done? Any code examples in C#?
EDIT:
After some suggestions and reviews of tens of similar questions I still can not get this going. Here's the general idea of what I am trying to do, but the image is not displayed in the picture box on the form. What is specifically wrong with my implementation and how to fix it?
// array of data I collected
byte[] dataArray = new byte[100 * 100 * 2];
//create a pointer to the data
IntPtr hglobal = Marshal.AllocHGlobal(100 * 100 * 2);
// copy my array to global
Marshal.Copy(dataArray, 0, hglobal, dataArray.Length);
// create a bitmap: 100x100 pixels, 2bytes/pixel, 16bitgrayscale
Bitmap newBitmap = new Bitmap(100, 100, 2 * 100, PixelFormat.Format16bppGrayScale, hglobal);
// display bitmap
pictureBox1.Image = newBitmap;
// free the memory
Marshal.FreeHGlobal(hglobal);
The main problem is that PixelFormat.Format16bppGrayScale is not supported (at least on my Win 8.1 x64 system). So you have to convert image to rgb before displaying:
private void Form1_Load(object sender, EventArgs e)
{
//Create pixel data to put in image, use 2 since it is 16bpp
Random r = new Random();
int width = 100;
int height = 100;
byte[] pixelValues = new byte[width * height * 2];
for (int i = 0; i < pixelValues.Length; ++i)
{
// Just creating random pixel values for test
pixelValues[i] = (byte)r.Next(0, 256);
}
var rgbData = Convert16BitGrayScaleToRgb48(pixelValues, width, height);
var bmp = CreateBitmapFromBytes(rgbData, width, height);
// display bitmap
pictureBox1.Image = bmp;
}
private static byte[] Convert16BitGrayScaleToRgb48(byte[] inBuffer, int width, int height)
{
int inBytesPerPixel = 2;
int outBytesPerPixel = 6;
byte[] outBuffer = new byte[width * height * outBytesPerPixel];
int inStride = width * inBytesPerPixel;
int outStride = width * outBytesPerPixel;
// Step through the image by row
for (int y = 0; y < height; y++)
{
// Step through the image by column
for (int x = 0; x < width; x++)
{
// Get inbuffer index and outbuffer index
int inIndex = (y * inStride) + (x * inBytesPerPixel);
int outIndex = (y * outStride) + (x * outBytesPerPixel);
byte hibyte = inBuffer[inIndex + 1];
byte lobyte = inBuffer[inIndex];
//R
outBuffer[outIndex] = lobyte;
outBuffer[outIndex + 1] = hibyte;
//G
outBuffer[outIndex + 2] = lobyte;
outBuffer[outIndex + 3] = hibyte;
//B
outBuffer[outIndex + 4] = lobyte;
outBuffer[outIndex + 5] = hibyte;
}
}
return outBuffer;
}
private static Bitmap CreateBitmapFromBytes(byte[] pixelValues, int width, int height)
{
//Create an image that will hold the image data
Bitmap bmp = new Bitmap(width, height, PixelFormat.Format48bppRgb);
//Get a reference to the images pixel data
Rectangle dimension = new Rectangle(0, 0, bmp.Width, bmp.Height);
BitmapData picData = bmp.LockBits(dimension, ImageLockMode.ReadWrite, bmp.PixelFormat);
IntPtr pixelStartAddress = picData.Scan0;
//Copy the pixel data into the bitmap structure
System.Runtime.InteropServices.Marshal.Copy(pixelValues, 0, pixelStartAddress, pixelValues.Length);
bmp.UnlockBits(picData);
return bmp;
}
Idea was taken from this thread.
Use this Bitmap constructor:
public Bitmap(
int width,
int height,
int stride,
PixelFormat format,
IntPtr scan0
)
You pass it the shape of your bitmap, the stride (how many bytes per line, including padding), pixel format and the pixel data as a void * pointer. You can create the latter with Marshal.AllocHGlobal and fill it in as normal with pointer operations. Don't forget to free this memory after you create your bitmap.
Edit to account for updated question:
Simply call IntPtr.ToPointer() to get back a pointer. If you're familiar with C, the rest should be cake:
var p=(char *)hglobal.ToPointer(); // bad name by the way, it's not a handle, it's a pointer
p[0]=0; // access it like any normal pointer
However, you can use the Marshaller to copy memory for you from managed to unmanaged (getting your hands dirty is usually frowned upon in C#):
Marshal.Copy(dataArray, 0, hglobal, dataArray.Length); // again, terrible name
A Bitmap is an Image (as in, it derives from it), however you're using Graphics.DrawImage() wrong. As the error says, it's not a static method, you draw it to a specific graphic context. Now what that graphic context is, that's up to you:
If you want to paint it in response to WM_PAINT, use the Paint event -- it provides you with a special Graphics object set up with clipping and everything as instructed by the windowing system.
If you want to paint it on a bitmap to be later displayed somehow (the common use, also called double buffering), use Graphics.FromImage() on the source bitmap then draw your bitmap over it.
You can (and should) delete your virtual memory buffer as soon as you get the result back from the Bitmap constructor. Don't leak memory, use a try..finally construct.
I am using the MakeTransparent() function call on the c# Bitmap object to convert images to transparent images. When this method is called, it will convert a background color to be transparent by setting the alpha channel, but it then converts the background color to black.
I need to find a fast way of converting this background color back to white or whatever the original color was, as occasionally I need to flatten the image to a non-alpha channel enabled format.
Make transparent doesn't seem to have any flags or overloads that allows you to tell it to leave the background color alone, and altering the image pixel by pixel is way to inefficient. Anyone have any suggestions or GDI tricks to solve this problem?
There doesn't seem to be a fast way to do this using the managed code interface. Using individual pixel manipulation, or using unmanaged code to update the pixels seem to be the only real options.
This is actually possible in managed code, by using Marshal.Copy to copy the backing byte array out of a bitmap object, then editing it, and then copying it back.
So basically, with that general method in mind, you just go over the pixels, line by line, detect which pixels have the colour you want replaced, and set their alpha byte to 0.
Note that "ARGB" refers to the order of the components inside the Int32 value of one read pixel. Since this value is little-endian, the actual order of the bytes at a given offset is the reverse; B = offset + 0, G = offset + 1, R = offset + 2, A = offset + 3.
/// <summary>
/// Clears the alpha value of all pixels matching the given colour.
/// </summary>
public static Bitmap MakeTransparentKeepColour(Bitmap image, Color clearColour)
{
Int32 width = image.Width;
Int32 height = image.Height;
// Paint on 32bppargb, so we're sure of the byte data format
Bitmap bm32 = new Bitmap(width, height, PixelFormat.Format32bppArgb);
using (Graphics gr = Graphics.FromImage(bm32))
gr.DrawImage(image, new Rectangle(0, 0, width, height));
BitmapData sourceData = bm32.LockBits(new Rectangle(0, 0, width, height), ImageLockMode.ReadOnly, bm32.PixelFormat);
Int32 stride = sourceData.Stride;
// Copy the image data into a local array so we can use managed functions to manipulate it.
Byte[] data = new Byte[stride * height];
Marshal.Copy(sourceData.Scan0, data, 0, data.Length);
Byte colR = clearColour.R;
Byte colG = clearColour.G;
Byte colB = clearColour.B;
for (Int32 y = 0; y < height; y++)
{
Int32 inputOffs = y * stride;
for (Int32 x = 0; x < width; x++)
{
if (data[inputOffs + 2] == colR && data[inputOffs + 1] == colG && data[inputOffs] == colB)
data[inputOffs + 3] = 0;
inputOffs += 4;
}
}
// Copy the edited image data back.
Marshal.Copy(data, 0, sourceData.Scan0, data.Length);
bm32.UnlockBits(sourceData);
return bm32;
}
This can easily be enhanced with a tolerance level instead of an exact match, with something like Math.Abs(data[inputOffs + 2] - colR) < tolerance, or by actually converting the bytes to a colour object and doing some other kind of approximation (like hue/saturation/brightness).
I need to create an image in memory (can be huge image!) and to extract from it byte array in the size of width x height. Each byte must have value of 0-255 (256 gray scale values: 0 for white and 255 for black).
The part of creating the image is easy, here is a simple example of my code:
img = new Bitmap(width, height);
drawing = Graphics.FromImage(img);
drawing.Clear(Color.Black);// paint the background
drawing.DrawString(text, font, Brushes.White, 0, 0);
Problem is to convert it to "my" special gray scale byte array. When I'm using any pixel format other then Format8bppIndexed, the byte array I'm getting from the bitmap is not in the size I need (width*length) so I need a conversion that takes too much time. When I'm using Format8bppIndexed I'm getting the byte array very fast and in the right size, but each byte/pixel is 0-15.
Changing the bitmap palette has no affect:
var pal = img.Palette;
for (int i = 1; i < 256; i++){
pal.Entries[i] = Color.FromArgb(255, 255, 255);
}
img.Palette = pal;
Any idea how to do it?
Edit: Full code:
// assume font can be Times New Roman, size 7500!
static private Bitmap DrawText(String text, Font font)
{
//first, create a dummy bitmap just to get a graphics object
var img = new Bitmap(1, 1);
var drawing = Graphics.FromImage(img);
//measure the string to see how big the image needs to be
var textSize = drawing.MeasureString(text, font);
//free up the dummy image and old graphics object
img.Dispose();
drawing.Dispose();
//create a new image of the right size (must be multiple of 4)
int width = (int) (textSize.Width/4) * 4;
int height = (int)(textSize.Height / 4) * 4;
img = new Bitmap(width, height);
drawing = Graphics.FromImage(img);
// paint the background
drawing.Clear(Color.Black);
drawing.DrawString(text, font, Brushes.White, 0, 0);
var bmpData = img.LockBits(new Rectangle(0, 0, img.Width, img.Height), ImageLockMode.ReadOnly, PixelFormat.Format8bppIndexed);
var newBitmap = new Bitmap(width, height, bmpData.Stride, PixelFormat.Format8bppIndexed, bmpData.Scan0);
drawing.Dispose();
return newBitmap;
}
private static byte[] GetGrayscleBytesFastest(Bitmap bitmap)
{
BitmapData bmpdata = bitmap.LockBits(new Rectangle(0, 0, bitmap.Width, bitmap.Height), ImageLockMode.ReadOnly, bitmap.PixelFormat);
int numbytes = bmpdata.Stride * bitmap.Height;
byte[] bytedata = new byte[numbytes];
IntPtr ptr = bmpdata.Scan0;
Marshal.Copy(ptr, bytedata, 0, numbytes);
bitmap.UnlockBits(bmpdata);
return bytedata;
}
You probably want to do this in two steps. First, create a 16bpp grayscale copy of your original image as described in Convert an image to grayscale.
Then, create your 8bpp image with the appropriate color table and draw the 16bpp grayscale image onto that image. That will do the conversion for you, converting the 16-bit grayscale values to your 256 different colors.
You should then have an 8bpp image with your 256 different shades of gray. You can then call LockBits to get access to the bitmap bits, which will be index values in the range 0 to 255.
I have solved this problem with ImageSharp
I calculate the gray value from the rgb values and then add it to the array.
using SixLabors.ImageSharp;
using SixLabors.ImageSharp.PixelFormats;
private static byte[] GetImageData(byte[] imageData)
{
using (var image = Image.Load<Rgba32>(imageData))
{
var buffer = new byte[image.Width * image.Height];
var index = 0;
image.ProcessPixelRows(accessor =>
{
for (int y = 0; y < accessor.Height; y++)
{
Span<Rgba32> pixelRow = accessor.GetRowSpan(y);
for (int x = 0; x < pixelRow.Length; x++)
{
ref Rgba32 pixel = ref pixelRow[x];
buffer[index] = (byte)((pixel.R + pixel.G + pixel.B) / 3);
index++;
}
}
});
return buffer;
}
}
The user provides my app an image, from which the app needs to make a mask:
The mask contains a red pixel for each transparent pixel in the original image.
I tried the following:
Bitmap OrgImg = Image.FromFile(FilePath);
Bitmap NewImg = new Bitmap(OrgImg.Width, OrgImg.Height);
for (int y = 0; y <= OrgImg.Height - 1; y++) {
for (int x = 0; x <= OrgImg.Width - 1; x++) {
if (OrgImg.GetPixel(x, y).A != 255) {
NewImg.SetPixel(x, y, Color.FromArgb(255 - OrgImg.GetPixel(x, y).A, 255, 0, 0));
}
}
}
OrgImg.Dispose();
PictureBox1.Image = NewImg;
I am worried about the performance on slow PCs. Is there a better approach to do this?
It is perfectly acceptable to use GetPixel() if it is only used sporadicly, e.g. on loading one image. However, if you want to do a more serious image processing, it is better to work directly with BitmapData. A small example:
//Load the bitmap
Bitmap image = (Bitmap)Image.FromFile("image.png");
//Get the bitmap data
var bitmapData = image.LockBits (
new Rectangle (0, 0, image.Width, image.Height),
ImageLockMode.ReadWrite,
image.PixelFormat
);
//Initialize an array for all the image data
byte[] imageBytes = new byte[bitmapData.Stride * image.Height];
//Copy the bitmap data to the local array
Marshal.Copy(bitmapData.Scan0,imageBytes,0,imageBytes.Length);
//Unlock the bitmap
image.UnlockBits(bitmapData);
//Find pixelsize
int pixelSize = Image.GetPixelFormatSize(image.PixelFormat);
// An example on how to use the pixels, lets make a copy
int x = 0;
int y = 0;
var bitmap = new Bitmap (image.Width, image.Height);
//Loop pixels
for(int i=0;i<imageBytes.Length;i+=pixelSize/8)
{
//Copy the bits into a local array
var pixelData = new byte[3];
Array.Copy(imageBytes,i,pixelData,0,3);
//Get the color of a pixel
var color = Color.FromArgb (pixelData [0], pixelData [1], pixelData [2]);
//Set the color of a pixel
bitmap.SetPixel (x,y,color);
//Map the 1D array to (x,y)
x++;
if( x >= bitmap.Width)
{
x=0;
y++;
}
}
//Save the duplicate
bitmap.Save ("image_copy.png");
This approach is indeed slow. A better approach would be using Lockbits and access the underlying matrix directly. Take a look at https://web.archive.org/web/20141229164101/http://bobpowell.net/lockingbits.aspx or http://www.mfranc.com/programming/operacje-na-bitmapkach-net-1/ or https://learn.microsoft.com/en-us/dotnet/api/system.drawing.bitmap.lockbits or other articles about lockbits in StackOverflow.
It's a tiny bit more complex since you'll have to work with bytes directly (4 per pixel if you're working with RGBA), but the performance boost is significant and is well worth it.
Another note - OrgImg.GetPixel(x, y) is slow, if you're sticking with this (and not lockbits) make sure you only use it once (it may be already optimized, just check if there's a difference).
i am creating png image which painted on my base, from the base i can save a png image, for your reference
Graphics g = e.Graphics;
....
g.DrawLine(pen, new Point(x, y), new Point(x1, y1));
.....
base.OnPaint(e);
using (var bmp = new Bitmap(500, 50))
{
base.DrawToBitmap(bmp, new Rectangle(0, 0, 500, 50));
bmp.Save(outPath);
}
this is single color transparency image, now how do i can inverse this image like png filled with any color and the real image portion should be transparent, is there any possibilities?
bit detail : so transparent will go nontransparent and where there is fill will go to transparent
There's a faster way if you're willing to use unsafe code:
private unsafe void Invert(Bitmap bmp)
{
int w = bmp.Width, h = bmp.Height;
BitmapData data = bmp.LockBits(new Rectangle(0, 0, w, h), ImageLockMode.ReadWrite, PixelFormat.Format32bppArgb);
int* bytes = (int*)data.Scan0;
for ( int i = w*h-1; i >= 0; i-- )
bytes[i] = ~bytes[i];
bmp.UnlockBits(data);
}
Note that this doesn't care about the colors and will invert those as well. If you wish to use a specific color, then the code will have to be modified a bit.
EDIT (thanks for Thomas notation)
public void ApplyInvert()
{
byte A, R, G, B;
Color pixelColor;
for (int y = 0; y < bitmapImage.Height; y++)
{
for (int x = 0; x < bitmapImage.Width; x++)
{
pixelColor = bitmapImage.GetPixel(x, y);
A = (byte)(255 - pixelColor.A);
R = pixelColor.R;
G = pixelColor.G;
B = pixelColor.B;
bitmapImage.SetPixel(x, y, Color.FromArgb((int)A, (int)R, (int)G, (int)B));
}
}
}
from here : Image Processing in C#: Inverting an image
For anyone who wants a fast method for inverting Bitmap colors without using unsafe:
public static void BitmapInvertColors(Bitmap bitmapImage)
{
var bitmapRead = bitmapImage.LockBits(new Rectangle(0, 0, bitmapImage.Width, bitmapImage.Height), ImageLockMode.ReadOnly, PixelFormat.Format32bppPArgb);
var bitmapLength = bitmapRead.Stride * bitmapRead.Height;
var bitmapBGRA = new byte[bitmapLength];
Marshal.Copy(bitmapRead.Scan0, bitmapBGRA, 0, bitmapLength);
bitmapImage.UnlockBits(bitmapRead);
for (int i = 0; i < bitmapLength; i += 4)
{
bitmapBGRA[i] = (byte)(255 - bitmapBGRA[i]);
bitmapBGRA[i + 1] = (byte)(255 - bitmapBGRA[i + 1]);
bitmapBGRA[i + 2] = (byte)(255 - bitmapBGRA[i + 2]);
// [i + 3] = ALPHA.
}
var bitmapWrite = bitmapImage.LockBits(new Rectangle(0, 0, bitmapImage.Width, bitmapImage.Height), ImageLockMode.WriteOnly, PixelFormat.Format32bppPArgb);
Marshal.Copy(bitmapBGRA, 0, bitmapWrite.Scan0, bitmapLength);
bitmapImage.UnlockBits(bitmapWrite);
}
Bitmap GetPixel and SetPixel are extremly slow, this method works by copying the Bitmap pixels into a byte array, which you can then loop through and change, before finally copying the pixels back.
When you say invert the transparent sections into color, are you storing the real colors in the PNG image just set to full transparency? A lot of programs will optimize a png by removing the color data from transparency so you can't reverse it.
Colors can be converted to transparency
But transparency (without underlying colors) cannot be converted to color.
If your lucky your PNG will be non optimized and still have the original color data intact, but if your doing this from user input then it won't work for a high percentage of cases.