-
Notifications
You must be signed in to change notification settings - Fork 813
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Implement drawing CGPaths through CGContext #1307
Changes from 2 commits
d43b075
5f79b50
5eab616
24f5707
34bd6d5
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -26,6 +26,8 @@ | |
|
||
#import "LoggingNative.h" | ||
|
||
#import "D2DWrapper.h" | ||
|
||
#pragma clang diagnostic push | ||
#pragma clang diagnostic ignored "-Wdeprecated-register" | ||
|
||
|
@@ -452,7 +454,7 @@ | |
BYTE* imageData = static_cast<BYTE*>(LockImageData()); | ||
ComPtr<IWICBitmap> wicBitmap = Make<CGIWICBitmap>(this, SurfaceFormat()); | ||
ComPtr<ID2D1Factory> d2dFactory; | ||
THROW_IF_FAILED(D2D1CreateFactory(D2D1_FACTORY_TYPE_SINGLE_THREADED, __uuidof(ID2D1Factory), &d2dFactory)); | ||
FAIL_FAST_IF_FAILED(_CGGetD2DFactory(&d2dFactory)); | ||
ComPtr<ID2D1RenderTarget> renderTarget; | ||
THROW_IF_FAILED(d2dFactory->CreateWicBitmapRenderTarget(wicBitmap.Get(), D2D1::RenderTargetProperties(), &renderTarget)); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. :-( super sad at the inconsistencies here. I thought the decision was to be error returning out to the public layer at which point a decision would be made on the correct course of action. This still seems internal as it returns a D2D object. Additionally, returning a Com object with a raw * usually speaks to a problematic design pattern. This is because it requires the "unjacketing" of the ComObject to return it to the caller without a clear communication on transfer/sharing of ownership which is by definition occuring by handing it out. #Resolved There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The render target generators in CGImage and friends are going away (and are only being updated as necessary as part of this PR); ideally, yes, we'd fix everything all at once. There's little point when it's gonna be turfed. :) There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This code has gone away, i'll be sending out the PR soon. In reply to: 86612430 [](ancestors = 86612430) There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. |
||
_renderTarget = renderTarget.Detach(); | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -27,6 +27,7 @@ | |
#import <CoreGraphics/CGGradient.h> | ||
#import "CGColorSpaceInternal.h" | ||
#import "CGContextInternal.h" | ||
#import "CGPathInternal.h" | ||
|
||
#import <CFCppBase.h> | ||
|
||
|
@@ -1646,8 +1647,12 @@ void CGContextFillRects(CGContextRef context, const CGRect* rects, size_t count) | |
*/ | ||
void CGContextDrawPath(CGContextRef context, CGPathDrawingMode mode) { | ||
NOISY_RETURN_IF_NULL(context); | ||
UNIMPLEMENTED(); | ||
context->ClearPath(); | ||
if (context->HasPath()) { | ||
ID2D1Geometry* pGeometry; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Please make this a You should almost never use bare COM interface pointers. :) #Resolved There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Unless you're at a module boundary, or a boundary that makes sense (like CGContext <-> CGPath), and even then only in function parameters. #Resolved There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yes, yes, yes. using raw * means you really have to think about it and its easy to forget something / mess up. Com stuff gets a ComPtr almost always. In reply to: 86611265 [](ancestors = 86611265) |
||
FAIL_FAST_IF_FAILED(_CGPathGetGeometry(context->Path(), &pGeometry)); | ||
__CGContextDrawGeometry(context, pGeometry, mode); | ||
context->ClearPath(); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. should do something with the HRESULT here #Resolved |
||
} | ||
} | ||
|
||
/** | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -24,11 +24,12 @@ | |
#import <CoreFoundation/CoreFoundation.h> | ||
#import <CFRuntime.h> | ||
#import "D2DWrapper.h" | ||
#import "CGPathInternal.h" | ||
|
||
#include <COMIncludes.h> | ||
#import <wrl/client.h> | ||
#import <D2d1.h> | ||
#include <COMIncludes_End.h> | ||
|
||
#import <CFCPPBase.h> | ||
|
||
static const wchar_t* TAG = L"CGPath"; | ||
|
@@ -91,7 +92,8 @@ void SetStartingPoint(CGPoint newPoint) { | |
HRESULT PreparePathForEditing() { | ||
if (!_impl.geometrySink) { | ||
// Re-open this geometry. | ||
ComPtr<ID2D1Factory> factory = _GetD2DFactoryInstance(); | ||
ComPtr<ID2D1Factory> factory; | ||
RETURN_IF_FAILED(_CGGetD2DFactory(&factory)); | ||
|
||
// Create temp vars for new path/sink | ||
ComPtr<ID2D1PathGeometry> newPath; | ||
|
@@ -113,12 +115,13 @@ HRESULT PreparePathForEditing() { | |
return S_OK; | ||
} | ||
|
||
void ClosePath() { | ||
HRESULT ClosePath() { | ||
if (_impl.geometrySink) { | ||
EndFigure(D2D1_FIGURE_END_OPEN); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Can this fail? #ByDesign There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. virtual void EndFigure( It cannot. Calling it multiple times can result in a bad state though which makes this API slightly weird since it doesn't report that itself. In reply to: 86611357 [](ancestors = 86611357) |
||
_impl.geometrySink->Close(); | ||
RETURN_IF_FAILED(_impl.geometrySink->Close()); | ||
_impl.geometrySink = nullptr; | ||
} | ||
return S_OK; | ||
} | ||
|
||
void BeginFigure() { | ||
|
@@ -136,7 +139,8 @@ void EndFigure(D2D1_FIGURE_END figureStatus) { | |
} | ||
|
||
HRESULT InitializeGeometries() { | ||
ComPtr<ID2D1Factory> factory = _GetD2DFactoryInstance(); | ||
ComPtr<ID2D1Factory> factory; | ||
RETURN_IF_FAILED(_CGGetD2DFactory(&factory)); | ||
|
||
RETURN_IF_FAILED(factory->CreatePathGeometry(&_impl.pathGeometry)); | ||
RETURN_IF_FAILED(_impl.pathGeometry->Open(&_impl.geometrySink)); | ||
|
@@ -145,6 +149,14 @@ HRESULT InitializeGeometries() { | |
} | ||
}; | ||
|
||
HRESULT _CGPathGetGeometry(CGPathRef path, ID2D1Geometry** pGeometry) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
if pGeometry is invalid, we end up returning S_OK. RETURN_HR_IF_NULL(E_POINTER,pGeometry); |
||
if (path && pGeometry) { | ||
RETURN_IF_FAILED(path->ClosePath()); | ||
*pGeometry = path->GetPathGeometry().Get(); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. You might need to There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. not might. Definitely need. CopyTo is ever so slightly worse than a .Detach here as CopyTo does an extra AddRef Release that isn't needed for the anonymous rvalue vs detach but yes. The more efficient and more sane, probably, pattern is a raw ** here for the GetPathGeometry method. In reply to: 86611487 [](ancestors = 86611487) |
||
} | ||
return S_OK; | ||
} | ||
|
||
CFTypeID CGPathGetTypeID() { | ||
return __CGPath::GetTypeID(); | ||
} | ||
|
@@ -159,8 +171,8 @@ static Boolean __CGPathEqual(CFTypeRef cf1, CFTypeRef cf2) { | |
__CGPath* path1 = (__CGPath*)cf1; | ||
__CGPath* path2 = (__CGPath*)cf2; | ||
|
||
path1->ClosePath(); | ||
path2->ClosePath(); | ||
RETURN_FALSE_IF_FAILED(path1->ClosePath()); | ||
RETURN_FALSE_IF_FAILED(path2->ClosePath()); | ||
|
||
// ID2D1 Geometries have no isEquals method. However, for two geometries to be equal they are both reported to contain the other. | ||
// Thus we must do two comparisons. | ||
|
@@ -212,7 +224,7 @@ CGMutablePathRef CGPathCreateMutableCopy(CGPathRef path) { | |
// In order to call stream and copy the contents of the original path into the | ||
// new copy we must close this path. | ||
// Otherwise the D2D calls will return that a bad state has been entered. | ||
path->ClosePath(); | ||
FAIL_FAST_IF_FAILED(path->ClosePath()); | ||
|
||
FAIL_FAST_IF_FAILED(path->GetPathGeometry()->Stream(mutableRet->GetGeometrySink().Get())); | ||
|
||
|
@@ -451,7 +463,7 @@ void CGPathAddPath(CGMutablePathRef path, const CGAffineTransform* transform, CG | |
RETURN_IF(!path || !toAdd); | ||
|
||
// Close the path we're adding and open the path we need to add to. | ||
toAdd->ClosePath(); | ||
FAIL_FAST_IF_FAILED(toAdd->ClosePath()); | ||
FAIL_FAST_IF_FAILED(path->PreparePathForEditing()); | ||
FAIL_FAST_IF_FAILED(toAdd->GetPathGeometry()->Stream(path->GetGeometrySink().Get())); | ||
} | ||
|
@@ -510,7 +522,9 @@ CGRect CGPathGetBoundingBox(CGPathRef path) { | |
|
||
D2D1_RECT_F bounds; | ||
|
||
path->ClosePath(); | ||
if (FAILED(path->ClosePath())) { | ||
return CGRectNull; | ||
} | ||
|
||
if (FAILED(path->GetPathGeometry()->GetBounds(D2D1::IdentityMatrix(), &bounds))) { | ||
return CGRectNull; | ||
|
@@ -530,7 +544,7 @@ bool CGPathIsEmpty(CGPathRef path) { | |
|
||
UINT32 count; | ||
|
||
path->ClosePath(); | ||
RETURN_FALSE_IF_FAILED(path->ClosePath()); | ||
|
||
RETURN_FALSE_IF_FAILED(path->GetPathGeometry()->GetFigureCount(&count)); | ||
return count == 0; | ||
|
@@ -673,7 +687,7 @@ bool CGPathContainsPoint(CGPathRef path, const CGAffineTransform* transform, CGP | |
|
||
BOOL containsPoint = FALSE; | ||
|
||
path->ClosePath(); | ||
RETURN_FALSE_IF_FAILED(path->ClosePath()); | ||
RETURN_FALSE_IF_FAILED(path->GetPathGeometry()->FillContainsPoint(_CGPointToD2D_F(point), D2D1::IdentityMatrix(), &containsPoint)); | ||
|
||
return (containsPoint ? true : false); | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -20,15 +20,21 @@ | |
using namespace Microsoft::WRL; | ||
|
||
// Private helper for creating a D2DFactory | ||
static ComPtr<ID2D1Factory> __createD2DFactory() { | ||
ComPtr<ID2D1Factory> d2dFactory; | ||
FAIL_FAST_IF_FAILED(D2D1CreateFactory(D2D1_FACTORY_TYPE_SINGLE_THREADED, __uuidof(ID2D1Factory), &d2dFactory)); | ||
return d2dFactory; | ||
static HRESULT __createD2DFactory(ID2D1Factory** factory) { | ||
return D2D1CreateFactory(D2D1_FACTORY_TYPE_SINGLE_THREADED, factory); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. If we are not using a magic static, there is no real point in having this be a separate function #Resolved |
||
} | ||
|
||
ComPtr<ID2D1Factory> _GetD2DFactoryInstance() { | ||
static ComPtr<ID2D1Factory> factory = __createD2DFactory(); | ||
return factory; | ||
HRESULT _CGGetD2DFactory(ID2D1Factory** factory) { | ||
static ComPtr<ID2D1Factory> sFactory; | ||
static HRESULT sHr; | ||
static dispatch_once_t dispatchToken; | ||
|
||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. If one static referring to another as part of its initialization is safe, then this should be totally fine. |
||
dispatch_once(&dispatchToken, ^{ | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. why did we go back to dispatch once? as mentioned before, dispatch_once doesn't buy you much except make code more dependent on a third party library and harder to read. I preferred the second helper function / you could do something with a c++ lambda if you are concerned about the "multi-return" / "return transforming" you need to do. That is a language feature that we can be more confident in than our pseudo hacked up libdispatch just to get a static initialized. #Resolved |
||
sHr = __createD2DFactory(&sFactory); | ||
}); | ||
sFactory.Get()->AddRef(); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. only CopyTo There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
!!!!!!!!!ACHTUNG!!!!!!!!! manual AddRef / Release of Com Objects almost always speaks to a design patten issue. Especially when used in conjuction with ComPtr methods in other places. What is the goal here? You are AddRefing once and then again in the CopyTo. Hooray extra amazing leakage. #Resolved There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. #1307 (comment) 😉 #Closed There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. achtung? atchung sounds like an old heavy cash register #Closed There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. |
||
*factory = sFactory.Get(); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. prefer |
||
return sHr; | ||
} | ||
|
||
static ComPtr<IWICImagingFactory> __createWICFactory() { | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -412,7 +412,7 @@ CGPathRef newPathForRoundRect(CGRect rect, CGFloat radius) { | |
return path; | ||
} | ||
|
||
TEST(CGPath, CGPathContainsPointOutsideRect) { | ||
DISABLED_TEST(CGPath, CGPathContainsPointOutsideRect) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
These tests should have been disabled initially since they involve work with arcs/curves. These will be re-enabled then. |
||
CGFloat originX = 10.0f; | ||
CGFloat originY = 20.0f; | ||
CGFloat pathWidth = 100.0f; | ||
|
@@ -438,7 +438,7 @@ CGPathRef newPathForRoundRect(CGRect rect, CGFloat radius) { | |
EXPECT_FALSE(test); | ||
} | ||
|
||
TEST(CGPath, CGPathContainsPointInsideRectOutsidePath) { | ||
DISABLED_TEST(CGPath, CGPathContainsPointInsideRectOutsidePath) { | ||
CGFloat originX = 10.0f; | ||
CGFloat originY = 20.0f; | ||
CGFloat pathWidth = 100.0f; | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Please no, THROW_IF_FAILED() is marginally better. FAIL_FAST is essentially abort, and as framework we should seldom be using it. And in other places too, per our offline discussion.