Kira
Kira

Reputation: 1443

Graphics.Clear(Color.Transparent) draw blurry text inside tab control

I have my user control inside a tab. Text inside user control is blurry. Please find the code of user control paint event below

protected override void OnPaint(PaintEventArgs e)
{
     //Avoid redrawing the control unnecessarily
     if(redraw)
     {
         using (Graphics g = Graphics.FromImage(buffer))
         {
                g.Clear(Color.Transparent);
                draw(g, ClientRectangle);
         }
     }
     e.Graphics.DrawImage(buffer, Point.Empty);
}

I noticed following things if I place my control inside Tab control available in VS toolbox.

1) Text drawn in user control is blurry

2) Paint event triggered three times. Due to conditional redraw, drawing logic will be executed once and existing buffer will be drawn on 2nd and 3rd time

3) If I use g.Clear(Color.White), text looks good

The line g.Clear is supposed to clear the Bitmap(buffer) and fill it with specified color. It also clears the state of the graphics object. If I use any color other than Transparent, text is not blurry.

Is there any notable difference between g.Clear(Color.White) and g.Clear(Color.Transparent)?

Note: Color.White is just an example. Any named color gets rid of the problem. Also occurs only with Tab control

Edit: Blurred and correct images are available below

Blurry Text

Normal image

Upvotes: 0

Views: 2958

Answers (2)

TaW
TaW

Reputation: 54433

In addition to a suggestion how to solve the problem, let me correct two misconceptions..

  • Color.Transparent is essentially a no-op—it does absolutely nothing

Filling/clearing with Color.Transparent will make all pixels a transparent black: (0,0,0,0).

Contrary to sombody's remarks this is not nothing; it is useful and a normal start when one really wants to draw onto a transparent canvas.

Since your result looks wrong with it, you better clear the canvas with the color you really want. If you want to display something like a copy of a TabPage you should probably start by filling with the BackColor of that page:

g.Clear(tapPage1.BackColor);

Another remark, this times by you is that :

  • using g.Clear(Color.Transparent) fills the bitmap with black color.

Well, as I said it does fill with transparent black (0,0,0,0). It does not fill with real black (255,0,0,0).

Transparency is not supported by all imaging software. A notably bad program is the (otherwise great) IrfanView, which to this day will display any transparency as black. Paint .Net will 'display' it 'correctly' meaning by underlaying it with a checkerboard pattern that shines trough at the transparent pixels..

To make an image 'just' transparent (alpha=0) without clearing the RBG channels you can use a routine like this (although I prefer Lockbits to unsafe), but as I said, this is not what you want..

Update

g.Clear(Color.Transparent);

in fact can make the canvas transparent; this is what happens when you use it on a bitmap..

..or it brings up the parent's color when you use it on a control. If you use it on a Form, which doesn't have a parent it makes it look black.

Upvotes: 2

Cody Gray
Cody Gray

Reputation: 244732

In what universe does it make sense to "clear" with a transparent color?

Color.Transparent is…transparent. Defined as the absence of color. If you fill with a transparent color, you fill with nothing. Emptiness. Transparency.

