I use a black and white pictures as a mask to generate nice contours after applying a rectangle. Unfortunately to get rid of the black color I use the MakeTransparent method, unfortunately it is very slow, in my code I have to perform such a procedure twice, which in the case of 20 images takes about 5 seconds. Is there any other solution to speed up this procedure?
Bitmap contour = new Bitmap(
imageMaskCountour.Width, imageMaskCountour.Height, PixelFormat.Format32bppArgb);
using (Graphics g = Graphics.FromImage(contour))
{
g.InterpolationMode = System.Drawing.Drawing2D.InterpolationMode.NearestNeighbor;
g.FillRectangle(ContourBrush, 0, 0, contour.Width, contour.Height);
g.DrawImage(
imageMaskCountour,
new Rectangle(0, 0, contour.Width, contour.Height),
new Rectangle(0, 0, imageMaskCountour.Width, imageMaskCountour.Height),
GraphicsUnit.Pixel);
}
contour.MakeTransparent(Color.Black);
Edit:
I try add LockBitmap and add the following method:
public void MakeTransparent(Color color)
{
for (int y = 0; y < this.Height; y++)
{
for (int x = 0; x < this.Width; x++)
{
if (this.GetPixel(x, y) == color)
{
this.SetPixel(x, y, Color.Transparent);
}
}
}
}
But it is much slower.
Every operation you are doing on the Bitmap requires locking and unlocking of the bits. That makes it very slow. See this answer how to access the bitmap data directly by locking once, manipulating all data and finally unlocking it.
The following code is an extension method for a regular Bitmap using the direct access just mentioned. It implements the logic of your method. Note that I save the Height and Width to local variables. Iam doing that because I recognized that it is much slower if used multiple times like in a loop condition.
Caution:
The underlying code only works for PixelFormat.Format32bppArgb, see PixelFormat reference.
The code gives you an idea how to access the pixel data directly and can be adapted to other pixel formats too.
The following points must be respected if adapting to other pixel formats.
Byte size of one destination pixel to correctly write the color value.
The correct row addressing through the Bitmap.Stride property, see Stride for reference. In case of PixelFormat.Format32bppArgb the stride matches the width by: Bitmap.Stride == Bitmap.Width * 4 and therefore no row address adjustments are needed.
public static void FastMakeTransparent(this Bitmap bitmap, Color color)
{
BitmapData bitmapData = bitmap.LockBits(
new Rectangle(0, 0, bitmap.Width, bitmap.Height),
ImageLockMode.ReadWrite, PixelFormat.Format32bppArgb);
unsafe
{
int* pixelPointer = (int*)bitmapData.Scan0;
int bitmapHeight = bitmap.Height;
int bitmapWidth = bitmap.Width;
int colorInt = color.ToArgb();
int transparentInt = Color.Transparent.ToArgb();
for (int i = 0; i < bitmapHeight; ++i)
{
for (int j = 0; j < bitmapWidth; ++j)
{
if (*pixelPointer == colorInt)
*pixelPointer = transparentInt;
++pixelPointer;
}
}
}
bitmap.UnlockBits(bitmapData);
}
Benchmark with Stopwatch class on my Intel Core2Duo P8400 (2.26 GHz) CPU.
Bitmap size 1000x1000 random filled accumulated time of 100 runs
Target AnyCPU .NetFramework 4.5.2
Release build
MakeTransparent Ticks: 24224801 | Time: 2422 ms
FastMakeTransparent Ticks: 1332730 | Time: 133 ms
Debug build
MakeTransparent Ticks: 24681178 | Time: 2468 ms
FastMakeTransparent Ticks: 5976810 | Time: 597 ms
Related
I have an image and I would like to create a GraphicsPath representing a selection on the image based on some sort of thresholding on the pixels making up the image. For instance a color range algorithm that takes into account pixels with color values close to a given color.
My problem at the moment is not on the algorithm to select pixels, but on the GraphicsPath which with my current code becomes far too complicated (too many points) and hence time-expensive to create it and especially to further manage it.
At the moment I'm simply using the following code (in this example I simply take opaque pixels) where I add a Rectangle for each taken pixel.
BitmapData bitmapData = sourceBitmap.LockBits(new Rectangle(0, 0, sourceBitmap.Width, sourceBitmap.Height), ImageLockMode.ReadOnly, sourceBitmap.PixelFormat);
GraphicsPath gp = new GraphicsPath();
unsafe
{
byte* imagePointer = (byte*)bitmapData.Scan0;
for (int x = 0; x < bitmapData.Width; x++)
{
for (int y = 0; y < bitmapData.Height; y++)
{
if (imagePointer[3] != 0)
gp.AddRectangle(new Rectangle(x, y, 1, 1));
imagePointer += 4;
}
}
}
How can I simplify this process or the resulting GraphicsPath to obtain a much simpler path, ideally with only contours?
EDIT
I will then use to path to: display its contours on screen (running ants); use it as a mask for other images (cancel what is out from the path); do hit tests (whether a point is inside the path). As an example:
Software takes an image as input (bmp, jpeg, whatever) with a white background and a black grid on it, I need to recognize grid propertys like Heigth, Width and cell size
This would be an easy job if the pixel color were sharp, but as we know due to compression it's not like that
I tried some normalizing algorithm in order to work with a pure black and white image but they are not 100% accurate, this the result using this simple function:
public static Bitmap BlackWhite(Bitmap bmp)
{
Bitmap bw = bmp.Clone(new System.Drawing.Rectangle(0, 0, bmp.Width, bmp.Height), PixelFormat.Format1bppIndexed);
return bw;
}
before and after
as you can see outline is not correct
I get better results using a slow home made function:
public static Bitmap ColorSharp(Bitmap bitmap)
{
int i = 0;
int y = 0;
for(i = 0; i < bitmap.Width; i++)
{
for (y = 0; y < bitmap.Height; y++)
{
Color color = bitmap.GetPixel(i, y);
if (color.R <= 145 && color.G <= 145 && color.B <= 145)
bitmap.SetPixel(i, y, Color.Black);
else
bitmap.SetPixel(i, y, Color.White);
}
}
return bitmap;
}
but still not perfect
i know it's probabily impossible to have a 100% accuracy, expecially with high compressed image, but do you have any idea how to deal with this kind of problem?
You can try to apply Skeletonization or Thinning morphological operations to bitmap matrix. There are standard functions in OpenCV which will work fast.
I try to load JPEG file and delete all black and white pixels from image
C# Code:
...
m_SrcImage = new Bitmap(imagePath);
Rectangle r = new Rectangle(0, 0, m_SrcImage.Width, m_SrcImage.Height);
BitmapData bd = m_SrcImage.LockBits(r, ImageLockMode.ReadWrite, PixelFormat.Format32bppArgb);
//Load Colors
int[] colours = new int[m_SrcImage.Width * m_SrcImage.Height];
Marshal.Copy(bd.Scan0, colours, 0, colours.Length);
m_SrcImage.UnlockBits(bd);
int len = colours.Length;
List<Color> result = new List<Color>(len);
for (int i = 0; i < len; ++i)
{
uint w = ((uint)colours[i]) & 0x00FFFFFF; //Delete alpha-channel
if (w != 0x00000000 && w != 0x00FFFFFF) //Check pixel is not black or white
{
w |= 0xFF000000; //Return alpha channel
result.Add(Color.FromArgb((int)w));
}
}
...
After that I try to find unique colors in List by this code
result.Sort((a, b) =>
{
return a.R != b.R ? a.R - b.R :
a.G != b.G ? a.G - b.G :
a.B != b.B ? a.B - b.B :
0;
});
List<Color> uniqueColors = new List<Color>( result.Count);
Color rgbTemp = result[0];
for (int i = 0; i < len; ++i)
{
if (rgbTemp == result[i])
{
continue;
}
uniqueColors.Add(rgbTemp);
rgbTemp = result[i];
}
uniqueColors.Add(rgbTemp);
And this code produces different results on different machines on same image!
For example, on this image it produces:
43198 unique colors on XP SP3 with .NET version 4
43168 unique colors on Win7 Ultimate with .NEt version 4.5
Minimum test project you can download here. It just opens selected image and produces txt-file with unique colors.
One more fact. Some pixels are read differently on different machines. I compare txt-files with notepad++ and it shows that some pixels have different RGB components. The difference is 1 for each component, e.g.
Win7 pixel: 255 200 100
WinXP pixel: 254 199 99
I have read this post
stackoverflow.com/questions/2419598/why-might-different-computers-calculate-different-arithmetic-results-in-vb-net
(sorry, I haven't enough raiting for normal link).
...but there wasn't information how to fix it.
Project was compiled for .NET 4 Client profile on machine with OS Windows 7 in VS 2015 Commumity Edition.
Wikipedia has this to say about the accuracy requirements for JPEG Decoders:
The encoding description in the JPEG standard does not fix the precision needed for the output compressed image. However, the JPEG standard (and the similar MPEG standards) includes some precision requirements for the decoding, including all parts of the decoding process (variable length decoding, inverse DCT, dequantization, renormalization of outputs); the output from the reference algorithm must not exceed:
a maximum of one bit of difference for each pixel component
low mean square error over each 8×8-pixel block
very low mean error over each 8×8-pixel block
very low mean square error over the whole image
extremely low mean error over the whole image
(my emphasis)
In short, there is simply two different decoder implementations at play here, and they produce different images, within the accuracy requirement (1 bit = +/- 1 in the component values, as you observed).
So short of using the same (non-built-in) jpeg decoder, this is to be expected. If you need to have the exact same output then you probably need to switch to a different decoder, one that will be the same no matter which .NET version or Windows you're running this on. I'm guessing that GDI+ is the culprit here as this has undergone larger changes since Windows XP.
I solve my problem by adding Libjpeg.NET to project and write this code:
private Bitmap JpegToBitmap(JpegImage jpeg)
{
int width = jpeg.Width;
int height = jpeg.Height;
// Read the image into the memory buffer
int[] raster = new int[height * width];
for(int i = 0; i < height; ++i)
{
byte[] temp = jpeg.GetRow(i).ToBytes();
for (int j = 0; j < temp.Length; j += 3)
{
int offset = i*width + j / 3;
raster[offset] = 0;
raster[offset] |= (((int)temp[j+2]) << 16);
raster[offset] |= (((int)temp[j+1]) << 8);
raster[offset] |= (int)temp[j];
}
}
Bitmap bmp = new Bitmap(width, height, PixelFormat.Format24bppRgb);
Rectangle rect = new Rectangle(0, 0, bmp.Width, bmp.Height);
BitmapData bmpdata = bmp.LockBits(rect, ImageLockMode.ReadWrite, PixelFormat.Format24bppRgb);
byte[] bits = new byte[bmpdata.Stride * bmpdata.Height];
for (int y = 0; y < bmp.Height; y++)
{
int rasterOffset = y * bmp.Width;
int bitsOffset = (bmp.Height - y - 1) * bmpdata.Stride;
for (int x = 0; x < bmp.Width; x++)
{
int rgba = raster[rasterOffset++];
bits[bitsOffset++] = (byte)((rgba >> 16) & 0xff);
bits[bitsOffset++] = (byte)((rgba >> 8) & 0xff);
bits[bitsOffset++] = (byte)(rgba & 0xff);
}
}
System.Runtime.InteropServices.Marshal.Copy(bits, 0, bmpdata.Scan0, bits.Length);
bmp.UnlockBits(bmpdata);
return bmp;
}
So, that's enough for me.
EDIT: I deeply appreciate the replies. What I need more than anything here is sample code for what I do with the few lines of code in the nested loop, since that's what works right in GetPixel/SetPixel, but also what I can't get to work right using Lockbits. Thank you
I'm trying to convert my image processing filters from GetPixel / SetPixel to Lockbits, to improve processing time. I have seen Lockbits tutorials here on Stack Overflow, MSDN, and other sites as well, but I'm doing something wrong. I'm starting with an exceedingly simple filter, which simply reduces green to create a red and purple effect. Here's my code:
private void redsAndPurplesToolStripMenuItem_Click(object sender, EventArgs e)
{
// Get bitmap from picturebox
Bitmap bmpMain = (Bitmap)pictureBoxMain.Image.Clone();
// search through each pixel via x, y coordinates, examine and make changes. Dont let values exceed 255 or fall under 0.
for (int y = 0; y < bmpMain.Height; y++)
for (int x = 0; x < bmpMain.Width; x++)
{
bmpMain.GetPixel(x, y);
Color c = bmpMain.GetPixel(x, y);
int myRed = c.R, myGreen = c.G, myBlue = c.B;
myGreen -= 128;
if (myGreen < 0) myGreen = 0;
bmpMain.SetPixel(x, y, Color.FromArgb(255, myRed, myGreen, myBlue));
}
// assign the new bitmap to the picturebox
pictureBoxMain.Image = (Bitmap)bmpMain;
// Save a copy to the HD for undo / redo.
string myString = Environment.GetEnvironmentVariable("temp", EnvironmentVariableTarget.Machine);
pictureBoxMain.Image.Save(myString + "\\ColorAppRedo.png", System.Drawing.Imaging.ImageFormat.Png);
}
So that GetPixel / SetPixel code works fine, but it's slow. So I tried this:
private void redsAndPurplesToolStripMenuItem_Click(object sender, EventArgs e)
{
// Get bitmap from picturebox
Bitmap bmpMain = (Bitmap)pictureBoxMain.Image.Clone();
Rectangle rect = new Rectangle(Point.Empty, bmpMain.Size);
BitmapData bmpData = bmpMain.LockBits(rect, ImageLockMode.ReadOnly, bmpMain.PixelFormat);
// search through each pixel via x, y coordinates, examine and make changes. Dont let values exceed 255 or fall under 0.
for (int y = 0; y < bmpMain.Height; y++)
for (int x = 0; x < bmpMain.Width; x++)
{
bmpMain.GetPixel(x, y);
Color c = new Color();
int myRed = c.R, myGreen = c.G, myBlue = c.B;
myGreen -= 128;
if (myGreen < 0) myGreen = 0;
bmpMain.SetPixel(x, y, Color.FromArgb(255, myRed, myGreen, myBlue));
}
bmpMain.UnlockBits(bmpData);
// assign the new bitmap to the picturebox
pictureBoxMain.Image = (Bitmap)bmpMain;
// Save a copy to the HD for undo / redo.
string myString = Environment.GetEnvironmentVariable("temp", EnvironmentVariableTarget.Machine);
pictureBoxMain.Image.Save(myString + "\\ColorAppRedo.png", System.Drawing.Imaging.ImageFormat.Png);
}
Which throws the error "An unhandled exception of type 'System.InvalidOperationException' occurred in System.Drawing.dll Additional information: Bitmap region is already locked" when it reaches the first line of the nested loop.
I realize this has to be a beginner's error, I'd appreciate if someone could demonstrate the correct way to convert this very simple filter to Lockbits. Thank you very much
The array returned by scan0 is in this format BGRA BGRA BGRA BGRA ... and so on,
where B = Blue, G = Green, R = Red, A = Alpha.
Example of a very small bitmap 4 pixels wide and 3 pixels height.
BGRA BGRA BGRA BGRA
BGRA BGRA BGRA BGRA
BGRA BGRA BGRA BGRA
stride = width*bytesPerPixel = 4*4 = 16 bytes
height = 3
maxLenght = stride*height= 16*3 = 48 bytes
To reach a certain pixel in the image (x, y) use this formula
int certainPixel = bytesPerPixel*x + stride * y;
B = scan0[certainPixel + 0];
G = scan0[certainPixel + 1];
R = scan0[certainPixel + 2];
A = scan0[certainPixel + 3];
public unsafe void Test(Bitmap bmp)
{
int width = bmp.Width;
int height = bmp.Height;
//TODO determine bytes per pixel
int bytesPerPixel = 4; // we assume that image is Format32bppArgb
int maxPointerLenght = width * height * bytesPerPixel;
int stride = width * bytesPerPixel;
byte R, G, B, A;
BitmapData bData = bmp.LockBits(
new System.Drawing.Rectangle(0, 0, bmp.Width, bmp.Height),
ImageLockMode.ReadWrite, bmp.PixelFormat);
byte* scan0 = (byte*)bData.Scan0.ToPointer();
for (int i = 0; i < maxPointerLenght; i += 4)
{
B = scan0[i + 0];
G = scan0[i + 1];
R = scan0[i + 2];
A = scan0[i + 3];
// do anything with the colors
// Set the green component to 0
G = 0;
// do something with red
R = R < 54 ? (byte)(R + 127) : R;
R = R > 255 ? 255 : R;
}
bmp.UnlockBits(bData);
}
You can test is yourself. Create a very small bitmap ( few pixels wide/height) in paint or any other program and put a breakpoint at the begining of the method.
Additional information: Bitmap region is already locked"
You now know why GetPixel() is slow, it also uses Un/LockBits under the hood. But does so for each individual pixel, the overhead steals cpu cycles. A bitmap can be locked only once, that's why you got the exception. Also the basic reason that you can't access a bitmap in multiple threads simultaneously.
The point of LockBits is that you can access the memory occupied by the bitmap pixels directly. The BitmapData.Scan0 member gives you the memory address. Directly addressing the memory is very fast. You'll however have to work with an IntPtr, the type of Scan0, that requires using a pointer or Marshal.Copy(). Using a pointer is the optimal way, there are many existing examples on how to do this, I won't repeat it here.
... = bmpMain.LockBits(rect, ImageLockMode.ReadOnly, bmpMain.PixelFormat);
The last argument you pass is very, very important. It selects the pixel format of the data and that affects the code you write. Using bmpMain.PixelFormat is the fastest way to lock but it is also very inconvenient. Since that now requires you to adapt your code to the specific pixel format. There are many, take a good look at the PixelFormat enum. They differ in the number of bytes taken for each pixel and how the colors are encoded in the bits.
The only convenient pixel format is Format32bppArgb, every pixel takes 4 bytes, the color/alpha is encoded in a single byte and you can very easily and quickly address the pixels with an uint*. You can still deal with Format24bppRgb but you now need a byte*, that's a lot slower. The ones that have a P in the name are pre-multiplied formats, very fast to display but exceedingly awkward to deal with. You may thus be well ahead by taking the perf hit of forcing LockBits() to convert the pixel format. Paying attention to the pixel format up front is important to avoid this kind of lossage.
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).