Skip to content
Zplutor edited this page May 3, 2017 · 1 revision

Fuzzy Edge in Direct 2D

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.

Clear Edge in zaf

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.

Transfer Coordinate Manually

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.

Transfer Coordinate Automatically

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.