If you fill something that had text on it with a transparent color, it will have no effect and the text will remain there. Then, if you draw the same text back on top of it, you will see blurriness (at least, assuming the text is anti-aliased; if it's not, nothing will change).

Any color other than Color.Transparent will work to clear the area because they are opaque colors. Meaning that they are the opposite of transparent. You can't see through them, so they actually cover up what is behind them.


You've also tried too hard to optimize the code. When a Paint event handler is called, the item needs to be painted. The operating system already takes care of optimizing this for you. So the if (redraw) check is neither necessary nor a good idea. Just fill with your background color, and draw the image or text on top of it.


Update: I was pointed here to code that supposedly reproduces the problem. Like Moonlight Sheng, I was unable to reproduce the described problem with that code.

This is not surprising. In the linked code, the call to g.Clear is completely superfluous for two reasons. First, as I have already explained, clearing with Color.Transparent is essentially a no-op—it does absolutely nothing. Second, the line immediately following fills the entire image rectangle with a solid white brush. This serves the purpose of "clearing" the drawing surface, making it all white. You could have accomplished the same thing with g.Clear(Color.White). The text is then drawn on top of this white background, and finally the image is drawn onto the control. This works perfectly because there are never any "transparent" areas. Whenever the image is drawn on top of the control, it covers up (therefore erasing) everything that was there previously. Absolutely no blurriness. As such, this has nothing to do with the use of Graphics.Clear or Color.Transparent.

I suppose that since you are overriding OnLayout, you intend for this control to be used with either its Anchor or Dock properties set. So I set the Dock property to Fill in my test project, and re-ran the application. Again, I fail to reproduce the problem. However, I do notice that when rapidly resizing the container form, I can produce "tearing" effects or other visual artifacts. For example, when I make the form really small, I see the text drawn multiple times:

This is a race condition caused by the rapid resizing of the form. The dimensions of the client rectangle are changing too quickly, and the newly-exposed regions of the form are not being erased. Let me see if I can explain what is actually happening.

  • When the form (and control) changes to size X, you destroy the old buffer image and create a new buffer image of size X. Then you call DrawString to draw text into it. The image is too narrow to fit the entire string on one line, so it wraps the "100" onto a second line. The image is then drawn onto the form, and everything looks fine.
  • Then the form (and control) changes to size Y. So you again destroy the old buffer image and create a new buffer image of size Y. This time, the width has increased so that when the text is drawn, everything fits on one line. The image is then drawn onto the form.
  • Oh, but wait. Size Y is wider than size X, but also shorter (less tall). So when you draw this new buffer to the form, it doesn't cause the previous area to be erased. This is why you see the old "100", wrapped onto the second line, remain visible.

It's very difficult to explain, and I imagine harder still to understand, unless you already know how painting works.

The important part is that there is a simple solution: set the form's ResizeRedraw property to true, ensuring that a Paint event gets raised every time the form is resized. Forcing a repaint in the control's OnLayout event handler method would also work (this.Invalidate()), like so:

public partial class MyControl : UserControl
{
   Image buffer;
   public MyControl()
   {
      InitializeComponent();
   }

   protected override void OnLayout(LayoutEventArgs e)
   {
      if (buffer != null)
      {
         buffer.Dispose();
         buffer = null;
      }
      base.OnLayout(e);

      this.Invalidate();  // force a repaint after the layout has changed
   }

   protected override void OnPaint(PaintEventArgs e)
   {
      if (buffer == null)
      {
         buffer = new Bitmap(ClientRectangle.Width, ClientRectangle.Height);

         using (Graphics g = Graphics.FromImage(buffer))
         {
            //g.Clear(Color.Transparent);  // pointless
            g.FillRectangle(Brushes.White, ClientRectangle);
            using (Font f = new System.Drawing.Font("Segoe UI", 12, FontStyle.Regular))
            {
               g.DrawString("Simple text 100", f, Brushes.Black, ClientRectangle);
            }
         }
      }
      e.Graphics.DrawImage(buffer, ClientRectangle);
      base.OnPaint(e);
   }
}

Anyway, this is not a bug as claimed, but simply a misunderstanding of how the painting subsystem works in Windows.

Incidentally, if you remove that call to FillRectangle (which fills the control's client area with solid white), you would see the blurry-text effect that you describe originally. This is occurring for the exact reasons that I have already stated: the text is anti-aliased, and drawing anti-aliased text on top of anti-aliased text produces a "blurry" effect. You cannot fix this in any sensible way. Anti-aliased text absolutely must be drawn on top of a solid-color background; it cannot be drawn on a transparent background. You will need to fill with whatever your form's background color is. By default, that is SystemColors.Control.

Upvotes: 1

Related Questions