Add stroking a path with a gradient on OSX, and also gradient transforms

This commit is contained in:
Robin Dunn 2019-08-07 11:37:18 -07:00 committed by Robin Dunn
parent 2738565c0d
commit 239b8a17b1

View File

@ -302,17 +302,221 @@ protected :
int m_hatch; int m_hatch;
}; };
class wxMacCoreGraphicsPenData : public wxGraphicsObjectRefData // ----------------------------------------------------------------------------
// Base class for information shared between pens and brushes, basically just
// the things needed for gradient support.
class wxMacCoreGraphicsPenBrushDataBase : public wxGraphicsObjectRefData
{
public:
wxMacCoreGraphicsPenBrushDataBase(wxGraphicsRenderer* renderer);
~wxMacCoreGraphicsPenBrushDataBase();
void CreateLinearGradientShading(wxDouble x1, wxDouble y1,
wxDouble x2, wxDouble y2,
const wxGraphicsGradientStops& stops,
const wxGraphicsMatrix& matrix);
void CreateRadialGradientShading(wxDouble xo, wxDouble yo,
wxDouble xc, wxDouble yc, wxDouble radius,
const wxGraphicsGradientStops& stops,
const wxGraphicsMatrix& matrix);
virtual bool IsShading() { return m_isShading; }
CGShadingRef GetShading() { return m_shading; }
wxGraphicsMatrix& GetMatrix() { return m_shadingMatrix; }
protected:
void Init();
CGFunctionRef CreateGradientFunction(const wxGraphicsGradientStops& stops);
static void CalculateShadingValues (void *info, const CGFloat *in, CGFloat *out);
bool m_isShading;
CGFunctionRef m_gradientFunction;
CGShadingRef m_shading;
wxGraphicsMatrix m_shadingMatrix;
// information about a single gradient component
struct GradientComponent
{
CGFloat pos;
CGFloat red;
CGFloat green;
CGFloat blue;
CGFloat alpha;
};
// and information about all of them
struct GradientComponents
{
GradientComponents()
{
count = 0;
comps = NULL;
}
void Init(unsigned count_)
{
count = count_;
comps = new GradientComponent[count];
}
~GradientComponents()
{
delete [] comps;
}
unsigned count;
GradientComponent *comps;
};
GradientComponents m_gradientComponents;
};
wxMacCoreGraphicsPenBrushDataBase::wxMacCoreGraphicsPenBrushDataBase(wxGraphicsRenderer* renderer)
: wxGraphicsObjectRefData(renderer)
{
Init();
}
wxMacCoreGraphicsPenBrushDataBase::~wxMacCoreGraphicsPenBrushDataBase()
{
if ( m_shading )
CGShadingRelease(m_shading);
if( m_gradientFunction )
CGFunctionRelease(m_gradientFunction);
}
void
wxMacCoreGraphicsPenBrushDataBase::Init()
{
m_gradientFunction = NULL;
m_shading = NULL;
m_isShading = false;
}
void
wxMacCoreGraphicsPenBrushDataBase::CreateLinearGradientShading(
wxDouble x1, wxDouble y1,
wxDouble x2, wxDouble y2,
const wxGraphicsGradientStops& stops,
const wxGraphicsMatrix& matrix)
{
m_gradientFunction = CreateGradientFunction(stops);
m_shading = CGShadingCreateAxial( wxMacGetGenericRGBColorSpace(),
CGPointMake((CGFloat) x1, (CGFloat) y1),
CGPointMake((CGFloat) x2, (CGFloat) y2),
m_gradientFunction, true, true );
m_isShading = true;
m_shadingMatrix = matrix;
}
void
wxMacCoreGraphicsPenBrushDataBase::CreateRadialGradientShading(
wxDouble xo, wxDouble yo,
wxDouble xc, wxDouble yc,
wxDouble radius,
const wxGraphicsGradientStops& stops,
const wxGraphicsMatrix& matrix)
{
m_gradientFunction = CreateGradientFunction(stops);
m_shading = CGShadingCreateRadial( wxMacGetGenericRGBColorSpace(),
CGPointMake((CGFloat) xo, (CGFloat) yo), 0,
CGPointMake((CGFloat) xc, (CGFloat) yc), (CGFloat) radius,
m_gradientFunction, true, true );
m_isShading = true;
m_shadingMatrix = matrix;
}
void wxMacCoreGraphicsPenBrushDataBase::CalculateShadingValues(void *info, const CGFloat *in, CGFloat *out)
{
const GradientComponents& stops = *(GradientComponents*) info ;
CGFloat f = *in;
if (f <= 0.0)
{
// Start
out[0] = stops.comps[0].red;
out[1] = stops.comps[0].green;
out[2] = stops.comps[0].blue;
out[3] = stops.comps[0].alpha;
}
else if (f >= 1.0)
{
// end
out[0] = stops.comps[stops.count - 1].red;
out[1] = stops.comps[stops.count - 1].green;
out[2] = stops.comps[stops.count - 1].blue;
out[3] = stops.comps[stops.count - 1].alpha;
}
else
{
// Find first component with position greater than f
unsigned i;
for ( i = 0; i < stops.count; i++ )
{
if (stops.comps[i].pos > f)
break;
}
// Interpolated between stops
CGFloat diff = (f - stops.comps[i-1].pos);
CGFloat range = (stops.comps[i].pos - stops.comps[i-1].pos);
CGFloat fact = diff / range;
out[0] = stops.comps[i - 1].red + (stops.comps[i].red - stops.comps[i - 1].red) * fact;
out[1] = stops.comps[i - 1].green + (stops.comps[i].green - stops.comps[i - 1].green) * fact;
out[2] = stops.comps[i - 1].blue + (stops.comps[i].blue - stops.comps[i - 1].blue) * fact;
out[3] = stops.comps[i - 1].alpha + (stops.comps[i].alpha - stops.comps[i - 1].alpha) * fact;
}
}
CGFunctionRef
wxMacCoreGraphicsPenBrushDataBase::CreateGradientFunction(const wxGraphicsGradientStops& stops)
{
static const CGFunctionCallbacks callbacks = { 0, &CalculateShadingValues, NULL };
static const CGFloat input_value_range [2] = { 0, 1 };
static const CGFloat output_value_ranges [8] = { 0, 1, 0, 1, 0, 1, 0, 1 };
m_gradientComponents.Init(stops.GetCount());
for ( unsigned i = 0; i < m_gradientComponents.count; i++ )
{
const wxGraphicsGradientStop stop = stops.Item(i);
m_gradientComponents.comps[i].pos = stop.GetPosition();
const wxColour col = stop.GetColour();
m_gradientComponents.comps[i].red = (CGFloat) (col.Red() / 255.0);
m_gradientComponents.comps[i].green = (CGFloat) (col.Green() / 255.0);
m_gradientComponents.comps[i].blue = (CGFloat) (col.Blue() / 255.0);
m_gradientComponents.comps[i].alpha = (CGFloat) (col.Alpha() / 255.0);
}
return CGFunctionCreate ( &m_gradientComponents, 1,
input_value_range,
4,
output_value_ranges,
&callbacks);
}
//-----------------------------------------------------------------------------
// Pen data
class wxMacCoreGraphicsPenData : public wxMacCoreGraphicsPenBrushDataBase
{ {
public: public:
wxMacCoreGraphicsPenData( wxGraphicsRenderer* renderer, const wxGraphicsPenInfo& info ); wxMacCoreGraphicsPenData( wxGraphicsRenderer* renderer, const wxGraphicsPenInfo& info );
~wxMacCoreGraphicsPenData(); ~wxMacCoreGraphicsPenData();
void Init();
virtual void Apply( wxGraphicsContext* context ); virtual void Apply( wxGraphicsContext* context );
virtual wxDouble GetWidth() { return m_width; } virtual wxDouble GetWidth() { return m_width; }
protected : protected :
void Init();
CGLineCap m_cap; CGLineCap m_cap;
wxCFRef<CGColorRef> m_color; wxCFRef<CGColorRef> m_color;
wxCFRef<CGColorSpaceRef> m_colorSpace; wxCFRef<CGColorSpaceRef> m_colorSpace;
@ -332,7 +536,7 @@ protected :
wxMacCoreGraphicsPenData::wxMacCoreGraphicsPenData( wxGraphicsRenderer* renderer, wxMacCoreGraphicsPenData::wxMacCoreGraphicsPenData( wxGraphicsRenderer* renderer,
const wxGraphicsPenInfo& info ) const wxGraphicsPenInfo& info )
: wxGraphicsObjectRefData( renderer ) : wxMacCoreGraphicsPenBrushDataBase( renderer )
{ {
Init(); Init();
@ -457,7 +661,7 @@ wxMacCoreGraphicsPenData::wxMacCoreGraphicsPenData( wxGraphicsRenderer* renderer
m_patternColorComponents[0] = (CGFloat) (info.GetColour().Red() / 255.0); m_patternColorComponents[0] = (CGFloat) (info.GetColour().Red() / 255.0);
m_patternColorComponents[1] = (CGFloat) (info.GetColour().Green() / 255.0); m_patternColorComponents[1] = (CGFloat) (info.GetColour().Green() / 255.0);
m_patternColorComponents[2] = (CGFloat) (info.GetColour().Blue() / 255.0); m_patternColorComponents[2] = (CGFloat) (info.GetColour().Blue() / 255.0);
m_patternColorComponents[3] = (CGFloat) (info.GetColour().Alpha() / 255.0); m_patternColorComponents[3] = (CGFloat) (info.GetColour().Alpha() / 255.0);
} }
break; break;
} }
@ -466,6 +670,28 @@ wxMacCoreGraphicsPenData::wxMacCoreGraphicsPenData( wxGraphicsRenderer* renderer
// force the line cap, otherwise we get artifacts (overlaps) and just solid lines // force the line cap, otherwise we get artifacts (overlaps) and just solid lines
m_cap = kCGLineCapButt; m_cap = kCGLineCapButt;
} }
switch ( info.GetGradientType() )
{
case wxGRADIENT_NONE:
break;
case wxGRADIENT_LINEAR:
CreateLinearGradientShading(info.GetX1(), info.GetY1(),
info.GetX2(), info.GetY2(),
info.GetStops(),
info.GetMatrix());
break;
case wxGRADIENT_RADIAL:
CreateRadialGradientShading(info.GetXO(), info.GetYO(),
info.GetXC(), info.GetYC(),
info.GetRadius(),
info.GetStops(),
info.GetMatrix());
break;
}
} }
wxMacCoreGraphicsPenData::~wxMacCoreGraphicsPenData() wxMacCoreGraphicsPenData::~wxMacCoreGraphicsPenData()
@ -595,7 +821,7 @@ wxMacCoreGraphicsColour::wxMacCoreGraphicsColour( const wxBrush &brush )
} }
} }
class wxMacCoreGraphicsBrushData : public wxGraphicsObjectRefData class wxMacCoreGraphicsBrushData : public wxMacCoreGraphicsPenBrushDataBase
{ {
public: public:
wxMacCoreGraphicsBrushData( wxGraphicsRenderer* renderer ); wxMacCoreGraphicsBrushData( wxGraphicsRenderer* renderer );
@ -603,117 +829,24 @@ public:
~wxMacCoreGraphicsBrushData (); ~wxMacCoreGraphicsBrushData ();
virtual void Apply( wxGraphicsContext* context ); virtual void Apply( wxGraphicsContext* context );
void CreateLinearGradientBrush(wxDouble x1, wxDouble y1,
wxDouble x2, wxDouble y2,
const wxGraphicsGradientStops& stops,
const wxGraphicsMatrix& matrix);
void CreateRadialGradientBrush(wxDouble xo, wxDouble yo,
wxDouble xc, wxDouble yc, wxDouble radius,
const wxGraphicsGradientStops& stops,
const wxGraphicsMatrix& matrix);
virtual bool IsShading() { return m_isShading; }
CGShadingRef GetShading() { return m_shading; }
protected: protected:
CGFunctionRef CreateGradientFunction(const wxGraphicsGradientStops& stops);
static void CalculateShadingValues (void *info, const CGFloat *in, CGFloat *out);
virtual void Init();
wxMacCoreGraphicsColour m_cgColor; wxMacCoreGraphicsColour m_cgColor;
bool m_isShading;
CGFunctionRef m_gradientFunction;
CGShadingRef m_shading;
// information about a single gradient component
struct GradientComponent
{
CGFloat pos;
CGFloat red;
CGFloat green;
CGFloat blue;
CGFloat alpha;
};
// and information about all of them
struct GradientComponents
{
GradientComponents()
{
count = 0;
comps = NULL;
}
void Init(unsigned count_)
{
count = count_;
comps = new GradientComponent[count];
}
~GradientComponents()
{
delete [] comps;
}
unsigned count;
GradientComponent *comps;
};
GradientComponents m_gradientComponents;
}; };
wxMacCoreGraphicsBrushData::wxMacCoreGraphicsBrushData( wxGraphicsRenderer* renderer) : wxGraphicsObjectRefData( renderer ) wxMacCoreGraphicsBrushData::wxMacCoreGraphicsBrushData( wxGraphicsRenderer* renderer) :
wxMacCoreGraphicsPenBrushDataBase( renderer )
{ {
Init();
} }
void wxMacCoreGraphicsBrushData::wxMacCoreGraphicsBrushData(wxGraphicsRenderer* renderer, const wxBrush &brush) :
wxMacCoreGraphicsBrushData::CreateLinearGradientBrush(wxDouble x1, wxDouble y1, wxMacCoreGraphicsPenBrushDataBase( renderer ),
wxDouble x2, wxDouble y2,
const wxGraphicsGradientStops& stops,
const wxGraphicsMatrix& matrix)
{
m_gradientFunction = CreateGradientFunction(stops);
m_shading = CGShadingCreateAxial( wxMacGetGenericRGBColorSpace(), CGPointMake((CGFloat) x1, (CGFloat) y1),
CGPointMake((CGFloat) x2,(CGFloat) y2), m_gradientFunction, true, true ) ;
m_isShading = true ;
}
void
wxMacCoreGraphicsBrushData::CreateRadialGradientBrush(wxDouble xo, wxDouble yo,
wxDouble xc, wxDouble yc,
wxDouble radius,
const wxGraphicsGradientStops& stops,
const wxGraphicsMatrix& matrix)
{
m_gradientFunction = CreateGradientFunction(stops);
m_shading = CGShadingCreateRadial( wxMacGetGenericRGBColorSpace(), CGPointMake((CGFloat) xo,(CGFloat) yo), 0,
CGPointMake((CGFloat) xc,(CGFloat) yc), (CGFloat) radius, m_gradientFunction, true, true ) ;
m_isShading = true ;
}
wxMacCoreGraphicsBrushData::wxMacCoreGraphicsBrushData(wxGraphicsRenderer* renderer, const wxBrush &brush) : wxGraphicsObjectRefData( renderer ),
m_cgColor( brush ) m_cgColor( brush )
{ {
Init();
} }
wxMacCoreGraphicsBrushData::~wxMacCoreGraphicsBrushData() wxMacCoreGraphicsBrushData::~wxMacCoreGraphicsBrushData()
{ {
if ( m_shading )
CGShadingRelease(m_shading);
if( m_gradientFunction )
CGFunctionRelease(m_gradientFunction);
}
void wxMacCoreGraphicsBrushData::Init()
{
m_gradientFunction = NULL;
m_shading = NULL;
m_isShading = false;
} }
void wxMacCoreGraphicsBrushData::Apply( wxGraphicsContext* context ) void wxMacCoreGraphicsBrushData::Apply( wxGraphicsContext* context )
@ -730,77 +863,6 @@ void wxMacCoreGraphicsBrushData::Apply( wxGraphicsContext* context )
} }
} }
void wxMacCoreGraphicsBrushData::CalculateShadingValues (void *info, const CGFloat *in, CGFloat *out)
{
const GradientComponents& stops = *(GradientComponents*) info ;
CGFloat f = *in;
if (f <= 0.0)
{
// Start
out[0] = stops.comps[0].red;
out[1] = stops.comps[0].green;
out[2] = stops.comps[0].blue;
out[3] = stops.comps[0].alpha;
}
else if (f >= 1.0)
{
// end
out[0] = stops.comps[stops.count - 1].red;
out[1] = stops.comps[stops.count - 1].green;
out[2] = stops.comps[stops.count - 1].blue;
out[3] = stops.comps[stops.count - 1].alpha;
}
else
{
// Find first component with position greater than f
unsigned i;
for ( i = 0; i < stops.count; i++ )
{
if (stops.comps[i].pos > f)
break;
}
// Interpolated between stops
CGFloat diff = (f - stops.comps[i-1].pos);
CGFloat range = (stops.comps[i].pos - stops.comps[i-1].pos);
CGFloat fact = diff / range;
out[0] = stops.comps[i - 1].red + (stops.comps[i].red - stops.comps[i - 1].red) * fact;
out[1] = stops.comps[i - 1].green + (stops.comps[i].green - stops.comps[i - 1].green) * fact;
out[2] = stops.comps[i - 1].blue + (stops.comps[i].blue - stops.comps[i - 1].blue) * fact;
out[3] = stops.comps[i - 1].alpha + (stops.comps[i].alpha - stops.comps[i - 1].alpha) * fact;
}
}
CGFunctionRef
wxMacCoreGraphicsBrushData::CreateGradientFunction(const wxGraphicsGradientStops& stops)
{
static const CGFunctionCallbacks callbacks = { 0, &CalculateShadingValues, NULL };
static const CGFloat input_value_range [2] = { 0, 1 };
static const CGFloat output_value_ranges [8] = { 0, 1, 0, 1, 0, 1, 0, 1 };
m_gradientComponents.Init(stops.GetCount());
for ( unsigned i = 0; i < m_gradientComponents.count; i++ )
{
const wxGraphicsGradientStop stop = stops.Item(i);
m_gradientComponents.comps[i].pos = stop.GetPosition();
const wxColour col = stop.GetColour();
m_gradientComponents.comps[i].red = (CGFloat) (col.Red() / 255.0);
m_gradientComponents.comps[i].green = (CGFloat) (col.Green() / 255.0);
m_gradientComponents.comps[i].blue = (CGFloat) (col.Blue() / 255.0);
m_gradientComponents.comps[i].alpha = (CGFloat) (col.Alpha() / 255.0);
}
return CGFunctionCreate ( &m_gradientComponents, 1,
input_value_range,
4,
output_value_ranges,
&callbacks);
}
// //
// Font // Font
@ -833,8 +895,9 @@ private :
#endif #endif
}; };
wxMacCoreGraphicsFontData::wxMacCoreGraphicsFontData(wxGraphicsRenderer* renderer, const wxFont &font, const wxColour& col) : wxGraphicsObjectRefData( renderer ) wxMacCoreGraphicsFontData::wxMacCoreGraphicsFontData(wxGraphicsRenderer* renderer, const wxFont &font, const wxColour& col)
, m_colour(col) : wxGraphicsObjectRefData( renderer ),
m_colour(col)
{ {
m_underlined = font.GetUnderlined(); m_underlined = font.GetUnderlined();
m_strikethrough = font.GetStrikethrough(); m_strikethrough = font.GetStrikethrough();
@ -1468,7 +1531,7 @@ private:
// device context implementation // device context implementation
// //
// more and more of the dc functionality should be implemented by calling // more and more of the dc functionality should be implemented by calling
// the appropricate wxMacCoreGraphicsContext, but we will have to do that step by step // the appropriate wxMacCoreGraphicsContext, but we will have to do that step by step
// also coordinate conversions should be moved to native matrix ops // also coordinate conversions should be moved to native matrix ops
//----------------------------------------------------------------------------- //-----------------------------------------------------------------------------
@ -2056,10 +2119,34 @@ void wxMacCoreGraphicsContext::StrokePath( const wxGraphicsPath &path )
return; return;
wxQuartzOffsetHelper helper( m_cgContext , ShouldOffset() ); wxQuartzOffsetHelper helper( m_cgContext , ShouldOffset() );
wxMacCoreGraphicsPenData* penData = (wxMacCoreGraphicsPenData*)m_pen.GetRefData();
((wxMacCoreGraphicsPenData*)m_pen.GetRefData())->Apply(this); penData->Apply(this);
CGContextAddPath( m_cgContext , (CGPathRef) path.GetNativePath() );
CGContextStrokePath( m_cgContext ); if (penData->IsShading())
{
// To stroke with a gradient we first have to turn the path into a path
// that is essentially the outline of the original stroke, and then fill
// that path.
CGContextSaveGState( m_cgContext );
CGContextAddPath( m_cgContext, (CGPathRef)path.GetNativePath() );
CGContextReplacePathWithStrokedPath(m_cgContext);
CGContextClip( m_cgContext );
// Apply the gradient's transform, if there is one.
if (! penData->GetMatrix().IsNull() )
{
wxGraphicsMatrix m = penData->GetMatrix();
m.Invert();
ConcatTransform(m);
}
CGContextDrawShading( m_cgContext, penData->GetShading() );
CGContextRestoreGState( m_cgContext);
}
else
{
CGContextAddPath( m_cgContext, (CGPathRef)path.GetNativePath() );
CGContextStrokePath( m_cgContext );
}
CheckInvariants(); CheckInvariants();
} }
@ -2072,7 +2159,8 @@ void wxMacCoreGraphicsContext::DrawPath( const wxGraphicsPath &path , wxPolygonF
if (m_composition == wxCOMPOSITION_DEST) if (m_composition == wxCOMPOSITION_DEST)
return; return;
if ( !m_brush.IsNull() && ((wxMacCoreGraphicsBrushData*)m_brush.GetRefData())->IsShading() ) if ( (!m_brush.IsNull() && ((wxMacCoreGraphicsBrushData*)m_brush.GetRefData())->IsShading()) ||
(!m_pen.IsNull() && ((wxMacCoreGraphicsPenData*)m_pen.GetRefData())->IsShading()) )
{ {
// when using shading, we cannot draw pen and brush at the same time // when using shading, we cannot draw pen and brush at the same time
// revert to the base implementation of first filling and then stroking // revert to the base implementation of first filling and then stroking
@ -2130,12 +2218,21 @@ void wxMacCoreGraphicsContext::FillPath( const wxGraphicsPath &path , wxPolygonF
if (m_composition == wxCOMPOSITION_DEST) if (m_composition == wxCOMPOSITION_DEST)
return; return;
if ( ((wxMacCoreGraphicsBrushData*)m_brush.GetRefData())->IsShading() ) wxMacCoreGraphicsBrushData* brushData = (wxMacCoreGraphicsBrushData*)m_brush.GetRefData();
if ( brushData->IsShading() )
{ {
CGContextSaveGState( m_cgContext ); CGContextSaveGState( m_cgContext );
CGContextAddPath( m_cgContext , (CGPathRef) path.GetNativePath() ); CGContextAddPath( m_cgContext , (CGPathRef) path.GetNativePath() );
CGContextClip( m_cgContext ); CGContextClip( m_cgContext );
CGContextDrawShading( m_cgContext, ((wxMacCoreGraphicsBrushData*)m_brush.GetRefData())->GetShading() ); // Apply the gradient's transform, if there is one.
if (! brushData->GetMatrix().IsNull() )
{
wxGraphicsMatrix m = brushData->GetMatrix();
m.Invert();
ConcatTransform(m);
}
CGContextDrawShading( m_cgContext, brushData->GetShading() );
CGContextRestoreGState( m_cgContext); CGContextRestoreGState( m_cgContext);
} }
else else
@ -2935,7 +3032,7 @@ wxMacCoreGraphicsRenderer::CreateLinearGradientBrush(wxDouble x1, wxDouble y1,
{ {
wxGraphicsBrush p; wxGraphicsBrush p;
wxMacCoreGraphicsBrushData* d = new wxMacCoreGraphicsBrushData( this ); wxMacCoreGraphicsBrushData* d = new wxMacCoreGraphicsBrushData( this );
d->CreateLinearGradientBrush(x1, y1, x2, y2, stops, matrix); d->CreateLinearGradientShading(x1, y1, x2, y2, stops, matrix);
p.SetRefData(d); p.SetRefData(d);
return p; return p;
} }
@ -2949,7 +3046,7 @@ wxMacCoreGraphicsRenderer::CreateRadialGradientBrush(wxDouble xo, wxDouble yo,
{ {
wxGraphicsBrush p; wxGraphicsBrush p;
wxMacCoreGraphicsBrushData* d = new wxMacCoreGraphicsBrushData( this ); wxMacCoreGraphicsBrushData* d = new wxMacCoreGraphicsBrushData( this );
d->CreateRadialGradientBrush(xo, yo, xc, yc, radius, stops, matrix); d->CreateRadialGradientShading(xo, yo, xc, yc, radius, stops, matrix);
p.SetRefData(d); p.SetRefData(d);
return p; return p;
} }