Related
I need to load an image with green circle over a transparent background into a bitmap image using c# (System.Drawings).
That's the easy part. However I need to change the color of the circle before adding it to the bigger image, without affecting the transparency of the surrounding. In my case I need to change the circle color to yellow and add it as a sun.
I can't use fixed yellow circle image because the desired color is dynamic.
So in the code below, how can I change the color of the image before adding it to the bitmap?
Image i = Image.FromFile(greenCircleFile);
Bitmap b = new Bitmap(500, 500);
using(Graphics g = Graphics.FromImage(b))
{
//--> Here I need to change the color of the green circle to yellow
//afterwards I can add it to the bitmap image
g.DrawImage(i, 0, 0, 500, 500);
}
Please note that two things need to be into consideration: Keeping the anti-aliasing of the shape (circle), and the color needs to be picked by user and used as is to overlay the original color of the circle.
Fixed:
Thanks to #TaW, he provided the correct answer. However with a glitch, here's the final version that worked for me:
Image i = Image.FromFile(greenCircleFile);
Bitmap b = new Bitmap(500, 500);
using(Graphics g = Graphics.FromImage(b))
{
//Here I need to change the color of the green circle to yellow
i = ChangeToColor(b, Color.Gold)
//afterwards I can add it to the bitmap image
g.DrawImage(i, 0, 0, 500, 500);
}
While ChangeToColor function is as follows:
Bitmap ChangeToColor(Bitmap bmp, Color c)
{
Bitmap bmp2 = new Bitmap(bmp.Width, bmp.Height);
using (Graphics g = Graphics.FromImage(bmp2))
{
float tr = c.R / 255f;
float tg = c.G / 255f;
float tb = c.B / 255f;
ColorMatrix colorMatrix = new ColorMatrix(new float[][]
{
new float[] {0, 0, 0, 0, 0},
new float[] {0, 0, 0, 0, 0},
new float[] {0, 0, 0, 0, 0},
new float[] {0, 0, 0, 1, 0},
new float[] {tr, tg, tb, 0, 1}
});
ImageAttributes attributes = new ImageAttributes();
attributes.SetColorMatrix(colorMatrix);
g.DrawImage(bmp, new Rectangle(0, 0, bmp.Width, bmp.Height),
0, 0, bmp.Width, bmp.Height, GraphicsUnit.Pixel, attributes);
}
return bmp2;
}
This will create a new Bitmap with all non-transparent pixels moved strongly toward a new color:
Bitmap ChangeToColor(Bitmap bmp, Color c)
{
Bitmap bmp2 = new Bitmap(bmp.Width, bmp.Height);
using (Graphics g = Graphics.FromImage(bmp2))
{
float tr = c.R / 255f;
float tg = c.G / 255f;
float tb = c.B / 255f;
ColorMatrix colorMatrix = new ColorMatrix(new float[][]
{
new float[] {0, 0, 0, 0, 0},
new float[] {0, 0, 0, 0, 0},
new float[] {0, 0, 0, 0, 0},
new float[] {0, 0, 0, 1, 0},
new float[] {tr, tg, tb, 0, 1} // kudos to OP!
});
ImageAttributes attributes = new ImageAttributes();
attributes.SetColorMatrix(colorMatrix);
g.DrawImage(bmp, new Rectangle(0, 0, bmp.Width, bmp.Height),
0, 0, bmp.Width, bmp.Height, GraphicsUnit.Pixel, attributes);
}
return bmp2;
}
do make sure not to leak the Bitmaps you create!
Note that there are other methods as well. Here is a link to a method that uses ColorMapping. This allows for a range of colors to be replaced by another range, so it can keep gradients like the ones you get in anti-alised graphics..
Here's my solution you just need to create a new Control
then inherit the Picturebox check this out.
public partial class UICirclePicture : PictureBox
{
[Browsable(false)]
public int Depth { get; set; }
[Browsable(false)]
public SprikiwikiUI Ui
{
get { return SprikiwikiUI.Instance; }
}
[Browsable(false)]
public MouseState MouseState { get; set; }
public UICirclePicture()
{
BackColor = Ui.GetApplicationBackgroundColor();
SizeMode = PictureBoxSizeMode.StretchImage;
}
protected override void OnResize(EventArgs e)
{
base.OnResize(e);
using (var gp = new GraphicsPath())
{
gp.AddEllipse(new Rectangle(0, 0, this.Width - 1, this.Height - 1));
this.Region = new Region(gp);
}
}
}
This question already has answers here:
Invert image faster in C#
(3 answers)
Closed 7 years ago.
I'm using Winforms. In my form I have a picturebox where I load tiff documents. I use the code below to invert the colors back and forth in the document on button click. The problem with my code is, it is very slow. How do I convert the colors of the images faster?
Image info:
Image Size: 8.5 x 11 inch
Pixel Density: 300 x 300 pixel/inch
Pixel Dimensions - 2550 x 3300 pixels (usually because I open different documents to view)
private void button1_Click(object sender, EventArgs e)
{
Bitmap pic = new Bitmap(pictureBox1.Image);
for (int y = 0; (y <= (pic.Height - 1)); y++)
{
for (int x = 0; (x <= (pic.Width - 1)); x++)
{
Color inv = pic.GetPixel(x, y);
inv = Color.FromArgb(255, (255 - inv.R), (255 - inv.G), (255 - inv.B));
pic.SetPixel(x, y, inv);
}
}
pictureBox1.Image = pic;
}
GetPixel and SetPixel are really slow due too many reasons. Also it is often a bad idea to nest loops. Take a look at the following link for some faster implementation (not optimal but closer)
http://www.codeproject.com/Articles/1989/Image-Processing-for-Dummies-with-C-and-GDI-Part
It involves using unsafe code and the LockPixel method of Images.
Code from the link (You need to allow unsafe code in the project settings)
public static bool Invert(Bitmap b)
{
// GDI+ still lies to us - the return format is BGR, NOT RGB.
BitmapData bmData = b.LockBits(new Rectangle(0, 0, b.Width, b.Height),
ImageLockMode.ReadWrite, PixelFormat.Format24bppRgb);
int stride = bmData.Stride;
System.IntPtr Scan0 = bmData.Scan0;
unsafe
{
byte * p = (byte *)(void *)Scan0;
int nOffset = stride - b.Width*3;
int nWidth = b.Width * 3;
for(int y=0;y < b.Height;++y)
{
for(int x=0; x < nWidth; ++x )
{
p[0] = (byte)(255-p[0]);
++p;
}
p += nOffset;
}
}
b.UnlockBits(bmData);
return true;
}
If this code is not working since you have different image formats then you need to modify some parts, in terms of the size of a pixel. (its constant 3 in the example)
You can get this by calculation it the following way:
int pixelSize = imageData.Stride/image.Width;
and replace
PixelFormat.Format24bppRgb
with
b.PixelFormat
Take a look at this question:
Inverting image returns a black image
Dan uses a color matrix with image attributes in his solution.
Here is how you would use it (untested):
Declare your Color Matrix so you don't have to keep redefining it every time:
System.Drawing.Imaging.ColorMatrix m_colorMatrix;
private void Init()
{
// create the negative color matrix
m_colorMatrix = new System.Drawing.Imaging.ColorMatrix(new float[][] {
new float[] {-1, 0, 0, 0, 0},
new float[] {0, -1, 0, 0, 0},
new float[] {0, 0, -1, 0, 0},
new float[] {0, 0, 0, 1, 0},
new float[] {1, 1, 1, 0, 1}
});
}
Now, in your button Click event, supply your Picture Box control:
private void Button_Click(object sender, EventArgs e) {
var source = new Bitmap(pictureBox1.Image);
//create a blank bitmap the same size as original
var bmpInvert = new Bitmap(source.Width, source.Height);
//get a graphics object from the new image
using (var g = Graphics.FromImage(bmpInvert))
{
// create some image attributes
var attributes = new System.Drawing.Imaging.ImageAttributes();
attributes.SetColorMatrix(m_colorMatrix);
g.DrawImage(source, new Rectangle(0, 0, source.Width, source.Height),
0, 0, source.Width, source.Height, GraphicsUnit.Pixel, attributes);
pictureBox1.Image = bmpInvert;
}
}
Since I have included the Graphics object is in a using block, you should not call the Dispose method like he does.
That may get you what you need, but I don't have anything to test it with.
I am trying to make to put adjust brightness thing but I am getting does not exist in current context error for "NewBitmap" in this code
picBox.Image = AdjustBrightness(NewBitmap, trackBar1.Value);
Here is my code
private void trackBar1_Scroll(object sender, EventArgs e)
{
lblBrightNum.Text = trackBar1.Value.ToString();
picBox.Image = AdjustBrightness(NewBitmap, trackBar1.Value);
}
public static Bitmap AdjustBrightness(Bitmap Image, int Value)
{
Bitmap TempBitmap = (Bitmap)Image.Clone();
float FinalValue = (float)Value / 255.0f;
Bitmap NewBitmap = new Bitmap(TempBitmap.Width, TempBitmap.Height);
Graphics NewGraphics = Graphics.FromImage(NewBitmap);
float[][] FloatColorMatrix ={
new float[] {1, 0, 0, 0, 0},
new float[] {0, 1, 0, 0, 0},
new float[] {0, 0, 1, 0, 0},
new float[] {0, 0, 0, 1, 0},
new float[] {FinalValue, FinalValue, FinalValue, 1, 1}
};
ColorMatrix NewColorMatrix = new ColorMatrix(FloatColorMatrix);
ImageAttributes Attributes = new ImageAttributes();
Attributes.SetColorMatrix(NewColorMatrix);
NewGraphics.DrawImage(TempBitmap, new Rectangle(0, 0, TempBitmap.Width, TempBitmap.Height), 0, 0, TempBitmap.Width, TempBitmap.Height, GraphicsUnit.Pixel, Attributes);
Attributes.Dispose();
NewGraphics.Dispose();
return NewBitmap;
}
Your program mainly contains two methods, one is trackBar1_Scroll, another one is AdjustBrightness, visual studio knows what is "NewBitmap" in the AdjustBrightness method, but it doesn't know what is "NewBitmap" in the trackBar1_Scroll
private void trackBar1_Scroll(object sender, EventArgs e)
{
lblBrightNum.Text = trackBar1.Value.ToString();
//Visual studio is complaining about this, you haven't define "NewBitmap", you can fix by adding below:
Bitmap NewBitmap = //your bitmap
picBox.Image = AdjustBrightness(NewBitmap, trackBar1.Value);
}
public static Bitmap AdjustBrightness(Bitmap Image, int Value)
{
Bitmap TempBitmap = (Bitmap)Image.Clone();
float FinalValue = (float)Value / 255.0f;
Bitmap NewBitmap = new Bitmap(TempBitmap.Width, TempBitmap.Height);
Graphics NewGraphics = Graphics.FromImage(NewBitmap);
float[][] FloatColorMatrix ={
new float[] {1, 0, 0, 0, 0},
new float[] {0, 1, 0, 0, 0},
new float[] {0, 0, 1, 0, 0},
new float[] {0, 0, 0, 1, 0},
new float[] {FinalValue, FinalValue, FinalValue, 1, 1}
};
ColorMatrix NewColorMatrix = new ColorMatrix(FloatColorMatrix);
ImageAttributes Attributes = new ImageAttributes();
Attributes.SetColorMatrix(NewColorMatrix);
NewGraphics.DrawImage(TempBitmap, new Rectangle(0, 0, TempBitmap.Width, TempBitmap.Height), 0, 0, TempBitmap.Width, TempBitmap.Height, GraphicsUnit.Pixel, Attributes);
Attributes.Dispose();
NewGraphics.Dispose();
return NewBitmap;
I want my webBrowser to look transparent. My code looks like that:
Bitmap backGroungImage = new Bitmap(Width, Height);
myWebBrowser1.Visible = false;
DrawToBitmap(backGroungImage, new Rectangle(0, 0, backGroungImage.Width, backGroungImage.Height));
myWebBrowser1.Visible = true;
Bitmap backGroundImage2 = new Bitmap(myWebBrowser1.Width - 20, myWebBrowser1.Height - 20);
Graphics.FromImage(backGroundImage2).DrawImage(backGroungImage, new Rectangle(0, 0, backGroundImage2.Width, backGroundImage2.Height), new Rectangle(SystemInformation.FrameBorderSize.Width + myWebBrowser1.Location.X + 10, SystemInformation.CaptionHeight + SystemInformation.FrameBorderSize.Height + 10 + myWebBrowser1.Location.Y, myWebBrowser1.Width - 20, myWebBrowser1.Height - 20), GraphicsUnit.Pixel);
ImageAttributes imageAttributes = new ImageAttributes();
ColorMatrix colorMatrix = new ColorMatrix(new float[][]
{
new float[] {1, 0, 0, 0, 0},
new float[] {0, 1, 0, 0, 0},
new float[] {0, 0, 1, 0, 0},
new float[] {0, 0, 0, 1f - 230f/255f, 0},
new float[] {0, 0, 0, 0, 1}
});
imageAttributes.SetColorMatrix(colorMatrix, ColorMatrixFlag.Default, ColorAdjustType.Bitmap);
Bitmap backGroundImage3 = new Bitmap(backGroundImage2.Width, backGroundImage2.Height);
Graphics.FromImage(backGroundImage3).DrawImage(backGroundImage2, new Rectangle(0, 0, backGroundImage3.Width, backGroundImage3.Height), 0, 0, backGroundImage2.Width, backGroundImage2.Height, GraphicsUnit.Pixel, imageAttributes);
backGroundImage3.Save("d:\\BG.png", ImageFormat.Png);
and then I use "d:\BG.png" in html code.
It works fine on my windows 7, but on windows XP it's darker, doesn't fit other white elements drawn in GDI+ with alpha = 230 (I mean round frame around the WebBrowser control).
Before, when I didn't use ColorMatrix, I did that:
int alpha = 230;
Color c1, color = Color.FromArgb(255, 255, 255);
for (int x = 0; x < d.Width; x++)
for (int y = 0; y < d.Height; y++)
{
c1 = d.GetPixel(x, y);
d.SetPixel(x, y, Color.FromArgb(((255 - alpha) * c1.R + alpha * color.R) / 255, ((255 - alpha) * c1.G + alpha * color.G) / 255, ((255 - alpha) * c1.B + alpha * color.B) / 255));
}
And looked great on both OSes, but was very slow.
I want it to look the same as white color with alpha 230 in GDI+ and to be fast.
BTW. is there a way to put in WebBrowser a background image and then make it transparent?
PS. it's Visual Studio 2010 Professional and .NET Framework 4 installed from the same file on both computers.
I updated my IE browser to version 8, and works perfect.
How do I achieve this kind of color replacement programmatically?
So this is the function I have used to replace a pixel:
Color.FromArgb(
oldColorInThisPixel.R + (byte)((1 - oldColorInThisPixel.R / 255.0) * colorToReplaceWith.R),
oldColorInThisPixel.G + (byte)((1 - oldColorInThisPixel.G / 255.0) * colorToReplaceWith.G),
oldColorInThisPixel.B + (byte)((1 - oldColorInThisPixel.B / 255.0) * colorToReplaceWith.B)
)
Thank you, CodeInChaos!
The formula for calculating the new pixel is:
newColor.R = OldColor;
newColor.G = OldColor;
newColor.B = 255;
Generalizing to arbitrary colors:
I assume you want to map white to white and black to that color. So the formula is newColor = TargetColor + (White - TargetColor) * Input
newColor.R = OldColor + (1 - oldColor / 255.0) * TargetColor.R;
newColor.G = OldColor + (1 - oldColor / 255.0) * TargetColor.G;
newColor.B = OldColor + (1 - oldColor / 255.0) * TargetColor.B;
And then just iterate over the pixels of the image(byte array) and write them to a new RGB array. There are many threads on how to copy an image into a byte array and manipulate it.
Easiest would be to use ColorMatrix for processing images, you will even be able to process on fly preview of desired effect - this is how many color filters are made in graphic editing applications. Here and here you can find introductions to color effects using Colormatrix in C#. By using ColorMatrix you can make colorizing filter like you want, as well as sepia, black/white, invert, range, luminosity, contrast, brightness, levels (by multi-pass) etc.
EDIT: Here is example (update - fixed color matrix to shift darker values into blue instead of previous zeroing other than blue parts - and - added 0.5f to blue because on picture above black is changed into 50% blue):
var cm = new ColorMatrix(new float[][]
{
new float[] {1, 0, 0, 0, 0},
new float[] {0, 1, 1, 0, 0},
new float[] {0, 0, 1, 0, 0},
new float[] {0, 0, 0, 1, 0},
new float[] {0, 0, 0.5f, 0, 1}
});
var img = Image.FromFile("C:\\img.png");
var ia = new ImageAttributes();
ia.SetColorMatrix(cm);
var bmp = new Bitmap(img.Width, img.Height);
var gfx = Graphics.FromImage(bmp);
var rect = new Rectangle(0, 0, img.Width, img.Height);
gfx.DrawImage(img, rect, 0, 0, img.Width, img.Height, GraphicsUnit.Pixel, ia);
bmp.Save("C:\\processed.png", ImageFormat.Png);
You'll want to use a ColorMatrix here. The source image is grayscale, all its R, G and B values are equal. Then it is just a matter of replacing black with RGB = (0, 0, 255) for dark blue, white with RGB = (255, 255, 255) to get white. The matrix thus can look like this:
1 0 0 0 0 // not changing red
0 1 0 0 0 // not changing green
0 0 0 0 0 // B = 0
0 0 0 1 0 // not changing alpha
0 0 1 0 1 // B = 255
This sample form reproduces the right side image:
public partial class Form1 : Form {
public Form1() {
InitializeComponent();
}
private Image mImage;
protected override void OnPaint(PaintEventArgs e) {
if (mImage != null) e.Graphics.DrawImage(mImage, Point.Empty);
base.OnPaint(e);
}
private void button1_Click(object sender, EventArgs e) {
using (var srce = Image.FromFile(#"c:\temp\grayscale.png")) {
if (mImage != null) mImage.Dispose();
mImage = new Bitmap(srce.Width, srce.Height);
float[][] coeff = {
new float[] { 1, 0, 0, 0, 0 },
new float[] { 0, 1, 0, 0, 0 },
new float[] { 0, 0, 0, 0, 0 },
new float[] { 0, 0, 0, 1, 0 },
new float[] { 0, 0, 1, 0, 1 }};
ColorMatrix cm = new ColorMatrix(coeff);
var ia = new ImageAttributes();
ia.SetColorMatrix(new ColorMatrix(coeff));
using (var gr = Graphics.FromImage(mImage)) {
gr.DrawImage(srce, new Rectangle(0, 0, mImage.Width, mImage.Height),
0, 0, mImage.Width, mImage.Height, GraphicsUnit.Pixel, ia);
}
}
this.Invalidate();
}
}
Depends a lot on what your image format is and what your final format is going to be.
Also depends on what tool you wanna use.
You may use:
GDI
GD+
Image Processing library such as OpenCV
GDI is quite fast but can be quite cumbersome. You need to change the palette.
GDI+ is exposed in .NET and can be slower but easier.
OpenCV is great but adds dependency.
(UPDATE)
This code changes the image to blue-scales instead of grey-scales - image format is 32 bit ARGB:
private static unsafe void ChangeColors(string imageFileName)
{
const int noOfChannels = 4;
Bitmap img = (Bitmap) Image.FromFile(imageFileName);
BitmapData data = img.LockBits(new Rectangle(0,0,img.Width, img.Height), ImageLockMode.ReadWrite, img.PixelFormat);
byte* ptr = (byte*) data.Scan0;
for (int j = 0; j < data.Height; j++)
{
byte* scanPtr = ptr + (j * data.Stride);
for (int i = 0; i < data.Stride; i++, scanPtr++)
{
if (i % noOfChannels == 3)
{
*scanPtr = 255;
continue;
}
if (i % noOfChannels != 0)
{
*scanPtr = 0;
}
}
}
img.UnlockBits(data);
img.Save(Path.Combine( Path.GetDirectoryName(imageFileName), "result.png"), ImageFormat.Png);
}
This code project article covers this and more: http://www.codeproject.com/KB/GDI-plus/Image_Processing_Lab.aspx
It uses the AForge.NET library to do a Hue filter on an image for a similar effect:
// create filter
AForge.Imaging.Filters.HSLFiltering filter =
new AForge.Imaging.Filters.HSLFiltering( );
filter.Hue = new IntRange( 340, 20 );
filter.UpdateHue = false;
filter.UpdateLuminance = false;
// apply the filter
System.Drawing.Bitmap newImage = filter.Apply( image );
It also depends on what you want: do you want to keep the original and only adjust the way it is shown? An effect or pixelshader in WPF might do the trick and be very fast.
If any Android devs end up looking at this, this is what I came up with to gray scale and tint an image using CodesInChaos's formula and the android graphics classes ColorMatrix and ColorMatrixColorFilter.
Thanks for the help!
public static ColorFilter getColorFilter(Context context) {
final int tint = ContextCompat.getColor(context, R.color.tint);
final float R = Color.red(tint);
final float G = Color.green(tint);
final float B = Color.blue(tint);
final float Rs = R / 255;
final float Gs = G / 255;
final float Bs = B / 255;
// resultColor = oldColor + (1 - oldColor/255) * tintColor
final float[] colorTransform = {
1, -Rs, 0, 0, R,
1, -Gs, 0, 0, G,
1, -Bs, 0, 0, B,
0, 0, 0, 0.9f, 0};
final ColorMatrix grayMatrix = new ColorMatrix();
grayMatrix.setSaturation(0f);
grayMatrix.postConcat(new ColorMatrix(colorTransform));
return new ColorMatrixColorFilter(grayMatrix);
}
The ColorFilter can then be applied to an ImageView
imageView.setColorFilter(getColorFilter(imageView.getContext()));