Bug 893977. Support repeating gradients in the CoreGraphics backend. r=mattwoordow

CoreGraphics doesn't support repeating gradients natively so we have to
manually repeat them. This change missing support for interpolating a
stop for the center if it doesn't line up correctly. That will come later.
This commit is contained in:
Jeff Muizelaar 2013-07-18 20:08:51 -04:00
parent aeddabc415
commit 893fc26d2b

View File

@ -8,8 +8,11 @@
#include "ScaledFontMac.h"
#include "Tools.h"
#include <vector>
#include <algorithm>
#include "QuartzSupport.h"
using namespace std;
//CG_EXTERN void CGContextSetCompositeOperation (CGContextRef, PrivateCGCompositeMode);
// A private API that Cairo has been using for a long time
@ -313,37 +316,51 @@ class GradientStopsCG : public GradientStops
//XXX: The skia backend uses a vector and passes in aNumStops. It should do better
GradientStopsCG(GradientStop* aStops, uint32_t aNumStops, ExtendMode aExtendMode)
{
//XXX: do the stops need to be in any particular order?
// what should we do about the color space here? we certainly shouldn't be
// recreating it all the time
std::vector<CGFloat> colors;
std::vector<CGFloat> offsets;
colors.reserve(aNumStops*4);
offsets.reserve(aNumStops);
mExtend = aExtendMode;
if (aExtendMode == EXTEND_CLAMP) {
//XXX: do the stops need to be in any particular order?
// what should we do about the color space here? we certainly shouldn't be
// recreating it all the time
std::vector<CGFloat> colors;
std::vector<CGFloat> offsets;
colors.reserve(aNumStops*4);
offsets.reserve(aNumStops);
for (uint32_t i = 0; i < aNumStops; i++) {
colors.push_back(aStops[i].color.r);
colors.push_back(aStops[i].color.g);
colors.push_back(aStops[i].color.b);
colors.push_back(aStops[i].color.a);
for (uint32_t i = 0; i < aNumStops; i++) {
colors.push_back(aStops[i].color.r);
colors.push_back(aStops[i].color.g);
colors.push_back(aStops[i].color.b);
colors.push_back(aStops[i].color.a);
offsets.push_back(aStops[i].offset);
offsets.push_back(aStops[i].offset);
}
CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB();
mGradient = CGGradientCreateWithColorComponents(colorSpace,
&colors.front(),
&offsets.front(),
aNumStops);
CGColorSpaceRelease(colorSpace);
} else {
mGradient = nullptr;
mStops.reserve(aNumStops);
for (uint32_t i = 0; i < aNumStops; i++) {
mStops.push_back(aStops[i]);
}
}
CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB();
mGradient = CGGradientCreateWithColorComponents(colorSpace,
&colors.front(),
&offsets.front(),
aNumStops);
CGColorSpaceRelease(colorSpace);
}
virtual ~GradientStopsCG() {
CGGradientRelease(mGradient);
if (mGradient)
CGGradientRelease(mGradient);
}
// Will always report BACKEND_COREGRAPHICS, but it is compatible
// with BACKEND_COREGRAPHICS_ACCELERATED
BackendType GetBackendType() const { return BACKEND_COREGRAPHICS; }
// XXX this should be a union
CGGradientRef mGradient;
std::vector<GradientStop> mStops;
ExtendMode mExtend;
};
TemporaryRef<GradientStops>
@ -353,35 +370,183 @@ DrawTargetCG::CreateGradientStops(GradientStop *aStops, uint32_t aNumStops,
return new GradientStopsCG(aStops, aNumStops, aExtendMode);
}
static void
DrawGradient(CGContextRef cg, const Pattern &aPattern)
DrawLinearRepeatingGradient(CGContextRef cg, const LinearGradientPattern &aPattern, const CGRect &aExtents)
{
GradientStopsCG *stops = static_cast<GradientStopsCG*>(aPattern.mStops.get());
CGPoint startPoint = { aPattern.mBegin.x, aPattern.mBegin.y };
CGPoint endPoint = { aPattern.mEnd.x, aPattern.mEnd.y };
// extend the gradient line in multiples of the existing length in both
// directions until it crosses an edge of the extents box.
double xDiff = aPattern.mEnd.x - aPattern.mBegin.x;
double yDiff = aPattern.mEnd.y - aPattern.mBegin.y;
int repeatCount = 1;
// if we don't have a line then we can't extend it
if (xDiff || yDiff) {
while (startPoint.x > aExtents.origin.x
&& startPoint.y > aExtents.origin.y
&& startPoint.x < (aExtents.origin.x+aExtents.size.width)
&& startPoint.y < (aExtents.origin.y+aExtents.size.height))
{
startPoint.x -= xDiff;
startPoint.y -= yDiff;
repeatCount++;
}
while (endPoint.x > aExtents.origin.x
&& endPoint.y > aExtents.origin.y
&& endPoint.x < (aExtents.origin.x+aExtents.size.width)
&& endPoint.y < (aExtents.origin.y+aExtents.size.height))
{
endPoint.x += xDiff;
endPoint.y += yDiff;
repeatCount++;
}
}
double scale = 1./repeatCount;
std::vector<CGFloat> colors;
std::vector<CGFloat> offsets;
colors.reserve(stops->mStops.size()*repeatCount*4);
offsets.reserve(stops->mStops.size()*repeatCount);
for (int j = 0; j < repeatCount; j++) {
for (uint32_t i = 0; i < stops->mStops.size(); i++) {
colors.push_back(stops->mStops[i].color.r);
colors.push_back(stops->mStops[i].color.g);
colors.push_back(stops->mStops[i].color.b);
colors.push_back(stops->mStops[i].color.a);
offsets.push_back((stops->mStops[i].offset + j)*scale);
}
}
CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB();
CGGradientRef gradient = CGGradientCreateWithColorComponents(colorSpace,
&colors.front(),
&offsets.front(),
repeatCount*stops->mStops.size());
CGColorSpaceRelease(colorSpace);
CGContextDrawLinearGradient(cg, gradient, startPoint, endPoint,
kCGGradientDrawsBeforeStartLocation | kCGGradientDrawsAfterEndLocation);
CGGradientRelease(gradient);
}
static CGPoint CGRectTopLeft(CGRect a)
{ return a.origin; }
static CGPoint CGRectBottomLeft(CGRect a)
{ return CGPointMake(a.origin.x, a.origin.y + a.size.height); }
static CGPoint CGRectTopRight(CGRect a)
{ return CGPointMake(a.origin.x + a.size.width, a.origin.y); }
static CGPoint CGRectBottomRight(CGRect a)
{ return CGPointMake(a.origin.x + a.size.width, a.origin.y + a.size.height); }
static CGFloat
CGPointDistance(CGPoint a, CGPoint b)
{
return hypot(a.x-b.x, a.y-b.y);
}
static void
DrawRadialRepeatingGradient(CGContextRef cg, const RadialGradientPattern &aPattern, const CGRect &aExtents)
{
GradientStopsCG *stops = static_cast<GradientStopsCG*>(aPattern.mStops.get());
CGPoint startCenter = { aPattern.mCenter1.x, aPattern.mCenter1.y };
CGFloat startRadius = aPattern.mRadius1;
CGPoint endCenter = { aPattern.mCenter2.x, aPattern.mCenter2.y };
CGFloat endRadius = aPattern.mRadius2;
// find the maximum distance from endCenter to a corner of aExtents
double minimumEndRadius = endRadius;
minimumEndRadius = max(minimumEndRadius, CGPointDistance(endCenter, CGRectTopLeft(aExtents)));
minimumEndRadius = max(minimumEndRadius, CGPointDistance(endCenter, CGRectBottomLeft(aExtents)));
minimumEndRadius = max(minimumEndRadius, CGPointDistance(endCenter, CGRectTopRight(aExtents)));
minimumEndRadius = max(minimumEndRadius, CGPointDistance(endCenter, CGRectBottomRight(aExtents)));
CGFloat length = endRadius - startRadius;
int repeatCount = 1;
while (endRadius < minimumEndRadius) {
endRadius += length;
repeatCount++;
}
while (startRadius-length >= 0) {
startRadius -= length;
repeatCount++;
}
double scale = 1./repeatCount;
std::vector<CGFloat> colors;
std::vector<CGFloat> offsets;
colors.reserve(stops->mStops.size()*repeatCount*4);
offsets.reserve(stops->mStops.size()*repeatCount);
for (int j = 0; j < repeatCount; j++) {
for (uint32_t i = 0; i < stops->mStops.size(); i++) {
colors.push_back(stops->mStops[i].color.r);
colors.push_back(stops->mStops[i].color.g);
colors.push_back(stops->mStops[i].color.b);
colors.push_back(stops->mStops[i].color.a);
offsets.push_back((stops->mStops[i].offset + j)*scale);
}
}
CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB();
CGGradientRef gradient = CGGradientCreateWithColorComponents(colorSpace,
&colors.front(),
&offsets.front(),
repeatCount*stops->mStops.size());
CGColorSpaceRelease(colorSpace);
//XXX: are there degenerate radial gradients that we should avoid drawing?
CGContextDrawRadialGradient(cg, gradient, startCenter, startRadius, endCenter, endRadius,
kCGGradientDrawsBeforeStartLocation | kCGGradientDrawsAfterEndLocation);
CGGradientRelease(gradient);
}
static void
DrawGradient(CGContextRef cg, const Pattern &aPattern, const CGRect &aExtents)
{
if (aPattern.GetType() == PATTERN_LINEAR_GRADIENT) {
const LinearGradientPattern& pat = static_cast<const LinearGradientPattern&>(aPattern);
GradientStopsCG *stops = static_cast<GradientStopsCG*>(pat.mStops.get());
// XXX: we should take the m out of the properties of LinearGradientPatterns
CGPoint startPoint = { pat.mBegin.x, pat.mBegin.y };
CGPoint endPoint = { pat.mEnd.x, pat.mEnd.y };
if (stops->mExtend == EXTEND_CLAMP) {
// Canvas spec states that we should avoid drawing degenerate gradients (XXX: should this be in common code?)
//if (startPoint.x == endPoint.x && startPoint.y == endPoint.y)
// return;
// XXX: we should take the m out of the properties of LinearGradientPatterns
CGPoint startPoint = { pat.mBegin.x, pat.mBegin.y };
CGPoint endPoint = { pat.mEnd.x, pat.mEnd.y };
CGContextDrawLinearGradient(cg, stops->mGradient, startPoint, endPoint,
kCGGradientDrawsBeforeStartLocation | kCGGradientDrawsAfterEndLocation);
// Canvas spec states that we should avoid drawing degenerate gradients (XXX: should this be in common code?)
//if (startPoint.x == endPoint.x && startPoint.y == endPoint.y)
// return;
CGContextDrawLinearGradient(cg, stops->mGradient, startPoint, endPoint,
kCGGradientDrawsBeforeStartLocation | kCGGradientDrawsAfterEndLocation);
} else if (stops->mExtend == EXTEND_REPEAT) {
DrawLinearRepeatingGradient(cg, pat, aExtents);
}
} else if (aPattern.GetType() == PATTERN_RADIAL_GRADIENT) {
const RadialGradientPattern& pat = static_cast<const RadialGradientPattern&>(aPattern);
GradientStopsCG *stops = static_cast<GradientStopsCG*>(pat.mStops.get());
if (stops->mExtend == EXTEND_CLAMP) {
// XXX: we should take the m out of the properties of RadialGradientPatterns
CGPoint startCenter = { pat.mCenter1.x, pat.mCenter1.y };
CGFloat startRadius = pat.mRadius1;
CGPoint endCenter = { pat.mCenter2.x, pat.mCenter2.y };
CGFloat endRadius = pat.mRadius2;
// XXX: we should take the m out of the properties of RadialGradientPatterns
CGPoint startCenter = { pat.mCenter1.x, pat.mCenter1.y };
CGFloat startRadius = pat.mRadius1;
CGPoint endCenter = { pat.mCenter2.x, pat.mCenter2.y };
CGFloat endRadius = pat.mRadius2;
//XXX: are there degenerate radial gradients that we should avoid drawing?
CGContextDrawRadialGradient(cg, stops->mGradient, startCenter, startRadius, endCenter, endRadius,
kCGGradientDrawsBeforeStartLocation | kCGGradientDrawsAfterEndLocation);
//XXX: are there degenerate radial gradients that we should avoid drawing?
CGContextDrawRadialGradient(cg, stops->mGradient, startCenter, startRadius, endCenter, endRadius,
kCGGradientDrawsBeforeStartLocation | kCGGradientDrawsAfterEndLocation);
} else if (stops->mExtend == EXTEND_REPEAT) {
DrawRadialRepeatingGradient(cg, pat, aExtents);
}
} else {
assert(0);
}
@ -554,7 +719,7 @@ DrawTargetCG::MaskSurface(const Pattern &aSource,
if (isGradient(aSource)) {
// we shouldn't need to clip to an additional rectangle
// as the cliping to the mask should be sufficient.
DrawGradient(cg, aSource);
DrawGradient(cg, aSource, CGRectMake(aOffset.x, aOffset.y, size.width, size.height));
} else {
SetFillFromPattern(cg, mColorSpace, aSource);
CGContextFillRect(cg, CGRectMake(aOffset.x, aOffset.y, size.width, size.height));
@ -585,7 +750,7 @@ DrawTargetCG::FillRect(const Rect &aRect,
if (isGradient(aPattern)) {
CGContextClipToRect(cg, RectToCGRect(aRect));
DrawGradient(cg, aPattern);
DrawGradient(cg, aPattern, RectToCGRect(aRect));
} else {
SetFillFromPattern(cg, mColorSpace, aPattern);
CGContextFillRect(cg, RectToCGRect(aRect));
@ -617,9 +782,10 @@ DrawTargetCG::StrokeLine(const Point &p1, const Point &p2, const Pattern &aPatte
if (isGradient(aPattern)) {
CGContextReplacePathWithStrokedPath(cg);
CGRect extents = CGContextGetPathBoundingBox(cg);
//XXX: should we use EO clip here?
CGContextClip(cg);
DrawGradient(cg, aPattern);
DrawGradient(cg, aPattern, extents);
} else {
SetStrokeFromPattern(cg, mColorSpace, aPattern);
CGContextStrokePath(cg);
@ -653,9 +819,10 @@ DrawTargetCG::StrokeRect(const Rect &aRect,
CGContextBeginPath(cg);
CGContextAddRect(cg, RectToCGRect(aRect));
CGContextReplacePathWithStrokedPath(cg);
CGRect extents = CGContextGetPathBoundingBox(cg);
//XXX: should we use EO clip here?
CGContextClip(cg);
DrawGradient(cg, aPattern);
DrawGradient(cg, aPattern, extents);
} else {
SetStrokeFromPattern(cg, mColorSpace, aPattern);
CGContextStrokeRect(cg, RectToCGRect(aRect));
@ -704,9 +871,10 @@ DrawTargetCG::Stroke(const Path *aPath, const Pattern &aPattern, const StrokeOpt
if (isGradient(aPattern)) {
CGContextReplacePathWithStrokedPath(cg);
CGRect extents = CGContextGetPathBoundingBox(cg);
//XXX: should we use EO clip here?
CGContextClip(cg);
DrawGradient(cg, aPattern);
DrawGradient(cg, aPattern, extents);
} else {
// XXX: we could put fill mode into the path fill rule if we wanted
@ -740,19 +908,22 @@ DrawTargetCG::Fill(const Path *aPath, const Pattern &aPattern, const DrawOptions
if (isGradient(aPattern)) {
// setup a clip to draw the gradient through
CGRect extents;
if (CGPathIsEmpty(cgPath->GetPath())) {
// Adding an empty path will cause us not to clip
// so clip everything explicitly
CGContextClipToRect(mCg, CGRectZero);
extents = CGRectZero;
} else {
CGContextAddPath(cg, cgPath->GetPath());
extents = CGContextGetPathBoundingBox(cg);
if (cgPath->GetFillRule() == FILL_EVEN_ODD)
CGContextEOClip(mCg);
else
CGContextClip(mCg);
}
DrawGradient(cg, aPattern);
DrawGradient(cg, aPattern, extents);
} else {
CGContextAddPath(cg, cgPath->GetPath());
@ -768,6 +939,29 @@ DrawTargetCG::Fill(const Path *aPath, const Pattern &aPattern, const DrawOptions
CGContextRestoreGState(mCg);
}
CGRect ComputeGlyphsExtents(CGRect *bboxes, CGPoint *positions, CFIndex count, float scale)
{
CGFloat x1, x2, y1, y2;
if (count < 1)
return CGRectZero;
x1 = bboxes[0].origin.x + positions[0].x;
x2 = bboxes[0].origin.x + positions[0].x + scale*bboxes[0].size.width;
y1 = bboxes[0].origin.y + positions[0].y;
y2 = bboxes[0].origin.y + positions[0].y + scale*bboxes[0].size.height;
// accumulate max and minimum coordinates
for (int i = 1; i < count; i++) {
x1 = min(x1, bboxes[i].origin.x + positions[i].x);
y1 = min(y1, bboxes[i].origin.y + positions[i].y);
x2 = max(x2, bboxes[i].origin.x + positions[i].x + scale*bboxes[i].size.width);
y2 = max(y2, bboxes[i].origin.y + positions[i].y + scale*bboxes[i].size.height);
}
CGRect extents = {{x1, y1}, {x2-x1, y2-y1}};
return extents;
}
void
DrawTargetCG::FillGlyphs(ScaledFont *aFont, const GlyphBuffer &aBuffer, const Pattern &aPattern, const DrawOptions &aDrawOptions,
@ -811,17 +1005,27 @@ DrawTargetCG::FillGlyphs(ScaledFont *aFont, const GlyphBuffer &aBuffer, const Pa
//XXX: CGContextShowGlyphsAtPositions is 10.5+ for older versions use CGContextShowGlyphsWithAdvances
if (isGradient(aPattern)) {
CGContextSetTextDrawingMode(cg, kCGTextClip);
CGRect extents;
if (ScaledFontMac::CTFontDrawGlyphsPtr != nullptr) {
CGRect *bboxes = new CGRect[aBuffer.mNumGlyphs];
CTFontGetBoundingRectsForGlyphs(macFont->mCTFont, kCTFontDefaultOrientation,
&glyphs.front(), bboxes, aBuffer.mNumGlyphs);
extents = ComputeGlyphsExtents(bboxes, &positions.front(), aBuffer.mNumGlyphs, 1.0f);
ScaledFontMac::CTFontDrawGlyphsPtr(macFont->mCTFont, &glyphs.front(),
&positions.front(),
aBuffer.mNumGlyphs, cg);
&positions.front(), aBuffer.mNumGlyphs, cg);
delete bboxes;
} else {
CGRect *bboxes = new CGRect[aBuffer.mNumGlyphs];
CGFontGetGlyphBBoxes(macFont->mFont, &glyphs.front(), aBuffer.mNumGlyphs, bboxes);
extents = ComputeGlyphsExtents(bboxes, &positions.front(), aBuffer.mNumGlyphs, macFont->mSize);
CGContextSetFont(cg, macFont->mFont);
CGContextSetFontSize(cg, macFont->mSize);
CGContextShowGlyphsAtPositions(cg, &glyphs.front(), &positions.front(),
aBuffer.mNumGlyphs);
delete bboxes;
}
DrawGradient(cg, aPattern);
DrawGradient(cg, aPattern, extents);
} else {
//XXX: with CoreGraphics we can stroke text directly instead of going
// through GetPath. It would be nice to add support for using that