From 0ac4c9201af73d08106de39d55188d1f1592056d Mon Sep 17 00:00:00 2001 From: Jeff Muizelaar Date: Mon, 9 Jan 2012 13:54:44 -0500 Subject: [PATCH] Bug 692879. Implement CoreGraphics Azure backend. r=mwoodrow --HG-- extra : rebase_source : 94f8c205e16e961b5407396c0d0d3b41067222dc --- gfx/2d/DrawTargetCG.cpp | 855 +++++++++++++++++- gfx/2d/DrawTargetCG.h | 117 ++- gfx/2d/DrawTargetSkia.cpp | 7 +- gfx/2d/Factory.cpp | 31 +- gfx/2d/Makefile.in | 9 +- gfx/2d/PathCG.cpp | 273 ++++++ gfx/2d/PathCG.h | 133 +++ ...{ScaledFontSkia.cpp => ScaledFontBase.cpp} | 72 +- gfx/2d/{ScaledFontSkia.h => ScaledFontBase.h} | 25 +- gfx/2d/ScaledFontMac.cpp | 59 +- gfx/2d/ScaledFontMac.h | 15 +- gfx/2d/ScaledFontWin.cpp | 18 +- gfx/2d/ScaledFontWin.h | 10 +- gfx/2d/SourceSurfaceCG.cpp | 191 +++- gfx/2d/SourceSurfaceCG.h | 38 +- gfx/2d/Types.h | 6 +- gfx/thebes/gfxFont.h | 2 +- gfx/thebes/gfxPlatformMac.cpp | 25 +- gfx/thebes/gfxPlatformMac.h | 3 + gfx/thebes/gfxWindowsPlatform.cpp | 1 + 20 files changed, 1760 insertions(+), 130 deletions(-) create mode 100644 gfx/2d/PathCG.cpp create mode 100644 gfx/2d/PathCG.h rename gfx/2d/{ScaledFontSkia.cpp => ScaledFontBase.cpp} (69%) rename gfx/2d/{ScaledFontSkia.h => ScaledFontBase.h} (84%) diff --git a/gfx/2d/DrawTargetCG.cpp b/gfx/2d/DrawTargetCG.cpp index 6e7d91e36b3c..9e2a5de36ee7 100644 --- a/gfx/2d/DrawTargetCG.cpp +++ b/gfx/2d/DrawTargetCG.cpp @@ -19,7 +19,7 @@ * the Initial Developer. All Rights Reserved. * * Contributor(s): - * Bas Schouten + * Jeff Muizelaar * * Alternatively, the contents of this file may be used under the terms of * either the GNU General Public License Version 2 or later (the "GPL"), or @@ -37,6 +37,9 @@ #include "DrawTargetCG.h" #include "SourceSurfaceCG.h" #include "Rect.h" +#include "ScaledFontMac.h" +#include "Tools.h" +#include //CG_EXTERN void CGContextSetCompositeOperation (CGContextRef, PrivateCGCompositeMode); @@ -48,6 +51,11 @@ static CGRect RectToCGRect(Rect r) return CGRectMake(r.x, r.y, r.width, r.height); } +static CGRect IntRectToCGRect(IntRect r) +{ + return CGRectMake(r.x, r.y, r.width, r.height); +} + CGBlendMode ToBlendMode(CompositionOp op) { CGBlendMode mode; @@ -55,18 +63,40 @@ CGBlendMode ToBlendMode(CompositionOp op) case OP_OVER: mode = kCGBlendModeNormal; break; - case OP_SOURCE: - mode = kCGBlendModeCopy; - break; - case OP_CLEAR: - mode = kCGBlendModeClear; - break; case OP_ADD: mode = kCGBlendModePlusLighter; break; case OP_ATOP: mode = kCGBlendModeSourceAtop; break; + case OP_OUT: + mode = kCGBlendModeSourceOut; + break; + case OP_IN: + mode = kCGBlendModeSourceIn; + break; + case OP_SOURCE: + mode = kCGBlendModeCopy; + break; + case OP_DEST_IN: + mode = kCGBlendModeDestinationIn; + break; + case OP_DEST_OUT: + mode = kCGBlendModeDestinationOut; + break; + case OP_DEST_OVER: + mode = kCGBlendModeDestinationOver; + break; + case OP_DEST_ATOP: + mode = kCGBlendModeDestinationAtop; + break; + case OP_XOR: + mode = kCGBlendModeXOR; + break; + /* + case OP_CLEAR: + mode = kCGBlendModeClear; + break;*/ default: mode = kCGBlendModeNormal; } @@ -81,12 +111,31 @@ DrawTargetCG::DrawTargetCG() DrawTargetCG::~DrawTargetCG() { + // We need to conditionally release these because Init can fail without initializing these. + if (mColorSpace) + CGColorSpaceRelease(mColorSpace); + if (mCg) + CGContextRelease(mCg); } TemporaryRef DrawTargetCG::Snapshot() { - return NULL; + RefPtr newSurf = new SourceSurfaceCG(CGBitmapContextCreateImage(mCg)); + return newSurf; +} + +TemporaryRef +DrawTargetCG::CreateSimilarDrawTarget(const IntSize &aSize, SurfaceFormat aFormat) const +{ + // 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 newTarget = new DrawTargetCG(); + if (newTarget->Init(aSize, aFormat)) { + return newTarget; + } else { + return NULL; + } } TemporaryRef @@ -97,7 +146,7 @@ DrawTargetCG::CreateSourceSurfaceFromData(unsigned char *aData, { RefPtr newSurf = new SourceSurfaceCG(); - if (!newSurf->InitFromData(aData, aSize, aStride, aFormat)) { + if (!newSurf->InitFromData(aData, aSize, aStride, aFormat)) { return NULL; } @@ -110,16 +159,67 @@ DrawTargetCG::OptimizeSourceSurface(SourceSurface *aSurface) const return NULL; } +class UnboundnessFixer +{ + CGRect mClipBounds; + CGLayerRef mLayer; + CGContextRef mCg; + public: + UnboundnessFixer() : mCg(NULL) {} + + CGContextRef Check(CGContextRef baseCg, CompositionOp blend) + { + if (!IsOperatorBoundByMask(blend)) { + mClipBounds = CGContextGetClipBoundingBox(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, NULL); + mCg = 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); + + return mCg; + } else { + return baseCg; + } + } + + void Fix(CGContextRef baseCg) + { + if (mCg) { + CGContextTranslateCTM(baseCg, 0, mClipBounds.size.height); + CGContextScaleCTM(baseCg, 1, -1); + mClipBounds.origin.y *= -1; + CGContextDrawLayerAtPoint(baseCg, mClipBounds.origin, mLayer); + CGContextRelease(mCg); + } + } +}; + void DrawTargetCG::DrawSurface(SourceSurface *aSurface, const Rect &aDest, const Rect &aSource, - const DrawOptions &aOptions, - const DrawSurfaceOptions &aSurfOptions) + const DrawSurfaceOptions &aSurfOptions, + const DrawOptions &aDrawOptions) { CGImageRef image; CGImageRef subimage = NULL; - if (aSurface->GetType() == COREGRAPHICS_IMAGE) { + if (aSurface->GetType() == SURFACE_COREGRAPHICS_IMAGE) { + CGContextSaveGState(mCg); + + CGContextSetBlendMode(mCg, ToBlendMode(aDrawOptions.mCompositionOp)); + UnboundnessFixer fixer; + CGContextRef cg = fixer.Check(mCg, aDrawOptions.mCompositionOp); + CGContextSetAlpha(cg, aDrawOptions.mAlpha); + + CGContextConcatCTM(cg, GfxMatrixToCGAffineTransform(mTransform)); image = static_cast(aSurface)->GetImage(); /* we have two options here: * - create a subimage -- this is slower @@ -129,56 +229,737 @@ DrawTargetCG::DrawSurface(SourceSurface *aSurface, image = subimage; } - CGContextDrawImage(mCg, RectToCGRect(aDest), image); + CGContextScaleCTM(cg, 1, -1); + + CGRect flippedRect = CGRectMake(aDest.x, -(aDest.y + aDest.height), + aDest.width, aDest.height); + + //XXX: we should implement this for patterns too + if (aSurfOptions.mFilter == FILTER_POINT) + CGContextSetInterpolationQuality(cg, kCGInterpolationNone); + + CGContextDrawImage(cg, flippedRect, image); + + fixer.Fix(mCg); + + CGContextRestoreGState(mCg); + + CGImageRelease(subimage); + } +} + +static CGColorRef ColorToCGColor(CGColorSpaceRef aColorSpace, const Color& aColor) +{ + CGFloat components[4] = {aColor.r, aColor.g, aColor.b, aColor.a}; + return CGColorCreate(aColorSpace, components); +} + +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) + { + //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 colors; + std::vector 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); + } + + CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB(); + mGradient = CGGradientCreateWithColorComponents(colorSpace, + &colors.front(), + &offsets.front(), + aNumStops); + CGColorSpaceRelease(colorSpace); + } + virtual ~GradientStopsCG() { + CGGradientRelease(mGradient); + } + BackendType GetBackendType() const { return BACKEND_COREGRAPHICS; } + CGGradientRef mGradient; +}; + +TemporaryRef +DrawTargetCG::CreateGradientStops(GradientStop *aStops, uint32_t aNumStops, + ExtendMode aExtendMode) const +{ + return new GradientStopsCG(aStops, aNumStops, aExtendMode); +} + +static void +DrawGradient(CGContextRef cg, const Pattern &aPattern) +{ + if (aPattern.GetType() == PATTERN_LINEAR_GRADIENT) { + const LinearGradientPattern& pat = static_cast(aPattern); + GradientStopsCG *stops = static_cast(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 }; + + // 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 (aPattern.GetType() == PATTERN_RADIAL_GRADIENT) { + const RadialGradientPattern& pat = static_cast(aPattern); + GradientStopsCG *stops = static_cast(pat.mStops.get()); + + // 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 { + assert(0); + } + +} + +static void +drawPattern(void *info, CGContextRef context) +{ + CGImageRef image = static_cast(info); + CGRect rect = {{0, 0}, + {static_cast(CGImageGetWidth(image)), + static_cast(CGImageGetHeight(image))}}; + CGContextDrawImage(context, rect, image); +} + +static void +releaseInfo(void *info) +{ + CGImageRef image = static_cast(info); + CGImageRelease(image); +} + +CGPatternCallbacks patternCallbacks = { + 0, + drawPattern, + releaseInfo +}; + +static bool +isGradient(const Pattern &aPattern) +{ + return aPattern.GetType() == PATTERN_LINEAR_GRADIENT || aPattern.GetType() == PATTERN_RADIAL_GRADIENT; +} + +/* CoreGraphics patterns ignore the userspace transform so + * we need to multiply it in */ +static CGPatternRef +CreateCGPattern(const Pattern &aPattern, CGAffineTransform aUserSpace) +{ + const SurfacePattern& pat = static_cast(aPattern); + // XXX: is .get correct here? + CGImageRef image = static_cast(pat.mSurface.get())->GetImage(); + CGFloat xStep, yStep; + switch (pat.mExtendMode) { + case EXTEND_CLAMP: + // The 1 << 22 comes from Webkit see Pattern::createPlatformPattern() in PatternCG.cpp for more info + xStep = static_cast(1 << 22); + yStep = static_cast(1 << 22); + break; + case EXTEND_REFLECT: + assert(0); + case EXTEND_REPEAT: + xStep = static_cast(CGImageGetWidth(image)); + yStep = static_cast(CGImageGetHeight(image)); + // webkit uses wkCGPatternCreateWithImageAndTransform a wrapper around CGPatternCreateWithImage2 + // this is done to avoid pixel-cracking along pattern boundaries + // (see https://bugs.webkit.org/show_bug.cgi?id=53055) + // typedef enum { + // wkPatternTilingNoDistortion, + // wkPatternTilingConstantSpacingMinimalDistortion, + // wkPatternTilingConstantSpacing + // } wkPatternTiling; + // extern CGPatternRef (*wkCGPatternCreateWithImageAndTransform)(CGImageRef, CGAffineTransform, int); + } + + //XXX: We should be using CGContextDrawTiledImage when we can. Even though it + // creates a pattern, it seems to go down a faster path than using a delegate + // like we do below + CGRect bounds = { + {0, 0,}, + {static_cast(CGImageGetWidth(image)), static_cast(CGImageGetHeight(image))} + }; + CGAffineTransform transform = CGAffineTransformConcat(CGAffineTransformMakeScale(1, -1), aUserSpace); + transform = CGAffineTransformTranslate(transform, 0, -static_cast(CGImageGetHeight(image))); + return CGPatternCreate(CGImageRetain(image), bounds, transform, xStep, yStep, kCGPatternTilingConstantSpacing, + true, &patternCallbacks); +} + +static void +SetFillFromPattern(CGContextRef cg, CGColorSpaceRef aColorSpace, const Pattern &aPattern) +{ + assert(!isGradient(aPattern)); + if (aPattern.GetType() == PATTERN_COLOR) { + + const Color& color = static_cast(aPattern).mColor; + //XXX: we should cache colors + CGColorRef cgcolor = ColorToCGColor(aColorSpace, color); + CGContextSetFillColorWithColor(cg, cgcolor); + CGColorRelease(cgcolor); + } else if (aPattern.GetType() == PATTERN_SURFACE) { + + CGColorSpaceRef patternSpace; + patternSpace = CGColorSpaceCreatePattern (NULL); + CGContextSetFillColorSpace(cg, patternSpace); + CGColorSpaceRelease(patternSpace); + + CGPatternRef pattern = CreateCGPattern(aPattern, CGContextGetCTM(cg)); + CGFloat alpha = 1.; + CGContextSetFillPattern(cg, pattern, &alpha); + CGPatternRelease(pattern); + } +} + +static void +SetStrokeFromPattern(CGContextRef cg, CGColorSpaceRef aColorSpace, const Pattern &aPattern) +{ + assert(!isGradient(aPattern)); + if (aPattern.GetType() == PATTERN_COLOR) { + const Color& color = static_cast(aPattern).mColor; + //XXX: we should cache colors + CGColorRef cgcolor = ColorToCGColor(aColorSpace, color); + CGContextSetStrokeColorWithColor(cg, cgcolor); + CGColorRelease(cgcolor); + } else if (aPattern.GetType() == PATTERN_SURFACE) { + CGColorSpaceRef patternSpace; + patternSpace = CGColorSpaceCreatePattern (NULL); + CGContextSetStrokeColorSpace(cg, patternSpace); + CGColorSpaceRelease(patternSpace); + + CGPatternRef pattern = CreateCGPattern(aPattern, CGContextGetCTM(cg)); + CGFloat alpha = 1.; + CGContextSetStrokePattern(cg, pattern, &alpha); + CGPatternRelease(pattern); + } + +} + + +void +DrawTargetCG::FillRect(const Rect &aRect, + const Pattern &aPattern, + const DrawOptions &aDrawOptions) +{ + CGContextSaveGState(mCg); + + UnboundnessFixer fixer; + CGContextRef cg = fixer.Check(mCg, aDrawOptions.mCompositionOp); + CGContextSetAlpha(mCg, aDrawOptions.mAlpha); + CGContextSetBlendMode(mCg, ToBlendMode(aDrawOptions.mCompositionOp)); + + CGContextConcatCTM(cg, GfxMatrixToCGAffineTransform(mTransform)); + + if (isGradient(aPattern)) { + CGContextClipToRect(cg, RectToCGRect(aRect)); + DrawGradient(cg, aPattern); + } else { + SetFillFromPattern(cg, mColorSpace, aPattern); + CGContextFillRect(cg, RectToCGRect(aRect)); + } + + fixer.Fix(mCg); + CGContextRestoreGState(mCg); +} + +void +DrawTargetCG::StrokeLine(const Point &p1, const Point &p2, const Pattern &aPattern, const StrokeOptions &aStrokeOptions, const DrawOptions &aDrawOptions) +{ + CGContextSaveGState(mCg); + + UnboundnessFixer fixer; + CGContextRef cg = fixer.Check(mCg, aDrawOptions.mCompositionOp); + CGContextSetAlpha(mCg, aDrawOptions.mAlpha); + CGContextSetBlendMode(mCg, ToBlendMode(aDrawOptions.mCompositionOp)); + + CGContextConcatCTM(cg, GfxMatrixToCGAffineTransform(mTransform)); + + CGContextBeginPath(cg); + CGContextMoveToPoint(cg, p1.x, p1.y); + CGContextAddLineToPoint(cg, p2.x, p2.y); + + SetStrokeOptions(cg, aStrokeOptions); + + if (isGradient(aPattern)) { + CGContextReplacePathWithStrokedPath(cg); + //XXX: should we use EO clip here? + CGContextClip(cg); + DrawGradient(cg, aPattern); + } else { + SetStrokeFromPattern(cg, mColorSpace, aPattern); + CGContextStrokePath(cg); + } + + fixer.Fix(mCg); + CGContextRestoreGState(mCg); +} + +void +DrawTargetCG::StrokeRect(const Rect &aRect, + const Pattern &aPattern, + const StrokeOptions &aStrokeOptions, + const DrawOptions &aDrawOptions) +{ + CGContextSaveGState(mCg); + + UnboundnessFixer fixer; + CGContextRef cg = fixer.Check(mCg, aDrawOptions.mCompositionOp); + CGContextSetAlpha(mCg, aDrawOptions.mAlpha); + CGContextSetBlendMode(mCg, ToBlendMode(aDrawOptions.mCompositionOp)); + + 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); + + if (isGradient(aPattern)) { + // There's no CGContextClipStrokeRect so we do it by hand + CGContextBeginPath(cg); + CGContextAddRect(cg, RectToCGRect(aRect)); + CGContextReplacePathWithStrokedPath(cg); + //XXX: should we use EO clip here? + CGContextClip(cg); + DrawGradient(cg, aPattern); + } else { + SetStrokeFromPattern(cg, mColorSpace, aPattern); + CGContextStrokeRect(cg, RectToCGRect(aRect)); + } + + fixer.Fix(mCg); + CGContextRestoreGState(mCg); +} + + +void +DrawTargetCG::ClearRect(const Rect &aRect) +{ + CGContextSaveGState(mCg); + CGContextConcatCTM(mCg, GfxMatrixToCGAffineTransform(mTransform)); + + CGContextClearRect(mCg, RectToCGRect(aRect)); + + CGContextRestoreGState(mCg); +} + +void +DrawTargetCG::Stroke(const Path *aPath, const Pattern &aPattern, const StrokeOptions &aStrokeOptions, const DrawOptions &aDrawOptions) +{ + CGContextSaveGState(mCg); + + UnboundnessFixer fixer; + CGContextRef cg = fixer.Check(mCg, aDrawOptions.mCompositionOp); + CGContextSetAlpha(mCg, aDrawOptions.mAlpha); + CGContextSetBlendMode(mCg, ToBlendMode(aDrawOptions.mCompositionOp)); + + CGContextConcatCTM(cg, GfxMatrixToCGAffineTransform(mTransform)); + + + CGContextBeginPath(cg); + + assert(aPath->GetBackendType() == BACKEND_COREGRAPHICS); + const PathCG *cgPath = static_cast(aPath); + CGContextAddPath(cg, cgPath->GetPath()); + + SetStrokeOptions(cg, aStrokeOptions); + + if (isGradient(aPattern)) { + CGContextReplacePathWithStrokedPath(cg); + //XXX: should we use EO clip here? + CGContextClip(cg); + DrawGradient(cg, aPattern); + } else { + CGContextBeginPath(cg); + // XXX: we could put fill mode into the path fill rule if we wanted + const PathCG *cgPath = static_cast(aPath); + CGContextAddPath(cg, cgPath->GetPath()); + + SetStrokeFromPattern(cg, mColorSpace, aPattern); + CGContextStrokePath(cg); + } + + fixer.Fix(mCg); + CGContextRestoreGState(mCg); +} + +void +DrawTargetCG::Fill(const Path *aPath, const Pattern &aPattern, const DrawOptions &aDrawOptions) +{ + assert(aPath->GetBackendType() == BACKEND_COREGRAPHICS); + + CGContextSaveGState(mCg); + + CGContextSetBlendMode(mCg, ToBlendMode(aDrawOptions.mCompositionOp)); + UnboundnessFixer fixer; + CGContextRef cg = fixer.Check(mCg, aDrawOptions.mCompositionOp); + CGContextSetAlpha(cg, aDrawOptions.mAlpha); + + CGContextConcatCTM(cg, GfxMatrixToCGAffineTransform(mTransform)); + + if (isGradient(aPattern)) { + // XXX: we should be able to avoid the extra SaveState that PushClip does + PushClip(aPath); + DrawGradient(cg, aPattern); + PopClip(); + } else { + CGContextBeginPath(cg); + // XXX: we could put fill mode into the path fill rule if we wanted + const PathCG *cgPath = static_cast(aPath); + CGContextAddPath(cg, cgPath->GetPath()); + + SetFillFromPattern(cg, mColorSpace, aPattern); + + if (cgPath->GetFillRule() == FILL_EVEN_ODD) + CGContextEOFillPath(cg); + else + CGContextFillPath(cg); + } + + fixer.Fix(mCg); + CGContextRestoreGState(mCg); +} + + +void +DrawTargetCG::FillGlyphs(ScaledFont *aFont, const GlyphBuffer &aBuffer, const Pattern &aPattern, const DrawOptions &aDrawOptions) +{ + assert(aBuffer.mNumGlyphs); + CGContextSaveGState(mCg); + + CGContextSetBlendMode(mCg, ToBlendMode(aDrawOptions.mCompositionOp)); + UnboundnessFixer fixer; + CGContextRef cg = fixer.Check(mCg, aDrawOptions.mCompositionOp); + CGContextSetAlpha(cg, aDrawOptions.mAlpha); + + CGContextConcatCTM(cg, GfxMatrixToCGAffineTransform(mTransform)); + + ScaledFontMac* cgFont = static_cast(aFont); + CGContextSetFont(cg, cgFont->mFont); + CGContextSetFontSize(cg, cgFont->mSize); + + //XXX: we should use a stack vector here when we have a class like that + std::vector glyphs; + std::vector positions; + glyphs.resize(aBuffer.mNumGlyphs); + positions.resize(aBuffer.mNumGlyphs); + + CGFloat xprev = aBuffer.mGlyphs[0].mPosition.x; + CGFloat yprev = aBuffer.mGlyphs[0].mPosition.y; + CGContextSetTextPosition(cg, xprev, yprev); + + // Handle the flip + CGAffineTransform matrix = CGAffineTransformMakeScale(1, -1);//CGAffineTransformMake(1, 0, 0, -1, 0, -mSize.height); + // "Note that the text matrix is not a part of the graphics state" + CGContextSetTextMatrix(cg, matrix); + + for (unsigned int i = 0; i < aBuffer.mNumGlyphs; i++) { + glyphs[i] = aBuffer.mGlyphs[i].mIndex; + // XXX: CGPointMake might not be inlined + positions[i] = CGPointMake(aBuffer.mGlyphs[i].mPosition.x, + -aBuffer.mGlyphs[i].mPosition.y); + } + + //XXX: CGContextShowGlyphsAtPositions is 10.5+ for older versions use CGContextShowGlyphsWithAdvances + if (isGradient(aPattern)) { + CGContextSetTextDrawingMode(cg, kCGTextClip); + CGContextShowGlyphsAtPositions(cg, &glyphs.front(), &positions.front(), aBuffer.mNumGlyphs); + DrawGradient(cg, aPattern); + } 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); + CGContextShowGlyphsAtPositions(cg, &glyphs.front(), &positions.front(), aBuffer.mNumGlyphs); + } + + fixer.Fix(mCg); + CGContextRestoreGState(cg); +} + +extern "C" { +void +CGContextResetClip(CGContextRef); +}; + +void +DrawTargetCG::CopySurface(SourceSurface *aSurface, + const IntRect& aSourceRect, + const IntPoint &aDestination) +{ + CGImageRef image; + CGImageRef subimage = NULL; + if (aSurface->GetType() == SURFACE_COREGRAPHICS_IMAGE) { + image = static_cast(aSurface)->GetImage(); + /* 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; + } + // 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); + CGContextSetBlendMode(mCg, kCGBlendModeCopy); + + CGContextScaleCTM(mCg, 1, -1); + + CGRect flippedRect = CGRectMake(aDestination.x, -(aDestination.y + aSourceRect.height), + aSourceRect.width, aSourceRect.height); + + CGContextDrawImage(mCg, flippedRect, image); + + CGContextRestoreGState(mCg); CGImageRelease(subimage); } } void -DrawTargetCG::FillRect(const Rect &aRect, - const Pattern &aPattern, - const DrawOptions &aOptions) +DrawTargetCG::DrawSurfaceWithShadow(SourceSurface *aSurface, const Point &aDest, const Color &aColor, const Point &aOffset, Float aSigma, CompositionOp aOperator) { - //XXX: it would be nice to hang a CGColor off of the pattern here - if (aPattern.GetType() == COLOR) { - Color color = static_cast(&aPattern)->mColor; - //XXX: the m prefixes are painful here - CGContextSetRGBFillColor(mCg, color.mR, color.mG, color.mB, color.mA); - } + CGImageRef image; + CGImageRef subimage = NULL; + if (aSurface->GetType() == SURFACE_COREGRAPHICS_IMAGE) { + image = static_cast(aSurface)->GetImage(); - CGContextSetBlendMode(mCg, ToBlendMode(aOptions.mCompositionOp)); - CGContextFillRect(mCg, RectToCGRect(aRect)); + IntSize size = aSurface->GetSize(); + CGContextSaveGState(mCg); + //XXX do we need to do the fixup here? + CGContextSetBlendMode(mCg, ToBlendMode(aOperator)); + + CGContextScaleCTM(mCg, 1, -1); + + CGRect flippedRect = CGRectMake(aDest.x, -(aDest.y + size.height), + size.width, size.height); + + CGColorRef color = ColorToCGColor(mColorSpace, aColor); + CGSize offset = {aOffset.x, -aOffset.y}; + // CoreGraphics needs twice sigma as it's amount of blur + CGContextSetShadowWithColor(mCg, offset, 2*aSigma, color); + CGColorRelease(color); + + CGContextDrawImage(mCg, flippedRect, image); + + CGContextRestoreGState(mCg); + + CGImageRelease(subimage); + } } +bool +DrawTargetCG::Init(CGContextRef cgContext, const IntSize &aSize) +{ + // XXX: we should come up with some consistent semantics for dealing + // with zero area drawtargets + if (aSize.width == 0 || aSize.height == 0) { + mColorSpace = NULL; + mCg = NULL; + return false; + } + + //XXX: handle SurfaceFormat + + //XXX: we'd be better off reusing the Colorspace across draw targets + mColorSpace = CGColorSpaceCreateDeviceRGB(); + + mSize = aSize; + + mCg = cgContext; + + mData = NULL; + + 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); + + //XXX: set correct format + mFormat = FORMAT_B8G8R8A8; + + return true; +} bool -DrawTargetCG::Init(const IntSize &aSize) +DrawTargetCG::Init(const IntSize &aSize, SurfaceFormat &) { - CGColorSpaceRef cgColorspace; - cgColorspace = CGColorSpaceCreateDeviceRGB(); + // XXX: we should come up with some consistent semantics for dealing + // with zero area drawtargets + if (aSize.width == 0 || aSize.height == 0) { + mColorSpace = NULL; + mCg = NULL; + return false; + } + + //XXX: handle SurfaceFormat + + //XXX: we'd be better off reusing the Colorspace across draw targets + mColorSpace = CGColorSpaceCreateDeviceRGB(); mSize = aSize; int bitsPerComponent = 8; - int stride = mSize.width; + int stride = mSize.width*4; CGBitmapInfo bitinfo; bitinfo = kCGBitmapByteOrder32Host | kCGImageAlphaPremultipliedFirst; - // XXX: mWidth is ugly - mCg = CGBitmapContextCreate (NULL, - mSize.width, - mSize.height, - bitsPerComponent, - stride, - cgColorspace, - bitinfo); + // XXX: currently we allocate ourselves so that we can easily return a gfxImageSurface + // we might not need to later if once we don't need to support gfxImageSurface + //XXX: currently Init implicitly clears, that can often be a waste of time + // XXX: leaked + mData = calloc(mSize.height * stride, 1); + // XXX: what should we do if this fails? + mCg = CGBitmapContextCreate (mData, + mSize.width, + mSize.height, + bitsPerComponent, + stride, + mColorSpace, + bitinfo); - CGColorSpaceRelease (cgColorspace); + + 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); + + //XXX: set correct format + mFormat = FORMAT_B8G8R8A8; return true; } + +TemporaryRef +DrawTargetCG::CreatePathBuilder(FillRule aFillRule) const +{ + RefPtr pb = new PathBuilderCG(aFillRule); + return pb; +} + +void* +DrawTargetCG::GetNativeSurface(NativeSurfaceType aType) +{ + if (aType == NATIVE_SURFACE_CGCONTEXT) { + return mCg; + } else { + return NULL; + } +} + +void +DrawTargetCG::Mask(const Pattern &aSource, + const Pattern &aMask, + const DrawOptions &aDrawOptions) +{ + + CGContextSaveGState(mCg); + + if (isGradient(aMask)) { + assert(0); + } else { + if (aMask.GetType() == PATTERN_COLOR) { + DrawOptions drawOptions(aDrawOptions); + const Color& color = static_cast(aMask).mColor; + drawOptions.mAlpha *= color.a; + assert(0); + // 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) { + const SurfacePattern& pat = static_cast(aMask); + CGImageRef mask = static_cast(pat.mSurface.get())->GetImage(); + 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); + } + } + + CGContextRestoreGState(mCg); +} + +void +DrawTargetCG::PushClipRect(const Rect &aRect) +{ + CGContextSaveGState(mCg); + + CGContextClipToRect(mCg, RectToCGRect(aRect)); +} + + +void +DrawTargetCG::PushClip(const Path *aPath) +{ + CGContextSaveGState(mCg); + + CGContextBeginPath(mCg); + assert(aPath->GetBackendType() == BACKEND_COREGRAPHICS); + + const PathCG *cgPath = static_cast(aPath); + + // Weirdly, CoreGraphics clips empty paths as all shown + // but emtpy rects as all clipped. We detect this situation and + // workaround it appropriately + if (CGPathIsEmpty(cgPath->GetPath())) { + // XXX: should we return here? + CGContextClipToRect(mCg, CGRectZero); + } + + + CGContextAddPath(mCg, cgPath->GetPath()); + if (cgPath->GetFillRule() == FILL_EVEN_ODD) + CGContextEOClip(mCg); + else + CGContextClip(mCg); +} + +void +DrawTargetCG::PopClip() +{ + CGContextRestoreGState(mCg); +} + + + } } diff --git a/gfx/2d/DrawTargetCG.h b/gfx/2d/DrawTargetCG.h index d6908f917513..046bb4b43c4a 100644 --- a/gfx/2d/DrawTargetCG.h +++ b/gfx/2d/DrawTargetCG.h @@ -35,50 +35,155 @@ * * ***** END LICENSE BLOCK ***** */ -#pragma once - #include #include "2D.h" #include "Rect.h" +#include "PathCG.h" + namespace mozilla { namespace gfx { +static inline CGAffineTransform +GfxMatrixToCGAffineTransform(Matrix m) +{ + CGAffineTransform t; + t.a = m._11; + t.b = m._12; + t.c = m._21; + t.d = m._22; + t.tx = m._31; + t.ty = m._32; + return t; +} + +static inline Rect +CGRectToRect(CGRect rect) +{ + return Rect(rect.origin.x, + rect.origin.y, + rect.size.width, + rect.size.height); +} + +static inline void +SetStrokeOptions(CGContextRef cg, const StrokeOptions &aStrokeOptions) +{ + switch (aStrokeOptions.mLineCap) + { + case CAP_BUTT: + CGContextSetLineCap(cg, kCGLineCapButt); + break; + case CAP_ROUND: + CGContextSetLineCap(cg, kCGLineCapRound); + break; + case CAP_SQUARE: + CGContextSetLineCap(cg, kCGLineCapSquare); + break; + } + + 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); + CGContextSetMiterLimit(cg, aStrokeOptions.mMiterLimit); + + // XXX: rename mDashLength to dashLength + if (aStrokeOptions.mDashLength > 1) { + // we use a regular array instead of a std::vector here because we don't want to leak the include + CGFloat *dashes = new CGFloat[aStrokeOptions.mDashLength]; + for (size_t i=0; i Snapshot(); virtual void DrawSurface(SourceSurface *aSurface, const Rect &aDest, const Rect &aSource, - const DrawOptions &aOptions = DrawOptions(), - const DrawSurfaceOptions &aSurfOptions = DrawSurfaceOptions()); + const DrawSurfaceOptions &aSurfOptions = DrawSurfaceOptions(), + const DrawOptions &aOptions = DrawOptions()); virtual void FillRect(const Rect &aRect, const Pattern &aPattern, const DrawOptions &aOptions = DrawOptions()); - bool Init(const IntSize &aSize); + //XXX: why do we take a reference to SurfaceFormat? + bool Init(const IntSize &aSize, SurfaceFormat&); bool Init(CGContextRef cgContext, const IntSize &aSize); + + virtual void Flush() {} + + virtual void DrawSurfaceWithShadow(SourceSurface *, const Point &, const Color &, const Point &, Float, CompositionOp); + virtual void ClearRect(const Rect &); + virtual void CopySurface(SourceSurface *, const IntRect&, const IntPoint&); + virtual void StrokeRect(const Rect &, const Pattern &, const StrokeOptions&, const DrawOptions&); + virtual void StrokeLine(const Point &, const Point &, const Pattern &, const StrokeOptions &, const DrawOptions &); + virtual void Stroke(const Path *, const Pattern &, const StrokeOptions &, const DrawOptions &); + virtual void Fill(const Path *, const Pattern &, const DrawOptions &); + virtual void FillGlyphs(ScaledFont *, const GlyphBuffer&, const Pattern &, const DrawOptions &); + virtual void Mask(const Pattern &aSource, + const Pattern &aMask, + const DrawOptions &aOptions = DrawOptions()); + virtual void PushClip(const Path *); + virtual void PushClipRect(const Rect &aRect); + virtual void PopClip(); + virtual TemporaryRef CreateSourceSurfaceFromNativeSurface(const NativeSurface&) const { return NULL;} + virtual TemporaryRef CreateSimilarDrawTarget(const IntSize &, SurfaceFormat) const; + virtual TemporaryRef CreatePathBuilder(FillRule) const; + virtual TemporaryRef CreateGradientStops(GradientStop *, uint32_t, + ExtendMode aExtendMode = EXTEND_CLAMP) const; + + virtual void *GetNativeSurface(NativeSurfaceType); + + virtual IntSize GetSize() { return mSize; } + + /* This is for creating good compatible surfaces */ virtual TemporaryRef CreateSourceSurfaceFromData(unsigned char *aData, const IntSize &aSize, int32_t aStride, SurfaceFormat aFormat) const; virtual TemporaryRef OptimizeSourceSurface(SourceSurface *aSurface) const; + CGContextRef GetCGContext() { + return mCg; + } private: bool InitCGRenderTarget(); IntSize mSize; + CGColorSpaceRef mColorSpace; CGContextRef mCg; + void *mData; + + SurfaceFormat mFormat; + }; } diff --git a/gfx/2d/DrawTargetSkia.cpp b/gfx/2d/DrawTargetSkia.cpp index 0ecee0f040cd..def50823b82d 100644 --- a/gfx/2d/DrawTargetSkia.cpp +++ b/gfx/2d/DrawTargetSkia.cpp @@ -37,7 +37,7 @@ #include "DrawTargetSkia.h" #include "SourceSurfaceSkia.h" -#include "ScaledFontSkia.h" +#include "ScaledFontBase.h" #include "skia/SkDevice.h" #include "skia/SkTypeface.h" #include "skia/SkGradientShader.h" @@ -58,6 +58,7 @@ namespace gfx { SkColor ColorToSkColor(const Color &color, Float aAlpha) { + //XXX: do a better job converting to int return SkColorSetARGB(color.a*aAlpha*255.0, color.r*255.0, color.g*255.0, color.b*255.0); } @@ -520,10 +521,10 @@ DrawTargetSkia::FillGlyphs(ScaledFont *aFont, MarkChanged(); - ScaledFontSkia* skiaFont = static_cast(aFont); + ScaledFontBase* skiaFont = static_cast(aFont); AutoPaintSetup paint(mCanvas.get(), aOptions, aPattern); - paint.mPaint.setTypeface(skiaFont->mTypeface); + paint.mPaint.setTypeface(skiaFont->GetSkTypeface()); paint.mPaint.setTextSize(SkFloatToScalar(skiaFont->mSize)); paint.mPaint.setTextEncoding(SkPaint::kGlyphID_TextEncoding); diff --git a/gfx/2d/Factory.cpp b/gfx/2d/Factory.cpp index e3e5def12c30..dd697630a3a2 100644 --- a/gfx/2d/Factory.cpp +++ b/gfx/2d/Factory.cpp @@ -44,13 +44,20 @@ #ifdef USE_SKIA #include "DrawTargetSkia.h" -#ifdef XP_MACOSX -#include "ScaledFontMac.h" +#include "ScaledFontBase.h" #endif + #ifdef WIN32 #include "ScaledFontWin.h" #endif -#include "ScaledFontSkia.h" + +#ifdef XP_MACOSX +#include "ScaledFontMac.h" +#endif + + +#ifdef XP_MACOSX +#include "DrawTargetCG.h" #endif #ifdef WIN32 @@ -90,7 +97,7 @@ Factory::CreateDrawTarget(BackendType aBackend, const IntSize &aSize, SurfaceFor } break; } -#endif +#elif defined XP_MACOSX || defined ANDROID #ifdef USE_SKIA case BACKEND_SKIA: { @@ -101,6 +108,18 @@ Factory::CreateDrawTarget(BackendType aBackend, const IntSize &aSize, SurfaceFor } break; } +#endif +#ifdef XP_MACOSX + case BACKEND_COREGRAPHICS: + { + RefPtr newTarget; + newTarget = new DrawTargetCG(); + if (newTarget->Init(aSize, aFormat)) { + return newTarget; + } + break; + } +#endif #endif default: gfxDebug() << "Invalid draw target type specified."; @@ -122,13 +141,13 @@ Factory::CreateScaledFontForNativeFont(const NativeFont &aNativeFont, Float aSiz return new ScaledFontDWrite(static_cast(aNativeFont.mFont), aSize); } #endif -#ifdef USE_SKIA #ifdef XP_MACOSX case NATIVE_FONT_MAC_FONT_FACE: { return new ScaledFontMac(static_cast(aNativeFont.mFont), aSize); } #endif +#ifdef USE_SKIA #ifdef WIN32 case NATIVE_FONT_GDI_FONT_FACE: { @@ -137,7 +156,7 @@ Factory::CreateScaledFontForNativeFont(const NativeFont &aNativeFont, Float aSiz #endif case NATIVE_FONT_SKIA_FONT_FACE: { - return new ScaledFontSkia(static_cast(aNativeFont.mFont), aSize); + return new ScaledFontBase(static_cast(aNativeFont.mFont), aSize); } #endif case NATIVE_FONT_CAIRO_FONT_FACE: diff --git a/gfx/2d/Makefile.in b/gfx/2d/Makefile.in index b3afd83fab80..8002de1cada4 100644 --- a/gfx/2d/Makefile.in +++ b/gfx/2d/Makefile.in @@ -72,8 +72,16 @@ CPPSRCS = \ SourceSurfaceCairo.cpp \ PathCairo.cpp \ Blur.cpp \ + ScaledFontBase.cpp \ $(NULL) +ifeq (cocoa,$(MOZ_WIDGET_TOOLKIT)) +CPPSRCS += \ + SourceSurfaceCG.cpp \ + DrawTargetCG.cpp \ + PathCG.cpp \ + $(NULL) +endif DEFINES += -DMOZ_GFX -DUSE_CAIRO @@ -82,7 +90,6 @@ CPPSRCS += \ SourceSurfaceSkia.cpp \ DrawTargetSkia.cpp \ PathSkia.cpp \ - ScaledFontSkia.cpp \ $(NULL) DEFINES += -DUSE_SKIA diff --git a/gfx/2d/PathCG.cpp b/gfx/2d/PathCG.cpp new file mode 100644 index 000000000000..a7d1d1066196 --- /dev/null +++ b/gfx/2d/PathCG.cpp @@ -0,0 +1,273 @@ +/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * ***** BEGIN LICENSE BLOCK ***** + * Version: MPL 1.1/GPL 2.0/LGPL 2.1 + * + * The contents of this file are subject to the Mozilla Public License Version + * 1.1 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * http://www.mozilla.org/MPL/ + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + * + * The Original Code is Mozilla Corporation code. + * + * The Initial Developer of the Original Code is Mozilla Foundation. + * Portions created by the Initial Developer are Copyright (C) 2011 + * the Initial Developer. All Rights Reserved. + * + * Contributor(s): + * Bas Schouten + * + * Alternatively, the contents of this file may be used under the terms of + * either the GNU General Public License Version 2 or later (the "GPL"), or + * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), + * in which case the provisions of the GPL or the LGPL are applicable instead + * of those above. If you wish to allow use of your version of this file only + * under the terms of either the GPL or the LGPL, and not to allow others to + * use your version of this file under the terms of the MPL, indicate your + * decision by deleting the provisions above and replace them with the notice + * and other provisions required by the GPL or the LGPL. If you do not delete + * the provisions above, a recipient may use your version of this file under + * the terms of any one of the MPL, the GPL or the LGPL. + * + * ***** END LICENSE BLOCK ***** */ + +#include "PathCG.h" +#include +#include "DrawTargetCG.h" +#include "Logging.h" + +namespace mozilla { +namespace gfx { + +PathBuilderCG::~PathBuilderCG() +{ + CGPathRelease(mCGPath); +} + +void +PathBuilderCG::MoveTo(const Point &aPoint) +{ + CGPathMoveToPoint(mCGPath, NULL, aPoint.x, aPoint.y); +} + +void +PathBuilderCG::LineTo(const Point &aPoint) +{ + if (CGPathIsEmpty(mCGPath)) + MoveTo(aPoint); + else + CGPathAddLineToPoint(mCGPath, NULL, aPoint.x, aPoint.y); +} + +void +PathBuilderCG::BezierTo(const Point &aCP1, + const Point &aCP2, + const Point &aCP3) +{ + + if (CGPathIsEmpty(mCGPath)) + MoveTo(aCP1); + else + CGPathAddCurveToPoint(mCGPath, NULL, + aCP1.x, aCP1.y, + aCP2.x, aCP2.y, + aCP3.x, aCP3.y); + +} + +void +PathBuilderCG::QuadraticBezierTo(const Point &aCP1, + const Point &aCP2) +{ + if (CGPathIsEmpty(mCGPath)) + MoveTo(aCP1); + else + CGPathAddQuadCurveToPoint(mCGPath, NULL, + aCP1.x, aCP1.y, + aCP2.x, aCP2.y); +} + +void +PathBuilderCG::Close() +{ + if (!CGPathIsEmpty(mCGPath)) + CGPathCloseSubpath(mCGPath); +} + +void +PathBuilderCG::Arc(const Point &aOrigin, Float aRadius, Float aStartAngle, + Float aEndAngle, bool aAntiClockwise) +{ +} + +Point +PathBuilderCG::CurrentPoint() const +{ + CGPoint pt = CGPathGetCurrentPoint(mCGPath); + Point ret(pt.x, pt.y); + return ret; +} + +void +PathBuilderCG::EnsureActive(const Point &aPoint) +{ +} + +TemporaryRef +PathBuilderCG::Finish() +{ + RefPtr path = new PathCG(mCGPath, mFillRule); + return path; +} + +TemporaryRef +PathCG::CopyToBuilder(FillRule aFillRule) const +{ + CGMutablePathRef path = CGPathCreateMutableCopy(mPath); + RefPtr builder = new PathBuilderCG(path, aFillRule); + return builder; +} + + + +TemporaryRef +PathCG::TransformedCopyToBuilder(const Matrix &aTransform, FillRule aFillRule) const +{ + // 10.7 adds CGPathCreateMutableCopyByTransformingPath it might be faster than doing + // this by hand + + struct TransformApplier { + CGMutablePathRef path; + CGAffineTransform transform; + static void + TranformCGPathApplierFunc(void *vinfo, const CGPathElement *element) + { + TransformApplier *info = reinterpret_cast(vinfo); + switch (element->type) { + case kCGPathElementMoveToPoint: + { + CGPoint pt = element->points[0]; + CGPathMoveToPoint(info->path, &info->transform, pt.x, pt.y); + break; + } + case kCGPathElementAddLineToPoint: + { + CGPoint pt = element->points[0]; + CGPathAddLineToPoint(info->path, &info->transform, pt.x, pt.y); + break; + } + case kCGPathElementAddQuadCurveToPoint: + { + CGPoint pt = element->points[0]; + CGPoint cpt = element->points[1]; + CGPathAddQuadCurveToPoint(info->path, &info->transform, cpt.x, cpt.y, pt.x, pt.y); + break; + } + case kCGPathElementAddCurveToPoint: + { + CGPoint pt = element->points[0]; + CGPoint cpt1 = element->points[1]; + CGPoint cpt2 = element->points[2]; + CGPathAddCurveToPoint(info->path, &info->transform, cpt1.x, cpt1.y, cpt2.x, cpt2.y, pt.x, pt.y); + break; + } + case kCGPathElementCloseSubpath: + { + CGPathCloseSubpath(info->path); + break; + } + } + } + }; + + TransformApplier ta; + ta.path = CGPathCreateMutable(); + ta.transform = GfxMatrixToCGAffineTransform(aTransform); + + CGPathApply(mPath, &ta, TransformApplier::TranformCGPathApplierFunc); + RefPtr builder = new PathBuilderCG(ta.path, aFillRule); + return builder; +} + + +bool +PathCG::ContainsPoint(const Point &aPoint, const Matrix &aTransform) const +{ + Matrix inverse = aTransform; + inverse.Invert(); + Point transformedPoint = inverse*aPoint; + // We could probably drop the input transform and just transform the point at the caller? + CGPoint point = {transformedPoint.x, transformedPoint.y}; + + // The transform parameter of CGPathContainsPoint doesn't seem to work properly on OS X 10.5 + // so we transform aPoint ourselves. + return CGPathContainsPoint(mPath, NULL, point, mFillRule == FILL_EVEN_ODD); +} + +static size_t +PutBytesNull(void *info, const void *buffer, size_t count) +{ + return count; +} + +/* The idea of a scratch context comes from WebKit */ +static CGContextRef +CreateScratchContext() +{ + CGDataConsumerCallbacks callbacks = {PutBytesNull, NULL}; + CGDataConsumerRef consumer = CGDataConsumerCreate(NULL, &callbacks); + CGContextRef cg = CGPDFContextCreate(consumer, NULL, NULL); + CGDataConsumerRelease(consumer); + return cg; +} + +static CGContextRef +ScratchContext() +{ + static CGContextRef cg = CreateScratchContext(); + return cg; +} + +//XXX: what should these functions return for an empty path? +// currently they return CGRectNull {inf,inf, 0, 0} +Rect +PathCG::GetBounds(const Matrix &aTransform) const +{ + //XXX: are these bounds tight enough + Rect bounds = CGRectToRect(CGPathGetBoundingBox(mPath)); + //XXX: curretnly this returns the bounds of the transformed bounds + // this is strictly looser than the bounds of the transformed path + return aTransform.TransformBounds(bounds); +} + +Rect +PathCG::GetStrokedBounds(const StrokeOptions &aStrokeOptions, + const Matrix &aTransform) const +{ + // 10.7 has CGPathCreateCopyByStrokingPath which we could use + // instead of this scratch context business + CGContextRef cg = ScratchContext(); + + CGContextSaveGState(cg); + + CGContextBeginPath(cg); + CGContextAddPath(cg, mPath); + + SetStrokeOptions(cg, aStrokeOptions); + + CGContextReplacePathWithStrokedPath(cg); + Rect bounds = CGRectToRect(CGContextGetPathBoundingBox(cg)); + + CGContextRestoreGState(cg); + + return aTransform.TransformBounds(bounds); +} + + +} + +} diff --git a/gfx/2d/PathCG.h b/gfx/2d/PathCG.h new file mode 100644 index 000000000000..2bff9d5a0a21 --- /dev/null +++ b/gfx/2d/PathCG.h @@ -0,0 +1,133 @@ +/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * ***** BEGIN LICENSE BLOCK ***** + * Version: MPL 1.1/GPL 2.0/LGPL 2.1 + * + * The contents of this file are subject to the Mozilla Public License Version + * 1.1 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * http://www.mozilla.org/MPL/ + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + * + * The Original Code is Mozilla Corporation code. + * + * The Initial Developer of the Original Code is Mozilla Foundation. + * Portions created by the Initial Developer are Copyright (C) 2011 + * the Initial Developer. All Rights Reserved. + * + * Contributor(s): + * Bas Schouten + * + * Alternatively, the contents of this file may be used under the terms of + * either the GNU General Public License Version 2 or later (the "GPL"), or + * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), + * in which case the provisions of the GPL or the LGPL are applicable instead + * of those above. If you wish to allow use of your version of this file only + * under the terms of either the GPL or the LGPL, and not to allow others to + * use your version of this file under the terms of the MPL, indicate your + * decision by deleting the provisions above and replace them with the notice + * and other provisions required by the GPL or the LGPL. If you do not delete + * the provisions above, a recipient may use your version of this file under + * the terms of any one of the MPL, the GPL or the LGPL. + * + * ***** END LICENSE BLOCK ***** */ + +#ifndef MOZILLA_GFX_PATHCG_H_ +#define MOZILLA_GFX_PATHCG_H_ + +#include +#include "2D.h" + +namespace mozilla { +namespace gfx { + +class PathCG; + +class PathBuilderCG : public PathBuilder +{ +public: + // absorbs a reference of aPath + PathBuilderCG(CGMutablePathRef aPath, FillRule aFillRule) + : mFigureActive(false) + , mFillRule(aFillRule) + { + mCGPath = aPath; + } + + PathBuilderCG(FillRule aFillRule) + : mFigureActive(false) + , mFillRule(aFillRule) + { + mCGPath = CGPathCreateMutable(); + } + + virtual ~PathBuilderCG(); + + virtual void MoveTo(const Point &aPoint); + virtual void LineTo(const Point &aPoint); + virtual void BezierTo(const Point &aCP1, + const Point &aCP2, + const Point &aCP3); + virtual void QuadraticBezierTo(const Point &aCP1, + const Point &aCP2); + virtual void Close(); + virtual void Arc(const Point &aOrigin, Float aRadius, Float aStartAngle, + Float aEndAngle, bool aAntiClockwise = false); + virtual Point CurrentPoint() const; + + virtual TemporaryRef Finish(); + +private: + friend class PathCG; + + void EnsureActive(const Point &aPoint); + + CGMutablePathRef mCGPath; + bool mFigureActive; + Point mCurrentPoint; + Point mBeginPoint; + FillRule mFillRule; +}; + +class PathCG : public Path +{ +public: + PathCG(CGMutablePathRef aPath, FillRule aFillRule) + : mPath(aPath) + , mFillRule(aFillRule) + { + CGPathRetain(mPath); + } + virtual ~PathCG() { CGPathRelease(mPath); } + + virtual BackendType GetBackendType() const { return BACKEND_COREGRAPHICS; } + + virtual TemporaryRef CopyToBuilder(FillRule aFillRule = FILL_WINDING) const; + virtual TemporaryRef TransformedCopyToBuilder(const Matrix &aTransform, + FillRule aFillRule = FILL_WINDING) const; + + virtual bool ContainsPoint(const Point &aPoint, const Matrix &aTransform) const; + virtual Rect GetBounds(const Matrix &aTransform = Matrix()) const; + virtual Rect GetStrokedBounds(const StrokeOptions &aStrokeOptions, + const Matrix &aTransform = Matrix()) const; + + virtual FillRule GetFillRule() const { return mFillRule; } + + CGMutablePathRef GetPath() const { return mPath; } + +private: + friend class DrawTargetCG; + + CGMutablePathRef mPath; + bool mEndedActive; + Point mEndPoint; + FillRule mFillRule; +}; + +} +} + +#endif diff --git a/gfx/2d/ScaledFontSkia.cpp b/gfx/2d/ScaledFontBase.cpp similarity index 69% rename from gfx/2d/ScaledFontSkia.cpp rename to gfx/2d/ScaledFontBase.cpp index 30d11b47351f..ebe1b05acd17 100644 --- a/gfx/2d/ScaledFontSkia.cpp +++ b/gfx/2d/ScaledFontBase.cpp @@ -35,10 +35,12 @@ * * ***** END LICENSE BLOCK ***** */ -#include "ScaledFontSkia.h" +#include "ScaledFontBase.h" +#ifdef USE_SKIA #include "PathSkia.h" #include "skia/SkPaint.h" #include "skia/SkPath.h" +#endif #include #include using namespace std; @@ -46,7 +48,7 @@ using namespace std; namespace mozilla { namespace gfx { - +#ifdef USE_SKIA static SkTypeface::Style gfxFontStyleToSkia(const gfxFontStyle* aStyle) { if (aStyle->style == NS_FONT_STYLE_ITALIC) { @@ -61,49 +63,57 @@ static SkTypeface::Style gfxFontStyleToSkia(const gfxFontStyle* aStyle) return SkTypeface::kNormal; } -ScaledFontSkia::ScaledFontSkia(gfxFont* aFont, Float aSize) +ScaledFontBase::ScaledFontBase(gfxFont* aFont, Float aSize) : mSize(aSize) { NS_LossyConvertUTF16toASCII name(aFont->GetName()); mTypeface = SkTypeface::CreateFromName(name.get(), gfxFontStyleToSkia(aFont->GetStyle())); } +#endif -ScaledFontSkia::ScaledFontSkia(Float aSize) +ScaledFontBase::~ScaledFontBase() +{ +#ifdef USE_SKIA + SkSafeUnref(mTypeface); +#endif +} + +ScaledFontBase::ScaledFontBase(Float aSize) : mSize(aSize) { +#ifdef USE_SKIA + mTypeface = NULL; +#endif } -ScaledFontSkia::~ScaledFontSkia() -{ - SkSafeUnref(mTypeface); -} TemporaryRef -ScaledFontSkia::GetPathForGlyphs(const GlyphBuffer &aBuffer, const DrawTarget *aTarget) +ScaledFontBase::GetPathForGlyphs(const GlyphBuffer &aBuffer, const DrawTarget *aTarget) { - if (aTarget->GetType() != BACKEND_SKIA) { - return NULL; +#ifdef USE_SKIA + if (aTarget->GetType() == BACKEND_SKIA) { + SkPaint paint; + paint.setTypeface(GetSkTypeface()); + paint.setTextEncoding(SkPaint::kGlyphID_TextEncoding); + paint.setTextSize(SkFloatToScalar(mSize)); + + std::vector indices; + std::vector offsets; + indices.resize(aBuffer.mNumGlyphs); + offsets.resize(aBuffer.mNumGlyphs); + + for (unsigned int i = 0; i < aBuffer.mNumGlyphs; i++) { + indices[i] = aBuffer.mGlyphs[i].mIndex; + offsets[i].fX = SkFloatToScalar(aBuffer.mGlyphs[i].mPosition.x); + offsets[i].fY = SkFloatToScalar(aBuffer.mGlyphs[i].mPosition.y); + } + + SkPath path; + paint.getPosTextPath(&indices.front(), aBuffer.mNumGlyphs*2, &offsets.front(), &path); + return new PathSkia(path, FILL_WINDING); } - - SkPaint paint; - paint.setTypeface(mTypeface); - paint.setTextEncoding(SkPaint::kGlyphID_TextEncoding); - paint.setTextSize(SkFloatToScalar(mSize)); - - std::vector indices; - std::vector offsets; - indices.resize(aBuffer.mNumGlyphs); - offsets.resize(aBuffer.mNumGlyphs); - - for (unsigned int i = 0; i < aBuffer.mNumGlyphs; i++) { - indices[i] = aBuffer.mGlyphs[i].mIndex; - offsets[i].fX = SkFloatToScalar(aBuffer.mGlyphs[i].mPosition.x); - offsets[i].fY = SkFloatToScalar(aBuffer.mGlyphs[i].mPosition.y); - } - - SkPath path; - paint.getPosTextPath(&indices.front(), aBuffer.mNumGlyphs*2, &offsets.front(), &path); - return new PathSkia(path, FILL_WINDING); +#endif + return NULL; } } diff --git a/gfx/2d/ScaledFontSkia.h b/gfx/2d/ScaledFontBase.h similarity index 84% rename from gfx/2d/ScaledFontSkia.h rename to gfx/2d/ScaledFontBase.h index 45277038816c..cd4d2ad2b51c 100644 --- a/gfx/2d/ScaledFontSkia.h +++ b/gfx/2d/ScaledFontBase.h @@ -35,36 +35,41 @@ * * ***** END LICENSE BLOCK ***** */ -#ifndef MOZILLA_GFX_SCALEDFONTSKIA_H_ -#define MOZILLA_GFX_SCALEDFONTSKIA_H_ +#ifndef MOZILLA_GFX_SCALEDFONTBASE_H_ +#define MOZILLA_GFX_SCALEDFONTBASE_H_ #include "2D.h" +#ifdef USE_SKIA #include "skia/SkTypeface.h" +#endif class gfxFont; namespace mozilla { namespace gfx { -class ScaledFontSkia : public ScaledFont +class ScaledFontBase : public ScaledFont { public: - ScaledFontSkia(gfxFont* aFont, Float aSize); - ScaledFontSkia(Float aSize); - virtual ~ScaledFontSkia(); - - virtual FontType GetType() const { return FONT_SKIA; } + ScaledFontBase(Float aSize); + virtual ~ScaledFontBase(); virtual TemporaryRef GetPathForGlyphs(const GlyphBuffer &aBuffer, const DrawTarget *aTarget); +#ifdef USE_SKIA + ScaledFontBase(gfxFont* aFont, Float aSize); + virtual SkTypeface* GetSkTypeface() { return mTypeface; } + virtual FontType GetType() const { return FONT_SKIA; } +#endif protected: friend class DrawTargetSkia; - +#ifdef USE_SKIA SkTypeface* mTypeface; +#endif Float mSize; }; } } -#endif /* MOZILLA_GFX_SCALEDFONTSKIA_H_ */ +#endif /* MOZILLA_GFX_SCALEDFONTBASE_H_ */ diff --git a/gfx/2d/ScaledFontMac.cpp b/gfx/2d/ScaledFontMac.cpp index 147b43db502e..ebd70c0d00ee 100644 --- a/gfx/2d/ScaledFontMac.cpp +++ b/gfx/2d/ScaledFontMac.cpp @@ -36,25 +36,76 @@ * ***** END LICENSE BLOCK ***** */ #include "ScaledFontMac.h" +#ifdef USE_SKIA #include "PathSkia.h" #include "skia/SkPaint.h" #include "skia/SkPath.h" #include "skia/SkTypeface_mac.h" +#endif +#include "DrawTargetCG.h" #include +// prototype for private API +extern "C" { +CGPathRef CGFontGetGlyphPath(CGFontRef fontRef, CGAffineTransform *textTransform, int unknown, CGGlyph glyph); +}; + + namespace mozilla { namespace gfx { ScaledFontMac::ScaledFontMac(CGFontRef aFont, Float aSize) - : ScaledFontSkia(aSize) + : ScaledFontBase(aSize) { - mFontFace = CTFontCreateWithGraphicsFont(aFont, aSize, NULL, NULL); - mTypeface = SkCreateTypefaceFromCTFont(mFontFace); + // XXX: should we be taking a reference + mFont = CGFontRetain(aFont); } ScaledFontMac::~ScaledFontMac() { - CFRelease(mFontFace); + CGFontRelease(mFont); +} + +#ifdef USE_SKIA +SkTypeface* ScaledFontMac::GetSkTypeface() +{ + if (!mTypeface) { + CTFontRef fontFace = CTFontCreateWithGraphicsFont(mFont, mSize, NULL, NULL); + mTypeface = SkCreateTypefaceFromCTFont(fontFace); + CFRelease(fontFace); + } + return mTypeface; +} +#endif + +// private API here are the public options on OS X +// CTFontCreatePathForGlyph +// ATSUGlyphGetCubicPaths +// we've used this in cairo sucessfully for some time. +// Note: cairo dlsyms it. We could do that but maybe it's +// safe just to use? + +TemporaryRef +ScaledFontMac::GetPathForGlyphs(const GlyphBuffer &aBuffer, const DrawTarget *aTarget) +{ + if (aTarget->GetType() == BACKEND_COREGRAPHICS) { + CGMutablePathRef path = CGPathCreateMutable(); + + for (unsigned int i = 0; i < aBuffer.mNumGlyphs; i++) { + // XXX: we could probably fold both of these transforms together to avoid extra work + CGAffineTransform flip = CGAffineTransformMakeScale(1, -1); + CGPathRef glyphPath = ::CGFontGetGlyphPath(mFont, &flip, 0, aBuffer.mGlyphs[i].mIndex); + + CGAffineTransform matrix = CGAffineTransformMake(mSize, 0, 0, mSize, + aBuffer.mGlyphs[i].mPosition.x, + aBuffer.mGlyphs[i].mPosition.y); + CGPathAddPath(path, &matrix, glyphPath); + CGPathRelease(glyphPath); + } + return new PathCG(path, FILL_WINDING); + } else { + return ScaledFontBase::GetPathForGlyphs(aBuffer, aTarget); + } } } diff --git a/gfx/2d/ScaledFontMac.h b/gfx/2d/ScaledFontMac.h index 5d055001af8d..5d54124256e1 100644 --- a/gfx/2d/ScaledFontMac.h +++ b/gfx/2d/ScaledFontMac.h @@ -38,25 +38,28 @@ #ifndef MOZILLA_GFX_SCALEDFONTMAC_H_ #define MOZILLA_GFX_SCALEDFONTMAC_H_ -#include "ScaledFontSkia.h" #import +#include "2D.h" +#include "ScaledFontBase.h" namespace mozilla { namespace gfx { -class ScaledFontMac : public ScaledFontSkia +class ScaledFontMac : public ScaledFontBase { public: ScaledFontMac(CGFontRef aFont, Float aSize); virtual ~ScaledFontMac(); virtual FontType GetType() const { return FONT_MAC; } - +#ifdef USE_SKIA + virtual SkTypeface* GetSkTypeface(); +#endif + virtual TemporaryRef GetPathForGlyphs(const GlyphBuffer &aBuffer, const DrawTarget *aTarget); private: - friend class DrawTargetSkia; - - CTFontRef mFontFace; + friend class DrawTargetCG; + CGFontRef mFont; }; } diff --git a/gfx/2d/ScaledFontWin.cpp b/gfx/2d/ScaledFontWin.cpp index d1614e42c214..ebce36098b09 100644 --- a/gfx/2d/ScaledFontWin.cpp +++ b/gfx/2d/ScaledFontWin.cpp @@ -36,18 +36,32 @@ * ***** END LICENSE BLOCK ***** */ #include "ScaledFontWin.h" +#include "ScaeldFontBase.h" + +#ifdef USE_SKIA #include "skia/SkTypeface_win.h" +#endif namespace mozilla { namespace gfx { ScaledFontWin::ScaledFontWin(gfxGDIFont* aFont, Float aSize) - : ScaledFontSkia(aSize) + : ScaledFontBase(aSize) { LOGFONT lf; GetObject(aFont->GetHFONT(), sizeof(LOGFONT), &lf); - mTypeface = SkCreateTypefaceFromLOGFONT(lf); } +#ifdef USE_SKIA +SkTypeface* ScaledFontWin::GetSkTypeface() +{ + if (!mTypeface) { + mTypeface = SkCreateTypefaceFromLOGFONT(lf); + } + return mTypeface; +} +#endif + + } } diff --git a/gfx/2d/ScaledFontWin.h b/gfx/2d/ScaledFontWin.h index b9eccc07bafc..950ec2dfea0e 100644 --- a/gfx/2d/ScaledFontWin.h +++ b/gfx/2d/ScaledFontWin.h @@ -38,21 +38,25 @@ #ifndef MOZILLA_GFX_SCALEDFONTWIN_H_ #define MOZILLA_GFX_SCALEDFONTWIN_H_ -#include "ScaledFontSkia.h" +#include "ScaledFontBase.h" #include "gfxGDIFont.h" namespace mozilla { namespace gfx { -class ScaledFontWin : public ScaledFontSkia +class ScaledFontWin : public ScaledFontBase { public: ScaledFontWin(gfxGDIFont* aFont, Float aSize); virtual FontType GetType() const { return FONT_GDI; } - +#ifdef USE_SKIA + virtual SkTypeface* GetSkTypeface(); +#endif private: +#ifdef USE_SKIA friend class DrawTargetSkia; +#endif }; } diff --git a/gfx/2d/SourceSurfaceCG.cpp b/gfx/2d/SourceSurfaceCG.cpp index f3cc3c8bffaa..2264d997e792 100644 --- a/gfx/2d/SourceSurfaceCG.cpp +++ b/gfx/2d/SourceSurfaceCG.cpp @@ -40,9 +40,6 @@ namespace mozilla { namespace gfx { -SourceSurfaceCG::SourceSurfaceCG() -{ -} SourceSurfaceCG::~SourceSurfaceCG() { @@ -53,8 +50,8 @@ IntSize SourceSurfaceCG::GetSize() const { IntSize size; - size.width = CGImageGetHeight(mImage); - size.height = CGImageGetWidth(mImage); + size.width = CGImageGetWidth(mImage); + size.height = CGImageGetHeight(mImage); return size; } @@ -67,10 +64,13 @@ SourceSurfaceCG::GetFormat() const TemporaryRef SourceSurfaceCG::GetDataSurface() { - return NULL; + //XXX: we should be more disciplined about who takes a reference and where + CGImageRetain(mImage); + RefPtr dataSurf = + new DataSourceSurfaceCG(mImage); + return dataSurf; } - static void releaseCallback(void *info, const void *data, size_t size) { free(info); } @@ -88,22 +88,24 @@ SourceSurfaceCG::InitFromData(unsigned char *aData, int bitsPerComponent = 0; int bitsPerPixel = 0; + assert(aSize.width >= 0 && aSize.height >= 0); + switch (aFormat) { - case B8G8R8A8: + case FORMAT_B8G8R8A8: colorSpace = CGColorSpaceCreateDeviceRGB(); bitinfo = kCGImageAlphaPremultipliedFirst | kCGBitmapByteOrder32Host; bitsPerComponent = 8; bitsPerPixel = 32; break; - case B8G8R8X8: + case FORMAT_B8G8R8X8: colorSpace = CGColorSpaceCreateDeviceRGB(); bitinfo = kCGImageAlphaNoneSkipFirst | kCGBitmapByteOrder32Host; bitsPerComponent = 8; bitsPerPixel = 32; break; - case A8: + case FORMAT_A8: // XXX: why don't we set a colorspace here? bitsPerComponent = 8; bitsPerPixel = 8; @@ -119,7 +121,7 @@ SourceSurfaceCG::InitFromData(unsigned char *aData, aSize.height * aStride, releaseCallback); - if (aFormat == A8) { + if (aFormat == FORMAT_A8) { CGFloat decode[] = {1.0, 0.0}; mImage = CGImageMaskCreate (aSize.width, aSize.height, bitsPerComponent, @@ -145,12 +147,173 @@ SourceSurfaceCG::InitFromData(unsigned char *aData, CGDataProviderRelease(dataProvider); CGColorSpaceRelease (colorSpace); - if (mImage) { - return false; + return mImage != NULL; +} + +DataSourceSurfaceCG::~DataSourceSurfaceCG() +{ + CGImageRelease(mImage); + free(CGBitmapContextGetData(mCg)); + CGContextRelease(mCg); +} + +IntSize +DataSourceSurfaceCG::GetSize() const +{ + IntSize size; + size.width = CGImageGetWidth(mImage); + size.height = CGImageGetHeight(mImage); + return size; +} + +bool +DataSourceSurfaceCG::InitFromData(unsigned char *aData, + const IntSize &aSize, + int32_t aStride, + SurfaceFormat aFormat) +{ + //XXX: we should avoid creating this colorspace everytime + CGColorSpaceRef colorSpace = NULL; + CGBitmapInfo bitinfo = 0; + CGDataProviderRef dataProvider = NULL; + int bitsPerComponent = 0; + int bitsPerPixel = 0; + + switch (aFormat) { + case FORMAT_B8G8R8A8: + colorSpace = CGColorSpaceCreateDeviceRGB(); + bitinfo = kCGImageAlphaPremultipliedFirst | kCGBitmapByteOrder32Host; + bitsPerComponent = 8; + bitsPerPixel = 32; + break; + + case FORMAT_B8G8R8X8: + colorSpace = CGColorSpaceCreateDeviceRGB(); + bitinfo = kCGImageAlphaNoneSkipFirst | kCGBitmapByteOrder32Host; + bitsPerComponent = 8; + bitsPerPixel = 32; + break; + + case FORMAT_A8: + // XXX: why don't we set a colorspace here? + bitsPerComponent = 8; + bitsPerPixel = 8; + }; + + void *data = malloc(aStride * aSize.height); + memcpy(data, aData, aStride * aSize.height); + + //mFormat = aFormat; + + dataProvider = CGDataProviderCreateWithData (data, + data, + aSize.height * aStride, + releaseCallback); + + if (aFormat == FORMAT_A8) { + CGFloat decode[] = {1.0, 0.0}; + mImage = CGImageMaskCreate (aSize.width, aSize.height, + bitsPerComponent, + bitsPerPixel, + aStride, + dataProvider, + decode, + true); + + } else { + mImage = CGImageCreate (aSize.width, aSize.height, + bitsPerComponent, + bitsPerPixel, + aStride, + colorSpace, + bitinfo, + dataProvider, + NULL, + true, + kCGRenderingIntentDefault); } - return true; + CGDataProviderRelease(dataProvider); + CGColorSpaceRelease (colorSpace); + + return mImage; } +CGContextRef CreateBitmapContextForImage(CGImageRef image) +{ + CGColorSpaceRef colorSpace; + + size_t width = CGImageGetWidth(image); + size_t height = CGImageGetHeight(image); + + int bitmapBytesPerRow = (width * 4); + int bitmapByteCount = (bitmapBytesPerRow * height); + + void *data = calloc(bitmapByteCount, 1); + //XXX: which color space should we be using here? + colorSpace = CGColorSpaceCreateDeviceRGB(); + assert(colorSpace); + + // we'd like to pass NULL as the first parameter + // to let Quartz manage this memory for us. However, + // on 10.5 and older CGBitmapContextGetData will return + // NULL instead of the associated buffer so we need + // to manage it ourselves. + CGContextRef cg = CGBitmapContextCreate(data, + width, + height, + 8, + bitmapBytesPerRow, + colorSpace, + kCGBitmapByteOrder32Host | kCGImageAlphaPremultipliedFirst); + assert(cg); + + CGColorSpaceRelease(colorSpace); + + return cg; +} + +DataSourceSurfaceCG::DataSourceSurfaceCG(CGImageRef aImage) +{ + mImage = aImage; + mCg = CreateBitmapContextForImage(aImage); + if (mCg == NULL) { + // error creating context + return; + } + + // Get image width, height. We'll use the entire image. + CGFloat w = CGImageGetWidth(aImage); + CGFloat h = CGImageGetHeight(aImage); + CGRect rect = {{0,0},{w,h}}; + + // Draw the image to the bitmap context. Once we draw, the memory + // allocated for the context for rendering will then contain the + // raw image data in the specified color space. + CGContextDrawImage(mCg, rect, aImage); + + // Now we can get a pointer to the image data associated with the bitmap + // context. + mData = CGBitmapContextGetData(mCg); + assert(mData); +} + +unsigned char * +DataSourceSurfaceCG::GetData() +{ + // See http://developer.apple.com/library/mac/#qa/qa1509/_index.html + // the following only works on 10.5+, the Q&A above suggests a method + // that can be used for earlier versions + //CFDataRef data = CGDataProviderCopyData(CGImageGetDataProvider(cgImage)); + //unsigned char *dataPtr = CFDataGetBytePtr(data); + //CFDataRelease(data); + // unfortunately the the method above only works for read-only access and + // we need read-write for DataSourceSurfaces + + return (unsigned char*)mData; +} + + + } } diff --git a/gfx/2d/SourceSurfaceCG.h b/gfx/2d/SourceSurfaceCG.h index e6facd9e7a24..1aa1b6ae45cc 100644 --- a/gfx/2d/SourceSurfaceCG.h +++ b/gfx/2d/SourceSurfaceCG.h @@ -47,10 +47,11 @@ namespace gfx { class SourceSurfaceCG : public SourceSurface { public: - SourceSurfaceCG(); + SourceSurfaceCG() {} + SourceSurfaceCG(CGImageRef aImage) : mImage(aImage) {} ~SourceSurfaceCG(); - virtual SurfaceType GetType() const { return COREGRAPHICS_IMAGE; } + virtual SurfaceType GetType() const { return SURFACE_COREGRAPHICS_IMAGE; } virtual IntSize GetSize() const; virtual SurfaceFormat GetFormat() const; virtual TemporaryRef GetDataSurface(); @@ -71,5 +72,38 @@ private: SurfaceFormat mFormat; }; +class DataSourceSurfaceCG : public DataSourceSurface +{ +public: + DataSourceSurfaceCG() {} + DataSourceSurfaceCG(CGImageRef aImage); + ~DataSourceSurfaceCG(); + + virtual SurfaceType GetType() const { return SURFACE_DATA; } + virtual IntSize GetSize() const; + virtual SurfaceFormat GetFormat() const { return FORMAT_B8G8R8A8; } + + CGImageRef GetImage() { return mImage; } + + bool InitFromData(unsigned char *aData, + const IntSize &aSize, + int32_t aStride, + SurfaceFormat aFormat); + + virtual unsigned char *GetData(); + + virtual int32_t Stride() { return CGImageGetBytesPerRow(mImage); } + + +private: + CGContextRef mCg; + CGImageRef mImage; + //XXX: we don't need to store mData we can just get it from the CGContext + void *mData; + /* It might be better to just use the bitmap info from the CGImageRef to + * deduce the format to save space in SourceSurfaceCG, + * for now we just store it in mFormat */ +}; + } } diff --git a/gfx/2d/Types.h b/gfx/2d/Types.h index cc8c80468ec3..484e987c1cda 100644 --- a/gfx/2d/Types.h +++ b/gfx/2d/Types.h @@ -80,13 +80,15 @@ enum FontType FONT_GDI, FONT_MAC, FONT_SKIA, - FONT_CAIRO + FONT_CAIRO, + FONT_COREGRAPHICS }; enum NativeSurfaceType { NATIVE_SURFACE_D3D10_TEXTURE, - NATIVE_SURFACE_CAIRO_SURFACE + NATIVE_SURFACE_CAIRO_SURFACE, + NATIVE_SURFACE_CGCONTEXT }; enum NativeFontType diff --git a/gfx/thebes/gfxFont.h b/gfx/thebes/gfxFont.h index a3fe6bd3d44f..f38363abedac 100644 --- a/gfx/thebes/gfxFont.h +++ b/gfx/thebes/gfxFont.h @@ -683,7 +683,7 @@ struct gfxTextRange { * completely, with all its words, and avoid the cost of aging the words * individually. That only happens with longer-lived fonts. */ -class THEBES_API gfxFontCache : public nsExpirationTracker { +class THEBES_API gfxFontCache MOZ_FINAL : public nsExpirationTracker { public: enum { FONT_TIMEOUT_SECONDS = 10, diff --git a/gfx/thebes/gfxPlatformMac.cpp b/gfx/thebes/gfxPlatformMac.cpp index 8771e5531e5a..f2d1d98ecc9f 100644 --- a/gfx/thebes/gfxPlatformMac.cpp +++ b/gfx/thebes/gfxPlatformMac.cpp @@ -57,6 +57,7 @@ #include "qcms.h" #include +#include "mozilla/gfx/2D.h" using namespace mozilla; using namespace mozilla::gfx; @@ -131,7 +132,7 @@ gfxPlatformMac::CreateOffscreenSurface(const gfxIntSize& size, NS_IF_ADDREF(newSurface); return newSurface; } - + already_AddRefed gfxPlatformMac::OptimizeImage(gfxImageSurface *aSurface, gfxASurface::gfxImageFormat format) @@ -162,7 +163,7 @@ gfxPlatformMac::GetScaledFontForFont(gfxFont *aFont) bool gfxPlatformMac::SupportsAzure(BackendType& aBackend) { - aBackend = BACKEND_SKIA; + aBackend = BACKEND_COREGRAPHICS; return true; } @@ -298,6 +299,26 @@ gfxPlatformMac::ReadAntiAliasingThreshold() return threshold; } +already_AddRefed +gfxPlatformMac::GetThebesSurfaceForDrawTarget(DrawTarget *aTarget) +{ + if (aTarget->GetType() == BACKEND_COREGRAPHICS) { + CGContextRef cg = static_cast(aTarget->GetNativeSurface(NATIVE_SURFACE_CGCONTEXT)); + + //XXX: it would be nice to have an implicit conversion from IntSize to gfxIntSize + IntSize intSize = aTarget->GetSize(); + gfxIntSize size(intSize.width, intSize.height); + + nsRefPtr surf = + new gfxQuartzSurface(cg, size); + + return surf.forget(); + } + + return gfxPlatform::GetThebesSurfaceForDrawTarget(aTarget); +} + + qcms_profile * gfxPlatformMac::GetPlatformCMSOutputProfile() { diff --git a/gfx/thebes/gfxPlatformMac.h b/gfx/thebes/gfxPlatformMac.h index fa262d10d59b..8bedb9a857e3 100644 --- a/gfx/thebes/gfxPlatformMac.h +++ b/gfx/thebes/gfxPlatformMac.h @@ -50,6 +50,7 @@ #define MAC_OS_X_MAJOR_VERSION_MASK 0xFFFFFFF0U class gfxTextRun; +class mozilla::gfx::DrawTarget; class THEBES_API gfxPlatformMac : public gfxPlatform { public: @@ -104,6 +105,8 @@ public: // lower threshold on font anti-aliasing PRUint32 GetAntiAliasingThreshold() { return mFontAntiAliasingThreshold; } + virtual already_AddRefed + GetThebesSurfaceForDrawTarget(mozilla::gfx::DrawTarget *aTarget); private: virtual qcms_profile* GetPlatformCMSOutputProfile(); diff --git a/gfx/thebes/gfxWindowsPlatform.cpp b/gfx/thebes/gfxWindowsPlatform.cpp index 18f47edb6f3b..2b7ca62b7981 100644 --- a/gfx/thebes/gfxWindowsPlatform.cpp +++ b/gfx/thebes/gfxWindowsPlatform.cpp @@ -519,6 +519,7 @@ gfxWindowsPlatform::GetThebesSurfaceForDrawTarget(DrawTarget *aTarget) nsRefPtr surf = new gfxD2DSurface(texture, ContentForFormat(aTarget->GetFormat())); + // shouldn't this hold a reference? surf->SetData(&kDrawTarget, aTarget, NULL); return surf.forget();