Replace Color Gradient In Transparent Image - c#

I have small function which will recolor pixels in a Bitmap from a given color to a new given color.
The problems I have with the code are as follows:
1)
The function gives results which are remapping white pixels which should not be concidered since I have a threshold... (unless I have defined this calculation wrong)
2) When certain colors are given e.g. LimeGreen wierd results are seen in the image returned from the function (I beleive this is due to overflow of the byte type in the addition or subtraction case)
The base image I am using can be found here:
http://www.freeimagehosting.net/uploads/c8745a9de1.png
Results I have obtained can be found here:
freeimagehosting.net/uploads/fa48e5a0eb.png (Called with Color.Magenta as remapColor, Color.Red as newColor, Seems like white pixels are effected and the end of the gradient is not colored correctly)
freeimagehosting.net/uploads/8faec6a569.png (Called with Color.Magenta as remapColor, Color.Yellow as newColor, Seems like white pixels are effected and the end of the gradient is not colored correctly)
freeimagehosting.net/uploads/2efd4c04aa.png (Called with Color.Magenta as remapColor, Color.Blue as newColor, Seems like gradient not colored correctly)
freeimagehosting.net/uploads/defdf04e16.png (Called with Color.Magenta as remapColor, Color.Teal as newColor, Seems like white pixels are effected and none of the gradient is calculated correctly)
The function I have for this code is below: UPDATED per suggestions
public unsafe static Bitmap RecolorImage(Bitmap original, Color remapColor, Color newColor)
{
Bitmap result = new Bitmap(original.Width, original.Height);
//lock the original bitmap in memory
BitmapData originalData = original.LockBits(
new Rectangle(0, 0, original.Width, original.Height),
ImageLockMode.ReadOnly, PixelFormat.Format32bppArgb);
//lock the new bitmap in memory
BitmapData newData = result.LockBits(
new Rectangle(0, 0, original.Width, original.Height),
ImageLockMode.WriteOnly, PixelFormat.Format32bppArgb);
//set the number of bytes per pixel
int pixelSize = 4;
int rthreshold = 128;
int gthreshold = 128;
int bthreshold = 128;
for (int y = 0; y < original.Height; y++)
{
//get the data from the original image
byte* oRow = (byte*)originalData.Scan0 + (y * originalData.Stride);
//get the data from the new image
byte* nRow = (byte*)newData.Scan0 + (y * newData.Stride);
for (int x = 0; x < original.Width; x++)
{
//examine the rgb values
byte r = (byte)((oRow[x * pixelSize]));
byte g = (byte)((oRow[x * pixelSize + 1]));
byte b = (byte)((oRow[x * pixelSize + 2]));
byte a = (byte)((oRow[x * pixelSize + 3]));
if (a > 0 &&
Math.Abs(remapColor.R - r) <= rthreshold &&
Math.Abs(remapColor.B - b) <= bthreshold &&
Math.Abs(remapColor.G - g) <= gthreshold
)
{
if (newColor.R == 0)
{
r = 0;
}
else
{
if (newColor.R > remapColor.R)
r = (byte)(r - newColor.R);
else
r = (byte)(r + newColor.R);
}
if (newColor.G == 0)
{
g = 0;
}
else
{
if (newColor.G > remapColor.G)
g = (byte)(g - newColor.G);
else
g = (byte)(g + newColor.G);
}
if (newColor.B == 0)
{
b = 0;
}
else
{
if (newColor.B > remapColor.B)
b = (byte)(b - newColor.B);
else
b = (byte)(b + newColor.B);
}
}
//set the new image's pixel remaped pixel color
nRow[x * pixelSize] = b; //B
nRow[x * pixelSize + 1] = g; //G
nRow[x * pixelSize + 2] = r; //R
nRow[x * pixelSize + 3] = a; //A
}
}
original.UnlockBits(originalData);
result.UnlockBits(newData);
return result;
}
What gives....
Is what I am trying to do possible?
Is it reliable?
Is there just a bug in my code?
Is there a better way to achive this "re-mapable technique" on bitmaps using gradients?
Thank you for your time.

