locked
Direct2D: Unexpected result when both fill and stroke have opacity

    Question

  • // Red stroke color with 50% opacity
    Microsoft::WRL::ComPtr<ID2D1SolidColorBrush> strokeBrush;
    m_d2dContext->CreateSolidColorBrush(D2D1::ColorF(1.0, 0.0, 0.0, 0.5), &strokeBrush);
    
    // Blue fill color with 50% opacity
    Microsoft::WRL::ComPtr<ID2D1SolidColorBrush> fillBrush;
    m_d2dContext->CreateSolidColorBrush(D2D1::ColorF(0.0, 0.0, 1.0, 0.5), &fillBrush);
    
    // Stroke and then fill the path 
    m_d2dContext->DrawGeometry(pathGeometry, strokeBrush.Get(), 40.0);
    m_d2dContext->FillGeometry(pathGeometry, fillBrush.Get());

    With the above code, I am trying to draw a path with both fill and stroke having opacity, in my metro app.

    But the inside of the stroke which overlaps with the fill is getting a composite color due to the opacity applied on both the brushes.

    See: https://picasaweb.google.com/113406662374959447955/Direct2DProblem?authuser=0&authkey=Gv1sRgCMSPuqD5l6OdggE&feat=directlink

    I do not want the fill and stroke color of the same path to blend and create a new color.

    How can I get the expected result?

    Thursday, August 09, 2012 1:59 PM

Answers

  • While at the Microsoft's BUILD conference, I got to interact with folks having direct contact in Direct2D team, and I've got the following resolution:

    Direct2D behaves a little bit differently than XAML in this regard. We don’t expose a single primitive that renders both the stroke and the fill in one operation; however, D2D layers can be used to achieve the same effect. To do this, one issues the following set of calls:

    • PushLayer (with the opacity to be applied to the group of primitives)
    • FillGeometry (with opacity = 1)
    • DrawGeometry (with opacity = 1)
    • PopLayer

    Direct2D Layers: http://msdn.microsoft.com/en-us/library/windows/desktop/dd756654(v=vs.85).aspx

    I've tried this approach and it works! I am a bit concerned about the performance impact of this change though, as now we're creating one layer per object having opacity less than 1.0.

    From the Layers article :

    Although layers offer a powerful rendering technique for producing interesting effects, excessive number of layers in an application can adversely affect its performance, because of the various costs associated with managing layers and layer resources. For example, there is the cost of filling or clearing the layer and then blending it back, especially on higher-end hardware. Then, there is the cost of  managing the layer resources. If you reallocate these frequently, the resulting stalls against the GPU will be the most significant problem. When you design your application, try to maximize reusing layer resources.

    Also the problem with XAML remains, but we're not actually creating XAML path primitives. I only included it to highlight the broken imaging model.

    Thanks.



    • Marked as answer by Varun S. Nair Wednesday, November 07, 2012 3:21 AM
    • Edited by Varun S. Nair Thursday, November 08, 2012 6:32 AM
    Wednesday, November 07, 2012 3:20 AM

All replies

  • I can't find an API which takes both fill and stroke together and provides the expected result.

    Can someone from Microsoft please assist on this?

    Tuesday, August 14, 2012 3:47 AM
  • This is expected behavior. The stroke is centered on the path, so the inner half of it overlaps the fill. Two transparent colours will add together. To avoid this you will either need to make the top item opaque (generally the stroke) or alter the sizes so that they don't overlap.

    --Rob

    • Proposed as answer by Jesse Jiang Tuesday, August 14, 2012 7:15 AM
    • Unproposed as answer by Varun S. Nair Friday, October 12, 2012 4:05 AM
    Tuesday, August 14, 2012 4:38 AM
    Owner
  • I understand that for the above code it is expected, as I am applying the fill and stroke separately.  I don't want to do that (specify them separately), I want to specify both of them together so that interaction is taken into account, but can't find the API to do that.

    The problem is there in XAML too where I *CAN* specify both of them together:

    <Ellipse Width="400" Height="300" Fill="#770000FF" Stroke="#77FF0000" StrokeThickness="40" />

    Even if I specify the opacity separately and not as part of the color, still the same incorrect rendering is achieved:

    <Ellipse Width="400" Height="300" Opacity="0.5" Fill="#FF0000FF" Stroke="#FFFF0000" StrokeThickness="40" />

    Compare with the rendering achieved in SVG using the same semantics:

    <?xml version="1.0" encoding="utf-8"?>
    <!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
    <svg version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px" width="1000px" height="1000px" viewBox="0 0 1000 1000" enable-background="new 0 0 1000 1000" >
    <ellipse cx="500" cy="500" rx="200" ry="150" opacity="0.5" fill="#0000FF" stroke="#FF0000" stroke-width="40" />
    </svg>
    
    Removing the opacity from the stroke is not an option as it is a feature. And I believe calculating the geometry so that the color do not overlap for an arbitrary bezier is non-trivial. If not, is it available in Direct2D?
    Thursday, August 16, 2012 5:19 AM
  • Can someone please provide a viable solution to this problem?
    Thursday, August 23, 2012 2:48 AM
  • We're still struck with this issue. Can someone from Microsoft please look into this and provide a solution?

    Thanks.

    Friday, October 12, 2012 4:04 AM
  • While at the Microsoft's BUILD conference, I got to interact with folks having direct contact in Direct2D team, and I've got the following resolution:

    Direct2D behaves a little bit differently than XAML in this regard. We don’t expose a single primitive that renders both the stroke and the fill in one operation; however, D2D layers can be used to achieve the same effect. To do this, one issues the following set of calls:

    • PushLayer (with the opacity to be applied to the group of primitives)
    • FillGeometry (with opacity = 1)
    • DrawGeometry (with opacity = 1)
    • PopLayer

    Direct2D Layers: http://msdn.microsoft.com/en-us/library/windows/desktop/dd756654(v=vs.85).aspx

    I've tried this approach and it works! I am a bit concerned about the performance impact of this change though, as now we're creating one layer per object having opacity less than 1.0.

    From the Layers article :

    Although layers offer a powerful rendering technique for producing interesting effects, excessive number of layers in an application can adversely affect its performance, because of the various costs associated with managing layers and layer resources. For example, there is the cost of filling or clearing the layer and then blending it back, especially on higher-end hardware. Then, there is the cost of  managing the layer resources. If you reallocate these frequently, the resulting stalls against the GPU will be the most significant problem. When you design your application, try to maximize reusing layer resources.

    Also the problem with XAML remains, but we're not actually creating XAML path primitives. I only included it to highlight the broken imaging model.

    Thanks.



    • Marked as answer by Varun S. Nair Wednesday, November 07, 2012 3:21 AM
    • Edited by Varun S. Nair Thursday, November 08, 2012 6:32 AM
    Wednesday, November 07, 2012 3:20 AM