Obaid Riaz

Hi,

Actually, I have an application that is using .Net Framework 1.1. I am doing custom painting of controls which is a requirement due to the look of the controls. The problem that I am facing is the painting of buttons. As the buttons have bitmaps applied to them which give them the desired look, their repainting time is very slow.

I dragged the windows calculator on the form rapidly and found that the buttons remained unpainted for quite a while whether the calculator was frequently dragged over them or not and the background turned white and as soon as the calculator was halted, they suddenly got painted. If I reduce the pace of calculator drag, the buttons will slowly but surely get painted. I load png files and merge them to create a bitmap in order to apply it to the button. I have tried to profile and then optimize the code as well. I have also used double buffering and have already cached the button bitmaps but it hasn¡¯t made much difference. At the moment it seems that I can not do much regarding this behavior as .Net DrawImage(), DrawString(), setting the text rendering hint to anti alias and setting the button region is taking most of the time. I have also tried to use DrawImageUnscaled() but the image gets deteriorated while the calculator is moved. I think I cannot do much now and this seems to be a problem of GDI+ performance. Please suggest any idea about what should I try now. Another thing which I wanted to ask was the usage of Managed DirectX. Please suggest whether this would be better for painting buttons having bitmaps as I desperately want to improve the painting performance and I think DirectX is my last hope.



Re: Windows Forms General GDI+ button image painting problem

Philip Wright

Can post some code for your paint routine as it will be tricky to recommend where the problem is without it.

Phil Wright
Free user interface controls for Windows.Forms
http://www.componentfactory.com





Re: Windows Forms General GDI+ button image painting problem

Obaid Riaz

Hi Wright,
Thanks for the reply, following is the code for painting buttons. Please have a look


public override bool OnPaint( PaintEventArgs robjArgs, Control rctlComponent )
{
AbstractButton btnComp = rctlComponent as AbstractButton;
if( btnComp == null )
return false; // do nothing
Graphics objComponentGraphics = robjArgs.Graphics;

//This is done to repaint only that area which is actually invalidated
objComponentGraphics.SetClip( robjArgs.ClipRectangle );

if( mstBkColor.IsEmpty )
mstBkColor = DrawingUtils.SelectSolidColor( this.GetBackColor( btnComp.PlafClassName ), btnComp.ComponentBackColor );
if( mstFrColor.IsEmpty)
mstFrColor = DrawingUtils.SelectSolidColor( this.GetForeColor( btnComp.PlafClassName ), btnComp.ForeColor );
if( mstHoverColor.IsEmpty)
mstHoverColor = DrawingUtils.SelectSolidColor( this.GetHoverColor( btnComp.PlafClassName ), Color.Yellow );

//This rectangle can change its size for e.g. on resize, this is the reason why it hasn't been made part of state
Rectangle mstImageRect = btnComp.ClientRectangle;
mstImageRect = new Rectangle( mstImageRect.X + 1, mstImageRect.Y + 1, mstImageRect.Width - 3, mstImageRect.Height - 3 );

//Setting the button face color
Color stFaceColor = mstBkColor;
if( !btnComp.Enabled )
stFaceColor = Color.Gray;
else if( ( ( btnComp.State & ButtonUIState.Pressed ) == ButtonUIState.Pressed ||
( btnComp.State & ButtonUIState.MouseOver ) == ButtonUIState.MouseOver ) )
stFaceColor = mstHoverColor;

DrawButtonImage( objComponentGraphics, robjArgs.ClipRectangle, mstImageRect, stFaceColor, btnComp );

string strText = btnComp.Text;
if( strText != null && strText.Length != 0 )
{

PaintText( objComponentGraphics, btnComp, strText, mstFrColor, true, OFFSET_BORDER );
}

if( btnComp.Image != null )
PaintIcon( objComponentGraphics, btnComp, true, OFFSET_BORDER );

if( btnComp.Focused && btnComp.Enabled )
PaintFocus( objComponentGraphics, btnComp );


return true;
}


