I'm trying to draw images on a C# form (in PictureBoxes, as well as using Graphics.DrawImage()), and am looking for a way to draw them smooth. The images must be a format that supports transparency, so PNG, GIF, SVG, and WMF. C# doesn't support SVG files out of the box, and I haven't found a good third-party library to use (I found SvgNet, but couldn't figure it out).
I need to draw a WMF file, which C# can do via the Image.FromFile() function, but it's not anti-aliased. I was wondering if there's any way to smooth this out?
The previous answers, while well intended were only partially correct.
What was correct? PictureBox doesn't expose InterpolationMode.
What was off base?
1) While you can easily set that property in the Paint event from the picture box, in its parent, or via an override in a derived class. . . Either way works and both are just as easy. However, unless SmoothingMode is set, the InterpolationMode will be ignored. You won't get any anti-aliasing without SmoothingMode set to SmoothingMode.AnitAlias.
2) Using a Panel when you've clearly expressed an interest in using the features of PictureBox is the wrong direction to go. You will lack any ability to load, save, or assign images directly to it without explicitly coding those properties. . . Why re-invent the wheel? By deriving off of PictureBox you get all of that for free.
The news gets even better as I've done the hard work for you and it took me less time than writing this message.
I've provided two version both of which derive from PictureBox. First is a simple example which always uses the best quality rendering possible. This is also the slowest rendering. Second is a class that allows anyone to set the various rendering parameters via properties off the derived class. Once set these are used in the OnPaint override.
public class HighQualitySmoothPictureBox : PictureBox
{
protected override void OnPaint(PaintEventArgs pe)
{
// This is the only line needed for anti-aliasing to be turned on.
pe.Graphics.SmoothingMode = System.Drawing.Drawing2D.SmoothingMode.AntiAlias;
// the next two lines of code (not comments) are needed to get the highest
// possible quiality of anti-aliasing. Remove them if you want the image to render faster.
pe.Graphics.CompositingQuality = System.Drawing.Drawing2D.CompositingQuality.HighQuality;
pe.Graphics.InterpolationMode = System.Drawing.Drawing2D.InterpolationMode.HighQualityBicubic;
// this line is needed for .net to draw the contents.
base.OnPaint(pe);
}
}
...
public class ConfigurableQualityPictureBox : PictureBox
{
// Note: the use of the "?" indicates the value type is "nullable."
// If the property is unset, it doesn't have a value, and therefore isn't
// used when the OnPaint method executes.
System.Drawing.Drawing2D.SmoothingMode? smoothingMode;
System.Drawing.Drawing2D.CompositingQuality? compositingQuality;
System.Drawing.Drawing2D.InterpolationMode? interpolationMode;
public System.Drawing.Drawing2D.SmoothingMode? SmoothingMode
{
get { return smoothingMode; }
set { smoothingMode = value; }
}
public System.Drawing.Drawing2D.CompositingQuality? CompositingQuality
{
get { return compositingQuality; }
set { compositingQuality = value; }
}
public System.Drawing.Drawing2D.InterpolationMode? InterpolationMode
{
get { return interpolationMode; }
set { interpolationMode = value; }
}
protected override void OnPaint(PaintEventArgs pe)
{
if (smoothingMode.HasValue)
pe.Graphics.SmoothingMode = smoothingMode.Value;
if (compositingQuality.HasValue)
pe.Graphics.CompositingQuality = compositingQuality.Value;
if (interpolationMode.HasValue)
pe.Graphics.InterpolationMode = interpolationMode.Value;
// this line is needed for .net to draw the contents.
base.OnPaint(pe);
}
}
When drawing the image to a canvas, you can change the interpolation mode to something nicer then nearest neighbor to make resized images smooth:
Graphics g = ...
g.InterpolationMode = InterpolationMode.HighQualityBicubic;
g.DrawImage(...);
You'll need to add System.Drawing.Drawing2D to get the InterpolationMode enum.
Using PictureBox will be a problem - it doesn't expose an InterpolationMode property, so you'll need to roll your own or download one.
Related
Scenario
Having a Windows Forms Form-derived form that contains a Panel-derived control:
The form gets a black background color set:
public MyForm()
{
InitializeComponent();
base.BackColor = Color.Black;
}
And the panel control is configured to be double buffered and the like, as described here, here and here:
public MyPanel()
{
base.DoubleBuffered = true;
SetStyle(ControlStyles.AllPaintingInWmPaint, true);
SetStyle(ControlStyles.ResizeRedraw, true);
SetStyle(ControlStyles.UserPaint, true);
SetStyle(ControlStyles.OptimizedDoubleBuffer, true);
UpdateStyles();
}
The actual drawing is done inside these overrides in MyPanel:
protected override void OnPaintBackground(PaintEventArgs e)
{
e.Graphics.Clear(Color.Black);
}
protected override void OnPaint(PaintEventArgs e)
{
e.Graphics.Clear(Color.Black);
}
Wrong behaviour
Every now and then, when the form is initially shown, my owner drawn panel is shortly drawn as white, before my own drawing code is actually called:
After my drawing code is called, everything is drawn correctly and I never can reproduce the white background again:
Even resizing the window and panel does not make it flicker in white.
Enforcing the wrong behavior
I can enforce the initial white-drawn of my panel, if I put a sleep in the Shown event handler of my form:
private void MyForm_Shown(object sender, EventArgs e)
{
Thread.Sleep(1000);
}
The panel is shown in white for 1000 ms just before my owner-drawn paint code is being called.
My question
How can I avoid the initial white displaying when having an owner drawn/custom drawn panel?
My goal is to have the control "know" its initial background color (black in my example) right from the start, not after it is initially shown.
Some thoughts
I've tried to play with all kind of things including the CreateParams property, but with no visible success.
My initial idea was to provide some initial background color through the WNDCLASSEX structure, but after digging through the Reference Source, I still have no clue whether this is possible and would help.
Whole code
Just to be safe, following is my whole code.
MyForm.cs:
public partial class MyForm : Form
{
public MyForm()
{
InitializeComponent();
base.BackColor = Color.Black;
}
private void MyForm_Shown(object sender, EventArgs e)
{
Thread.Sleep(1000);
}
}
MyPanel.cs:
public class MyPanel : Panel
{
public MyPanel()
{
base.DoubleBuffered = true;
SetStyle(ControlStyles.AllPaintingInWmPaint, true);
SetStyle(ControlStyles.ResizeRedraw, true);
SetStyle(ControlStyles.UserPaint, true);
SetStyle(ControlStyles.OptimizedDoubleBuffer, true);
UpdateStyles();
}
protected override void OnPaintBackground(PaintEventArgs e)
{
e.Graphics.Clear(Color.Black);
}
protected override void OnPaint(PaintEventArgs e)
{
e.Graphics.Clear(Color.Black);
}
}
I had a custom control, pretty much completely self-drawn, that was dynamically getting added to a Form in the dropdown mechanism in PropertyGrid.
If I read your problem right, it's basically the same overall issue.
I had BackColor set and was fine. But with DoubleBuffer set, it seems to just ignore it for a bit.
Taking in all of the existing comments on the question, I was able to have this solution, and hope the details help someone else make their own.
Problem 1
My control flickered unless I repainted whole control every OnPaint.
If did proper painting only attempting to paint things that intersected the e.ClipRectangle, then it would flicker in real-time, like effects from invalidates that had to do when the mouse was moved. Attempt to Paint whole thing, no problem.
I watched trace output real-time and watched all of my draws and invalidates print, and never a time where should be introducing flicker myself, on myself directly.
Problem 2
If I turn on DoubleBuffered, then instead it flickered badly as the control was shown every time opening the dropdown. From white background only for 100-200 ms, at least, then to black and rendered foreground suddenly in one step.
Problem 3
I never actually needed double buffer. Both the problem 1 and 2 were always related to WM_ERASEBKGND.
The actual original flicker seems to be caused by WM_ERASEBKGND very briefly visibly whacking my already painted thing, right before I painted it again. Did not really need actual double buffering in my case. For some reason when I was blindly painting the whole list maybe the timing was different and was painting over the erase before could see it.
All that said, if I turn DoubleBuffered on which removes WM_ERASEBKGND via turning on AllPaintingInWmPaint, then initial background won't be painted until I suppose the double buffer and paint process works its way, all the way through the first time.
Problem 4
If I let the WM_ERASEBKGND "just happen", then it's still double painting, and I don't know if or when it might end up flicking anyway for someone else.
If I only turn on SetStyle(OptimizedDoubleBuffer, then I now know I'll be letting the initial background paint and not flicker on show. But I also know I'm using double buffer to mask the WM_ERASEBKGND for the entirety of the life of the control after it is shown.
So....
I did something like this:
Part 1
if the user of the control sees a need to double buffer doing something that might flicker, create a way for them to easily enable it, without forcing AllPaintingInWmPaint. Like if they want to use Paint or a DrawXXX event and doing something that animates or something related to mouse movement.
bool _isDoubleBuffer;
[Category("Behavior")]
public virtual bool DoubleBuffer
{
get { return _isDoubleBuffer; } // dont care about real SetStyle state
set
{
if (value != DoubleBuffer)
{
_isDoubleBuffer = value;
SetStyle(ControlStyles.OptimizedDoubleBuffer, value);
}
}
}
Part 2
Manage WM_ERASEBKGND yourself, as the choice is otherwise 1) always off with AllPaintingInWmPaint, and no background paint on show, or 2) violating what double buffer expects where it would be always masking the WM_ERASEBKGND.
protected override void WndProc(ref Message m)
{
switch (m.Msg)
{
case WM_ERASEBKGND:
if (_hasPaintForeground && _isDoubleBuffer)
return;
}
base.WndProc(ref m);
}
Part 3
You are now your own decider of what AllPaintingInWmPaint means.
In this case would want the initial messages to process like normal. When we knew for sure the .Net and DoubleBuffer side was finally kicking it, by seeing our first real paint happen, then turn WM_ERASEBKGND off for the duration.
bool _hasPaintForeground;
protected override void OnPaint(PaintEventArgs e)
{
// your paint code here, if any
base.OnPaint(e);
if (!_hasPaintForeground) // read cheaper than write every time
{
_hasPaintForeground = true;
}
}
Part 4
In my case, I also had originally gimped the OnBackground draw, which works if you are opaque drawing each element yourself in OnPaint. This allowed me to not have double buffer on for so long until I started following the clip and changed the timing so that I started also seeing the other WM_ERASEBKGND side effects and issues.
protected override void OnPaintBackground(PaintEventArgs e)
{ // Paint default background until first time paint foreground.
if (!_hasPaintForeground) // This will kill background image but stop background flicker
{ // and eliminate a lot of code, and need for double buffer.
base.OnPaintBackground(e);
} // PaintBackground is needed on first show so no white background
}
I may not need this part anymore, but if my DoubleBuffer is off then I would. So long as I'm always painting opaque in OnPaint covering the whole draw Clip area.
Addendum:
In addition to all of that....
Separate issue with text render.
It looks like if I render only 250 x 42, like two rows and two text renders, which all occur in one OnPaint, verified with Diagnostics.Trace.WriteLine, then the text renders at least one monitor frame later, every single time. Making it look like the text is flashing. Is just 2x paint background single color then 2x paint text each for rows.
However, if I attempt to paint the whole client area of like 250 x 512 or whatever, like 17 rows, even though the e.Clip is exactly those two rows, because I'm the one that invalidated it, then no flicker of the text, 0 flicker.
There is either some timing issue or other side effect. But that's 17 chances instead of two, for at least one row to flicker text where the whole background is shown before the text renders, and it never happens. If I try to only render rows that are in the clip area it happens every time.
There is def something going with .Net or Windows. I tried with both g.DrawString and TextRenderer.DrawText and they both do it. Flicker if draw 2, not flicker if attempt to draw 17. Every time.
Maybe has something to do with drawing text near the mouse pointer, when OnPaint comes back too quickly?
Maybe if I draw enough things or OnPaint takes longer to come back, it's doing double buffer anyway? Dunno
So....
It's a good thing I went through this exercise with the original question.
I may choose to just render the whole client every time, but I'll never be able to do it the "right way" without something like my example code above.
I have a winform c# app.
I am using Emgu for comparing the motion differences between 2 images.
I impose the changes detected onto the 1st image so in affect the first image now looks identical to the second image.
I am overriding the onpaint event of a user control to draw the image. I have to Invalidate the usercontrol to force the onpaint event.
This works well but the memory still 'spikes' a bit. Is there a way to invalidate only the pixels that have changed - like regions for instance?
This is my current code:
protected override void OnPaint(PaintEventArgs pe)
{
Graphics g = pe.Graphics;
if (CurrentFrame != null)
{
pe.Graphics.DrawImageUnscaled(CurrentFrame, 0, 0);
}
}
The CurrentFrame is a static Bitmap
Thanks
I am trying to integrate Winforms with a SharpDX project, in order to use Winforms (and eventually WPF via HostElement) in my 3D app.
I need to create or configure a Control or Form such that I can:
a. Render it to a texture (that I can display as a sprite*)
b. Filter its input to remove mouse/keyboard events when the control is not active.
I have tried subclassing Control and Form, to override the OnPaint and OnPaintBackground but these have no effect on the child controls - or for that matter the forms borders (and even if they did they are not sufficient on their own as I am still left with a white square where I presume the 'parent' has been drawn).
How can I stop a Control or Form painting to the screen and instead draw only to a bitmap? (Is there some way I can override Graphics before the tree is painted, for example?)
*It needs to be done this way (as opposed to letting the control render to the screen) as Winforms doesn't support true transparency, so I need to clip colour coded pixels in my pixel shader.
(To confirm, I don't mean a DirectX texture specifically - I am happy with (in fact would prefer) a simple System.Drawing Bitmap)
Here is one way to start going about it:
Create a derived control class so that we can expose InvokePaint which is protected
Call our custom method to get the Control's image
Test form needs a picture box and an instance of Mybutton
using System;
using System.Drawing;
using System.Windows.Forms;
namespace WindowsFormsApplication1
{
public partial class Form1 : Form
{
public Form1() { InitializeComponent(); }
private void Form1_Load(object sender, EventArgs e)
{
// create image to which we will draw
var img = new Bitmap(100, 100);
// get a Graphics object via which we will draw to the image
var g = Graphics.FromImage(img);
// create event args with the graphics object
var pea = new PaintEventArgs(g, new Rectangle(new Point(0,0), new Size(100,100)));
// call DoPaint method of our inherited object
btnTarget.DoPaint(pea);
// modify the image with algorithms of your choice...
// display the result in a picture box for testing and proof
pictureBox.BackgroundImage = img;
}
}
public class MyButton : Button
{
// wrapping InvokePaint via a public method
public void DoPaint(PaintEventArgs pea)
{
InvokePaint(this, pea);
}
}
}
Excuse the code dump, these are functions within a UserControl
private void PNGQuantPreviewControl_Resize(object sender, EventArgs e)
{
createOffScreenBm();
draw();
}
private void createOffScreenBm()
{
offScreenBm = new Bitmap(this.Size.Width, this.Size.Height);
offScreenGfx = Graphics.FromImage(offScreenBm);
}
private void draw()
{
// draw background
offScreenGfx.FillRectangle(transTexture, 0, 0, offScreenBm.Width, offScreenBm.Height);
// draw image preview
offScreenGfx.DrawImage(pngQuantPreview, getTopLeftPosition());
// apply to picture box
this.CreateGraphics().DrawImage(offScreenBm, 0, 0);
}
So, when the control changes size, it recreates the offscreen bitmap to reflect the new size and redraws the image.
However, if I quickly resize the control the bitmap doesn't fill it, there's a gap left at the right and/or bottom.
I'm fairly new to C#, so there's probably something obvious I'm doing wrong, or I'm reading the size values at the wrong time. Any ideas?
First of all you need to overwrite OnPaint method, or subscribe to Paint event and draw everything there.
Second you do not need to create offscreen bitmap for double buffering, because in .net already exist class for such purposes BufferedGraphics.
And third, it is much better to create UserControl descedant and enable internal .net double buffering, something like this:
public UserControl2
{
SetStyle(ControlStyles.OptimizedDoubleBuffer | ControlStyles.AllPaintingInWmPaint | ControlStyles.UserPaint, true);
}
Using this approach you will get double-buffering, and all you need is to draw your graphics in OnPaint method. You can read more about this control styles in Msdn.
Have you considered overriding the OnPaint method and placing the code within that method? This would result in your drawing code being executed any time the control needs to be redrawn, regardless of the reason.
A resize event does not necessarily wait until you are finished resizing the parent container. When the resize event is raised it needs to wait until the code exits before it can capture a new resize event so when the window/control is resized quickly, it can't keep up all that well and what you get is the last time it was able to capture the event, not necessarily the final state of the control ... if that makes any sense.
Do you have anything like a splitter on your control, or a MinSize or MaxSize declared?
is there a way, to make a picture transparent in CF2.0? I have to lay a small Image over a textbox, but it has to be transparent so the User can see the text anyway. Do you have an Idea?
Thank you very much
twickl
Edit:
Thanks for your answers, I will check those links out!
To complete my Post, here is what I´m trying to do:
I want to show a small image (the image does not exist yet and I have to make ist, so I´m totaly open for all formats) that is an X on the right end of a textbox. By clicking that X the Text inside the textbox will be erased...like on the iPhone. But I can not build my own control becuse in my Project are so many TextBoxes that are allready custom Controls with the windows TextBox on it, that it will be to much work and testing to switch all of them to custom controls. So I have the Idea to make a small Panel, Picturebox, whatever, that lays above the Textbox. But it has to be transparent. The OS is Windows CE 5.0 with CF 2.0 on it.
Depending on what kind of transparency you need, you might choose any of these options:
1.) If you have an image with a specific portion that should be entirely transparent, you can use ImageAttributes.SetColorKey() to set a single transparent color and then pass this to Graphics.DrawImage. Your image will need to have one color (e.g. Color.Cyan) that will be drawn completely transparent.
2.) If you'd like the entire image to be partially transparent, e.g. for a fade in/fade out effect, you can P/Invoke the AlphaBlend() function, as demonstrated here.
3.) If you have an image with transparency information built in, e.g. a transparent PNG image that needs to be rendered on a variety of background colors, these previous methods will not work and you need to use the COM based IImage interface.
The COM interop from .NETCF is documented on this page (search for "IImage interface" on that page).
Option 3 is the most flexible, but it also involves the most implementation effort. If you follow up with more information about the kind of image you want to draw transparently and your target platform, we might be able to help more.
I did it by deriving a class from PictureBox and handling OnPaint. The key is the ImageAttributes object passed to DrawImage. I'm assuming pixel 0,0 is the transparent color, but you could handle that differently.
public partial class TransparentPictureBox : PictureBox
{
private Color tColor;
public TransparentPictureBox()
{
InitializeComponent();
}
public new Image Image
{
get { return base.Image; }
set
{
if (value == base.Image)
return;
if (value != null)
{
Bitmap bmp = new Bitmap(value);
tColor = bmp.GetPixel(0, 0);
this.Width = value.Width;
this.Height = value.Height;
}
base.Image = value;
}
}
protected override void OnPaint(PaintEventArgs e)
{
e.Graphics.Clear(this.BackColor);
if (Image == null)
return;
ImageAttributes attr = new ImageAttributes();
// Set the transparency color.
attr.SetColorKey(tColor, tColor);
Rectangle dstRect = new Rectangle(0, 0, base.Image.Width, base.Image.Height);
e.Graphics.DrawImage(base.Image, dstRect, 0, 0, base.Image.Width, base.Image.Height, GraphicsUnit.Pixel, attr);
}
}