mirror of
https://gitlab.winehq.org/wine/wine-gecko.git
synced 2024-09-13 09:24:08 -07:00
Bug 805831: Add a cache for border corner gradients. r=joedrew
This commit is contained in:
parent
8b803ab863
commit
e52a32f0ff
@ -119,6 +119,12 @@ public:
|
||||
return newColor;
|
||||
}
|
||||
|
||||
uint32_t ToABGR() const
|
||||
{
|
||||
return uint32_t(r * 255.0f) | uint32_t(g * 255.0f) << 8 |
|
||||
uint32_t(b * 255.0f) << 16 | uint32_t(a * 255.0f) << 24;
|
||||
}
|
||||
|
||||
Float r, g, b, a;
|
||||
};
|
||||
|
||||
|
@ -466,6 +466,7 @@ void nsCSSRendering::Init()
|
||||
NS_ASSERTION(!gInlineBGData, "Init called twice");
|
||||
gInlineBGData = new InlineBackgroundData();
|
||||
gGradientCache = new GradientCache();
|
||||
nsCSSBorderRenderer::Init();
|
||||
}
|
||||
|
||||
// Clean up any global variables used by nsCSSRendering.
|
||||
@ -475,6 +476,7 @@ void nsCSSRendering::Shutdown()
|
||||
gInlineBGData = nullptr;
|
||||
delete gGradientCache;
|
||||
gGradientCache = nullptr;
|
||||
nsCSSBorderRenderer::Shutdown();
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -26,11 +26,154 @@
|
||||
#include "nsINameSpaceManager.h"
|
||||
#include "nsBlockFrame.h"
|
||||
#include "sampler.h"
|
||||
#include "nsExpirationTracker.h"
|
||||
|
||||
#include "gfxContext.h"
|
||||
|
||||
#include "nsCSSRenderingBorders.h"
|
||||
|
||||
#include "mozilla/gfx/2D.h"
|
||||
#include "gfx2DGlue.h"
|
||||
|
||||
using namespace mozilla;
|
||||
using namespace mozilla::gfx;
|
||||
|
||||
struct BorderGradientCacheKey : public PLDHashEntryHdr {
|
||||
typedef const BorderGradientCacheKey& KeyType;
|
||||
typedef const BorderGradientCacheKey* KeyTypePointer;
|
||||
|
||||
enum { ALLOW_MEMMOVE = true };
|
||||
|
||||
const uint32_t mColor1;
|
||||
const uint32_t mColor2;
|
||||
const BackendType mBackendType;
|
||||
|
||||
BorderGradientCacheKey(const Color& aColor1, const Color& aColor2,
|
||||
BackendType aBackendType)
|
||||
: mColor1(aColor1.ToABGR()), mColor2(aColor2.ToABGR())
|
||||
, mBackendType(aBackendType)
|
||||
{ }
|
||||
|
||||
BorderGradientCacheKey(const BorderGradientCacheKey* aOther)
|
||||
: mColor1(aOther->mColor1), mColor2(aOther->mColor2)
|
||||
, mBackendType(aOther->mBackendType)
|
||||
{ }
|
||||
|
||||
static PLDHashNumber
|
||||
HashKey(const KeyTypePointer aKey)
|
||||
{
|
||||
PLDHashNumber hash = 0;
|
||||
hash = AddToHash(hash, aKey->mColor1);
|
||||
hash = AddToHash(hash, aKey->mColor2);
|
||||
hash = AddToHash(hash, aKey->mBackendType);
|
||||
return hash;
|
||||
}
|
||||
|
||||
bool KeyEquals(KeyTypePointer aKey) const
|
||||
{
|
||||
return (aKey->mColor1 == mColor1) &&
|
||||
(aKey->mColor2 == mColor2) &&
|
||||
(aKey->mBackendType == mBackendType);
|
||||
}
|
||||
static KeyTypePointer KeyToPointer(KeyType aKey)
|
||||
{
|
||||
return &aKey;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* This class is what is cached. It need to be allocated in an object separated
|
||||
* to the cache entry to be able to be tracked by the nsExpirationTracker.
|
||||
* */
|
||||
struct BorderGradientCacheData {
|
||||
BorderGradientCacheData(GradientStops* aStops, const BorderGradientCacheKey& aKey)
|
||||
: mStops(aStops), mKey(aKey)
|
||||
{}
|
||||
|
||||
BorderGradientCacheData(const BorderGradientCacheData& aOther)
|
||||
: mStops(aOther.mStops),
|
||||
mKey(aOther.mKey)
|
||||
{ }
|
||||
|
||||
nsExpirationState *GetExpirationState() {
|
||||
return &mExpirationState;
|
||||
}
|
||||
|
||||
nsExpirationState mExpirationState;
|
||||
RefPtr<GradientStops> mStops;
|
||||
BorderGradientCacheKey mKey;
|
||||
};
|
||||
|
||||
/**
|
||||
* This class implements a cache with no maximum size, that retains the
|
||||
* gradient stops used to draw border corners.
|
||||
*
|
||||
* The key is formed by the two gradient stops, they're always both located
|
||||
* at an offset of 0.5. So they can generously be reused. The key also includes
|
||||
* the backend type a certain gradient was created for.
|
||||
*
|
||||
* An entry stays in the cache as long as it is used often.
|
||||
*
|
||||
* This code was pretty bluntly stolen and modified from nsCSSRendering.
|
||||
*/
|
||||
class BorderGradientCache MOZ_FINAL : public nsExpirationTracker<BorderGradientCacheData,4>
|
||||
{
|
||||
public:
|
||||
BorderGradientCache()
|
||||
: nsExpirationTracker<BorderGradientCacheData, 4>(GENERATION_MS)
|
||||
{
|
||||
mHashEntries.Init();
|
||||
mTimerPeriod = GENERATION_MS;
|
||||
}
|
||||
|
||||
virtual void NotifyExpired(BorderGradientCacheData* aObject)
|
||||
{
|
||||
// This will free the gfxPattern.
|
||||
RemoveObject(aObject);
|
||||
mHashEntries.Remove(aObject->mKey);
|
||||
}
|
||||
|
||||
BorderGradientCacheData* Lookup(const Color& aColor1, const Color& aColor2,
|
||||
BackendType aBackendType)
|
||||
{
|
||||
BorderGradientCacheData* gradient =
|
||||
mHashEntries.Get(BorderGradientCacheKey(aColor1, aColor2, aBackendType));
|
||||
|
||||
if (gradient) {
|
||||
MarkUsed(gradient);
|
||||
}
|
||||
|
||||
return gradient;
|
||||
}
|
||||
|
||||
// Returns true if we successfully register the gradient in the cache, false
|
||||
// otherwise.
|
||||
bool RegisterEntry(BorderGradientCacheData* aValue)
|
||||
{
|
||||
nsresult rv = AddObject(aValue);
|
||||
if (NS_FAILED(rv)) {
|
||||
// We are OOM, and we cannot track this object. We don't want stall
|
||||
// entries in the hash table (since the expiration tracker is responsible
|
||||
// for removing the cache entries), so we avoid putting that entry in the
|
||||
// table, which is a good things considering we are short on memory
|
||||
// anyway, we probably don't want to retain things.
|
||||
return false;
|
||||
}
|
||||
mHashEntries.Put(aValue->mKey, aValue);
|
||||
return true;
|
||||
}
|
||||
|
||||
protected:
|
||||
uint32_t mTimerPeriod;
|
||||
static const uint32_t GENERATION_MS = 4000;
|
||||
/**
|
||||
* FIXME use nsTHashtable to avoid duplicating the BorderGradientCacheKey.
|
||||
* This is analogous to the issue for the generic gradient cache:
|
||||
* https://bugzilla.mozilla.org/show_bug.cgi?id=785794
|
||||
*/
|
||||
nsClassHashtable<BorderGradientCacheKey, BorderGradientCacheData> mHashEntries;
|
||||
};
|
||||
|
||||
/**
|
||||
* nsCSSRendering::PaintBorder
|
||||
* nsCSSRendering::PaintOutline
|
||||
@ -122,6 +265,8 @@ typedef enum {
|
||||
CORNER_DOT
|
||||
} CornerStyle;
|
||||
|
||||
static BorderGradientCache* gBorderGradientCache = nullptr;
|
||||
|
||||
nsCSSBorderRenderer::nsCSSBorderRenderer(int32_t aAppUnitsPerPixel,
|
||||
gfxContext* aDestContext,
|
||||
gfxRect& aOuterRect,
|
||||
@ -162,6 +307,18 @@ nsCSSBorderRenderer::nsCSSBorderRenderer(int32_t aAppUnitsPerPixel,
|
||||
mAvoidStroke = false;
|
||||
}
|
||||
|
||||
void
|
||||
nsCSSBorderRenderer::Init()
|
||||
{
|
||||
gBorderGradientCache = new BorderGradientCache();
|
||||
}
|
||||
|
||||
void
|
||||
nsCSSBorderRenderer::Shutdown()
|
||||
{
|
||||
delete gBorderGradientCache;
|
||||
}
|
||||
|
||||
/* static */ void
|
||||
nsCSSBorderRenderer::ComputeInnerRadii(const gfxCornerSizes& aRadii,
|
||||
const gfxFloat *aBorderSizes,
|
||||
@ -1062,7 +1219,7 @@ nsCSSBorderRenderer::CreateCornerGradient(mozilla::css::Corner aCorner,
|
||||
{ -1, -1 },
|
||||
{ +1, -1 },
|
||||
{ +1, +1 } };
|
||||
|
||||
|
||||
// Sides which form the 'width' and 'height' for the calculation of the angle
|
||||
// for our gradient.
|
||||
const int cornerWidth[4] = { 3, 1, 1, 3 };
|
||||
@ -1103,6 +1260,87 @@ nsCSSBorderRenderer::CreateCornerGradient(mozilla::css::Corner aCorner,
|
||||
return pattern.forget();
|
||||
}
|
||||
|
||||
TemporaryRef<GradientStops>
|
||||
nsCSSBorderRenderer::CreateCornerGradient(mozilla::css::Corner aCorner,
|
||||
const gfxRGBA &aFirstColor,
|
||||
const gfxRGBA &aSecondColor,
|
||||
DrawTarget *aDT,
|
||||
Point &aPoint1,
|
||||
Point &aPoint2)
|
||||
{
|
||||
typedef struct { gfxFloat a, b; } twoFloats;
|
||||
|
||||
const twoFloats gradientCoeff[4] = { { -1, +1 },
|
||||
{ -1, -1 },
|
||||
{ +1, -1 },
|
||||
{ +1, +1 } };
|
||||
|
||||
// Sides which form the 'width' and 'height' for the calculation of the angle
|
||||
// for our gradient.
|
||||
const int cornerWidth[4] = { 3, 1, 1, 3 };
|
||||
const int cornerHeight[4] = { 0, 0, 2, 2 };
|
||||
|
||||
gfxPoint cornerOrigin = mOuterRect.AtCorner(aCorner);
|
||||
|
||||
gfxPoint pat1, pat2;
|
||||
pat1.x = cornerOrigin.x +
|
||||
mBorderWidths[cornerHeight[aCorner]] * gradientCoeff[aCorner].a;
|
||||
pat1.y = cornerOrigin.y +
|
||||
mBorderWidths[cornerWidth[aCorner]] * gradientCoeff[aCorner].b;
|
||||
pat2.x = cornerOrigin.x -
|
||||
mBorderWidths[cornerHeight[aCorner]] * gradientCoeff[aCorner].a;
|
||||
pat2.y = cornerOrigin.y -
|
||||
mBorderWidths[cornerWidth[aCorner]] * gradientCoeff[aCorner].b;
|
||||
|
||||
float gradientOffset = 0.25 / sqrt(pow(mBorderWidths[cornerHeight[aCorner]], 2) +
|
||||
pow(mBorderWidths[cornerHeight[aCorner]], 2));
|
||||
|
||||
aPoint1 = Point(pat1.x, pat1.y);
|
||||
aPoint2 = Point(pat2.x, pat2.y);
|
||||
|
||||
Color firstColor = ToColor(aFirstColor);
|
||||
Color secondColor = ToColor(aSecondColor);
|
||||
|
||||
BorderGradientCacheData *data =
|
||||
gBorderGradientCache->Lookup(firstColor, secondColor, aDT->GetType());
|
||||
|
||||
if (!data) {
|
||||
// Having two corners, both with reversed color stops is pretty common
|
||||
// for certain border types. Let's optimize it!
|
||||
data = gBorderGradientCache->Lookup(secondColor, firstColor, aDT->GetType());
|
||||
|
||||
if (data) {
|
||||
Point tmp = aPoint1;
|
||||
aPoint1 = aPoint2;
|
||||
aPoint2 = tmp;
|
||||
}
|
||||
}
|
||||
|
||||
RefPtr<GradientStops> stops;
|
||||
if (data) {
|
||||
stops = data->mStops;
|
||||
} else {
|
||||
GradientStop rawStops[2];
|
||||
// This is only guaranteed to give correct (and in some cases more correct)
|
||||
// rendering with the Direct2D Azure and Quartz Cairo backends. For other
|
||||
// cairo backends it could create un-antialiased border corner transitions
|
||||
// since that at least used to be pixman's behaviour for hard stops.
|
||||
rawStops[0].color = firstColor;
|
||||
rawStops[0].offset = 0.5;
|
||||
rawStops[1].color = secondColor;
|
||||
rawStops[1].offset = 0.5;
|
||||
stops = aDT->CreateGradientStops(rawStops, 2);
|
||||
|
||||
data = new BorderGradientCacheData(stops, BorderGradientCacheKey(firstColor, secondColor, aDT->GetType()));
|
||||
|
||||
if (!gBorderGradientCache->RegisterEntry(data)) {
|
||||
delete data;
|
||||
}
|
||||
}
|
||||
|
||||
return stops;
|
||||
}
|
||||
|
||||
typedef struct { gfxFloat a, b; } twoFloats;
|
||||
|
||||
void
|
||||
@ -1283,6 +1521,162 @@ nsCSSBorderRenderer::DrawNoCompositeColorSolidBorder()
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
nsCSSBorderRenderer::DrawNoCompositeColorSolidBorderAzure()
|
||||
{
|
||||
DrawTarget *dt = mContext->GetDrawTarget();
|
||||
|
||||
const gfxFloat alpha = 0.55191497064665766025;
|
||||
|
||||
const twoFloats cornerMults[4] = { { -1, 0 },
|
||||
{ 0, -1 },
|
||||
{ +1, 0 },
|
||||
{ 0, +1 } };
|
||||
|
||||
const twoFloats centerAdjusts[4] = { { 0, +0.5 },
|
||||
{ -0.5, 0 },
|
||||
{ 0, -0.5 },
|
||||
{ +0.5, 0 } };
|
||||
|
||||
Point pc, pci, p0, p1, p2, p3, pd, p3i;
|
||||
|
||||
gfxCornerSizes innerRadii;
|
||||
ComputeInnerRadii(mBorderRadii, mBorderWidths, &innerRadii);
|
||||
|
||||
gfxRect strokeRect = mOuterRect;
|
||||
strokeRect.Deflate(gfxMargin(mBorderWidths[3] / 2.0, mBorderWidths[0] / 2.0,
|
||||
mBorderWidths[1] / 2.0, mBorderWidths[2] / 2.0));
|
||||
|
||||
ColorPattern colorPat(Color(0, 0, 0, 0));
|
||||
LinearGradientPattern gradPat(Point(), Point(), NULL);
|
||||
|
||||
NS_FOR_CSS_CORNERS(i) {
|
||||
// the corner index -- either 1 2 3 0 (cw) or 0 3 2 1 (ccw)
|
||||
mozilla::css::Corner c = mozilla::css::Corner((i+1) % 4);
|
||||
mozilla::css::Corner prevCorner = mozilla::css::Corner(i);
|
||||
|
||||
// i+2 and i+3 respectively. These are used to index into the corner
|
||||
// multiplier table, and were deduced by calculating out the long form
|
||||
// of each corner and finding a pattern in the signs and values.
|
||||
int i1 = (i+1) % 4;
|
||||
int i2 = (i+2) % 4;
|
||||
int i3 = (i+3) % 4;
|
||||
|
||||
pc = ToPoint(mOuterRect.AtCorner(c));
|
||||
pci = ToPoint(mInnerRect.AtCorner(c));
|
||||
|
||||
nscolor firstColor, secondColor;
|
||||
if (IsVisible(mBorderStyles[i]) && IsVisible(mBorderStyles[i1])) {
|
||||
firstColor = mBorderColors[i];
|
||||
secondColor = mBorderColors[i1];
|
||||
} else if (IsVisible(mBorderStyles[i])) {
|
||||
firstColor = mBorderColors[i];
|
||||
secondColor = mBorderColors[i];
|
||||
} else {
|
||||
firstColor = mBorderColors[i1];
|
||||
secondColor = mBorderColors[i1];
|
||||
}
|
||||
|
||||
RefPtr<PathBuilder> builder = dt->CreatePathBuilder();
|
||||
|
||||
Point strokeStart, strokeEnd;
|
||||
|
||||
strokeStart.x = mOuterRect.AtCorner(prevCorner).x +
|
||||
mBorderCornerDimensions[prevCorner].width * cornerMults[i2].a;
|
||||
strokeStart.y = mOuterRect.AtCorner(prevCorner).y +
|
||||
mBorderCornerDimensions[prevCorner].height * cornerMults[i2].b;
|
||||
|
||||
strokeEnd.x = pc.x + mBorderCornerDimensions[c].width * cornerMults[i].a;
|
||||
strokeEnd.y = pc.y + mBorderCornerDimensions[c].height * cornerMults[i].b;
|
||||
|
||||
strokeStart.x += centerAdjusts[i].a * mBorderWidths[i];
|
||||
strokeStart.y += centerAdjusts[i].b * mBorderWidths[i];
|
||||
strokeEnd.x += centerAdjusts[i].a * mBorderWidths[i];
|
||||
strokeEnd.y += centerAdjusts[i].b * mBorderWidths[i];
|
||||
|
||||
builder->MoveTo(strokeStart);
|
||||
builder->LineTo(strokeEnd);
|
||||
RefPtr<Path> path = builder->Finish();
|
||||
dt->Stroke(path, ColorPattern(Color::FromABGR(mBorderColors[i])), StrokeOptions(mBorderWidths[i]));
|
||||
|
||||
Pattern *pattern;
|
||||
|
||||
if (firstColor != secondColor) {
|
||||
gradPat.mStops = CreateCornerGradient(c, firstColor, secondColor, dt, gradPat.mBegin, gradPat.mEnd);
|
||||
pattern = &gradPat;
|
||||
} else {
|
||||
colorPat.mColor = Color::FromABGR(firstColor);
|
||||
pattern = &colorPat;
|
||||
}
|
||||
|
||||
builder = dt->CreatePathBuilder();
|
||||
|
||||
if (mBorderRadii[c].width > 0 && mBorderRadii[c].height > 0) {
|
||||
p0.x = pc.x + cornerMults[i].a * mBorderRadii[c].width;
|
||||
p0.y = pc.y + cornerMults[i].b * mBorderRadii[c].height;
|
||||
|
||||
p3.x = pc.x + cornerMults[i3].a * mBorderRadii[c].width;
|
||||
p3.y = pc.y + cornerMults[i3].b * mBorderRadii[c].height;
|
||||
|
||||
p1.x = p0.x + alpha * cornerMults[i2].a * mBorderRadii[c].width;
|
||||
p1.y = p0.y + alpha * cornerMults[i2].b * mBorderRadii[c].height;
|
||||
|
||||
p2.x = p3.x - alpha * cornerMults[i3].a * mBorderRadii[c].width;
|
||||
p2.y = p3.y - alpha * cornerMults[i3].b * mBorderRadii[c].height;
|
||||
|
||||
Point cornerStart;
|
||||
cornerStart.x = pc.x + cornerMults[i].a * mBorderCornerDimensions[c].width;
|
||||
cornerStart.y = pc.y + cornerMults[i].b * mBorderCornerDimensions[c].height;
|
||||
|
||||
builder->MoveTo(cornerStart);
|
||||
builder->LineTo(p0);
|
||||
|
||||
builder->BezierTo(p1, p2, p3);
|
||||
|
||||
Point outerCornerEnd;
|
||||
outerCornerEnd.x = pc.x + cornerMults[i3].a * mBorderCornerDimensions[c].width;
|
||||
outerCornerEnd.y = pc.y + cornerMults[i3].b * mBorderCornerDimensions[c].height;
|
||||
|
||||
builder->LineTo(outerCornerEnd);
|
||||
|
||||
p0.x = pci.x + cornerMults[i].a * innerRadii[c].width;
|
||||
p0.y = pci.y + cornerMults[i].b * innerRadii[c].height;
|
||||
|
||||
p3i.x = pci.x + cornerMults[i3].a * innerRadii[c].width;
|
||||
p3i.y = pci.y + cornerMults[i3].b * innerRadii[c].height;
|
||||
|
||||
p1.x = p0.x + alpha * cornerMults[i2].a * innerRadii[c].width;
|
||||
p1.y = p0.y + alpha * cornerMults[i2].b * innerRadii[c].height;
|
||||
|
||||
p2.x = p3i.x - alpha * cornerMults[i3].a * innerRadii[c].width;
|
||||
p2.y = p3i.y - alpha * cornerMults[i3].b * innerRadii[c].height;
|
||||
builder->LineTo(p3i);
|
||||
builder->BezierTo(p2, p1, p0);
|
||||
builder->Close();
|
||||
path = builder->Finish();
|
||||
dt->Fill(path, *pattern);
|
||||
} else {
|
||||
Point c1, c2, c3, c4;
|
||||
|
||||
c1.x = pc.x + cornerMults[i].a * mBorderCornerDimensions[c].width;
|
||||
c1.y = pc.y + cornerMults[i].b * mBorderCornerDimensions[c].height;
|
||||
c2 = pc;
|
||||
c3.x = pc.x + cornerMults[i3].a * mBorderCornerDimensions[c].width;
|
||||
c3.y = pc.y + cornerMults[i3].b * mBorderCornerDimensions[c].height;
|
||||
|
||||
builder->MoveTo(c1);
|
||||
builder->LineTo(c2);
|
||||
builder->LineTo(c3);
|
||||
builder->LineTo(pci);
|
||||
builder->Close();
|
||||
|
||||
path = builder->Finish();
|
||||
|
||||
dt->Fill(path, *pattern);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
nsCSSBorderRenderer::DrawRectangularCompositeColors()
|
||||
{
|
||||
@ -1512,7 +1906,11 @@ nsCSSBorderRenderer::DrawBorders()
|
||||
if (allBordersSolid && !hasCompositeColors &&
|
||||
!mAvoidStroke)
|
||||
{
|
||||
DrawNoCompositeColorSolidBorder();
|
||||
if (mContext->IsCairo()) {
|
||||
DrawNoCompositeColorSolidBorder();
|
||||
} else {
|
||||
DrawNoCompositeColorSolidBorderAzure();
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -11,6 +11,7 @@
|
||||
#include "nsStyleStruct.h"
|
||||
|
||||
#include "gfxContext.h"
|
||||
#include "mozilla/gfx/2D.h"
|
||||
|
||||
// define this to enable a bunch of debug dump info
|
||||
#undef DEBUG_NEW_BORDERS
|
||||
@ -74,6 +75,9 @@ struct nsCSSBorderRenderer {
|
||||
int aSkipSides,
|
||||
nscolor aBackgroundColor);
|
||||
|
||||
static void Init();
|
||||
static void Shutdown();
|
||||
|
||||
gfxCornerSizes mBorderCornerDimensions;
|
||||
|
||||
// destination context
|
||||
@ -185,12 +189,21 @@ struct nsCSSBorderRenderer {
|
||||
const gfxRGBA &aFirstColor,
|
||||
const gfxRGBA &aSecondColor);
|
||||
|
||||
// Azure variant of CreateCornerGradient.
|
||||
mozilla::TemporaryRef<mozilla::gfx::GradientStops>
|
||||
CreateCornerGradient(mozilla::css::Corner aCorner, const gfxRGBA &aFirstColor,
|
||||
const gfxRGBA &aSecondColor, mozilla::gfx::DrawTarget *aDT,
|
||||
mozilla::gfx::Point &aPoint1, mozilla::gfx::Point &aPoint2);
|
||||
|
||||
// Draw a solid color border that is uniformly the same width.
|
||||
void DrawSingleWidthSolidBorder();
|
||||
|
||||
// Draw any border which is solid on all sides and does not use
|
||||
// CompositeColors.
|
||||
void DrawNoCompositeColorSolidBorder();
|
||||
// Draw any border which is solid on all sides and does not use
|
||||
// CompositeColors. Using Azure.
|
||||
void DrawNoCompositeColorSolidBorderAzure();
|
||||
|
||||
// Draw a solid border that has no border radius (i.e. is rectangular) and
|
||||
// uses CompositeColors.
|
||||
|
Loading…
Reference in New Issue
Block a user