1.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 1.2 +++ b/gfx/2d/DrawTargetCG.cpp Wed Dec 31 06:09:35 2014 +0100 1.3 @@ -0,0 +1,1609 @@ 1.4 +/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 2 -*- 1.5 + * This Source Code Form is subject to the terms of the Mozilla Public 1.6 + * License, v. 2.0. If a copy of the MPL was not distributed with this 1.7 + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 1.8 +#include "BorrowedContext.h" 1.9 +#include "DataSurfaceHelpers.h" 1.10 +#include "DrawTargetCG.h" 1.11 +#include "Logging.h" 1.12 +#include "SourceSurfaceCG.h" 1.13 +#include "Rect.h" 1.14 +#include "ScaledFontMac.h" 1.15 +#include "Tools.h" 1.16 +#include <vector> 1.17 +#include <algorithm> 1.18 +#include "MacIOSurface.h" 1.19 +#include "FilterNodeSoftware.h" 1.20 +#include "mozilla/Assertions.h" 1.21 +#include "mozilla/Types.h" // for decltype 1.22 +#include "mozilla/FloatingPoint.h" 1.23 + 1.24 +using namespace std; 1.25 + 1.26 +//CG_EXTERN void CGContextSetCompositeOperation (CGContextRef, PrivateCGCompositeMode); 1.27 + 1.28 +// A private API that Cairo has been using for a long time 1.29 +CG_EXTERN void CGContextSetCTM(CGContextRef, CGAffineTransform); 1.30 + 1.31 +namespace mozilla { 1.32 +namespace gfx { 1.33 + 1.34 +static CGRect RectToCGRect(Rect r) 1.35 +{ 1.36 + return CGRectMake(r.x, r.y, r.width, r.height); 1.37 +} 1.38 + 1.39 +CGBlendMode ToBlendMode(CompositionOp op) 1.40 +{ 1.41 + CGBlendMode mode; 1.42 + switch (op) { 1.43 + case CompositionOp::OP_OVER: 1.44 + mode = kCGBlendModeNormal; 1.45 + break; 1.46 + case CompositionOp::OP_ADD: 1.47 + mode = kCGBlendModePlusLighter; 1.48 + break; 1.49 + case CompositionOp::OP_ATOP: 1.50 + mode = kCGBlendModeSourceAtop; 1.51 + break; 1.52 + case CompositionOp::OP_OUT: 1.53 + mode = kCGBlendModeSourceOut; 1.54 + break; 1.55 + case CompositionOp::OP_IN: 1.56 + mode = kCGBlendModeSourceIn; 1.57 + break; 1.58 + case CompositionOp::OP_SOURCE: 1.59 + mode = kCGBlendModeCopy; 1.60 + break; 1.61 + case CompositionOp::OP_DEST_IN: 1.62 + mode = kCGBlendModeDestinationIn; 1.63 + break; 1.64 + case CompositionOp::OP_DEST_OUT: 1.65 + mode = kCGBlendModeDestinationOut; 1.66 + break; 1.67 + case CompositionOp::OP_DEST_OVER: 1.68 + mode = kCGBlendModeDestinationOver; 1.69 + break; 1.70 + case CompositionOp::OP_DEST_ATOP: 1.71 + mode = kCGBlendModeDestinationAtop; 1.72 + break; 1.73 + case CompositionOp::OP_XOR: 1.74 + mode = kCGBlendModeXOR; 1.75 + break; 1.76 + case CompositionOp::OP_MULTIPLY: 1.77 + mode = kCGBlendModeMultiply; 1.78 + break; 1.79 + case CompositionOp::OP_SCREEN: 1.80 + mode = kCGBlendModeScreen; 1.81 + break; 1.82 + case CompositionOp::OP_OVERLAY: 1.83 + mode = kCGBlendModeOverlay; 1.84 + break; 1.85 + case CompositionOp::OP_DARKEN: 1.86 + mode = kCGBlendModeDarken; 1.87 + break; 1.88 + case CompositionOp::OP_LIGHTEN: 1.89 + mode = kCGBlendModeLighten; 1.90 + break; 1.91 + case CompositionOp::OP_COLOR_DODGE: 1.92 + mode = kCGBlendModeColorDodge; 1.93 + break; 1.94 + case CompositionOp::OP_COLOR_BURN: 1.95 + mode = kCGBlendModeColorBurn; 1.96 + break; 1.97 + case CompositionOp::OP_HARD_LIGHT: 1.98 + mode = kCGBlendModeHardLight; 1.99 + break; 1.100 + case CompositionOp::OP_SOFT_LIGHT: 1.101 + mode = kCGBlendModeSoftLight; 1.102 + break; 1.103 + case CompositionOp::OP_DIFFERENCE: 1.104 + mode = kCGBlendModeDifference; 1.105 + break; 1.106 + case CompositionOp::OP_EXCLUSION: 1.107 + mode = kCGBlendModeExclusion; 1.108 + break; 1.109 + case CompositionOp::OP_HUE: 1.110 + mode = kCGBlendModeHue; 1.111 + break; 1.112 + case CompositionOp::OP_SATURATION: 1.113 + mode = kCGBlendModeSaturation; 1.114 + break; 1.115 + case CompositionOp::OP_COLOR: 1.116 + mode = kCGBlendModeColor; 1.117 + break; 1.118 + case CompositionOp::OP_LUMINOSITY: 1.119 + mode = kCGBlendModeLuminosity; 1.120 + break; 1.121 + /* 1.122 + case OP_CLEAR: 1.123 + mode = kCGBlendModeClear; 1.124 + break;*/ 1.125 + default: 1.126 + mode = kCGBlendModeNormal; 1.127 + } 1.128 + return mode; 1.129 +} 1.130 + 1.131 +static CGInterpolationQuality 1.132 +InterpolationQualityFromFilter(Filter aFilter) 1.133 +{ 1.134 + switch (aFilter) { 1.135 + default: 1.136 + case Filter::LINEAR: 1.137 + return kCGInterpolationLow; 1.138 + case Filter::POINT: 1.139 + return kCGInterpolationNone; 1.140 + case Filter::GOOD: 1.141 + return kCGInterpolationDefault; 1.142 + } 1.143 +} 1.144 + 1.145 + 1.146 +DrawTargetCG::DrawTargetCG() : mCg(nullptr), mSnapshot(nullptr) 1.147 +{ 1.148 +} 1.149 + 1.150 +DrawTargetCG::~DrawTargetCG() 1.151 +{ 1.152 + MarkChanged(); 1.153 + 1.154 + // We need to conditionally release these because Init can fail without initializing these. 1.155 + if (mColorSpace) 1.156 + CGColorSpaceRelease(mColorSpace); 1.157 + if (mCg) 1.158 + CGContextRelease(mCg); 1.159 +} 1.160 + 1.161 +BackendType 1.162 +DrawTargetCG::GetType() const 1.163 +{ 1.164 + // It may be worth spliting Bitmap and IOSurface DrawTarget 1.165 + // into seperate classes. 1.166 + if (GetContextType(mCg) == CG_CONTEXT_TYPE_IOSURFACE) { 1.167 + return BackendType::COREGRAPHICS_ACCELERATED; 1.168 + } else { 1.169 + return BackendType::COREGRAPHICS; 1.170 + } 1.171 +} 1.172 + 1.173 +TemporaryRef<SourceSurface> 1.174 +DrawTargetCG::Snapshot() 1.175 +{ 1.176 + if (!mSnapshot) { 1.177 + if (GetContextType(mCg) == CG_CONTEXT_TYPE_IOSURFACE) { 1.178 + return new SourceSurfaceCGIOSurfaceContext(this); 1.179 + } else { 1.180 + mSnapshot = new SourceSurfaceCGBitmapContext(this); 1.181 + } 1.182 + } 1.183 + 1.184 + return mSnapshot; 1.185 +} 1.186 + 1.187 +TemporaryRef<DrawTarget> 1.188 +DrawTargetCG::CreateSimilarDrawTarget(const IntSize &aSize, SurfaceFormat aFormat) const 1.189 +{ 1.190 + // XXX: in thebes we use CGLayers to do this kind of thing. It probably makes sense 1.191 + // to add that in somehow, but at a higher level 1.192 + RefPtr<DrawTargetCG> newTarget = new DrawTargetCG(); 1.193 + if (newTarget->Init(GetType(), aSize, aFormat)) { 1.194 + return newTarget; 1.195 + } else { 1.196 + return nullptr; 1.197 + } 1.198 +} 1.199 + 1.200 +TemporaryRef<SourceSurface> 1.201 +DrawTargetCG::CreateSourceSurfaceFromData(unsigned char *aData, 1.202 + const IntSize &aSize, 1.203 + int32_t aStride, 1.204 + SurfaceFormat aFormat) const 1.205 +{ 1.206 + RefPtr<SourceSurfaceCG> newSurf = new SourceSurfaceCG(); 1.207 + 1.208 + if (!newSurf->InitFromData(aData, aSize, aStride, aFormat)) { 1.209 + return nullptr; 1.210 + } 1.211 + 1.212 + return newSurf; 1.213 +} 1.214 + 1.215 +// This function returns a retained CGImage that needs to be released after 1.216 +// use. The reason for this is that we want to either reuse an existing CGImage 1.217 +// or create a new one. 1.218 +static CGImageRef 1.219 +GetRetainedImageFromSourceSurface(SourceSurface *aSurface) 1.220 +{ 1.221 + if (aSurface->GetType() == SurfaceType::COREGRAPHICS_IMAGE) 1.222 + return CGImageRetain(static_cast<SourceSurfaceCG*>(aSurface)->GetImage()); 1.223 + else if (aSurface->GetType() == SurfaceType::COREGRAPHICS_CGCONTEXT) 1.224 + return CGImageRetain(static_cast<SourceSurfaceCGContext*>(aSurface)->GetImage()); 1.225 + 1.226 + if (aSurface->GetType() == SurfaceType::DATA) { 1.227 + DataSourceSurface* dataSource = static_cast<DataSourceSurface*>(aSurface); 1.228 + return CreateCGImage(nullptr, dataSource->GetData(), dataSource->GetSize(), 1.229 + dataSource->Stride(), dataSource->GetFormat()); 1.230 + } 1.231 + 1.232 + MOZ_CRASH("unsupported source surface"); 1.233 +} 1.234 + 1.235 +TemporaryRef<SourceSurface> 1.236 +DrawTargetCG::OptimizeSourceSurface(SourceSurface *aSurface) const 1.237 +{ 1.238 + if (aSurface->GetType() == SurfaceType::COREGRAPHICS_IMAGE || 1.239 + aSurface->GetType() == SurfaceType::COREGRAPHICS_CGCONTEXT) { 1.240 + return aSurface; 1.241 + } 1.242 + return aSurface->GetDataSurface(); 1.243 +} 1.244 + 1.245 +class UnboundnessFixer 1.246 +{ 1.247 + CGRect mClipBounds; 1.248 + CGLayerRef mLayer; 1.249 + CGContextRef mCg; 1.250 + public: 1.251 + UnboundnessFixer() : mCg(nullptr) {} 1.252 + 1.253 + CGContextRef Check(CGContextRef baseCg, CompositionOp blend, const Rect* maskBounds = nullptr) 1.254 + { 1.255 + if (!IsOperatorBoundByMask(blend)) { 1.256 + mClipBounds = CGContextGetClipBoundingBox(baseCg); 1.257 + // If we're entirely clipped out or if the drawing operation covers the entire clip then 1.258 + // we don't need to create a temporary surface. 1.259 + if (CGRectIsEmpty(mClipBounds) || 1.260 + (maskBounds && maskBounds->Contains(CGRectToRect(mClipBounds)))) { 1.261 + return baseCg; 1.262 + } 1.263 + 1.264 + // TransparencyLayers aren't blended using the blend mode so 1.265 + // we are forced to use CGLayers 1.266 + 1.267 + //XXX: The size here is in default user space units, of the layer relative to the graphics context. 1.268 + // is the clip bounds still correct if, for example, we have a scale applied to the context? 1.269 + mLayer = CGLayerCreateWithContext(baseCg, mClipBounds.size, nullptr); 1.270 + mCg = CGLayerGetContext(mLayer); 1.271 + // CGContext's default to have the origin at the bottom left 1.272 + // so flip it to the top left and adjust for the origin 1.273 + // of the layer 1.274 + CGContextTranslateCTM(mCg, -mClipBounds.origin.x, mClipBounds.origin.y + mClipBounds.size.height); 1.275 + CGContextScaleCTM(mCg, 1, -1); 1.276 + 1.277 + return mCg; 1.278 + } else { 1.279 + return baseCg; 1.280 + } 1.281 + } 1.282 + 1.283 + void Fix(CGContextRef baseCg) 1.284 + { 1.285 + if (mCg) { 1.286 + CGContextTranslateCTM(baseCg, 0, mClipBounds.size.height); 1.287 + CGContextScaleCTM(baseCg, 1, -1); 1.288 + mClipBounds.origin.y *= -1; 1.289 + CGContextDrawLayerAtPoint(baseCg, mClipBounds.origin, mLayer); 1.290 + CGContextRelease(mCg); 1.291 + } 1.292 + } 1.293 +}; 1.294 + 1.295 +void 1.296 +DrawTargetCG::DrawSurface(SourceSurface *aSurface, 1.297 + const Rect &aDest, 1.298 + const Rect &aSource, 1.299 + const DrawSurfaceOptions &aSurfOptions, 1.300 + const DrawOptions &aDrawOptions) 1.301 +{ 1.302 + MarkChanged(); 1.303 + 1.304 + CGContextSaveGState(mCg); 1.305 + 1.306 + CGContextSetBlendMode(mCg, ToBlendMode(aDrawOptions.mCompositionOp)); 1.307 + UnboundnessFixer fixer; 1.308 + CGContextRef cg = fixer.Check(mCg, aDrawOptions.mCompositionOp, &aDest); 1.309 + CGContextSetAlpha(cg, aDrawOptions.mAlpha); 1.310 + CGContextSetShouldAntialias(cg, aDrawOptions.mAntialiasMode != AntialiasMode::NONE); 1.311 + 1.312 + CGContextConcatCTM(cg, GfxMatrixToCGAffineTransform(mTransform)); 1.313 + 1.314 + CGContextSetInterpolationQuality(cg, InterpolationQualityFromFilter(aSurfOptions.mFilter)); 1.315 + 1.316 + CGImageRef image = GetRetainedImageFromSourceSurface(aSurface); 1.317 + 1.318 + if (aSurfOptions.mFilter == Filter::POINT) { 1.319 + CGImageRef subimage = CGImageCreateWithImageInRect(image, RectToCGRect(aSource)); 1.320 + CGImageRelease(image); 1.321 + 1.322 + CGContextScaleCTM(cg, 1, -1); 1.323 + 1.324 + CGRect flippedRect = CGRectMake(aDest.x, -(aDest.y + aDest.height), 1.325 + aDest.width, aDest.height); 1.326 + 1.327 + CGContextDrawImage(cg, flippedRect, subimage); 1.328 + CGImageRelease(subimage); 1.329 + } else { 1.330 + CGRect destRect = CGRectMake(aDest.x, aDest.y, aDest.width, aDest.height); 1.331 + CGContextClipToRect(cg, destRect); 1.332 + 1.333 + float xScale = aSource.width / aDest.width; 1.334 + float yScale = aSource.height / aDest.height; 1.335 + CGContextTranslateCTM(cg, aDest.x - aSource.x / xScale, aDest.y - aSource.y / yScale); 1.336 + 1.337 + CGRect adjustedDestRect = CGRectMake(0, 0, CGImageGetWidth(image) / xScale, 1.338 + CGImageGetHeight(image) / yScale); 1.339 + 1.340 + CGContextTranslateCTM(cg, 0, CGRectGetHeight(adjustedDestRect)); 1.341 + CGContextScaleCTM(cg, 1, -1); 1.342 + 1.343 + CGContextDrawImage(cg, adjustedDestRect, image); 1.344 + CGImageRelease(image); 1.345 + } 1.346 + 1.347 + fixer.Fix(mCg); 1.348 + 1.349 + CGContextRestoreGState(mCg); 1.350 +} 1.351 + 1.352 +TemporaryRef<FilterNode> 1.353 +DrawTargetCG::CreateFilter(FilterType aType) 1.354 +{ 1.355 + return FilterNodeSoftware::Create(aType); 1.356 +} 1.357 + 1.358 +void 1.359 +DrawTargetCG::DrawFilter(FilterNode *aNode, 1.360 + const Rect &aSourceRect, 1.361 + const Point &aDestPoint, 1.362 + const DrawOptions &aOptions) 1.363 +{ 1.364 + FilterNodeSoftware* filter = static_cast<FilterNodeSoftware*>(aNode); 1.365 + filter->Draw(this, aSourceRect, aDestPoint, aOptions); 1.366 +} 1.367 + 1.368 +static CGColorRef ColorToCGColor(CGColorSpaceRef aColorSpace, const Color& aColor) 1.369 +{ 1.370 + CGFloat components[4] = {aColor.r, aColor.g, aColor.b, aColor.a}; 1.371 + return CGColorCreate(aColorSpace, components); 1.372 +} 1.373 + 1.374 +class GradientStopsCG : public GradientStops 1.375 +{ 1.376 + public: 1.377 + MOZ_DECLARE_REFCOUNTED_VIRTUAL_TYPENAME(GradientStopsCG) 1.378 + //XXX: The skia backend uses a vector and passes in aNumStops. It should do better 1.379 + GradientStopsCG(GradientStop* aStops, uint32_t aNumStops, ExtendMode aExtendMode) 1.380 + { 1.381 + mExtend = aExtendMode; 1.382 + if (aExtendMode == ExtendMode::CLAMP) { 1.383 + //XXX: do the stops need to be in any particular order? 1.384 + // what should we do about the color space here? we certainly shouldn't be 1.385 + // recreating it all the time 1.386 + std::vector<CGFloat> colors; 1.387 + std::vector<CGFloat> offsets; 1.388 + colors.reserve(aNumStops*4); 1.389 + offsets.reserve(aNumStops); 1.390 + 1.391 + for (uint32_t i = 0; i < aNumStops; i++) { 1.392 + colors.push_back(aStops[i].color.r); 1.393 + colors.push_back(aStops[i].color.g); 1.394 + colors.push_back(aStops[i].color.b); 1.395 + colors.push_back(aStops[i].color.a); 1.396 + 1.397 + offsets.push_back(aStops[i].offset); 1.398 + } 1.399 + 1.400 + CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB(); 1.401 + mGradient = CGGradientCreateWithColorComponents(colorSpace, 1.402 + &colors.front(), 1.403 + &offsets.front(), 1.404 + aNumStops); 1.405 + CGColorSpaceRelease(colorSpace); 1.406 + } else { 1.407 + mGradient = nullptr; 1.408 + mStops.reserve(aNumStops); 1.409 + for (uint32_t i = 0; i < aNumStops; i++) { 1.410 + mStops.push_back(aStops[i]); 1.411 + } 1.412 + } 1.413 + 1.414 + } 1.415 + virtual ~GradientStopsCG() { 1.416 + if (mGradient) 1.417 + CGGradientRelease(mGradient); 1.418 + } 1.419 + // Will always report BackendType::COREGRAPHICS, but it is compatible 1.420 + // with BackendType::COREGRAPHICS_ACCELERATED 1.421 + BackendType GetBackendType() const { return BackendType::COREGRAPHICS; } 1.422 + // XXX this should be a union 1.423 + CGGradientRef mGradient; 1.424 + std::vector<GradientStop> mStops; 1.425 + ExtendMode mExtend; 1.426 +}; 1.427 + 1.428 +TemporaryRef<GradientStops> 1.429 +DrawTargetCG::CreateGradientStops(GradientStop *aStops, uint32_t aNumStops, 1.430 + ExtendMode aExtendMode) const 1.431 +{ 1.432 + return new GradientStopsCG(aStops, aNumStops, aExtendMode); 1.433 +} 1.434 + 1.435 +static void 1.436 +UpdateLinearParametersToIncludePoint(double *min_t, double *max_t, 1.437 + CGPoint *start, 1.438 + double dx, double dy, 1.439 + double x, double y) 1.440 +{ 1.441 + MOZ_ASSERT(IsFinite(x) && IsFinite(y)); 1.442 + 1.443 + /** 1.444 + * Compute a parameter t such that a line perpendicular to the (dx,dy) 1.445 + * vector, passing through (start->x + dx*t, start->y + dy*t), also 1.446 + * passes through (x,y). 1.447 + * 1.448 + * Let px = x - start->x, py = y - start->y. 1.449 + * t is given by 1.450 + * (px - dx*t)*dx + (py - dy*t)*dy = 0 1.451 + * 1.452 + * Solving for t we get 1.453 + * numerator = dx*px + dy*py 1.454 + * denominator = dx^2 + dy^2 1.455 + * t = numerator/denominator 1.456 + * 1.457 + * In CalculateRepeatingGradientParams we know the length of (dx,dy) 1.458 + * is not zero. (This is checked in DrawLinearRepeatingGradient.) 1.459 + */ 1.460 + double px = x - start->x; 1.461 + double py = y - start->y; 1.462 + double numerator = dx * px + dy * py; 1.463 + double denominator = dx * dx + dy * dy; 1.464 + double t = numerator / denominator; 1.465 + 1.466 + if (*min_t > t) { 1.467 + *min_t = t; 1.468 + } 1.469 + if (*max_t < t) { 1.470 + *max_t = t; 1.471 + } 1.472 +} 1.473 + 1.474 +/** 1.475 + * Repeat the gradient line such that lines extended perpendicular to the 1.476 + * gradient line at both start and end would completely enclose the drawing 1.477 + * extents. 1.478 + */ 1.479 +static void 1.480 +CalculateRepeatingGradientParams(CGPoint *aStart, CGPoint *aEnd, 1.481 + CGRect aExtents, int *aRepeatCount) 1.482 +{ 1.483 + double t_min = INFINITY; 1.484 + double t_max = -INFINITY; 1.485 + double dx = aEnd->x - aStart->x; 1.486 + double dy = aEnd->y - aStart->y; 1.487 + 1.488 + double bounds_x1 = aExtents.origin.x; 1.489 + double bounds_y1 = aExtents.origin.y; 1.490 + double bounds_x2 = aExtents.origin.x + aExtents.size.width; 1.491 + double bounds_y2 = aExtents.origin.y + aExtents.size.height; 1.492 + 1.493 + UpdateLinearParametersToIncludePoint(&t_min, &t_max, aStart, dx, dy, 1.494 + bounds_x1, bounds_y1); 1.495 + UpdateLinearParametersToIncludePoint(&t_min, &t_max, aStart, dx, dy, 1.496 + bounds_x2, bounds_y1); 1.497 + UpdateLinearParametersToIncludePoint(&t_min, &t_max, aStart, dx, dy, 1.498 + bounds_x2, bounds_y2); 1.499 + UpdateLinearParametersToIncludePoint(&t_min, &t_max, aStart, dx, dy, 1.500 + bounds_x1, bounds_y2); 1.501 + 1.502 + MOZ_ASSERT(!isinf(t_min) && !isinf(t_max), 1.503 + "The first call to UpdateLinearParametersToIncludePoint should have made t_min and t_max non-infinite."); 1.504 + 1.505 + // Move t_min and t_max to the nearest usable integer to try to avoid 1.506 + // subtle variations due to numerical instability, especially accidentally 1.507 + // cutting off a pixel. Extending the gradient repetitions is always safe. 1.508 + t_min = floor (t_min); 1.509 + t_max = ceil (t_max); 1.510 + aEnd->x = aStart->x + dx * t_max; 1.511 + aEnd->y = aStart->y + dy * t_max; 1.512 + aStart->x = aStart->x + dx * t_min; 1.513 + aStart->y = aStart->y + dy * t_min; 1.514 + 1.515 + *aRepeatCount = t_max - t_min; 1.516 +} 1.517 + 1.518 +static void 1.519 +DrawLinearRepeatingGradient(CGContextRef cg, const LinearGradientPattern &aPattern, const CGRect &aExtents) 1.520 +{ 1.521 + GradientStopsCG *stops = static_cast<GradientStopsCG*>(aPattern.mStops.get()); 1.522 + CGPoint startPoint = { aPattern.mBegin.x, aPattern.mBegin.y }; 1.523 + CGPoint endPoint = { aPattern.mEnd.x, aPattern.mEnd.y }; 1.524 + 1.525 + int repeatCount = 1; 1.526 + // if we don't have a line then we can't extend it 1.527 + if (aPattern.mEnd.x != aPattern.mBegin.x || 1.528 + aPattern.mEnd.y != aPattern.mBegin.y) { 1.529 + CalculateRepeatingGradientParams(&startPoint, &endPoint, aExtents, 1.530 + &repeatCount); 1.531 + } 1.532 + 1.533 + double scale = 1./repeatCount; 1.534 + 1.535 + std::vector<CGFloat> colors; 1.536 + std::vector<CGFloat> offsets; 1.537 + colors.reserve(stops->mStops.size()*repeatCount*4); 1.538 + offsets.reserve(stops->mStops.size()*repeatCount); 1.539 + 1.540 + for (int j = 0; j < repeatCount; j++) { 1.541 + for (uint32_t i = 0; i < stops->mStops.size(); i++) { 1.542 + colors.push_back(stops->mStops[i].color.r); 1.543 + colors.push_back(stops->mStops[i].color.g); 1.544 + colors.push_back(stops->mStops[i].color.b); 1.545 + colors.push_back(stops->mStops[i].color.a); 1.546 + 1.547 + offsets.push_back((stops->mStops[i].offset + j)*scale); 1.548 + } 1.549 + } 1.550 + 1.551 + CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB(); 1.552 + CGGradientRef gradient = CGGradientCreateWithColorComponents(colorSpace, 1.553 + &colors.front(), 1.554 + &offsets.front(), 1.555 + repeatCount*stops->mStops.size()); 1.556 + CGColorSpaceRelease(colorSpace); 1.557 + 1.558 + CGContextDrawLinearGradient(cg, gradient, startPoint, endPoint, 1.559 + kCGGradientDrawsBeforeStartLocation | kCGGradientDrawsAfterEndLocation); 1.560 + CGGradientRelease(gradient); 1.561 +} 1.562 + 1.563 +static CGPoint CGRectTopLeft(CGRect a) 1.564 +{ return a.origin; } 1.565 +static CGPoint CGRectBottomLeft(CGRect a) 1.566 +{ return CGPointMake(a.origin.x, a.origin.y + a.size.height); } 1.567 +static CGPoint CGRectTopRight(CGRect a) 1.568 +{ return CGPointMake(a.origin.x + a.size.width, a.origin.y); } 1.569 +static CGPoint CGRectBottomRight(CGRect a) 1.570 +{ return CGPointMake(a.origin.x + a.size.width, a.origin.y + a.size.height); } 1.571 + 1.572 +static CGFloat 1.573 +CGPointDistance(CGPoint a, CGPoint b) 1.574 +{ 1.575 + return hypot(a.x-b.x, a.y-b.y); 1.576 +} 1.577 + 1.578 +static void 1.579 +DrawRadialRepeatingGradient(CGContextRef cg, const RadialGradientPattern &aPattern, const CGRect &aExtents) 1.580 +{ 1.581 + GradientStopsCG *stops = static_cast<GradientStopsCG*>(aPattern.mStops.get()); 1.582 + CGPoint startCenter = { aPattern.mCenter1.x, aPattern.mCenter1.y }; 1.583 + CGFloat startRadius = aPattern.mRadius1; 1.584 + CGPoint endCenter = { aPattern.mCenter2.x, aPattern.mCenter2.y }; 1.585 + CGFloat endRadius = aPattern.mRadius2; 1.586 + 1.587 + // find the maximum distance from endCenter to a corner of aExtents 1.588 + CGFloat minimumEndRadius = endRadius; 1.589 + minimumEndRadius = max(minimumEndRadius, CGPointDistance(endCenter, CGRectTopLeft(aExtents))); 1.590 + minimumEndRadius = max(minimumEndRadius, CGPointDistance(endCenter, CGRectBottomLeft(aExtents))); 1.591 + minimumEndRadius = max(minimumEndRadius, CGPointDistance(endCenter, CGRectTopRight(aExtents))); 1.592 + minimumEndRadius = max(minimumEndRadius, CGPointDistance(endCenter, CGRectBottomRight(aExtents))); 1.593 + 1.594 + CGFloat length = endRadius - startRadius; 1.595 + int repeatCount = 1; 1.596 + while (endRadius < minimumEndRadius) { 1.597 + endRadius += length; 1.598 + repeatCount++; 1.599 + } 1.600 + 1.601 + while (startRadius-length >= 0) { 1.602 + startRadius -= length; 1.603 + repeatCount++; 1.604 + } 1.605 + 1.606 + double scale = 1./repeatCount; 1.607 + 1.608 + std::vector<CGFloat> colors; 1.609 + std::vector<CGFloat> offsets; 1.610 + colors.reserve(stops->mStops.size()*repeatCount*4); 1.611 + offsets.reserve(stops->mStops.size()*repeatCount); 1.612 + for (int j = 0; j < repeatCount; j++) { 1.613 + for (uint32_t i = 0; i < stops->mStops.size(); i++) { 1.614 + colors.push_back(stops->mStops[i].color.r); 1.615 + colors.push_back(stops->mStops[i].color.g); 1.616 + colors.push_back(stops->mStops[i].color.b); 1.617 + colors.push_back(stops->mStops[i].color.a); 1.618 + 1.619 + offsets.push_back((stops->mStops[i].offset + j)*scale); 1.620 + } 1.621 + } 1.622 + 1.623 + CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB(); 1.624 + CGGradientRef gradient = CGGradientCreateWithColorComponents(colorSpace, 1.625 + &colors.front(), 1.626 + &offsets.front(), 1.627 + repeatCount*stops->mStops.size()); 1.628 + CGColorSpaceRelease(colorSpace); 1.629 + 1.630 + //XXX: are there degenerate radial gradients that we should avoid drawing? 1.631 + CGContextDrawRadialGradient(cg, gradient, startCenter, startRadius, endCenter, endRadius, 1.632 + kCGGradientDrawsBeforeStartLocation | kCGGradientDrawsAfterEndLocation); 1.633 + CGGradientRelease(gradient); 1.634 +} 1.635 + 1.636 +static void 1.637 +DrawGradient(CGContextRef cg, const Pattern &aPattern, const CGRect &aExtents) 1.638 +{ 1.639 + if (CGRectIsEmpty(aExtents)) { 1.640 + return; 1.641 + } 1.642 + 1.643 + if (aPattern.GetType() == PatternType::LINEAR_GRADIENT) { 1.644 + const LinearGradientPattern& pat = static_cast<const LinearGradientPattern&>(aPattern); 1.645 + GradientStopsCG *stops = static_cast<GradientStopsCG*>(pat.mStops.get()); 1.646 + CGContextConcatCTM(cg, GfxMatrixToCGAffineTransform(pat.mMatrix)); 1.647 + if (stops->mExtend == ExtendMode::CLAMP) { 1.648 + 1.649 + // XXX: we should take the m out of the properties of LinearGradientPatterns 1.650 + CGPoint startPoint = { pat.mBegin.x, pat.mBegin.y }; 1.651 + CGPoint endPoint = { pat.mEnd.x, pat.mEnd.y }; 1.652 + 1.653 + // Canvas spec states that we should avoid drawing degenerate gradients (XXX: should this be in common code?) 1.654 + //if (startPoint.x == endPoint.x && startPoint.y == endPoint.y) 1.655 + // return; 1.656 + 1.657 + CGContextDrawLinearGradient(cg, stops->mGradient, startPoint, endPoint, 1.658 + kCGGradientDrawsBeforeStartLocation | kCGGradientDrawsAfterEndLocation); 1.659 + } else if (stops->mExtend == ExtendMode::REPEAT) { 1.660 + DrawLinearRepeatingGradient(cg, pat, aExtents); 1.661 + } 1.662 + } else if (aPattern.GetType() == PatternType::RADIAL_GRADIENT) { 1.663 + const RadialGradientPattern& pat = static_cast<const RadialGradientPattern&>(aPattern); 1.664 + CGContextConcatCTM(cg, GfxMatrixToCGAffineTransform(pat.mMatrix)); 1.665 + GradientStopsCG *stops = static_cast<GradientStopsCG*>(pat.mStops.get()); 1.666 + if (stops->mExtend == ExtendMode::CLAMP) { 1.667 + 1.668 + // XXX: we should take the m out of the properties of RadialGradientPatterns 1.669 + CGPoint startCenter = { pat.mCenter1.x, pat.mCenter1.y }; 1.670 + CGFloat startRadius = pat.mRadius1; 1.671 + CGPoint endCenter = { pat.mCenter2.x, pat.mCenter2.y }; 1.672 + CGFloat endRadius = pat.mRadius2; 1.673 + 1.674 + //XXX: are there degenerate radial gradients that we should avoid drawing? 1.675 + CGContextDrawRadialGradient(cg, stops->mGradient, startCenter, startRadius, endCenter, endRadius, 1.676 + kCGGradientDrawsBeforeStartLocation | kCGGradientDrawsAfterEndLocation); 1.677 + } else if (stops->mExtend == ExtendMode::REPEAT) { 1.678 + DrawRadialRepeatingGradient(cg, pat, aExtents); 1.679 + } 1.680 + } else { 1.681 + assert(0); 1.682 + } 1.683 + 1.684 +} 1.685 + 1.686 +static void 1.687 +drawPattern(void *info, CGContextRef context) 1.688 +{ 1.689 + CGImageRef image = static_cast<CGImageRef>(info); 1.690 + CGRect rect = {{0, 0}, 1.691 + {static_cast<CGFloat>(CGImageGetWidth(image)), 1.692 + static_cast<CGFloat>(CGImageGetHeight(image))}}; 1.693 + CGContextDrawImage(context, rect, image); 1.694 +} 1.695 + 1.696 +static void 1.697 +releaseInfo(void *info) 1.698 +{ 1.699 + CGImageRef image = static_cast<CGImageRef>(info); 1.700 + CGImageRelease(image); 1.701 +} 1.702 + 1.703 +CGPatternCallbacks patternCallbacks = { 1.704 + 0, 1.705 + drawPattern, 1.706 + releaseInfo 1.707 +}; 1.708 + 1.709 +static bool 1.710 +isGradient(const Pattern &aPattern) 1.711 +{ 1.712 + return aPattern.GetType() == PatternType::LINEAR_GRADIENT || aPattern.GetType() == PatternType::RADIAL_GRADIENT; 1.713 +} 1.714 + 1.715 +/* CoreGraphics patterns ignore the userspace transform so 1.716 + * we need to multiply it in */ 1.717 +static CGPatternRef 1.718 +CreateCGPattern(const Pattern &aPattern, CGAffineTransform aUserSpace) 1.719 +{ 1.720 + const SurfacePattern& pat = static_cast<const SurfacePattern&>(aPattern); 1.721 + // XXX: is .get correct here? 1.722 + CGImageRef image = GetRetainedImageFromSourceSurface(pat.mSurface.get()); 1.723 + CGFloat xStep, yStep; 1.724 + switch (pat.mExtendMode) { 1.725 + case ExtendMode::CLAMP: 1.726 + // The 1 << 22 comes from Webkit see Pattern::createPlatformPattern() in PatternCG.cpp for more info 1.727 + xStep = static_cast<CGFloat>(1 << 22); 1.728 + yStep = static_cast<CGFloat>(1 << 22); 1.729 + break; 1.730 + case ExtendMode::REFLECT: 1.731 + assert(0); 1.732 + case ExtendMode::REPEAT: 1.733 + xStep = static_cast<CGFloat>(CGImageGetWidth(image)); 1.734 + yStep = static_cast<CGFloat>(CGImageGetHeight(image)); 1.735 + // webkit uses wkCGPatternCreateWithImageAndTransform a wrapper around CGPatternCreateWithImage2 1.736 + // this is done to avoid pixel-cracking along pattern boundaries 1.737 + // (see https://bugs.webkit.org/show_bug.cgi?id=53055) 1.738 + // typedef enum { 1.739 + // wkPatternTilingNoDistortion, 1.740 + // wkPatternTilingConstantSpacingMinimalDistortion, 1.741 + // wkPatternTilingConstantSpacing 1.742 + // } wkPatternTiling; 1.743 + // extern CGPatternRef (*wkCGPatternCreateWithImageAndTransform)(CGImageRef, CGAffineTransform, int); 1.744 + } 1.745 + 1.746 + //XXX: We should be using CGContextDrawTiledImage when we can. Even though it 1.747 + // creates a pattern, it seems to go down a faster path than using a delegate 1.748 + // like we do below 1.749 + CGRect bounds = { 1.750 + {0, 0,}, 1.751 + {static_cast<CGFloat>(CGImageGetWidth(image)), static_cast<CGFloat>(CGImageGetHeight(image))} 1.752 + }; 1.753 + CGAffineTransform transform = 1.754 + CGAffineTransformConcat(CGAffineTransformConcat(CGAffineTransformMakeScale(1, 1.755 + -1), 1.756 + GfxMatrixToCGAffineTransform(pat.mMatrix)), 1.757 + aUserSpace); 1.758 + transform = CGAffineTransformTranslate(transform, 0, -static_cast<float>(CGImageGetHeight(image))); 1.759 + return CGPatternCreate(image, bounds, transform, xStep, yStep, kCGPatternTilingConstantSpacing, 1.760 + true, &patternCallbacks); 1.761 +} 1.762 + 1.763 +static void 1.764 +SetFillFromPattern(CGContextRef cg, CGColorSpaceRef aColorSpace, const Pattern &aPattern) 1.765 +{ 1.766 + assert(!isGradient(aPattern)); 1.767 + if (aPattern.GetType() == PatternType::COLOR) { 1.768 + 1.769 + const Color& color = static_cast<const ColorPattern&>(aPattern).mColor; 1.770 + //XXX: we should cache colors 1.771 + CGColorRef cgcolor = ColorToCGColor(aColorSpace, color); 1.772 + CGContextSetFillColorWithColor(cg, cgcolor); 1.773 + CGColorRelease(cgcolor); 1.774 + } else if (aPattern.GetType() == PatternType::SURFACE) { 1.775 + 1.776 + CGColorSpaceRef patternSpace; 1.777 + patternSpace = CGColorSpaceCreatePattern (nullptr); 1.778 + CGContextSetFillColorSpace(cg, patternSpace); 1.779 + CGColorSpaceRelease(patternSpace); 1.780 + 1.781 + CGPatternRef pattern = CreateCGPattern(aPattern, CGContextGetCTM(cg)); 1.782 + const SurfacePattern& pat = static_cast<const SurfacePattern&>(aPattern); 1.783 + CGContextSetInterpolationQuality(cg, InterpolationQualityFromFilter(pat.mFilter)); 1.784 + CGFloat alpha = 1.; 1.785 + CGContextSetFillPattern(cg, pattern, &alpha); 1.786 + CGPatternRelease(pattern); 1.787 + } 1.788 +} 1.789 + 1.790 +static void 1.791 +SetStrokeFromPattern(CGContextRef cg, CGColorSpaceRef aColorSpace, const Pattern &aPattern) 1.792 +{ 1.793 + assert(!isGradient(aPattern)); 1.794 + if (aPattern.GetType() == PatternType::COLOR) { 1.795 + const Color& color = static_cast<const ColorPattern&>(aPattern).mColor; 1.796 + //XXX: we should cache colors 1.797 + CGColorRef cgcolor = ColorToCGColor(aColorSpace, color); 1.798 + CGContextSetStrokeColorWithColor(cg, cgcolor); 1.799 + CGColorRelease(cgcolor); 1.800 + } else if (aPattern.GetType() == PatternType::SURFACE) { 1.801 + CGColorSpaceRef patternSpace; 1.802 + patternSpace = CGColorSpaceCreatePattern (nullptr); 1.803 + CGContextSetStrokeColorSpace(cg, patternSpace); 1.804 + CGColorSpaceRelease(patternSpace); 1.805 + 1.806 + CGPatternRef pattern = CreateCGPattern(aPattern, CGContextGetCTM(cg)); 1.807 + const SurfacePattern& pat = static_cast<const SurfacePattern&>(aPattern); 1.808 + CGContextSetInterpolationQuality(cg, InterpolationQualityFromFilter(pat.mFilter)); 1.809 + CGFloat alpha = 1.; 1.810 + CGContextSetStrokePattern(cg, pattern, &alpha); 1.811 + CGPatternRelease(pattern); 1.812 + } 1.813 + 1.814 +} 1.815 + 1.816 +void 1.817 +DrawTargetCG::MaskSurface(const Pattern &aSource, 1.818 + SourceSurface *aMask, 1.819 + Point aOffset, 1.820 + const DrawOptions &aDrawOptions) 1.821 +{ 1.822 + MarkChanged(); 1.823 + 1.824 + CGContextSaveGState(mCg); 1.825 + 1.826 + CGContextSetBlendMode(mCg, ToBlendMode(aDrawOptions.mCompositionOp)); 1.827 + UnboundnessFixer fixer; 1.828 + CGContextRef cg = fixer.Check(mCg, aDrawOptions.mCompositionOp); 1.829 + CGContextSetAlpha(cg, aDrawOptions.mAlpha); 1.830 + CGContextSetShouldAntialias(cg, aDrawOptions.mAntialiasMode != AntialiasMode::NONE); 1.831 + 1.832 + CGContextConcatCTM(cg, GfxMatrixToCGAffineTransform(mTransform)); 1.833 + CGImageRef image = GetRetainedImageFromSourceSurface(aMask); 1.834 + 1.835 + // use a negative-y so that the mask image draws right ways up 1.836 + CGContextScaleCTM(cg, 1, -1); 1.837 + 1.838 + IntSize size = aMask->GetSize(); 1.839 + 1.840 + CGContextClipToMask(cg, CGRectMake(aOffset.x, -(aOffset.y + size.height), size.width, size.height), image); 1.841 + 1.842 + CGContextScaleCTM(cg, 1, -1); 1.843 + if (isGradient(aSource)) { 1.844 + // we shouldn't need to clip to an additional rectangle 1.845 + // as the cliping to the mask should be sufficient. 1.846 + DrawGradient(cg, aSource, CGRectMake(aOffset.x, aOffset.y, size.width, size.height)); 1.847 + } else { 1.848 + SetFillFromPattern(cg, mColorSpace, aSource); 1.849 + CGContextFillRect(cg, CGRectMake(aOffset.x, aOffset.y, size.width, size.height)); 1.850 + } 1.851 + 1.852 + CGImageRelease(image); 1.853 + 1.854 + fixer.Fix(mCg); 1.855 + 1.856 + CGContextRestoreGState(mCg); 1.857 +} 1.858 + 1.859 + 1.860 + 1.861 +void 1.862 +DrawTargetCG::FillRect(const Rect &aRect, 1.863 + const Pattern &aPattern, 1.864 + const DrawOptions &aDrawOptions) 1.865 +{ 1.866 + MarkChanged(); 1.867 + 1.868 + CGContextSaveGState(mCg); 1.869 + 1.870 + UnboundnessFixer fixer; 1.871 + CGContextRef cg = fixer.Check(mCg, aDrawOptions.mCompositionOp, &aRect); 1.872 + CGContextSetAlpha(mCg, aDrawOptions.mAlpha); 1.873 + CGContextSetShouldAntialias(cg, aDrawOptions.mAntialiasMode != AntialiasMode::NONE); 1.874 + CGContextSetBlendMode(mCg, ToBlendMode(aDrawOptions.mCompositionOp)); 1.875 + 1.876 + CGContextConcatCTM(cg, GfxMatrixToCGAffineTransform(mTransform)); 1.877 + 1.878 + if (isGradient(aPattern)) { 1.879 + CGContextClipToRect(cg, RectToCGRect(aRect)); 1.880 + CGRect clipBounds = CGContextGetClipBoundingBox(cg); 1.881 + DrawGradient(cg, aPattern, clipBounds); 1.882 + } else { 1.883 + if (aPattern.GetType() == PatternType::SURFACE && static_cast<const SurfacePattern&>(aPattern).mExtendMode != ExtendMode::REPEAT) { 1.884 + // SetFillFromPattern can handle this case but using CGContextDrawImage 1.885 + // should give us better performance, better output, smaller PDF and 1.886 + // matches what cairo does. 1.887 + const SurfacePattern& pat = static_cast<const SurfacePattern&>(aPattern); 1.888 + CGImageRef image = GetRetainedImageFromSourceSurface(pat.mSurface.get()); 1.889 + CGContextClipToRect(cg, RectToCGRect(aRect)); 1.890 + CGContextConcatCTM(cg, GfxMatrixToCGAffineTransform(pat.mMatrix)); 1.891 + CGContextTranslateCTM(cg, 0, CGImageGetHeight(image)); 1.892 + CGContextScaleCTM(cg, 1, -1); 1.893 + 1.894 + CGRect imageRect = CGRectMake(0, 0, CGImageGetWidth(image), CGImageGetHeight(image)); 1.895 + 1.896 + CGContextSetInterpolationQuality(cg, InterpolationQualityFromFilter(pat.mFilter)); 1.897 + 1.898 + CGContextDrawImage(cg, imageRect, image); 1.899 + CGImageRelease(image); 1.900 + } else { 1.901 + SetFillFromPattern(cg, mColorSpace, aPattern); 1.902 + CGContextFillRect(cg, RectToCGRect(aRect)); 1.903 + } 1.904 + } 1.905 + 1.906 + fixer.Fix(mCg); 1.907 + CGContextRestoreGState(mCg); 1.908 +} 1.909 + 1.910 +void 1.911 +DrawTargetCG::StrokeLine(const Point &p1, const Point &p2, const Pattern &aPattern, const StrokeOptions &aStrokeOptions, const DrawOptions &aDrawOptions) 1.912 +{ 1.913 + if (!std::isfinite(p1.x) || 1.914 + !std::isfinite(p1.y) || 1.915 + !std::isfinite(p2.x) || 1.916 + !std::isfinite(p2.y)) { 1.917 + return; 1.918 + } 1.919 + 1.920 + MarkChanged(); 1.921 + 1.922 + CGContextSaveGState(mCg); 1.923 + 1.924 + UnboundnessFixer fixer; 1.925 + CGContextRef cg = fixer.Check(mCg, aDrawOptions.mCompositionOp); 1.926 + CGContextSetAlpha(mCg, aDrawOptions.mAlpha); 1.927 + CGContextSetShouldAntialias(cg, aDrawOptions.mAntialiasMode != AntialiasMode::NONE); 1.928 + CGContextSetBlendMode(mCg, ToBlendMode(aDrawOptions.mCompositionOp)); 1.929 + 1.930 + CGContextConcatCTM(cg, GfxMatrixToCGAffineTransform(mTransform)); 1.931 + 1.932 + CGContextBeginPath(cg); 1.933 + CGContextMoveToPoint(cg, p1.x, p1.y); 1.934 + CGContextAddLineToPoint(cg, p2.x, p2.y); 1.935 + 1.936 + SetStrokeOptions(cg, aStrokeOptions); 1.937 + 1.938 + if (isGradient(aPattern)) { 1.939 + CGContextReplacePathWithStrokedPath(cg); 1.940 + CGRect extents = CGContextGetPathBoundingBox(cg); 1.941 + //XXX: should we use EO clip here? 1.942 + CGContextClip(cg); 1.943 + DrawGradient(cg, aPattern, extents); 1.944 + } else { 1.945 + SetStrokeFromPattern(cg, mColorSpace, aPattern); 1.946 + CGContextStrokePath(cg); 1.947 + } 1.948 + 1.949 + fixer.Fix(mCg); 1.950 + CGContextRestoreGState(mCg); 1.951 +} 1.952 + 1.953 +void 1.954 +DrawTargetCG::StrokeRect(const Rect &aRect, 1.955 + const Pattern &aPattern, 1.956 + const StrokeOptions &aStrokeOptions, 1.957 + const DrawOptions &aDrawOptions) 1.958 +{ 1.959 + if (!aRect.IsFinite()) { 1.960 + return; 1.961 + } 1.962 + 1.963 + MarkChanged(); 1.964 + 1.965 + CGContextSaveGState(mCg); 1.966 + 1.967 + UnboundnessFixer fixer; 1.968 + CGContextRef cg = fixer.Check(mCg, aDrawOptions.mCompositionOp); 1.969 + CGContextSetAlpha(mCg, aDrawOptions.mAlpha); 1.970 + CGContextSetShouldAntialias(cg, aDrawOptions.mAntialiasMode != AntialiasMode::NONE); 1.971 + CGContextSetBlendMode(mCg, ToBlendMode(aDrawOptions.mCompositionOp)); 1.972 + 1.973 + CGContextConcatCTM(cg, GfxMatrixToCGAffineTransform(mTransform)); 1.974 + 1.975 + SetStrokeOptions(cg, aStrokeOptions); 1.976 + 1.977 + if (isGradient(aPattern)) { 1.978 + // There's no CGContextClipStrokeRect so we do it by hand 1.979 + CGContextBeginPath(cg); 1.980 + CGContextAddRect(cg, RectToCGRect(aRect)); 1.981 + CGContextReplacePathWithStrokedPath(cg); 1.982 + CGRect extents = CGContextGetPathBoundingBox(cg); 1.983 + //XXX: should we use EO clip here? 1.984 + CGContextClip(cg); 1.985 + DrawGradient(cg, aPattern, extents); 1.986 + } else { 1.987 + SetStrokeFromPattern(cg, mColorSpace, aPattern); 1.988 + CGContextStrokeRect(cg, RectToCGRect(aRect)); 1.989 + } 1.990 + 1.991 + fixer.Fix(mCg); 1.992 + CGContextRestoreGState(mCg); 1.993 +} 1.994 + 1.995 + 1.996 +void 1.997 +DrawTargetCG::ClearRect(const Rect &aRect) 1.998 +{ 1.999 + MarkChanged(); 1.1000 + 1.1001 + CGContextSaveGState(mCg); 1.1002 + CGContextConcatCTM(mCg, GfxMatrixToCGAffineTransform(mTransform)); 1.1003 + 1.1004 + CGContextClearRect(mCg, RectToCGRect(aRect)); 1.1005 + 1.1006 + CGContextRestoreGState(mCg); 1.1007 +} 1.1008 + 1.1009 +void 1.1010 +DrawTargetCG::Stroke(const Path *aPath, const Pattern &aPattern, const StrokeOptions &aStrokeOptions, const DrawOptions &aDrawOptions) 1.1011 +{ 1.1012 + if (!aPath->GetBounds().IsFinite()) { 1.1013 + return; 1.1014 + } 1.1015 + 1.1016 + MarkChanged(); 1.1017 + 1.1018 + CGContextSaveGState(mCg); 1.1019 + 1.1020 + UnboundnessFixer fixer; 1.1021 + CGContextRef cg = fixer.Check(mCg, aDrawOptions.mCompositionOp); 1.1022 + CGContextSetAlpha(mCg, aDrawOptions.mAlpha); 1.1023 + CGContextSetShouldAntialias(cg, aDrawOptions.mAntialiasMode != AntialiasMode::NONE); 1.1024 + CGContextSetBlendMode(mCg, ToBlendMode(aDrawOptions.mCompositionOp)); 1.1025 + 1.1026 + CGContextConcatCTM(cg, GfxMatrixToCGAffineTransform(mTransform)); 1.1027 + 1.1028 + 1.1029 + CGContextBeginPath(cg); 1.1030 + 1.1031 + assert(aPath->GetBackendType() == BackendType::COREGRAPHICS); 1.1032 + const PathCG *cgPath = static_cast<const PathCG*>(aPath); 1.1033 + CGContextAddPath(cg, cgPath->GetPath()); 1.1034 + 1.1035 + SetStrokeOptions(cg, aStrokeOptions); 1.1036 + 1.1037 + if (isGradient(aPattern)) { 1.1038 + CGContextReplacePathWithStrokedPath(cg); 1.1039 + CGRect extents = CGContextGetPathBoundingBox(cg); 1.1040 + //XXX: should we use EO clip here? 1.1041 + CGContextClip(cg); 1.1042 + DrawGradient(cg, aPattern, extents); 1.1043 + } else { 1.1044 + // XXX: we could put fill mode into the path fill rule if we wanted 1.1045 + 1.1046 + SetStrokeFromPattern(cg, mColorSpace, aPattern); 1.1047 + CGContextStrokePath(cg); 1.1048 + } 1.1049 + 1.1050 + fixer.Fix(mCg); 1.1051 + CGContextRestoreGState(mCg); 1.1052 +} 1.1053 + 1.1054 +void 1.1055 +DrawTargetCG::Fill(const Path *aPath, const Pattern &aPattern, const DrawOptions &aDrawOptions) 1.1056 +{ 1.1057 + MarkChanged(); 1.1058 + 1.1059 + assert(aPath->GetBackendType() == BackendType::COREGRAPHICS); 1.1060 + 1.1061 + CGContextSaveGState(mCg); 1.1062 + 1.1063 + CGContextSetBlendMode(mCg, ToBlendMode(aDrawOptions.mCompositionOp)); 1.1064 + UnboundnessFixer fixer; 1.1065 + CGContextRef cg = fixer.Check(mCg, aDrawOptions.mCompositionOp); 1.1066 + CGContextSetAlpha(cg, aDrawOptions.mAlpha); 1.1067 + CGContextSetShouldAntialias(cg, aDrawOptions.mAntialiasMode != AntialiasMode::NONE); 1.1068 + 1.1069 + CGContextConcatCTM(cg, GfxMatrixToCGAffineTransform(mTransform)); 1.1070 + 1.1071 + CGContextBeginPath(cg); 1.1072 + // XXX: we could put fill mode into the path fill rule if we wanted 1.1073 + const PathCG *cgPath = static_cast<const PathCG*>(aPath); 1.1074 + 1.1075 + if (isGradient(aPattern)) { 1.1076 + // setup a clip to draw the gradient through 1.1077 + CGRect extents; 1.1078 + if (CGPathIsEmpty(cgPath->GetPath())) { 1.1079 + // Adding an empty path will cause us not to clip 1.1080 + // so clip everything explicitly 1.1081 + CGContextClipToRect(mCg, CGRectZero); 1.1082 + extents = CGRectZero; 1.1083 + } else { 1.1084 + CGContextAddPath(cg, cgPath->GetPath()); 1.1085 + extents = CGContextGetPathBoundingBox(cg); 1.1086 + if (cgPath->GetFillRule() == FillRule::FILL_EVEN_ODD) 1.1087 + CGContextEOClip(mCg); 1.1088 + else 1.1089 + CGContextClip(mCg); 1.1090 + } 1.1091 + 1.1092 + DrawGradient(cg, aPattern, extents); 1.1093 + } else { 1.1094 + CGContextAddPath(cg, cgPath->GetPath()); 1.1095 + 1.1096 + SetFillFromPattern(cg, mColorSpace, aPattern); 1.1097 + 1.1098 + if (cgPath->GetFillRule() == FillRule::FILL_EVEN_ODD) 1.1099 + CGContextEOFillPath(cg); 1.1100 + else 1.1101 + CGContextFillPath(cg); 1.1102 + } 1.1103 + 1.1104 + fixer.Fix(mCg); 1.1105 + CGContextRestoreGState(mCg); 1.1106 +} 1.1107 + 1.1108 +CGRect ComputeGlyphsExtents(CGRect *bboxes, CGPoint *positions, CFIndex count, float scale) 1.1109 +{ 1.1110 + CGFloat x1, x2, y1, y2; 1.1111 + if (count < 1) 1.1112 + return CGRectZero; 1.1113 + 1.1114 + x1 = bboxes[0].origin.x + positions[0].x; 1.1115 + x2 = bboxes[0].origin.x + positions[0].x + scale*bboxes[0].size.width; 1.1116 + y1 = bboxes[0].origin.y + positions[0].y; 1.1117 + y2 = bboxes[0].origin.y + positions[0].y + scale*bboxes[0].size.height; 1.1118 + 1.1119 + // accumulate max and minimum coordinates 1.1120 + for (int i = 1; i < count; i++) { 1.1121 + x1 = min(x1, bboxes[i].origin.x + positions[i].x); 1.1122 + y1 = min(y1, bboxes[i].origin.y + positions[i].y); 1.1123 + x2 = max(x2, bboxes[i].origin.x + positions[i].x + scale*bboxes[i].size.width); 1.1124 + y2 = max(y2, bboxes[i].origin.y + positions[i].y + scale*bboxes[i].size.height); 1.1125 + } 1.1126 + 1.1127 + CGRect extents = {{x1, y1}, {x2-x1, y2-y1}}; 1.1128 + return extents; 1.1129 +} 1.1130 + 1.1131 + 1.1132 +void 1.1133 +DrawTargetCG::FillGlyphs(ScaledFont *aFont, const GlyphBuffer &aBuffer, const Pattern &aPattern, const DrawOptions &aDrawOptions, 1.1134 + const GlyphRenderingOptions*) 1.1135 +{ 1.1136 + MarkChanged(); 1.1137 + 1.1138 + assert(aBuffer.mNumGlyphs); 1.1139 + CGContextSaveGState(mCg); 1.1140 + 1.1141 + CGContextSetBlendMode(mCg, ToBlendMode(aDrawOptions.mCompositionOp)); 1.1142 + UnboundnessFixer fixer; 1.1143 + CGContextRef cg = fixer.Check(mCg, aDrawOptions.mCompositionOp); 1.1144 + CGContextSetAlpha(cg, aDrawOptions.mAlpha); 1.1145 + CGContextSetShouldAntialias(cg, aDrawOptions.mAntialiasMode != AntialiasMode::NONE); 1.1146 + if (aDrawOptions.mAntialiasMode != AntialiasMode::DEFAULT) { 1.1147 + CGContextSetShouldSmoothFonts(cg, aDrawOptions.mAntialiasMode == AntialiasMode::SUBPIXEL); 1.1148 + } 1.1149 + 1.1150 + CGContextConcatCTM(cg, GfxMatrixToCGAffineTransform(mTransform)); 1.1151 + 1.1152 + ScaledFontMac* macFont = static_cast<ScaledFontMac*>(aFont); 1.1153 + 1.1154 + //XXX: we should use a stack vector here when we have a class like that 1.1155 + std::vector<CGGlyph> glyphs; 1.1156 + std::vector<CGPoint> positions; 1.1157 + glyphs.resize(aBuffer.mNumGlyphs); 1.1158 + positions.resize(aBuffer.mNumGlyphs); 1.1159 + 1.1160 + // Handle the flip 1.1161 + CGContextScaleCTM(cg, 1, -1); 1.1162 + // CGContextSetTextMatrix works differently with kCGTextClip && kCGTextFill 1.1163 + // It seems that it transforms the positions with TextFill and not with TextClip 1.1164 + // Therefore we'll avoid it. See also: 1.1165 + // http://cgit.freedesktop.org/cairo/commit/?id=9c0d761bfcdd28d52c83d74f46dd3c709ae0fa69 1.1166 + 1.1167 + for (unsigned int i = 0; i < aBuffer.mNumGlyphs; i++) { 1.1168 + glyphs[i] = aBuffer.mGlyphs[i].mIndex; 1.1169 + // XXX: CGPointMake might not be inlined 1.1170 + positions[i] = CGPointMake(aBuffer.mGlyphs[i].mPosition.x, 1.1171 + -aBuffer.mGlyphs[i].mPosition.y); 1.1172 + } 1.1173 + 1.1174 + //XXX: CGContextShowGlyphsAtPositions is 10.5+ for older versions use CGContextShowGlyphsWithAdvances 1.1175 + if (isGradient(aPattern)) { 1.1176 + CGContextSetTextDrawingMode(cg, kCGTextClip); 1.1177 + CGRect extents; 1.1178 + if (ScaledFontMac::CTFontDrawGlyphsPtr != nullptr) { 1.1179 + CGRect *bboxes = new CGRect[aBuffer.mNumGlyphs]; 1.1180 + CTFontGetBoundingRectsForGlyphs(macFont->mCTFont, kCTFontDefaultOrientation, 1.1181 + &glyphs.front(), bboxes, aBuffer.mNumGlyphs); 1.1182 + extents = ComputeGlyphsExtents(bboxes, &positions.front(), aBuffer.mNumGlyphs, 1.0f); 1.1183 + ScaledFontMac::CTFontDrawGlyphsPtr(macFont->mCTFont, &glyphs.front(), 1.1184 + &positions.front(), aBuffer.mNumGlyphs, cg); 1.1185 + delete bboxes; 1.1186 + } else { 1.1187 + CGRect *bboxes = new CGRect[aBuffer.mNumGlyphs]; 1.1188 + CGFontGetGlyphBBoxes(macFont->mFont, &glyphs.front(), aBuffer.mNumGlyphs, bboxes); 1.1189 + extents = ComputeGlyphsExtents(bboxes, &positions.front(), aBuffer.mNumGlyphs, macFont->mSize); 1.1190 + 1.1191 + CGContextSetFont(cg, macFont->mFont); 1.1192 + CGContextSetFontSize(cg, macFont->mSize); 1.1193 + CGContextShowGlyphsAtPositions(cg, &glyphs.front(), &positions.front(), 1.1194 + aBuffer.mNumGlyphs); 1.1195 + delete bboxes; 1.1196 + } 1.1197 + CGContextScaleCTM(cg, 1, -1); 1.1198 + DrawGradient(cg, aPattern, extents); 1.1199 + } else { 1.1200 + //XXX: with CoreGraphics we can stroke text directly instead of going 1.1201 + // through GetPath. It would be nice to add support for using that 1.1202 + CGContextSetTextDrawingMode(cg, kCGTextFill); 1.1203 + SetFillFromPattern(cg, mColorSpace, aPattern); 1.1204 + if (ScaledFontMac::CTFontDrawGlyphsPtr != nullptr) { 1.1205 + ScaledFontMac::CTFontDrawGlyphsPtr(macFont->mCTFont, &glyphs.front(), 1.1206 + &positions.front(), 1.1207 + aBuffer.mNumGlyphs, cg); 1.1208 + } else { 1.1209 + CGContextSetFont(cg, macFont->mFont); 1.1210 + CGContextSetFontSize(cg, macFont->mSize); 1.1211 + CGContextShowGlyphsAtPositions(cg, &glyphs.front(), &positions.front(), 1.1212 + aBuffer.mNumGlyphs); 1.1213 + } 1.1214 + } 1.1215 + 1.1216 + fixer.Fix(mCg); 1.1217 + CGContextRestoreGState(cg); 1.1218 +} 1.1219 + 1.1220 +extern "C" { 1.1221 +void 1.1222 +CGContextResetClip(CGContextRef); 1.1223 +}; 1.1224 + 1.1225 +void 1.1226 +DrawTargetCG::CopySurface(SourceSurface *aSurface, 1.1227 + const IntRect& aSourceRect, 1.1228 + const IntPoint &aDestination) 1.1229 +{ 1.1230 + MarkChanged(); 1.1231 + 1.1232 + if (aSurface->GetType() == SurfaceType::COREGRAPHICS_IMAGE || 1.1233 + aSurface->GetType() == SurfaceType::COREGRAPHICS_CGCONTEXT || 1.1234 + aSurface->GetType() == SurfaceType::DATA) { 1.1235 + CGImageRef image = GetRetainedImageFromSourceSurface(aSurface); 1.1236 + 1.1237 + // XXX: it might be more efficient for us to do the copy directly if we have access to the bits 1.1238 + 1.1239 + CGContextSaveGState(mCg); 1.1240 + 1.1241 + // CopySurface ignores the clip, so we need to use private API to temporarily reset it 1.1242 + CGContextResetClip(mCg); 1.1243 + CGRect destRect = CGRectMake(aDestination.x, aDestination.y, 1.1244 + aSourceRect.width, aSourceRect.height); 1.1245 + CGContextClipToRect(mCg, destRect); 1.1246 + 1.1247 + CGContextSetBlendMode(mCg, kCGBlendModeCopy); 1.1248 + 1.1249 + CGContextScaleCTM(mCg, 1, -1); 1.1250 + 1.1251 + CGRect flippedRect = CGRectMake(aDestination.x - aSourceRect.x, -(aDestination.y - aSourceRect.y + double(CGImageGetHeight(image))), 1.1252 + CGImageGetWidth(image), CGImageGetHeight(image)); 1.1253 + 1.1254 + // Quartz seems to copy A8 surfaces incorrectly if we don't initialize them 1.1255 + // to transparent first. 1.1256 + if (mFormat == SurfaceFormat::A8) { 1.1257 + CGContextClearRect(mCg, flippedRect); 1.1258 + } 1.1259 + CGContextDrawImage(mCg, flippedRect, image); 1.1260 + 1.1261 + CGContextRestoreGState(mCg); 1.1262 + CGImageRelease(image); 1.1263 + } 1.1264 +} 1.1265 + 1.1266 +void 1.1267 +DrawTargetCG::DrawSurfaceWithShadow(SourceSurface *aSurface, const Point &aDest, const Color &aColor, const Point &aOffset, Float aSigma, CompositionOp aOperator) 1.1268 +{ 1.1269 + MarkChanged(); 1.1270 + 1.1271 + CGImageRef image = GetRetainedImageFromSourceSurface(aSurface); 1.1272 + 1.1273 + IntSize size = aSurface->GetSize(); 1.1274 + CGContextSaveGState(mCg); 1.1275 + //XXX do we need to do the fixup here? 1.1276 + CGContextSetBlendMode(mCg, ToBlendMode(aOperator)); 1.1277 + 1.1278 + CGContextScaleCTM(mCg, 1, -1); 1.1279 + 1.1280 + CGRect flippedRect = CGRectMake(aDest.x, -(aDest.y + size.height), 1.1281 + size.width, size.height); 1.1282 + 1.1283 + CGColorRef color = ColorToCGColor(mColorSpace, aColor); 1.1284 + CGSize offset = {aOffset.x, -aOffset.y}; 1.1285 + // CoreGraphics needs twice sigma as it's amount of blur 1.1286 + CGContextSetShadowWithColor(mCg, offset, 2*aSigma, color); 1.1287 + CGColorRelease(color); 1.1288 + 1.1289 + CGContextDrawImage(mCg, flippedRect, image); 1.1290 + 1.1291 + CGImageRelease(image); 1.1292 + CGContextRestoreGState(mCg); 1.1293 + 1.1294 +} 1.1295 + 1.1296 +bool 1.1297 +DrawTargetCG::Init(BackendType aType, 1.1298 + unsigned char* aData, 1.1299 + const IntSize &aSize, 1.1300 + int32_t aStride, 1.1301 + SurfaceFormat aFormat) 1.1302 +{ 1.1303 + // XXX: we should come up with some consistent semantics for dealing 1.1304 + // with zero area drawtargets 1.1305 + if (aSize.width <= 0 || aSize.height <= 0 || 1.1306 + // 32767 is the maximum size supported by cairo 1.1307 + // we clamp to that to make it easier to interoperate 1.1308 + aSize.width > 32767 || aSize.height > 32767) { 1.1309 + gfxWarning() << "Failed to Init() DrawTargetCG because of bad size."; 1.1310 + mColorSpace = nullptr; 1.1311 + mCg = nullptr; 1.1312 + return false; 1.1313 + } 1.1314 + 1.1315 + //XXX: handle SurfaceFormat 1.1316 + 1.1317 + //XXX: we'd be better off reusing the Colorspace across draw targets 1.1318 + mColorSpace = CGColorSpaceCreateDeviceRGB(); 1.1319 + 1.1320 + if (aData == nullptr && aType != BackendType::COREGRAPHICS_ACCELERATED) { 1.1321 + // XXX: Currently, Init implicitly clears, that can often be a waste of time 1.1322 + size_t bufLen = BufferSizeFromStrideAndHeight(aStride, aSize.height); 1.1323 + if (bufLen == 0) { 1.1324 + mColorSpace = nullptr; 1.1325 + mCg = nullptr; 1.1326 + return false; 1.1327 + } 1.1328 + static_assert(sizeof(decltype(mData[0])) == 1, 1.1329 + "mData.Realloc() takes an object count, so its objects must be 1-byte sized if we use bufLen"); 1.1330 + mData.Realloc(/* actually an object count */ bufLen); 1.1331 + aData = static_cast<unsigned char*>(mData); 1.1332 + memset(aData, 0, bufLen); 1.1333 + } 1.1334 + 1.1335 + mSize = aSize; 1.1336 + 1.1337 + if (aType == BackendType::COREGRAPHICS_ACCELERATED) { 1.1338 + RefPtr<MacIOSurface> ioSurface = MacIOSurface::CreateIOSurface(aSize.width, aSize.height); 1.1339 + mCg = ioSurface->CreateIOSurfaceContext(); 1.1340 + // If we don't have the symbol for 'CreateIOSurfaceContext' mCg will be null 1.1341 + // and we will fallback to software below 1.1342 + } 1.1343 + 1.1344 + mFormat = SurfaceFormat::B8G8R8A8; 1.1345 + 1.1346 + if (!mCg || aType == BackendType::COREGRAPHICS) { 1.1347 + int bitsPerComponent = 8; 1.1348 + 1.1349 + CGBitmapInfo bitinfo; 1.1350 + if (aFormat == SurfaceFormat::A8) { 1.1351 + if (mColorSpace) 1.1352 + CGColorSpaceRelease(mColorSpace); 1.1353 + mColorSpace = nullptr; 1.1354 + bitinfo = kCGImageAlphaOnly; 1.1355 + mFormat = SurfaceFormat::A8; 1.1356 + } else { 1.1357 + bitinfo = kCGBitmapByteOrder32Host; 1.1358 + if (aFormat == SurfaceFormat::B8G8R8X8) { 1.1359 + bitinfo |= kCGImageAlphaNoneSkipFirst; 1.1360 + mFormat = aFormat; 1.1361 + } else { 1.1362 + bitinfo |= kCGImageAlphaPremultipliedFirst; 1.1363 + } 1.1364 + } 1.1365 + // XXX: what should we do if this fails? 1.1366 + mCg = CGBitmapContextCreate (aData, 1.1367 + mSize.width, 1.1368 + mSize.height, 1.1369 + bitsPerComponent, 1.1370 + aStride, 1.1371 + mColorSpace, 1.1372 + bitinfo); 1.1373 + } 1.1374 + 1.1375 + assert(mCg); 1.1376 + // CGContext's default to have the origin at the bottom left 1.1377 + // so flip it to the top left 1.1378 + CGContextTranslateCTM(mCg, 0, mSize.height); 1.1379 + CGContextScaleCTM(mCg, 1, -1); 1.1380 + // See Bug 722164 for performance details 1.1381 + // Medium or higher quality lead to expensive interpolation 1.1382 + // for canvas we want to use low quality interpolation 1.1383 + // to have competitive performance with other canvas 1.1384 + // implementation. 1.1385 + // XXX: Create input parameter to control interpolation and 1.1386 + // use the default for content. 1.1387 + CGContextSetInterpolationQuality(mCg, kCGInterpolationLow); 1.1388 + 1.1389 + 1.1390 + if (aType == BackendType::COREGRAPHICS_ACCELERATED) { 1.1391 + // The bitmap backend uses callac to clear, we can't do that without 1.1392 + // reading back the surface. This should trigger something equivilent 1.1393 + // to glClear. 1.1394 + ClearRect(Rect(0, 0, mSize.width, mSize.height)); 1.1395 + } 1.1396 + 1.1397 + return true; 1.1398 +} 1.1399 + 1.1400 +void 1.1401 +DrawTargetCG::Flush() 1.1402 +{ 1.1403 + if (GetContextType(mCg) == CG_CONTEXT_TYPE_IOSURFACE) { 1.1404 + CGContextFlush(mCg); 1.1405 + } 1.1406 +} 1.1407 + 1.1408 +bool 1.1409 +DrawTargetCG::Init(CGContextRef cgContext, const IntSize &aSize) 1.1410 +{ 1.1411 + // XXX: we should come up with some consistent semantics for dealing 1.1412 + // with zero area drawtargets 1.1413 + if (aSize.width == 0 || aSize.height == 0) { 1.1414 + mColorSpace = nullptr; 1.1415 + mCg = nullptr; 1.1416 + return false; 1.1417 + } 1.1418 + 1.1419 + //XXX: handle SurfaceFormat 1.1420 + 1.1421 + //XXX: we'd be better off reusing the Colorspace across draw targets 1.1422 + mColorSpace = CGColorSpaceCreateDeviceRGB(); 1.1423 + 1.1424 + mSize = aSize; 1.1425 + 1.1426 + mCg = cgContext; 1.1427 + CGContextRetain(mCg); 1.1428 + 1.1429 + assert(mCg); 1.1430 + 1.1431 + // CGContext's default to have the origin at the bottom left. 1.1432 + // However, currently the only use of this function is to construct a 1.1433 + // DrawTargetCG around a CGContextRef from a cairo quartz surface which 1.1434 + // already has it's origin adjusted. 1.1435 + // 1.1436 + // CGContextTranslateCTM(mCg, 0, mSize.height); 1.1437 + // CGContextScaleCTM(mCg, 1, -1); 1.1438 + 1.1439 + mFormat = SurfaceFormat::B8G8R8A8; 1.1440 + if (GetContextType(mCg) == CG_CONTEXT_TYPE_BITMAP) { 1.1441 + CGColorSpaceRef colorspace; 1.1442 + CGBitmapInfo bitinfo = CGBitmapContextGetBitmapInfo(mCg); 1.1443 + colorspace = CGBitmapContextGetColorSpace (mCg); 1.1444 + if (CGColorSpaceGetNumberOfComponents(colorspace) == 1) { 1.1445 + mFormat = SurfaceFormat::A8; 1.1446 + } else if ((bitinfo & kCGBitmapAlphaInfoMask) == kCGImageAlphaNoneSkipFirst) { 1.1447 + mFormat = SurfaceFormat::B8G8R8X8; 1.1448 + } 1.1449 + } 1.1450 + 1.1451 + return true; 1.1452 +} 1.1453 + 1.1454 +bool 1.1455 +DrawTargetCG::Init(BackendType aType, const IntSize &aSize, SurfaceFormat &aFormat) 1.1456 +{ 1.1457 + int32_t stride = GetAlignedStride<16>(aSize.width * BytesPerPixel(aFormat)); 1.1458 + 1.1459 + // Calling Init with aData == nullptr will allocate. 1.1460 + return Init(aType, nullptr, aSize, stride, aFormat); 1.1461 +} 1.1462 + 1.1463 +TemporaryRef<PathBuilder> 1.1464 +DrawTargetCG::CreatePathBuilder(FillRule aFillRule) const 1.1465 +{ 1.1466 + RefPtr<PathBuilderCG> pb = new PathBuilderCG(aFillRule); 1.1467 + return pb; 1.1468 +} 1.1469 + 1.1470 +void* 1.1471 +DrawTargetCG::GetNativeSurface(NativeSurfaceType aType) 1.1472 +{ 1.1473 + if ((aType == NativeSurfaceType::CGCONTEXT && GetContextType(mCg) == CG_CONTEXT_TYPE_BITMAP) || 1.1474 + (aType == NativeSurfaceType::CGCONTEXT_ACCELERATED && GetContextType(mCg) == CG_CONTEXT_TYPE_IOSURFACE)) { 1.1475 + return mCg; 1.1476 + } else { 1.1477 + return nullptr; 1.1478 + } 1.1479 +} 1.1480 + 1.1481 +void 1.1482 +DrawTargetCG::Mask(const Pattern &aSource, 1.1483 + const Pattern &aMask, 1.1484 + const DrawOptions &aDrawOptions) 1.1485 +{ 1.1486 + MarkChanged(); 1.1487 + 1.1488 + CGContextSaveGState(mCg); 1.1489 + 1.1490 + if (isGradient(aMask)) { 1.1491 + assert(0); 1.1492 + } else { 1.1493 + if (aMask.GetType() == PatternType::COLOR) { 1.1494 + DrawOptions drawOptions(aDrawOptions); 1.1495 + const Color& color = static_cast<const ColorPattern&>(aMask).mColor; 1.1496 + drawOptions.mAlpha *= color.a; 1.1497 + assert(0); 1.1498 + // XXX: we need to get a rect that when transformed covers the entire surface 1.1499 + //Rect 1.1500 + //FillRect(rect, aSource, drawOptions); 1.1501 + } else if (aMask.GetType() == PatternType::SURFACE) { 1.1502 + const SurfacePattern& pat = static_cast<const SurfacePattern&>(aMask); 1.1503 + CGImageRef mask = GetRetainedImageFromSourceSurface(pat.mSurface.get()); 1.1504 + Rect rect(0,0, CGImageGetWidth(mask), CGImageGetHeight(mask)); 1.1505 + // XXX: probably we need to do some flipping of the image or something 1.1506 + CGContextClipToMask(mCg, RectToCGRect(rect), mask); 1.1507 + FillRect(rect, aSource, aDrawOptions); 1.1508 + CGImageRelease(mask); 1.1509 + } 1.1510 + } 1.1511 + 1.1512 + CGContextRestoreGState(mCg); 1.1513 +} 1.1514 + 1.1515 +void 1.1516 +DrawTargetCG::PushClipRect(const Rect &aRect) 1.1517 +{ 1.1518 + CGContextSaveGState(mCg); 1.1519 + 1.1520 + /* We go through a bit of trouble to temporarilly set the transform 1.1521 + * while we add the path */ 1.1522 + CGAffineTransform previousTransform = CGContextGetCTM(mCg); 1.1523 + CGContextConcatCTM(mCg, GfxMatrixToCGAffineTransform(mTransform)); 1.1524 + CGContextClipToRect(mCg, RectToCGRect(aRect)); 1.1525 + CGContextSetCTM(mCg, previousTransform); 1.1526 +} 1.1527 + 1.1528 + 1.1529 +void 1.1530 +DrawTargetCG::PushClip(const Path *aPath) 1.1531 +{ 1.1532 + CGContextSaveGState(mCg); 1.1533 + 1.1534 + CGContextBeginPath(mCg); 1.1535 + assert(aPath->GetBackendType() == BackendType::COREGRAPHICS); 1.1536 + 1.1537 + const PathCG *cgPath = static_cast<const PathCG*>(aPath); 1.1538 + 1.1539 + // Weirdly, CoreGraphics clips empty paths as all shown 1.1540 + // but emtpy rects as all clipped. We detect this situation and 1.1541 + // workaround it appropriately 1.1542 + if (CGPathIsEmpty(cgPath->GetPath())) { 1.1543 + // XXX: should we return here? 1.1544 + CGContextClipToRect(mCg, CGRectZero); 1.1545 + } 1.1546 + 1.1547 + 1.1548 + /* We go through a bit of trouble to temporarilly set the transform 1.1549 + * while we add the path. XXX: this could be improved if we keep 1.1550 + * the CTM as resident state on the DrawTarget. */ 1.1551 + CGContextSaveGState(mCg); 1.1552 + CGContextConcatCTM(mCg, GfxMatrixToCGAffineTransform(mTransform)); 1.1553 + CGContextAddPath(mCg, cgPath->GetPath()); 1.1554 + CGContextRestoreGState(mCg); 1.1555 + 1.1556 + if (cgPath->GetFillRule() == FillRule::FILL_EVEN_ODD) 1.1557 + CGContextEOClip(mCg); 1.1558 + else 1.1559 + CGContextClip(mCg); 1.1560 +} 1.1561 + 1.1562 +void 1.1563 +DrawTargetCG::PopClip() 1.1564 +{ 1.1565 + CGContextRestoreGState(mCg); 1.1566 +} 1.1567 + 1.1568 +void 1.1569 +DrawTargetCG::MarkChanged() 1.1570 +{ 1.1571 + if (mSnapshot) { 1.1572 + if (mSnapshot->refCount() > 1) { 1.1573 + // We only need to worry about snapshots that someone else knows about 1.1574 + mSnapshot->DrawTargetWillChange(); 1.1575 + } 1.1576 + mSnapshot = nullptr; 1.1577 + } 1.1578 +} 1.1579 + 1.1580 +CGContextRef 1.1581 +BorrowedCGContext::BorrowCGContextFromDrawTarget(DrawTarget *aDT) 1.1582 +{ 1.1583 + if (aDT->GetType() == BackendType::COREGRAPHICS || aDT->GetType() == BackendType::COREGRAPHICS_ACCELERATED) { 1.1584 + DrawTargetCG* cgDT = static_cast<DrawTargetCG*>(aDT); 1.1585 + cgDT->MarkChanged(); 1.1586 + 1.1587 + // swap out the context 1.1588 + CGContextRef cg = cgDT->mCg; 1.1589 + cgDT->mCg = nullptr; 1.1590 + 1.1591 + // save the state to make it easier for callers to avoid mucking with things 1.1592 + CGContextSaveGState(cg); 1.1593 + 1.1594 + CGContextConcatCTM(cg, GfxMatrixToCGAffineTransform(cgDT->mTransform)); 1.1595 + 1.1596 + return cg; 1.1597 + } 1.1598 + return nullptr; 1.1599 +} 1.1600 + 1.1601 +void 1.1602 +BorrowedCGContext::ReturnCGContextToDrawTarget(DrawTarget *aDT, CGContextRef cg) 1.1603 +{ 1.1604 + DrawTargetCG* cgDT = static_cast<DrawTargetCG*>(aDT); 1.1605 + 1.1606 + CGContextRestoreGState(cg); 1.1607 + cgDT->mCg = cg; 1.1608 +} 1.1609 + 1.1610 + 1.1611 +} 1.1612 +}