Why doesn't c# bitmap setPixel() method work? - c#

I have a method that takes in numbers for a rectangle and "blanks" the pixels (turns them white) for that area. For some reason though when my program searches for non white pixels, its finding the ones i just set to white.
Why is it saying its not white when it is? I have an image that gets cropped and is saved to my hard drive. So I'm able to view the area it says isn't white, but when i open the image, its completely white as can be. So I'm at a lose as to why this isn't working. The program stops on the same pixel every time. It says the R value is 238 and I know that pixel was set to white because i stepped through the debugger and watched the pixel value go into the bmp.SetPixel method.
This is the code for the "blanking" method:
void blankArea(int x, int y, int width, int height)
{
for (int i = y; i <= height+y; ++i)
for (int t = x; t <= width+x; ++t)
{
if (t == w)
break;
bmp.SetPixel(t, i, Color.White);
}
}
this is the code that says that pixel isn't white:
bool allWhiteColumn (int col)
{
for (int i = 5; i < h; ++i)
if (bmp.GetPixel(col - 1, i).R < 248 && bmp.GetPixel(col - 1, i).G < 248)
{
imageBelowColumEnd = bmp.GetPixel(col - 1, i).R;
this.row = i;
return false;
}
return true;
}
Any help at this point would be greatly appreciated. I have no idea why it's saying the R is 238 after I set it to white. Thanks
EDIT- since I post a comment I'll edit instead. Taking the if out, did nothing. Same problem. I can't check for plain white. The program finds images from a scanner. So because a scanners "white" areas are not true white, I can not look for Color.WHITE. I'll try the bitlock though...
EDIT 2- "Attempted to read or write protected memory. This is often an indication that other memory is corrupt." was thrown when i tried to do the example for the locking and unlocking of the bits. It threw it once it tried to copy the bits into the array. Apparently thats not allowed.
Thanks for the answers though.

You could try to use the Bitmap.Lockbits() method before, and Bitmap.Unlockbits() after you have changed the pixels.
Actually, here is an example: http://msdn.microsoft.com/en-us/library/5ey6h79d.aspx

Remove the if (t == w) break; in your first function, whatever is w (which you did not describe). I guess the code breaks and you end up with an incomplete white region.

Would it not make more sense to check for white for starters?
bool allWhiteColumn (int col)
{
for (int i = 5; i < h; ++i)
if (bmp.GetPixel(col - 1, i) != Color.White)
{
imageBelowColumEnd = bmp.GetPixel(col - 1, i).R;
this.row = i;
return false;
}
return true;
}

Related

Aiming for a more accurate and precise color replacer

