I've got a PNG image that I'm operating on via the System.Drawing API in .NET. It has large transparent regions, and I would like to replace the transparent regions with white fill--so that there are no transparent regions in the image. Easy enough in an image editing program... but so far I've had no success doing this in C#.
Can someone give me some pointers?
I'm not sure how to detect transparent pixel. I know if the Alpha is 0 it's completly transparent and if it's 255 it's opaque. I'm not sure if you should check for Alpha == 0 or Alpha != 255 ; if you can try it and give me a feedback that would be helpful.
From MSDN
The alpha component specifies the
transparency of the color: 0 is fully
transparent, and 255 is fully opaque.
Likewise, an A value of 255 represents
an opaque color. An A value from 1
through 254 represents a
semitransparent color. The color
becomes more opaque as A approaches
255.
void Foo(Bitmap image)
{
for (int y = 0; y < image.Height; ++y)
{
for (int x = 0; x < image.Width; ++x)
{
// not very sure about the condition.
if (image.GetPixel(x, y).A != 255)
{
image.SetPixel(x,y,Color.White);
}
}
}
}
My example:
public void FillPngWhite(Bitmap bmp)
{
if (bmp.PixelFormat != PixelFormat.Format32bppArgb)
throw new ApplicationException("Not supported PNG image!");
// Lock the bitmap's bits.
Rectangle rect = new Rectangle(0, 0, bmp.Width, bmp.Height);
BitmapData bmpData = bmp.LockBits(rect, ImageLockMode.ReadWrite, bmp.PixelFormat);
// Get the address of the first line.
IntPtr ptr = bmpData.Scan0;
// Declare an array to hold the bytes of the bitmap.
int bytes = Math.Abs(bmpData.Stride) * bmp.Height;
byte[] rgbaValues = new byte[bytes];
// Copy the RGB values into the array.
System.Runtime.InteropServices.Marshal.Copy(ptr, rgbaValues, 0, bytes);
// array consists of values RGBARGBARGBA
for (int counter = 0; counter < rgbaValues.Length; counter += 4)
{
double t = rgbaValues[counter + 3]/255.0; // transparency of pixel between 0 .. 1 , easier to do math with this
double rt = 1 - t; // inverted value of transparency
// C = C * t + W * (1-t) // alpha transparency for your case C-color, W-white (255)
// same for each color
rgbaValues[counter] = (byte) (rgbaValues[counter]*t + 255*rt); // R color
rgbaValues[counter + 1] = (byte)(rgbaValues[counter + 1] * t + 255 * rt); // G color
rgbaValues[counter + 2] = (byte)(rgbaValues[counter + 2] * t + 255 * rt); // B color
rgbaValues[counter + 3] = 255; // A = 255 => no transparency
}
// Copy the RGB values back to the bitmap
System.Runtime.InteropServices.Marshal.Copy(rgbaValues, 0, ptr, bytes);
// Unlock the bits.
bmp.UnlockBits(bmpData);
}
This is different bacause:
I use LockBits instead GetPixel and SetPixel. It is much more faster, but little harder to understand. It's a little modified example from : MSDN
I'm taking real aplha value into consideration, as I said in the comment to your question. This will make black with 50% transparency (128) look like gray instead of black. Reason for this is by "replace alpha with white in graphics editor" I imagine creating new layer underneath you image filled with white and then flattening both layers together. This example will have same effect.
Once you have a handle to the bitmap object, just do something like:
Bitmap yourImage = HOWEVER YOU LOAD YOUR IMAGE;
int width = YOUR IMAGE WIDTH;
int height = YOUR IMAGE HEIGHT;
Color c;
Color white = new Color(255,255,255,255)
for(int w = 0; w < width; w++)
for(int h = 0; h < height; h++)
{
c = yourImage.GetPixel(w,h);
yourImage.SetPixel(w,h, ((((short)(c.A)) & 0x00FF) <= 0)? white:c); //replace 0 here with some higher tolerance if needed
}
This may be oversimplifying your problem, but if it's on a form or other readily available control, you could simply paint the background White before placing the Image on top.
Related
I need to convert pictures with colored text on colored background to image with white background and black font color. Example of original image:
I tried grayscale from Convert an image to grayscale but it's not for me because I need only white back and black font. Result of grayscale method:
When I tried divide original image to Dictionary<pixelColorCode, countInImage>. I think the most popular color is color of background original image, and other colors are for text on image. So I draw new image like original, but with white and black pixels:
for (int i = 0; i < originalImage.Width; i++)
{
for (int j = 0; j < originalImage.Height; j++)
{
if (originalImage.GetPixel(i, j).ToArgb == mostPopularColorOfOriginalImage)
{
newBitmap.SetPixel(i, j, Color.White);
}
else
{
newBitmap.SetPixel(i, j, Color.Black);
}
}
}
It's better, but text in black-and-white image is not good. Result:
The basic method of solving this is the following:
Go over the image, and build up a histogram of all occurring colours. Since colours can be represented as an Int32, You can use a Dictionary<Int32,Int32> for this.
Take the two top colours from this histogram. Designate the most commonly occurring one as the "background colour" and the second as the "content colour".
Take the differences in R, G and B between the background and the content colour, and use those to make a colour palette with a smooth 256-colour fade from one colour to the other.
Go over all pixels of the image, and for each pixel, use the Pythagorean distance in 3D colour space to determine which colour from the generated palette it is the closest to.
On the resulting image, set the colours to a smooth fade from black to white.
Now, GetPixel and SetPixel, when looped over an entire image, are ridiculously slow, because they have to perform a rather heavy LockBits operation on the image for every pixel they handle. So instead, you can perform the LockBits operation once on the entire image, copy the bytes out, do all your operations on the resulting byte array, and then use LockBits a second time to copy the result into a new image.
Since we're dealing with grayscale colours here, it's also probably more efficient to write the end result into a 8-bit image. This also makes it trivially easy to manipulate the colour palette after doing the actual colour matching.
If you instead prefer to match it to exactly two colours, the method is exactly the same, except that instead of generating a colour fade between the two colours, the palette to match to will only contain the two found colours. Since the only possible indices to match on that will be 0 and 1, the final image's palette will likewise just need to have index 0 and index 1 set to black and white, rather than getting a whole grayscale fade.
The resulting method:
/// <summary>
/// Finds the two most prominent colours in an image, and uses them as
/// extremes for matching all pixels on the image to a grayscale palette.
/// </summary>
/// <param name="image">Image to reduce.</param>
/// <param name="bgWhite">True if the background (the most found colour) should become the white colour. If not, it will be the black one.</param>
/// <returns>
/// An 8-bit image with the image content of the input reduced to grayscale,
/// with the found two most found colours as black and white.
/// </returns>
public static Bitmap ReduceToTwoColorFade(Bitmap image, Boolean bgWhite)
{
// Get data out of the image, using LockBits and Marshal.Copy
Int32 width = image.Width;
Int32 height = image.Height;
// LockBits can actually -convert- the image data to the requested colour depth.
// 32 bpp is the easiest to get the colour components out.
BitmapData sourceData = image.LockBits(new Rectangle(0, 0, width, height), ImageLockMode.ReadOnly, PixelFormat.Format32bppArgb);
// Not really needed for 32bpp, but technically the stride does not always match the
// amount of used data on each line, since the stride gets rounded up to blocks of 4.
Int32 stride = sourceData.Stride;
Byte[] imgBytes = new Byte[stride * height];
Marshal.Copy(sourceData.Scan0, imgBytes, 0, imgBytes.Length);
image.UnlockBits(sourceData);
// Make colour population histogram
Int32 lineOffset = 0;
Dictionary<Int32, Int32> histogram = new Dictionary<Int32, Int32>();
for (Int32 y = 0; y < height; y++)
{
Int32 offset = lineOffset;
for (Int32 x = 0; x < width; x++)
{
// Optional check: only handle if not mostly-transparent
if (imgBytes[offset + 3] > 0x7F)
{
// Get colour values from bytes, without alpha.
// Little-endian: UInt32 0xAARRGGBB = Byte[] { BB, GG, RR, AA }
Int32 val = (imgBytes[offset + 2] << 16) | (imgBytes[offset + 1] << 8) | imgBytes[offset + 0];
if (histogram.ContainsKey(val))
histogram[val] = histogram[val] + 1;
else
histogram[val] = 1;
}
offset += 4;
}
lineOffset += stride;
}
// Sort the histogram. This requires System.Linq
KeyValuePair<Int32, Int32>[] histoSorted = histogram.OrderByDescending(c => c.Value).ToArray();
// Technically these colours will be transparent when built like this, since their
// alpha is 0, but we won't use them directly as colours anyway.
// Since we filter on alpha, getting a result is not 100% guaranteed.
Color colBackgr = histoSorted.Length < 1 ? Color.Black : Color.FromArgb(histoSorted[0].Key);
// if less than 2 colors, just default it to the same.
Color colContent = histoSorted.Length < 2 ? colBackgr : Color.FromArgb(histoSorted[1].Key);
// Make a new 256-colour palette, making a fade between these two colours, for feeding into GetClosestPaletteIndexMatch later
Color[] matchPal = new Color[0x100];
Color toBlack = bgWhite ? colContent : colBackgr;
Color toWhite = bgWhite ? colBackgr : colContent;
Int32 rFirst = toBlack.R;
Int32 gFirst = toBlack.G;
Int32 bFirst = toBlack.B;
Double rDif = (toBlack.R - toWhite.R) / 255.0;
Double gDif = (toBlack.G - toWhite.G) / 255.0;
Double bDif = (toBlack.B - toWhite.B) / 255.0;
for (Int32 i = 0; i < 0x100; i++)
matchPal[i] = Color.FromArgb(
Math.Min(0xFF, Math.Max(0, rFirst - (Int32)Math.Round(rDif * i, MidpointRounding.AwayFromZero))),
Math.Min(0xFF, Math.Max(0, gFirst - (Int32)Math.Round(gDif * i, MidpointRounding.AwayFromZero))),
Math.Min(0xFF, Math.Max(0, bFirst - (Int32)Math.Round(bDif * i, MidpointRounding.AwayFromZero))));
// Ensure start and end point are correct, and not mangled by small rounding errors.
matchPal[0x00] = Color.FromArgb(toBlack.R, toBlack.G, toBlack.B);
matchPal[0xFF] = Color.FromArgb(toWhite.R, toWhite.G, toWhite.B);
// The 8-bit stride is simply the width in this case.
Int32 stride8Bit = width;
// Make 8-bit array to store the result
Byte[] imgBytes8Bit = new Byte[stride8Bit * height];
// Reset offset for a new loop through the image data
lineOffset = 0;
// Make new offset var for a loop through the 8-bit image data
Int32 lineOffset8Bit = 0;
for (Int32 y = 0; y < height; y++)
{
Int32 offset = lineOffset;
Int32 offset8Bit = lineOffset8Bit;
for (Int32 x = 0; x < width; x++)
{
Int32 toWrite;
// If transparent, revert to background colour.
if (imgBytes[offset + 3] <= 0x7F)
{
toWrite = bgWhite ? 0xFF : 0x00;
}
else
{
Color col = Color.FromArgb(imgBytes[offset + 2], imgBytes[offset + 1], imgBytes[offset + 0]);
toWrite = GetClosestPaletteIndexMatch(col, matchPal);
}
// Write the found colour index to the 8-bit byte array.
imgBytes8Bit[offset8Bit] = (Byte)toWrite;
offset += 4;
offset8Bit++;
}
lineOffset += stride;
lineOffset8Bit += stride8Bit;
}
// Make new 8-bit image and copy the data into it.
Bitmap newBm = new Bitmap(width, height, PixelFormat.Format8bppIndexed);
BitmapData targetData = newBm.LockBits(new Rectangle(0, 0, width, height), ImageLockMode.WriteOnly, newBm.PixelFormat);
// get minimum data width for the pixel format.
Int32 newDataWidth = ((Image.GetPixelFormatSize(newBm.PixelFormat) * width) + 7) / 8;
// Note that this Stride will most likely NOT match the image width; it is rounded up to the
// next multiple of 4 bytes. For that reason, we copy the data per line, and not as one block.
Int32 targetStride = targetData.Stride;
Int64 scan0 = targetData.Scan0.ToInt64();
for (Int32 y = 0; y < height; ++y)
Marshal.Copy(imgBytes8Bit, y * stride8Bit, new IntPtr(scan0 + y * targetStride), newDataWidth);
newBm.UnlockBits(targetData);
// Set final image palette to grayscale fade.
// 'Image.Palette' makes a COPY of the palette when accessed.
// So copy it out, modify it, then copy it back in.
ColorPalette pal = newBm.Palette;
for (Int32 i = 0; i < 0x100; i++)
pal.Entries[i] = Color.FromArgb(i, i, i);
newBm.Palette = pal;
return newBm;
}
The used GetClosestPaletteIndexMatch function:
/// <summary>
/// Uses Pythagorean distance in 3D colour space to find the closest match to a given colour on
/// a given colour palette, and returns the index on the palette at which that match was found.
/// </summary>
/// <param name="col">The colour to find the closest match to</param>
/// <param name="colorPalette">The palette of available colours to match</param>
/// <returns>The index on the palette of the colour that is the closest to the given colour.</returns>
public static Int32 GetClosestPaletteIndexMatch(Color col, Color[] colorPalette)
{
Int32 colorMatch = 0;
Int32 leastDistance = Int32.MaxValue;
Int32 red = col.R;
Int32 green = col.G;
Int32 blue = col.B;
for (Int32 i = 0; i < colorPalette.Length; ++i)
{
Color paletteColor = colorPalette[i];
Int32 redDistance = paletteColor.R - red;
Int32 greenDistance = paletteColor.G - green;
Int32 blueDistance = paletteColor.B - blue;
// Technically, Pythagorean distance needs to have a root taken of the result, but this is not needed for just comparing them.
Int32 distance = (redDistance * redDistance) + (greenDistance * greenDistance) + (blueDistance * blueDistance);
if (distance >= leastDistance)
continue;
colorMatch = i;
leastDistance = distance;
if (distance == 0)
return i;
}
return colorMatch;
}
The result:
```
if (alpha != null && input != null)
{
Bitmap output = new Bitmap(input.Width, input.Height, PixelFormat.Format32bppArgb);
var rect = new Rectangle(0, 0, input.Width, input.Height);
var bitsAlpha = alpha.LockBits(rect, ImageLockMode.ReadOnly, PixelFormat.Format32bppArgb);
var bitsInput = input.LockBits(rect, ImageLockMode.ReadOnly, PixelFormat.Format32bppArgb);
var bitsOutput = output.LockBits(rect, ImageLockMode.WriteOnly, PixelFormat.Format32bppArgb);
unsafe
{
for (int y = 0; y < input.Height; y++)
{
byte* ptrAlpha = (byte*)bitsAlpha.Scan0 + y * bitsAlpha.Stride;
byte* ptrInput = (byte*)bitsInput.Scan0 + y * bitsInput.Stride;
byte* ptrOutput = (byte*)bitsOutput.Scan0 + y * bitsOutput.Stride;
for (int x = 0; x < input.Width; x++)
{
ptrOutput[4 * x] = ptrInput[4 * x]; // blue
ptrOutput[4 * x + 1] = ptrInput[4 * x + 1]; // green
ptrOutput[4 * x + 2] = ptrInput[4 * x + 2]; // red
ptrOutput[4 * x + 3] = ptrAlpha[4 * x]; // alpha
}
}
}
alpha.UnlockBits(bitsAlpha);
input.UnlockBits(bitsInput);
output.UnlockBits(bitsOutput);
return output;
}
```
I changed the PixelFormat to Format8bppIndexed.I set the pixel format to Format8bppIndexed and came to this conclusion image . Please help me
From what I can see, you're trying to use an 8-bit grayscale image as alpha for another picture.
This does not mean the final output will be 8-bit. It doesn't even mean the input image is 8-bit. In fact, the output of this should still be 32-bit, since 8-bit only supports palette-based transparency, meaning you set alpha to specific colours (affecting all pixels on the image that use that colour), rather than to specific pixels on the image.
The only things you need to change are these:
Since the alpha image is apparently 8-bit, lock that one as 8-bit. But to be sure, you should add a specific check in advance to test if its pixel format is indeed Format8bppIndexed.
Since that image is now locked as 8-bit, its single pixels are not grouped per 4 bytes but per 1 byte. So in the code that retrieves the alpha from it, remove the * 4 part.
The changed lines:
var bitsAlpha = alpha.LockBits(rect, ImageLockMode.ReadOnly, PixelFormat.Format8bppIndexed);
and
ptrOutput[4 * x + 3] = ptrAlpha[x]; // alpha
Besides this, the code should be kept as it is.
Red, Green, Blue and Alpha is for 32bit images (each of these is stored as a byte which 8 bits, 4 x 8 = 32), indexed images doesn't work this way.
1 . if your image is a 32bit image, then your loop steps should be 4:
for (int x = 0; x < input.Width; x+=4) // x+=3 for 24bit images (without alpha like jpg images)
instead of
for (int x = 0; x < input.Width; x++)
for 8bit indexed images it does not work that way and the colors are stored in a pallet (have a look at this)
lets say i have a image
i would like to find the black rectangle bounds(right,left,width,height) in the image(lets say there's no other black pixels in this image).
my code so far is:
private unsafe Bitmap GetDiffBitmap(Bitmap bmp)
{
bmData = bmp.LockBits(new Rectangle(0, 0, bmp.Width, bmp.Height), System.Drawing.Imaging.ImageLockMode.ReadOnly, bmp.PixelFormat);
IntPtr scan0 = bmData.Scan0;
int stride = bmData.Stride;
int nWidth = bmp.Width;
int nHeight = bmp.Height;
for(int y = 0; y < nHeight; y++)
{
//define the pointers inside the first loop for parallelizing
byte* p = (byte*)scan0.ToPointer();
p += y * stride;
for (int x = 0; x < nWidth; x++)
{
//always get the complete pixel when differences are found
if(p[0]==0 && p[1]==0 && p[2]==0)
{
// p[0] = 255;
// p[1] = 255;
// p[2] =255;
right = nWidth;//geting the position of the lastest black pixel;
}
p += 4;
}
}
bmp.UnlockBits(bmData);
return bmp;
}
its seems like my nwidth is also as the image width-so its not working.. i got acces to these pixels and i can change them but i dont know why i can count them and find a proper bounds of the black rectangle... if anyone could help me i would really apperciate it,
There are a few issues with this kind of image analysis:
1 replace
if(p[0]==0 && p[1]==0 && p[2]==0)
{
right = nWidth;//geting the position of the lastest black pixel;
}
with
if(p[0]==0 && p[1]==0 && p[2]==0)
{
right = x; //geting the position of the lastest black pixel;
}
Your x-iteration variable already counts which pixel you are on.
2 The code you provided only works for 32bpp pixel formats. If this is on purpose you should check if the bitmap you are analysing is in a compatible format.
3 With most compressed image formats you often won't get an exactly 0 on all 3 color channels for black, you should do a "less than something dark" check instead of a zero.
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.
I am creating a program that scans all the pixels of an image, and whenever it finds a pixel that contains the color pink. It makes the pixel black. But it doesn't seem to find a pink pixel when there is two of them on the image. I do not know if I am using LockBits correctly, maybe I am using it wrong. Can someone please help me solve this I would greatly appreciate it.
Here is the code below:
Bitmap bitmap = pictureBox1.Image as Bitmap;
System.Drawing.Imaging.BitmapData d = bitmap.LockBits(new Rectangle(0, 0, bitmap.Width, bitmap.Height), System.Drawing.Imaging.ImageLockMode.ReadWrite, bitmap.PixelFormat);
IntPtr ptr = d.Scan0;
byte[] rgbs = new byte[Math.Abs(d.Stride) * bitmap.Height];
Marshal.Copy(ptr, rgbs, 0, rgbs.Length);
Graphics g = pictureBox1.CreateGraphics();
for (int index = 2; index < rgbs.Length; index += 3)
{
if (rgbs[index] == 255 && rgbs[index - 1] == 0 && rgbs[index - 2] == 255) // If color = RGB(255, 0, 255) Then ...
{
// This never gets executed!
rgbs[index] = 0;
rgbs[index - 1] = 0;
rgbs[index - 2] = 0;
}
}
Marshal.Copy(rgbs, 0, ptr, rgbs.Length); // Copy rgb values back to the memory location of the bitmap.
pictureBox1.Image = bitmap;
bitmap.UnlockBits(d);
You don't need to copy the pixel data into an array. The point of LockBits is it gives you direct (unsafe) access the memory. You can just iterate the pixels and change them as you find them. You will need to know the format of the image to do this successfully.
BitmapData bmd=bm.LockBits(new Rectangle(0, 0, 10, 10),
ImageLockMode.ReadOnly, bm.PixelFormat);
// Blue, Green, Red, Alpha (Format32BppArgb)
int pixelSize=4;
for(int y=0; y<bmd.Height; y++)
{
byte* row=(byte *)bmd.Scan0+(y*bmd.Stride);
for(int x=0; x<bmd.Width; x++)
{
int offSet = x*pixelSize;
// read pixels
byte blue = row[offSet];
byte green = row[offSet+1];
byte red = row[offSet+2];
byte alpha = row[offSet+3];
// set blue pixel
row[x*pixelSize]=255;
}
}
It's a little more tricky in VB than C# as VB has no knowledge of pointers and requires the use of the marshal class to access unmanaged data. Here's some sample code. (For some reason I originally though this was a VB question).
Dim x As Integer
Dim y As Integer
' Blue, Green, Red, Alpha (Format32BppArgb)
Dim PixelSize As Integer = 4
Dim bmd As BitmapData = bm.LockBits(new Rectangle(0, 0, 10, 10),
ImageLockMode.ReadOnly, bm.PixelFormat)
For y = 0 To bmd.Height - 1
For x = 0 To bmd.Width - 1
Dim offSet As Int32 = (bmd.Stride * y) + (4 * x)
' read pixel data
Dim blue As Byte = Marshal.ReadByte(bmd.Scan0, offSet)
Dim green As Byte = Marshal.ReadByte(bmd.Scan0, offSet + 1)
Dim red As Byte = Marshal.ReadByte(bmd.Scan0, offSet + 2)
Dim alpha As Byte = Marshal.ReadByte(bmd.Scan0, offSet + 3)
' set blue pixel
Marshal.WriteByte(bmd.Scan0, offSet , 255)
Next
Next