Groups | Blog | Home
all groups > dotnet drawing api > february 2008 >

dotnet drawing api : Loss of precision when drawing gridlines and blocks on top.....


alexstevens@blueyonder.co.uk
2/26/2008 3:56:26 AM
Hi All, I'd really appreciate some help for drawing precision images.

I'm trying to draw gridlines and have them perfectly spaced. I've
provided some code at the end to simulate the issues I'm facing.

In psuedo-code (where I am splitting up the control into four (using
three gridlines), I take the width of the controls client ares, and
calculate the position of each gridline. I do this by allowing for a
border of 1 pixel either side, and three gridlines = 5 pixels taken up
by lines. I then divide the remainder by the number of segments (4).
So:

float segmentWidth = (float)(this.clientRectangle - (5)) / 4;This
works fine when the grid is wide. As soon as you start adding to the
number of segments, or make the control smaller - it visiblly shows
the segments not being the same width. (Try the sample code I've
provided, by pasting into a new cotnrol and dropping it on to a form,
anchor it to all sides and run, then resize the form a few times).

I suspect it's because the precision of using floats means that the
display cannot draw the required number of pixels accurately???

The problem gets worse when you try to add blocks to fill the segment
(i'm trying to devise a timeline control). I calculate the width
required for the block by using the segment's size (and allowing for
any gridlines it crosses). This works OK until the gridlines start
falling out of place and the two seperate drawings (the gridlines and
the blocks) do not correspond.

Any ideas on the best way to go forwards with this guys / gals???

Thanks

protected override void OnPaint(PaintEventArgs pe)
{
Int32 i32GridLineWidth = 1;
RectangleF rectGrid = new RectangleF(0, 0,
this.ClientRectangle.Width - 1, this.ClientRectangle.Height - 1);
SizeF vertLineSize = new SizeF();
SizeF horzLineSize = new SizeF();
SizeF segmentSize = new SizeF();

Int32 i32NumberOfHorzSegements = 15;
Int32 i32NumberOfVertSegements = 10;

//Define the vertical of the gridline
vertLineSize.Height = (float)rectGrid.Height;
vertLineSize.Width = (float)i32GridLineWidth;

//Define the hoizontal gridline
horzLineSize.Height = (float)i32GridLineWidth;
horzLineSize.Width = (float)rectGrid.Width;

//calulate the width (allowing for the gridlines aswell
segmentSize.Width = (float)(rectGrid.Width -
(i32NumberOfHorzSegements * vertLineSize.Width)) /
i32NumberOfHorzSegements;
segmentSize.Height = (float)(rectGrid.Height -
(i32NumberOfVertSegements * horzLineSize.Height)) /
i32NumberOfVertSegements;

//Draw the gridlines
using (Pen gridlinePen = new Pen(Color.Gray,
i32GridLineWidth))
{
//Draw the vertical gridlines.
for (Int32 i32Line = 1; i32Line <=
i32NumberOfHorzSegements - 1; i32Line++)
{
float fltXpos = (float)((vertLineSize.Width *
(i32Line - 1)) + (i32Line * segmentSize.Width));
pe.Graphics.DrawLine(gridlinePen, fltXpos,
rectGrid.Top, fltXpos, rectGrid.Height);
}
//Draw the horizontal gridlines.
for (Int32 i32Line = 1; i32Line <=
i32NumberOfVertSegements - 1; i32Line++)
{
float fltYpos = (float)((horzLineSize.Height *
(i32Line - 1)) + (i32Line * segmentSize.Height));
pe.Graphics.DrawLine(gridlinePen, rectGrid.Left,
fltYpos, rectGrid.Width, fltYpos);
}
//Draw the outer control border.
pe.Graphics.DrawRectangles(gridlinePen, new
RectangleF[] { rectGrid });
}

//Draw some bars.
using (SolidBrush segmentBrush = new
SolidBrush(Color.Crimson))
{

Int32 i32VertSegment = 1;
Int32 i32HorzSegment = 3;
Int32 i32Length = 10;

RectangleF segmentToFill = new RectangleF(
rectGrid.X + (i32HorzSegment * (i32GridLineWidth +
segmentSize.Width)),
rectGrid.Y + ((i32VertSegment - 1) *
(i32GridLineWidth + segmentSize.Height)) + i32GridLineWidth,
rectGrid.X + (i32Length * (i32GridLineWidth +
segmentSize.Width)) - (2 * i32GridLineWidth),
rectGrid.Y + (1 * (i32GridLineWidth +
segmentSize.Height)) - (2 * i32GridLineWidth));
pe.Graphics.FillRectangle(segmentBrush,
segmentToFill);

}

// Calling the base class OnPaint
//base.OnPaint(pe);
}

protected override void OnResize(EventArgs e)
{
//base.OnResize(e);
this.Invalidate();
alexstevens@blueyonder.co.uk
2/27/2008 12:26:56 AM
[quoted text, click to view]

Michael - many thanks for your response. It may be the overall
approach to this which is wrong.

I'm trying to create a grid which has 24 hours across the top, and a
number of channels down the left. I need to firstly draw out the grid
lines to form a matrix, and then plot blocks of time on the grid to
represent programmes. How can I guarantee the accuracy of elements
that I draw? I've no problem with being unable to draw at a sub-pixel
level (as I suspected), but when I plot the location and size of two
seperate elements (the gridlines and the block on top) and then
resize, the inaccuracy is augmented.

Run up the sample code an resize it in realtime, you'll see that issue
immediately.

I couldn't find anything on codeproject which was close enough to my
needs, anything commercially available isn't worth it for a pet
project, and I thought it would be a good way to get to grips with
custom drawing.

Michael C
2/27/2008 3:19:28 PM
[quoted text, click to view]

I have to say, the answer to this is fairly obvious. The number of pixels
you can divide into does not result in a nice round number. If you have 70
pixels and want to draw 20 lines then it's not going to divide evenly.
Approx half will 3 pixels space and half will have 2 pixels of space.

As for options I'm not too sure, what are you trying to do?

Michael


Bob Powell [MVP]
2/28/2008 6:37:41 PM
..NET drawing uses floats for accuracy but must ultimately be rendered on
an integer raster. The only way to ensure the accuracy is to plot the
lines positioned by floats and ensure that the Graphics object selects
the nearest pixel by setting the pixeloffset mode correctly.

When converting floats to integers always round to the nearest by adding
0.5f. This will make the general appearance much better.

--
Bob Powell [MVP]
Visual C#, System.Drawing

Ramuseco Limited .NET consulting
http://www.ramuseco.com

Find great Windows Forms articles in Windows Forms Tips and Tricks
http://www.bobpowell.net/tipstricks.htm

Answer those GDI+ questions with the GDI+ FAQ
http://www.bobpowell.net/faqmain.htm

All new articles provide code in C# and VB.NET.
Subscribe to the RSS feeds provided and never miss a new article.





[quoted text, click to view]
Michael C
2/29/2008 10:36:35 AM
[quoted text, click to view]

It's a very good idea to learn user drawing, it's a very powerful tool. It
can put your programs a level above others (in the GUI). In your case you
could just set a minimum size that the control can be resized to. If your
control is large then something being 10 pixels vs 9 isn't going to matter
as much as when it's 2 vs 3 pixels. The other option is to just make your
control a fixed size and add scrollbars (most programs would do this). The
UserControl object in dot net has a built in feature that will give you
scrollbars. Have a look at the AutoScroll properties.

[quoted text, click to view]

alexstevens@blueyonder.co.uk
3/3/2008 11:03:15 AM
Thanks for the responses Michael and Bob - I'm very grateful.

What I've ended up doing is having a property which allows you to
define the width of a column (represents one hour - I'm building a
timeline control for multiple elements down the left). I then
calculate the amount of pixels as a float which represent a second in
time (columnwidth / 3600) and then multiply that up by the amount of
time I'm trying to display. I do the same to plot the gridlines which
define the columns, and round using a generic function which
theoretically should give me the same result for two different
calculations with the same parameters. You're right though, to try to
split up 50-100 pixels into 24 equal segments for display is
ridiculous - hence my new approach of allowing the user to define the
column width.

This leads me onto my next problem. I need to implement scrolling in
my control, but I'm struggling to find resource to help me with it.
There are a couple of alternatives:

1. Create a composite control and have scrollbars.
2. Inherit from Scrollable control and let the framework draw the
scroll bars (i've not been able to get this to work in the slightest!!
I'm trying to follow this but it doesn't display scrollbars when I
paint outside of the bounds of the controls ClientRect:
http://www.dotnet247.com/247reference/msgs/53/267790.aspx - This is my
preference.
3. Draw my own scrollbars - this isn't my preference.

Could anyone give me a good place to start. I'm drawing my control
bigger than the client area - I just need to display scrollbars to
allow the user to scroll to non-visible areas.

AddThis Social Bookmark Button