What I'm trying to achieve:
In my program, I want to let the user enter any image, then my program resizes it then it gets all of the pixels and checks whether their color matches the color of the pixel at (0, 0). If it does match that pixel then it's going to be replaced with a hardcoded color.
The issue I'm having
My code works when I try to resize and replace the pixel colors with new ones, however when replacing the pixel colors, it usually does not replace some of the pixels, especially those that are closest to the image itself where the colors begin to diverse.
What I tried to fix the problem
I tried declaring the HEX colors as a string and whenever the C letter came, I would tell the program to remove the pixel that string. However, I found that to be very inefficient due to how often it can replace pixels that shouldn't be replaced.
My code
public class PicGen
{
public PicGen(PictureBox pictureBox)
{
Bitmap picBitmap = new(pictureBox.Image);
Bitmap resized = new(picBitmap, new(52, 52));
Color backColorBottom = resized.GetPixel(51, 0);
for (int i = 0; i < resized.Width; i++)
{
for (int j = 0; j < resized.Height; j++)
{
if (resized.GetPixel(i, j) == backColorBottom)
resized.SetPixel(i, j, Color.FromArgb(54, 57, 63));
}
}
Clipboard.SetImage(resized);
}
Example: 1 Notice how the white around the image isn't replaced like the rest of the background? ||
Original 2

Optimizing a bitmap character drawing algorithm in C#

Is there any way to optimize this:
A character is stored in a "matrix" of bytes, dimensions 9x16, for the sake of the example, let's call it character.
The bytes can be values either 1 or 0 , meaning draw foreground and draw background respectively.
The X and Y variables are integers, representing X and Y coordinates used for the SetPixel() function. BG and FG represent background and foreground colors respectively, both type of Color.
The drawing part of the algorithm itself looks like this:
for(int i=0;i<16;i++)
{
for(int j=0;j<9;j++)
{
if(character[i][j] == 1)
{
SetPixel(X,Y,BG);
}
else
{
SetPixel(X,Y,FG);
}
X++;
}
X=0;
Y++;
}
Later on, X incremented by 9 and Y is set back to 0.
The problem with this algorithm is , when it's called for drawing a string (many characters sequentially), it's extremely slow.
I'm not really sure what characters mean, however.
GetPixel internally calls LockBits to pin the memory
Ergo Its best to use LockBits once and be done with it
Always call UnlockBits
Direct pointer access using unsafe can give you a small amount of performance as well
Also (in this case) your for loops can be optimized (code wise) to include your other indexes.
Exmaple
protected unsafe void DoStuff(string path)
{
...
using (var b = new Bitmap(path))
{
var r = new Rectangle(Point.Empty, b.Size);
var data = b.LockBits(r, ImageLockMode.ReadOnly, PixelFormat.Format32bppPArgb);
var p = (int*)data.Scan0;
try
{
for (int i = 0; i < 16; i++, Y++)
for (int j = 0, X = 0; j < 9; j++, X++)
*(p + X + Y * r.Width) = character[i][j] == 1 ? BG : FG;
}
finally
{
b.UnlockBits(data);
}
}
}
Bitmap.LockBits
Locks a Bitmap into system memory.
Bitmap.UnlockBits
Unlocks this Bitmap from system memory.
unsafe
The unsafe keyword denotes an unsafe context, which is required for
any operation involving pointers.
Further reading
Unsafe Code and Pointers
Bitmap.GetPixel
LockBits vs Get Pixel Set Pixel - Performance

Find / replace colors in (i.e. recolor) a picture

I am trying to recreate the Recolor Picture dialog that Microsoft unfortunately discontinued in the transition from Office 2003 to 2007. This was very useful for replacing colors in a picture (see http://www.indezine.com/products/powerpoint/learn/picturesandvisuals/recolor-pictures-ppt2003.html for full description of dialog).
I am mostly interested in doing this for images in the metafile format (EMF or WMF), which tend to have fewer colors than other picture formats, in my experience. The picture below is an example of an enhanced metafile picture pasted from Excel into PowerPoint that appears to contain just 6 colors:
If I was able to use the legacy Office dialog pictured above, I would see my 6 colors on the left in the "Original" column, and I could easily change the blue font (and border) color to black. The problem here is that if I use GetPixel() to programmatically inventory the colors in the image, I get dozens of colors due to anti-aliasing of the fonts, and it isn't practical to show the user all these recoloring options (which would effectively require the user to manually recreate the proper anti-aliasing effect). The snippet of code below illustrates how I have tried to inventory the colors:
Dim listColors as New List(Of Color)
Dim shp as PowerPoint.Shape = [a metafile picture in PowerPoint]
Dim strTemp as String = Path.Combine(Environ("temp"), "temp_img.emf")
shp.Export(strTemp, PowerPoint.PpShapeFormat.ppShapeFormatEMF, 0, 0)
Using bmp As New Bitmap(strTemp)
For x As Integer = 0 To bmp.Width - 1
For y As Integer = 0 To bmp.Height - 1
listColors.Add(bmp.GetPixel(x, y))
Next
Next
End Using
I see that there is an optional Palette property for metafiles, which I thought could provide an answer, but an exception is thrown when I try to access it, so that was a dead end. I also see that there are headers for metafile images, but I cannot decipher what to do with them from the limited documentation on the Internet, and I am not even sure that that would get me to the right answer (i.e. 6 colors).
To summarize, part 1 of the question is how to inventory (i.e. identify) the 6 "core" colors in the image above, and part 2 is how to replace one of these 6 colors with another. VB.NET solutions are preferred, although I can probably translate C# code if not too complex.
If needed, you can download the EMF version of the image above at https://www.dropbox.com/s/n03ys3dh9pcd0xu/temp_img.emf?dl=0.
EDIT: To be clear, I am not interested in "computing" the six "core" colors in the image above. I believe, perhaps incorrectly, that these six colors are explicit properties of the image, and my first objective is to figure out how to access them. Indeed, if you simply ungroup the metafile picture twice in PowerPoint, you can loop through the resulting shapes to get these six colors. That would address part 1 of the question, although it seems a bit sloppy, works only for metafiles (which may be fine, actually), and I doubt that is how the legacy Recolor Picture dialog worked. To address part 2, I could regroup the metafile picture shapes after swapping colors but, again, that seems sloppy and modifies the picture in ways other than what is intended. So, how can I explicitly retrieve / modify / set these "core" colors in a [metafile] picture?
So I've had a quick stab at implementing one of my ideas to use a dictionary of colours and what not. The code isn't working properly just yet but I figured I show it here so that you can have a quick look into how it works and develop on it from there.
using (Bitmap bitmap = new Bitmap(#"InputPath"))
{
Dictionary<Color, List<Color>> colourDictionary = new Dictionary<Color, List<Color>>();
int nTolerance = 60;
int nBytesPerPixel = Bitmap.GetPixelFormatSize(bitmap.PixelFormat) / 8;
System.Drawing.Imaging.BitmapData bitmapData = bitmap.LockBits(new Rectangle(0, 0, bitmap.Width, bitmap.Height), System.Drawing.Imaging.ImageLockMode.ReadOnly, bitmap.PixelFormat);
try
{
int nByteCount = bitmapData.Stride * bitmap.Height;
byte[] _baPixels = new byte[nByteCount];
System.Runtime.InteropServices.Marshal.Copy(bitmapData.Scan0, _baPixels, 0, _baPixels.Length);
int _nStride = bitmapData.Stride;
for (int h = 0; h < bitmap.Height; h++)
{
int nCurrentLine = h * _nStride;
for (int w = 0; w < (bitmap.Width * nBytesPerPixel); w += nBytesPerPixel)
{
int nBlue = _baPixels[nCurrentLine + w];
int nGreen = _baPixels[nCurrentLine + w + 1];
int nRed = _baPixels[nCurrentLine + w + 2];
if (colourDictionary.Keys.Count > 0)
{
Color[] caNearbyColours = colourDictionary.Keys.Select(c => c)
.Where(c => (int)c.B <= (nBlue + nTolerance) && (int)c.B >= (nBlue - nTolerance)
&& (int)c.G <= (nGreen + nTolerance) && (int)c.G >= (nGreen - nTolerance)
&& (int)c.R <= (nRed + nTolerance) && (int)c.R >= (nRed - nTolerance)).ToArray();
if (caNearbyColours.Length > 0)
{
if (!colourDictionary[caNearbyColours.FirstOrDefault()].Any(c => c.R == nRed && c.G == nGreen && c.B == nBlue))
colourDictionary[caNearbyColours.FirstOrDefault()].Add(Color.FromArgb(255, nRed, nGreen, nBlue));
}
else
colourDictionary.Add(Color.FromArgb(255, nRed, nGreen, nBlue), new List<Color>());
}
else
colourDictionary.Add(Color.FromArgb(255, nRed, nGreen, nBlue), new List<Color>());
}
}
}
finally
{
bitmap.UnlockBits(bitmapData);
}
using (Bitmap colourBitmap = new Bitmap(bitmap.Width, bitmap.Height, bitmap.PixelFormat))
{
using (Graphics g = Graphics.FromImage(colourBitmap))
{
for (int h = 0; h < colourBitmap.Height; h++)
{
for (int w = 0; w < colourBitmap.Width; w++)
{
Color colour = bitmap.GetPixel(w, h);
if (!colourDictionary.ContainsKey(colour))
{
Color keyColour = colourDictionary.Keys.FirstOrDefault(k => colourDictionary[k].Any(v => v == colour));
colourBitmap.SetPixel(w, h, keyColour);
}
else
colourBitmap.SetPixel(w, h, colour);
}
}
}
colourBitmap.Save(#"OutputPath", System.Drawing.Imaging.ImageFormat.Png);
}
}
Notice how the top section uses Lockbits for better performance. This can easily be transferred onto the bottom section. Also note that the lock bits code is set to work for images with a bytes per pixel of 3 or 4.
Now onto how the code is attempting to work:
It starts off by looping over the initial image and finding colours. If the colour is found already it skips it, however if the colour is not in the dictionary it will add it and also if the colour has a similar colour in the dictionaries keys (will need changing to look at values too) it will add it to it's values.
This then loops through the pixels in the output image setting them according to the keys in the dictionary, thus creating a 'blocked' image.
Now as I said it isn't working just yet, so here are some improvements needed to be made:
As said above checking the colour to those in the values
Adding lock bits to the bottom code for better performance
Tweaking the nTolerance value for better results
Keep track of counts of colours and at the end of looping set the key to that with the largest count
And of course anything else that I have not thought of

How to measure width of character precisely?

Maybe I've got something wrong, but... I want to simulate character spacing.
I break the word (text) into the list of single characters, measure their widths, and then painting them one after another on the bitmap. I supposed, that overall width of the rendered text will be the same as the width of the whole not splitted string, but there is something wrong. Rendering characters in a loop show wider result. Is there any way to get common (expected) results?
here is a code snippet:
private struct CharWidths
{
public char Char;
public float Width;
}
private List<CharWidths> CharacterWidths = new List<CharWidths>();
...
private void GetCharacterWidths(string Text, Bitmap BMP)
{
int i;
int l = Text.Length;
CharacterWidths.Clear();
Graphics g = Graphics.FromImage(BMP);
CharWidths cw = new CharWidths();
for (i = 0; i < l; i++)
{
Size textSize = TextRenderer.MeasureText(Text[i].ToString(), Font);
cw.Char = Text[i];
cw.Width = textSize.Width;
CharacterWidths.Add(cw);
}
}
...
public void RenderToBitmap(Bitmap BMP)
{
//MessageBox.Show("color");
Graphics g = Graphics.FromImage(BMP);
GetCharacterWidths("Lyborko", BMP);
int i;
float X = 0;
PointF P = new PointF();
for (i = 0; i < CharacterWidths.Count; i++)
{
P.X = X;
P.Y = 0;
g.DrawString(CharacterWidths[i].Char.ToString(), Font, Brushes.White, P);
X = X+CharacterWidths[i].Width;
}
P.X = 0;
P.Y = 30;
g.DrawString("Lyborko", Font, Brushes.White, P);
// see the difference
}
Thanx a lot
First of all should say that don't have a silver bullet solution for this, but have a couple of suggessions on subject:
Considering that you by calling TextRenderer.MeasureText do not pass current device context (the same one you use to draw a string after) and knowing a simple fact that MeasureText simply in case of lack of that parameter creates a new one compatible with desktop and calls DrawTextEx WindowsSDK function, I would say first use an overload of MeasureText where you specify like a first argument device context which you use to render a text after. Could make a difference.
If it fails, I would try to use Control.GetPreferredSize method to guess most presize possible rendering dimension of the control on the screen, so actually the dimension of you future string's bitmap. To do that you can create some temporary control, assign a string, render and after call this function. It's clear to me that this solution may hardly fit in your app architecture, but can possibly produce a better results.
Hope this helps.

c# getPixel not sellecting all pixels

I have a problem with getting the pixels from an image. I load a image, select a pixel from the image and retrieve it's color and then i generate a matrix indexMatrix[bitmap_height][bitmap_width] which contains 1 or 0 depending if the [x,y] color of the bitmap is the same as the color selected. The problem is that the program doesn't select all the pixels although it should. It only retrieves a part of them ( i am sure the pixels 'forgotten' are the same color as the selected color )
The wierd thing is that if i run my program for the new image ( the one constructed from the matrix ) it returns the same image ( as it should ) but i can't figure out how to fix the problem.
Please Help!!!
Regards,
Alex Badescu
and some code from my project :
bitmap declaration:
m_Bitmap = (Bitmap)Bitmap.FromFile(openFileDialog.FileName, false);
Here i calculate the matrix:
int bitmapWidth = m_Bitmap.Width;
int bitmapHeight = m_Bitmap.Height;
indexMatrix = new int[bitmapHeight][];
if (imageIsLoaded && colorIsSelected)
{
for (int i = 0; i < bitmapHeight; i++)
{
indexMatrix[i] = new int[bitmapWidth];
for (int j = 0; j < bitmapWidth; j++)
{
Color temp = m_Bitmap.GetPixel(j, i);
if (temp == selectedColor)
indexMatrix[i][j] = 1;
else indexMatrix[i][j] = 0;
}
}
MessageBox.Show("matrix generated succesfully");
}
matrixIsCalculated = true;
}
There is no obvious failure mode here. Other than that the pixel isn't actually a match with the color. Being off by, say, only one in the Color.B value for example. You cannot see this with the unaided eye.
These kind of very subtle color changes are quite common when the image was resized. An interpolation filter alters the color subtly, even if not strictly needed. Another failure mode is using a compressed image format like JPEG, the compression algorithm changes colors.

Categories

Resources