Search code examples
c++gdi+direct2d

Convert Gdiplus::Region to ID2D1Geometry* for clipping


I am trying to migrate my graphics interface project from Gdiplus to Direct2D.

Currently, I have a code that calculates clipping area for an rendering object:

Graphics g(hdc);
Region regC = Rect(x, y, cx + padding[2] + padding[0], cy + padding[3] + padding[1]);
RecursRegPos(this->parent, &regC);
RecursRegClip(this->parent, &regC);
g.setClip(g);

...

inline void RecursRegClip(Object *parent, Region* reg)
{
    if (parent == CARD_PARENT)
        return;

    if (parent->overflow != OVISIBLE)
    {
        Rect regC(parent->getAbsoluteX(), parent->getAbsoluteY(), parent->getCx(), parent->getCy()); // Implementation of these function is not necceassary

        GraphicsPath path;
        path.Reset();
        GetRoundRectPath(&path, regC, parent->borderRadius[0]);
        // source https://stackoverflow.com/a/71431813/15220214, but if diameter is zero, just plain rect is returned
        
        reg->Intersect(&path);
    }

    RecursRegClip(parent->parent, reg);
}

inline void RecursRegPos(Object* parent, Rect* reg)
{
    if (parent == CARD_PARENT)
        return;
    
    reg->X += parent->getX() + parent->padding[0];
    reg->Y += parent->getY() + parent->padding[1];
    if (parent->overflow == OSCROLL || parent->overflow == OSCROLLH)
    {
        reg->X -= parent->scrollLeft;
        reg->Y -= parent->scrollTop;
    }

    RecursRegPos(parent->parent, reg);
}

And now I need to convert it to Direct2D. As You may notice, there is no need to create Graphics object to get complete calculated clipping region, so I it would be cool if there is way to just convert Region to ID2D1Geometry*, that, as far, as I understand from msdn article need to create clipping layer.

Also, there is probably way to convert existing code (RecursRegClip, RecursRegPos) to Direct2D, but I am facing problems, because I need to work with path, but current functions get region as an argument.


Update 1

There is Region::GetData method that returns, as I understand array of points, so maybe there is possibility to define either ID2D1PathGeometry or ID2D1GeometrySink by points?


Update 2

Oh, maybe

ID2D1GeometrySink::AddLines(const D2D1_POINT_2F *points, UINT32 pointsCount)

is what do I need?


Unfortunately, GetData of region based on just (0,0,4,4) rectangle returns 36 mystique values:

Region reg(Rect(0, 0, 4, 4));
auto so = reg.GetDataSize();
BYTE* are = new BYTE[so];
UINT fi = 0;
reg.GetData(are, so, &fi);

wchar_t ou[1024]=L"\0";

for (int i = 0; i < fi; i++)
{
    wchar_t buf[10] = L"";
    _itow_s(are[i], buf, 10, 10);

    wcscat_s(ou, 1024, buf);
    wcscat_s(ou, 1024, L", ");
}

// ou - 28, 0, 0, 0, 188, 90, 187, 128, 2, 16, 192, 219, 0, 0, 0, 0, 0, 0, 0, 16, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 128, 64, 0, 0, 128, 64,

Solution

  • I rewrote the solution completely, it seems to be working:

            // zclip is ID2D1PathGeometry*
            
        inline void Render(ID2D1HwndRenderTarget *target)
        {
            ID2D1RoundedRectangleGeometry* mask = nullptr;
            ID2D1Layer* clip = nullptr;
    
            if(ONE_OF_PARENTS_CLIPS_THIS || THIS_HAS_BORDER_RADIUS)
            {
            Region reg = Rect(x, y, cx + padding[2] + padding[0], cy + padding[3] + padding[1]);
    
            RecursRegPos(this->parent, &reg);
            
            D2D1_ROUNDED_RECT maskRect;
            maskRect.rect.left = reg.X;
            maskRect.rect.top = reg.Y;
            maskRect.rect.right = reg.X + reg.Width;
            maskRect.rect.bottom = reg.Y + reg.Height;
            maskRect.radiusX = this->borderRadius[0];
            maskRect.radiusY = this->borderRadius[1];
    
            factory->CreateRoundedRectangleGeometry(maskRect, &mask);
    
            RecursGeoClip(this->parent, mask);
    
            
            target->CreateLayer(NULL, &clip);
            if(zclip)
                target->PushLayer(D2D1::LayerParameters(D2D1::InfiniteRect(), zclip), clip);
            else
                target->PushLayer(D2D1::LayerParameters(D2D1::InfiniteRect(), mask), clip);
    
            SafeRelease(&mask);
            }
    
            // Draw stuff here
    
            if (clip)
            {
                target->PopLayer();
                SafeRelease(&clip);
                SafeRelease(&mask);
                SafeRelease(&zclip);
            }
        }
    
        ...
    
    
        inline void RecursGeoClip(Object* parent, ID2D1Geometry* geo)
        {
        if (parent == CARD_PARENT)
            return;
    
        ID2D1RoundedRectangleGeometry* maskParent = nullptr;
    
        if (parent->overflow != OVISIBLE)
        {
            Rect regC(parent->getAbsoluteX(), parent->getAbsoluteY(), parent->getCx(), parent->getCy());
    
            ID2D1GeometrySink* sink = nullptr;
            ID2D1PathGeometry* path = nullptr;
    
            SafeRelease(&path);
            factory->CreatePathGeometry(&path);
    
            D2D1_ROUNDED_RECT maskRect;
            maskRect.rect.left = regC.X;
            maskRect.rect.top = regC.Y;
            maskRect.rect.right = regC.X + regC.Width;
            maskRect.rect.bottom = regC.Y + regC.Height;
            maskRect.radiusX = parent->borderRadius[0];
            maskRect.radiusY = parent->borderRadius[1];
    
            path->Open(&sink);
    
            factory->CreateRoundedRectangleGeometry(maskRect, &maskParent);
    
            geo->CombineWithGeometry(maskParent, D2D1_COMBINE_MODE_INTERSECT, NULL, sink);
                        
            sink->Close();
            SafeRelease(&sink);
            
            SafeRelease(&this->zclip);
            this->zclip = path;
    
            RecursGeoClip(parent->parent, this->zclip);
        }
        else
            RecursGeoClip(parent->parent, geo);
    
        SafeRelease(&maskParent);
        }
    

    Now I can enjoy drawing one image and two rectangles in more than 60 fps, instead of 27 (in case of 200x200 image size, higher size - lower fps) with Gdi+ -_- :

    enter image description here