It looks like your threshold test is incorrect. Take the line:
remapColor.R - r <= rthreshold
If the current pixel is white, then r will be 255, and the test will always be true, no matter what remapColor.R and rthreshold are.
I think Math.Abs(remapColor.R - r) might work.
And you're likely correct about your byte values being out of bounds. Fixing the threshold test might stop that from happening. Otherwise, try putting some bounds checking in to see where it's happening.

I have decided that although this may be possible if I study the various materials regarding color spaces and their supporting theories. It seems that this will take a bit more than some quick threshold calculation and normalization to the remapColor.
I am going to propose that instead of performing this type of modification on a raster bitmap image that the graphics be modified in their vector form.
The process should be something like this:
The graphics are created in whatever imaging suite the designer is working in.
They are saved to a vector format e.g. SVG this will allow the customizable paths to be named, traversed and altered programmatically (and for more than color if needed) with SVG Rendering Engine(http://svg.codeplex.com/)
With this solution we can either output the SVG direct to the browser if supported and do the modifications directly on the client or use the server and output as PNG when needed.
I feel that this arrangement will provide us with more flexibility and a more robust solution than what I was initially going to hack together.
Thank you guys for your time!

Related

Bitmap.GetPixel only returning 0s for R, G and B

I am loading an image inside a Console application using Image.FromFile.
After that, I am casting it to a Bitmap to be able to use the Bitmap.GetPixel() method.
Surprisingly, while looping through all the pixels, all what I am getting from the GetPixel is 0,0,0 for R,G,B.
To make sure that the image is well read from the file, I added a reference to System.Windows.Forms and I loaded the image inside a PictureBox to see the result, and the image is well seen.
Original Image:
Here's how I am loading the image and showing it into a PictureBox:
Bitmap img = (Bitmap)Image.FromFile("image.png");
PictureBox pb = new PictureBox
{
Image = img
};
Form frm = new Form
{
Controls = { pb }
};
frm.ShowDialog();
Which shows the image as it is:
And after that, I am getting the pixels like:
byte[] input = new byte[784];
for (int x = 0; x < 28; x++)
{
for (int y = 0; y < 28; y++)
{
Color color = img.GetPixel(x, y);
byte r = color.R;
byte g = color.G;
byte b = color.B;
Console.Write($"{color.R},{color.G},{color.B}|||");
input[x * 28 + y] = (byte)((((r + g + b) / 3)));
}
Console.WriteLine();
}
Note that the image size is 28x28px, and I have tried other images and I've got the same result.
What I expected that the results shows the real color values since I've used that method before and it worked perfectly, but now all the results printed to the console are zeros which I am finding it difficult to understand.
Edit:
Since the PictureBox is showing the real image representation, I tried to get the pixels from the PictureBox.Image like:
Color color = ((Bitmap)pb.Image).GetPixel(x, y);
This also didn't work and the results came back as zeros.
What could be the reason behind this and how to fix it?
In your image all color channels of all pixels are 0, so you code actually works fine.
And most of the pixels also have an alpha of 0, so they are transparent. Some are semi-transparent, anti-aliased pixels and a few are fully opaque.
Where transparent or semi-transprent pixels are, the backcolor will shine through.
If you want to recognize the really, non-transparent black pixels you need to test the alpha channel as well.
Here is a function that will create a composite color of a pixel's color and a given background color..:
Color Composite(Color color, Color backcolor)
{
byte r = (byte)(((255 - color.A) * backcolor.R + color.R * color.A) / 256);
byte g = (byte)(((255 - color.A) * backcolor.G + color.G * color.A) / 256);
byte b = (byte)(((255 - color.A) * backcolor.B + color.B * color.A) / 256);
Color c2 = Color.FromArgb(255, r, g, b);
return c2;
}
If you want the brightness of one of your pixels you could create a composite of its color with white. But, given the image you have, you could just as well use color.A directly..

Unsafe C# bitmap floodfill

**
How to make 'GetPixel2' work for finding the color at a point
**
So I have a bitmap with lots of single colored shapes.
I have a list of x,y points for those shapes. Then, a second list
with the expected color at those points.
Finally have an algorithm using bitmap.Getpixel and SetPixel working.
Which was definitely slow.
http://csharpexamples.com/fast-image-processing-c/
Suggests using direct memory access to solve this. I'd like to use their sample without looping through the entire image, and hit a single x,y point.
Bitmap bmp2 = (Bitmap)Bitmap.FromFile(Environment.CurrentDirectory + #"\Content\map\provinces.bmp");
BitmapData bitmapData = bmp2.LockBits(new System.Drawing.Rectangle(0, 0, bmp2.Width, bmp2.Height), ImageLockMode.ReadWrite, bmp2.PixelFormat);
int bytesPerPixel = System.Drawing.Bitmap.GetPixelFormatSize(bmp2.PixelFormat) / 8;
int heightInPixels = bitmapData.Height;
int widthInBytes = bitmapData.Width * bytesPerPixel;
System.Drawing.Point pt = new System.Drawing.Point((int)provpos2[0].X, (int)provpos2[0].Y);
System.Drawing.Color targetColor = System.Drawing.Color.FromArgb(255, provcolors[0].R, provcolors[0].G, provcolors[0].B);
if (!ColorMatch(GetPixel2(pt.X, pt.Y, bytesPerPixel, bitmapData), targetColor)){
// This hits the completely wrong area.
}
public System.Drawing.Color GetPixel2(int x, int y, int bytesPerPixel, BitmapData bitmapData)
{
unsafe
{
byte* ptrFirstPixel = (byte*)bitmapData.Scan0;
byte* currentLine = ptrFirstPixel + (y * bitmapData.Stride);
x = x + bytesPerPixel;
System.Drawing.Color a = System.Drawing.Color.FromArgb(255, currentLine[x + 2], currentLine[x + 1], currentLine[x]);
return a;
}
}
public static bool ColorMatch(System.Drawing.Color a,System.Drawing.Color b)
{
return (a.ToArgb() & 0xffffff) == (b.ToArgb() & 0xffffff);
}
bytesPerPixel comes out at 3. Tried changing it to 4 just hits another undesired location on the bitmap.
It seems to hit around 1023x,351y instead of the desired 3084x,319y on a 5632x2048 bitmap.
Not entirely sure why it doesnt workout fo you, but keep in mind this:
Bits per pixel comes from colour format used there are a few formats some are handier then others, and sometimes you need to convert them to a strict RGB format. ea 8 bits per colour channel, there also exists RGBA, and there is RGB in bitwise 565 notation as used in some camera's, and there is 24bits per colour. Some formats are not supported in winforms, but are supported in wpf based applications, like 16bit gray formats. (since wpf is more new age like design friendly)
maybe try this it works great for me:
http://www.codeproject.com/Tips/240428/Work-with-bitmap-faster-with-Csharp?msg=5136670
if its 565 maybe do something like
private Bitmap Convert565bppTo24bpp(Bitmap ConvertMe)
{
Bitmap clone = new Bitmap(ConvertMe.Width, ConvertMe.Height, System.Drawing.Imaging.PixelFormat.Format24bppRgb);//.Format32bppPArgb);
using (Graphics gr = Graphics.FromImage(clone))
{ gr.DrawImage(ConvertMe, new Rectangle(0, 0, clone.Width, clone.Height)); }
return clone;
}

How to determine the background color of document when there are 3 options, using c# or imagemagick

i am currently developing an application that has to process scanned forms. One of the tasks of my application is to determine which kind of form is scanned. There are 3 possible types of forms with a unique background color to identify each kind. The 3 colors that are possible are red/pink, green and blue. The problem i am having is, that my attempts fail to distinguish between the green and blue forms.
Here are links to the green and blue sample files:
http://dl.dropbox.com/u/686228/Image0037.JPG
http://dl.dropbox.com/u/686228/Image0038.JPG
I am using C# .net Application and ImageMagick for some tasks i need to perform.
Currently i am getting color reduced histogram of my scanned form and try to determine which colors are in the form. But my app can't rely distinguish the green and blue ones.
Any advise or maybe a smarter approach would be gladly appreciated.
Thanks,
Erik
I found this rather interesting and dug into it a little deeper.
The code to get the average color of a bitmap found at How to calculate the average rgb color values of a bitmap had problems like some invalid casts and red/blue channels swapped. Here is a fixed version:
private System.Drawing.Color CalculateAverageColor(Bitmap bm)
{
int width = bm.Width;
int height = bm.Height;
int red = 0;
int green = 0;
int blue = 0;
int minDiversion = 15; // drop pixels that do not differ by at least minDiversion between color values (white, gray or black)
int dropped = 0; // keep track of dropped pixels
long[] totals = new long[] { 0, 0, 0 };
int bppModifier = bm.PixelFormat == System.Drawing.Imaging.PixelFormat.Format24bppRgb ? 3 : 4; // cutting corners, will fail on anything else but 32 and 24 bit images
BitmapData srcData = bm.LockBits(new System.Drawing.Rectangle(0, 0, bm.Width, bm.Height), ImageLockMode.ReadOnly, bm.PixelFormat);
int stride = srcData.Stride;
IntPtr Scan0 = srcData.Scan0;
unsafe
{
byte* p = (byte*)(void*)Scan0;
for (int y = 0; y < height; y++)
{
for (int x = 0; x < width; x++)
{
int idx = (y * stride) + x * bppModifier;
red = p[idx + 2];
green = p[idx + 1];
blue = p[idx];
if (Math.Abs(red - green) > minDiversion || Math.Abs(red - blue) > minDiversion || Math.Abs(green - blue) > minDiversion)
{
totals[2] += red;
totals[1] += green;
totals[0] += blue;
}
else
{
dropped++;
}
}
}
}
int count = width * height - dropped;
int avgR = (int)(totals[2] / count);
int avgG = (int)(totals[1] / count);
int avgB = (int)(totals[0] / count);
return System.Drawing.Color.FromArgb(avgR, avgG, avgB);
}
Running this function on your input images, however, returned some indistinguishable grayish color for both of them, as already anticipated by Will A in the comments, which is why i'm dropping any colors from the calculation that do not have a difference of at least 15 between R, G and B.
The interesting thing is that the supposedly blue prescription scan averages equal values for G and B (R: 214, G: 237, B: 237). However the green prescription scan resulted in a big difference (18) between the values for G and B (R: 202, G: 232, B: 214) so that might be what you should be looking into. Ex:
if (color.G - color.B > 15) { form.Type = FormTypes.GreenForm }
Workflow outline:
Convert Image into HSL/HSV colorspace
Build histogram of the H channel
The histogram should show a clear peak (for your samples at least) in the blue/green channel
If that's not distinctive enough, you can weight the histogram votes by something to reduce the effect of the white areas (e.g. in the HSV colorspace, weight by S).
I haven't tried this out, but how about resizing the image to 1x1 pixel (which should "average" out all the pixels) and then check the hue of that pixel to see if it closest to red, blue or green.
EDIT
I don't have ImageMagik installed, so I hacked this with GetThumbnailImage:
private static bool ThumbnailCallback()
{
return false;
}
static void Main(string[] args)
{
var blueImage = Image.FromFile("blue.jpg").GetThumbnailImage(1, 1, new Image.GetThumbnailImageAbort(ThumbnailCallback), IntPtr.Zero);
var blueBitmap = new Bitmap(blueImage);
var blueHue = blueBitmap.GetPixel(0, 0).GetHue();
var greenImage = Image.FromFile("green.jpg").GetThumbnailImage(1, 1, new Image.GetThumbnailImageAbort(ThumbnailCallback), IntPtr.Zero);
var greenBitmap = new Bitmap(greenImage);
var greenHue = greenBitmap.GetPixel(0, 0).GetHue();
}
Using your images I got a blueHue value of 169.0909 (Where true blue would be 180), and greenHue equal to 140 (pure green is 120, cyan is 150).
Red forms should be somewhere near 0 or 360.
I know you've already found an answer - just thought I'd give you an alternative.

Multiply two images in C# as multiply two layers in Photoshop

I have two images and I want to multiply these two images together in C# as we multiply two layers in Photoshop.
I have found the method by which the layers are multiplied in photoshop or any other application.
Following is the formula that I have found on GIMP documentation. It says that
E=(M*I)/255
where M and I are the color component(R,G,B) values of the two layers. We have to apply this to every color component. E will be the resultant value for that color component.
If the color component values are >255 then it should be set to white i.e. 255 and if it is <0 then it should be set as Black i.e. 0
Here I have a suggestion - I didn't test it, so sorry for any errors - I'm also assuming that both images have the same size and are greylevel.
Basically I'm multiplying the image A for the relative pixel percentage of image B.
You can try different formulas like:
int result = ptrB[0] * ( (ptrA[0] / 255) + 1);
or
int result = (ptrB[0] * ptrA[0]) / 255;
Never forget to check for overflow (above 255)
public void Multiply(Bitmap srcA, Bitmap srcB, Rectangle roi)
{
BitmapData dataA = SetImageToProcess(srcA, roi);
BitmapData dataB = SetImageToProcess(srcB, roi);
int width = dataA.Width;
int height = dataA.Height;
int offset = dataA.Stride - width;
unsafe
{
byte* ptrA = (byte*)dataA.Scan0;
byte* ptrB = (byte*)dataB.Scan0;
for (int y = 0; y < height; ++y)
{
for (int x = 0; x < width; ++x, ++ptrA, ++ptrB)
{
int result = ptrA[0] * ( (ptrB[0] / 255) + 1);
ptrA[0] = result > 255 ? 255 : (byte)result;
}
ptrA += offset;
ptrB += offset;
}
}
srcA.UnlockBits(dataA);
srcB.UnlockBits(dataB);
}
static public BitmapData SetImageToProcess(Bitmap image, Rectangle roi)
{
if (image != null)
return image.LockBits(
roi,
ImageLockMode.ReadWrite,
image.PixelFormat);
return null;
}

Adjust the contrast of an image in C# efficiently

Is there an efficient way of adjusting the contrast of an image in C#?
I've seen this article which advocates doing a per-pixel operation. Not quick.
I'm using colour matrices in places already and find them to be quick. Is there a way to adjust contrast using them? (Note: This guy gets it wrong.)
I'm also using EmguCV. I notice that OpenCV (which Emgu wraps) seems to have a contrast function - is there any way of accessing this through Emgu? At the moment all I can do in Emgu is normalise the histogram, which does change the contrast, but not with any degree of control on my part.
Anyone got any ideas?
If the code in that sample works for you, you can speed it up massively (by orders of magnitude) by using Bitmap.LockBits, which returns a BitmapData object that allows access to the Bitmap's pixel data via pointers. There are numerous samples on the web and on StackOverflow that show how to use LockBits.
Bitmap.SetPixel() and Bitmap.GetPixel() are the slowest methods known to mankind, and they both utilize the Color class, which is the slowest class known to mankind. They should have been named Bitmap.GetPixelAndByGodYoullBeSorryYouDid() and Bitmap.SetPixelWhileGettingCoffee as a warning to unwary developers.
Update: If you're going to modify the code in that sample, note that this chunk:
System.Drawing.Bitmap TempBitmap = Image;
System.Drawing.Bitmap NewBitmap = new System.Drawing.Bitmap(TempBitmap.Width,
TempBitmap.Height);
System.Drawing.Graphics NewGraphics =
System.Drawing.Graphics.FromImage(NewBitmap);
NewGraphics.DrawImage(TempBitmap, new System.Drawing.Rectangle(0, 0,
TempBitmap.Width, TempBitmap.Height),
new System.Drawing.Rectangle(0, 0, TempBitmap.Width, TempBitmap.Height),
System.Drawing.GraphicsUnit.Pixel);
NewGraphics.Dispose();
can be replaced with this:
Bitmap NewBitmap = (Bitmap)Image.Clone();
Update 2: Here is the LockBits version of the AdjustContrast method (with a few other speed improvements):
public static Bitmap AdjustContrast(Bitmap Image, float Value)
{
Value = (100.0f + Value) / 100.0f;
Value *= Value;
Bitmap NewBitmap = (Bitmap)Image.Clone();
BitmapData data = NewBitmap.LockBits(
new Rectangle(0, 0, NewBitmap.Width, NewBitmap.Height),
ImageLockMode.ReadWrite,
NewBitmap.PixelFormat);
int Height = NewBitmap.Height;
int Width = NewBitmap.Width;
unsafe
{
for (int y = 0; y < Height; ++y)
{
byte* row = (byte*)data.Scan0 + (y * data.Stride);
int columnOffset = 0;
for (int x = 0; x < Width; ++x)
{
byte B = row[columnOffset];
byte G = row[columnOffset + 1];
byte R = row[columnOffset + 2];
float Red = R / 255.0f;
float Green = G / 255.0f;
float Blue = B / 255.0f;
Red = (((Red - 0.5f) * Value) + 0.5f) * 255.0f;
Green = (((Green - 0.5f) * Value) + 0.5f) * 255.0f;
Blue = (((Blue - 0.5f) * Value) + 0.5f) * 255.0f;
int iR = (int)Red;
iR = iR > 255 ? 255 : iR;
iR = iR < 0 ? 0 : iR;
int iG = (int)Green;
iG = iG > 255 ? 255 : iG;
iG = iG < 0 ? 0 : iG;
int iB = (int)Blue;
iB = iB > 255 ? 255 : iB;
iB = iB < 0 ? 0 : iB;
row[columnOffset] = (byte)iB;
row[columnOffset + 1] = (byte)iG;
row[columnOffset + 2] = (byte)iR;
columnOffset += 4;
}
}
}
NewBitmap.UnlockBits(data);
return NewBitmap;
}
NOTE: this code requires using System.Drawing.Imaging; in your class' using statements, and it requires that the project's allow unsafe code option be checked (on the Build Properties tab for the project).
One of the reasons GetPixel and SetPixel are so slow for pixel-by-pixel operations is that the overhead of the method call itself starts to become a huge factor. Normally, my code sample here would be considered a candidate for refactoring, since you could write your own SetPixel and GetPixel methods that use an existing BitmapData object, but the processing time for the math inside the functions would be very small relative to the method overhead of each call. This is why I removed the Clamp calls in the original method as well.
One other way to speed this up would be to simply make it a "destructive" function, and modify the passed Bitmap parameter instead of making a copy and returning the modified copy.
#MusiGenesis,
Just wanted to note that I used this method for an image editor I've been writing. It works well, but sometimes this method triggers an AccessViolationException on this line:
byte B = row[columnOffset];
I realised it was because there was no standardisation of BitDepth, so if an image was 32 bit colour I was getting this error. So I changed this line:
BitmapData data = NewBitmap.LockBits(new Rectangle(0, 0, NewBitmap.Width, NewBitmap.Height), ImageLockMode.ReadWrite, NewBitmap.PixelFormat);
to:
BitmapData data = NewBitmap.LockBits(new Rectangle(0, 0, NewBitmap.Width, NewBitmap.Height), ImageLockMode.ReadWrite, PixelFormat.Format32bppRgb);
Hope this helps as it seems to have eradicated my problem.
Thanks for the post.
Jib
I'm a bit late, but use a color matrix implementation as these will be optimised for such transformations and is much easier than manipulating the pixels yourself: http://www.geekpedia.com/tutorial202_Using-the-ColorMatrix-in-Csharp.html

Categories