summaryrefslogtreecommitdiffstats
path: root/gfx/2d/DrawTargetSkia.cpp
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--gfx/2d/DrawTargetSkia.cpp2070
1 files changed, 2070 insertions, 0 deletions
diff --git a/gfx/2d/DrawTargetSkia.cpp b/gfx/2d/DrawTargetSkia.cpp
new file mode 100644
index 0000000000..0ddf9fef52
--- /dev/null
+++ b/gfx/2d/DrawTargetSkia.cpp
@@ -0,0 +1,2070 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "DrawTargetSkia.h"
+#include "SourceSurfaceSkia.h"
+#include "ScaledFontBase.h"
+#include "FilterNodeSoftware.h"
+#include "HelpersSkia.h"
+
+#include "mozilla/CheckedInt.h"
+#include "mozilla/Vector.h"
+
+#include "skia/include/core/SkBitmap.h"
+#include "skia/include/core/SkCanvas.h"
+#include "skia/include/core/SkFont.h"
+#include "skia/include/core/SkSurface.h"
+#include "skia/include/core/SkTextBlob.h"
+#include "skia/include/core/SkTypeface.h"
+#include "skia/include/effects/SkGradientShader.h"
+#include "skia/include/core/SkColorFilter.h"
+#include "skia/include/core/SkRegion.h"
+#include "skia/include/effects/SkImageFilters.h"
+#include "skia/include/private/base/SkMalloc.h"
+#include "Blur.h"
+#include "Logging.h"
+#include "Tools.h"
+#include "PathHelpers.h"
+#include "PathSkia.h"
+#include "Swizzle.h"
+#include <algorithm>
+#include <cmath>
+
+#ifdef MOZ_WIDGET_COCOA
+# include "BorrowedContext.h"
+# include <ApplicationServices/ApplicationServices.h>
+#endif
+
+#ifdef XP_WIN
+# include "ScaledFontDWrite.h"
+#endif
+
+namespace mozilla {
+
+void RefPtrTraits<SkSurface>::Release(SkSurface* aSurface) {
+ SkSafeUnref(aSurface);
+}
+
+void RefPtrTraits<SkSurface>::AddRef(SkSurface* aSurface) {
+ SkSafeRef(aSurface);
+}
+
+} // namespace mozilla
+
+namespace mozilla::gfx {
+
+class GradientStopsSkia : public GradientStops {
+ public:
+ MOZ_DECLARE_REFCOUNTED_VIRTUAL_TYPENAME(GradientStopsSkia, override)
+
+ GradientStopsSkia(const std::vector<GradientStop>& aStops, uint32_t aNumStops,
+ ExtendMode aExtendMode)
+ : mCount(aNumStops), mExtendMode(aExtendMode) {
+ if (mCount == 0) {
+ return;
+ }
+
+ // Skia gradients always require a stop at 0.0 and 1.0, insert these if
+ // we don't have them.
+ uint32_t shift = 0;
+ if (aStops[0].offset != 0) {
+ mCount++;
+ shift = 1;
+ }
+ if (aStops[aNumStops - 1].offset != 1) {
+ mCount++;
+ }
+ mColors.resize(mCount);
+ mPositions.resize(mCount);
+ if (aStops[0].offset != 0) {
+ mColors[0] = ColorToSkColor(aStops[0].color, 1.0);
+ mPositions[0] = 0;
+ }
+ for (uint32_t i = 0; i < aNumStops; i++) {
+ mColors[i + shift] = ColorToSkColor(aStops[i].color, 1.0);
+ mPositions[i + shift] = SkFloatToScalar(aStops[i].offset);
+ }
+ if (aStops[aNumStops - 1].offset != 1) {
+ mColors[mCount - 1] = ColorToSkColor(aStops[aNumStops - 1].color, 1.0);
+ mPositions[mCount - 1] = SK_Scalar1;
+ }
+ }
+
+ BackendType GetBackendType() const override { return BackendType::SKIA; }
+
+ std::vector<SkColor> mColors;
+ std::vector<SkScalar> mPositions;
+ int mCount;
+ ExtendMode mExtendMode;
+};
+
+/**
+ * When constructing a temporary SkImage via GetSkImageForSurface, we may also
+ * have to construct a temporary DataSourceSurface, which must live as long as
+ * the SkImage. We attach this temporary surface to the image's pixelref, so
+ * that it can be released once the pixelref is freed.
+ */
+static void ReleaseTemporarySurface(const void* aPixels, void* aContext) {
+ DataSourceSurface* surf = static_cast<DataSourceSurface*>(aContext);
+ if (surf) {
+ surf->Release();
+ }
+}
+
+static void ReleaseTemporaryMappedSurface(const void* aPixels, void* aContext) {
+ DataSourceSurface* surf = static_cast<DataSourceSurface*>(aContext);
+ if (surf) {
+ surf->Unmap();
+ surf->Release();
+ }
+}
+
+static void WriteRGBXFormat(uint8_t* aData, const IntSize& aSize,
+ const int32_t aStride, SurfaceFormat aFormat) {
+ if (aFormat != SurfaceFormat::B8G8R8X8 || aSize.IsEmpty()) {
+ return;
+ }
+
+ SwizzleData(aData, aStride, SurfaceFormat::X8R8G8B8_UINT32, aData, aStride,
+ SurfaceFormat::A8R8G8B8_UINT32, aSize);
+}
+
+#ifdef DEBUG
+static IntRect CalculateSurfaceBounds(const IntSize& aSize, const Rect* aBounds,
+ const Matrix* aMatrix) {
+ IntRect surfaceBounds(IntPoint(0, 0), aSize);
+ if (!aBounds) {
+ return surfaceBounds;
+ }
+
+ MOZ_ASSERT(aMatrix);
+ Matrix inverse(*aMatrix);
+ if (!inverse.Invert()) {
+ return surfaceBounds;
+ }
+
+ IntRect bounds;
+ Rect sampledBounds = inverse.TransformBounds(*aBounds);
+ if (!sampledBounds.ToIntRect(&bounds)) {
+ return surfaceBounds;
+ }
+
+ return surfaceBounds.Intersect(bounds);
+}
+
+static const int kARGBAlphaOffset =
+ SurfaceFormat::A8R8G8B8_UINT32 == SurfaceFormat::B8G8R8A8 ? 3 : 0;
+
+static bool VerifyRGBXFormat(uint8_t* aData, const IntSize& aSize,
+ const int32_t aStride, SurfaceFormat aFormat) {
+ if (aFormat != SurfaceFormat::B8G8R8X8 || aSize.IsEmpty()) {
+ return true;
+ }
+ // We should've initialized the data to be opaque already
+ // On debug builds, verify that this is actually true.
+ int height = aSize.height;
+ int width = aSize.width * 4;
+
+ for (int row = 0; row < height; ++row) {
+ for (int column = 0; column < width; column += 4) {
+ if (aData[column + kARGBAlphaOffset] != 0xFF) {
+ gfxCriticalError() << "RGBX pixel at (" << column << "," << row
+ << ") in " << width << "x" << height
+ << " surface is not opaque: " << int(aData[column])
+ << "," << int(aData[column + 1]) << ","
+ << int(aData[column + 2]) << ","
+ << int(aData[column + 3]);
+ }
+ }
+ aData += aStride;
+ }
+
+ return true;
+}
+
+// Since checking every pixel is expensive, this only checks the four corners
+// and center of a surface that their alpha value is 0xFF.
+static bool VerifyRGBXCorners(uint8_t* aData, const IntSize& aSize,
+ const int32_t aStride, SurfaceFormat aFormat,
+ const Rect* aBounds = nullptr,
+ const Matrix* aMatrix = nullptr) {
+ if (aFormat != SurfaceFormat::B8G8R8X8 || aSize.IsEmpty()) {
+ return true;
+ }
+
+ IntRect bounds = CalculateSurfaceBounds(aSize, aBounds, aMatrix);
+ if (bounds.IsEmpty()) {
+ return true;
+ }
+
+ const int height = bounds.Height();
+ const int width = bounds.Width();
+ const int pixelSize = 4;
+ MOZ_ASSERT(aSize.width * pixelSize <= aStride);
+
+ const int translation = bounds.Y() * aStride + bounds.X() * pixelSize;
+ const int topLeft = translation;
+ const int topRight = topLeft + (width - 1) * pixelSize;
+ const int bottomLeft = translation + (height - 1) * aStride;
+ const int bottomRight = bottomLeft + (width - 1) * pixelSize;
+
+ // Lastly the center pixel
+ const int middleRowHeight = height / 2;
+ const int middleRowWidth = (width / 2) * pixelSize;
+ const int middle = translation + aStride * middleRowHeight + middleRowWidth;
+
+ const int offsets[] = {topLeft, topRight, bottomRight, bottomLeft, middle};
+ for (int offset : offsets) {
+ if (aData[offset + kARGBAlphaOffset] != 0xFF) {
+ int row = offset / aStride;
+ int column = (offset % aStride) / pixelSize;
+ gfxCriticalError() << "RGBX corner pixel at (" << column << "," << row
+ << ") in " << aSize.width << "x" << aSize.height
+ << " surface, bounded by "
+ << "(" << bounds.X() << "," << bounds.Y() << ","
+ << width << "," << height
+ << ") is not opaque: " << int(aData[offset]) << ","
+ << int(aData[offset + 1]) << ","
+ << int(aData[offset + 2]) << ","
+ << int(aData[offset + 3]);
+ }
+ }
+
+ return true;
+}
+#endif
+
+static sk_sp<SkImage> GetSkImageForSurface(SourceSurface* aSurface,
+ Maybe<MutexAutoLock>* aLock,
+ const Rect* aBounds = nullptr,
+ const Matrix* aMatrix = nullptr) {
+ if (!aSurface) {
+ gfxDebug() << "Creating null Skia image from null SourceSurface";
+ return nullptr;
+ }
+
+ if (aSurface->GetType() == SurfaceType::SKIA) {
+ return static_cast<SourceSurfaceSkia*>(aSurface)->GetImage(aLock);
+ }
+
+ RefPtr<DataSourceSurface> dataSurface = aSurface->GetDataSurface();
+ if (!dataSurface) {
+ gfxWarning() << "Failed getting DataSourceSurface for Skia image";
+ return nullptr;
+ }
+
+ DataSourceSurface::MappedSurface map;
+ SkImage::RasterReleaseProc releaseProc;
+ if (dataSurface->GetType() == SurfaceType::DATA_SHARED_WRAPPER) {
+ // Technically all surfaces should be mapped and unmapped explicitly but it
+ // appears SourceSurfaceSkia and DataSourceSurfaceWrapper have issues with
+ // this. For now, we just map SourceSurfaceSharedDataWrapper to ensure we
+ // don't unmap the data during the transaction (for blob images).
+ if (!dataSurface->Map(DataSourceSurface::MapType::READ, &map)) {
+ gfxWarning() << "Failed mapping DataSourceSurface for Skia image";
+ return nullptr;
+ }
+ releaseProc = ReleaseTemporaryMappedSurface;
+ } else {
+ map.mData = dataSurface->GetData();
+ map.mStride = dataSurface->Stride();
+ releaseProc = ReleaseTemporarySurface;
+ }
+
+ DataSourceSurface* surf = dataSurface.forget().take();
+
+ // Skia doesn't support RGBX surfaces so ensure that the alpha value is opaque
+ // white.
+ MOZ_ASSERT(VerifyRGBXCorners(map.mData, surf->GetSize(), map.mStride,
+ surf->GetFormat(), aBounds, aMatrix));
+
+ SkPixmap pixmap(MakeSkiaImageInfo(surf->GetSize(), surf->GetFormat()),
+ map.mData, map.mStride);
+ sk_sp<SkImage> image = SkImage::MakeFromRaster(pixmap, releaseProc, surf);
+ if (!image) {
+ releaseProc(map.mData, surf);
+ gfxDebug() << "Failed making Skia raster image for temporary surface";
+ }
+
+ return image;
+}
+
+DrawTargetSkia::DrawTargetSkia()
+ : mCanvas(nullptr),
+ mSnapshot(nullptr),
+ mSnapshotLock{"DrawTargetSkia::mSnapshotLock"}
+#ifdef MOZ_WIDGET_COCOA
+ ,
+ mCG(nullptr),
+ mColorSpace(nullptr),
+ mCanvasData(nullptr),
+ mCGSize(0, 0),
+ mNeedLayer(false)
+#endif
+{
+}
+
+DrawTargetSkia::~DrawTargetSkia() {
+ if (mSnapshot) {
+ MutexAutoLock lock(mSnapshotLock);
+ // We're going to go away, hand our SkSurface to the SourceSurface.
+ mSnapshot->GiveSurface(mSurface.forget().take());
+ }
+
+#ifdef MOZ_WIDGET_COCOA
+ if (mCG) {
+ CGContextRelease(mCG);
+ mCG = nullptr;
+ }
+
+ if (mColorSpace) {
+ CGColorSpaceRelease(mColorSpace);
+ mColorSpace = nullptr;
+ }
+#endif
+}
+
+already_AddRefed<SourceSurface> DrawTargetSkia::Snapshot(
+ SurfaceFormat aFormat) {
+ // Without this lock, this could cause us to get out a snapshot and race with
+ // Snapshot::~Snapshot() actually destroying itself.
+ MutexAutoLock lock(mSnapshotLock);
+ if (mSnapshot && aFormat != mSnapshot->GetFormat()) {
+ if (!mSnapshot->hasOneRef()) {
+ mSnapshot->DrawTargetWillChange();
+ }
+ mSnapshot = nullptr;
+ }
+ RefPtr<SourceSurfaceSkia> snapshot = mSnapshot;
+ if (mSurface && !snapshot) {
+ snapshot = new SourceSurfaceSkia();
+ sk_sp<SkImage> image;
+ // If the surface is raster, making a snapshot may trigger a pixel copy.
+ // Instead, try to directly make a raster image referencing the surface
+ // pixels.
+ SkPixmap pixmap;
+ if (mSurface->peekPixels(&pixmap)) {
+ image = SkImage::MakeFromRaster(pixmap, nullptr, nullptr);
+ } else {
+ image = mSurface->makeImageSnapshot();
+ }
+ if (!snapshot->InitFromImage(image, aFormat, this)) {
+ return nullptr;
+ }
+ mSnapshot = snapshot;
+ }
+
+ return snapshot.forget();
+}
+
+already_AddRefed<SourceSurface> DrawTargetSkia::GetBackingSurface() {
+ if (mBackingSurface) {
+ RefPtr<SourceSurface> snapshot = mBackingSurface;
+ return snapshot.forget();
+ }
+ return Snapshot();
+}
+
+bool DrawTargetSkia::LockBits(uint8_t** aData, IntSize* aSize, int32_t* aStride,
+ SurfaceFormat* aFormat, IntPoint* aOrigin) {
+ SkImageInfo info;
+ size_t rowBytes;
+ SkIPoint origin;
+ void* pixels = mCanvas->accessTopLayerPixels(&info, &rowBytes, &origin);
+ if (!pixels ||
+ // Ensure the layer is at the origin if required.
+ (!aOrigin && !origin.isZero())) {
+ return false;
+ }
+
+ MarkChanged();
+
+ *aData = reinterpret_cast<uint8_t*>(pixels);
+ *aSize = IntSize(info.width(), info.height());
+ *aStride = int32_t(rowBytes);
+ *aFormat = SkiaColorTypeToGfxFormat(info.colorType(), info.alphaType());
+ if (aOrigin) {
+ *aOrigin = IntPoint(origin.x(), origin.y());
+ }
+ return true;
+}
+
+void DrawTargetSkia::ReleaseBits(uint8_t* aData) {}
+
+static void ReleaseImage(const void* aPixels, void* aContext) {
+ SkImage* image = static_cast<SkImage*>(aContext);
+ SkSafeUnref(image);
+}
+
+static sk_sp<SkImage> ExtractSubset(sk_sp<SkImage> aImage,
+ const IntRect& aRect) {
+ SkIRect subsetRect = IntRectToSkIRect(aRect);
+ if (aImage->bounds() == subsetRect) {
+ return aImage;
+ }
+ // makeSubset is slow, so prefer to use SkPixmap::extractSubset where
+ // possible.
+ SkPixmap pixmap, subsetPixmap;
+ if (aImage->peekPixels(&pixmap) &&
+ pixmap.extractSubset(&subsetPixmap, subsetRect)) {
+ // Release the original image reference so only the subset image keeps it
+ // alive.
+ return SkImage::MakeFromRaster(subsetPixmap, ReleaseImage,
+ aImage.release());
+ }
+ return aImage->makeSubset(subsetRect);
+}
+
+static void FreeAlphaPixels(void* aBuf, void*) { sk_free(aBuf); }
+
+static bool ExtractAlphaBitmap(const sk_sp<SkImage>& aImage,
+ SkBitmap* aResultBitmap,
+ bool aAllowReuse = false) {
+ SkPixmap pixmap;
+ if (aAllowReuse && aImage->isAlphaOnly() && aImage->peekPixels(&pixmap)) {
+ SkBitmap bitmap;
+ bitmap.installPixels(pixmap.info(), pixmap.writable_addr(),
+ pixmap.rowBytes());
+ *aResultBitmap = bitmap;
+ return true;
+ }
+ SkImageInfo info = SkImageInfo::MakeA8(aImage->width(), aImage->height());
+ // Skia does not fully allocate the last row according to stride.
+ // Since some of our algorithms (i.e. blur) depend on this, we must allocate
+ // the bitmap pixels manually.
+ size_t stride = GetAlignedStride<4>(info.width(), info.bytesPerPixel());
+ if (stride) {
+ CheckedInt<size_t> size = stride;
+ size *= info.height();
+ // We need to leave room for an additional 3 bytes for a potential overrun
+ // in our blurring code.
+ size += 3;
+ if (size.isValid()) {
+ void* buf = sk_malloc_flags(size.value(), 0);
+ if (buf) {
+ SkBitmap bitmap;
+ if (bitmap.installPixels(info, buf, stride, FreeAlphaPixels, nullptr) &&
+ aImage->readPixels(bitmap.info(), bitmap.getPixels(),
+ bitmap.rowBytes(), 0, 0)) {
+ *aResultBitmap = bitmap;
+ return true;
+ }
+ }
+ }
+ }
+
+ gfxWarning() << "Failed reading alpha pixels for Skia bitmap";
+ return false;
+}
+
+static void SetPaintPattern(SkPaint& aPaint, const Pattern& aPattern,
+ Maybe<MutexAutoLock>& aLock, Float aAlpha = 1.0,
+ const SkMatrix* aMatrix = nullptr,
+ const Rect* aBounds = nullptr) {
+ switch (aPattern.GetType()) {
+ case PatternType::COLOR: {
+ DeviceColor color = static_cast<const ColorPattern&>(aPattern).mColor;
+ aPaint.setColor(ColorToSkColor(color, aAlpha));
+ break;
+ }
+ case PatternType::LINEAR_GRADIENT: {
+ const LinearGradientPattern& pat =
+ static_cast<const LinearGradientPattern&>(aPattern);
+ GradientStopsSkia* stops =
+ pat.mStops && pat.mStops->GetBackendType() == BackendType::SKIA
+ ? static_cast<GradientStopsSkia*>(pat.mStops.get())
+ : nullptr;
+ if (!stops || stops->mCount < 2 || !pat.mBegin.IsFinite() ||
+ !pat.mEnd.IsFinite() || pat.mBegin == pat.mEnd) {
+ aPaint.setColor(SK_ColorTRANSPARENT);
+ } else {
+ SkTileMode mode = ExtendModeToTileMode(stops->mExtendMode, Axis::BOTH);
+ SkPoint points[2];
+ points[0] = SkPoint::Make(SkFloatToScalar(pat.mBegin.x),
+ SkFloatToScalar(pat.mBegin.y));
+ points[1] = SkPoint::Make(SkFloatToScalar(pat.mEnd.x),
+ SkFloatToScalar(pat.mEnd.y));
+
+ SkMatrix mat;
+ GfxMatrixToSkiaMatrix(pat.mMatrix, mat);
+ if (aMatrix) {
+ mat.postConcat(*aMatrix);
+ }
+ sk_sp<SkShader> shader = SkGradientShader::MakeLinear(
+ points, &stops->mColors.front(), &stops->mPositions.front(),
+ stops->mCount, mode, 0, &mat);
+ if (shader) {
+ aPaint.setShader(shader);
+ } else {
+ aPaint.setColor(SK_ColorTRANSPARENT);
+ }
+ }
+ break;
+ }
+ case PatternType::RADIAL_GRADIENT: {
+ const RadialGradientPattern& pat =
+ static_cast<const RadialGradientPattern&>(aPattern);
+ GradientStopsSkia* stops =
+ pat.mStops && pat.mStops->GetBackendType() == BackendType::SKIA
+ ? static_cast<GradientStopsSkia*>(pat.mStops.get())
+ : nullptr;
+ if (!stops || stops->mCount < 2 || !pat.mCenter1.IsFinite() ||
+ !std::isfinite(pat.mRadius1) || !pat.mCenter2.IsFinite() ||
+ !std::isfinite(pat.mRadius2) ||
+ (pat.mCenter1 == pat.mCenter2 && pat.mRadius1 == pat.mRadius2)) {
+ aPaint.setColor(SK_ColorTRANSPARENT);
+ } else {
+ SkTileMode mode = ExtendModeToTileMode(stops->mExtendMode, Axis::BOTH);
+ SkPoint points[2];
+ points[0] = SkPoint::Make(SkFloatToScalar(pat.mCenter1.x),
+ SkFloatToScalar(pat.mCenter1.y));
+ points[1] = SkPoint::Make(SkFloatToScalar(pat.mCenter2.x),
+ SkFloatToScalar(pat.mCenter2.y));
+
+ SkMatrix mat;
+ GfxMatrixToSkiaMatrix(pat.mMatrix, mat);
+ if (aMatrix) {
+ mat.postConcat(*aMatrix);
+ }
+ sk_sp<SkShader> shader = SkGradientShader::MakeTwoPointConical(
+ points[0], SkFloatToScalar(pat.mRadius1), points[1],
+ SkFloatToScalar(pat.mRadius2), &stops->mColors.front(),
+ &stops->mPositions.front(), stops->mCount, mode, 0, &mat);
+ if (shader) {
+ aPaint.setShader(shader);
+ } else {
+ aPaint.setColor(SK_ColorTRANSPARENT);
+ }
+ }
+ break;
+ }
+ case PatternType::CONIC_GRADIENT: {
+ const ConicGradientPattern& pat =
+ static_cast<const ConicGradientPattern&>(aPattern);
+ GradientStopsSkia* stops =
+ pat.mStops && pat.mStops->GetBackendType() == BackendType::SKIA
+ ? static_cast<GradientStopsSkia*>(pat.mStops.get())
+ : nullptr;
+ if (!stops || stops->mCount < 2 || !pat.mCenter.IsFinite() ||
+ !std::isfinite(pat.mAngle)) {
+ aPaint.setColor(SK_ColorTRANSPARENT);
+ } else {
+ SkMatrix mat;
+ GfxMatrixToSkiaMatrix(pat.mMatrix, mat);
+ if (aMatrix) {
+ mat.postConcat(*aMatrix);
+ }
+
+ SkScalar cx = SkFloatToScalar(pat.mCenter.x);
+ SkScalar cy = SkFloatToScalar(pat.mCenter.y);
+
+ // Skia's sweep gradient angles are relative to the x-axis, not the
+ // y-axis.
+ Float angle = (pat.mAngle * 180.0 / M_PI) - 90.0;
+ if (angle != 0.0) {
+ mat.preRotate(angle, cx, cy);
+ }
+
+ SkTileMode mode = ExtendModeToTileMode(stops->mExtendMode, Axis::BOTH);
+ sk_sp<SkShader> shader = SkGradientShader::MakeSweep(
+ cx, cy, &stops->mColors.front(), &stops->mPositions.front(),
+ stops->mCount, mode, 360 * pat.mStartOffset, 360 * pat.mEndOffset,
+ 0, &mat);
+
+ if (shader) {
+ aPaint.setShader(shader);
+ } else {
+ aPaint.setColor(SK_ColorTRANSPARENT);
+ }
+ }
+ break;
+ }
+ case PatternType::SURFACE: {
+ const SurfacePattern& pat = static_cast<const SurfacePattern&>(aPattern);
+ sk_sp<SkImage> image =
+ GetSkImageForSurface(pat.mSurface, &aLock, aBounds, &pat.mMatrix);
+ if (!image) {
+ aPaint.setColor(SK_ColorTRANSPARENT);
+ break;
+ }
+
+ SkMatrix mat;
+ GfxMatrixToSkiaMatrix(pat.mMatrix, mat);
+ if (aMatrix) {
+ mat.postConcat(*aMatrix);
+ }
+
+ if (!pat.mSamplingRect.IsEmpty()) {
+ image = ExtractSubset(image, pat.mSamplingRect);
+ if (!image) {
+ aPaint.setColor(SK_ColorTRANSPARENT);
+ break;
+ }
+ mat.preTranslate(pat.mSamplingRect.X(), pat.mSamplingRect.Y());
+ }
+
+ SkTileMode xTile = ExtendModeToTileMode(pat.mExtendMode, Axis::X_AXIS);
+ SkTileMode yTile = ExtendModeToTileMode(pat.mExtendMode, Axis::Y_AXIS);
+
+ SkFilterMode filterMode = pat.mSamplingFilter == SamplingFilter::POINT
+ ? SkFilterMode::kNearest
+ : SkFilterMode::kLinear;
+
+ sk_sp<SkShader> shader =
+ image->makeShader(xTile, yTile, SkSamplingOptions(filterMode), mat);
+ if (shader) {
+ aPaint.setShader(shader);
+ } else {
+ gfxDebug() << "Failed creating Skia surface shader: x-tile="
+ << (int)xTile << " y-tile=" << (int)yTile
+ << " matrix=" << (mat.isFinite() ? "finite" : "non-finite");
+ aPaint.setColor(SK_ColorTRANSPARENT);
+ }
+ break;
+ }
+ }
+}
+
+static inline Rect GetClipBounds(SkCanvas* aCanvas) {
+ // Use a manually transformed getClipDeviceBounds instead of
+ // getClipBounds because getClipBounds inflates the the bounds
+ // by a pixel in each direction to compensate for antialiasing.
+ SkIRect deviceBounds;
+ if (!aCanvas->getDeviceClipBounds(&deviceBounds)) {
+ return Rect();
+ }
+ SkMatrix inverseCTM;
+ if (!aCanvas->getTotalMatrix().invert(&inverseCTM)) {
+ return Rect();
+ }
+ SkRect localBounds;
+ inverseCTM.mapRect(&localBounds, SkRect::Make(deviceBounds));
+ return SkRectToRect(localBounds);
+}
+
+struct AutoPaintSetup {
+ AutoPaintSetup(SkCanvas* aCanvas, const DrawOptions& aOptions,
+ const Pattern& aPattern, const Rect* aMaskBounds = nullptr,
+ const SkMatrix* aMatrix = nullptr,
+ const Rect* aSourceBounds = nullptr)
+ : mNeedsRestore(false), mAlpha(1.0) {
+ Init(aCanvas, aOptions, aMaskBounds, false);
+ SetPaintPattern(mPaint, aPattern, mLock, mAlpha, aMatrix, aSourceBounds);
+ }
+
+ AutoPaintSetup(SkCanvas* aCanvas, const DrawOptions& aOptions,
+ const Rect* aMaskBounds = nullptr, bool aForceGroup = false)
+ : mNeedsRestore(false), mAlpha(1.0) {
+ Init(aCanvas, aOptions, aMaskBounds, aForceGroup);
+ }
+
+ ~AutoPaintSetup() {
+ if (mNeedsRestore) {
+ mCanvas->restore();
+ }
+ }
+
+ void Init(SkCanvas* aCanvas, const DrawOptions& aOptions,
+ const Rect* aMaskBounds, bool aForceGroup) {
+ mPaint.setBlendMode(GfxOpToSkiaOp(aOptions.mCompositionOp));
+ mCanvas = aCanvas;
+
+ // TODO: Can we set greyscale somehow?
+ if (aOptions.mAntialiasMode != AntialiasMode::NONE) {
+ mPaint.setAntiAlias(true);
+ } else {
+ mPaint.setAntiAlias(false);
+ }
+
+ bool needsGroup =
+ aForceGroup ||
+ (!IsOperatorBoundByMask(aOptions.mCompositionOp) &&
+ (!aMaskBounds || !aMaskBounds->Contains(GetClipBounds(aCanvas))));
+
+ // TODO: We could skip the temporary for operator_source and just
+ // clear the clip rect. The other operators would be harder
+ // but could be worth it to skip pushing a group.
+ if (needsGroup) {
+ mPaint.setBlendMode(SkBlendMode::kSrcOver);
+ SkPaint temp;
+ temp.setBlendMode(GfxOpToSkiaOp(aOptions.mCompositionOp));
+ temp.setAlpha(ColorFloatToByte(aOptions.mAlpha));
+ // TODO: Get a rect here
+ SkCanvas::SaveLayerRec rec(nullptr, &temp,
+ SkCanvas::kPreserveLCDText_SaveLayerFlag);
+ mCanvas->saveLayer(rec);
+ mNeedsRestore = true;
+ } else {
+ mPaint.setAlpha(ColorFloatToByte(aOptions.mAlpha));
+ mAlpha = aOptions.mAlpha;
+ }
+ }
+
+ // TODO: Maybe add an operator overload to access this easier?
+ SkPaint mPaint;
+ bool mNeedsRestore;
+ SkCanvas* mCanvas;
+ Maybe<MutexAutoLock> mLock;
+ Float mAlpha;
+};
+
+void DrawTargetSkia::Flush() { mCanvas->flush(); }
+
+void DrawTargetSkia::DrawSurface(SourceSurface* aSurface, const Rect& aDest,
+ const Rect& aSource,
+ const DrawSurfaceOptions& aSurfOptions,
+ const DrawOptions& aOptions) {
+ if (aSource.IsEmpty()) {
+ return;
+ }
+
+ MarkChanged();
+
+ Maybe<MutexAutoLock> lock;
+ sk_sp<SkImage> image = GetSkImageForSurface(aSurface, &lock);
+ if (!image) {
+ return;
+ }
+
+ SkRect destRect = RectToSkRect(aDest);
+ SkRect sourceRect = RectToSkRect(aSource - aSurface->GetRect().TopLeft());
+ bool forceGroup =
+ image->isAlphaOnly() && aOptions.mCompositionOp != CompositionOp::OP_OVER;
+
+ AutoPaintSetup paint(mCanvas, aOptions, &aDest, forceGroup);
+
+ SkFilterMode filterMode =
+ aSurfOptions.mSamplingFilter == SamplingFilter::POINT
+ ? SkFilterMode::kNearest
+ : SkFilterMode::kLinear;
+
+ mCanvas->drawImageRect(image, sourceRect, destRect,
+ SkSamplingOptions(filterMode), &paint.mPaint,
+ SkCanvas::kStrict_SrcRectConstraint);
+}
+
+DrawTargetType DrawTargetSkia::GetType() const {
+ return DrawTargetType::SOFTWARE_RASTER;
+}
+
+void DrawTargetSkia::DrawFilter(FilterNode* aNode, const Rect& aSourceRect,
+ const Point& aDestPoint,
+ const DrawOptions& aOptions) {
+ if (!aNode || aNode->GetBackendType() != FILTER_BACKEND_SOFTWARE) {
+ return;
+ }
+ FilterNodeSoftware* filter = static_cast<FilterNodeSoftware*>(aNode);
+ filter->Draw(this, aSourceRect, aDestPoint, aOptions);
+}
+
+void DrawTargetSkia::DrawSurfaceWithShadow(SourceSurface* aSurface,
+ const Point& aDest,
+ const ShadowOptions& aShadow,
+ CompositionOp aOperator) {
+ if (aSurface->GetSize().IsEmpty()) {
+ return;
+ }
+
+ MarkChanged();
+
+ Maybe<MutexAutoLock> lock;
+ sk_sp<SkImage> image = GetSkImageForSurface(aSurface, &lock);
+ if (!image) {
+ return;
+ }
+
+ mCanvas->save();
+ mCanvas->resetMatrix();
+
+ SkPaint paint;
+ paint.setBlendMode(GfxOpToSkiaOp(aOperator));
+
+ // bug 1201272
+ // We can't use the SkDropShadowImageFilter here because it applies the xfer
+ // mode first to render the bitmap to a temporary layer, and then implicitly
+ // uses src-over to composite the resulting shadow.
+ // The canvas spec, however, states that the composite op must be used to
+ // composite the resulting shadow, so we must instead use a SkBlurImageFilter
+ // to blur the image ourselves.
+
+ SkPaint shadowPaint;
+ shadowPaint.setBlendMode(GfxOpToSkiaOp(aOperator));
+
+ auto shadowDest = IntPoint::Round(aDest + aShadow.mOffset);
+
+ SkBitmap blurMask;
+ // Extract the alpha channel of the image into a bitmap. If the image is A8
+ // format already, then we can directly reuse the bitmap rather than create a
+ // new one as the surface only needs to be drawn from once.
+ if (ExtractAlphaBitmap(image, &blurMask, true)) {
+ // Prefer using our own box blur instead of Skia's. It currently performs
+ // much better than SkBlurImageFilter or SkBlurMaskFilter on the CPU.
+ AlphaBoxBlur blur(Rect(0, 0, blurMask.width(), blurMask.height()),
+ int32_t(blurMask.rowBytes()), aShadow.mSigma,
+ aShadow.mSigma);
+ blur.Blur(reinterpret_cast<uint8_t*>(blurMask.getPixels()));
+ blurMask.notifyPixelsChanged();
+
+ shadowPaint.setColor(ColorToSkColor(aShadow.mColor, 1.0f));
+
+ mCanvas->drawImage(blurMask.asImage(), shadowDest.x, shadowDest.y,
+ SkSamplingOptions(SkFilterMode::kLinear), &shadowPaint);
+ } else {
+ sk_sp<SkImageFilter> blurFilter(
+ SkImageFilters::Blur(aShadow.mSigma, aShadow.mSigma, nullptr));
+ sk_sp<SkColorFilter> colorFilter(SkColorFilters::Blend(
+ ColorToSkColor(aShadow.mColor, 1.0f), SkBlendMode::kSrcIn));
+
+ shadowPaint.setImageFilter(blurFilter);
+ shadowPaint.setColorFilter(colorFilter);
+
+ mCanvas->drawImage(image, shadowDest.x, shadowDest.y,
+ SkSamplingOptions(SkFilterMode::kLinear), &shadowPaint);
+ }
+
+ if (aSurface->GetFormat() != SurfaceFormat::A8) {
+ // Composite the original image after the shadow
+ auto dest = IntPoint::Round(aDest);
+ mCanvas->drawImage(image, dest.x, dest.y,
+ SkSamplingOptions(SkFilterMode::kLinear), &paint);
+ }
+
+ mCanvas->restore();
+}
+
+void DrawTargetSkia::FillRect(const Rect& aRect, const Pattern& aPattern,
+ const DrawOptions& aOptions) {
+ // The sprite blitting path in Skia can be faster than the shader blitter for
+ // operators other than source (or source-over with opaque surface). So, when
+ // possible/beneficial, route to DrawSurface which will use the sprite
+ // blitter.
+ if (aPattern.GetType() == PatternType::SURFACE &&
+ aOptions.mCompositionOp != CompositionOp::OP_SOURCE) {
+ const SurfacePattern& pat = static_cast<const SurfacePattern&>(aPattern);
+ // Verify there is a valid surface and a pattern matrix without skew.
+ if (pat.mSurface &&
+ (aOptions.mCompositionOp != CompositionOp::OP_OVER ||
+ GfxFormatToSkiaAlphaType(pat.mSurface->GetFormat()) !=
+ kOpaque_SkAlphaType) &&
+ !pat.mMatrix.HasNonAxisAlignedTransform()) {
+ // Bound the sampling to smaller of the bounds or the sampling rect.
+ IntRect srcRect(IntPoint(0, 0), pat.mSurface->GetSize());
+ if (!pat.mSamplingRect.IsEmpty()) {
+ srcRect = srcRect.Intersect(pat.mSamplingRect);
+ }
+ // Transform the destination rectangle by the inverse of the pattern
+ // matrix so that it is in pattern space like the source rectangle.
+ Rect patRect = aRect - pat.mMatrix.GetTranslation();
+ patRect.Scale(1.0f / pat.mMatrix._11, 1.0f / pat.mMatrix._22);
+ // Verify the pattern rectangle will not tile or clamp.
+ if (!patRect.IsEmpty() && srcRect.Contains(RoundedOut(patRect))) {
+ // The pattern is a surface with an axis-aligned source rectangle
+ // fitting entirely in its bounds, so just treat it as a DrawSurface.
+ DrawSurface(pat.mSurface, aRect, patRect,
+ DrawSurfaceOptions(pat.mSamplingFilter), aOptions);
+ return;
+ }
+ }
+ }
+
+ MarkChanged();
+ SkRect rect = RectToSkRect(aRect);
+ AutoPaintSetup paint(mCanvas, aOptions, aPattern, &aRect, nullptr, &aRect);
+
+ mCanvas->drawRect(rect, paint.mPaint);
+}
+
+void DrawTargetSkia::Stroke(const Path* aPath, const Pattern& aPattern,
+ const StrokeOptions& aStrokeOptions,
+ const DrawOptions& aOptions) {
+ MarkChanged();
+ MOZ_ASSERT(aPath, "Null path");
+ if (aPath->GetBackendType() != BackendType::SKIA) {
+ return;
+ }
+
+ const PathSkia* skiaPath = static_cast<const PathSkia*>(aPath);
+
+ AutoPaintSetup paint(mCanvas, aOptions, aPattern);
+ if (!StrokeOptionsToPaint(paint.mPaint, aStrokeOptions)) {
+ return;
+ }
+
+ if (!skiaPath->GetPath().isFinite()) {
+ return;
+ }
+
+ mCanvas->drawPath(skiaPath->GetPath(), paint.mPaint);
+}
+
+static Double DashPeriodLength(const StrokeOptions& aStrokeOptions) {
+ Double length = 0;
+ for (size_t i = 0; i < aStrokeOptions.mDashLength; i++) {
+ length += aStrokeOptions.mDashPattern[i];
+ }
+ if (aStrokeOptions.mDashLength & 1) {
+ // "If an odd number of values is provided, then the list of values is
+ // repeated to yield an even number of values."
+ // Double the length.
+ length += length;
+ }
+ return length;
+}
+
+static inline Double RoundDownToMultiple(Double aValue, Double aFactor) {
+ return floor(aValue / aFactor) * aFactor;
+}
+
+static Rect UserSpaceStrokeClip(const IntRect& aDeviceClip,
+ const Matrix& aTransform,
+ const StrokeOptions& aStrokeOptions) {
+ Matrix inverse = aTransform;
+ if (!inverse.Invert()) {
+ return Rect();
+ }
+ Rect deviceClip(aDeviceClip);
+ deviceClip.Inflate(MaxStrokeExtents(aStrokeOptions, aTransform));
+ return inverse.TransformBounds(deviceClip);
+}
+
+static Rect ShrinkClippedStrokedRect(const Rect& aStrokedRect,
+ const IntRect& aDeviceClip,
+ const Matrix& aTransform,
+ const StrokeOptions& aStrokeOptions) {
+ Rect userSpaceStrokeClip =
+ UserSpaceStrokeClip(aDeviceClip, aTransform, aStrokeOptions);
+ RectDouble strokedRectDouble(aStrokedRect.X(), aStrokedRect.Y(),
+ aStrokedRect.Width(), aStrokedRect.Height());
+ RectDouble intersection = strokedRectDouble.Intersect(
+ RectDouble(userSpaceStrokeClip.X(), userSpaceStrokeClip.Y(),
+ userSpaceStrokeClip.Width(), userSpaceStrokeClip.Height()));
+ Double dashPeriodLength = DashPeriodLength(aStrokeOptions);
+ if (intersection.IsEmpty() || dashPeriodLength == 0.0f) {
+ return Rect(intersection.X(), intersection.Y(), intersection.Width(),
+ intersection.Height());
+ }
+
+ // Reduce the rectangle side lengths in multiples of the dash period length
+ // so that the visible dashes stay in the same place.
+ MarginDouble insetBy = strokedRectDouble - intersection;
+ insetBy.top = RoundDownToMultiple(insetBy.top, dashPeriodLength);
+ insetBy.right = RoundDownToMultiple(insetBy.right, dashPeriodLength);
+ insetBy.bottom = RoundDownToMultiple(insetBy.bottom, dashPeriodLength);
+ insetBy.left = RoundDownToMultiple(insetBy.left, dashPeriodLength);
+
+ strokedRectDouble.Deflate(insetBy);
+ return Rect(strokedRectDouble.X(), strokedRectDouble.Y(),
+ strokedRectDouble.Width(), strokedRectDouble.Height());
+}
+
+void DrawTargetSkia::StrokeRect(const Rect& aRect, const Pattern& aPattern,
+ const StrokeOptions& aStrokeOptions,
+ const DrawOptions& aOptions) {
+ // Stroking large rectangles with dashes is expensive with Skia (fixed
+ // overhead based on the number of dashes, regardless of whether the dashes
+ // are visible), so we try to reduce the size of the stroked rectangle as
+ // much as possible before passing it on to Skia.
+ Rect rect = aRect;
+ if (aStrokeOptions.mDashLength > 0 && !rect.IsEmpty()) {
+ IntRect deviceClip(IntPoint(0, 0), mSize);
+ SkIRect clipBounds;
+ if (mCanvas->getDeviceClipBounds(&clipBounds)) {
+ deviceClip = SkIRectToIntRect(clipBounds);
+ }
+ rect =
+ ShrinkClippedStrokedRect(rect, deviceClip, mTransform, aStrokeOptions);
+ if (rect.IsEmpty()) {
+ return;
+ }
+ }
+
+ MarkChanged();
+ AutoPaintSetup paint(mCanvas, aOptions, aPattern);
+ if (!StrokeOptionsToPaint(paint.mPaint, aStrokeOptions)) {
+ return;
+ }
+
+ mCanvas->drawRect(RectToSkRect(rect), paint.mPaint);
+}
+
+void DrawTargetSkia::StrokeLine(const Point& aStart, const Point& aEnd,
+ const Pattern& aPattern,
+ const StrokeOptions& aStrokeOptions,
+ const DrawOptions& aOptions) {
+ MarkChanged();
+ AutoPaintSetup paint(mCanvas, aOptions, aPattern);
+ if (!StrokeOptionsToPaint(paint.mPaint, aStrokeOptions)) {
+ return;
+ }
+
+ mCanvas->drawLine(SkFloatToScalar(aStart.x), SkFloatToScalar(aStart.y),
+ SkFloatToScalar(aEnd.x), SkFloatToScalar(aEnd.y),
+ paint.mPaint);
+}
+
+void DrawTargetSkia::Fill(const Path* aPath, const Pattern& aPattern,
+ const DrawOptions& aOptions) {
+ MarkChanged();
+ if (!aPath || aPath->GetBackendType() != BackendType::SKIA) {
+ return;
+ }
+
+ const PathSkia* skiaPath = static_cast<const PathSkia*>(aPath);
+
+ AutoPaintSetup paint(mCanvas, aOptions, aPattern);
+
+ if (!skiaPath->GetPath().isFinite()) {
+ return;
+ }
+
+ mCanvas->drawPath(skiaPath->GetPath(), paint.mPaint);
+}
+
+#ifdef MOZ_WIDGET_COCOA
+static inline CGAffineTransform GfxMatrixToCGAffineTransform(const Matrix& m) {
+ CGAffineTransform t;
+ t.a = m._11;
+ t.b = m._12;
+ t.c = m._21;
+ t.d = m._22;
+ t.tx = m._31;
+ t.ty = m._32;
+ return t;
+}
+
+/***
+ * We have to do a lot of work to draw glyphs with CG because
+ * CG assumes that the origin of rects are in the bottom left
+ * while every other DrawTarget assumes the top left is the origin.
+ * This means we have to transform the CGContext to have rects
+ * actually be applied in top left fashion. We do this by:
+ *
+ * 1) Translating the context up by the height of the canvas
+ * 2) Flipping the context by the Y axis so it's upside down.
+ *
+ * These two transforms put the origin in the top left.
+ * Transforms are better understood thinking about them from right to left order
+ * (mathematically).
+ *
+ * Consider a point we want to draw at (0, 10) in normal cartesian planes with
+ * a box of (100, 100). in CG terms, this would be at (0, 10).
+ * Positive Y values point up.
+ * In our DrawTarget terms, positive Y values point down, so (0, 10) would be
+ * at (0, 90) in cartesian plane terms. That means our point at (0, 10) in
+ * DrawTarget terms should end up at (0, 90). How does this work with the
+ * current transforms?
+ *
+ * Going right to left with the transforms, a CGPoint of (0, 10) has cartesian
+ * coordinates of (0, 10). The first flip of the Y axis puts the point now at
+ * (0, -10); Next, we translate the context up by the size of the canvas
+ * (Positive Y values go up in CG coordinates but down in our draw target
+ * coordinates). Since our canvas size is (100, 100), the resulting coordinate
+ * becomes (0, 90), which is what we expect from our DrawTarget code. These two
+ * transforms put the CG context equal to what every other DrawTarget expects.
+ *
+ * Next, we need two more transforms for actual text. IF we left the transforms
+ * as is, the text would be drawn upside down, so we need another flip of the Y
+ * axis to draw the text right side up. However, with only the flip, the text
+ * would be drawn in the wrong place. Thus we also have to invert the Y position
+ * of the glyphs to get them in the right place.
+ *
+ * Thus we have the following transforms:
+ * 1) Translation of the context up
+ * 2) Flipping the context around the Y axis
+ * 3) Flipping the context around the Y axis
+ * 4) Inverting the Y position of each glyph
+ *
+ * We cannot cancel out (2) and (3) as we have to apply the clips and transforms
+ * of DrawTargetSkia between (2) and (3).
+ *
+ * Consider the example letter P, drawn at (0, 20) in CG coordinates in a
+ * (100, 100) rect.
+ * Again, going right to left of the transforms. We'd get:
+ *
+ * 1) The letter P drawn at (0, -20) due to the inversion of the Y axis
+ * 2) The letter P upside down (b) at (0, 20) due to the second flip
+ * 3) The letter P right side up at (0, -20) due to the first flip
+ * 4) The letter P right side up at (0, 80) due to the translation
+ *
+ * tl;dr - CGRects assume origin is bottom left, DrawTarget rects assume top
+ * left.
+ */
+static bool SetupCGContext(DrawTargetSkia* aDT, CGContextRef aCGContext,
+ SkCanvas* aCanvas, const IntPoint& aOrigin,
+ const IntSize& aSize, bool aClipped) {
+ // DrawTarget expects the origin to be at the top left, but CG
+ // expects it to be at the bottom left. Transform to set the origin to
+ // the top left. Have to set this before we do anything else.
+ // This is transform (1) up top
+ CGContextTranslateCTM(aCGContext, -aOrigin.x, aOrigin.y + aSize.height);
+
+ // Transform (2) from the comments.
+ CGContextScaleCTM(aCGContext, 1, -1);
+
+ // Want to apply clips BEFORE the transform since the transform
+ // will apply to the clips we apply.
+ if (aClipped) {
+ SkRegion clipRegion;
+ aCanvas->temporary_internal_getRgnClip(&clipRegion);
+ Vector<CGRect, 8> rects;
+ for (SkRegion::Iterator it(clipRegion); !it.done(); it.next()) {
+ const SkIRect& rect = it.rect();
+ if (!rects.append(
+ CGRectMake(rect.x(), rect.y(), rect.width(), rect.height()))) {
+ break;
+ }
+ }
+ if (rects.length()) {
+ CGContextClipToRects(aCGContext, rects.begin(), rects.length());
+ }
+ }
+
+ CGContextConcatCTM(aCGContext,
+ GfxMatrixToCGAffineTransform(aDT->GetTransform()));
+ return true;
+}
+// End long comment about transforms.
+
+// The context returned from this method will have the origin
+// in the top left and will have applied all the neccessary clips
+// and transforms to the CGContext. See the comment above
+// SetupCGContext.
+CGContextRef DrawTargetSkia::BorrowCGContext(const DrawOptions& aOptions) {
+ // Since we can't replay Skia clips, we have to use a layer if we have a
+ // complex clip. After saving a layer, the SkCanvas queries for needing a
+ // layer change so save if we pushed a layer.
+ mNeedLayer = !mCanvas->isClipEmpty() && !mCanvas->isClipRect();
+ if (mNeedLayer) {
+ SkPaint paint;
+ paint.setBlendMode(SkBlendMode::kSrc);
+ SkCanvas::SaveLayerRec rec(nullptr, &paint,
+ SkCanvas::kInitWithPrevious_SaveLayerFlag);
+ mCanvas->saveLayer(rec);
+ }
+
+ uint8_t* data = nullptr;
+ int32_t stride;
+ SurfaceFormat format;
+ IntSize size;
+ IntPoint origin;
+ if (!LockBits(&data, &size, &stride, &format, &origin)) {
+ NS_WARNING("Could not lock skia bits to wrap CG around");
+ return nullptr;
+ }
+
+ if (!mNeedLayer && (data == mCanvasData) && mCG && (mCGSize == size)) {
+ // If our canvas data still points to the same data,
+ // we can reuse the CG Context
+ CGContextSetAlpha(mCG, aOptions.mAlpha);
+ CGContextSetShouldAntialias(mCG,
+ aOptions.mAntialiasMode != AntialiasMode::NONE);
+ CGContextSaveGState(mCG);
+ SetupCGContext(this, mCG, mCanvas, origin, size, true);
+ return mCG;
+ }
+
+ if (!mColorSpace) {
+ mColorSpace = (format == SurfaceFormat::A8) ? CGColorSpaceCreateDeviceGray()
+ : CGColorSpaceCreateDeviceRGB();
+ }
+
+ if (mCG) {
+ // Release the old CG context since it's no longer valid.
+ CGContextRelease(mCG);
+ }
+
+ mCanvasData = data;
+ mCGSize = size;
+
+ uint32_t bitmapInfo =
+ (format == SurfaceFormat::A8)
+ ? kCGImageAlphaOnly
+ : kCGImageAlphaPremultipliedFirst | kCGBitmapByteOrder32Host;
+
+ mCG = CGBitmapContextCreateWithData(
+ mCanvasData, mCGSize.width, mCGSize.height, 8, /* bits per component */
+ stride, mColorSpace, bitmapInfo, NULL, /* Callback when released */
+ NULL);
+ if (!mCG) {
+ if (mNeedLayer) {
+ mCanvas->restore();
+ }
+ ReleaseBits(mCanvasData);
+ NS_WARNING("Could not create bitmap around skia data\n");
+ return nullptr;
+ }
+
+ CGContextSetAlpha(mCG, aOptions.mAlpha);
+ CGContextSetShouldAntialias(mCG,
+ aOptions.mAntialiasMode != AntialiasMode::NONE);
+ CGContextSetShouldSmoothFonts(mCG, true);
+ CGContextSetTextDrawingMode(mCG, kCGTextFill);
+ CGContextSaveGState(mCG);
+ SetupCGContext(this, mCG, mCanvas, origin, size, !mNeedLayer);
+ return mCG;
+}
+
+void DrawTargetSkia::ReturnCGContext(CGContextRef aCGContext) {
+ MOZ_ASSERT(aCGContext == mCG);
+ ReleaseBits(mCanvasData);
+ CGContextRestoreGState(aCGContext);
+
+ if (mNeedLayer) {
+ // A layer was used for clipping and is about to be popped by the restore.
+ // Make sure the CG context referencing it is released first so the popped
+ // layer doesn't accidentally get used.
+ if (mCG) {
+ CGContextRelease(mCG);
+ mCG = nullptr;
+ }
+ mCanvas->restore();
+ }
+}
+
+CGContextRef BorrowedCGContext::BorrowCGContextFromDrawTarget(DrawTarget* aDT) {
+ DrawTargetSkia* skiaDT = static_cast<DrawTargetSkia*>(aDT);
+ return skiaDT->BorrowCGContext(DrawOptions());
+}
+
+void BorrowedCGContext::ReturnCGContextToDrawTarget(DrawTarget* aDT,
+ CGContextRef cg) {
+ DrawTargetSkia* skiaDT = static_cast<DrawTargetSkia*>(aDT);
+ skiaDT->ReturnCGContext(cg);
+}
+#endif
+
+static bool CanDrawFont(ScaledFont* aFont) {
+ switch (aFont->GetType()) {
+ case FontType::FREETYPE:
+ case FontType::FONTCONFIG:
+ case FontType::MAC:
+ case FontType::GDI:
+ case FontType::DWRITE:
+ return true;
+ default:
+ return false;
+ }
+}
+
+void DrawTargetSkia::DrawGlyphs(ScaledFont* aFont, const GlyphBuffer& aBuffer,
+ const Pattern& aPattern,
+ const StrokeOptions* aStrokeOptions,
+ const DrawOptions& aOptions) {
+ if (!CanDrawFont(aFont)) {
+ return;
+ }
+
+ MarkChanged();
+
+ ScaledFontBase* skiaFont = static_cast<ScaledFontBase*>(aFont);
+ SkTypeface* typeface = skiaFont->GetSkTypeface();
+ if (!typeface) {
+ return;
+ }
+
+ AutoPaintSetup paint(mCanvas, aOptions, aPattern);
+ if (aStrokeOptions && !StrokeOptionsToPaint(paint.mPaint, *aStrokeOptions)) {
+ return;
+ }
+
+ AntialiasMode aaMode = aFont->GetDefaultAAMode();
+ if (aOptions.mAntialiasMode != AntialiasMode::DEFAULT) {
+ aaMode = aOptions.mAntialiasMode;
+ }
+ bool aaEnabled = aaMode != AntialiasMode::NONE;
+ paint.mPaint.setAntiAlias(aaEnabled);
+
+ SkFont font(sk_ref_sp(typeface), SkFloatToScalar(skiaFont->mSize));
+
+ bool useSubpixelAA =
+ GetPermitSubpixelAA() &&
+ (aaMode == AntialiasMode::DEFAULT || aaMode == AntialiasMode::SUBPIXEL);
+ font.setEdging(useSubpixelAA ? SkFont::Edging::kSubpixelAntiAlias
+ : (aaEnabled ? SkFont::Edging::kAntiAlias
+ : SkFont::Edging::kAlias));
+
+ skiaFont->SetupSkFontDrawOptions(font);
+
+ // Limit the amount of internal batch allocations Skia does.
+ const uint32_t kMaxGlyphBatchSize = 8192;
+
+ for (uint32_t offset = 0; offset < aBuffer.mNumGlyphs;) {
+ uint32_t batchSize =
+ std::min(aBuffer.mNumGlyphs - offset, kMaxGlyphBatchSize);
+ SkTextBlobBuilder builder;
+ auto runBuffer = builder.allocRunPos(font, batchSize);
+ for (uint32_t i = 0; i < batchSize; i++, offset++) {
+ runBuffer.glyphs[i] = aBuffer.mGlyphs[offset].mIndex;
+ runBuffer.points()[i] = PointToSkPoint(aBuffer.mGlyphs[offset].mPosition);
+ }
+
+ sk_sp<SkTextBlob> text = builder.make();
+ mCanvas->drawTextBlob(text, 0, 0, paint.mPaint);
+ }
+}
+
+Maybe<Rect> DrawTargetSkia::GetGlyphLocalBounds(
+ ScaledFont* aFont, const GlyphBuffer& aBuffer, const Pattern& aPattern,
+ const StrokeOptions* aStrokeOptions, const DrawOptions& aOptions) {
+ if (!CanDrawFont(aFont)) {
+ return Nothing();
+ }
+
+ ScaledFontBase* skiaFont = static_cast<ScaledFontBase*>(aFont);
+ SkTypeface* typeface = skiaFont->GetSkTypeface();
+ if (!typeface) {
+ return Nothing();
+ }
+
+ AutoPaintSetup paint(mCanvas, aOptions, aPattern);
+ if (aStrokeOptions && !StrokeOptionsToPaint(paint.mPaint, *aStrokeOptions)) {
+ return Nothing();
+ }
+
+ AntialiasMode aaMode = aFont->GetDefaultAAMode();
+ if (aOptions.mAntialiasMode != AntialiasMode::DEFAULT) {
+ aaMode = aOptions.mAntialiasMode;
+ }
+ bool aaEnabled = aaMode != AntialiasMode::NONE;
+ paint.mPaint.setAntiAlias(aaEnabled);
+
+ SkFont font(sk_ref_sp(typeface), SkFloatToScalar(skiaFont->mSize));
+
+ bool useSubpixelAA =
+ GetPermitSubpixelAA() &&
+ (aaMode == AntialiasMode::DEFAULT || aaMode == AntialiasMode::SUBPIXEL);
+ font.setEdging(useSubpixelAA ? SkFont::Edging::kSubpixelAntiAlias
+ : (aaEnabled ? SkFont::Edging::kAntiAlias
+ : SkFont::Edging::kAlias));
+
+ skiaFont->SetupSkFontDrawOptions(font);
+
+ // Limit the amount of internal batch allocations Skia does.
+ const uint32_t kMaxGlyphBatchSize = 8192;
+
+ // Avoid using TextBlobBuilder for bounds computations as the conservative
+ // bounds can be wrong due to buggy font metrics. Instead, explicitly compute
+ // tight bounds directly with the SkFont.
+ Vector<SkGlyphID, 32> glyphs;
+ Vector<SkRect, 32> rects;
+ Rect bounds;
+ for (uint32_t offset = 0; offset < aBuffer.mNumGlyphs;) {
+ uint32_t batchSize =
+ std::min(aBuffer.mNumGlyphs - offset, kMaxGlyphBatchSize);
+ if (glyphs.resizeUninitialized(batchSize) &&
+ rects.resizeUninitialized(batchSize)) {
+ for (uint32_t i = 0; i < batchSize; i++) {
+ glyphs[i] = aBuffer.mGlyphs[offset + i].mIndex;
+ }
+ font.getBounds(glyphs.begin(), batchSize, rects.begin(), nullptr);
+ for (uint32_t i = 0; i < batchSize; i++) {
+ bounds = bounds.Union(SkRectToRect(rects[i]) +
+ aBuffer.mGlyphs[offset + i].mPosition);
+ }
+ }
+ offset += batchSize;
+ }
+
+ SkRect storage;
+ bounds = SkRectToRect(
+ paint.mPaint.computeFastBounds(RectToSkRect(bounds), &storage));
+
+ if (bounds.IsEmpty()) {
+ return Nothing();
+ }
+
+ return Some(bounds);
+}
+
+void DrawTargetSkia::FillGlyphs(ScaledFont* aFont, const GlyphBuffer& aBuffer,
+ const Pattern& aPattern,
+ const DrawOptions& aOptions) {
+ DrawGlyphs(aFont, aBuffer, aPattern, nullptr, aOptions);
+}
+
+void DrawTargetSkia::StrokeGlyphs(ScaledFont* aFont, const GlyphBuffer& aBuffer,
+ const Pattern& aPattern,
+ const StrokeOptions& aStrokeOptions,
+ const DrawOptions& aOptions) {
+ DrawGlyphs(aFont, aBuffer, aPattern, &aStrokeOptions, aOptions);
+}
+
+void DrawTargetSkia::Mask(const Pattern& aSource, const Pattern& aMask,
+ const DrawOptions& aOptions) {
+ Maybe<MutexAutoLock> lock;
+ SkPaint maskPaint;
+ SetPaintPattern(maskPaint, aMask, lock);
+
+ sk_sp<SkShader> maskShader(maskPaint.getShader());
+ if (!maskShader && maskPaint.getAlpha() != 0xFF) {
+ if (maskPaint.getAlpha() == 0) {
+ return;
+ }
+ maskShader = SkShaders::Color(maskPaint.getColor());
+ if (!maskShader) {
+ gfxDebug() << "Failed creating Skia clip shader for Mask";
+ return;
+ }
+ }
+
+ MarkChanged();
+ AutoPaintSetup paint(mCanvas, aOptions, aSource);
+
+ mCanvas->save();
+ if (maskShader) {
+ mCanvas->clipShader(maskShader);
+ }
+
+ mCanvas->drawPaint(paint.mPaint);
+
+ mCanvas->restore();
+}
+
+void DrawTargetSkia::MaskSurface(const Pattern& aSource, SourceSurface* aMask,
+ Point aOffset, const DrawOptions& aOptions) {
+ Maybe<MutexAutoLock> lock;
+ sk_sp<SkImage> maskImage = GetSkImageForSurface(aMask, &lock);
+ SkMatrix maskOffset = SkMatrix::Translate(
+ PointToSkPoint(aOffset + Point(aMask->GetRect().TopLeft())));
+ sk_sp<SkShader> maskShader = maskImage->makeShader(
+ SkTileMode::kClamp, SkTileMode::kClamp,
+ SkSamplingOptions(SkFilterMode::kLinear), maskOffset);
+ if (!maskShader) {
+ gfxDebug() << "Failed creating Skia clip shader for MaskSurface";
+ return;
+ }
+
+ MarkChanged();
+ AutoPaintSetup paint(mCanvas, aOptions, aSource);
+
+ mCanvas->save();
+ mCanvas->clipShader(maskShader);
+
+ mCanvas->drawRect(RectToSkRect(Rect(aMask->GetRect()) + aOffset),
+ paint.mPaint);
+
+ mCanvas->restore();
+}
+
+bool DrawTarget::Draw3DTransformedSurface(SourceSurface* aSurface,
+ const Matrix4x4& aMatrix) {
+ // Composite the 3D transform with the DT's transform.
+ Matrix4x4 fullMat = aMatrix * Matrix4x4::From2D(mTransform);
+ if (fullMat.IsSingular()) {
+ return false;
+ }
+ // Transform the surface bounds and clip to this DT.
+ IntRect xformBounds = RoundedOut(fullMat.TransformAndClipBounds(
+ Rect(Point(0, 0), Size(aSurface->GetSize())),
+ Rect(Point(0, 0), Size(GetSize()))));
+ if (xformBounds.IsEmpty()) {
+ return true;
+ }
+ // Offset the matrix by the transformed origin.
+ fullMat.PostTranslate(-xformBounds.X(), -xformBounds.Y(), 0);
+
+ // Read in the source data.
+ Maybe<MutexAutoLock> lock;
+ sk_sp<SkImage> srcImage = GetSkImageForSurface(aSurface, &lock);
+ if (!srcImage) {
+ return true;
+ }
+
+ // Set up an intermediate destination surface only the size of the transformed
+ // bounds. Try to pass through the source's format unmodified in both the BGRA
+ // and ARGB cases.
+ RefPtr<DataSourceSurface> dstSurf = Factory::CreateDataSourceSurface(
+ xformBounds.Size(),
+ !srcImage->isOpaque() ? aSurface->GetFormat()
+ : SurfaceFormat::A8R8G8B8_UINT32,
+ true);
+ if (!dstSurf) {
+ return false;
+ }
+
+ DataSourceSurface::ScopedMap map(dstSurf, DataSourceSurface::READ_WRITE);
+ std::unique_ptr<SkCanvas> dstCanvas(SkCanvas::MakeRasterDirect(
+ SkImageInfo::Make(xformBounds.Width(), xformBounds.Height(),
+ GfxFormatToSkiaColorType(dstSurf->GetFormat()),
+ kPremul_SkAlphaType),
+ map.GetData(), map.GetStride()));
+ if (!dstCanvas) {
+ return false;
+ }
+
+ // Do the transform.
+ SkPaint paint;
+ paint.setAntiAlias(true);
+ paint.setBlendMode(SkBlendMode::kSrc);
+
+ SkMatrix xform;
+ GfxMatrixToSkiaMatrix(fullMat, xform);
+ dstCanvas->setMatrix(xform);
+
+ dstCanvas->drawImage(srcImage, 0, 0, SkSamplingOptions(SkFilterMode::kLinear),
+ &paint);
+ dstCanvas->flush();
+
+ // Temporarily reset the DT's transform, since it has already been composed
+ // above.
+ Matrix origTransform = mTransform;
+ SetTransform(Matrix());
+
+ // Draw the transformed surface within the transformed bounds.
+ DrawSurface(dstSurf, Rect(xformBounds),
+ Rect(Point(0, 0), Size(xformBounds.Size())));
+
+ SetTransform(origTransform);
+
+ return true;
+}
+
+bool DrawTargetSkia::Draw3DTransformedSurface(SourceSurface* aSurface,
+ const Matrix4x4& aMatrix) {
+ if (aMatrix.IsSingular()) {
+ return false;
+ }
+
+ MarkChanged();
+
+ Maybe<MutexAutoLock> lock;
+ sk_sp<SkImage> image = GetSkImageForSurface(aSurface, &lock);
+ if (!image) {
+ return true;
+ }
+
+ mCanvas->save();
+
+ SkPaint paint;
+ paint.setAntiAlias(true);
+
+ SkMatrix xform;
+ GfxMatrixToSkiaMatrix(aMatrix, xform);
+ mCanvas->concat(xform);
+
+ mCanvas->drawImage(image, 0, 0, SkSamplingOptions(SkFilterMode::kLinear),
+ &paint);
+
+ mCanvas->restore();
+
+ return true;
+}
+
+already_AddRefed<SourceSurface> DrawTargetSkia::CreateSourceSurfaceFromData(
+ unsigned char* aData, const IntSize& aSize, int32_t aStride,
+ SurfaceFormat aFormat) const {
+ RefPtr<SourceSurfaceSkia> newSurf = new SourceSurfaceSkia();
+
+ if (!newSurf->InitFromData(aData, aSize, aStride, aFormat)) {
+ gfxDebug() << *this
+ << ": Failure to create source surface from data. Size: "
+ << aSize;
+ return nullptr;
+ }
+
+ return newSurf.forget();
+}
+
+already_AddRefed<DrawTarget> DrawTargetSkia::CreateSimilarDrawTarget(
+ const IntSize& aSize, SurfaceFormat aFormat) const {
+ RefPtr<DrawTargetSkia> target = new DrawTargetSkia();
+#ifdef DEBUG
+ if (!IsBackedByPixels(mCanvas)) {
+ // If our canvas is backed by vector storage such as PDF then we want to
+ // create a new DrawTarget with similar storage to avoid losing fidelity
+ // (fidelity will be lost if the returned DT is Snapshot()'ed and drawn
+ // back onto us since a raster will be drawn instead of vector commands).
+ NS_WARNING("Not backed by pixels - we need to handle PDF backed SkCanvas");
+ }
+#endif
+
+ if (!target->Init(aSize, aFormat)) {
+ return nullptr;
+ }
+ return target.forget();
+}
+
+bool DrawTargetSkia::CanCreateSimilarDrawTarget(const IntSize& aSize,
+ SurfaceFormat aFormat) const {
+ auto minmaxPair = std::minmax(aSize.width, aSize.height);
+ return minmaxPair.first > 0 &&
+ size_t(minmaxPair.second) < GetMaxSurfaceSize();
+}
+
+RefPtr<DrawTarget> DrawTargetSkia::CreateClippedDrawTarget(
+ const Rect& aBounds, SurfaceFormat aFormat) {
+ SkIRect clipBounds;
+
+ RefPtr<DrawTarget> result;
+ // Doing this save()/restore() dance is wasteful
+ mCanvas->save();
+ if (!aBounds.IsEmpty()) {
+ mCanvas->clipRect(RectToSkRect(aBounds), SkClipOp::kIntersect, true);
+ }
+ if (mCanvas->getDeviceClipBounds(&clipBounds)) {
+ RefPtr<DrawTarget> dt = CreateSimilarDrawTarget(
+ IntSize(clipBounds.width(), clipBounds.height()), aFormat);
+ if (dt) {
+ result = gfx::Factory::CreateOffsetDrawTarget(
+ dt, IntPoint(clipBounds.x(), clipBounds.y()));
+ if (result) {
+ result->SetTransform(mTransform);
+ }
+ }
+ } else {
+ // Everything is clipped but we still want some kind of surface
+ result = CreateSimilarDrawTarget(IntSize(1, 1), aFormat);
+ }
+ mCanvas->restore();
+ return result;
+}
+
+already_AddRefed<SourceSurface>
+DrawTargetSkia::OptimizeSourceSurfaceForUnknownAlpha(
+ SourceSurface* aSurface) const {
+ if (aSurface->GetType() == SurfaceType::SKIA) {
+ RefPtr<SourceSurface> surface(aSurface);
+ return surface.forget();
+ }
+
+ RefPtr<DataSourceSurface> dataSurface = aSurface->GetDataSurface();
+ DataSourceSurface::ScopedMap map(dataSurface, DataSourceSurface::READ_WRITE);
+
+ // For plugins, GDI can sometimes just write 0 to the alpha channel
+ // even for RGBX formats. In this case, we have to manually write
+ // the alpha channel to make Skia happy with RGBX and in case GDI
+ // writes some bad data. Luckily, this only happens on plugins.
+ WriteRGBXFormat(map.GetData(), dataSurface->GetSize(), map.GetStride(),
+ dataSurface->GetFormat());
+ return dataSurface.forget();
+}
+
+already_AddRefed<SourceSurface> DrawTargetSkia::OptimizeSourceSurface(
+ SourceSurface* aSurface) const {
+ if (aSurface->GetType() == SurfaceType::SKIA) {
+ RefPtr<SourceSurface> surface(aSurface);
+ return surface.forget();
+ }
+
+ // If we're not using skia-gl then drawing doesn't require any
+ // uploading, so any data surface is fine. Call GetDataSurface
+ // to trigger any required readback so that it only happens
+ // once.
+ RefPtr<DataSourceSurface> dataSurface = aSurface->GetDataSurface();
+#ifdef DEBUG
+ DataSourceSurface::ScopedMap map(dataSurface, DataSourceSurface::READ);
+ MOZ_ASSERT(VerifyRGBXFormat(map.GetData(), dataSurface->GetSize(),
+ map.GetStride(), dataSurface->GetFormat()));
+#endif
+ return dataSurface.forget();
+}
+
+already_AddRefed<SourceSurface>
+DrawTargetSkia::CreateSourceSurfaceFromNativeSurface(
+ const NativeSurface& aSurface) const {
+ return nullptr;
+}
+
+void DrawTargetSkia::CopySurface(SourceSurface* aSurface,
+ const IntRect& aSourceRect,
+ const IntPoint& aDestination) {
+ MarkChanged();
+
+ Maybe<MutexAutoLock> lock;
+ sk_sp<SkImage> image = GetSkImageForSurface(aSurface, &lock);
+ if (!image) {
+ return;
+ }
+
+ SkPixmap srcPixmap;
+ if (!image->peekPixels(&srcPixmap)) {
+ return;
+ }
+
+ // Ensure the source rect intersects the surface bounds.
+ IntRect srcRect = aSourceRect.Intersect(SkIRectToIntRect(srcPixmap.bounds()));
+ // Move the destination offset to match the altered source rect.
+ IntPoint dstOffset =
+ aDestination + (srcRect.TopLeft() - aSourceRect.TopLeft());
+ // Then ensure the dest rect intersect the canvas bounds.
+ IntRect dstRect = IntRect(dstOffset, srcRect.Size()).Intersect(GetRect());
+ // Move the source rect to match the altered dest rect.
+ srcRect += dstRect.TopLeft() - dstOffset;
+ srcRect.SizeTo(dstRect.Size());
+
+ if (!srcPixmap.extractSubset(&srcPixmap, IntRectToSkIRect(srcRect))) {
+ return;
+ }
+
+ mCanvas->writePixels(srcPixmap.info(), srcPixmap.addr(), srcPixmap.rowBytes(),
+ dstRect.x, dstRect.y);
+}
+
+static inline SkPixelGeometry GetSkPixelGeometry() {
+ return Factory::GetBGRSubpixelOrder() ? kBGR_H_SkPixelGeometry
+ : kRGB_H_SkPixelGeometry;
+}
+
+template <typename T>
+[[nodiscard]] static already_AddRefed<T> AsRefPtr(sk_sp<T>&& aSkPtr) {
+ return already_AddRefed<T>(aSkPtr.release());
+}
+
+bool DrawTargetSkia::Init(const IntSize& aSize, SurfaceFormat aFormat) {
+ if (size_t(std::max(aSize.width, aSize.height)) > GetMaxSurfaceSize()) {
+ return false;
+ }
+
+ // we need to have surfaces that have a stride aligned to 4 for interop with
+ // cairo
+ SkImageInfo info = MakeSkiaImageInfo(aSize, aFormat);
+ size_t stride = GetAlignedStride<4>(info.width(), info.bytesPerPixel());
+ if (!stride) {
+ return false;
+ }
+ SkSurfaceProps props(0, GetSkPixelGeometry());
+
+ if (aFormat == SurfaceFormat::A8) {
+ // Skia does not fully allocate the last row according to stride.
+ // Since some of our algorithms (i.e. blur) depend on this, we must allocate
+ // the bitmap pixels manually.
+ CheckedInt<size_t> size = stride;
+ size *= info.height();
+ // We need to leave room for an additional 3 bytes for a potential overrun
+ // in our blurring code.
+ size += 3;
+ if (!size.isValid()) {
+ return false;
+ }
+ void* buf = sk_malloc_flags(size.value(), SK_MALLOC_ZERO_INITIALIZE);
+ if (!buf) {
+ return false;
+ }
+ mSurface = AsRefPtr(SkSurface::MakeRasterDirectReleaseProc(
+ info, buf, stride, FreeAlphaPixels, nullptr, &props));
+ } else {
+ mSurface = AsRefPtr(SkSurface::MakeRaster(info, stride, &props));
+ }
+ if (!mSurface) {
+ return false;
+ }
+
+ mSize = aSize;
+ mFormat = aFormat;
+ mCanvas = mSurface->getCanvas();
+ SetPermitSubpixelAA(IsOpaque(mFormat));
+
+ if (info.isOpaque()) {
+ mCanvas->clear(SK_ColorBLACK);
+ }
+ return true;
+}
+
+bool DrawTargetSkia::Init(SkCanvas* aCanvas) {
+ mCanvas = aCanvas;
+
+ SkImageInfo imageInfo = mCanvas->imageInfo();
+
+ // If the canvas is backed by pixels we clear it to be on the safe side. If
+ // it's not (for example, for PDF output) we don't.
+ if (IsBackedByPixels(mCanvas)) {
+ SkColor clearColor =
+ imageInfo.isOpaque() ? SK_ColorBLACK : SK_ColorTRANSPARENT;
+ mCanvas->clear(clearColor);
+ }
+
+ SkISize size = mCanvas->getBaseLayerSize();
+ mSize.width = size.width();
+ mSize.height = size.height();
+ mFormat =
+ SkiaColorTypeToGfxFormat(imageInfo.colorType(), imageInfo.alphaType());
+ SetPermitSubpixelAA(IsOpaque(mFormat));
+ return true;
+}
+
+bool DrawTargetSkia::Init(unsigned char* aData, const IntSize& aSize,
+ int32_t aStride, SurfaceFormat aFormat,
+ bool aUninitialized) {
+ MOZ_ASSERT((aFormat != SurfaceFormat::B8G8R8X8) || aUninitialized ||
+ VerifyRGBXFormat(aData, aSize, aStride, aFormat));
+
+ SkSurfaceProps props(0, GetSkPixelGeometry());
+ mSurface = AsRefPtr(SkSurface::MakeRasterDirect(
+ MakeSkiaImageInfo(aSize, aFormat), aData, aStride, &props));
+ if (!mSurface) {
+ return false;
+ }
+
+ mSize = aSize;
+ mFormat = aFormat;
+ mCanvas = mSurface->getCanvas();
+ SetPermitSubpixelAA(IsOpaque(mFormat));
+ return true;
+}
+
+bool DrawTargetSkia::Init(RefPtr<DataSourceSurface>&& aSurface) {
+ auto map =
+ new DataSourceSurface::ScopedMap(aSurface, DataSourceSurface::READ_WRITE);
+ if (!map->IsMapped()) {
+ delete map;
+ return false;
+ }
+
+ SurfaceFormat format = aSurface->GetFormat();
+ IntSize size = aSurface->GetSize();
+ MOZ_ASSERT((format != SurfaceFormat::B8G8R8X8) ||
+ VerifyRGBXFormat(map->GetData(), size, map->GetStride(), format));
+
+ SkSurfaceProps props(0, GetSkPixelGeometry());
+ mSurface = AsRefPtr(SkSurface::MakeRasterDirectReleaseProc(
+ MakeSkiaImageInfo(size, format), map->GetData(), map->GetStride(),
+ DrawTargetSkia::ReleaseMappedSkSurface, map, &props));
+ if (!mSurface) {
+ delete map;
+ return false;
+ }
+
+ // map is now owned by mSurface
+ mBackingSurface = std::move(aSurface);
+ mSize = size;
+ mFormat = format;
+ mCanvas = mSurface->getCanvas();
+ SetPermitSubpixelAA(IsOpaque(format));
+ return true;
+}
+
+/* static */ void DrawTargetSkia::ReleaseMappedSkSurface(void* aPixels,
+ void* aContext) {
+ auto map = reinterpret_cast<DataSourceSurface::ScopedMap*>(aContext);
+ delete map;
+}
+
+void DrawTargetSkia::SetTransform(const Matrix& aTransform) {
+ SkMatrix mat;
+ GfxMatrixToSkiaMatrix(aTransform, mat);
+ mCanvas->setMatrix(mat);
+ mTransform = aTransform;
+}
+
+void* DrawTargetSkia::GetNativeSurface(NativeSurfaceType aType) {
+ return nullptr;
+}
+
+already_AddRefed<PathBuilder> DrawTargetSkia::CreatePathBuilder(
+ FillRule aFillRule) const {
+ return PathBuilderSkia::Create(aFillRule);
+}
+
+void DrawTargetSkia::ClearRect(const Rect& aRect) {
+ MarkChanged();
+ mCanvas->save();
+ // Restrict clearing to the clip region if requested
+ mCanvas->clipRect(RectToSkRect(aRect), SkClipOp::kIntersect, true);
+ SkColor clearColor = (mFormat == SurfaceFormat::B8G8R8X8)
+ ? SK_ColorBLACK
+ : SK_ColorTRANSPARENT;
+ mCanvas->clear(clearColor);
+ mCanvas->restore();
+}
+
+void DrawTargetSkia::PushClip(const Path* aPath) {
+ if (aPath->GetBackendType() != BackendType::SKIA) {
+ return;
+ }
+
+ const PathSkia* skiaPath = static_cast<const PathSkia*>(aPath);
+ mCanvas->save();
+ mCanvas->clipPath(skiaPath->GetPath(), SkClipOp::kIntersect, true);
+}
+
+void DrawTargetSkia::PushDeviceSpaceClipRects(const IntRect* aRects,
+ uint32_t aCount) {
+ // Build a region by unioning all the rects together.
+ SkRegion region;
+ for (uint32_t i = 0; i < aCount; i++) {
+ region.op(IntRectToSkIRect(aRects[i]), SkRegion::kUnion_Op);
+ }
+
+ // Clip with the resulting region. clipRegion does not transform
+ // this region by the current transform, unlike the other SkCanvas
+ // clip methods, so it is just passed through in device-space.
+ mCanvas->save();
+ mCanvas->clipRegion(region, SkClipOp::kIntersect);
+}
+
+void DrawTargetSkia::PushClipRect(const Rect& aRect) {
+ SkRect rect = RectToSkRect(aRect);
+
+ mCanvas->save();
+ mCanvas->clipRect(rect, SkClipOp::kIntersect, true);
+}
+
+void DrawTargetSkia::PopClip() {
+ mCanvas->restore();
+ SetTransform(GetTransform());
+}
+
+bool DrawTargetSkia::RemoveAllClips() {
+ mCanvas->restoreToCount(1);
+ SetTransform(GetTransform());
+ return true;
+}
+
+// Get clip bounds in device space for the clipping region. By default, only
+// bounds for simple (empty or rect) regions are reported. If explicitly
+// allowed, the bounds will be reported for complex (all other) regions as well.
+Maybe<IntRect> DrawTargetSkia::GetDeviceClipRect(bool aAllowComplex) const {
+ if (mCanvas->isClipEmpty()) {
+ return Some(IntRect());
+ }
+ if (aAllowComplex || mCanvas->isClipRect()) {
+ SkIRect deviceBounds;
+ if (mCanvas->getDeviceClipBounds(&deviceBounds)) {
+ return Some(SkIRectToIntRect(deviceBounds));
+ }
+ }
+ return Nothing();
+}
+
+void DrawTargetSkia::PushLayer(bool aOpaque, Float aOpacity,
+ SourceSurface* aMask,
+ const Matrix& aMaskTransform,
+ const IntRect& aBounds, bool aCopyBackground) {
+ PushLayerWithBlend(aOpaque, aOpacity, aMask, aMaskTransform, aBounds,
+ aCopyBackground, CompositionOp::OP_OVER);
+}
+
+void DrawTargetSkia::PushLayerWithBlend(bool aOpaque, Float aOpacity,
+ SourceSurface* aMask,
+ const Matrix& aMaskTransform,
+ const IntRect& aBounds,
+ bool aCopyBackground,
+ CompositionOp aCompositionOp) {
+ SkPaint paint;
+
+ paint.setAlpha(ColorFloatToByte(aOpacity));
+ paint.setBlendMode(GfxOpToSkiaOp(aCompositionOp));
+
+ // aBounds is supplied in device space, but SaveLayerRec wants local space.
+ SkRect bounds = SkRect::MakeEmpty();
+ if (!aBounds.IsEmpty()) {
+ Matrix inverseTransform = mTransform;
+ if (inverseTransform.Invert()) {
+ bounds = RectToSkRect(inverseTransform.TransformBounds(Rect(aBounds)));
+ }
+ }
+
+ // We don't pass a lock object to GetSkImageForSurface here, to force a
+ // copy of the data if this is a copy-on-write snapshot. If we instead held
+ // the lock until the corresponding PopLayer, we'd risk deadlocking if someone
+ // tried to touch the originating DrawTarget while the layer was pushed.
+ sk_sp<SkImage> clipImage = GetSkImageForSurface(aMask, nullptr);
+ bool usedMask = false;
+ if (bool(clipImage)) {
+ Rect maskBounds(aMask->GetRect());
+ sk_sp<SkShader> shader = clipImage->makeShader(
+ SkTileMode::kClamp, SkTileMode::kClamp,
+ SkSamplingOptions(SkFilterMode::kLinear),
+ SkMatrix::Translate(PointToSkPoint(maskBounds.TopLeft())));
+ if (shader) {
+ usedMask = true;
+ mCanvas->save();
+
+ auto oldMatrix = mCanvas->getLocalToDevice();
+ SkMatrix clipMatrix;
+ GfxMatrixToSkiaMatrix(aMaskTransform, clipMatrix);
+ mCanvas->concat(clipMatrix);
+
+ mCanvas->clipRect(RectToSkRect(maskBounds));
+ mCanvas->clipShader(shader);
+
+ mCanvas->setMatrix(oldMatrix);
+ } else {
+ gfxDebug() << "Failed to create Skia clip shader for PushLayerWithBlend";
+ }
+ }
+
+ PushedLayer layer(GetPermitSubpixelAA(), usedMask ? aMask : nullptr);
+ mPushedLayers.push_back(layer);
+
+ SkCanvas::SaveLayerRec saveRec(
+ aBounds.IsEmpty() ? nullptr : &bounds, &paint, nullptr,
+ SkCanvas::kPreserveLCDText_SaveLayerFlag |
+ (aCopyBackground ? SkCanvas::kInitWithPrevious_SaveLayerFlag : 0));
+
+ mCanvas->saveLayer(saveRec);
+
+ SetPermitSubpixelAA(aOpaque);
+
+#ifdef MOZ_WIDGET_COCOA
+ CGContextRelease(mCG);
+ mCG = nullptr;
+#endif
+}
+
+void DrawTargetSkia::PopLayer() {
+ MOZ_RELEASE_ASSERT(!mPushedLayers.empty());
+
+ MarkChanged();
+
+ const PushedLayer& layer = mPushedLayers.back();
+
+ mCanvas->restore();
+
+ if (layer.mMask) {
+ mCanvas->restore();
+ }
+
+ SetTransform(GetTransform());
+ SetPermitSubpixelAA(layer.mOldPermitSubpixelAA);
+
+ mPushedLayers.pop_back();
+
+#ifdef MOZ_WIDGET_COCOA
+ CGContextRelease(mCG);
+ mCG = nullptr;
+#endif
+}
+
+already_AddRefed<GradientStops> DrawTargetSkia::CreateGradientStops(
+ GradientStop* aStops, uint32_t aNumStops, ExtendMode aExtendMode) const {
+ std::vector<GradientStop> stops;
+ stops.resize(aNumStops);
+ for (uint32_t i = 0; i < aNumStops; i++) {
+ stops[i] = aStops[i];
+ }
+ std::stable_sort(stops.begin(), stops.end());
+
+ return MakeAndAddRef<GradientStopsSkia>(stops, aNumStops, aExtendMode);
+}
+
+already_AddRefed<FilterNode> DrawTargetSkia::CreateFilter(FilterType aType) {
+ return FilterNodeSoftware::Create(aType);
+}
+
+void DrawTargetSkia::MarkChanged() {
+ // I'm not entirely certain whether this lock is needed, as multiple threads
+ // should never modify the DrawTarget at the same time anyway, but this seems
+ // like the safest.
+ MutexAutoLock lock(mSnapshotLock);
+ if (mSnapshot) {
+ if (mSnapshot->hasOneRef()) {
+ // No owners outside of this DrawTarget's own reference. Just dump it.
+ mSnapshot = nullptr;
+ return;
+ }
+
+ mSnapshot->DrawTargetWillChange();
+ mSnapshot = nullptr;
+
+ // Handle copying of any image snapshots bound to the surface.
+ if (mSurface) {
+ mSurface->notifyContentWillChange(SkSurface::kRetain_ContentChangeMode);
+ }
+ }
+}
+
+} // namespace mozilla::gfx