diff options
Diffstat (limited to 'gfx/2d/DrawTargetCG.cpp')
-rw-r--r-- | gfx/2d/DrawTargetCG.cpp | 1270 |
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; +} } |