From 36d22d82aa202bb199967e9512281e9a53db42c9 Mon Sep 17 00:00:00 2001 From: Daniel Baumann Date: Sun, 7 Apr 2024 21:33:14 +0200 Subject: Adding upstream version 115.7.0esr. Signed-off-by: Daniel Baumann --- gfx/2d/DrawTargetSkia.cpp | 2070 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 2070 insertions(+) create mode 100644 gfx/2d/DrawTargetSkia.cpp (limited to 'gfx/2d/DrawTargetSkia.cpp') 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 +#include + +#ifdef MOZ_WIDGET_COCOA +# include "BorrowedContext.h" +# include +#endif + +#ifdef XP_WIN +# include "ScaledFontDWrite.h" +#endif + +namespace mozilla { + +void RefPtrTraits::Release(SkSurface* aSurface) { + SkSafeUnref(aSurface); +} + +void RefPtrTraits::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& 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 mColors; + std::vector 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(aContext); + if (surf) { + surf->Release(); + } +} + +static void ReleaseTemporaryMappedSurface(const void* aPixels, void* aContext) { + DataSourceSurface* surf = static_cast(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 GetSkImageForSurface(SourceSurface* aSurface, + Maybe* 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(aSurface)->GetImage(aLock); + } + + RefPtr 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 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 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 snapshot = mSnapshot; + if (mSurface && !snapshot) { + snapshot = new SourceSurfaceSkia(); + sk_sp 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 DrawTargetSkia::GetBackingSurface() { + if (mBackingSurface) { + RefPtr 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(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(aContext); + SkSafeUnref(image); +} + +static sk_sp ExtractSubset(sk_sp 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& 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 = 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& aLock, Float aAlpha = 1.0, + const SkMatrix* aMatrix = nullptr, + const Rect* aBounds = nullptr) { + switch (aPattern.GetType()) { + case PatternType::COLOR: { + DeviceColor color = static_cast(aPattern).mColor; + aPaint.setColor(ColorToSkColor(color, aAlpha)); + break; + } + case PatternType::LINEAR_GRADIENT: { + const LinearGradientPattern& pat = + static_cast(aPattern); + GradientStopsSkia* stops = + pat.mStops && pat.mStops->GetBackendType() == BackendType::SKIA + ? static_cast(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 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(aPattern); + GradientStopsSkia* stops = + pat.mStops && pat.mStops->GetBackendType() == BackendType::SKIA + ? static_cast(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 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(aPattern); + GradientStopsSkia* stops = + pat.mStops && pat.mStops->GetBackendType() == BackendType::SKIA + ? static_cast(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 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(aPattern); + sk_sp 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 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 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 lock; + sk_sp 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(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 lock; + sk_sp 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(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 blurFilter( + SkImageFilters::Blur(aShadow.mSigma, aShadow.mSigma, nullptr)); + sk_sp 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(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(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(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 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(aDT); + return skiaDT->BorrowCGContext(DrawOptions()); +} + +void BorrowedCGContext::ReturnCGContextToDrawTarget(DrawTarget* aDT, + CGContextRef cg) { + DrawTargetSkia* skiaDT = static_cast(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(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 text = builder.make(); + mCanvas->drawTextBlob(text, 0, 0, paint.mPaint); + } +} + +Maybe 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(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 glyphs; + Vector 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 lock; + SkPaint maskPaint; + SetPaintPattern(maskPaint, aMask, lock); + + sk_sp 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 lock; + sk_sp maskImage = GetSkImageForSurface(aMask, &lock); + SkMatrix maskOffset = SkMatrix::Translate( + PointToSkPoint(aOffset + Point(aMask->GetRect().TopLeft()))); + sk_sp 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 lock; + sk_sp 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 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 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 lock; + sk_sp 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 DrawTargetSkia::CreateSourceSurfaceFromData( + unsigned char* aData, const IntSize& aSize, int32_t aStride, + SurfaceFormat aFormat) const { + RefPtr 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 DrawTargetSkia::CreateSimilarDrawTarget( + const IntSize& aSize, SurfaceFormat aFormat) const { + RefPtr 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 DrawTargetSkia::CreateClippedDrawTarget( + const Rect& aBounds, SurfaceFormat aFormat) { + SkIRect clipBounds; + + RefPtr 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 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 +DrawTargetSkia::OptimizeSourceSurfaceForUnknownAlpha( + SourceSurface* aSurface) const { + if (aSurface->GetType() == SurfaceType::SKIA) { + RefPtr surface(aSurface); + return surface.forget(); + } + + RefPtr 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 DrawTargetSkia::OptimizeSourceSurface( + SourceSurface* aSurface) const { + if (aSurface->GetType() == SurfaceType::SKIA) { + RefPtr 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 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 +DrawTargetSkia::CreateSourceSurfaceFromNativeSurface( + const NativeSurface& aSurface) const { + return nullptr; +} + +void DrawTargetSkia::CopySurface(SourceSurface* aSurface, + const IntRect& aSourceRect, + const IntPoint& aDestination) { + MarkChanged(); + + Maybe lock; + sk_sp 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 +[[nodiscard]] static already_AddRefed AsRefPtr(sk_sp&& aSkPtr) { + return already_AddRefed(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 = 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&& 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(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 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(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 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 clipImage = GetSkImageForSurface(aMask, nullptr); + bool usedMask = false; + if (bool(clipImage)) { + Rect maskBounds(aMask->GetRect()); + sk_sp 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 DrawTargetSkia::CreateGradientStops( + GradientStop* aStops, uint32_t aNumStops, ExtendMode aExtendMode) const { + std::vector 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(stops, aNumStops, aExtendMode); +} + +already_AddRefed 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 -- cgit v1.2.3