private void DrawButtonImage( Graphics robjArgs, Rectangle rstClipRect, Rectangle rstRect, Color rstBackColor, AbstractButton rbtnComp )
{
string strHash = rstRect.GetHashCode().ToString();
Bitmap bmpImage = (Bitmap) sobjImageMap[ strHash ];

if( bmpImage == null)
{
bmpImage = this.GetButtonFaceImage( rstRect );
sobjImageMap.Add( strHash, bmpImage );
}

//This is done to give the desired look to the button otherwise the button would give a brighter look
ColorMatrix cmMatrix = GetColorMatrix( rstBackColor );
ImageAttributes attrFill = new ImageAttributes();
attrFill.SetColorMatrix( cmMatrix, ColorMatrixFlag.Default, ColorAdjustType.Bitmap );


robjArgs.DrawImage( bmpImage, rstClipRect, rstClipRect.X, rstClipRect.Y, rstClipRect.Width,
rstClipRect.Height, GraphicsUnit.Pixel, attrFill );

//Here, the undesired region of the button is cut or the desired region is identified and added to the button region
if( !mstClientRectangle.Equals( rstRect ) )
{
mstClientRectangle = rstRect;
Region objRegion = sobjRegionMap[ strHash ] as Region;
if( objRegion == null )
{
GraphicsPath objPath = BitmapToGraphicsPath( bmpImage );
objRegion = new Region( objPath );
sobjRegionMap.Add( strHash, objRegion );
objPath.Dispose();
rbtnComp.Region = objRegion;

}

else
rbtnComp.Region = objRegion.Clone();

}
}

private Bitmap GetButtonFaceImage( Rectangle rstRect )
{
Bitmap bmpImage = new Bitmap( rstRect.Width, rstRect.Height, System.Drawing.Imaging.PixelFormat.Format32bppPArgb );
Graphics objGfx = Graphics.FromImage( bmpImage );

int iLeft = simgLeft.Width;
int iRight = simgRight.Width;
int iFill = 0;

if ( simgLeft.Width + simgRight.Width > rstRect.Width )
iLeft = iRight = rstRect.Width / 2;
else
iFill = rstRect.Width - ( iLeft + iRight ) - 1;

//The code below draws the left, right and middle portions of the button. Only the middle portion is streched
//and its proportional to the width of the button.
Rectangle stLeft = new Rectangle( rstRect.Left, rstRect.Top, iLeft, rstRect.Height );
objGfx.DrawImage( simgLeft, stLeft, 0, 0, simgLeft.Width, simgLeft.Height, GraphicsUnit.Pixel );

if ( iFill > 0 )
{
Rectangle stFill = new Rectangle( rstRect.Left + iLeft, rstRect.Top, iFill, rstRect.Height);
ImageAttributes attrFill = new ImageAttributes();
attrFill.SetWrapMode( WrapMode.Tile );
objGfx.DrawImage( simgFill, stFill, 0, 0, simgFill.Width, simgFill.Height, GraphicsUnit.Pixel, attrFill );
}

Rectangle stRight = new Rectangle( rstRect.Width - iRight, rstRect.Top, iRight, rstRect.Height );
objGfx.DrawImage( simgRight, stRight, 0, 0, simgRight.Width, simgRight.Height, GraphicsUnit.Pixel );
objGfx.Dispose();
return bmpImage;
}

public static GraphicsPath BitmapToGraphicsPath( Bitmap robjBitmap )
{
int iHeight = robjBitmap.Height;
int iWidth = robjBitmap.Width;
// Create GraphicsPath for our bitmap calculation
GraphicsPath objGraphicsPath = new GraphicsPath();
int iColNext = 0;
Rectangle stOpaqueRectangle = Rectangle.Empty;
for( int iRow = 0; iRow < iHeight; iRow ++ )
{
for( int iCol = 0; iCol < iWidth; iCol ++ )
{
if( robjBitmap.GetPixel( iCol , iRow).A != 0 )// if color is opaque
{
iColNext = iWidth - iCol;
stOpaqueRectangle.X = iCol;
stOpaqueRectangle.Y = iRow;
stOpaqueRectangle.Width = iColNext - iCol + 1;
stOpaqueRectangle.Height = 1;
objGraphicsPath.AddRectangle( stOpaqueRectangle );
break;
}
}
}
return objGraphicsPath;
}

