I want to apply the opacity of a linear gradient to an image, but all I have been able to get is the gradient painted on top of the image.
Following this stackoverflow post, I created a user control that inherits from PictureBox and I overrode the OnPaint method
protected override void OnPaint(PaintEventArgs e)
{
base.OnPaint(e);
LinearGradientBrush linearGradientBrush = new LinearGradientBrush(
this.ClientRectangle,
Color.FromArgb(255, Color.White),
Color.FromArgb(0, Color.White),
0f);
e.Graphics.FillRectangle(linearGradientBrush, this.ClientRectangle);
}
However, that simply paints the linear gradient on top of the image.
How do I apply the opacity of the linear gradient to the image?
I find examples in XAML (), but not for winforms.
It is not necessary to use a user control and override the OnPaint event. Just create a graphics object from a blank image and do your image manipulation and assign the image to a PicturePox.
First draw the linearGradientBrush to the background and afterwards draw the image over it. Always take care about the sequence of your image operations.
FileInfo inputImageFile = new FileInfo(#"C:\Temp\TheSimpsons.png");
Bitmap inputImage = (Bitmap)Bitmap.FromFile(inputImageFile.FullName);
// create blank bitmap with same size
Bitmap combinedImage = new Bitmap(inputImage.Width, inputImage.Height);
// create graphics object on new blank bitmap
Graphics g = Graphics.FromImage(combinedImage);
// also use the same size for the gradient brush and for the FillRectangle function
LinearGradientBrush linearGradientBrush = new LinearGradientBrush(
new Rectangle(0,0,combinedImage.Width, combinedImage.Height),
Color.FromArgb(255, Color.White), //Color.White,
Color.FromArgb(0, Color.White), // Color.Transparent,
0f);
g.FillRectangle(linearGradientBrush, 0, 0, combinedImage.Width, combinedImage.Height);
g.DrawImage(inputImage, 0,0);
previewPictureBox.Image = combinedImage;
Result with black as the forms background color and the example image with transparency.
EDIT:
I may have misunderstood the intention, and either haven't found an easy way like in WPF, but it is not this much difficult.
FileInfo inputImageFile = new FileInfo(#"C:\Temp\TheSimpsons.png");
Bitmap inputImage = (Bitmap)Bitmap.FromFile(inputImageFile.FullName);
// create blank bitmap
Bitmap adjImage = new Bitmap(inputImage.Width, inputImage.Height);
// create graphics object on new blank bitmap
Graphics g = Graphics.FromImage(adjImage);
LinearGradientBrush linearGradientBrush = new LinearGradientBrush(
new Rectangle(0, 0, adjImage.Width, adjImage.Height),
Color.White,
Color.Transparent,
0f);
Rectangle rect = new Rectangle(0, 0, adjImage.Width, adjImage.Height);
g.FillRectangle(linearGradientBrush, rect);
int x;
int y;
for (x = 0; x < adjImage.Width; ++x)
{
for (y = 0; y < adjImage.Height; ++y)
{
Color inputPixelColor = inputImage.GetPixel(x, y);
Color adjPixelColor = adjImage.GetPixel(x, y);
Color newColor = Color.FromArgb(adjPixelColor.A, inputPixelColor.R, inputPixelColor.G, inputPixelColor.B);
adjImage.SetPixel(x, y, newColor);
}
}
previewPictureBox.Image = adjImage;
Related
I'm trying to extend the C# Button class so that I can color the button, with up to 3 colours, according to the percentages provided. It appears to work apart from the button text is not shown. Please can somebody advise how to display the text?
Also, I'm unsure as to whether Refresh() is the correct method to call in the Setter methods to trigger a redraw with the new percentage provided?
Many thanks.
class TriColorsButton : Button
{
private double percentOfLeftColor, percentOfRightColor;
public TriColorsButton(String text, double percentOfLeftColor, double percentOfRightColor)
{
this.Text = text;
this.percentOfLeftColor = percentOfLeftColor;
this.percentOfRightColor = percentOfRightColor;
}
protected override void OnPaint(PaintEventArgs pe)
{
base.OnPaint(pe);
Brush brush;
RectangleF rectFirst, rectSecond, rectThird;
Color c1, c2, c3;
// create the first rectangle
rectFirst = new RectangleF(0, 0, this.Width, this.Height);
// create the second rectangle
int callWidth = (int)(this.Width * (100 - percentOfRightColor) / 100);
rectSecond = new RectangleF(0, 0, callWidth, this.Height);
// create the third rectangle
int leftWidth = (int)(this.Width * percentOfLeftColor / 100);
rectThird = new RectangleF(0, 0, leftWidth, this.Height);
// create the colors
c1 = Constants.altFoldColor;
c2 = Constants.altCallColor;
c3 = Constants.altRaiseColor;
// create the brush
brush = new SolidBrush(c1);
// fill the segment
pe.Graphics.FillRectangle(brush, rectFirst);
// create the brush
brush = new SolidBrush(c2);
// fill the segment
pe.Graphics.FillRectangle(brush, rectSecond);
// create the brush
brush = new SolidBrush(c3);
// fill the segment
pe.Graphics.FillRectangle(brush, rectThird);
// dispose of the brush
brush.Dispose();
}
public void SetPercentOfLeftColor(double percentOfLeftColor)
{
this.percentOfLeftColor = percentOfLeftColor;
Refresh();
}
public double GetPercentOfLeftColor()
{
return percentOfLeftColor;
}
public void SetPercentOfRightColor(double percentOfRightColor)
{
this.percentOfRightColor = percentOfRightColor;
Refresh();
}
public double GetPercentOfRightColor()
{
return percentOfRightColor;
}
}
Also, although this paints the colors that I want, I lose the original look and feel of a standard button. I'd like to retain the standard button look and feel such as the on-hover highlighting and border of a standard button. How can I achieve this?
EDIT: Upon suggestion I tried to draw a background image for the button instead. This had a strange effect of causing most of my form to go white. It would redraw and look better if I moved the form on screen but other updates wouldn't happen appropriately. This is what I tried:
protected override void OnPaint(PaintEventArgs pe)
{
base.OnPaint(pe);
Image bmp = new Bitmap(Width, Height);
using (Graphics g = Graphics.FromImage(bmp))
{
Brush brush;
RectangleF rectFirst, rectSecond, rectThird;
Color c1, c2, c3;
// create the first rectangle
rectFirst = new RectangleF(0, 0, this.Width, this.Height);
// create the second rectangle
int callWidth = (int)(this.Width * (100 - percentOfRightColor) / 100);
rectSecond = new RectangleF(0, 0, callWidth, this.Height);
// create the third rectangle
int leftWidth = (int)(this.Width * percentOfLeftColor / 100);
rectThird = new RectangleF(0, 0, leftWidth, this.Height);
// create the colors
c1 = Constants.altFoldColor;
c2 = Constants.altCallColor;
c3 = Constants.altRaiseColor;
// create the brush
brush = new SolidBrush(c1);
// fill the segment
g.FillRectangle(brush, rectFirst);
// create the brush
brush = new SolidBrush(c2);
// fill the segment
g.FillRectangle(brush, rectSecond);
// create the brush
brush = new SolidBrush(c3);
// fill the segment
g.FillRectangle(brush, rectThird);
// dispose of the brush
brush.Dispose();
}
this.BackgroundImage = bmp;
}
EDIT2: I figured out how to achieve what I wanted. I added a method that draws and sets the background and called it from the constructor. The OnPaint method no longer requires an override:
private void SetBackgroundImage()
{
Image bmp = new Bitmap(Width, Height);
using (Graphics g = Graphics.FromImage(bmp))
{
Brush brush;
RectangleF rectFirst, rectSecond, rectThird;
Color c1, c2, c3;
// create the first rectangle
rectFirst = new RectangleF(0, 0, this.Width, this.Height);
// create the second rectangle
int callWidth = (int)(this.Width * (100 - percentOfRightColor) / 100);
rectSecond = new RectangleF(0, 0, callWidth, this.Height);
// create the third rectangle
int leftWidth = (int)(this.Width * percentOfLeftColor / 100);
rectThird = new RectangleF(0, 0, leftWidth, this.Height);
// create the colors
c1 = Constants.altFoldColor;
c2 = Constants.altCallColor;
c3 = Constants.altRaiseColor;
// create the brush
brush = new SolidBrush(c1);
// fill the segment
g.FillRectangle(brush, rectFirst);
// create the brush
brush = new SolidBrush(c2);
// fill the segment
g.FillRectangle(brush, rectSecond);
// create the brush
brush = new SolidBrush(c3);
// fill the segment
g.FillRectangle(brush, rectThird);
// dispose of the brush
brush.Dispose();
}
this.BackgroundImage = bmp;
}
Overriding OnPaint in windows forms is mostly an all or nothing affair. I.e. if you need custom drawing, you need to draw everything, including the text. In this regard WPF is a bit better since it allows for better modularity.
This should be fairly easy to fix by inserting a call to Graphics.DrawString or TextRendered.DrawText at the end of your OnPaint method, where TextRendered.DrawText should be more consistent with other controls. You might also need some way to ensure sufficient contrast, like adding a drop shadow or a background rectangle. Note that drawing is done in the same order as the calls are made, i.e. later draw calls will be on top of earlier, so your call to base.OnPaint(pe); might be redundant if it is completely overdrawn anyway.
Also, I'm unsure as to whether Refresh() is the correct method to call in the Setter methods to trigger a redraw with the new percentage provided?
I usually use .Invalidate(). If I understand the documentation correctly, the difference is that Invalidate redraws the control at some later time, while Refresh redraws it immediately.
Im drawing Text using the following code onto a Bitmap
GraphicsPath pth = new GraphicsPath();
var style = (int)myfont.Style;
pth.AddString(tcaption.Text, myfont.FontFamily, style, myfont.Size, point, StringFormat.GenericTypographic);
p = new Pen(new SolidBrush(bc), 2f);
mygraphics.DrawPath(p, pth);
I'm using the TextRenderer to measure the size of the string..
int Width = TextRenderer.MeasureText(tcaption.Text, myfont).Width;
But this does not produce the correct size of the drawn string; there is around 20-30% difference from the actual size of the drawn string?
What im i doing wrong? Please advice.
UPDATE:
I want to draw a Text and an Image onto a Bitmap,so inorder to accommodate both i'm creating an Bitmap like this
intWidth = TextRenderer.MeasureText(tcaption.Text, cfont).Width + image.Width;
intHeight = TextRenderer.MeasureText(tcaption.Text, cfont).Height +image.Height;
tempimage= new Bitmap(intWidth, intHeight);
Then i create Graphics object from the Bitmap like this
using (Graphics newg = Graphics.FromImage(tempimage))
#Hans Passant
I have also tried the Graphics.MeasureString as an alternative to TextRenderer
Now i set the position of the text and image-I need to draw the image at the top left corner .. so
imageposy = 0;
imageposx = 10;
textposy = image.Height;
textposx = 0;
Then i draw the text like this
po=new Point(textposx, textposy);
newg.SmoothingMode = SmoothingMode.AntiAlias;
GraphicsPath pth = new GraphicsPath();
var style = (int)myfont.Style;
pth.AddString(tcaption.Text, myfont.FontFamily, style, myfont.Size, po,
StringFormat.GenericTypographic);
newg.FillPath(new SolidBrush(fc), pth);
Now i draw the image like this
Rectangle nrect = new Rectangle(imageposx, imageposy, image.Width,
image.Height);
objGraphics = Graphics.FromImage(tempimage);
objGraphics.DrawImage(image, nrect);
As you have seen i need to add the offset 10 to imageposition x coordinate to correct the measurement issue.
Hope my update throws more light into the question... what im i doing wrong?
Please advice..
instead of using TextRenderer use GraphicsPath:
var path = new GraphicsPath();
path.AddString(text, font.FontFamily, (int)font.Style, size, new Point(0, 0), StringFormat.GenericTypographic);
var area = Rectangle.Round(path.GetBounds());
Here is sample code that generates image with size of text:
private Image DrawText(String text, Font font, int size, Color textColor, Color backColor)
{
var path = new GraphicsPath();
path.AddString(text, font.FontFamily, (int)font.Style, size, new Point(0, 0), StringFormat.GenericTypographic);
var area = Rectangle.Round(path.GetBounds());
Rectangle br = Rectangle.Round(path.GetBounds());
var img = new Bitmap(br.Width, br.Height);
var drawing = Graphics.FromImage(img);
drawing.TextRenderingHint = System.Drawing.Text.TextRenderingHint.AntiAliasGridFit;
drawing.SmoothingMode = SmoothingMode.HighSpeed;
drawing.Clear(backColor);
drawing.TranslateTransform((img.Width - br.Width) / 2 - br.X, (img.Height - br.Height) / 2 - br.Y);
drawing.FillPath(Brushes.Black, path);
Brush textBrush = new SolidBrush(textColor);
drawing.Save();
textBrush.Dispose();
drawing.Dispose();
return img;
}
Here are sample results:
I have an app that will draw many shapes (rectangle, line and circle) on a panel.
the panel can zoom in and out this shapes.
What I'm trying to do is when the application is fired I need to have the shapes zoomed to fit the window.
How I can do that, I read a lot about defined Images but not shapes.
here is my snap shot
private void panel1_Paint(object sender, PaintEventArgs e)
{
SolidBrush brushs = new SolidBrush(Color.White);
e.Graphics.Clip = new Region(new Rectangle(0, 0, Viewer.Width, Viewer.Height));
e.Graphics.FillRegion(brushs, e.Graphics.Clip);
Graphics g = e.Graphics;
g.TranslateTransform(_ImgX, _ImgY);
g.ScaleTransform(_Zoom, _Zoom);
g.SmoothingMode = SmoothingMode.AntiAlias;
SolidBrush myBrush = new SolidBrush(Color.Black);
Pen p = new Pen(Color.Red);
foreach (CircuitData.ResistorRow resistorRow in ResistorData.Resistor)
{
RectangleF rec = new RectangleF((float)(resistorRow.CenterX - resistorRow.Length/ 2), (float)(resistorRow.CenterY - resistorRow.Width/ 2), (float)resistorRow.Length, (float)resistorRow.Width);
float orientation = 360 - (float)resistorRow.Orientation;
PointF center = new PointF((float)resistorRow.CenterX, (float)resistorRow.CenterY);
PointF[] points = CreatePolygon(rec, center, orientation);
if (!Double.IsNaN(resistorRow.HiX) && !Double.IsNaN(resistorRow.HiY))
{
g.FillEllipse(myBrush, (float)resistorRow.HiX - 2 , (float)resistorRow.HiY - 2, 4, 4);
g.DrawLine(p, new PointF((float)resistorRow.HiX , (float)resistorRow.HiY ), center);
}
g.FillPolygon(myBrush, points);
}
}
Thanks
Draw your shapes onto a Metafile. Its dimensions will be calculated automatically after it is created. At the end you can zoom it safely when you draw it onto the panel.
// the reference Graphics can be taken from your form, its size does not matter
Graphics refGraph = this.CreateGraphics();
IntPtr hdc = refGraph.GetHdc();
Metafile result = new Metafile(hdc, EmfType.EmfOnly, "Shapes");
using (var g = Graphics.FromImage(result))
{
// draw your shapes here (zooming is not necessary)
DrawShapes(g);
}
refGraph.ReleaseHdc(hdc);
refGraph.Dispose();
// use this metafile on the panel
return result;
I need to copy the pixels from one image to another, but only the black pixels. I have to separate the black pixels out for sending to a printer using the K Panel on the printer ribbon.
Basically I need to make every pixel in the image that isn't black transparent or white.
What's the best way to do this?
The only idea I have is something along the lines of:
var attr = new ImageAttributes();
attr.SetColorKey(minColor, maxColor);
using (Graphics g = Graphics.FromImage(backGround))
{
var destRect = new Rectangle(0, 0, backGround.Width, backGround.Height);
g.DrawImage(kPanelImage, destRect, 0, 0, backGround.Width, backGround.Height, GraphicsUnit.Pixel, attr);
}
Bitmap mySource = new Bitmap("your_image.jpg");
for(int w=0; w<mySource.Width; ++w)
for(int h=0; h<mySource.Height; ++h)
{
Color pixelColor = mySource .GetPixel(w, h);
if ( pixelColor != Color.Black )
mySource .SetPixel(w, h, Color.White);
}
Why don't you iterate every pixel in the image then check it's color. If it is not black then change it's color to white
here is the code i'm using, i really need to make it so the text does not appear inside of a box. is that possible?
int width = (int)g.MeasureString(line, f).Width;
int height = (int)g.MeasureString(line,f).Height;
b = new Bitmap(b, new Size(width, height));
g = Graphics.FromImage(b);
g.Clear(Color.Empty);
g.DrawString(line,f, new SolidBrush(Color.Black), 0, 0);
b.Save(savepoint+line+".tif", System.Drawing.Imaging.ImageFormat.Tiff);
g.Flush();
What i mean is there can be no Rectangle around the text that is converted to image. So I need to watch it to the same color to create the illusion there is no box, or to never write out that rectangle period.
Use the color Transparent for the backgtround and a file format that supports transparency, like PNG:
var measure = g.MeasureString(line, f);
int width = (int)measure.Width;
int height = (int)measure.Height;
using (Bitmap b = new Bitmap(width, height)) {
using (Graphics bg = Graphics.FromImage(b)) {
bg.Clear(Color.Transparent);
using (Brush black = new SolidBrush(Color.Black)) {
bg.DrawString(line, f, black, 0, 0);
}
}
b.Save(savepoint+line+".png", System.Drawing.Imaging.ImageFormat.Png);
}
I notice that you did overwrite your Graphics instance, didn't dispose the objects that you create, and called Graphics.Flush for no apparent reason...