-
Notifications
You must be signed in to change notification settings - Fork 6
Edge of Graphic
Drawing a rectangle which size is 100x100, border thickness is 1 and color is #ff0000 at coordinate (10,10) with GDI and Direct 2D respectively, the result is obviously different, as the picture shows below:
The rectangle drawn with Direct 2D appears fuzzy, because of its border thickness is 2 pixels rather than 1 pixel, and its color isn’t #ff0000 either.
The reason of the difference is that Direct 2D uses vector coordinate system. A vector image needs to be converted to a scalar image when it is displayed on screen. This fuzzy rectangle is the result of the conversion.
For another example, drawing a straight line which thickness is 1 and color is #ff0000 from coordinate (2,2) to (8,2), it appears on vector coordinate system like below:
Obviously, for screen, it can’t just draw half of a pixel, thus all pixels the line passes through need to be drawn. Now the line turns to 2 pixels and its color is weakened correspondingly. This results in a fuzzy edge, as the picture shows below:
To avoid this case, the coordinates of line can be adjusted for 0.5 offset, then the image of line on vector coordinate system is the same as the one on screen, no difference any more. As the picture shows below:
We can draw a conclusion that when drawing lines, fuzzy edges appear if below conditions are met:
- After rounding up to integer, the line’s thickness is odd number, and its coordinates doesn’t align to 0.5 unit.
- After rounding up to integer, the line’s thickness is even number, and its coordinates doesn’t align to 1 unit.
Besides lines, solid graphics can also appear fuzzy edges. The rules for solid graphics are simpler relatively. If the below condition is met, fuzzy edges appear:
- The graphic’s coordinates doesn’t align to 1 unit.
For zaf, clear edges are expected rather than fuzzy edges in most cases, just like what GDI draws. Therefore, zaf provides some methods to help drawing clear edges.
There are overloaded functions MakeClearEdgeForLine
and MakeClearEdgeForFill
in header file graphic/clear_edge.h
. These two class functions are used to draw lines and fill solids respectively, and they transfer the graphic’s coordinate to a proper value. Objects to be transferred could be zaf::Point
, zaf::Rect
, or zaf::Ellipse
etc.
For example, to draw a hollow rectangle with 1 border thickness and clear edges, MakeClearEdgeForLine
can be used to transfer the coordinate firstly:
//Get rectangle rect.
zaf::Rect rectangle_rect = …;
//Transfer to clear edge rect.
rectangle_rect = zaf::MakeClearEdgeForLine(rectangle_rect, 1, zaf::ClearEdgeOption::Clear);
//Use the rect to draw.
...
The first argument of MakeClearEdgeForLine
is the object to be transferred; the second argument is border thickness; and the third argument is clear edge option. The option zaf::ClearEdgeOption::Clear
indicates to transfer to clear edge, and the other option zaf::ClearEdgeOption::None
indicates that nothing is transferred.
Usage of MakeClearEdgeForFill
is the same, except that it doesn’t have the second argument.
Usually, we use an instance of zaf::Canvas
to draw in control’s Paint
method. zaf::Canvas
holds a drawing state, which contains a clear edge option. This option can be accessed via GetClearEdgeOption
and SetClearEdgeOption
methods. The default value to the option is zaf::ClearEdgeOption::Clear
, it means that most graphics we draw using zaf::Canvas
have clear edges automatically, and we don’t need to transfer the coordinates manually.
However, we still need to transfer coordinates by ourselves in some cases. Such as using zaf::PathGeometry
to create a complex graphic, it needs to set coordinates before drawing. At this point, besides using the global functions MakeClearEdgeForLine
and MakeClearEdgeForFill
introduced above, we can also use the same name methods of zaf::Canvas
. Below is an example to draw a flipped triangle in combo box(codes are omitted):
void ComboBox::Paint(Canvas& canvas, const Rect& dirty_rect) {
//Calculate the coordinate of left upper point.
Point left_point = ...;
left_point = canvas.MakeClearEdgeForFill(left_point);
//Calculate the coordinate of rigth upper point.
Point right_point = ...;
right_point = canvas.MakeClearEdgeForFill(right_point);
//Calculate the coordinate of bottom point.
Point bottom_point = ...;
bottom_point = canvas.MakeClearEdgeForFill(bottom_point);
//Create and open a path to compose.
PathGeometry path = ...;
GeometrySink sink = path.Open();
//Compose the triangle graphic.
sink.BeginFigure(left_point, GeometrySink::BeginFigureOption::Fill);
sink.AddLine(right_point);
sink.AddLine(bottom_point);
sink.EndFigure(GeometrySink::EndFigureOption::Close);
sink.Close();
//Draw the path geometry.
canvas.SetBrushWithColor(GetDropDownButtonColor());
canvas.DrawGeometry(path);
}
MakeClearEdgeForLine
and MakeClearEdgeForFill
of zaf::Canvas
forward the call to the global functions with clear edge option set in zaf::Canvas
actually. It is recommended to use member version rather than global version, for keeping clear edge option consistent with drawing methods.