summaryrefslogtreecommitdiff
path: root/gfx/2d/DrawTargetCG.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'gfx/2d/DrawTargetCG.cpp')
-rw-r--r--gfx/2d/DrawTargetCG.cpp1270
1 files changed, 991 insertions, 279 deletions
diff --git a/gfx/2d/DrawTargetCG.cpp b/gfx/2d/DrawTargetCG.cpp
index e24d2906a..1bd92f212 100644
--- a/gfx/2d/DrawTargetCG.cpp
+++ b/gfx/2d/DrawTargetCG.cpp
@@ -2,13 +2,25 @@
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "BorrowedContext.h"
+#include "DataSurfaceHelpers.h"
#include "DrawTargetCG.h"
+#include "Logging.h"
#include "SourceSurfaceCG.h"
#include "Rect.h"
#include "ScaledFontMac.h"
#include "Tools.h"
#include <vector>
-#include "QuartzSupport.h"
+#include <algorithm>
+#include "MacIOSurface.h"
+#include "FilterNodeSoftware.h"
+#include "mozilla/Assertions.h"
+#include "mozilla/FloatingPoint.h"
+#include "mozilla/Types.h" // for decltype
+#include "mozilla/Vector.h"
+
+using namespace std;
//CG_EXTERN void CGContextSetCompositeOperation (CGContextRef, PrivateCGCompositeMode);
@@ -18,12 +30,8 @@ CG_EXTERN void CGContextSetCTM(CGContextRef, CGAffineTransform);
namespace mozilla {
namespace gfx {
-static CGRect RectToCGRect(Rect r)
-{
- return CGRectMake(r.x, r.y, r.width, r.height);
-}
-
-static CGRect IntRectToCGRect(IntRect r)
+template <typename T>
+static CGRect RectToCGRect(const T& r)
{
return CGRectMake(r.x, r.y, r.width, r.height);
}
@@ -32,82 +40,82 @@ CGBlendMode ToBlendMode(CompositionOp op)
{
CGBlendMode mode;
switch (op) {
- case OP_OVER:
+ case CompositionOp::OP_OVER:
mode = kCGBlendModeNormal;
break;
- case OP_ADD:
+ case CompositionOp::OP_ADD:
mode = kCGBlendModePlusLighter;
break;
- case OP_ATOP:
+ case CompositionOp::OP_ATOP:
mode = kCGBlendModeSourceAtop;
break;
- case OP_OUT:
+ case CompositionOp::OP_OUT:
mode = kCGBlendModeSourceOut;
break;
- case OP_IN:
+ case CompositionOp::OP_IN:
mode = kCGBlendModeSourceIn;
break;
- case OP_SOURCE:
+ case CompositionOp::OP_SOURCE:
mode = kCGBlendModeCopy;
break;
- case OP_DEST_IN:
+ case CompositionOp::OP_DEST_IN:
mode = kCGBlendModeDestinationIn;
break;
- case OP_DEST_OUT:
+ case CompositionOp::OP_DEST_OUT:
mode = kCGBlendModeDestinationOut;
break;
- case OP_DEST_OVER:
+ case CompositionOp::OP_DEST_OVER:
mode = kCGBlendModeDestinationOver;
break;
- case OP_DEST_ATOP:
+ case CompositionOp::OP_DEST_ATOP:
mode = kCGBlendModeDestinationAtop;
break;
- case OP_XOR:
+ case CompositionOp::OP_XOR:
mode = kCGBlendModeXOR;
break;
- case OP_MULTIPLY:
+ case CompositionOp::OP_MULTIPLY:
mode = kCGBlendModeMultiply;
break;
- case OP_SCREEN:
+ case CompositionOp::OP_SCREEN:
mode = kCGBlendModeScreen;
break;
- case OP_OVERLAY:
+ case CompositionOp::OP_OVERLAY:
mode = kCGBlendModeOverlay;
break;
- case OP_DARKEN:
+ case CompositionOp::OP_DARKEN:
mode = kCGBlendModeDarken;
break;
- case OP_LIGHTEN:
+ case CompositionOp::OP_LIGHTEN:
mode = kCGBlendModeLighten;
break;
- case OP_COLOR_DODGE:
+ case CompositionOp::OP_COLOR_DODGE:
mode = kCGBlendModeColorDodge;
break;
- case OP_COLOR_BURN:
+ case CompositionOp::OP_COLOR_BURN:
mode = kCGBlendModeColorBurn;
break;
- case OP_HARD_LIGHT:
+ case CompositionOp::OP_HARD_LIGHT:
mode = kCGBlendModeHardLight;
break;
- case OP_SOFT_LIGHT:
+ case CompositionOp::OP_SOFT_LIGHT:
mode = kCGBlendModeSoftLight;
break;
- case OP_DIFFERENCE:
+ case CompositionOp::OP_DIFFERENCE:
mode = kCGBlendModeDifference;
break;
- case OP_EXCLUSION:
+ case CompositionOp::OP_EXCLUSION:
mode = kCGBlendModeExclusion;
break;
- case OP_HUE:
+ case CompositionOp::OP_HUE:
mode = kCGBlendModeHue;
break;
- case OP_SATURATION:
+ case CompositionOp::OP_SATURATION:
mode = kCGBlendModeSaturation;
break;
- case OP_COLOR:
+ case CompositionOp::OP_COLOR:
mode = kCGBlendModeColor;
break;
- case OP_LUMINOSITY:
+ case CompositionOp::OP_LUMINOSITY:
mode = kCGBlendModeLuminosity;
break;
/*
@@ -120,33 +128,60 @@ CGBlendMode ToBlendMode(CompositionOp op)
return mode;
}
+static CGInterpolationQuality
+InterpolationQualityFromFilter(Filter aFilter)
+{
+ switch (aFilter) {
+ default:
+ case Filter::LINEAR:
+ return kCGInterpolationLow;
+ case Filter::POINT:
+ return kCGInterpolationNone;
+ case Filter::GOOD:
+ return kCGInterpolationLow;
+ }
+}
-DrawTargetCG::DrawTargetCG() : mCg(nullptr), mSnapshot(nullptr)
+DrawTargetCG::DrawTargetCG()
+ : mColorSpace(nullptr)
+ , mCg(nullptr)
+ , mMayContainInvalidPremultipliedData(false)
{
}
DrawTargetCG::~DrawTargetCG()
{
- MarkChanged();
+ if (mSnapshot) {
+ if (mSnapshot->refCount() > 1) {
+ // We only need to worry about snapshots that someone else knows about
+ mSnapshot->DrawTargetWillGoAway();
+ }
+ mSnapshot = nullptr;
+ }
- // We need to conditionally release these because Init can fail without initializing these.
- if (mColorSpace)
- CGColorSpaceRelease(mColorSpace);
- if (mCg)
- CGContextRelease(mCg);
- free(mData);
+ // Both of these are OK with nullptr arguments, so we do not
+ // need to check (these could be nullptr if Init fails)
+ CGColorSpaceRelease(mColorSpace);
+ CGContextRelease(mCg);
}
-BackendType
+DrawTargetType
DrawTargetCG::GetType() const
{
+ return GetBackendType() == BackendType::COREGRAPHICS_ACCELERATED ?
+ DrawTargetType::HARDWARE_RASTER : DrawTargetType::SOFTWARE_RASTER;
+}
+
+BackendType
+DrawTargetCG::GetBackendType() const
+{
// It may be worth spliting Bitmap and IOSurface DrawTarget
// into seperate classes.
if (GetContextType(mCg) == CG_CONTEXT_TYPE_IOSURFACE) {
- return BACKEND_COREGRAPHICS_ACCELERATED;
+ return BackendType::COREGRAPHICS_ACCELERATED;
} else {
- return BACKEND_COREGRAPHICS;
+ return BackendType::COREGRAPHICS;
}
}
@@ -156,9 +191,9 @@ DrawTargetCG::Snapshot()
if (!mSnapshot) {
if (GetContextType(mCg) == CG_CONTEXT_TYPE_IOSURFACE) {
return new SourceSurfaceCGIOSurfaceContext(this);
- } else {
- mSnapshot = new SourceSurfaceCGBitmapContext(this);
}
+ Flush();
+ mSnapshot = new SourceSurfaceCGBitmapContext(this);
}
return mSnapshot;
@@ -170,11 +205,10 @@ DrawTargetCG::CreateSimilarDrawTarget(const IntSize &aSize, SurfaceFormat aForma
// XXX: in thebes we use CGLayers to do this kind of thing. It probably makes sense
// to add that in somehow, but at a higher level
RefPtr<DrawTargetCG> newTarget = new DrawTargetCG();
- if (newTarget->Init(GetType(), aSize, aFormat)) {
- return newTarget;
- } else {
- return nullptr;
+ if (newTarget->Init(GetBackendType(), aSize, aFormat)) {
+ return newTarget.forget();
}
+ return nullptr;
}
TemporaryRef<SourceSurface>
@@ -185,58 +219,88 @@ DrawTargetCG::CreateSourceSurfaceFromData(unsigned char *aData,
{
RefPtr<SourceSurfaceCG> newSurf = new SourceSurfaceCG();
- if (!newSurf->InitFromData(aData, aSize, aStride, aFormat)) {
+ if (!newSurf->InitFromData(aData, aSize, aStride, aFormat)) {
return nullptr;
}
- return newSurf;
+ return newSurf.forget();
}
+static void releaseDataSurface(void* info, const void *data, size_t size)
+{
+ static_cast<DataSourceSurface*>(info)->Release();
+}
+
+// This function returns a retained CGImage that needs to be released after
+// use. The reason for this is that we want to either reuse an existing CGImage
+// or create a new one.
static CGImageRef
-GetImageFromSourceSurface(SourceSurface *aSurface)
+GetRetainedImageFromSourceSurface(SourceSurface *aSurface)
{
- if (aSurface->GetType() == SURFACE_COREGRAPHICS_IMAGE)
- return static_cast<SourceSurfaceCG*>(aSurface)->GetImage();
- else if (aSurface->GetType() == SURFACE_COREGRAPHICS_CGCONTEXT)
- return static_cast<SourceSurfaceCGContext*>(aSurface)->GetImage();
- else if (aSurface->GetType() == SURFACE_DATA)
- return static_cast<DataSourceSurfaceCG*>(aSurface)->GetImage();
- abort();
+ switch(aSurface->GetType()) {
+ case SurfaceType::COREGRAPHICS_IMAGE:
+ return CGImageRetain(static_cast<SourceSurfaceCG*>(aSurface)->GetImage());
+
+ case SurfaceType::COREGRAPHICS_CGCONTEXT:
+ return CGImageRetain(static_cast<SourceSurfaceCGContext*>(aSurface)->GetImage());
+
+ default:
+ {
+ RefPtr<DataSourceSurface> data = aSurface->GetDataSurface();
+ if (!data) {
+ MOZ_CRASH("unsupported source surface");
+ }
+ data->AddRef();
+ return CreateCGImage(releaseDataSurface, data.get(),
+ data->GetData(), data->GetSize(),
+ data->Stride(), data->GetFormat());
+ }
+ }
}
TemporaryRef<SourceSurface>
DrawTargetCG::OptimizeSourceSurface(SourceSurface *aSurface) const
{
- return nullptr;
+ return aSurface;
}
class UnboundnessFixer
{
CGRect mClipBounds;
CGLayerRef mLayer;
- CGContextRef mCg;
+ CGContextRef mLayerCg;
public:
- UnboundnessFixer() : mCg(nullptr) {}
+ UnboundnessFixer() : mLayerCg(nullptr) {}
- CGContextRef Check(CGContextRef baseCg, CompositionOp blend)
+ CGContextRef Check(CGContextRef baseCg, CompositionOp blend, const Rect* maskBounds = nullptr)
{
+ MOZ_ASSERT(baseCg);
if (!IsOperatorBoundByMask(blend)) {
mClipBounds = CGContextGetClipBoundingBox(baseCg);
+ // If we're entirely clipped out or if the drawing operation covers the entire clip then
+ // we don't need to create a temporary surface.
+ if (CGRectIsEmpty(mClipBounds) ||
+ (maskBounds && maskBounds->Contains(CGRectToRect(mClipBounds)))) {
+ return baseCg;
+ }
+
// TransparencyLayers aren't blended using the blend mode so
// we are forced to use CGLayers
//XXX: The size here is in default user space units, of the layer relative to the graphics context.
// is the clip bounds still correct if, for example, we have a scale applied to the context?
mLayer = CGLayerCreateWithContext(baseCg, mClipBounds.size, nullptr);
- //XXX: if the size is 0x0 we get a nullptr CGContext back from GetContext
- mCg = CGLayerGetContext(mLayer);
+ mLayerCg = CGLayerGetContext(mLayer);
// CGContext's default to have the origin at the bottom left
// so flip it to the top left and adjust for the origin
// of the layer
- CGContextTranslateCTM(mCg, -mClipBounds.origin.x, mClipBounds.origin.y + mClipBounds.size.height);
- CGContextScaleCTM(mCg, 1, -1);
+ if (MOZ2D_ERROR_IF(!mLayerCg)) {
+ return nullptr;
+ }
+ CGContextTranslateCTM(mLayerCg, -mClipBounds.origin.x, mClipBounds.origin.y + mClipBounds.size.height);
+ CGContextScaleCTM(mLayerCg, 1, -1);
- return mCg;
+ return mLayerCg;
} else {
return baseCg;
}
@@ -244,12 +308,14 @@ class UnboundnessFixer
void Fix(CGContextRef baseCg)
{
- if (mCg) {
+ if (mLayerCg) {
+ // we pushed a layer so draw it to baseCg
+ MOZ_ASSERT(baseCg);
CGContextTranslateCTM(baseCg, 0, mClipBounds.size.height);
CGContextScaleCTM(baseCg, 1, -1);
mClipBounds.origin.y *= -1;
CGContextDrawLayerAtPoint(baseCg, mClipBounds.origin, mLayer);
- CGContextRelease(mCg);
+ CGContextRelease(mLayerCg);
}
}
};
@@ -261,75 +327,76 @@ DrawTargetCG::DrawSurface(SourceSurface *aSurface,
const DrawSurfaceOptions &aSurfOptions,
const DrawOptions &aDrawOptions)
{
+ if (MOZ2D_ERROR_IF(!mCg)) {
+ return;
+ }
MarkChanged();
- CGImageRef image;
- CGImageRef subimage = nullptr;
CGContextSaveGState(mCg);
CGContextSetBlendMode(mCg, ToBlendMode(aDrawOptions.mCompositionOp));
UnboundnessFixer fixer;
- CGContextRef cg = fixer.Check(mCg, aDrawOptions.mCompositionOp);
+ CGContextRef cg = fixer.Check(mCg, aDrawOptions.mCompositionOp, &aDest);
+ if (MOZ2D_ERROR_IF(!cg)) {
+ return;
+ }
CGContextSetAlpha(cg, aDrawOptions.mAlpha);
+ CGContextSetShouldAntialias(cg, aDrawOptions.mAntialiasMode != AntialiasMode::NONE);
CGContextConcatCTM(cg, GfxMatrixToCGAffineTransform(mTransform));
- image = GetImageFromSourceSurface(aSurface);
- /* we have two options here:
- * - create a subimage -- this is slower
- * - fancy things with clip and different dest rects */
- {
- subimage = CGImageCreateWithImageInRect(image, RectToCGRect(aSource));
- image = subimage;
- }
- CGContextScaleCTM(cg, 1, -1);
+ CGContextSetInterpolationQuality(cg, InterpolationQualityFromFilter(aSurfOptions.mFilter));
- CGRect flippedRect = CGRectMake(aDest.x, -(aDest.y + aDest.height),
- aDest.width, aDest.height);
+ CGImageRef image = GetRetainedImageFromSourceSurface(aSurface);
- //XXX: we should implement this for patterns too
- if (aSurfOptions.mFilter == FILTER_POINT)
- CGContextSetInterpolationQuality(cg, kCGInterpolationNone);
+ if (aSurfOptions.mFilter == Filter::POINT) {
+ CGImageRef subimage = CGImageCreateWithImageInRect(image, RectToCGRect(aSource));
+ CGImageRelease(image);
- CGContextDrawImage(cg, flippedRect, image);
+ CGContextScaleCTM(cg, 1, -1);
- fixer.Fix(mCg);
+ CGRect flippedRect = CGRectMake(aDest.x, -(aDest.y + aDest.height),
+ aDest.width, aDest.height);
- CGContextRestoreGState(mCg);
+ CGContextDrawImage(cg, flippedRect, subimage);
+ CGImageRelease(subimage);
+ } else {
+ CGRect destRect = CGRectMake(aDest.x, aDest.y, aDest.width, aDest.height);
+ CGContextClipToRect(cg, destRect);
- CGImageRelease(subimage);
-}
+ float xScale = aSource.width / aDest.width;
+ float yScale = aSource.height / aDest.height;
+ CGContextTranslateCTM(cg, aDest.x - aSource.x / xScale, aDest.y - aSource.y / yScale);
-void
-DrawTargetCG::MaskSurface(const Pattern &aSource,
- SourceSurface *aMask,
- Point aOffset,
- const DrawOptions &aDrawOptions)
-{
- MarkChanged();
+ CGRect adjustedDestRect = CGRectMake(0, 0, CGImageGetWidth(image) / xScale,
+ CGImageGetHeight(image) / yScale);
- CGImageRef image;
- CGContextSaveGState(mCg);
+ CGContextTranslateCTM(cg, 0, CGRectGetHeight(adjustedDestRect));
+ CGContextScaleCTM(cg, 1, -1);
- CGContextSetBlendMode(mCg, ToBlendMode(aDrawOptions.mCompositionOp));
- UnboundnessFixer fixer;
- CGContextRef cg = fixer.Check(mCg, aDrawOptions.mCompositionOp);
- CGContextSetAlpha(cg, aDrawOptions.mAlpha);
-
- CGContextConcatCTM(cg, GfxMatrixToCGAffineTransform(mTransform));
- image = GetImageFromSourceSurface(aMask);
-
- CGContextScaleCTM(cg, 1, -1);
+ CGContextDrawImage(cg, adjustedDestRect, image);
+ CGImageRelease(image);
+ }
- IntSize size = aMask->GetSize();
- CGContextClipToMask(cg, CGRectMake(aOffset.x, aOffset.y, size.width, size.height), image);
-
- FillRect(Rect(0, 0, size.width, size.height), aSource, aDrawOptions);
-
fixer.Fix(mCg);
CGContextRestoreGState(mCg);
+}
+TemporaryRef<FilterNode>
+DrawTargetCG::CreateFilter(FilterType aType)
+{
+ return FilterNodeSoftware::Create(aType);
+}
+
+void
+DrawTargetCG::DrawFilter(FilterNode *aNode,
+ const Rect &aSourceRect,
+ const Point &aDestPoint,
+ const DrawOptions &aOptions)
+{
+ FilterNodeSoftware* filter = static_cast<FilterNodeSoftware*>(aNode);
+ filter->Draw(this, aSourceRect, aDestPoint, aOptions);
}
static CGColorRef ColorToCGColor(CGColorSpaceRef aColorSpace, const Color& aColor)
@@ -341,78 +408,319 @@ static CGColorRef ColorToCGColor(CGColorSpaceRef aColorSpace, const Color& aColo
class GradientStopsCG : public GradientStops
{
public:
- //XXX: The skia backend uses a vector and passes in aNumStops. It should do better
- GradientStopsCG(GradientStop* aStops, uint32_t aNumStops, ExtendMode aExtendMode)
+ MOZ_DECLARE_REFCOUNTED_VIRTUAL_TYPENAME(GradientStopsCG)
+
+ GradientStopsCG(CGColorSpaceRef aColorSpace,
+ const std::vector<GradientStop>& aStops,
+ ExtendMode aExtendMode)
+ : mGradient(nullptr)
{
- //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);
-
- offsets.push_back(aStops[i].offset);
+ // This all works fine with empty aStops vector
+
+ mExtend = aExtendMode;
+ if (aExtendMode == ExtendMode::CLAMP) {
+ size_t numStops = aStops.size();
+
+ std::vector<CGFloat> colors;
+ std::vector<CGFloat> offsets;
+ colors.reserve(numStops*4);
+ offsets.reserve(numStops);
+
+ for (size_t i = 0; i < numStops; 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);
+ }
+
+ mGradient = CGGradientCreateWithColorComponents(aColorSpace,
+ &colors.front(),
+ &offsets.front(),
+ offsets.size());
+ } else {
+ mStops = aStops;
}
- CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB();
- mGradient = CGGradientCreateWithColorComponents(colorSpace,
- &colors.front(),
- &offsets.front(),
- aNumStops);
- CGColorSpaceRelease(colorSpace);
}
+
virtual ~GradientStopsCG() {
+ // CGGradientRelease is OK with nullptr argument
CGGradientRelease(mGradient);
}
- // Will always report BACKEND_COREGRAPHICS, but it is compatible
- // with BACKEND_COREGRAPHICS_ACCELERATED
- BackendType GetBackendType() const { return BACKEND_COREGRAPHICS; }
+
+ // Will always report BackendType::COREGRAPHICS, but it is compatible
+ // with BackendType::COREGRAPHICS_ACCELERATED
+ BackendType GetBackendType() const { return BackendType::COREGRAPHICS; }
+ // XXX this should be a union
CGGradientRef mGradient;
+ std::vector<GradientStop> mStops;
+ ExtendMode mExtend;
};
TemporaryRef<GradientStops>
DrawTargetCG::CreateGradientStops(GradientStop *aStops, uint32_t aNumStops,
ExtendMode aExtendMode) const
{
- return new GradientStopsCG(aStops, aNumStops, aExtendMode);
+ std::vector<GradientStop> stops(aStops, aStops+aNumStops);
+ return new GradientStopsCG(mColorSpace, stops, aExtendMode);
}
static void
-DrawGradient(CGContextRef cg, const Pattern &aPattern)
+UpdateLinearParametersToIncludePoint(double *min_t, double *max_t,
+ CGPoint *start,
+ double dx, double dy,
+ double x, double y)
{
- 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 };
+ MOZ_ASSERT(IsFinite(x) && IsFinite(y));
+
+ /**
+ * Compute a parameter t such that a line perpendicular to the (dx,dy)
+ * vector, passing through (start->x + dx*t, start->y + dy*t), also
+ * passes through (x,y).
+ *
+ * Let px = x - start->x, py = y - start->y.
+ * t is given by
+ * (px - dx*t)*dx + (py - dy*t)*dy = 0
+ *
+ * Solving for t we get
+ * numerator = dx*px + dy*py
+ * denominator = dx^2 + dy^2
+ * t = numerator/denominator
+ *
+ * In CalculateRepeatingGradientParams we know the length of (dx,dy)
+ * is not zero. (This is checked in DrawLinearRepeatingGradient.)
+ */
+ double px = x - start->x;
+ double py = y - start->y;
+ double numerator = dx * px + dy * py;
+ double denominator = dx * dx + dy * dy;
+ double t = numerator / denominator;
+
+ if (*min_t > t) {
+ *min_t = t;
+ }
+ if (*max_t < t) {
+ *max_t = t;
+ }
+}
- // 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;
+/**
+ * Repeat the gradient line such that lines extended perpendicular to the
+ * gradient line at both start and end would completely enclose the drawing
+ * extents.
+ */
+static void
+CalculateRepeatingGradientParams(CGPoint *aStart, CGPoint *aEnd,
+ CGRect aExtents, int *aRepeatStartFactor,
+ int *aRepeatEndFactor)
+{
+ double t_min = INFINITY;
+ double t_max = -INFINITY;
+ double dx = aEnd->x - aStart->x;
+ double dy = aEnd->y - aStart->y;
+
+ double bounds_x1 = aExtents.origin.x;
+ double bounds_y1 = aExtents.origin.y;
+ double bounds_x2 = aExtents.origin.x + aExtents.size.width;
+ double bounds_y2 = aExtents.origin.y + aExtents.size.height;
+
+ UpdateLinearParametersToIncludePoint(&t_min, &t_max, aStart, dx, dy,
+ bounds_x1, bounds_y1);
+ UpdateLinearParametersToIncludePoint(&t_min, &t_max, aStart, dx, dy,
+ bounds_x2, bounds_y1);
+ UpdateLinearParametersToIncludePoint(&t_min, &t_max, aStart, dx, dy,
+ bounds_x2, bounds_y2);
+ UpdateLinearParametersToIncludePoint(&t_min, &t_max, aStart, dx, dy,
+ bounds_x1, bounds_y2);
+
+ MOZ_ASSERT(!isinf(t_min) && !isinf(t_max),
+ "The first call to UpdateLinearParametersToIncludePoint should have made t_min and t_max non-infinite.");
+
+ // Move t_min and t_max to the nearest usable integer to try to avoid
+ // subtle variations due to numerical instability, especially accidentally
+ // cutting off a pixel. Extending the gradient repetitions is always safe.
+ t_min = floor (t_min);
+ t_max = ceil (t_max);
+ aEnd->x = aStart->x + dx * t_max;
+ aEnd->y = aStart->y + dy * t_max;
+ aStart->x = aStart->x + dx * t_min;
+ aStart->y = aStart->y + dy * t_min;
+
+ *aRepeatStartFactor = t_min;
+ *aRepeatEndFactor = t_max;
+}
- CGContextDrawLinearGradient(cg, stops->mGradient, startPoint, endPoint,
- kCGGradientDrawsBeforeStartLocation | kCGGradientDrawsAfterEndLocation);
- } else if (aPattern.GetType() == PATTERN_RADIAL_GRADIENT) {
- const RadialGradientPattern& pat = static_cast<const RadialGradientPattern&>(aPattern);
- GradientStopsCG *stops = static_cast<GradientStopsCG*>(pat.mStops.get());
+static CGGradientRef
+CreateRepeatingGradient(CGColorSpaceRef aColorSpace,
+ CGContextRef cg, GradientStopsCG* aStops,
+ int aRepeatStartFactor, int aRepeatEndFactor,
+ bool aReflect)
+{
+ int repeatCount = aRepeatEndFactor - aRepeatStartFactor;
+ uint32_t stopCount = aStops->mStops.size();
+ double scale = 1./repeatCount;
+
+ std::vector<CGFloat> colors;
+ std::vector<CGFloat> offsets;
+ colors.reserve(stopCount*repeatCount*4);
+ offsets.reserve(stopCount*repeatCount);
+
+ for (int j = aRepeatStartFactor; j < aRepeatEndFactor; j++) {
+ bool isReflected = aReflect && (j % 2) != 0;
+ for (uint32_t i = 0; i < stopCount; i++) {
+ uint32_t stopIndex = isReflected ? stopCount - i - 1 : i;
+ colors.push_back(aStops->mStops[stopIndex].color.r);
+ colors.push_back(aStops->mStops[stopIndex].color.g);
+ colors.push_back(aStops->mStops[stopIndex].color.b);
+ colors.push_back(aStops->mStops[stopIndex].color.a);
+
+ CGFloat offset = aStops->mStops[stopIndex].offset;
+ if (isReflected) {
+ offset = 1 - offset;
+ }
+ offsets.push_back((offset + (j - aRepeatStartFactor)) * scale);
+ }
+ }
+
+ CGGradientRef gradient = CGGradientCreateWithColorComponents(aColorSpace,
+ &colors.front(),
+ &offsets.front(),
+ repeatCount*stopCount);
+ return gradient;
+}
+
+static void
+DrawLinearRepeatingGradient(CGColorSpaceRef aColorSpace, CGContextRef cg,
+ const LinearGradientPattern &aPattern,
+ const CGRect &aExtents, bool aReflect)
+{
+ GradientStopsCG *stops = static_cast<GradientStopsCG*>(aPattern.mStops.get());
+ CGPoint startPoint = { aPattern.mBegin.x, aPattern.mBegin.y };
+ CGPoint endPoint = { aPattern.mEnd.x, aPattern.mEnd.y };
+
+ int repeatStartFactor = 0, repeatEndFactor = 1;
+ // if we don't have a line then we can't extend it
+ if (aPattern.mEnd.x != aPattern.mBegin.x ||
+ aPattern.mEnd.y != aPattern.mBegin.y) {
+ CalculateRepeatingGradientParams(&startPoint, &endPoint, aExtents,
+ &repeatStartFactor, &repeatEndFactor);
+ }
- // 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;
+ CGGradientRef gradient = CreateRepeatingGradient(aColorSpace, cg, stops, repeatStartFactor, repeatEndFactor, aReflect);
- //XXX: are there degenerate radial gradients that we should avoid drawing?
- CGContextDrawRadialGradient(cg, stops->mGradient, startCenter, startRadius, endCenter, endRadius,
- kCGGradientDrawsBeforeStartLocation | kCGGradientDrawsAfterEndLocation);
+ 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(CGColorSpaceRef aColorSpace, CGContextRef cg,
+ const RadialGradientPattern &aPattern,
+ const CGRect &aExtents, bool aReflect)
+{
+ 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
+ CGFloat 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 repeatStartFactor = 0, repeatEndFactor = 1;
+ while (endRadius < minimumEndRadius) {
+ endRadius += length;
+ repeatEndFactor++;
+ }
+
+ while (startRadius-length >= 0) {
+ startRadius -= length;
+ repeatStartFactor--;
+ }
+
+ CGGradientRef gradient = CreateRepeatingGradient(aColorSpace, cg, stops, repeatStartFactor, repeatEndFactor, aReflect);
+
+ //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(CGColorSpaceRef aColorSpace,
+ CGContextRef cg, const Pattern &aPattern, const CGRect &aExtents)
+{
+ if (MOZ2D_ERROR_IF(!cg)) {
+ return;
+ }
+
+ if (CGRectIsEmpty(aExtents)) {
+ return;
+ }
+
+ if (aPattern.GetType() == PatternType::LINEAR_GRADIENT) {
+ const LinearGradientPattern& pat = static_cast<const LinearGradientPattern&>(aPattern);
+ GradientStopsCG *stops = static_cast<GradientStopsCG*>(pat.mStops.get());
+ CGAffineTransform patternMatrix = GfxMatrixToCGAffineTransform(pat.mMatrix);
+ CGContextConcatCTM(cg, patternMatrix);
+ CGRect extents = CGRectApplyAffineTransform(aExtents, CGAffineTransformInvert(patternMatrix));
+ if (stops->mExtend == ExtendMode::CLAMP) {
+
+ // 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 };
+
+ // 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 == ExtendMode::REPEAT || stops->mExtend == ExtendMode::REFLECT) {
+ DrawLinearRepeatingGradient(aColorSpace, cg, pat, extents, stops->mExtend == ExtendMode::REFLECT);
+ }
+ } else if (aPattern.GetType() == PatternType::RADIAL_GRADIENT) {
+ const RadialGradientPattern& pat = static_cast<const RadialGradientPattern&>(aPattern);
+ CGAffineTransform patternMatrix = GfxMatrixToCGAffineTransform(pat.mMatrix);
+ CGContextConcatCTM(cg, patternMatrix);
+ CGRect extents = CGRectApplyAffineTransform(aExtents, CGAffineTransformInvert(patternMatrix));
+ GradientStopsCG *stops = static_cast<GradientStopsCG*>(pat.mStops.get());
+ if (stops->mExtend == ExtendMode::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: are there degenerate radial gradients that we should avoid drawing?
+ CGContextDrawRadialGradient(cg, stops->mGradient, startCenter, startRadius, endCenter, endRadius,
+ kCGGradientDrawsBeforeStartLocation | kCGGradientDrawsAfterEndLocation);
+ } else if (stops->mExtend == ExtendMode::REPEAT || stops->mExtend == ExtendMode::REFLECT) {
+ DrawRadialRepeatingGradient(aColorSpace, cg, pat, extents, stops->mExtend == ExtendMode::REFLECT);
+ }
} else {
assert(0);
}
@@ -445,7 +753,14 @@ CGPatternCallbacks patternCallbacks = {
static bool
isGradient(const Pattern &aPattern)
{
- return aPattern.GetType() == PATTERN_LINEAR_GRADIENT || aPattern.GetType() == PATTERN_RADIAL_GRADIENT;
+ return aPattern.GetType() == PatternType::LINEAR_GRADIENT || aPattern.GetType() == PatternType::RADIAL_GRADIENT;
+}
+
+static bool
+isNonRepeatingSurface(const Pattern& aPattern)
+{
+ return aPattern.GetType() == PatternType::SURFACE &&
+ static_cast<const SurfacePattern&>(aPattern).mExtendMode != ExtendMode::REPEAT;
}
/* CoreGraphics patterns ignore the userspace transform so
@@ -455,17 +770,24 @@ CreateCGPattern(const Pattern &aPattern, CGAffineTransform aUserSpace)
{
const SurfacePattern& pat = static_cast<const SurfacePattern&>(aPattern);
// XXX: is .get correct here?
- CGImageRef image = GetImageFromSourceSurface(pat.mSurface.get());
+ CGImageRef image = GetRetainedImageFromSourceSurface(pat.mSurface.get());
+ Matrix patTransform = pat.mMatrix;
+ if (!pat.mSamplingRect.IsEmpty()) {
+ CGImageRef temp = CGImageCreateWithImageInRect(image, RectToCGRect(pat.mSamplingRect));
+ CGImageRelease(image);
+ image = temp;
+ patTransform.PreTranslate(pat.mSamplingRect.x, pat.mSamplingRect.y);
+ }
CGFloat xStep, yStep;
switch (pat.mExtendMode) {
- case EXTEND_CLAMP:
+ case ExtendMode::CLAMP:
// The 1 << 22 comes from Webkit see Pattern::createPlatformPattern() in PatternCG.cpp for more info
xStep = static_cast<CGFloat>(1 << 22);
yStep = static_cast<CGFloat>(1 << 22);
break;
- case EXTEND_REFLECT:
+ case ExtendMode::REFLECT:
assert(0);
- case EXTEND_REPEAT:
+ case ExtendMode::REPEAT:
xStep = static_cast<CGFloat>(CGImageGetWidth(image));
yStep = static_cast<CGFloat>(CGImageGetHeight(image));
// webkit uses wkCGPatternCreateWithImageAndTransform a wrapper around CGPatternCreateWithImage2
@@ -489,25 +811,29 @@ CreateCGPattern(const Pattern &aPattern, CGAffineTransform aUserSpace)
CGAffineTransform transform =
CGAffineTransformConcat(CGAffineTransformConcat(CGAffineTransformMakeScale(1,
-1),
- GfxMatrixToCGAffineTransform(pat.mMatrix)),
+ GfxMatrixToCGAffineTransform(patTransform)),
aUserSpace);
transform = CGAffineTransformTranslate(transform, 0, -static_cast<float>(CGImageGetHeight(image)));
- return CGPatternCreate(CGImageRetain(image), bounds, transform, xStep, yStep, kCGPatternTilingConstantSpacing,
+ return CGPatternCreate(image, bounds, transform, xStep, yStep, kCGPatternTilingConstantSpacing,
true, &patternCallbacks);
}
static void
SetFillFromPattern(CGContextRef cg, CGColorSpaceRef aColorSpace, const Pattern &aPattern)
{
+ if (MOZ2D_ERROR_IF(!cg)) {
+ return;
+ }
+
assert(!isGradient(aPattern));
- if (aPattern.GetType() == PATTERN_COLOR) {
+ if (aPattern.GetType() == PatternType::COLOR) {
const Color& color = static_cast<const ColorPattern&>(aPattern).mColor;
//XXX: we should cache colors
CGColorRef cgcolor = ColorToCGColor(aColorSpace, color);
CGContextSetFillColorWithColor(cg, cgcolor);
CGColorRelease(cgcolor);
- } else if (aPattern.GetType() == PATTERN_SURFACE) {
+ } else if (aPattern.GetType() == PatternType::SURFACE) {
CGColorSpaceRef patternSpace;
patternSpace = CGColorSpaceCreatePattern (nullptr);
@@ -515,6 +841,8 @@ SetFillFromPattern(CGContextRef cg, CGColorSpaceRef aColorSpace, const Pattern &
CGColorSpaceRelease(patternSpace);
CGPatternRef pattern = CreateCGPattern(aPattern, CGContextGetCTM(cg));
+ const SurfacePattern& pat = static_cast<const SurfacePattern&>(aPattern);
+ CGContextSetInterpolationQuality(cg, InterpolationQualityFromFilter(pat.mFilter));
CGFloat alpha = 1.;
CGContextSetFillPattern(cg, pattern, &alpha);
CGPatternRelease(pattern);
@@ -525,19 +853,21 @@ static void
SetStrokeFromPattern(CGContextRef cg, CGColorSpaceRef aColorSpace, const Pattern &aPattern)
{
assert(!isGradient(aPattern));
- if (aPattern.GetType() == PATTERN_COLOR) {
+ if (aPattern.GetType() == PatternType::COLOR) {
const Color& color = static_cast<const ColorPattern&>(aPattern).mColor;
//XXX: we should cache colors
CGColorRef cgcolor = ColorToCGColor(aColorSpace, color);
CGContextSetStrokeColorWithColor(cg, cgcolor);
CGColorRelease(cgcolor);
- } else if (aPattern.GetType() == PATTERN_SURFACE) {
+ } else if (aPattern.GetType() == PatternType::SURFACE) {
CGColorSpaceRef patternSpace;
patternSpace = CGColorSpaceCreatePattern (nullptr);
CGContextSetStrokeColorSpace(cg, patternSpace);
CGColorSpaceRelease(patternSpace);
CGPatternRef pattern = CreateCGPattern(aPattern, CGContextGetCTM(cg));
+ const SurfacePattern& pat = static_cast<const SurfacePattern&>(aPattern);
+ CGContextSetInterpolationQuality(cg, InterpolationQualityFromFilter(pat.mFilter));
CGFloat alpha = 1.;
CGContextSetStrokePattern(cg, pattern, &alpha);
CGPatternRelease(pattern);
@@ -545,26 +875,112 @@ SetStrokeFromPattern(CGContextRef cg, CGColorSpaceRef aColorSpace, const Pattern
}
+void
+DrawTargetCG::MaskSurface(const Pattern &aSource,
+ SourceSurface *aMask,
+ Point aOffset,
+ const DrawOptions &aDrawOptions)
+{
+ if (MOZ2D_ERROR_IF(!mCg)) {
+ return;
+ }
+
+ MarkChanged();
+
+ CGContextSaveGState(mCg);
+
+ CGContextSetBlendMode(mCg, ToBlendMode(aDrawOptions.mCompositionOp));
+ UnboundnessFixer fixer;
+ CGContextRef cg = fixer.Check(mCg, aDrawOptions.mCompositionOp);
+ if (MOZ2D_ERROR_IF(!cg)) {
+ return;
+ }
+
+ CGContextSetAlpha(cg, aDrawOptions.mAlpha);
+ CGContextSetShouldAntialias(cg, aDrawOptions.mAntialiasMode != AntialiasMode::NONE);
+
+ CGContextConcatCTM(cg, GfxMatrixToCGAffineTransform(mTransform));
+ CGImageRef image = GetRetainedImageFromSourceSurface(aMask);
+
+ // use a negative-y so that the mask image draws right ways up
+ CGContextScaleCTM(cg, 1, -1);
+
+ IntSize size = aMask->GetSize();
+
+ CGContextClipToMask(cg, CGRectMake(aOffset.x, -(aOffset.y + size.height), size.width, size.height), image);
+
+ CGContextScaleCTM(cg, 1, -1);
+ if (isGradient(aSource)) {
+ // we shouldn't need to clip to an additional rectangle
+ // as the cliping to the mask should be sufficient.
+ DrawGradient(mColorSpace, 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));
+ }
+
+ CGImageRelease(image);
+
+ fixer.Fix(mCg);
+
+ CGContextRestoreGState(mCg);
+}
+
+
void
DrawTargetCG::FillRect(const Rect &aRect,
- const Pattern &aPattern,
- const DrawOptions &aDrawOptions)
+ const Pattern &aPattern,
+ const DrawOptions &aDrawOptions)
{
+ if (MOZ2D_ERROR_IF(!mCg)) {
+ return;
+ }
+
MarkChanged();
CGContextSaveGState(mCg);
UnboundnessFixer fixer;
- CGContextRef cg = fixer.Check(mCg, aDrawOptions.mCompositionOp);
+ CGContextRef cg = fixer.Check(mCg, aDrawOptions.mCompositionOp, &aRect);
+ if (MOZ2D_ERROR_IF(!cg)) {
+ return;
+ }
+
CGContextSetAlpha(mCg, aDrawOptions.mAlpha);
+ CGContextSetShouldAntialias(cg, aDrawOptions.mAntialiasMode != AntialiasMode::NONE);
CGContextSetBlendMode(mCg, ToBlendMode(aDrawOptions.mCompositionOp));
CGContextConcatCTM(cg, GfxMatrixToCGAffineTransform(mTransform));
if (isGradient(aPattern)) {
CGContextClipToRect(cg, RectToCGRect(aRect));
- DrawGradient(cg, aPattern);
+ CGRect clipBounds = CGContextGetClipBoundingBox(cg);
+ DrawGradient(mColorSpace, cg, aPattern, clipBounds);
+ } else if (isNonRepeatingSurface(aPattern)) {
+ // SetFillFromPattern can handle this case but using CGContextDrawImage
+ // should give us better performance, better output, smaller PDF and
+ // matches what cairo does.
+ const SurfacePattern& pat = static_cast<const SurfacePattern&>(aPattern);
+ CGImageRef image = GetRetainedImageFromSourceSurface(pat.mSurface.get());
+ Matrix transform = pat.mMatrix;
+ if (!pat.mSamplingRect.IsEmpty()) {
+ CGImageRef temp = CGImageCreateWithImageInRect(image, RectToCGRect(pat.mSamplingRect));
+ CGImageRelease(image);
+ image = temp;
+ transform.PreTranslate(pat.mSamplingRect.x, pat.mSamplingRect.y);
+ }
+ CGContextClipToRect(cg, RectToCGRect(aRect));
+ CGContextConcatCTM(cg, GfxMatrixToCGAffineTransform(transform));
+ CGContextTranslateCTM(cg, 0, CGImageGetHeight(image));
+ CGContextScaleCTM(cg, 1, -1);
+
+ CGRect imageRect = CGRectMake(0, 0, CGImageGetWidth(image), CGImageGetHeight(image));
+
+ CGContextSetInterpolationQuality(cg, InterpolationQualityFromFilter(pat.mFilter));
+
+ CGContextDrawImage(cg, imageRect, image);
+ CGImageRelease(image);
} else {
SetFillFromPattern(cg, mColorSpace, aPattern);
CGContextFillRect(cg, RectToCGRect(aRect));
@@ -584,13 +1000,21 @@ DrawTargetCG::StrokeLine(const Point &p1, const Point &p2, const Pattern &aPatte
return;
}
+ if (MOZ2D_ERROR_IF(!mCg)) {
+ return;
+ }
+
MarkChanged();
CGContextSaveGState(mCg);
UnboundnessFixer fixer;
CGContextRef cg = fixer.Check(mCg, aDrawOptions.mCompositionOp);
+ if (MOZ2D_ERROR_IF(!cg)) {
+ return;
+ }
CGContextSetAlpha(mCg, aDrawOptions.mAlpha);
+ CGContextSetShouldAntialias(cg, aDrawOptions.mAntialiasMode != AntialiasMode::NONE);
CGContextSetBlendMode(mCg, ToBlendMode(aDrawOptions.mCompositionOp));
CGContextConcatCTM(cg, GfxMatrixToCGAffineTransform(mTransform));
@@ -603,9 +1027,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(mColorSpace, cg, aPattern, extents);
} else {
SetStrokeFromPattern(cg, mColorSpace, aPattern);
CGContextStrokePath(cg);
@@ -615,12 +1040,31 @@ DrawTargetCG::StrokeLine(const Point &p1, const Point &p2, const Pattern &aPatte
CGContextRestoreGState(mCg);
}
+static bool
+IsInteger(Float aValue)
+{
+ return floorf(aValue) == aValue;
+}
+
+static bool
+IsPixelAlignedStroke(const Rect& aRect, Float aLineWidth)
+{
+ Float halfWidth = aLineWidth/2;
+ return IsInteger(aLineWidth) &&
+ IsInteger(aRect.x - halfWidth) && IsInteger(aRect.y - halfWidth) &&
+ IsInteger(aRect.XMost() - halfWidth) && IsInteger(aRect.YMost() - halfWidth);
+}
+
void
DrawTargetCG::StrokeRect(const Rect &aRect,
const Pattern &aPattern,
const StrokeOptions &aStrokeOptions,
const DrawOptions &aDrawOptions)
{
+ if (MOZ2D_ERROR_IF(!mCg)) {
+ return;
+ }
+
if (!aRect.IsFinite()) {
return;
}
@@ -631,39 +1075,52 @@ DrawTargetCG::StrokeRect(const Rect &aRect,
UnboundnessFixer fixer;
CGContextRef cg = fixer.Check(mCg, aDrawOptions.mCompositionOp);
+ if (MOZ2D_ERROR_IF(!cg)) {
+ return;
+ }
CGContextSetAlpha(mCg, aDrawOptions.mAlpha);
CGContextSetBlendMode(mCg, ToBlendMode(aDrawOptions.mCompositionOp));
+ // Work around Quartz bug where antialiasing causes corner pixels to be off by
+ // 1 channel value (e.g. rgb(1,1,1) values appear at the corner of solid
+ // black stroke), by turning off antialiasing when the edges of the stroke
+ // are pixel-aligned. Note that when a transform's components are all
+ // integers, it maps integers coordinates to integer coordinates.
+ bool pixelAlignedStroke = mTransform.IsAllIntegers() &&
+ mTransform.PreservesAxisAlignedRectangles() &&
+ aPattern.GetType() == PatternType::COLOR &&
+ IsPixelAlignedStroke(aRect, aStrokeOptions.mLineWidth);
+ CGContextSetShouldAntialias(cg,
+ aDrawOptions.mAntialiasMode != AntialiasMode::NONE && !pixelAlignedStroke);
+
CGContextConcatCTM(cg, GfxMatrixToCGAffineTransform(mTransform));
- // we don't need to set all of the stroke state because
- // it doesn't apply when stroking rects
- switch (aStrokeOptions.mLineJoin)
- {
- case JOIN_BEVEL:
- CGContextSetLineJoin(cg, kCGLineJoinBevel);
- break;
- case JOIN_ROUND:
- CGContextSetLineJoin(cg, kCGLineJoinRound);
- break;
- case JOIN_MITER:
- case JOIN_MITER_OR_BEVEL:
- CGContextSetLineJoin(cg, kCGLineJoinMiter);
- break;
- }
- CGContextSetLineWidth(cg, aStrokeOptions.mLineWidth);
+ SetStrokeOptions(cg, aStrokeOptions);
if (isGradient(aPattern)) {
// There's no CGContextClipStrokeRect so we do it by hand
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(mColorSpace, cg, aPattern, extents);
} else {
SetStrokeFromPattern(cg, mColorSpace, aPattern);
- CGContextStrokeRect(cg, RectToCGRect(aRect));
+ // We'd like to use CGContextStrokeRect(cg, RectToCGRect(aRect));
+ // Unfortunately, newer versions of OS X no longer start at the top-left
+ // corner and stroke clockwise as older OS X versions and all the other
+ // Moz2D backends do. (Newer versions start at the top right-hand corner
+ // and stroke counter-clockwise.) For consistency we draw the rect by hand.
+ CGRect rect = RectToCGRect(aRect);
+ CGContextBeginPath(cg);
+ CGContextMoveToPoint(cg, CGRectGetMinX(rect), CGRectGetMinY(rect));
+ CGContextAddLineToPoint(cg, CGRectGetMaxX(rect), CGRectGetMinY(rect));
+ CGContextAddLineToPoint(cg, CGRectGetMaxX(rect), CGRectGetMaxY(rect));
+ CGContextAddLineToPoint(cg, CGRectGetMinX(rect), CGRectGetMaxY(rect));
+ CGContextClosePath(cg);
+ CGContextStrokePath(cg);
}
fixer.Fix(mCg);
@@ -674,6 +1131,10 @@ DrawTargetCG::StrokeRect(const Rect &aRect,
void
DrawTargetCG::ClearRect(const Rect &aRect)
{
+ if (MOZ2D_ERROR_IF(!mCg)) {
+ return;
+ }
+
MarkChanged();
CGContextSaveGState(mCg);
@@ -687,6 +1148,10 @@ DrawTargetCG::ClearRect(const Rect &aRect)
void
DrawTargetCG::Stroke(const Path *aPath, const Pattern &aPattern, const StrokeOptions &aStrokeOptions, const DrawOptions &aDrawOptions)
{
+ if (MOZ2D_ERROR_IF(!mCg)) {
+ return;
+ }
+
if (!aPath->GetBounds().IsFinite()) {
return;
}
@@ -697,7 +1162,12 @@ DrawTargetCG::Stroke(const Path *aPath, const Pattern &aPattern, const StrokeOpt
UnboundnessFixer fixer;
CGContextRef cg = fixer.Check(mCg, aDrawOptions.mCompositionOp);
+ if (MOZ2D_ERROR_IF(!cg)) {
+ return;
+ }
+
CGContextSetAlpha(mCg, aDrawOptions.mAlpha);
+ CGContextSetShouldAntialias(cg, aDrawOptions.mAntialiasMode != AntialiasMode::NONE);
CGContextSetBlendMode(mCg, ToBlendMode(aDrawOptions.mCompositionOp));
CGContextConcatCTM(cg, GfxMatrixToCGAffineTransform(mTransform));
@@ -705,7 +1175,7 @@ DrawTargetCG::Stroke(const Path *aPath, const Pattern &aPattern, const StrokeOpt
CGContextBeginPath(cg);
- assert(aPath->GetBackendType() == BACKEND_COREGRAPHICS);
+ assert(aPath->GetBackendType() == BackendType::COREGRAPHICS);
const PathCG *cgPath = static_cast<const PathCG*>(aPath);
CGContextAddPath(cg, cgPath->GetPath());
@@ -713,9 +1183,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(mColorSpace, cg, aPattern, extents);
} else {
// XXX: we could put fill mode into the path fill rule if we wanted
@@ -730,16 +1201,25 @@ DrawTargetCG::Stroke(const Path *aPath, const Pattern &aPattern, const StrokeOpt
void
DrawTargetCG::Fill(const Path *aPath, const Pattern &aPattern, const DrawOptions &aDrawOptions)
{
+ if (MOZ2D_ERROR_IF(!mCg)) {
+ return;
+ }
+
MarkChanged();
- assert(aPath->GetBackendType() == BACKEND_COREGRAPHICS);
+ assert(aPath->GetBackendType() == BackendType::COREGRAPHICS);
CGContextSaveGState(mCg);
CGContextSetBlendMode(mCg, ToBlendMode(aDrawOptions.mCompositionOp));
UnboundnessFixer fixer;
CGContextRef cg = fixer.Check(mCg, aDrawOptions.mCompositionOp);
+ if (MOZ2D_ERROR_IF(!cg)) {
+ return;
+ }
+
CGContextSetAlpha(cg, aDrawOptions.mAlpha);
+ CGContextSetShouldAntialias(cg, aDrawOptions.mAntialiasMode != AntialiasMode::NONE);
CGContextConcatCTM(cg, GfxMatrixToCGAffineTransform(mTransform));
@@ -749,25 +1229,28 @@ 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());
- if (cgPath->GetFillRule() == FILL_EVEN_ODD)
+ extents = CGContextGetPathBoundingBox(cg);
+ if (cgPath->GetFillRule() == FillRule::FILL_EVEN_ODD)
CGContextEOClip(mCg);
else
CGContextClip(mCg);
}
- DrawGradient(cg, aPattern);
+ DrawGradient(mColorSpace, cg, aPattern, extents);
} else {
CGContextAddPath(cg, cgPath->GetPath());
SetFillFromPattern(cg, mColorSpace, aPattern);
- if (cgPath->GetFillRule() == FILL_EVEN_ODD)
+ if (cgPath->GetFillRule() == FillRule::FILL_EVEN_ODD)
CGContextEOFillPath(cg);
else
CGContextFillPath(cg);
@@ -777,34 +1260,116 @@ 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;
+}
+
+typedef void (*CGContextSetFontSmoothingBackgroundColorFunc) (CGContextRef cgContext, CGColorRef color);
+
+static CGContextSetFontSmoothingBackgroundColorFunc
+GetCGContextSetFontSmoothingBackgroundColorFunc()
+{
+ static CGContextSetFontSmoothingBackgroundColorFunc func = nullptr;
+ static bool lookedUpFunc = false;
+ if (!lookedUpFunc) {
+ func = (CGContextSetFontSmoothingBackgroundColorFunc)dlsym(
+ RTLD_DEFAULT, "CGContextSetFontSmoothingBackgroundColor");
+ lookedUpFunc = true;
+ }
+ return func;
+}
void
DrawTargetCG::FillGlyphs(ScaledFont *aFont, const GlyphBuffer &aBuffer, const Pattern &aPattern, const DrawOptions &aDrawOptions,
- const GlyphRenderingOptions*)
+ const GlyphRenderingOptions *aGlyphRenderingOptions)
{
+ if (MOZ2D_ERROR_IF(!mCg)) {
+ return;
+ }
+
MarkChanged();
assert(aBuffer.mNumGlyphs);
CGContextSaveGState(mCg);
+ if (aGlyphRenderingOptions && aGlyphRenderingOptions->GetType() == FontType::MAC) {
+ Color fontSmoothingBackgroundColor =
+ static_cast<const GlyphRenderingOptionsCG*>(aGlyphRenderingOptions)->FontSmoothingBackgroundColor();
+ if (fontSmoothingBackgroundColor.a > 0) {
+ CGContextSetFontSmoothingBackgroundColorFunc setFontSmoothingBGColorFunc =
+ GetCGContextSetFontSmoothingBackgroundColorFunc();
+ if (setFontSmoothingBGColorFunc) {
+ CGColorRef color = ColorToCGColor(mColorSpace, fontSmoothingBackgroundColor);
+ setFontSmoothingBGColorFunc(mCg, color);
+ CGColorRelease(color);
+
+ // Font rendering with a non-transparent font smoothing background color
+ // can leave pixels in our buffer where the rgb components exceed the alpha
+ // component. When this happens we need to clean up the data afterwards.
+ // The purpose of this is probably the following: Correct compositing of
+ // subpixel anti-aliased fonts on transparent backgrounds requires
+ // different alpha values per RGB component. Usually, premultiplied color
+ // values are derived by multiplying all components with the same per-pixel
+ // alpha value. However, if you multiply each component with a *different*
+ // alpha, and set the alpha component of the pixel to, say, the average
+ // of the alpha values that you used during the premultiplication of the
+ // RGB components, you can trick OVER compositing into doing a simplified
+ // form of component alpha compositing. (You just need to make sure to
+ // clamp the components of the result pixel to [0,255] afterwards.)
+ mMayContainInvalidPremultipliedData = true;
+ }
+ }
+ }
+
CGContextSetBlendMode(mCg, ToBlendMode(aDrawOptions.mCompositionOp));
UnboundnessFixer fixer;
CGContextRef cg = fixer.Check(mCg, aDrawOptions.mCompositionOp);
+ if (MOZ2D_ERROR_IF(!cg)) {
+ return;
+ }
+
CGContextSetAlpha(cg, aDrawOptions.mAlpha);
+ CGContextSetShouldAntialias(cg, aDrawOptions.mAntialiasMode != AntialiasMode::NONE);
+ if (aDrawOptions.mAntialiasMode != AntialiasMode::DEFAULT) {
+ CGContextSetShouldSmoothFonts(cg, aDrawOptions.mAntialiasMode == AntialiasMode::SUBPIXEL);
+ }
CGContextConcatCTM(cg, GfxMatrixToCGAffineTransform(mTransform));
ScaledFontMac* macFont = static_cast<ScaledFontMac*>(aFont);
- //XXX: we should use a stack vector here when we have a class like that
- std::vector<CGGlyph> glyphs;
- std::vector<CGPoint> positions;
- glyphs.resize(aBuffer.mNumGlyphs);
- positions.resize(aBuffer.mNumGlyphs);
+ // This code can execute millions of times in short periods, so we want to
+ // avoid heap allocation whenever possible. So we use an inline vector
+ // capacity of 64 elements, which is enough to typically avoid heap
+ // allocation in ~99% of cases.
+ Vector<CGGlyph, 64> glyphs;
+ Vector<CGPoint, 64> positions;
+ if (!glyphs.resizeUninitialized(aBuffer.mNumGlyphs) ||
+ !positions.resizeUninitialized(aBuffer.mNumGlyphs)) {
+ MOZ_CRASH("glyphs/positions allocation failed");
+ }
// Handle the flip
- CGAffineTransform matrix = CGAffineTransformMakeScale(1, -1);
- CGContextConcatCTM(cg, matrix);
+ CGContextScaleCTM(cg, 1, -1);
// CGContextSetTextMatrix works differently with kCGTextClip && kCGTextFill
// It seems that it transforms the positions with TextFill and not with TextClip
// Therefore we'll avoid it. See also:
@@ -820,30 +1385,41 @@ 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) {
- ScaledFontMac::CTFontDrawGlyphsPtr(macFont->mCTFont, &glyphs.front(),
- &positions.front(),
- aBuffer.mNumGlyphs, cg);
+ CGRect *bboxes = new CGRect[aBuffer.mNumGlyphs];
+ CTFontGetBoundingRectsForGlyphs(macFont->mCTFont, kCTFontDefaultOrientation,
+ glyphs.begin(), bboxes, aBuffer.mNumGlyphs);
+ extents = ComputeGlyphsExtents(bboxes, positions.begin(), aBuffer.mNumGlyphs, 1.0f);
+ ScaledFontMac::CTFontDrawGlyphsPtr(macFont->mCTFont, glyphs.begin(),
+ positions.begin(), aBuffer.mNumGlyphs, cg);
+ delete[] bboxes;
} else {
+ CGRect *bboxes = new CGRect[aBuffer.mNumGlyphs];
+ CGFontGetGlyphBBoxes(macFont->mFont, glyphs.begin(), aBuffer.mNumGlyphs, bboxes);
+ extents = ComputeGlyphsExtents(bboxes, positions.begin(), aBuffer.mNumGlyphs, macFont->mSize);
+
CGContextSetFont(cg, macFont->mFont);
CGContextSetFontSize(cg, macFont->mSize);
- CGContextShowGlyphsAtPositions(cg, &glyphs.front(), &positions.front(),
+ CGContextShowGlyphsAtPositions(cg, glyphs.begin(), positions.begin(),
aBuffer.mNumGlyphs);
+ delete[] bboxes;
}
- DrawGradient(cg, aPattern);
+ CGContextScaleCTM(cg, 1, -1);
+ DrawGradient(mColorSpace, 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
CGContextSetTextDrawingMode(cg, kCGTextFill);
SetFillFromPattern(cg, mColorSpace, aPattern);
if (ScaledFontMac::CTFontDrawGlyphsPtr != nullptr) {
- ScaledFontMac::CTFontDrawGlyphsPtr(macFont->mCTFont, &glyphs.front(),
- &positions.front(),
+ ScaledFontMac::CTFontDrawGlyphsPtr(macFont->mCTFont, glyphs.begin(),
+ positions.begin(),
aBuffer.mNumGlyphs, cg);
} else {
CGContextSetFont(cg, macFont->mFont);
CGContextSetFontSize(cg, macFont->mSize);
- CGContextShowGlyphsAtPositions(cg, &glyphs.front(), &positions.front(),
+ CGContextShowGlyphsAtPositions(cg, glyphs.begin(), positions.begin(),
aBuffer.mNumGlyphs);
}
}
@@ -862,47 +1438,56 @@ DrawTargetCG::CopySurface(SourceSurface *aSurface,
const IntRect& aSourceRect,
const IntPoint &aDestination)
{
+ if (MOZ2D_ERROR_IF(!mCg)) {
+ return;
+ }
+
MarkChanged();
- CGImageRef image;
- CGImageRef subimage = nullptr;
- if (aSurface->GetType() == SURFACE_COREGRAPHICS_IMAGE) {
- image = GetImageFromSourceSurface(aSurface);
- /* we have two options here:
- * - create a subimage -- this is slower
- * - fancy things with clip and different dest rects */
- {
- subimage = CGImageCreateWithImageInRect(image, IntRectToCGRect(aSourceRect));
- image = subimage;
- }
+ if (aSurface->GetType() == SurfaceType::COREGRAPHICS_IMAGE ||
+ aSurface->GetType() == SurfaceType::COREGRAPHICS_CGCONTEXT ||
+ aSurface->GetType() == SurfaceType::DATA) {
+ CGImageRef image = GetRetainedImageFromSourceSurface(aSurface);
+
// XXX: it might be more efficient for us to do the copy directly if we have access to the bits
CGContextSaveGState(mCg);
// CopySurface ignores the clip, so we need to use private API to temporarily reset it
CGContextResetClip(mCg);
+ CGRect destRect = CGRectMake(aDestination.x, aDestination.y,
+ aSourceRect.width, aSourceRect.height);
+ CGContextClipToRect(mCg, destRect);
+
CGContextSetBlendMode(mCg, kCGBlendModeCopy);
CGContextScaleCTM(mCg, 1, -1);
- CGRect flippedRect = CGRectMake(aDestination.x, -(aDestination.y + aSourceRect.height),
- aSourceRect.width, aSourceRect.height);
+ CGRect flippedRect = CGRectMake(aDestination.x - aSourceRect.x, -(aDestination.y - aSourceRect.y + double(CGImageGetHeight(image))),
+ CGImageGetWidth(image), CGImageGetHeight(image));
+ // Quartz seems to copy A8 surfaces incorrectly if we don't initialize them
+ // to transparent first.
+ if (mFormat == SurfaceFormat::A8) {
+ CGContextClearRect(mCg, flippedRect);
+ }
CGContextDrawImage(mCg, flippedRect, image);
CGContextRestoreGState(mCg);
-
- CGImageRelease(subimage);
+ CGImageRelease(image);
}
}
void
DrawTargetCG::DrawSurfaceWithShadow(SourceSurface *aSurface, const Point &aDest, const Color &aColor, const Point &aOffset, Float aSigma, CompositionOp aOperator)
{
+ if (MOZ2D_ERROR_IF(!mCg)) {
+ return;
+ }
+
MarkChanged();
- CGImageRef image;
- image = GetImageFromSourceSurface(aSurface);
+ CGImageRef image = GetRetainedImageFromSourceSurface(aSurface);
IntSize size = aSurface->GetSize();
CGContextSaveGState(mCg);
@@ -922,6 +1507,7 @@ DrawTargetCG::DrawSurfaceWithShadow(SourceSurface *aSurface, const Point &aDest,
CGContextDrawImage(mCg, flippedRect, image);
+ CGImageRelease(image);
CGContextRestoreGState(mCg);
}
@@ -935,13 +1521,14 @@ DrawTargetCG::Init(BackendType aType,
{
// XXX: we should come up with some consistent semantics for dealing
// with zero area drawtargets
- if (aSize.width <= 0 || aSize.height <= 0 ||
- // 32767 is the maximum size supported by cairo
- // we clamp to that to make it easier to interoperate
- aSize.width > 32767 || aSize.height > 32767) {
+ if (aSize.width <= 0 ||
+ aSize.height <= 0 ||
+ size_t(aSize.width) > GetMaxSurfaceSize() ||
+ size_t(aSize.height) > GetMaxSurfaceSize())
+ {
+ gfxWarning() << "Failed to Init() DrawTargetCG because of bad size.";
mColorSpace = nullptr;
mCg = nullptr;
- mData = nullptr;
return false;
}
@@ -950,32 +1537,50 @@ DrawTargetCG::Init(BackendType aType,
//XXX: we'd be better off reusing the Colorspace across draw targets
mColorSpace = CGColorSpaceCreateDeviceRGB();
- if (aData == nullptr && aType != BACKEND_COREGRAPHICS_ACCELERATED) {
+ if (aData == nullptr && aType != BackendType::COREGRAPHICS_ACCELERATED) {
// XXX: Currently, Init implicitly clears, that can often be a waste of time
- mData = calloc(aSize.height * aStride, 1);
- aData = static_cast<unsigned char*>(mData);
- } else {
- // mData == nullptr means DrawTargetCG doesn't own the image data and will not
- // delete it in the destructor
- mData = nullptr;
+ size_t bufLen = BufferSizeFromStrideAndHeight(aStride, aSize.height);
+ if (bufLen == 0) {
+ mColorSpace = nullptr;
+ mCg = nullptr;
+ return false;
+ }
+ static_assert(sizeof(decltype(mData[0])) == 1,
+ "mData.Realloc() takes an object count, so its objects must be 1-byte sized if we use bufLen");
+ mData.Realloc(/* actually an object count */ bufLen, true);
+ aData = static_cast<unsigned char*>(mData);
}
mSize = aSize;
- if (aType == BACKEND_COREGRAPHICS_ACCELERATED) {
+ if (aType == BackendType::COREGRAPHICS_ACCELERATED) {
RefPtr<MacIOSurface> ioSurface = MacIOSurface::CreateIOSurface(aSize.width, aSize.height);
mCg = ioSurface->CreateIOSurfaceContext();
// If we don't have the symbol for 'CreateIOSurfaceContext' mCg will be null
// and we will fallback to software below
- mData = nullptr;
}
- if (!mCg || aType == BACKEND_COREGRAPHICS) {
+ mFormat = SurfaceFormat::B8G8R8A8;
+
+ if (!mCg || aType == BackendType::COREGRAPHICS) {
int bitsPerComponent = 8;
CGBitmapInfo bitinfo;
- bitinfo = kCGBitmapByteOrder32Host | kCGImageAlphaPremultipliedFirst;
-
+ if (aFormat == SurfaceFormat::A8) {
+ if (mColorSpace)
+ CGColorSpaceRelease(mColorSpace);
+ mColorSpace = nullptr;
+ bitinfo = kCGImageAlphaOnly;
+ mFormat = SurfaceFormat::A8;
+ } else {
+ bitinfo = kCGBitmapByteOrder32Host;
+ if (aFormat == SurfaceFormat::B8G8R8X8) {
+ bitinfo |= kCGImageAlphaNoneSkipFirst;
+ mFormat = aFormat;
+ } else {
+ bitinfo |= kCGImageAlphaPremultipliedFirst;
+ }
+ }
// XXX: what should we do if this fails?
mCg = CGBitmapContextCreate (aData,
mSize.width,
@@ -987,6 +1592,11 @@ DrawTargetCG::Init(BackendType aType,
}
assert(mCg);
+ if (!mCg) {
+ gfxCriticalError() << "Failed to create CG context";
+ return false;
+ }
+
// CGContext's default to have the origin at the bottom left
// so flip it to the top left
CGContextTranslateCTM(mCg, 0, mSize.height);
@@ -1000,10 +1610,8 @@ DrawTargetCG::Init(BackendType aType,
// use the default for content.
CGContextSetInterpolationQuality(mCg, kCGInterpolationLow);
- // XXX: set correct format
- mFormat = FORMAT_B8G8R8A8;
- if (aType == BACKEND_COREGRAPHICS_ACCELERATED) {
+ if (aType == BackendType::COREGRAPHICS_ACCELERATED) {
// The bitmap backend uses callac to clear, we can't do that without
// reading back the surface. This should trigger something equivilent
// to glClear.
@@ -1013,10 +1621,55 @@ DrawTargetCG::Init(BackendType aType,
return true;
}
+static void
+EnsureValidPremultipliedData(CGContextRef aContext)
+{
+ if (CGBitmapContextGetBitsPerPixel(aContext) != 32 ||
+ CGBitmapContextGetAlphaInfo(aContext) != kCGImageAlphaPremultipliedFirst) {
+ return;
+ }
+
+ uint8_t* bitmapData = (uint8_t*)CGBitmapContextGetData(aContext);
+ int w = CGBitmapContextGetWidth(aContext);
+ int h = CGBitmapContextGetHeight(aContext);
+ int stride = CGBitmapContextGetBytesPerRow(aContext);
+ for (int y = 0; y < h; y++) {
+ for (int x = 0; x < w; x++) {
+ int i = y * stride + x * 4;
+ uint8_t a = bitmapData[i + 3];
+
+ // Clamp rgb components to the alpha component.
+ if (bitmapData[i + 0] > a) {
+ bitmapData[i + 0] = a;
+ }
+ if (bitmapData[i + 1] > a) {
+ bitmapData[i + 1] = a;
+ }
+ if (bitmapData[i + 2] > a) {
+ bitmapData[i + 2] = a;
+ }
+ }
+ }
+}
+
void
DrawTargetCG::Flush()
{
- CGContextFlush(mCg);
+ if (GetContextType(mCg) == CG_CONTEXT_TYPE_IOSURFACE) {
+ CGContextFlush(mCg);
+ } else if (GetContextType(mCg) == CG_CONTEXT_TYPE_BITMAP &&
+ mMayContainInvalidPremultipliedData) {
+ // We can't guarantee that all our users can handle pixel data where an RGB
+ // component value exceeds the pixel's alpha value. In particular, the
+ // color conversion that CG does when we draw a CGImage snapshot of this
+ // context into a context that has a different color space throws up on
+ // invalid premultiplied data and creates completely wrong colors.
+ // Sanitizing the data means that we lose some of the fake component alpha
+ // behavior that font rendering tries to give us, but the result still
+ // looks good enough to prefer it over grayscale font anti-aliasing.
+ EnsureValidPremultipliedData(mCg);
+ mMayContainInvalidPremultipliedData = false;
+ }
}
bool
@@ -1027,7 +1680,6 @@ DrawTargetCG::Init(CGContextRef cgContext, const IntSize &aSize)
if (aSize.width == 0 || aSize.height == 0) {
mColorSpace = nullptr;
mCg = nullptr;
- mData = nullptr;
return false;
}
@@ -1039,17 +1691,33 @@ DrawTargetCG::Init(CGContextRef cgContext, const IntSize &aSize)
mSize = aSize;
mCg = cgContext;
-
- mData = nullptr;
+ CGContextRetain(mCg);
assert(mCg);
- // CGContext's default to have the origin at the bottom left
- // so flip it to the top left
- CGContextTranslateCTM(mCg, 0, mSize.height);
- CGContextScaleCTM(mCg, 1, -1);
+ if (!mCg) {
+ gfxCriticalError() << "Invalid CG context at Init";
+ return false;
+ }
- //XXX: set correct format
- mFormat = FORMAT_B8G8R8A8;
+ // CGContext's default to have the origin at the bottom left.
+ // However, currently the only use of this function is to construct a
+ // DrawTargetCG around a CGContextRef from a cairo quartz surface which
+ // already has it's origin adjusted.
+ //
+ // CGContextTranslateCTM(mCg, 0, mSize.height);
+ // CGContextScaleCTM(mCg, 1, -1);
+
+ mFormat = SurfaceFormat::B8G8R8A8;
+ if (GetContextType(mCg) == CG_CONTEXT_TYPE_BITMAP) {
+ CGColorSpaceRef colorspace;
+ CGBitmapInfo bitinfo = CGBitmapContextGetBitmapInfo(mCg);
+ colorspace = CGBitmapContextGetColorSpace (mCg);
+ if (CGColorSpaceGetNumberOfComponents(colorspace) == 1) {
+ mFormat = SurfaceFormat::A8;
+ } else if ((bitinfo & kCGBitmapAlphaInfoMask) == kCGImageAlphaNoneSkipFirst) {
+ mFormat = SurfaceFormat::B8G8R8X8;
+ }
+ }
return true;
}
@@ -1057,7 +1725,7 @@ DrawTargetCG::Init(CGContextRef cgContext, const IntSize &aSize)
bool
DrawTargetCG::Init(BackendType aType, const IntSize &aSize, SurfaceFormat &aFormat)
{
- int stride = aSize.width*4;
+ int32_t stride = GetAlignedStride<16>(aSize.width * BytesPerPixel(aFormat));
// Calling Init with aData == nullptr will allocate.
return Init(aType, nullptr, aSize, stride, aFormat);
@@ -1066,15 +1734,14 @@ DrawTargetCG::Init(BackendType aType, const IntSize &aSize, SurfaceFormat &aForm
TemporaryRef<PathBuilder>
DrawTargetCG::CreatePathBuilder(FillRule aFillRule) const
{
- RefPtr<PathBuilderCG> pb = new PathBuilderCG(aFillRule);
- return pb;
+ return new PathBuilderCG(aFillRule);
}
void*
DrawTargetCG::GetNativeSurface(NativeSurfaceType aType)
{
- if ((aType == NATIVE_SURFACE_CGCONTEXT && GetContextType(mCg) == CG_CONTEXT_TYPE_BITMAP) ||
- (aType == NATIVE_SURFACE_CGCONTEXT_ACCELERATED && GetContextType(mCg) == CG_CONTEXT_TYPE_IOSURFACE)) {
+ if ((aType == NativeSurfaceType::CGCONTEXT && GetContextType(mCg) == CG_CONTEXT_TYPE_BITMAP) ||
+ (aType == NativeSurfaceType::CGCONTEXT_ACCELERATED && GetContextType(mCg) == CG_CONTEXT_TYPE_IOSURFACE)) {
return mCg;
} else {
return nullptr;
@@ -1093,7 +1760,7 @@ DrawTargetCG::Mask(const Pattern &aSource,
if (isGradient(aMask)) {
assert(0);
} else {
- if (aMask.GetType() == PATTERN_COLOR) {
+ if (aMask.GetType() == PatternType::COLOR) {
DrawOptions drawOptions(aDrawOptions);
const Color& color = static_cast<const ColorPattern&>(aMask).mColor;
drawOptions.mAlpha *= color.a;
@@ -1101,13 +1768,15 @@ DrawTargetCG::Mask(const Pattern &aSource,
// XXX: we need to get a rect that when transformed covers the entire surface
//Rect
//FillRect(rect, aSource, drawOptions);
- } else if (aMask.GetType() == PATTERN_SURFACE) {
+ } else if (aMask.GetType() == PatternType::SURFACE) {
const SurfacePattern& pat = static_cast<const SurfacePattern&>(aMask);
- CGImageRef mask = GetImageFromSourceSurface(pat.mSurface.get());
+ CGImageRef mask = GetRetainedImageFromSourceSurface(pat.mSurface.get());
+ MOZ_ASSERT(pat.mSamplingRect.IsEmpty(), "Sampling rect not supported with masks!");
Rect rect(0,0, CGImageGetWidth(mask), CGImageGetHeight(mask));
// XXX: probably we need to do some flipping of the image or something
CGContextClipToMask(mCg, RectToCGRect(rect), mask);
FillRect(rect, aSource, aDrawOptions);
+ CGImageRelease(mask);
}
}
@@ -1117,6 +1786,10 @@ DrawTargetCG::Mask(const Pattern &aSource,
void
DrawTargetCG::PushClipRect(const Rect &aRect)
{
+ if (MOZ2D_ERROR_IF(!mCg)) {
+ return;
+ }
+
CGContextSaveGState(mCg);
/* We go through a bit of trouble to temporarilly set the transform
@@ -1131,10 +1804,14 @@ DrawTargetCG::PushClipRect(const Rect &aRect)
void
DrawTargetCG::PushClip(const Path *aPath)
{
+ if (MOZ2D_ERROR_IF(!mCg)) {
+ return;
+ }
+
CGContextSaveGState(mCg);
CGContextBeginPath(mCg);
- assert(aPath->GetBackendType() == BACKEND_COREGRAPHICS);
+ assert(aPath->GetBackendType() == BackendType::COREGRAPHICS);
const PathCG *cgPath = static_cast<const PathCG*>(aPath);
@@ -1155,7 +1832,7 @@ DrawTargetCG::PushClip(const Path *aPath)
CGContextAddPath(mCg, cgPath->GetPath());
CGContextRestoreGState(mCg);
- if (cgPath->GetFillRule() == FILL_EVEN_ODD)
+ if (cgPath->GetFillRule() == FillRule::FILL_EVEN_ODD)
CGContextEOClip(mCg);
else
CGContextClip(mCg);
@@ -1179,6 +1856,41 @@ DrawTargetCG::MarkChanged()
}
}
+CGContextRef
+BorrowedCGContext::BorrowCGContextFromDrawTarget(DrawTarget *aDT)
+{
+ if ((aDT->GetBackendType() == BackendType::COREGRAPHICS ||
+ aDT->GetBackendType() == BackendType::COREGRAPHICS_ACCELERATED) &&
+ !aDT->IsTiledDrawTarget() && !aDT->IsDualDrawTarget()) {
+ DrawTargetCG* cgDT = static_cast<DrawTargetCG*>(aDT);
+ cgDT->Flush();
+ cgDT->MarkChanged();
+
+ // swap out the context
+ CGContextRef cg = cgDT->mCg;
+ if (MOZ2D_ERROR_IF(!cg)) {
+ return nullptr;
+ }
+ cgDT->mCg = nullptr;
+
+ // save the state to make it easier for callers to avoid mucking with things
+ CGContextSaveGState(cg);
+
+ CGContextConcatCTM(cg, GfxMatrixToCGAffineTransform(cgDT->mTransform));
+
+ return cg;
+ }
+ return nullptr;
+}
+
+void
+BorrowedCGContext::ReturnCGContextToDrawTarget(DrawTarget *aDT, CGContextRef cg)
+{
+ DrawTargetCG* cgDT = static_cast<DrawTargetCG*>(aDT);
+
+ CGContextRestoreGState(cg);
+ cgDT->mCg = cg;
+}
}