protected void PaintText( Graphics robjGfx, AbstractButton rbtnComp, string rstrButtonText, Color rstFrColor, bool rbPressEffect, int riBorderWidth )
{
if ( !mbIsLoaded )
{
//Getting the button font in which the button text would be displayed
mobjButtonFont = this.GetFont( rbtnComp.PlafClassName );
if( mobjButtonFont == null )
mobjButtonFont = rbtnComp.Font;
else if( rbtnComp.Font != mobjButtonFont )
mobjButtonFont = new Font( mobjButtonFont.Name, rbtnComp.Font.Size, rbtnComp.Font.Style );

//Setting the string format in which the button text would be shown
meContentAlign = rbtnComp.TextAlign;
mobjStringFormat = GetStringFormat( meContentAlign );
mobjStringFormat.HotkeyPrefix = HotkeyPrefix.Show;

mbIsLoaded = true;
}

//Getting the size of the text, this can change as the button width can be changed on resize etc.
Size stTextSize = robjGfx.MeasureString( rstrButtonText, mobjButtonFont, rbtnComp.Width, StringFormat.GenericDefault ).ToSize();


Rectangle stRect = new Rectangle( 0, 0, stTextSize.Width + 1, stTextSize.Height + 1 );

SetRectLocation( ref stRect, meContentAlign, rbtnComp.Height, rbtnComp.Width, riBorderWidth );


// for efficient text rendering

robjGfx.TextRenderingHint = TextRenderingHint.AntiAlias;


if( ( rbtnComp.State & ButtonUIState.Disabled ) == ButtonUIState.Disabled )
ControlPaint.DrawStringDisabled( robjGfx, rstrButtonText, mobjButtonFont, rstFrColor, stRect, StringFormat.GenericDefault );
else
{
//This is done to show the button press effect by adding a value to the X and Y coordinates of the text rectangle.
if( ( rbPressEffect && ( ( rbtnComp.State & ButtonUIState.Pressed ) == ButtonUIState.Pressed ) ) )
{
stRect.X = stRect.X + 1;
stRect.Y = stRect.Y + 1;
}
robjGfx.DrawString( robjGfx, rstrButtonText, mobjButtonFont, rstFrColor, stRect, mobjStringFormat );
}
}




Re: Windows Forms General GDI+ button image painting problem

Philip Wright

I am pretty sure the BitmapToGraphicsPath routine is going to kill performance. Iterating over every single pixel in a bitmap is slow and then creating a path from that is also going to be slow. Painting using a graphics path is also a slow performer so the combination is real bad. Try removing that routine and see how much it improves the overall speed. Then come up with an alternative way of doing whatever it is you are trying to do.

Phil Wright
Free user interface controls for Windows.Forms
http://www.componentfactory.com





Re: Windows Forms General GDI+ button image painting problem

Obaid Riaz

Hi again,

I have already tried removing the BitmapToGraphicsPath() method but the result is the same i.e. the flickering still persist. Secondly, I am not iterating over every pixel, I am just trying to find the first opaque pixel per row and then reaching the end opaque pixel through the first pixel index so, therefore it would not take that much of time. The time taken by the AddRectangle() method is greater than the GetPixel() method in this method. Now, what I want to tell you is that the DrawImage() method where ever used is taking most of the time.
The only solution I can think of now is using Managed DirectX. Please correct me if I am wrong.




Re: Windows Forms General GDI+ button image painting problem

nobugz

I agree with Philip, creating a GraphicsPath from the pixels of the bitmap should be brutally slow, both in creating the path and rendering it with DrawImage(). I assume you're trying to create transparency with this code. Have you tried Bitmap.MakeTransparent()





Re: Windows Forms General GDI+ button image painting problem

Obaid Riaz

Well, with this code I am trying to remove the transparent region from the button but as I told earlier, I have tried commenting that function but the problem still exists and the flickering is still there. This means that the left over code still has performance problems and at the moment I don't have any idea about what to do next. Please help me out of this problem. Also, I asked about DirectX but you people haven't forwarded your suggestions so please do that as well ASAP.