gfx/2d/DrawTargetCG.cpp

Tue, 06 Jan 2015 21:39:09 +0100

author
Michael Schloh von Bennewitz <michael@schloh.com>
date
Tue, 06 Jan 2015 21:39:09 +0100
branch
TOR_BUG_9701
changeset 8
97036ab72558
permissions
-rw-r--r--

Conditionally force memory storage according to privacy.thirdparty.isolate;
This solves Tor bug #9701, complying with disk avoidance documented in
https://www.torproject.org/projects/torbrowser/design/#disk-avoidance.

michael@0 1 /* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 2 -*-
michael@0 2 * This Source Code Form is subject to the terms of the Mozilla Public
michael@0 3 * License, v. 2.0. If a copy of the MPL was not distributed with this
michael@0 4 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
michael@0 5 #include "BorrowedContext.h"
michael@0 6 #include "DataSurfaceHelpers.h"
michael@0 7 #include "DrawTargetCG.h"
michael@0 8 #include "Logging.h"
michael@0 9 #include "SourceSurfaceCG.h"
michael@0 10 #include "Rect.h"
michael@0 11 #include "ScaledFontMac.h"
michael@0 12 #include "Tools.h"
michael@0 13 #include <vector>
michael@0 14 #include <algorithm>
michael@0 15 #include "MacIOSurface.h"
michael@0 16 #include "FilterNodeSoftware.h"
michael@0 17 #include "mozilla/Assertions.h"
michael@0 18 #include "mozilla/Types.h" // for decltype
michael@0 19 #include "mozilla/FloatingPoint.h"
michael@0 20
michael@0 21 using namespace std;
michael@0 22
michael@0 23 //CG_EXTERN void CGContextSetCompositeOperation (CGContextRef, PrivateCGCompositeMode);
michael@0 24
michael@0 25 // A private API that Cairo has been using for a long time
michael@0 26 CG_EXTERN void CGContextSetCTM(CGContextRef, CGAffineTransform);
michael@0 27
michael@0 28 namespace mozilla {
michael@0 29 namespace gfx {
michael@0 30
michael@0 31 static CGRect RectToCGRect(Rect r)
michael@0 32 {
michael@0 33 return CGRectMake(r.x, r.y, r.width, r.height);
michael@0 34 }
michael@0 35
michael@0 36 CGBlendMode ToBlendMode(CompositionOp op)
michael@0 37 {
michael@0 38 CGBlendMode mode;
michael@0 39 switch (op) {
michael@0 40 case CompositionOp::OP_OVER:
michael@0 41 mode = kCGBlendModeNormal;
michael@0 42 break;
michael@0 43 case CompositionOp::OP_ADD:
michael@0 44 mode = kCGBlendModePlusLighter;
michael@0 45 break;
michael@0 46 case CompositionOp::OP_ATOP:
michael@0 47 mode = kCGBlendModeSourceAtop;
michael@0 48 break;
michael@0 49 case CompositionOp::OP_OUT:
michael@0 50 mode = kCGBlendModeSourceOut;
michael@0 51 break;
michael@0 52 case CompositionOp::OP_IN:
michael@0 53 mode = kCGBlendModeSourceIn;
michael@0 54 break;
michael@0 55 case CompositionOp::OP_SOURCE:
michael@0 56 mode = kCGBlendModeCopy;
michael@0 57 break;
michael@0 58 case CompositionOp::OP_DEST_IN:
michael@0 59 mode = kCGBlendModeDestinationIn;
michael@0 60 break;
michael@0 61 case CompositionOp::OP_DEST_OUT:
michael@0 62 mode = kCGBlendModeDestinationOut;
michael@0 63 break;
michael@0 64 case CompositionOp::OP_DEST_OVER:
michael@0 65 mode = kCGBlendModeDestinationOver;
michael@0 66 break;
michael@0 67 case CompositionOp::OP_DEST_ATOP:
michael@0 68 mode = kCGBlendModeDestinationAtop;
michael@0 69 break;
michael@0 70 case CompositionOp::OP_XOR:
michael@0 71 mode = kCGBlendModeXOR;
michael@0 72 break;
michael@0 73 case CompositionOp::OP_MULTIPLY:
michael@0 74 mode = kCGBlendModeMultiply;
michael@0 75 break;
michael@0 76 case CompositionOp::OP_SCREEN:
michael@0 77 mode = kCGBlendModeScreen;
michael@0 78 break;
michael@0 79 case CompositionOp::OP_OVERLAY:
michael@0 80 mode = kCGBlendModeOverlay;
michael@0 81 break;
michael@0 82 case CompositionOp::OP_DARKEN:
michael@0 83 mode = kCGBlendModeDarken;
michael@0 84 break;
michael@0 85 case CompositionOp::OP_LIGHTEN:
michael@0 86 mode = kCGBlendModeLighten;
michael@0 87 break;
michael@0 88 case CompositionOp::OP_COLOR_DODGE:
michael@0 89 mode = kCGBlendModeColorDodge;
michael@0 90 break;
michael@0 91 case CompositionOp::OP_COLOR_BURN:
michael@0 92 mode = kCGBlendModeColorBurn;
michael@0 93 break;
michael@0 94 case CompositionOp::OP_HARD_LIGHT:
michael@0 95 mode = kCGBlendModeHardLight;
michael@0 96 break;
michael@0 97 case CompositionOp::OP_SOFT_LIGHT:
michael@0 98 mode = kCGBlendModeSoftLight;
michael@0 99 break;
michael@0 100 case CompositionOp::OP_DIFFERENCE:
michael@0 101 mode = kCGBlendModeDifference;
michael@0 102 break;
michael@0 103 case CompositionOp::OP_EXCLUSION:
michael@0 104 mode = kCGBlendModeExclusion;
michael@0 105 break;
michael@0 106 case CompositionOp::OP_HUE:
michael@0 107 mode = kCGBlendModeHue;
michael@0 108 break;
michael@0 109 case CompositionOp::OP_SATURATION:
michael@0 110 mode = kCGBlendModeSaturation;
michael@0 111 break;
michael@0 112 case CompositionOp::OP_COLOR:
michael@0 113 mode = kCGBlendModeColor;
michael@0 114 break;
michael@0 115 case CompositionOp::OP_LUMINOSITY:
michael@0 116 mode = kCGBlendModeLuminosity;
michael@0 117 break;
michael@0 118 /*
michael@0 119 case OP_CLEAR:
michael@0 120 mode = kCGBlendModeClear;
michael@0 121 break;*/
michael@0 122 default:
michael@0 123 mode = kCGBlendModeNormal;
michael@0 124 }
michael@0 125 return mode;
michael@0 126 }
michael@0 127
michael@0 128 static CGInterpolationQuality
michael@0 129 InterpolationQualityFromFilter(Filter aFilter)
michael@0 130 {
michael@0 131 switch (aFilter) {
michael@0 132 default:
michael@0 133 case Filter::LINEAR:
michael@0 134 return kCGInterpolationLow;
michael@0 135 case Filter::POINT:
michael@0 136 return kCGInterpolationNone;
michael@0 137 case Filter::GOOD:
michael@0 138 return kCGInterpolationDefault;
michael@0 139 }
michael@0 140 }
michael@0 141
michael@0 142
michael@0 143 DrawTargetCG::DrawTargetCG() : mCg(nullptr), mSnapshot(nullptr)
michael@0 144 {
michael@0 145 }
michael@0 146
michael@0 147 DrawTargetCG::~DrawTargetCG()
michael@0 148 {
michael@0 149 MarkChanged();
michael@0 150
michael@0 151 // We need to conditionally release these because Init can fail without initializing these.
michael@0 152 if (mColorSpace)
michael@0 153 CGColorSpaceRelease(mColorSpace);
michael@0 154 if (mCg)
michael@0 155 CGContextRelease(mCg);
michael@0 156 }
michael@0 157
michael@0 158 BackendType
michael@0 159 DrawTargetCG::GetType() const
michael@0 160 {
michael@0 161 // It may be worth spliting Bitmap and IOSurface DrawTarget
michael@0 162 // into seperate classes.
michael@0 163 if (GetContextType(mCg) == CG_CONTEXT_TYPE_IOSURFACE) {
michael@0 164 return BackendType::COREGRAPHICS_ACCELERATED;
michael@0 165 } else {
michael@0 166 return BackendType::COREGRAPHICS;
michael@0 167 }
michael@0 168 }
michael@0 169
michael@0 170 TemporaryRef<SourceSurface>
michael@0 171 DrawTargetCG::Snapshot()
michael@0 172 {
michael@0 173 if (!mSnapshot) {
michael@0 174 if (GetContextType(mCg) == CG_CONTEXT_TYPE_IOSURFACE) {
michael@0 175 return new SourceSurfaceCGIOSurfaceContext(this);
michael@0 176 } else {
michael@0 177 mSnapshot = new SourceSurfaceCGBitmapContext(this);
michael@0 178 }
michael@0 179 }
michael@0 180
michael@0 181 return mSnapshot;
michael@0 182 }
michael@0 183
michael@0 184 TemporaryRef<DrawTarget>
michael@0 185 DrawTargetCG::CreateSimilarDrawTarget(const IntSize &aSize, SurfaceFormat aFormat) const
michael@0 186 {
michael@0 187 // XXX: in thebes we use CGLayers to do this kind of thing. It probably makes sense
michael@0 188 // to add that in somehow, but at a higher level
michael@0 189 RefPtr<DrawTargetCG> newTarget = new DrawTargetCG();
michael@0 190 if (newTarget->Init(GetType(), aSize, aFormat)) {
michael@0 191 return newTarget;
michael@0 192 } else {
michael@0 193 return nullptr;
michael@0 194 }
michael@0 195 }
michael@0 196
michael@0 197 TemporaryRef<SourceSurface>
michael@0 198 DrawTargetCG::CreateSourceSurfaceFromData(unsigned char *aData,
michael@0 199 const IntSize &aSize,
michael@0 200 int32_t aStride,
michael@0 201 SurfaceFormat aFormat) const
michael@0 202 {
michael@0 203 RefPtr<SourceSurfaceCG> newSurf = new SourceSurfaceCG();
michael@0 204
michael@0 205 if (!newSurf->InitFromData(aData, aSize, aStride, aFormat)) {
michael@0 206 return nullptr;
michael@0 207 }
michael@0 208
michael@0 209 return newSurf;
michael@0 210 }
michael@0 211
michael@0 212 // This function returns a retained CGImage that needs to be released after
michael@0 213 // use. The reason for this is that we want to either reuse an existing CGImage
michael@0 214 // or create a new one.
michael@0 215 static CGImageRef
michael@0 216 GetRetainedImageFromSourceSurface(SourceSurface *aSurface)
michael@0 217 {
michael@0 218 if (aSurface->GetType() == SurfaceType::COREGRAPHICS_IMAGE)
michael@0 219 return CGImageRetain(static_cast<SourceSurfaceCG*>(aSurface)->GetImage());
michael@0 220 else if (aSurface->GetType() == SurfaceType::COREGRAPHICS_CGCONTEXT)
michael@0 221 return CGImageRetain(static_cast<SourceSurfaceCGContext*>(aSurface)->GetImage());
michael@0 222
michael@0 223 if (aSurface->GetType() == SurfaceType::DATA) {
michael@0 224 DataSourceSurface* dataSource = static_cast<DataSourceSurface*>(aSurface);
michael@0 225 return CreateCGImage(nullptr, dataSource->GetData(), dataSource->GetSize(),
michael@0 226 dataSource->Stride(), dataSource->GetFormat());
michael@0 227 }
michael@0 228
michael@0 229 MOZ_CRASH("unsupported source surface");
michael@0 230 }
michael@0 231
michael@0 232 TemporaryRef<SourceSurface>
michael@0 233 DrawTargetCG::OptimizeSourceSurface(SourceSurface *aSurface) const
michael@0 234 {
michael@0 235 if (aSurface->GetType() == SurfaceType::COREGRAPHICS_IMAGE ||
michael@0 236 aSurface->GetType() == SurfaceType::COREGRAPHICS_CGCONTEXT) {
michael@0 237 return aSurface;
michael@0 238 }
michael@0 239 return aSurface->GetDataSurface();
michael@0 240 }
michael@0 241
michael@0 242 class UnboundnessFixer
michael@0 243 {
michael@0 244 CGRect mClipBounds;
michael@0 245 CGLayerRef mLayer;
michael@0 246 CGContextRef mCg;
michael@0 247 public:
michael@0 248 UnboundnessFixer() : mCg(nullptr) {}
michael@0 249
michael@0 250 CGContextRef Check(CGContextRef baseCg, CompositionOp blend, const Rect* maskBounds = nullptr)
michael@0 251 {
michael@0 252 if (!IsOperatorBoundByMask(blend)) {
michael@0 253 mClipBounds = CGContextGetClipBoundingBox(baseCg);
michael@0 254 // If we're entirely clipped out or if the drawing operation covers the entire clip then
michael@0 255 // we don't need to create a temporary surface.
michael@0 256 if (CGRectIsEmpty(mClipBounds) ||
michael@0 257 (maskBounds && maskBounds->Contains(CGRectToRect(mClipBounds)))) {
michael@0 258 return baseCg;
michael@0 259 }
michael@0 260
michael@0 261 // TransparencyLayers aren't blended using the blend mode so
michael@0 262 // we are forced to use CGLayers
michael@0 263
michael@0 264 //XXX: The size here is in default user space units, of the layer relative to the graphics context.
michael@0 265 // is the clip bounds still correct if, for example, we have a scale applied to the context?
michael@0 266 mLayer = CGLayerCreateWithContext(baseCg, mClipBounds.size, nullptr);
michael@0 267 mCg = CGLayerGetContext(mLayer);
michael@0 268 // CGContext's default to have the origin at the bottom left
michael@0 269 // so flip it to the top left and adjust for the origin
michael@0 270 // of the layer
michael@0 271 CGContextTranslateCTM(mCg, -mClipBounds.origin.x, mClipBounds.origin.y + mClipBounds.size.height);
michael@0 272 CGContextScaleCTM(mCg, 1, -1);
michael@0 273
michael@0 274 return mCg;
michael@0 275 } else {
michael@0 276 return baseCg;
michael@0 277 }
michael@0 278 }
michael@0 279
michael@0 280 void Fix(CGContextRef baseCg)
michael@0 281 {
michael@0 282 if (mCg) {
michael@0 283 CGContextTranslateCTM(baseCg, 0, mClipBounds.size.height);
michael@0 284 CGContextScaleCTM(baseCg, 1, -1);
michael@0 285 mClipBounds.origin.y *= -1;
michael@0 286 CGContextDrawLayerAtPoint(baseCg, mClipBounds.origin, mLayer);
michael@0 287 CGContextRelease(mCg);
michael@0 288 }
michael@0 289 }
michael@0 290 };
michael@0 291
michael@0 292 void
michael@0 293 DrawTargetCG::DrawSurface(SourceSurface *aSurface,
michael@0 294 const Rect &aDest,
michael@0 295 const Rect &aSource,
michael@0 296 const DrawSurfaceOptions &aSurfOptions,
michael@0 297 const DrawOptions &aDrawOptions)
michael@0 298 {
michael@0 299 MarkChanged();
michael@0 300
michael@0 301 CGContextSaveGState(mCg);
michael@0 302
michael@0 303 CGContextSetBlendMode(mCg, ToBlendMode(aDrawOptions.mCompositionOp));
michael@0 304 UnboundnessFixer fixer;
michael@0 305 CGContextRef cg = fixer.Check(mCg, aDrawOptions.mCompositionOp, &aDest);
michael@0 306 CGContextSetAlpha(cg, aDrawOptions.mAlpha);
michael@0 307 CGContextSetShouldAntialias(cg, aDrawOptions.mAntialiasMode != AntialiasMode::NONE);
michael@0 308
michael@0 309 CGContextConcatCTM(cg, GfxMatrixToCGAffineTransform(mTransform));
michael@0 310
michael@0 311 CGContextSetInterpolationQuality(cg, InterpolationQualityFromFilter(aSurfOptions.mFilter));
michael@0 312
michael@0 313 CGImageRef image = GetRetainedImageFromSourceSurface(aSurface);
michael@0 314
michael@0 315 if (aSurfOptions.mFilter == Filter::POINT) {
michael@0 316 CGImageRef subimage = CGImageCreateWithImageInRect(image, RectToCGRect(aSource));
michael@0 317 CGImageRelease(image);
michael@0 318
michael@0 319 CGContextScaleCTM(cg, 1, -1);
michael@0 320
michael@0 321 CGRect flippedRect = CGRectMake(aDest.x, -(aDest.y + aDest.height),
michael@0 322 aDest.width, aDest.height);
michael@0 323
michael@0 324 CGContextDrawImage(cg, flippedRect, subimage);
michael@0 325 CGImageRelease(subimage);
michael@0 326 } else {
michael@0 327 CGRect destRect = CGRectMake(aDest.x, aDest.y, aDest.width, aDest.height);
michael@0 328 CGContextClipToRect(cg, destRect);
michael@0 329
michael@0 330 float xScale = aSource.width / aDest.width;
michael@0 331 float yScale = aSource.height / aDest.height;
michael@0 332 CGContextTranslateCTM(cg, aDest.x - aSource.x / xScale, aDest.y - aSource.y / yScale);
michael@0 333
michael@0 334 CGRect adjustedDestRect = CGRectMake(0, 0, CGImageGetWidth(image) / xScale,
michael@0 335 CGImageGetHeight(image) / yScale);
michael@0 336
michael@0 337 CGContextTranslateCTM(cg, 0, CGRectGetHeight(adjustedDestRect));
michael@0 338 CGContextScaleCTM(cg, 1, -1);
michael@0 339
michael@0 340 CGContextDrawImage(cg, adjustedDestRect, image);
michael@0 341 CGImageRelease(image);
michael@0 342 }
michael@0 343
michael@0 344 fixer.Fix(mCg);
michael@0 345
michael@0 346 CGContextRestoreGState(mCg);
michael@0 347 }
michael@0 348
michael@0 349 TemporaryRef<FilterNode>
michael@0 350 DrawTargetCG::CreateFilter(FilterType aType)
michael@0 351 {
michael@0 352 return FilterNodeSoftware::Create(aType);
michael@0 353 }
michael@0 354
michael@0 355 void
michael@0 356 DrawTargetCG::DrawFilter(FilterNode *aNode,
michael@0 357 const Rect &aSourceRect,
michael@0 358 const Point &aDestPoint,
michael@0 359 const DrawOptions &aOptions)
michael@0 360 {
michael@0 361 FilterNodeSoftware* filter = static_cast<FilterNodeSoftware*>(aNode);
michael@0 362 filter->Draw(this, aSourceRect, aDestPoint, aOptions);
michael@0 363 }
michael@0 364
michael@0 365 static CGColorRef ColorToCGColor(CGColorSpaceRef aColorSpace, const Color& aColor)
michael@0 366 {
michael@0 367 CGFloat components[4] = {aColor.r, aColor.g, aColor.b, aColor.a};
michael@0 368 return CGColorCreate(aColorSpace, components);
michael@0 369 }
michael@0 370
michael@0 371 class GradientStopsCG : public GradientStops
michael@0 372 {
michael@0 373 public:
michael@0 374 MOZ_DECLARE_REFCOUNTED_VIRTUAL_TYPENAME(GradientStopsCG)
michael@0 375 //XXX: The skia backend uses a vector and passes in aNumStops. It should do better
michael@0 376 GradientStopsCG(GradientStop* aStops, uint32_t aNumStops, ExtendMode aExtendMode)
michael@0 377 {
michael@0 378 mExtend = aExtendMode;
michael@0 379 if (aExtendMode == ExtendMode::CLAMP) {
michael@0 380 //XXX: do the stops need to be in any particular order?
michael@0 381 // what should we do about the color space here? we certainly shouldn't be
michael@0 382 // recreating it all the time
michael@0 383 std::vector<CGFloat> colors;
michael@0 384 std::vector<CGFloat> offsets;
michael@0 385 colors.reserve(aNumStops*4);
michael@0 386 offsets.reserve(aNumStops);
michael@0 387
michael@0 388 for (uint32_t i = 0; i < aNumStops; i++) {
michael@0 389 colors.push_back(aStops[i].color.r);
michael@0 390 colors.push_back(aStops[i].color.g);
michael@0 391 colors.push_back(aStops[i].color.b);
michael@0 392 colors.push_back(aStops[i].color.a);
michael@0 393
michael@0 394 offsets.push_back(aStops[i].offset);
michael@0 395 }
michael@0 396
michael@0 397 CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB();
michael@0 398 mGradient = CGGradientCreateWithColorComponents(colorSpace,
michael@0 399 &colors.front(),
michael@0 400 &offsets.front(),
michael@0 401 aNumStops);
michael@0 402 CGColorSpaceRelease(colorSpace);
michael@0 403 } else {
michael@0 404 mGradient = nullptr;
michael@0 405 mStops.reserve(aNumStops);
michael@0 406 for (uint32_t i = 0; i < aNumStops; i++) {
michael@0 407 mStops.push_back(aStops[i]);
michael@0 408 }
michael@0 409 }
michael@0 410
michael@0 411 }
michael@0 412 virtual ~GradientStopsCG() {
michael@0 413 if (mGradient)
michael@0 414 CGGradientRelease(mGradient);
michael@0 415 }
michael@0 416 // Will always report BackendType::COREGRAPHICS, but it is compatible
michael@0 417 // with BackendType::COREGRAPHICS_ACCELERATED
michael@0 418 BackendType GetBackendType() const { return BackendType::COREGRAPHICS; }
michael@0 419 // XXX this should be a union
michael@0 420 CGGradientRef mGradient;
michael@0 421 std::vector<GradientStop> mStops;
michael@0 422 ExtendMode mExtend;
michael@0 423 };
michael@0 424
michael@0 425 TemporaryRef<GradientStops>
michael@0 426 DrawTargetCG::CreateGradientStops(GradientStop *aStops, uint32_t aNumStops,
michael@0 427 ExtendMode aExtendMode) const
michael@0 428 {
michael@0 429 return new GradientStopsCG(aStops, aNumStops, aExtendMode);
michael@0 430 }
michael@0 431
michael@0 432 static void
michael@0 433 UpdateLinearParametersToIncludePoint(double *min_t, double *max_t,
michael@0 434 CGPoint *start,
michael@0 435 double dx, double dy,
michael@0 436 double x, double y)
michael@0 437 {
michael@0 438 MOZ_ASSERT(IsFinite(x) && IsFinite(y));
michael@0 439
michael@0 440 /**
michael@0 441 * Compute a parameter t such that a line perpendicular to the (dx,dy)
michael@0 442 * vector, passing through (start->x + dx*t, start->y + dy*t), also
michael@0 443 * passes through (x,y).
michael@0 444 *
michael@0 445 * Let px = x - start->x, py = y - start->y.
michael@0 446 * t is given by
michael@0 447 * (px - dx*t)*dx + (py - dy*t)*dy = 0
michael@0 448 *
michael@0 449 * Solving for t we get
michael@0 450 * numerator = dx*px + dy*py
michael@0 451 * denominator = dx^2 + dy^2
michael@0 452 * t = numerator/denominator
michael@0 453 *
michael@0 454 * In CalculateRepeatingGradientParams we know the length of (dx,dy)
michael@0 455 * is not zero. (This is checked in DrawLinearRepeatingGradient.)
michael@0 456 */
michael@0 457 double px = x - start->x;
michael@0 458 double py = y - start->y;
michael@0 459 double numerator = dx * px + dy * py;
michael@0 460 double denominator = dx * dx + dy * dy;
michael@0 461 double t = numerator / denominator;
michael@0 462
michael@0 463 if (*min_t > t) {
michael@0 464 *min_t = t;
michael@0 465 }
michael@0 466 if (*max_t < t) {
michael@0 467 *max_t = t;
michael@0 468 }
michael@0 469 }
michael@0 470
michael@0 471 /**
michael@0 472 * Repeat the gradient line such that lines extended perpendicular to the
michael@0 473 * gradient line at both start and end would completely enclose the drawing
michael@0 474 * extents.
michael@0 475 */
michael@0 476 static void
michael@0 477 CalculateRepeatingGradientParams(CGPoint *aStart, CGPoint *aEnd,
michael@0 478 CGRect aExtents, int *aRepeatCount)
michael@0 479 {
michael@0 480 double t_min = INFINITY;
michael@0 481 double t_max = -INFINITY;
michael@0 482 double dx = aEnd->x - aStart->x;
michael@0 483 double dy = aEnd->y - aStart->y;
michael@0 484
michael@0 485 double bounds_x1 = aExtents.origin.x;
michael@0 486 double bounds_y1 = aExtents.origin.y;
michael@0 487 double bounds_x2 = aExtents.origin.x + aExtents.size.width;
michael@0 488 double bounds_y2 = aExtents.origin.y + aExtents.size.height;
michael@0 489
michael@0 490 UpdateLinearParametersToIncludePoint(&t_min, &t_max, aStart, dx, dy,
michael@0 491 bounds_x1, bounds_y1);
michael@0 492 UpdateLinearParametersToIncludePoint(&t_min, &t_max, aStart, dx, dy,
michael@0 493 bounds_x2, bounds_y1);
michael@0 494 UpdateLinearParametersToIncludePoint(&t_min, &t_max, aStart, dx, dy,
michael@0 495 bounds_x2, bounds_y2);
michael@0 496 UpdateLinearParametersToIncludePoint(&t_min, &t_max, aStart, dx, dy,
michael@0 497 bounds_x1, bounds_y2);
michael@0 498
michael@0 499 MOZ_ASSERT(!isinf(t_min) && !isinf(t_max),
michael@0 500 "The first call to UpdateLinearParametersToIncludePoint should have made t_min and t_max non-infinite.");
michael@0 501
michael@0 502 // Move t_min and t_max to the nearest usable integer to try to avoid
michael@0 503 // subtle variations due to numerical instability, especially accidentally
michael@0 504 // cutting off a pixel. Extending the gradient repetitions is always safe.
michael@0 505 t_min = floor (t_min);
michael@0 506 t_max = ceil (t_max);
michael@0 507 aEnd->x = aStart->x + dx * t_max;
michael@0 508 aEnd->y = aStart->y + dy * t_max;
michael@0 509 aStart->x = aStart->x + dx * t_min;
michael@0 510 aStart->y = aStart->y + dy * t_min;
michael@0 511
michael@0 512 *aRepeatCount = t_max - t_min;
michael@0 513 }
michael@0 514
michael@0 515 static void
michael@0 516 DrawLinearRepeatingGradient(CGContextRef cg, const LinearGradientPattern &aPattern, const CGRect &aExtents)
michael@0 517 {
michael@0 518 GradientStopsCG *stops = static_cast<GradientStopsCG*>(aPattern.mStops.get());
michael@0 519 CGPoint startPoint = { aPattern.mBegin.x, aPattern.mBegin.y };
michael@0 520 CGPoint endPoint = { aPattern.mEnd.x, aPattern.mEnd.y };
michael@0 521
michael@0 522 int repeatCount = 1;
michael@0 523 // if we don't have a line then we can't extend it
michael@0 524 if (aPattern.mEnd.x != aPattern.mBegin.x ||
michael@0 525 aPattern.mEnd.y != aPattern.mBegin.y) {
michael@0 526 CalculateRepeatingGradientParams(&startPoint, &endPoint, aExtents,
michael@0 527 &repeatCount);
michael@0 528 }
michael@0 529
michael@0 530 double scale = 1./repeatCount;
michael@0 531
michael@0 532 std::vector<CGFloat> colors;
michael@0 533 std::vector<CGFloat> offsets;
michael@0 534 colors.reserve(stops->mStops.size()*repeatCount*4);
michael@0 535 offsets.reserve(stops->mStops.size()*repeatCount);
michael@0 536
michael@0 537 for (int j = 0; j < repeatCount; j++) {
michael@0 538 for (uint32_t i = 0; i < stops->mStops.size(); i++) {
michael@0 539 colors.push_back(stops->mStops[i].color.r);
michael@0 540 colors.push_back(stops->mStops[i].color.g);
michael@0 541 colors.push_back(stops->mStops[i].color.b);
michael@0 542 colors.push_back(stops->mStops[i].color.a);
michael@0 543
michael@0 544 offsets.push_back((stops->mStops[i].offset + j)*scale);
michael@0 545 }
michael@0 546 }
michael@0 547
michael@0 548 CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB();
michael@0 549 CGGradientRef gradient = CGGradientCreateWithColorComponents(colorSpace,
michael@0 550 &colors.front(),
michael@0 551 &offsets.front(),
michael@0 552 repeatCount*stops->mStops.size());
michael@0 553 CGColorSpaceRelease(colorSpace);
michael@0 554
michael@0 555 CGContextDrawLinearGradient(cg, gradient, startPoint, endPoint,
michael@0 556 kCGGradientDrawsBeforeStartLocation | kCGGradientDrawsAfterEndLocation);
michael@0 557 CGGradientRelease(gradient);
michael@0 558 }
michael@0 559
michael@0 560 static CGPoint CGRectTopLeft(CGRect a)
michael@0 561 { return a.origin; }
michael@0 562 static CGPoint CGRectBottomLeft(CGRect a)
michael@0 563 { return CGPointMake(a.origin.x, a.origin.y + a.size.height); }
michael@0 564 static CGPoint CGRectTopRight(CGRect a)
michael@0 565 { return CGPointMake(a.origin.x + a.size.width, a.origin.y); }
michael@0 566 static CGPoint CGRectBottomRight(CGRect a)
michael@0 567 { return CGPointMake(a.origin.x + a.size.width, a.origin.y + a.size.height); }
michael@0 568
michael@0 569 static CGFloat
michael@0 570 CGPointDistance(CGPoint a, CGPoint b)
michael@0 571 {
michael@0 572 return hypot(a.x-b.x, a.y-b.y);
michael@0 573 }
michael@0 574
michael@0 575 static void
michael@0 576 DrawRadialRepeatingGradient(CGContextRef cg, const RadialGradientPattern &aPattern, const CGRect &aExtents)
michael@0 577 {
michael@0 578 GradientStopsCG *stops = static_cast<GradientStopsCG*>(aPattern.mStops.get());
michael@0 579 CGPoint startCenter = { aPattern.mCenter1.x, aPattern.mCenter1.y };
michael@0 580 CGFloat startRadius = aPattern.mRadius1;
michael@0 581 CGPoint endCenter = { aPattern.mCenter2.x, aPattern.mCenter2.y };
michael@0 582 CGFloat endRadius = aPattern.mRadius2;
michael@0 583
michael@0 584 // find the maximum distance from endCenter to a corner of aExtents
michael@0 585 CGFloat minimumEndRadius = endRadius;
michael@0 586 minimumEndRadius = max(minimumEndRadius, CGPointDistance(endCenter, CGRectTopLeft(aExtents)));
michael@0 587 minimumEndRadius = max(minimumEndRadius, CGPointDistance(endCenter, CGRectBottomLeft(aExtents)));
michael@0 588 minimumEndRadius = max(minimumEndRadius, CGPointDistance(endCenter, CGRectTopRight(aExtents)));
michael@0 589 minimumEndRadius = max(minimumEndRadius, CGPointDistance(endCenter, CGRectBottomRight(aExtents)));
michael@0 590
michael@0 591 CGFloat length = endRadius - startRadius;
michael@0 592 int repeatCount = 1;
michael@0 593 while (endRadius < minimumEndRadius) {
michael@0 594 endRadius += length;
michael@0 595 repeatCount++;
michael@0 596 }
michael@0 597
michael@0 598 while (startRadius-length >= 0) {
michael@0 599 startRadius -= length;
michael@0 600 repeatCount++;
michael@0 601 }
michael@0 602
michael@0 603 double scale = 1./repeatCount;
michael@0 604
michael@0 605 std::vector<CGFloat> colors;
michael@0 606 std::vector<CGFloat> offsets;
michael@0 607 colors.reserve(stops->mStops.size()*repeatCount*4);
michael@0 608 offsets.reserve(stops->mStops.size()*repeatCount);
michael@0 609 for (int j = 0; j < repeatCount; j++) {
michael@0 610 for (uint32_t i = 0; i < stops->mStops.size(); i++) {
michael@0 611 colors.push_back(stops->mStops[i].color.r);
michael@0 612 colors.push_back(stops->mStops[i].color.g);
michael@0 613 colors.push_back(stops->mStops[i].color.b);
michael@0 614 colors.push_back(stops->mStops[i].color.a);
michael@0 615
michael@0 616 offsets.push_back((stops->mStops[i].offset + j)*scale);
michael@0 617 }
michael@0 618 }
michael@0 619
michael@0 620 CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB();
michael@0 621 CGGradientRef gradient = CGGradientCreateWithColorComponents(colorSpace,
michael@0 622 &colors.front(),
michael@0 623 &offsets.front(),
michael@0 624 repeatCount*stops->mStops.size());
michael@0 625 CGColorSpaceRelease(colorSpace);
michael@0 626
michael@0 627 //XXX: are there degenerate radial gradients that we should avoid drawing?
michael@0 628 CGContextDrawRadialGradient(cg, gradient, startCenter, startRadius, endCenter, endRadius,
michael@0 629 kCGGradientDrawsBeforeStartLocation | kCGGradientDrawsAfterEndLocation);
michael@0 630 CGGradientRelease(gradient);
michael@0 631 }
michael@0 632
michael@0 633 static void
michael@0 634 DrawGradient(CGContextRef cg, const Pattern &aPattern, const CGRect &aExtents)
michael@0 635 {
michael@0 636 if (CGRectIsEmpty(aExtents)) {
michael@0 637 return;
michael@0 638 }
michael@0 639
michael@0 640 if (aPattern.GetType() == PatternType::LINEAR_GRADIENT) {
michael@0 641 const LinearGradientPattern& pat = static_cast<const LinearGradientPattern&>(aPattern);
michael@0 642 GradientStopsCG *stops = static_cast<GradientStopsCG*>(pat.mStops.get());
michael@0 643 CGContextConcatCTM(cg, GfxMatrixToCGAffineTransform(pat.mMatrix));
michael@0 644 if (stops->mExtend == ExtendMode::CLAMP) {
michael@0 645
michael@0 646 // XXX: we should take the m out of the properties of LinearGradientPatterns
michael@0 647 CGPoint startPoint = { pat.mBegin.x, pat.mBegin.y };
michael@0 648 CGPoint endPoint = { pat.mEnd.x, pat.mEnd.y };
michael@0 649
michael@0 650 // Canvas spec states that we should avoid drawing degenerate gradients (XXX: should this be in common code?)
michael@0 651 //if (startPoint.x == endPoint.x && startPoint.y == endPoint.y)
michael@0 652 // return;
michael@0 653
michael@0 654 CGContextDrawLinearGradient(cg, stops->mGradient, startPoint, endPoint,
michael@0 655 kCGGradientDrawsBeforeStartLocation | kCGGradientDrawsAfterEndLocation);
michael@0 656 } else if (stops->mExtend == ExtendMode::REPEAT) {
michael@0 657 DrawLinearRepeatingGradient(cg, pat, aExtents);
michael@0 658 }
michael@0 659 } else if (aPattern.GetType() == PatternType::RADIAL_GRADIENT) {
michael@0 660 const RadialGradientPattern& pat = static_cast<const RadialGradientPattern&>(aPattern);
michael@0 661 CGContextConcatCTM(cg, GfxMatrixToCGAffineTransform(pat.mMatrix));
michael@0 662 GradientStopsCG *stops = static_cast<GradientStopsCG*>(pat.mStops.get());
michael@0 663 if (stops->mExtend == ExtendMode::CLAMP) {
michael@0 664
michael@0 665 // XXX: we should take the m out of the properties of RadialGradientPatterns
michael@0 666 CGPoint startCenter = { pat.mCenter1.x, pat.mCenter1.y };
michael@0 667 CGFloat startRadius = pat.mRadius1;
michael@0 668 CGPoint endCenter = { pat.mCenter2.x, pat.mCenter2.y };
michael@0 669 CGFloat endRadius = pat.mRadius2;
michael@0 670
michael@0 671 //XXX: are there degenerate radial gradients that we should avoid drawing?
michael@0 672 CGContextDrawRadialGradient(cg, stops->mGradient, startCenter, startRadius, endCenter, endRadius,
michael@0 673 kCGGradientDrawsBeforeStartLocation | kCGGradientDrawsAfterEndLocation);
michael@0 674 } else if (stops->mExtend == ExtendMode::REPEAT) {
michael@0 675 DrawRadialRepeatingGradient(cg, pat, aExtents);
michael@0 676 }
michael@0 677 } else {
michael@0 678 assert(0);
michael@0 679 }
michael@0 680
michael@0 681 }
michael@0 682
michael@0 683 static void
michael@0 684 drawPattern(void *info, CGContextRef context)
michael@0 685 {
michael@0 686 CGImageRef image = static_cast<CGImageRef>(info);
michael@0 687 CGRect rect = {{0, 0},
michael@0 688 {static_cast<CGFloat>(CGImageGetWidth(image)),
michael@0 689 static_cast<CGFloat>(CGImageGetHeight(image))}};
michael@0 690 CGContextDrawImage(context, rect, image);
michael@0 691 }
michael@0 692
michael@0 693 static void
michael@0 694 releaseInfo(void *info)
michael@0 695 {
michael@0 696 CGImageRef image = static_cast<CGImageRef>(info);
michael@0 697 CGImageRelease(image);
michael@0 698 }
michael@0 699
michael@0 700 CGPatternCallbacks patternCallbacks = {
michael@0 701 0,
michael@0 702 drawPattern,
michael@0 703 releaseInfo
michael@0 704 };
michael@0 705
michael@0 706 static bool
michael@0 707 isGradient(const Pattern &aPattern)
michael@0 708 {
michael@0 709 return aPattern.GetType() == PatternType::LINEAR_GRADIENT || aPattern.GetType() == PatternType::RADIAL_GRADIENT;
michael@0 710 }
michael@0 711
michael@0 712 /* CoreGraphics patterns ignore the userspace transform so
michael@0 713 * we need to multiply it in */
michael@0 714 static CGPatternRef
michael@0 715 CreateCGPattern(const Pattern &aPattern, CGAffineTransform aUserSpace)
michael@0 716 {
michael@0 717 const SurfacePattern& pat = static_cast<const SurfacePattern&>(aPattern);
michael@0 718 // XXX: is .get correct here?
michael@0 719 CGImageRef image = GetRetainedImageFromSourceSurface(pat.mSurface.get());
michael@0 720 CGFloat xStep, yStep;
michael@0 721 switch (pat.mExtendMode) {
michael@0 722 case ExtendMode::CLAMP:
michael@0 723 // The 1 << 22 comes from Webkit see Pattern::createPlatformPattern() in PatternCG.cpp for more info
michael@0 724 xStep = static_cast<CGFloat>(1 << 22);
michael@0 725 yStep = static_cast<CGFloat>(1 << 22);
michael@0 726 break;
michael@0 727 case ExtendMode::REFLECT:
michael@0 728 assert(0);
michael@0 729 case ExtendMode::REPEAT:
michael@0 730 xStep = static_cast<CGFloat>(CGImageGetWidth(image));
michael@0 731 yStep = static_cast<CGFloat>(CGImageGetHeight(image));
michael@0 732 // webkit uses wkCGPatternCreateWithImageAndTransform a wrapper around CGPatternCreateWithImage2
michael@0 733 // this is done to avoid pixel-cracking along pattern boundaries
michael@0 734 // (see https://bugs.webkit.org/show_bug.cgi?id=53055)
michael@0 735 // typedef enum {
michael@0 736 // wkPatternTilingNoDistortion,
michael@0 737 // wkPatternTilingConstantSpacingMinimalDistortion,
michael@0 738 // wkPatternTilingConstantSpacing
michael@0 739 // } wkPatternTiling;
michael@0 740 // extern CGPatternRef (*wkCGPatternCreateWithImageAndTransform)(CGImageRef, CGAffineTransform, int);
michael@0 741 }
michael@0 742
michael@0 743 //XXX: We should be using CGContextDrawTiledImage when we can. Even though it
michael@0 744 // creates a pattern, it seems to go down a faster path than using a delegate
michael@0 745 // like we do below
michael@0 746 CGRect bounds = {
michael@0 747 {0, 0,},
michael@0 748 {static_cast<CGFloat>(CGImageGetWidth(image)), static_cast<CGFloat>(CGImageGetHeight(image))}
michael@0 749 };
michael@0 750 CGAffineTransform transform =
michael@0 751 CGAffineTransformConcat(CGAffineTransformConcat(CGAffineTransformMakeScale(1,
michael@0 752 -1),
michael@0 753 GfxMatrixToCGAffineTransform(pat.mMatrix)),
michael@0 754 aUserSpace);
michael@0 755 transform = CGAffineTransformTranslate(transform, 0, -static_cast<float>(CGImageGetHeight(image)));
michael@0 756 return CGPatternCreate(image, bounds, transform, xStep, yStep, kCGPatternTilingConstantSpacing,
michael@0 757 true, &patternCallbacks);
michael@0 758 }
michael@0 759
michael@0 760 static void
michael@0 761 SetFillFromPattern(CGContextRef cg, CGColorSpaceRef aColorSpace, const Pattern &aPattern)
michael@0 762 {
michael@0 763 assert(!isGradient(aPattern));
michael@0 764 if (aPattern.GetType() == PatternType::COLOR) {
michael@0 765
michael@0 766 const Color& color = static_cast<const ColorPattern&>(aPattern).mColor;
michael@0 767 //XXX: we should cache colors
michael@0 768 CGColorRef cgcolor = ColorToCGColor(aColorSpace, color);
michael@0 769 CGContextSetFillColorWithColor(cg, cgcolor);
michael@0 770 CGColorRelease(cgcolor);
michael@0 771 } else if (aPattern.GetType() == PatternType::SURFACE) {
michael@0 772
michael@0 773 CGColorSpaceRef patternSpace;
michael@0 774 patternSpace = CGColorSpaceCreatePattern (nullptr);
michael@0 775 CGContextSetFillColorSpace(cg, patternSpace);
michael@0 776 CGColorSpaceRelease(patternSpace);
michael@0 777
michael@0 778 CGPatternRef pattern = CreateCGPattern(aPattern, CGContextGetCTM(cg));
michael@0 779 const SurfacePattern& pat = static_cast<const SurfacePattern&>(aPattern);
michael@0 780 CGContextSetInterpolationQuality(cg, InterpolationQualityFromFilter(pat.mFilter));
michael@0 781 CGFloat alpha = 1.;
michael@0 782 CGContextSetFillPattern(cg, pattern, &alpha);
michael@0 783 CGPatternRelease(pattern);
michael@0 784 }
michael@0 785 }
michael@0 786
michael@0 787 static void
michael@0 788 SetStrokeFromPattern(CGContextRef cg, CGColorSpaceRef aColorSpace, const Pattern &aPattern)
michael@0 789 {
michael@0 790 assert(!isGradient(aPattern));
michael@0 791 if (aPattern.GetType() == PatternType::COLOR) {
michael@0 792 const Color& color = static_cast<const ColorPattern&>(aPattern).mColor;
michael@0 793 //XXX: we should cache colors
michael@0 794 CGColorRef cgcolor = ColorToCGColor(aColorSpace, color);
michael@0 795 CGContextSetStrokeColorWithColor(cg, cgcolor);
michael@0 796 CGColorRelease(cgcolor);
michael@0 797 } else if (aPattern.GetType() == PatternType::SURFACE) {
michael@0 798 CGColorSpaceRef patternSpace;
michael@0 799 patternSpace = CGColorSpaceCreatePattern (nullptr);
michael@0 800 CGContextSetStrokeColorSpace(cg, patternSpace);
michael@0 801 CGColorSpaceRelease(patternSpace);
michael@0 802
michael@0 803 CGPatternRef pattern = CreateCGPattern(aPattern, CGContextGetCTM(cg));
michael@0 804 const SurfacePattern& pat = static_cast<const SurfacePattern&>(aPattern);
michael@0 805 CGContextSetInterpolationQuality(cg, InterpolationQualityFromFilter(pat.mFilter));
michael@0 806 CGFloat alpha = 1.;
michael@0 807 CGContextSetStrokePattern(cg, pattern, &alpha);
michael@0 808 CGPatternRelease(pattern);
michael@0 809 }
michael@0 810
michael@0 811 }
michael@0 812
michael@0 813 void
michael@0 814 DrawTargetCG::MaskSurface(const Pattern &aSource,
michael@0 815 SourceSurface *aMask,
michael@0 816 Point aOffset,
michael@0 817 const DrawOptions &aDrawOptions)
michael@0 818 {
michael@0 819 MarkChanged();
michael@0 820
michael@0 821 CGContextSaveGState(mCg);
michael@0 822
michael@0 823 CGContextSetBlendMode(mCg, ToBlendMode(aDrawOptions.mCompositionOp));
michael@0 824 UnboundnessFixer fixer;
michael@0 825 CGContextRef cg = fixer.Check(mCg, aDrawOptions.mCompositionOp);
michael@0 826 CGContextSetAlpha(cg, aDrawOptions.mAlpha);
michael@0 827 CGContextSetShouldAntialias(cg, aDrawOptions.mAntialiasMode != AntialiasMode::NONE);
michael@0 828
michael@0 829 CGContextConcatCTM(cg, GfxMatrixToCGAffineTransform(mTransform));
michael@0 830 CGImageRef image = GetRetainedImageFromSourceSurface(aMask);
michael@0 831
michael@0 832 // use a negative-y so that the mask image draws right ways up
michael@0 833 CGContextScaleCTM(cg, 1, -1);
michael@0 834
michael@0 835 IntSize size = aMask->GetSize();
michael@0 836
michael@0 837 CGContextClipToMask(cg, CGRectMake(aOffset.x, -(aOffset.y + size.height), size.width, size.height), image);
michael@0 838
michael@0 839 CGContextScaleCTM(cg, 1, -1);
michael@0 840 if (isGradient(aSource)) {
michael@0 841 // we shouldn't need to clip to an additional rectangle
michael@0 842 // as the cliping to the mask should be sufficient.
michael@0 843 DrawGradient(cg, aSource, CGRectMake(aOffset.x, aOffset.y, size.width, size.height));
michael@0 844 } else {
michael@0 845 SetFillFromPattern(cg, mColorSpace, aSource);
michael@0 846 CGContextFillRect(cg, CGRectMake(aOffset.x, aOffset.y, size.width, size.height));
michael@0 847 }
michael@0 848
michael@0 849 CGImageRelease(image);
michael@0 850
michael@0 851 fixer.Fix(mCg);
michael@0 852
michael@0 853 CGContextRestoreGState(mCg);
michael@0 854 }
michael@0 855
michael@0 856
michael@0 857
michael@0 858 void
michael@0 859 DrawTargetCG::FillRect(const Rect &aRect,
michael@0 860 const Pattern &aPattern,
michael@0 861 const DrawOptions &aDrawOptions)
michael@0 862 {
michael@0 863 MarkChanged();
michael@0 864
michael@0 865 CGContextSaveGState(mCg);
michael@0 866
michael@0 867 UnboundnessFixer fixer;
michael@0 868 CGContextRef cg = fixer.Check(mCg, aDrawOptions.mCompositionOp, &aRect);
michael@0 869 CGContextSetAlpha(mCg, aDrawOptions.mAlpha);
michael@0 870 CGContextSetShouldAntialias(cg, aDrawOptions.mAntialiasMode != AntialiasMode::NONE);
michael@0 871 CGContextSetBlendMode(mCg, ToBlendMode(aDrawOptions.mCompositionOp));
michael@0 872
michael@0 873 CGContextConcatCTM(cg, GfxMatrixToCGAffineTransform(mTransform));
michael@0 874
michael@0 875 if (isGradient(aPattern)) {
michael@0 876 CGContextClipToRect(cg, RectToCGRect(aRect));
michael@0 877 CGRect clipBounds = CGContextGetClipBoundingBox(cg);
michael@0 878 DrawGradient(cg, aPattern, clipBounds);
michael@0 879 } else {
michael@0 880 if (aPattern.GetType() == PatternType::SURFACE && static_cast<const SurfacePattern&>(aPattern).mExtendMode != ExtendMode::REPEAT) {
michael@0 881 // SetFillFromPattern can handle this case but using CGContextDrawImage
michael@0 882 // should give us better performance, better output, smaller PDF and
michael@0 883 // matches what cairo does.
michael@0 884 const SurfacePattern& pat = static_cast<const SurfacePattern&>(aPattern);
michael@0 885 CGImageRef image = GetRetainedImageFromSourceSurface(pat.mSurface.get());
michael@0 886 CGContextClipToRect(cg, RectToCGRect(aRect));
michael@0 887 CGContextConcatCTM(cg, GfxMatrixToCGAffineTransform(pat.mMatrix));
michael@0 888 CGContextTranslateCTM(cg, 0, CGImageGetHeight(image));
michael@0 889 CGContextScaleCTM(cg, 1, -1);
michael@0 890
michael@0 891 CGRect imageRect = CGRectMake(0, 0, CGImageGetWidth(image), CGImageGetHeight(image));
michael@0 892
michael@0 893 CGContextSetInterpolationQuality(cg, InterpolationQualityFromFilter(pat.mFilter));
michael@0 894
michael@0 895 CGContextDrawImage(cg, imageRect, image);
michael@0 896 CGImageRelease(image);
michael@0 897 } else {
michael@0 898 SetFillFromPattern(cg, mColorSpace, aPattern);
michael@0 899 CGContextFillRect(cg, RectToCGRect(aRect));
michael@0 900 }
michael@0 901 }
michael@0 902
michael@0 903 fixer.Fix(mCg);
michael@0 904 CGContextRestoreGState(mCg);
michael@0 905 }
michael@0 906
michael@0 907 void
michael@0 908 DrawTargetCG::StrokeLine(const Point &p1, const Point &p2, const Pattern &aPattern, const StrokeOptions &aStrokeOptions, const DrawOptions &aDrawOptions)
michael@0 909 {
michael@0 910 if (!std::isfinite(p1.x) ||
michael@0 911 !std::isfinite(p1.y) ||
michael@0 912 !std::isfinite(p2.x) ||
michael@0 913 !std::isfinite(p2.y)) {
michael@0 914 return;
michael@0 915 }
michael@0 916
michael@0 917 MarkChanged();
michael@0 918
michael@0 919 CGContextSaveGState(mCg);
michael@0 920
michael@0 921 UnboundnessFixer fixer;
michael@0 922 CGContextRef cg = fixer.Check(mCg, aDrawOptions.mCompositionOp);
michael@0 923 CGContextSetAlpha(mCg, aDrawOptions.mAlpha);
michael@0 924 CGContextSetShouldAntialias(cg, aDrawOptions.mAntialiasMode != AntialiasMode::NONE);
michael@0 925 CGContextSetBlendMode(mCg, ToBlendMode(aDrawOptions.mCompositionOp));
michael@0 926
michael@0 927 CGContextConcatCTM(cg, GfxMatrixToCGAffineTransform(mTransform));
michael@0 928
michael@0 929 CGContextBeginPath(cg);
michael@0 930 CGContextMoveToPoint(cg, p1.x, p1.y);
michael@0 931 CGContextAddLineToPoint(cg, p2.x, p2.y);
michael@0 932
michael@0 933 SetStrokeOptions(cg, aStrokeOptions);
michael@0 934
michael@0 935 if (isGradient(aPattern)) {
michael@0 936 CGContextReplacePathWithStrokedPath(cg);
michael@0 937 CGRect extents = CGContextGetPathBoundingBox(cg);
michael@0 938 //XXX: should we use EO clip here?
michael@0 939 CGContextClip(cg);
michael@0 940 DrawGradient(cg, aPattern, extents);
michael@0 941 } else {
michael@0 942 SetStrokeFromPattern(cg, mColorSpace, aPattern);
michael@0 943 CGContextStrokePath(cg);
michael@0 944 }
michael@0 945
michael@0 946 fixer.Fix(mCg);
michael@0 947 CGContextRestoreGState(mCg);
michael@0 948 }
michael@0 949
michael@0 950 void
michael@0 951 DrawTargetCG::StrokeRect(const Rect &aRect,
michael@0 952 const Pattern &aPattern,
michael@0 953 const StrokeOptions &aStrokeOptions,
michael@0 954 const DrawOptions &aDrawOptions)
michael@0 955 {
michael@0 956 if (!aRect.IsFinite()) {
michael@0 957 return;
michael@0 958 }
michael@0 959
michael@0 960 MarkChanged();
michael@0 961
michael@0 962 CGContextSaveGState(mCg);
michael@0 963
michael@0 964 UnboundnessFixer fixer;
michael@0 965 CGContextRef cg = fixer.Check(mCg, aDrawOptions.mCompositionOp);
michael@0 966 CGContextSetAlpha(mCg, aDrawOptions.mAlpha);
michael@0 967 CGContextSetShouldAntialias(cg, aDrawOptions.mAntialiasMode != AntialiasMode::NONE);
michael@0 968 CGContextSetBlendMode(mCg, ToBlendMode(aDrawOptions.mCompositionOp));
michael@0 969
michael@0 970 CGContextConcatCTM(cg, GfxMatrixToCGAffineTransform(mTransform));
michael@0 971
michael@0 972 SetStrokeOptions(cg, aStrokeOptions);
michael@0 973
michael@0 974 if (isGradient(aPattern)) {
michael@0 975 // There's no CGContextClipStrokeRect so we do it by hand
michael@0 976 CGContextBeginPath(cg);
michael@0 977 CGContextAddRect(cg, RectToCGRect(aRect));
michael@0 978 CGContextReplacePathWithStrokedPath(cg);
michael@0 979 CGRect extents = CGContextGetPathBoundingBox(cg);
michael@0 980 //XXX: should we use EO clip here?
michael@0 981 CGContextClip(cg);
michael@0 982 DrawGradient(cg, aPattern, extents);
michael@0 983 } else {
michael@0 984 SetStrokeFromPattern(cg, mColorSpace, aPattern);
michael@0 985 CGContextStrokeRect(cg, RectToCGRect(aRect));
michael@0 986 }
michael@0 987
michael@0 988 fixer.Fix(mCg);
michael@0 989 CGContextRestoreGState(mCg);
michael@0 990 }
michael@0 991
michael@0 992
michael@0 993 void
michael@0 994 DrawTargetCG::ClearRect(const Rect &aRect)
michael@0 995 {
michael@0 996 MarkChanged();
michael@0 997
michael@0 998 CGContextSaveGState(mCg);
michael@0 999 CGContextConcatCTM(mCg, GfxMatrixToCGAffineTransform(mTransform));
michael@0 1000
michael@0 1001 CGContextClearRect(mCg, RectToCGRect(aRect));
michael@0 1002
michael@0 1003 CGContextRestoreGState(mCg);
michael@0 1004 }
michael@0 1005
michael@0 1006 void
michael@0 1007 DrawTargetCG::Stroke(const Path *aPath, const Pattern &aPattern, const StrokeOptions &aStrokeOptions, const DrawOptions &aDrawOptions)
michael@0 1008 {
michael@0 1009 if (!aPath->GetBounds().IsFinite()) {
michael@0 1010 return;
michael@0 1011 }
michael@0 1012
michael@0 1013 MarkChanged();
michael@0 1014
michael@0 1015 CGContextSaveGState(mCg);
michael@0 1016
michael@0 1017 UnboundnessFixer fixer;
michael@0 1018 CGContextRef cg = fixer.Check(mCg, aDrawOptions.mCompositionOp);
michael@0 1019 CGContextSetAlpha(mCg, aDrawOptions.mAlpha);
michael@0 1020 CGContextSetShouldAntialias(cg, aDrawOptions.mAntialiasMode != AntialiasMode::NONE);
michael@0 1021 CGContextSetBlendMode(mCg, ToBlendMode(aDrawOptions.mCompositionOp));
michael@0 1022
michael@0 1023 CGContextConcatCTM(cg, GfxMatrixToCGAffineTransform(mTransform));
michael@0 1024
michael@0 1025
michael@0 1026 CGContextBeginPath(cg);
michael@0 1027
michael@0 1028 assert(aPath->GetBackendType() == BackendType::COREGRAPHICS);
michael@0 1029 const PathCG *cgPath = static_cast<const PathCG*>(aPath);
michael@0 1030 CGContextAddPath(cg, cgPath->GetPath());
michael@0 1031
michael@0 1032 SetStrokeOptions(cg, aStrokeOptions);
michael@0 1033
michael@0 1034 if (isGradient(aPattern)) {
michael@0 1035 CGContextReplacePathWithStrokedPath(cg);
michael@0 1036 CGRect extents = CGContextGetPathBoundingBox(cg);
michael@0 1037 //XXX: should we use EO clip here?
michael@0 1038 CGContextClip(cg);
michael@0 1039 DrawGradient(cg, aPattern, extents);
michael@0 1040 } else {
michael@0 1041 // XXX: we could put fill mode into the path fill rule if we wanted
michael@0 1042
michael@0 1043 SetStrokeFromPattern(cg, mColorSpace, aPattern);
michael@0 1044 CGContextStrokePath(cg);
michael@0 1045 }
michael@0 1046
michael@0 1047 fixer.Fix(mCg);
michael@0 1048 CGContextRestoreGState(mCg);
michael@0 1049 }
michael@0 1050
michael@0 1051 void
michael@0 1052 DrawTargetCG::Fill(const Path *aPath, const Pattern &aPattern, const DrawOptions &aDrawOptions)
michael@0 1053 {
michael@0 1054 MarkChanged();
michael@0 1055
michael@0 1056 assert(aPath->GetBackendType() == BackendType::COREGRAPHICS);
michael@0 1057
michael@0 1058 CGContextSaveGState(mCg);
michael@0 1059
michael@0 1060 CGContextSetBlendMode(mCg, ToBlendMode(aDrawOptions.mCompositionOp));
michael@0 1061 UnboundnessFixer fixer;
michael@0 1062 CGContextRef cg = fixer.Check(mCg, aDrawOptions.mCompositionOp);
michael@0 1063 CGContextSetAlpha(cg, aDrawOptions.mAlpha);
michael@0 1064 CGContextSetShouldAntialias(cg, aDrawOptions.mAntialiasMode != AntialiasMode::NONE);
michael@0 1065
michael@0 1066 CGContextConcatCTM(cg, GfxMatrixToCGAffineTransform(mTransform));
michael@0 1067
michael@0 1068 CGContextBeginPath(cg);
michael@0 1069 // XXX: we could put fill mode into the path fill rule if we wanted
michael@0 1070 const PathCG *cgPath = static_cast<const PathCG*>(aPath);
michael@0 1071
michael@0 1072 if (isGradient(aPattern)) {
michael@0 1073 // setup a clip to draw the gradient through
michael@0 1074 CGRect extents;
michael@0 1075 if (CGPathIsEmpty(cgPath->GetPath())) {
michael@0 1076 // Adding an empty path will cause us not to clip
michael@0 1077 // so clip everything explicitly
michael@0 1078 CGContextClipToRect(mCg, CGRectZero);
michael@0 1079 extents = CGRectZero;
michael@0 1080 } else {
michael@0 1081 CGContextAddPath(cg, cgPath->GetPath());
michael@0 1082 extents = CGContextGetPathBoundingBox(cg);
michael@0 1083 if (cgPath->GetFillRule() == FillRule::FILL_EVEN_ODD)
michael@0 1084 CGContextEOClip(mCg);
michael@0 1085 else
michael@0 1086 CGContextClip(mCg);
michael@0 1087 }
michael@0 1088
michael@0 1089 DrawGradient(cg, aPattern, extents);
michael@0 1090 } else {
michael@0 1091 CGContextAddPath(cg, cgPath->GetPath());
michael@0 1092
michael@0 1093 SetFillFromPattern(cg, mColorSpace, aPattern);
michael@0 1094
michael@0 1095 if (cgPath->GetFillRule() == FillRule::FILL_EVEN_ODD)
michael@0 1096 CGContextEOFillPath(cg);
michael@0 1097 else
michael@0 1098 CGContextFillPath(cg);
michael@0 1099 }
michael@0 1100
michael@0 1101 fixer.Fix(mCg);
michael@0 1102 CGContextRestoreGState(mCg);
michael@0 1103 }
michael@0 1104
michael@0 1105 CGRect ComputeGlyphsExtents(CGRect *bboxes, CGPoint *positions, CFIndex count, float scale)
michael@0 1106 {
michael@0 1107 CGFloat x1, x2, y1, y2;
michael@0 1108 if (count < 1)
michael@0 1109 return CGRectZero;
michael@0 1110
michael@0 1111 x1 = bboxes[0].origin.x + positions[0].x;
michael@0 1112 x2 = bboxes[0].origin.x + positions[0].x + scale*bboxes[0].size.width;
michael@0 1113 y1 = bboxes[0].origin.y + positions[0].y;
michael@0 1114 y2 = bboxes[0].origin.y + positions[0].y + scale*bboxes[0].size.height;
michael@0 1115
michael@0 1116 // accumulate max and minimum coordinates
michael@0 1117 for (int i = 1; i < count; i++) {
michael@0 1118 x1 = min(x1, bboxes[i].origin.x + positions[i].x);
michael@0 1119 y1 = min(y1, bboxes[i].origin.y + positions[i].y);
michael@0 1120 x2 = max(x2, bboxes[i].origin.x + positions[i].x + scale*bboxes[i].size.width);
michael@0 1121 y2 = max(y2, bboxes[i].origin.y + positions[i].y + scale*bboxes[i].size.height);
michael@0 1122 }
michael@0 1123
michael@0 1124 CGRect extents = {{x1, y1}, {x2-x1, y2-y1}};
michael@0 1125 return extents;
michael@0 1126 }
michael@0 1127
michael@0 1128
michael@0 1129 void
michael@0 1130 DrawTargetCG::FillGlyphs(ScaledFont *aFont, const GlyphBuffer &aBuffer, const Pattern &aPattern, const DrawOptions &aDrawOptions,
michael@0 1131 const GlyphRenderingOptions*)
michael@0 1132 {
michael@0 1133 MarkChanged();
michael@0 1134
michael@0 1135 assert(aBuffer.mNumGlyphs);
michael@0 1136 CGContextSaveGState(mCg);
michael@0 1137
michael@0 1138 CGContextSetBlendMode(mCg, ToBlendMode(aDrawOptions.mCompositionOp));
michael@0 1139 UnboundnessFixer fixer;
michael@0 1140 CGContextRef cg = fixer.Check(mCg, aDrawOptions.mCompositionOp);
michael@0 1141 CGContextSetAlpha(cg, aDrawOptions.mAlpha);
michael@0 1142 CGContextSetShouldAntialias(cg, aDrawOptions.mAntialiasMode != AntialiasMode::NONE);
michael@0 1143 if (aDrawOptions.mAntialiasMode != AntialiasMode::DEFAULT) {
michael@0 1144 CGContextSetShouldSmoothFonts(cg, aDrawOptions.mAntialiasMode == AntialiasMode::SUBPIXEL);
michael@0 1145 }
michael@0 1146
michael@0 1147 CGContextConcatCTM(cg, GfxMatrixToCGAffineTransform(mTransform));
michael@0 1148
michael@0 1149 ScaledFontMac* macFont = static_cast<ScaledFontMac*>(aFont);
michael@0 1150
michael@0 1151 //XXX: we should use a stack vector here when we have a class like that
michael@0 1152 std::vector<CGGlyph> glyphs;
michael@0 1153 std::vector<CGPoint> positions;
michael@0 1154 glyphs.resize(aBuffer.mNumGlyphs);
michael@0 1155 positions.resize(aBuffer.mNumGlyphs);
michael@0 1156
michael@0 1157 // Handle the flip
michael@0 1158 CGContextScaleCTM(cg, 1, -1);
michael@0 1159 // CGContextSetTextMatrix works differently with kCGTextClip && kCGTextFill
michael@0 1160 // It seems that it transforms the positions with TextFill and not with TextClip
michael@0 1161 // Therefore we'll avoid it. See also:
michael@0 1162 // http://cgit.freedesktop.org/cairo/commit/?id=9c0d761bfcdd28d52c83d74f46dd3c709ae0fa69
michael@0 1163
michael@0 1164 for (unsigned int i = 0; i < aBuffer.mNumGlyphs; i++) {
michael@0 1165 glyphs[i] = aBuffer.mGlyphs[i].mIndex;
michael@0 1166 // XXX: CGPointMake might not be inlined
michael@0 1167 positions[i] = CGPointMake(aBuffer.mGlyphs[i].mPosition.x,
michael@0 1168 -aBuffer.mGlyphs[i].mPosition.y);
michael@0 1169 }
michael@0 1170
michael@0 1171 //XXX: CGContextShowGlyphsAtPositions is 10.5+ for older versions use CGContextShowGlyphsWithAdvances
michael@0 1172 if (isGradient(aPattern)) {
michael@0 1173 CGContextSetTextDrawingMode(cg, kCGTextClip);
michael@0 1174 CGRect extents;
michael@0 1175 if (ScaledFontMac::CTFontDrawGlyphsPtr != nullptr) {
michael@0 1176 CGRect *bboxes = new CGRect[aBuffer.mNumGlyphs];
michael@0 1177 CTFontGetBoundingRectsForGlyphs(macFont->mCTFont, kCTFontDefaultOrientation,
michael@0 1178 &glyphs.front(), bboxes, aBuffer.mNumGlyphs);
michael@0 1179 extents = ComputeGlyphsExtents(bboxes, &positions.front(), aBuffer.mNumGlyphs, 1.0f);
michael@0 1180 ScaledFontMac::CTFontDrawGlyphsPtr(macFont->mCTFont, &glyphs.front(),
michael@0 1181 &positions.front(), aBuffer.mNumGlyphs, cg);
michael@0 1182 delete bboxes;
michael@0 1183 } else {
michael@0 1184 CGRect *bboxes = new CGRect[aBuffer.mNumGlyphs];
michael@0 1185 CGFontGetGlyphBBoxes(macFont->mFont, &glyphs.front(), aBuffer.mNumGlyphs, bboxes);
michael@0 1186 extents = ComputeGlyphsExtents(bboxes, &positions.front(), aBuffer.mNumGlyphs, macFont->mSize);
michael@0 1187
michael@0 1188 CGContextSetFont(cg, macFont->mFont);
michael@0 1189 CGContextSetFontSize(cg, macFont->mSize);
michael@0 1190 CGContextShowGlyphsAtPositions(cg, &glyphs.front(), &positions.front(),
michael@0 1191 aBuffer.mNumGlyphs);
michael@0 1192 delete bboxes;
michael@0 1193 }
michael@0 1194 CGContextScaleCTM(cg, 1, -1);
michael@0 1195 DrawGradient(cg, aPattern, extents);
michael@0 1196 } else {
michael@0 1197 //XXX: with CoreGraphics we can stroke text directly instead of going
michael@0 1198 // through GetPath. It would be nice to add support for using that
michael@0 1199 CGContextSetTextDrawingMode(cg, kCGTextFill);
michael@0 1200 SetFillFromPattern(cg, mColorSpace, aPattern);
michael@0 1201 if (ScaledFontMac::CTFontDrawGlyphsPtr != nullptr) {
michael@0 1202 ScaledFontMac::CTFontDrawGlyphsPtr(macFont->mCTFont, &glyphs.front(),
michael@0 1203 &positions.front(),
michael@0 1204 aBuffer.mNumGlyphs, cg);
michael@0 1205 } else {
michael@0 1206 CGContextSetFont(cg, macFont->mFont);
michael@0 1207 CGContextSetFontSize(cg, macFont->mSize);
michael@0 1208 CGContextShowGlyphsAtPositions(cg, &glyphs.front(), &positions.front(),
michael@0 1209 aBuffer.mNumGlyphs);
michael@0 1210 }
michael@0 1211 }
michael@0 1212
michael@0 1213 fixer.Fix(mCg);
michael@0 1214 CGContextRestoreGState(cg);
michael@0 1215 }
michael@0 1216
michael@0 1217 extern "C" {
michael@0 1218 void
michael@0 1219 CGContextResetClip(CGContextRef);
michael@0 1220 };
michael@0 1221
michael@0 1222 void
michael@0 1223 DrawTargetCG::CopySurface(SourceSurface *aSurface,
michael@0 1224 const IntRect& aSourceRect,
michael@0 1225 const IntPoint &aDestination)
michael@0 1226 {
michael@0 1227 MarkChanged();
michael@0 1228
michael@0 1229 if (aSurface->GetType() == SurfaceType::COREGRAPHICS_IMAGE ||
michael@0 1230 aSurface->GetType() == SurfaceType::COREGRAPHICS_CGCONTEXT ||
michael@0 1231 aSurface->GetType() == SurfaceType::DATA) {
michael@0 1232 CGImageRef image = GetRetainedImageFromSourceSurface(aSurface);
michael@0 1233
michael@0 1234 // XXX: it might be more efficient for us to do the copy directly if we have access to the bits
michael@0 1235
michael@0 1236 CGContextSaveGState(mCg);
michael@0 1237
michael@0 1238 // CopySurface ignores the clip, so we need to use private API to temporarily reset it
michael@0 1239 CGContextResetClip(mCg);
michael@0 1240 CGRect destRect = CGRectMake(aDestination.x, aDestination.y,
michael@0 1241 aSourceRect.width, aSourceRect.height);
michael@0 1242 CGContextClipToRect(mCg, destRect);
michael@0 1243
michael@0 1244 CGContextSetBlendMode(mCg, kCGBlendModeCopy);
michael@0 1245
michael@0 1246 CGContextScaleCTM(mCg, 1, -1);
michael@0 1247
michael@0 1248 CGRect flippedRect = CGRectMake(aDestination.x - aSourceRect.x, -(aDestination.y - aSourceRect.y + double(CGImageGetHeight(image))),
michael@0 1249 CGImageGetWidth(image), CGImageGetHeight(image));
michael@0 1250
michael@0 1251 // Quartz seems to copy A8 surfaces incorrectly if we don't initialize them
michael@0 1252 // to transparent first.
michael@0 1253 if (mFormat == SurfaceFormat::A8) {
michael@0 1254 CGContextClearRect(mCg, flippedRect);
michael@0 1255 }
michael@0 1256 CGContextDrawImage(mCg, flippedRect, image);
michael@0 1257
michael@0 1258 CGContextRestoreGState(mCg);
michael@0 1259 CGImageRelease(image);
michael@0 1260 }
michael@0 1261 }
michael@0 1262
michael@0 1263 void
michael@0 1264 DrawTargetCG::DrawSurfaceWithShadow(SourceSurface *aSurface, const Point &aDest, const Color &aColor, const Point &aOffset, Float aSigma, CompositionOp aOperator)
michael@0 1265 {
michael@0 1266 MarkChanged();
michael@0 1267
michael@0 1268 CGImageRef image = GetRetainedImageFromSourceSurface(aSurface);
michael@0 1269
michael@0 1270 IntSize size = aSurface->GetSize();
michael@0 1271 CGContextSaveGState(mCg);
michael@0 1272 //XXX do we need to do the fixup here?
michael@0 1273 CGContextSetBlendMode(mCg, ToBlendMode(aOperator));
michael@0 1274
michael@0 1275 CGContextScaleCTM(mCg, 1, -1);
michael@0 1276
michael@0 1277 CGRect flippedRect = CGRectMake(aDest.x, -(aDest.y + size.height),
michael@0 1278 size.width, size.height);
michael@0 1279
michael@0 1280 CGColorRef color = ColorToCGColor(mColorSpace, aColor);
michael@0 1281 CGSize offset = {aOffset.x, -aOffset.y};
michael@0 1282 // CoreGraphics needs twice sigma as it's amount of blur
michael@0 1283 CGContextSetShadowWithColor(mCg, offset, 2*aSigma, color);
michael@0 1284 CGColorRelease(color);
michael@0 1285
michael@0 1286 CGContextDrawImage(mCg, flippedRect, image);
michael@0 1287
michael@0 1288 CGImageRelease(image);
michael@0 1289 CGContextRestoreGState(mCg);
michael@0 1290
michael@0 1291 }
michael@0 1292
michael@0 1293 bool
michael@0 1294 DrawTargetCG::Init(BackendType aType,
michael@0 1295 unsigned char* aData,
michael@0 1296 const IntSize &aSize,
michael@0 1297 int32_t aStride,
michael@0 1298 SurfaceFormat aFormat)
michael@0 1299 {
michael@0 1300 // XXX: we should come up with some consistent semantics for dealing
michael@0 1301 // with zero area drawtargets
michael@0 1302 if (aSize.width <= 0 || aSize.height <= 0 ||
michael@0 1303 // 32767 is the maximum size supported by cairo
michael@0 1304 // we clamp to that to make it easier to interoperate
michael@0 1305 aSize.width > 32767 || aSize.height > 32767) {
michael@0 1306 gfxWarning() << "Failed to Init() DrawTargetCG because of bad size.";
michael@0 1307 mColorSpace = nullptr;
michael@0 1308 mCg = nullptr;
michael@0 1309 return false;
michael@0 1310 }
michael@0 1311
michael@0 1312 //XXX: handle SurfaceFormat
michael@0 1313
michael@0 1314 //XXX: we'd be better off reusing the Colorspace across draw targets
michael@0 1315 mColorSpace = CGColorSpaceCreateDeviceRGB();
michael@0 1316
michael@0 1317 if (aData == nullptr && aType != BackendType::COREGRAPHICS_ACCELERATED) {
michael@0 1318 // XXX: Currently, Init implicitly clears, that can often be a waste of time
michael@0 1319 size_t bufLen = BufferSizeFromStrideAndHeight(aStride, aSize.height);
michael@0 1320 if (bufLen == 0) {
michael@0 1321 mColorSpace = nullptr;
michael@0 1322 mCg = nullptr;
michael@0 1323 return false;
michael@0 1324 }
michael@0 1325 static_assert(sizeof(decltype(mData[0])) == 1,
michael@0 1326 "mData.Realloc() takes an object count, so its objects must be 1-byte sized if we use bufLen");
michael@0 1327 mData.Realloc(/* actually an object count */ bufLen);
michael@0 1328 aData = static_cast<unsigned char*>(mData);
michael@0 1329 memset(aData, 0, bufLen);
michael@0 1330 }
michael@0 1331
michael@0 1332 mSize = aSize;
michael@0 1333
michael@0 1334 if (aType == BackendType::COREGRAPHICS_ACCELERATED) {
michael@0 1335 RefPtr<MacIOSurface> ioSurface = MacIOSurface::CreateIOSurface(aSize.width, aSize.height);
michael@0 1336 mCg = ioSurface->CreateIOSurfaceContext();
michael@0 1337 // If we don't have the symbol for 'CreateIOSurfaceContext' mCg will be null
michael@0 1338 // and we will fallback to software below
michael@0 1339 }
michael@0 1340
michael@0 1341 mFormat = SurfaceFormat::B8G8R8A8;
michael@0 1342
michael@0 1343 if (!mCg || aType == BackendType::COREGRAPHICS) {
michael@0 1344 int bitsPerComponent = 8;
michael@0 1345
michael@0 1346 CGBitmapInfo bitinfo;
michael@0 1347 if (aFormat == SurfaceFormat::A8) {
michael@0 1348 if (mColorSpace)
michael@0 1349 CGColorSpaceRelease(mColorSpace);
michael@0 1350 mColorSpace = nullptr;
michael@0 1351 bitinfo = kCGImageAlphaOnly;
michael@0 1352 mFormat = SurfaceFormat::A8;
michael@0 1353 } else {
michael@0 1354 bitinfo = kCGBitmapByteOrder32Host;
michael@0 1355 if (aFormat == SurfaceFormat::B8G8R8X8) {
michael@0 1356 bitinfo |= kCGImageAlphaNoneSkipFirst;
michael@0 1357 mFormat = aFormat;
michael@0 1358 } else {
michael@0 1359 bitinfo |= kCGImageAlphaPremultipliedFirst;
michael@0 1360 }
michael@0 1361 }
michael@0 1362 // XXX: what should we do if this fails?
michael@0 1363 mCg = CGBitmapContextCreate (aData,
michael@0 1364 mSize.width,
michael@0 1365 mSize.height,
michael@0 1366 bitsPerComponent,
michael@0 1367 aStride,
michael@0 1368 mColorSpace,
michael@0 1369 bitinfo);
michael@0 1370 }
michael@0 1371
michael@0 1372 assert(mCg);
michael@0 1373 // CGContext's default to have the origin at the bottom left
michael@0 1374 // so flip it to the top left
michael@0 1375 CGContextTranslateCTM(mCg, 0, mSize.height);
michael@0 1376 CGContextScaleCTM(mCg, 1, -1);
michael@0 1377 // See Bug 722164 for performance details
michael@0 1378 // Medium or higher quality lead to expensive interpolation
michael@0 1379 // for canvas we want to use low quality interpolation
michael@0 1380 // to have competitive performance with other canvas
michael@0 1381 // implementation.
michael@0 1382 // XXX: Create input parameter to control interpolation and
michael@0 1383 // use the default for content.
michael@0 1384 CGContextSetInterpolationQuality(mCg, kCGInterpolationLow);
michael@0 1385
michael@0 1386
michael@0 1387 if (aType == BackendType::COREGRAPHICS_ACCELERATED) {
michael@0 1388 // The bitmap backend uses callac to clear, we can't do that without
michael@0 1389 // reading back the surface. This should trigger something equivilent
michael@0 1390 // to glClear.
michael@0 1391 ClearRect(Rect(0, 0, mSize.width, mSize.height));
michael@0 1392 }
michael@0 1393
michael@0 1394 return true;
michael@0 1395 }
michael@0 1396
michael@0 1397 void
michael@0 1398 DrawTargetCG::Flush()
michael@0 1399 {
michael@0 1400 if (GetContextType(mCg) == CG_CONTEXT_TYPE_IOSURFACE) {
michael@0 1401 CGContextFlush(mCg);
michael@0 1402 }
michael@0 1403 }
michael@0 1404
michael@0 1405 bool
michael@0 1406 DrawTargetCG::Init(CGContextRef cgContext, const IntSize &aSize)
michael@0 1407 {
michael@0 1408 // XXX: we should come up with some consistent semantics for dealing
michael@0 1409 // with zero area drawtargets
michael@0 1410 if (aSize.width == 0 || aSize.height == 0) {
michael@0 1411 mColorSpace = nullptr;
michael@0 1412 mCg = nullptr;
michael@0 1413 return false;
michael@0 1414 }
michael@0 1415
michael@0 1416 //XXX: handle SurfaceFormat
michael@0 1417
michael@0 1418 //XXX: we'd be better off reusing the Colorspace across draw targets
michael@0 1419 mColorSpace = CGColorSpaceCreateDeviceRGB();
michael@0 1420
michael@0 1421 mSize = aSize;
michael@0 1422
michael@0 1423 mCg = cgContext;
michael@0 1424 CGContextRetain(mCg);
michael@0 1425
michael@0 1426 assert(mCg);
michael@0 1427
michael@0 1428 // CGContext's default to have the origin at the bottom left.
michael@0 1429 // However, currently the only use of this function is to construct a
michael@0 1430 // DrawTargetCG around a CGContextRef from a cairo quartz surface which
michael@0 1431 // already has it's origin adjusted.
michael@0 1432 //
michael@0 1433 // CGContextTranslateCTM(mCg, 0, mSize.height);
michael@0 1434 // CGContextScaleCTM(mCg, 1, -1);
michael@0 1435
michael@0 1436 mFormat = SurfaceFormat::B8G8R8A8;
michael@0 1437 if (GetContextType(mCg) == CG_CONTEXT_TYPE_BITMAP) {
michael@0 1438 CGColorSpaceRef colorspace;
michael@0 1439 CGBitmapInfo bitinfo = CGBitmapContextGetBitmapInfo(mCg);
michael@0 1440 colorspace = CGBitmapContextGetColorSpace (mCg);
michael@0 1441 if (CGColorSpaceGetNumberOfComponents(colorspace) == 1) {
michael@0 1442 mFormat = SurfaceFormat::A8;
michael@0 1443 } else if ((bitinfo & kCGBitmapAlphaInfoMask) == kCGImageAlphaNoneSkipFirst) {
michael@0 1444 mFormat = SurfaceFormat::B8G8R8X8;
michael@0 1445 }
michael@0 1446 }
michael@0 1447
michael@0 1448 return true;
michael@0 1449 }
michael@0 1450
michael@0 1451 bool
michael@0 1452 DrawTargetCG::Init(BackendType aType, const IntSize &aSize, SurfaceFormat &aFormat)
michael@0 1453 {
michael@0 1454 int32_t stride = GetAlignedStride<16>(aSize.width * BytesPerPixel(aFormat));
michael@0 1455
michael@0 1456 // Calling Init with aData == nullptr will allocate.
michael@0 1457 return Init(aType, nullptr, aSize, stride, aFormat);
michael@0 1458 }
michael@0 1459
michael@0 1460 TemporaryRef<PathBuilder>
michael@0 1461 DrawTargetCG::CreatePathBuilder(FillRule aFillRule) const
michael@0 1462 {
michael@0 1463 RefPtr<PathBuilderCG> pb = new PathBuilderCG(aFillRule);
michael@0 1464 return pb;
michael@0 1465 }
michael@0 1466
michael@0 1467 void*
michael@0 1468 DrawTargetCG::GetNativeSurface(NativeSurfaceType aType)
michael@0 1469 {
michael@0 1470 if ((aType == NativeSurfaceType::CGCONTEXT && GetContextType(mCg) == CG_CONTEXT_TYPE_BITMAP) ||
michael@0 1471 (aType == NativeSurfaceType::CGCONTEXT_ACCELERATED && GetContextType(mCg) == CG_CONTEXT_TYPE_IOSURFACE)) {
michael@0 1472 return mCg;
michael@0 1473 } else {
michael@0 1474 return nullptr;
michael@0 1475 }
michael@0 1476 }
michael@0 1477
michael@0 1478 void
michael@0 1479 DrawTargetCG::Mask(const Pattern &aSource,
michael@0 1480 const Pattern &aMask,
michael@0 1481 const DrawOptions &aDrawOptions)
michael@0 1482 {
michael@0 1483 MarkChanged();
michael@0 1484
michael@0 1485 CGContextSaveGState(mCg);
michael@0 1486
michael@0 1487 if (isGradient(aMask)) {
michael@0 1488 assert(0);
michael@0 1489 } else {
michael@0 1490 if (aMask.GetType() == PatternType::COLOR) {
michael@0 1491 DrawOptions drawOptions(aDrawOptions);
michael@0 1492 const Color& color = static_cast<const ColorPattern&>(aMask).mColor;
michael@0 1493 drawOptions.mAlpha *= color.a;
michael@0 1494 assert(0);
michael@0 1495 // XXX: we need to get a rect that when transformed covers the entire surface
michael@0 1496 //Rect
michael@0 1497 //FillRect(rect, aSource, drawOptions);
michael@0 1498 } else if (aMask.GetType() == PatternType::SURFACE) {
michael@0 1499 const SurfacePattern& pat = static_cast<const SurfacePattern&>(aMask);
michael@0 1500 CGImageRef mask = GetRetainedImageFromSourceSurface(pat.mSurface.get());
michael@0 1501 Rect rect(0,0, CGImageGetWidth(mask), CGImageGetHeight(mask));
michael@0 1502 // XXX: probably we need to do some flipping of the image or something
michael@0 1503 CGContextClipToMask(mCg, RectToCGRect(rect), mask);
michael@0 1504 FillRect(rect, aSource, aDrawOptions);
michael@0 1505 CGImageRelease(mask);
michael@0 1506 }
michael@0 1507 }
michael@0 1508
michael@0 1509 CGContextRestoreGState(mCg);
michael@0 1510 }
michael@0 1511
michael@0 1512 void
michael@0 1513 DrawTargetCG::PushClipRect(const Rect &aRect)
michael@0 1514 {
michael@0 1515 CGContextSaveGState(mCg);
michael@0 1516
michael@0 1517 /* We go through a bit of trouble to temporarilly set the transform
michael@0 1518 * while we add the path */
michael@0 1519 CGAffineTransform previousTransform = CGContextGetCTM(mCg);
michael@0 1520 CGContextConcatCTM(mCg, GfxMatrixToCGAffineTransform(mTransform));
michael@0 1521 CGContextClipToRect(mCg, RectToCGRect(aRect));
michael@0 1522 CGContextSetCTM(mCg, previousTransform);
michael@0 1523 }
michael@0 1524
michael@0 1525
michael@0 1526 void
michael@0 1527 DrawTargetCG::PushClip(const Path *aPath)
michael@0 1528 {
michael@0 1529 CGContextSaveGState(mCg);
michael@0 1530
michael@0 1531 CGContextBeginPath(mCg);
michael@0 1532 assert(aPath->GetBackendType() == BackendType::COREGRAPHICS);
michael@0 1533
michael@0 1534 const PathCG *cgPath = static_cast<const PathCG*>(aPath);
michael@0 1535
michael@0 1536 // Weirdly, CoreGraphics clips empty paths as all shown
michael@0 1537 // but emtpy rects as all clipped. We detect this situation and
michael@0 1538 // workaround it appropriately
michael@0 1539 if (CGPathIsEmpty(cgPath->GetPath())) {
michael@0 1540 // XXX: should we return here?
michael@0 1541 CGContextClipToRect(mCg, CGRectZero);
michael@0 1542 }
michael@0 1543
michael@0 1544
michael@0 1545 /* We go through a bit of trouble to temporarilly set the transform
michael@0 1546 * while we add the path. XXX: this could be improved if we keep
michael@0 1547 * the CTM as resident state on the DrawTarget. */
michael@0 1548 CGContextSaveGState(mCg);
michael@0 1549 CGContextConcatCTM(mCg, GfxMatrixToCGAffineTransform(mTransform));
michael@0 1550 CGContextAddPath(mCg, cgPath->GetPath());
michael@0 1551 CGContextRestoreGState(mCg);
michael@0 1552
michael@0 1553 if (cgPath->GetFillRule() == FillRule::FILL_EVEN_ODD)
michael@0 1554 CGContextEOClip(mCg);
michael@0 1555 else
michael@0 1556 CGContextClip(mCg);
michael@0 1557 }
michael@0 1558
michael@0 1559 void
michael@0 1560 DrawTargetCG::PopClip()
michael@0 1561 {
michael@0 1562 CGContextRestoreGState(mCg);
michael@0 1563 }
michael@0 1564
michael@0 1565 void
michael@0 1566 DrawTargetCG::MarkChanged()
michael@0 1567 {
michael@0 1568 if (mSnapshot) {
michael@0 1569 if (mSnapshot->refCount() > 1) {
michael@0 1570 // We only need to worry about snapshots that someone else knows about
michael@0 1571 mSnapshot->DrawTargetWillChange();
michael@0 1572 }
michael@0 1573 mSnapshot = nullptr;
michael@0 1574 }
michael@0 1575 }
michael@0 1576
michael@0 1577 CGContextRef
michael@0 1578 BorrowedCGContext::BorrowCGContextFromDrawTarget(DrawTarget *aDT)
michael@0 1579 {
michael@0 1580 if (aDT->GetType() == BackendType::COREGRAPHICS || aDT->GetType() == BackendType::COREGRAPHICS_ACCELERATED) {
michael@0 1581 DrawTargetCG* cgDT = static_cast<DrawTargetCG*>(aDT);
michael@0 1582 cgDT->MarkChanged();
michael@0 1583
michael@0 1584 // swap out the context
michael@0 1585 CGContextRef cg = cgDT->mCg;
michael@0 1586 cgDT->mCg = nullptr;
michael@0 1587
michael@0 1588 // save the state to make it easier for callers to avoid mucking with things
michael@0 1589 CGContextSaveGState(cg);
michael@0 1590
michael@0 1591 CGContextConcatCTM(cg, GfxMatrixToCGAffineTransform(cgDT->mTransform));
michael@0 1592
michael@0 1593 return cg;
michael@0 1594 }
michael@0 1595 return nullptr;
michael@0 1596 }
michael@0 1597
michael@0 1598 void
michael@0 1599 BorrowedCGContext::ReturnCGContextToDrawTarget(DrawTarget *aDT, CGContextRef cg)
michael@0 1600 {
michael@0 1601 DrawTargetCG* cgDT = static_cast<DrawTargetCG*>(aDT);
michael@0 1602
michael@0 1603 CGContextRestoreGState(cg);
michael@0 1604 cgDT->mCg = cg;
michael@0 1605 }
michael@0 1606
michael@0 1607
michael@0 1608 }
michael@0 1609 }

mercurial