forked from mirrors/gecko-dev
		
	 b2d4c86823
			
		
	
	
		b2d4c86823
		
	
	
	
	
		
			
			--HG-- extra : rebase_source : 56f2cc017632bf27115490ae05254019108c6179 extra : amend_source : 98ea6c3c02a9f7650d2cf65deaf5085cf9a2efa4
		
			
				
	
	
		
			2229 lines
		
	
	
	
		
			71 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
			
		
		
	
	
			2229 lines
		
	
	
	
		
			71 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
| /* -*- 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/ArrayUtils.h"
 | |
| #include "mozilla/CheckedInt.h"
 | |
| 
 | |
| #include "skia/include/core/SkSurface.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/SkBlurImageFilter.h"
 | |
| #include "Blur.h"
 | |
| #include "Logging.h"
 | |
| #include "Tools.h"
 | |
| #include "DataSurfaceHelpers.h"
 | |
| #include "PathHelpers.h"
 | |
| #include "SourceSurfaceCapture.h"
 | |
| #include "Swizzle.h"
 | |
| #include <algorithm>
 | |
| 
 | |
| #ifdef USE_SKIA_GPU
 | |
| #include "GLDefs.h"
 | |
| #include "skia/include/gpu/GrContext.h"
 | |
| #include "skia/include/gpu/GrTexture.h"
 | |
| #include "skia/include/gpu/gl/GrGLInterface.h"
 | |
| #endif
 | |
| 
 | |
| #ifdef MOZ_WIDGET_COCOA
 | |
| #include "BorrowedContext.h"
 | |
| #include <ApplicationServices/ApplicationServices.h>
 | |
| #include "mozilla/Vector.h"
 | |
| #include "ScaledFontMac.h"
 | |
| #include "CGTextDrawing.h"
 | |
| #endif
 | |
| 
 | |
| #ifdef XP_WIN
 | |
| #include "ScaledFontDWrite.h"
 | |
| #endif
 | |
| 
 | |
| using namespace std;
 | |
| 
 | |
| namespace mozilla {
 | |
| namespace 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
 | |
| 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, const Rect* aBounds = nullptr, const Matrix* aMatrix = nullptr)
 | |
| {
 | |
|   if (!aSurface) {
 | |
|     gfxDebug() << "Creating null Skia image from null SourceSurface";
 | |
|     return nullptr;
 | |
|   }
 | |
| 
 | |
|   if (aSurface->GetType() == SurfaceType::CAPTURE) {
 | |
|     SourceSurfaceCapture* capture = static_cast<SourceSurfaceCapture*>(aSurface);
 | |
|     RefPtr<SourceSurface> resolved = capture->Resolve(BackendType::SKIA);
 | |
|     if (!resolved) {
 | |
|       return nullptr;
 | |
|     }
 | |
|     MOZ_ASSERT(resolved->GetType() != SurfaceType::CAPTURE);
 | |
|     return GetSkImageForSurface(resolved, aBounds, aMatrix);
 | |
|   }
 | |
| 
 | |
|   if (aSurface->GetType() == SurfaceType::SKIA) {
 | |
|     return static_cast<SourceSurfaceSkia*>(aSurface)->GetImage();
 | |
|   }
 | |
| 
 | |
|   DataSourceSurface* surf = aSurface->GetDataSurface().take();
 | |
|   if (!surf) {
 | |
|     gfxWarning() << "Failed getting DataSourceSurface for Skia image";
 | |
|     return nullptr;
 | |
|   }
 | |
| 
 | |
|   SkPixmap pixmap(MakeSkiaImageInfo(surf->GetSize(), surf->GetFormat()),
 | |
|                   surf->GetData(), surf->Stride());
 | |
|   sk_sp<SkImage> image = SkImage::MakeFromRaster(pixmap, ReleaseTemporarySurface, surf);
 | |
|   if (!image) {
 | |
|     ReleaseTemporarySurface(nullptr, surf);
 | |
|     gfxDebug() << "Failed making Skia raster image for temporary surface";
 | |
|   }
 | |
| 
 | |
|   // Skia doesn't support RGBX surfaces so ensure that the alpha value is opaque white.
 | |
|   MOZ_ASSERT(VerifyRGBXCorners(surf->GetData(), surf->GetSize(),
 | |
|                                surf->Stride(), surf->GetFormat(),
 | |
|                                aBounds, aMatrix));
 | |
|   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);
 | |
|   }
 | |
| 
 | |
| #ifdef MOZ_WIDGET_COCOA
 | |
|   if (mCG) {
 | |
|     CGContextRelease(mCG);
 | |
|     mCG = nullptr;
 | |
|   }
 | |
| 
 | |
|   if (mColorSpace) {
 | |
|     CGColorSpaceRelease(mColorSpace);
 | |
|     mColorSpace = nullptr;
 | |
|   }
 | |
| #endif
 | |
| }
 | |
| 
 | |
| already_AddRefed<SourceSurface>
 | |
| DrawTargetSkia::Snapshot()
 | |
| {
 | |
|   // Without this lock, this could cause us to get out a snapshot and race with
 | |
|   // Snapshot::~Snapshot() actually destroying itself.
 | |
|   MutexAutoLock lock(mSnapshotLock);
 | |
|   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, mFormat, this)) {
 | |
|       return nullptr;
 | |
|     }
 | |
|     mSnapshot = snapshot;
 | |
|   }
 | |
| 
 | |
|   return snapshot.forget();
 | |
| }
 | |
| 
 | |
| 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
 | |
| FreeBitmapPixels(void* aBuf, void*)
 | |
| {
 | |
|   sk_free(aBuf);
 | |
| }
 | |
| 
 | |
| static bool
 | |
| ExtractAlphaBitmap(const sk_sp<SkImage>& aImage, SkBitmap* aResultBitmap)
 | |
| {
 | |
|   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 = SkAlign4(info.minRowBytes());
 | |
|   CheckedInt<size_t> size = stride;
 | |
|   size *= info.height();
 | |
|   if (size.isValid()) {
 | |
|     void* buf = sk_malloc_flags(size.value(), 0);
 | |
|     if (buf) {
 | |
|       SkBitmap bitmap;
 | |
|       if (bitmap.installPixels(info, buf, stride, FreeBitmapPixels, 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 sk_sp<SkImage>
 | |
| ExtractAlphaForSurface(SourceSurface* aSurface)
 | |
| {
 | |
|   sk_sp<SkImage> image = GetSkImageForSurface(aSurface);
 | |
|   if (!image) {
 | |
|     return nullptr;
 | |
|   }
 | |
|   if (image->isAlphaOnly()) {
 | |
|     return image;
 | |
|   }
 | |
| 
 | |
|   SkBitmap bitmap;
 | |
|   if (!ExtractAlphaBitmap(image, &bitmap)) {
 | |
|     return nullptr;
 | |
|   }
 | |
| 
 | |
|   // Mark the bitmap immutable so that it will be shared rather than copied.
 | |
|   bitmap.setImmutable();
 | |
|   return SkImage::MakeFromBitmap(bitmap);
 | |
| }
 | |
| 
 | |
| static void
 | |
| SetPaintPattern(SkPaint& aPaint, const Pattern& aPattern, Float aAlpha = 1.0, const SkMatrix* aMatrix = nullptr, const Rect* aBounds = nullptr)
 | |
| {
 | |
|   switch (aPattern.GetType()) {
 | |
|     case PatternType::COLOR: {
 | |
|       Color 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 = static_cast<GradientStopsSkia*>(pat.mStops.get());
 | |
|       if (!stops || stops->mCount < 2 ||
 | |
|           !pat.mBegin.IsFinite() || !pat.mEnd.IsFinite()) {
 | |
|         aPaint.setColor(SK_ColorTRANSPARENT);
 | |
|       } else {
 | |
|         SkShader::TileMode 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 = static_cast<GradientStopsSkia*>(pat.mStops.get());
 | |
|       if (!stops || stops->mCount < 2 ||
 | |
|           !pat.mCenter1.IsFinite() || !IsFinite(pat.mRadius1) ||
 | |
|           !pat.mCenter2.IsFinite() || !IsFinite(pat.mRadius2)) {
 | |
|         aPaint.setColor(SK_ColorTRANSPARENT);
 | |
|       } else {
 | |
|         SkShader::TileMode 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::SURFACE: {
 | |
|       const SurfacePattern& pat = static_cast<const SurfacePattern&>(aPattern);
 | |
|       sk_sp<SkImage> image = GetSkImageForSurface(pat.mSurface, 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);
 | |
|         mat.preTranslate(pat.mSamplingRect.X(), pat.mSamplingRect.Y());
 | |
|       }
 | |
| 
 | |
|       SkShader::TileMode xTileMode = ExtendModeToTileMode(pat.mExtendMode, Axis::X_AXIS);
 | |
|       SkShader::TileMode yTileMode = ExtendModeToTileMode(pat.mExtendMode, Axis::Y_AXIS);
 | |
| 
 | |
|       aPaint.setShader(image->makeShader(xTileMode, yTileMode, &mat));
 | |
| 
 | |
|       if (pat.mSamplingFilter == SamplingFilter::POINT) {
 | |
|         aPaint.setFilterQuality(kNone_SkFilterQuality);
 | |
|       }
 | |
|       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, 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
 | |
|       mCanvas->saveLayerPreserveLCDTextRequests(nullptr, &temp);
 | |
|       mNeedsRestore = true;
 | |
|     } else {
 | |
|       mPaint.setAlpha(ColorFloatToByte(aOptions.mAlpha));
 | |
|       mAlpha = aOptions.mAlpha;
 | |
|     }
 | |
|     mPaint.setFilterQuality(kLow_SkFilterQuality);
 | |
|   }
 | |
| 
 | |
|   // TODO: Maybe add an operator overload to access this easier?
 | |
|   SkPaint mPaint;
 | |
|   bool mNeedsRestore;
 | |
|   SkCanvas* mCanvas;
 | |
|   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();
 | |
| 
 | |
|   sk_sp<SkImage> image = GetSkImageForSurface(aSurface);
 | |
|   if (!image) {
 | |
|     return;
 | |
|   }
 | |
| 
 | |
|   SkRect destRect = RectToSkRect(aDest);
 | |
|   SkRect sourceRect = RectToSkRect(aSource);
 | |
|   bool forceGroup = image->isAlphaOnly() &&
 | |
|                     aOptions.mCompositionOp != CompositionOp::OP_OVER;
 | |
| 
 | |
|   AutoPaintSetup paint(mCanvas, aOptions, &aDest, forceGroup);
 | |
|   if (aSurfOptions.mSamplingFilter == SamplingFilter::POINT) {
 | |
|     paint.mPaint.setFilterQuality(kNone_SkFilterQuality);
 | |
|   }
 | |
| 
 | |
|   mCanvas->drawImageRect(image, sourceRect, destRect, &paint.mPaint);
 | |
| }
 | |
| 
 | |
| DrawTargetType
 | |
| DrawTargetSkia::GetType() const
 | |
| {
 | |
| #ifdef USE_SKIA_GPU
 | |
|   if (mGrContext) {
 | |
|     return DrawTargetType::HARDWARE_RASTER;
 | |
|   }
 | |
| #endif
 | |
|   return DrawTargetType::SOFTWARE_RASTER;
 | |
| }
 | |
| 
 | |
| void
 | |
| DrawTargetSkia::DrawFilter(FilterNode *aNode,
 | |
|                            const Rect &aSourceRect,
 | |
|                            const Point &aDestPoint,
 | |
|                            const DrawOptions &aOptions)
 | |
| {
 | |
|   FilterNodeSoftware* filter = static_cast<FilterNodeSoftware*>(aNode);
 | |
|   filter->Draw(this, aSourceRect, aDestPoint, aOptions);
 | |
| }
 | |
| 
 | |
| void
 | |
| DrawTargetSkia::DrawSurfaceWithShadow(SourceSurface *aSurface,
 | |
|                                       const Point &aDest,
 | |
|                                       const Color &aColor,
 | |
|                                       const Point &aOffset,
 | |
|                                       Float aSigma,
 | |
|                                       CompositionOp aOperator)
 | |
| {
 | |
|   if (aSurface->GetSize().IsEmpty()) {
 | |
|     return;
 | |
|   }
 | |
| 
 | |
|   MarkChanged();
 | |
| 
 | |
|   sk_sp<SkImage> image = GetSkImageForSurface(aSurface);
 | |
|   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 + aOffset);
 | |
| 
 | |
|   SkBitmap blurMask;
 | |
|   if (!UsingSkiaGPU() &&
 | |
|       ExtractAlphaBitmap(image, &blurMask)) {
 | |
|     // Prefer using our own box blur instead of Skia's when we're
 | |
|     // not using the GPU. 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()),
 | |
|                       aSigma, aSigma);
 | |
|     blur.Blur(reinterpret_cast<uint8_t*>(blurMask.getPixels()));
 | |
|     blurMask.notifyPixelsChanged();
 | |
| 
 | |
|     shadowPaint.setColor(ColorToSkColor(aColor, 1.0f));
 | |
| 
 | |
|     mCanvas->drawBitmap(blurMask, shadowDest.x, shadowDest.y, &shadowPaint);
 | |
|   } else {
 | |
|     sk_sp<SkImageFilter> blurFilter(SkBlurImageFilter::Make(aSigma, aSigma, nullptr));
 | |
|     sk_sp<SkColorFilter> colorFilter(
 | |
|       SkColorFilter::MakeModeFilter(ColorToSkColor(aColor, 1.0f), SkBlendMode::kSrcIn));
 | |
| 
 | |
|     shadowPaint.setImageFilter(blurFilter);
 | |
|     shadowPaint.setColorFilter(colorFilter);
 | |
| 
 | |
|     mCanvas->drawImage(image, shadowDest.x, shadowDest.y, &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, &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);
 | |
| }
 | |
| 
 | |
| bool
 | |
| DrawTargetSkia::ShouldLCDRenderText(FontType aFontType, AntialiasMode aAntialiasMode)
 | |
| {
 | |
|   // Only allow subpixel AA if explicitly permitted.
 | |
|   if (!GetPermitSubpixelAA()) {
 | |
|     return false;
 | |
|   }
 | |
| 
 | |
|   if (aAntialiasMode == AntialiasMode::DEFAULT) {
 | |
|     switch (aFontType) {
 | |
|       case FontType::MAC:
 | |
|       case FontType::GDI:
 | |
|       case FontType::DWRITE:
 | |
|       case FontType::FONTCONFIG:
 | |
|         return true;
 | |
|       case FontType::FREETYPE:
 | |
|       default:
 | |
|         // TODO: Figure out what to do for the other platforms.
 | |
|         return false;
 | |
|     }
 | |
|   }
 | |
|   return (aAntialiasMode == AntialiasMode::SUBPIXEL);
 | |
| }
 | |
| 
 | |
| #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;
 | |
| }
 | |
| 
 | |
| static bool
 | |
| SetupCGGlyphs(CGContextRef aCGContext,
 | |
|               const GlyphBuffer& aBuffer,
 | |
|               Vector<CGGlyph,32>& aGlyphs,
 | |
|               Vector<CGPoint,32>& aPositions)
 | |
| {
 | |
|   // Flip again so we draw text in right side up. Transform (3) from the top
 | |
|   CGContextScaleCTM(aCGContext, 1, -1);
 | |
| 
 | |
|   if (!aGlyphs.resizeUninitialized(aBuffer.mNumGlyphs) ||
 | |
|       !aPositions.resizeUninitialized(aBuffer.mNumGlyphs)) {
 | |
|     gfxDevCrash(LogReason::GlyphAllocFailedCG) << "glyphs/positions allocation failed";
 | |
|     return false;
 | |
|   }
 | |
| 
 | |
|   for (unsigned int i = 0; i < aBuffer.mNumGlyphs; i++) {
 | |
|     aGlyphs[i] = aBuffer.mGlyphs[i].mIndex;
 | |
| 
 | |
|     // Flip the y coordinates so that text ends up in the right spot after the (3) flip
 | |
|     // Inversion from (4) in the comments.
 | |
|     aPositions[i] = CGPointMake(aBuffer.mGlyphs[i].mPosition.x,
 | |
|                                 -aBuffer.mGlyphs[i].mPosition.y);
 | |
|   }
 | |
| 
 | |
|   return true;
 | |
| }
 | |
| // End long comment about transforms. SetupCGContext and SetupCGGlyphs should stay
 | |
| // next to each other.
 | |
| 
 | |
| // 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);
 | |
|   return;
 | |
| }
 | |
| 
 | |
| static void
 | |
| SetFontColor(CGContextRef aCGContext, CGColorSpaceRef aColorSpace, const Pattern& aPattern)
 | |
| {
 | |
|   const Color& color = static_cast<const ColorPattern&>(aPattern).mColor;
 | |
|   CGColorRef textColor = ColorToCGColor(aColorSpace, color);
 | |
|   CGContextSetFillColorWithColor(aCGContext, textColor);
 | |
|   CGColorRelease(textColor);
 | |
| }
 | |
| 
 | |
| /***
 | |
|  * We need this to support subpixel AA text on OS X in two cases:
 | |
|  * text in DrawTargets that are not opaque and text over vibrant backgrounds.
 | |
|  * Skia normally doesn't support subpixel AA text on transparent backgrounds.
 | |
|  * To get around this, we have to wrap the Skia bytes with a CGContext and ask
 | |
|  * CG to draw the text.
 | |
|  * In vibrancy cases, we have to use a private API,
 | |
|  * CGContextSetFontSmoothingBackgroundColor, which sets the expected
 | |
|  * background color the text will draw onto so that CG can render the text
 | |
|  * properly. After that, we have to go back and fixup the pixels
 | |
|  * such that their alpha values are correct.
 | |
|  */
 | |
| bool
 | |
| DrawTargetSkia::FillGlyphsWithCG(ScaledFont *aFont,
 | |
|                                  const GlyphBuffer &aBuffer,
 | |
|                                  const Pattern &aPattern,
 | |
|                                  const DrawOptions &aOptions)
 | |
| {
 | |
|   MOZ_ASSERT(aFont->GetType() == FontType::MAC);
 | |
|   MOZ_ASSERT(aPattern.GetType() == PatternType::COLOR);
 | |
| 
 | |
|   CGContextRef cgContext = BorrowCGContext(aOptions);
 | |
|   if (!cgContext) {
 | |
|     return false;
 | |
|   }
 | |
| 
 | |
|   Vector<CGGlyph,32> glyphs;
 | |
|   Vector<CGPoint,32> positions;
 | |
|   if (!SetupCGGlyphs(cgContext, aBuffer, glyphs, positions)) {
 | |
|     ReturnCGContext(cgContext);
 | |
|     return false;
 | |
|   }
 | |
| 
 | |
|   ScaledFontMac* macFont = static_cast<ScaledFontMac*>(aFont);
 | |
|   SetFontSmoothingBackgroundColor(cgContext, mColorSpace,
 | |
|                                   macFont->FontSmoothingBackgroundColor());
 | |
|   SetFontColor(cgContext, mColorSpace, aPattern);
 | |
| 
 | |
|   if (ScaledFontMac::CTFontDrawGlyphsPtr != nullptr) {
 | |
|     ScaledFontMac::CTFontDrawGlyphsPtr(macFont->mCTFont, glyphs.begin(),
 | |
|                                        positions.begin(),
 | |
|                                        aBuffer.mNumGlyphs, cgContext);
 | |
|   } else {
 | |
|     CGContextSetFont(cgContext, macFont->mFont);
 | |
|     CGContextSetFontSize(cgContext, macFont->mSize);
 | |
|     CGContextShowGlyphsAtPositions(cgContext, glyphs.begin(), positions.begin(),
 | |
|                                    aBuffer.mNumGlyphs);
 | |
|   }
 | |
| 
 | |
|   // Calculate the area of the text we just drew
 | |
|   auto *bboxes = new CGRect[aBuffer.mNumGlyphs];
 | |
|   CTFontGetBoundingRectsForGlyphs(macFont->mCTFont, kCTFontDefaultOrientation,
 | |
|                                   glyphs.begin(), bboxes, aBuffer.mNumGlyphs);
 | |
|   CGRect extents = ComputeGlyphsExtents(bboxes, positions.begin(), aBuffer.mNumGlyphs, 1.0f);
 | |
|   delete[] bboxes;
 | |
| 
 | |
|   CGAffineTransform cgTransform = CGContextGetCTM(cgContext);
 | |
|   extents = CGRectApplyAffineTransform(extents, cgTransform);
 | |
| 
 | |
|   // Have to round it out to ensure we fully cover all pixels
 | |
|   Rect rect(extents.origin.x, extents.origin.y, extents.size.width, extents.size.height);
 | |
|   rect.RoundOut();
 | |
|   extents = CGRectMake(rect.x, rect.y, rect.width, rect.height);
 | |
| 
 | |
|   EnsureValidPremultipliedData(cgContext, extents);
 | |
| 
 | |
|   ReturnCGContext(cgContext);
 | |
|   return true;
 | |
| }
 | |
| 
 | |
| static bool
 | |
| HasFontSmoothingBackgroundColor(ScaledFont* aFont)
 | |
| {
 | |
|   // This should generally only be true if we have a popup context menu
 | |
|   if (aFont && aFont->GetType() == FontType::MAC) {
 | |
|     Color fontSmoothingBackgroundColor =
 | |
|       static_cast<ScaledFontMac*>(aFont)->FontSmoothingBackgroundColor();
 | |
|     return fontSmoothingBackgroundColor.a > 0;
 | |
|   }
 | |
| 
 | |
|   return false;
 | |
| }
 | |
| 
 | |
| static bool
 | |
| ShouldUseCGToFillGlyphs(ScaledFont* aFont, const Pattern& aPattern)
 | |
| {
 | |
|   return HasFontSmoothingBackgroundColor(aFont) &&
 | |
|           aPattern.GetType() == PatternType::COLOR;
 | |
| }
 | |
| 
 | |
| #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();
 | |
| 
 | |
| #ifdef MOZ_WIDGET_COCOA
 | |
|   if (!aStrokeOptions &&
 | |
|       ShouldUseCGToFillGlyphs(aFont, aPattern)) {
 | |
|     if (FillGlyphsWithCG(aFont, aBuffer, aPattern, aOptions)) {
 | |
|       return;
 | |
|     }
 | |
|   }
 | |
| #endif
 | |
| 
 | |
|   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);
 | |
|   paint.mPaint.setTypeface(sk_ref_sp(typeface));
 | |
|   paint.mPaint.setTextSize(SkFloatToScalar(skiaFont->mSize));
 | |
|   paint.mPaint.setTextEncoding(SkPaint::kGlyphID_TextEncoding);
 | |
| 
 | |
|   bool shouldLCDRenderText = ShouldLCDRenderText(aFont->GetType(), aaMode);
 | |
|   paint.mPaint.setLCDRenderText(shouldLCDRenderText);
 | |
| 
 | |
|   bool useSubpixelText = true;
 | |
| 
 | |
|   switch (aFont->GetType()) {
 | |
|   case FontType::FREETYPE:
 | |
|   case FontType::FONTCONFIG:
 | |
|     // SkFontHost_cairo does not support subpixel text positioning,
 | |
|     // so only enable it for other font hosts.
 | |
|     useSubpixelText = false;
 | |
|     break;
 | |
|   case FontType::MAC:
 | |
|     if (aaMode == AntialiasMode::GRAY) {
 | |
|       // Normally, Skia enables LCD FontSmoothing which creates thicker fonts
 | |
|       // and also enables subpixel AA. CoreGraphics without font smoothing
 | |
|       // explicitly creates thinner fonts and grayscale AA.
 | |
|       // CoreGraphics doesn't support a configuration that produces thicker
 | |
|       // fonts with grayscale AA as LCD Font Smoothing enables or disables both.
 | |
|       // However, Skia supports it by enabling font smoothing (producing subpixel AA)
 | |
|       // and converts it to grayscale AA. Since Skia doesn't support subpixel AA on
 | |
|       // transparent backgrounds, we still want font smoothing for the thicker fonts,
 | |
|       // even if it is grayscale AA.
 | |
|       //
 | |
|       // With explicit Grayscale AA (from -moz-osx-font-smoothing:grayscale),
 | |
|       // we want to have grayscale AA with no smoothing at all. This means
 | |
|       // disabling the LCD font smoothing behaviour.
 | |
|       // To accomplish this we have to explicitly disable hinting,
 | |
|       // and disable LCDRenderText.
 | |
|       paint.mPaint.setHinting(SkPaint::kNo_Hinting);
 | |
|     }
 | |
|     break;
 | |
|   case FontType::GDI:
 | |
|   {
 | |
|     if (!shouldLCDRenderText && aaEnabled) {
 | |
|       // If we have non LCD GDI text, render the fonts as cleartype and convert them
 | |
|       // to grayscale. This seems to be what Chrome and IE are doing on Windows 7.
 | |
|       // This also applies if cleartype is disabled system wide.
 | |
|       paint.mPaint.setFlags(paint.mPaint.getFlags() | SkPaint::kGenA8FromLCD_Flag);
 | |
|     }
 | |
|     break;
 | |
|   }
 | |
| #ifdef XP_WIN
 | |
|   case FontType::DWRITE:
 | |
|   {
 | |
|     ScaledFontDWrite* dwriteFont = static_cast<ScaledFontDWrite*>(aFont);
 | |
|     paint.mPaint.setEmbeddedBitmapText(dwriteFont->UseEmbeddedBitmaps());
 | |
| 
 | |
|     if (dwriteFont->ForceGDIMode()) {
 | |
|       paint.mPaint.setEmbeddedBitmapText(true);
 | |
|       useSubpixelText = false;
 | |
|     }
 | |
|     break;
 | |
|   }
 | |
| #endif
 | |
|   default:
 | |
|     break;
 | |
|   }
 | |
| 
 | |
|   paint.mPaint.setSubpixelText(useSubpixelText);
 | |
| 
 | |
|   const uint32_t heapSize = 64;
 | |
|   uint16_t indicesOnStack[heapSize];
 | |
|   SkPoint offsetsOnStack[heapSize];
 | |
|   std::vector<uint16_t> indicesOnHeap;
 | |
|   std::vector<SkPoint> offsetsOnHeap;
 | |
|   uint16_t* indices = indicesOnStack;
 | |
|   SkPoint* offsets = offsetsOnStack;
 | |
|   if (aBuffer.mNumGlyphs > heapSize) {
 | |
|     // Heap allocation/ deallocation is slow, use it only if we need a
 | |
|     // bigger(>heapSize) buffer.
 | |
|     indicesOnHeap.resize(aBuffer.mNumGlyphs);
 | |
|     offsetsOnHeap.resize(aBuffer.mNumGlyphs);
 | |
|     indices = (uint16_t*)&indicesOnHeap.front();
 | |
|     offsets = (SkPoint*)&offsetsOnHeap.front();
 | |
|   }
 | |
| 
 | |
|   for (unsigned int i = 0; i < aBuffer.mNumGlyphs; i++) {
 | |
|     indices[i] = aBuffer.mGlyphs[i].mIndex;
 | |
|     offsets[i].fX = SkFloatToScalar(aBuffer.mGlyphs[i].mPosition.x);
 | |
|     offsets[i].fY = SkFloatToScalar(aBuffer.mGlyphs[i].mPosition.y);
 | |
|   }
 | |
| 
 | |
|   mCanvas->drawPosText(indices, aBuffer.mNumGlyphs*2, offsets, paint.mPaint);
 | |
| }
 | |
| 
 | |
| 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)
 | |
| {
 | |
|   SkIRect maskBounds;
 | |
|   if (!mCanvas->getDeviceClipBounds(&maskBounds)) {
 | |
|       return;
 | |
|   }
 | |
|   SkPoint maskOrigin;
 | |
|   maskOrigin.iset(maskBounds.fLeft, maskBounds.fTop);
 | |
| 
 | |
|   SkMatrix maskMatrix = mCanvas->getTotalMatrix();
 | |
|   maskMatrix.postTranslate(-maskOrigin.fX, -maskOrigin.fY);
 | |
| 
 | |
|   MarkChanged();
 | |
|   AutoPaintSetup paint(mCanvas, aOptions, aSource, nullptr, &maskMatrix);
 | |
| 
 | |
|   SkPaint maskPaint;
 | |
|   SetPaintPattern(maskPaint, aMask);
 | |
| 
 | |
|   SkBitmap maskBitmap;
 | |
|   if (!maskBitmap.tryAllocPixelsFlags(
 | |
|         SkImageInfo::MakeA8(maskBounds.width(), maskBounds.height()),
 | |
|         SkBitmap::kZeroPixels_AllocFlag)) {
 | |
|     return;
 | |
|   }
 | |
| 
 | |
|   SkCanvas maskCanvas(maskBitmap);
 | |
|   maskCanvas.setMatrix(maskMatrix);
 | |
|   maskCanvas.drawPaint(maskPaint);
 | |
| 
 | |
|   mCanvas->save();
 | |
|   mCanvas->resetMatrix();
 | |
| 
 | |
|   mCanvas->drawBitmap(maskBitmap, maskOrigin.fX, maskOrigin.fY, &paint.mPaint);
 | |
| 
 | |
|   mCanvas->restore();
 | |
| }
 | |
| 
 | |
| void
 | |
| DrawTargetSkia::MaskSurface(const Pattern &aSource,
 | |
|                             SourceSurface *aMask,
 | |
|                             Point aOffset,
 | |
|                             const DrawOptions &aOptions)
 | |
| {
 | |
|   MarkChanged();
 | |
| 
 | |
|   SkMatrix invOffset = SkMatrix::MakeTrans(SkFloatToScalar(-aOffset.x), SkFloatToScalar(-aOffset.y));
 | |
|   AutoPaintSetup paint(mCanvas, aOptions, aSource, nullptr, &invOffset);
 | |
| 
 | |
|   sk_sp<SkImage> alphaMask = ExtractAlphaForSurface(aMask);
 | |
|   if (!alphaMask) {
 | |
|     gfxDebug() << *this << ": MaskSurface() failed to extract alpha for mask";
 | |
|     return;
 | |
|   }
 | |
| 
 | |
|   mCanvas->drawImage(alphaMask, aOffset.x, aOffset.y, &paint.mPaint);
 | |
| }
 | |
| 
 | |
| 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.
 | |
|   sk_sp<SkImage> srcImage = GetSkImageForSurface(aSurface);
 | |
|   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.setFilterQuality(kLow_SkFilterQuality);
 | |
|   paint.setBlendMode(SkBlendMode::kSrc);
 | |
| 
 | |
|   SkMatrix xform;
 | |
|   GfxMatrixToSkiaMatrix(fullMat, xform);
 | |
|   dstCanvas->setMatrix(xform);
 | |
| 
 | |
|   dstCanvas->drawImage(srcImage, 0, 0, &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();
 | |
| 
 | |
|   sk_sp<SkImage> image = GetSkImageForSurface(aSurface);
 | |
|   if (!image) {
 | |
|     return true;
 | |
|   }
 | |
| 
 | |
|   mCanvas->save();
 | |
| 
 | |
|   SkPaint paint;
 | |
|   paint.setAntiAlias(true);
 | |
|   paint.setFilterQuality(kLow_SkFilterQuality);
 | |
| 
 | |
|   SkMatrix xform;
 | |
|   GfxMatrixToSkiaMatrix(aMatrix, xform);
 | |
|   mCanvas->concat(xform);
 | |
| 
 | |
|   mCanvas->drawImage(image, 0, 0, &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 USE_SKIA_GPU
 | |
|   if (UsingSkiaGPU()) {
 | |
|     // Try to create a GPU draw target first if we're currently using the GPU.
 | |
|     // Mark the DT as cached so that shadow DTs, extracted subrects, and similar can be reused.
 | |
|     if (target->InitWithGrContext(mGrContext.get(), aSize, aFormat, true)) {
 | |
|       return target.forget();
 | |
|     }
 | |
|     // Otherwise, just fall back to a software draw target.
 | |
|   }
 | |
| #endif
 | |
| 
 | |
| #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::UsingSkiaGPU() const
 | |
| {
 | |
| #ifdef USE_SKIA_GPU
 | |
|   return !!mGrContext;
 | |
| #else
 | |
|   return false;
 | |
| #endif
 | |
| }
 | |
| 
 | |
| #ifdef USE_SKIA_GPU
 | |
| already_AddRefed<SourceSurface>
 | |
| DrawTargetSkia::OptimizeGPUSourceSurface(SourceSurface *aSurface) const
 | |
| {
 | |
|   // Check if the underlying SkImage already has an associated GrTexture.
 | |
|   sk_sp<SkImage> image = GetSkImageForSurface(aSurface);
 | |
|   if (!image || image->isTextureBacked()) {
 | |
|     RefPtr<SourceSurface> surface(aSurface);
 | |
|     return surface.forget();
 | |
|   }
 | |
| 
 | |
|   // Upload the SkImage to a GrTexture otherwise.
 | |
|   sk_sp<SkImage> texture = image->makeTextureImage(mGrContext.get(), nullptr);
 | |
|   if (texture) {
 | |
|     // Create a new SourceSurfaceSkia whose SkImage contains the GrTexture.
 | |
|     RefPtr<SourceSurfaceSkia> surface = new SourceSurfaceSkia();
 | |
|     if (surface->InitFromImage(texture, aSurface->GetFormat())) {
 | |
|       return surface.forget();
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   // The data was too big to fit in a GrTexture.
 | |
|   if (aSurface->GetType() == SurfaceType::SKIA) {
 | |
|     // It is already a Skia source surface, so just reuse it as-is.
 | |
|     RefPtr<SourceSurface> surface(aSurface);
 | |
|     return surface.forget();
 | |
|   }
 | |
| 
 | |
|   // Wrap it in a Skia source surface so that can do tiled uploads on-demand.
 | |
|   RefPtr<SourceSurfaceSkia> surface = new SourceSurfaceSkia();
 | |
|   surface->InitFromImage(image);
 | |
|   return surface.forget();
 | |
| }
 | |
| #endif
 | |
| 
 | |
| already_AddRefed<SourceSurface>
 | |
| DrawTargetSkia::OptimizeSourceSurfaceForUnknownAlpha(SourceSurface *aSurface) const
 | |
| {
 | |
| #ifdef USE_SKIA_GPU
 | |
|   if (UsingSkiaGPU()) {
 | |
|     return OptimizeGPUSourceSurface(aSurface);
 | |
|   }
 | |
| #endif
 | |
| 
 | |
|   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
 | |
| {
 | |
| #ifdef USE_SKIA_GPU
 | |
|   if (UsingSkiaGPU()) {
 | |
|     return OptimizeGPUSourceSurface(aSurface);
 | |
|   }
 | |
| #endif
 | |
| 
 | |
|   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();
 | |
| }
 | |
| 
 | |
| #ifdef USE_SKIA_GPU
 | |
| static inline GrGLenum
 | |
| GfxFormatToGrGLFormat(SurfaceFormat format)
 | |
| {
 | |
|   switch (format)
 | |
|   {
 | |
|     case SurfaceFormat::B8G8R8A8:
 | |
|       return LOCAL_GL_BGRA8_EXT;
 | |
|     case SurfaceFormat::B8G8R8X8:
 | |
|       // We probably need to do something here.
 | |
|       return LOCAL_GL_BGRA8_EXT;
 | |
|     case SurfaceFormat::R5G6B5_UINT16:
 | |
|       return LOCAL_GL_RGB565;
 | |
|     case SurfaceFormat::A8:
 | |
|       return LOCAL_GL_ALPHA8;
 | |
|     default:
 | |
|       return LOCAL_GL_RGBA8;
 | |
|   }
 | |
| }
 | |
| #endif
 | |
| 
 | |
| already_AddRefed<SourceSurface>
 | |
| DrawTargetSkia::CreateSourceSurfaceFromNativeSurface(const NativeSurface &aSurface) const
 | |
| {
 | |
| #ifdef USE_SKIA_GPU
 | |
|   if (aSurface.mType == NativeSurfaceType::OPENGL_TEXTURE && UsingSkiaGPU()) {
 | |
|     // Wrap the OpenGL texture id in a Skia texture handle.
 | |
|     GrGLTextureInfo texInfo;
 | |
|     texInfo.fTarget = LOCAL_GL_TEXTURE_2D;
 | |
|     texInfo.fID = (GrGLuint)(uintptr_t)aSurface.mSurface;
 | |
|     texInfo.fFormat = GfxFormatToGrGLFormat(aSurface.mFormat);
 | |
|     GrBackendTexture texDesc(aSurface.mSize.width,
 | |
|                              aSurface.mSize.height,
 | |
|                              GrMipMapped::kNo,
 | |
|                              texInfo);
 | |
|     sk_sp<SkImage> texture =
 | |
|       SkImage::MakeFromAdoptedTexture(mGrContext.get(), texDesc,
 | |
|                                       kTopLeft_GrSurfaceOrigin,
 | |
|                                       GfxFormatToSkiaColorType(aSurface.mFormat),
 | |
|                                       GfxFormatToSkiaAlphaType(aSurface.mFormat));
 | |
|     RefPtr<SourceSurfaceSkia> newSurf = new SourceSurfaceSkia();
 | |
|     if (texture && newSurf->InitFromImage(texture, aSurface.mFormat)) {
 | |
|       return newSurf.forget();
 | |
|     }
 | |
|     return nullptr;
 | |
|   }
 | |
| #endif
 | |
| 
 | |
|   return nullptr;
 | |
| }
 | |
| 
 | |
| void
 | |
| DrawTargetSkia::CopySurface(SourceSurface *aSurface,
 | |
|                             const IntRect& aSourceRect,
 | |
|                             const IntPoint &aDestination)
 | |
| {
 | |
|   MarkChanged();
 | |
| 
 | |
|   sk_sp<SkImage> image = GetSkImageForSurface(aSurface);
 | |
|   if (!image) {
 | |
|     return;
 | |
|   }
 | |
| 
 | |
|   mCanvas->save();
 | |
|   mCanvas->setMatrix(SkMatrix::MakeTrans(SkIntToScalar(aDestination.x), SkIntToScalar(aDestination.y)));
 | |
|   mCanvas->clipRect(SkRect::MakeIWH(aSourceRect.Width(), aSourceRect.Height()), SkClipOp::kReplace_deprecated);
 | |
| 
 | |
|   SkPaint paint;
 | |
|   if (!image->isOpaque()) {
 | |
|     // Keep the xfermode as SOURCE_OVER for opaque bitmaps
 | |
|     // http://code.google.com/p/skia/issues/detail?id=628
 | |
|     paint.setBlendMode(SkBlendMode::kSrc);
 | |
|   }
 | |
|   // drawImage with A8 images ends up doing a mask operation
 | |
|   // so we need to clear before
 | |
|   if (image->isAlphaOnly()) {
 | |
|     mCanvas->clear(SK_ColorTRANSPARENT);
 | |
|   }
 | |
|   mCanvas->drawImage(image, -SkIntToScalar(aSourceRect.X()), -SkIntToScalar(aSourceRect.Y()), &paint);
 | |
|   mCanvas->restore();
 | |
| }
 | |
| 
 | |
| 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 = SkAlign4(info.minRowBytes());
 | |
|   mSurface = SkSurface::MakeRaster(info, stride, nullptr);
 | |
|   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;
 | |
| }
 | |
| 
 | |
| #ifdef USE_SKIA_GPU
 | |
| /** Indicating a DT should be cached means that space will be reserved in Skia's cache
 | |
|  * for the render target at creation time, with any unused resources exceeding the cache
 | |
|  * limits being purged. When the DT is freed, it will then be guaranteed to be kept around
 | |
|  * for subsequent allocations until it gets incidentally purged.
 | |
|  *
 | |
|  * If it is not marked as cached, no space will be purged to make room for the render
 | |
|  * target in the cache. When the DT is freed, If there is space within the resource limits
 | |
|  * it may be added to the cache, otherwise it will be freed immediately if the cache is
 | |
|  * already full.
 | |
|  *
 | |
|  * If you want to ensure that the resources will be kept around for reuse, it is better
 | |
|  * to mark them as cached. Such resources should be short-lived to ensure they don't
 | |
|  * permanently tie up cache resource limits. Long-lived resources should generally be
 | |
|  * left as uncached.
 | |
|  *
 | |
|  * In neither case will cache resource limits affect whether the resource allocation
 | |
|  * succeeds. The amount of in-use GPU resources is allowed to exceed the size of the cache.
 | |
|  * Thus, only hard GPU out-of-memory conditions will cause resource allocation to fail.
 | |
|  */
 | |
| bool
 | |
| DrawTargetSkia::InitWithGrContext(GrContext* aGrContext,
 | |
|                                   const IntSize &aSize,
 | |
|                                   SurfaceFormat aFormat,
 | |
|                                   bool aCached)
 | |
| {
 | |
|   MOZ_ASSERT(aGrContext, "null GrContext");
 | |
| 
 | |
|   if (size_t(std::max(aSize.width, aSize.height)) > GetMaxSurfaceSize()) {
 | |
|     return false;
 | |
|   }
 | |
| 
 | |
|   // Create a GPU rendertarget/texture using the supplied GrContext.
 | |
|   // MakeRenderTarget also implicitly clears the underlying texture on creation.
 | |
|   mSurface =
 | |
|     SkSurface::MakeRenderTarget(aGrContext,
 | |
|                                 SkBudgeted(aCached),
 | |
|                                 MakeSkiaImageInfo(aSize, aFormat));
 | |
|   if (!mSurface) {
 | |
|     return false;
 | |
|   }
 | |
| 
 | |
|   mGrContext = sk_ref_sp(aGrContext);
 | |
|   mSize = aSize;
 | |
|   mFormat = aFormat;
 | |
|   mCanvas = mSurface->getCanvas();
 | |
|   SetPermitSubpixelAA(IsOpaque(mFormat));
 | |
|   return true;
 | |
| }
 | |
| 
 | |
| #endif
 | |
| 
 | |
| 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));
 | |
| 
 | |
|   mSurface = SkSurface::MakeRasterDirect(MakeSkiaImageInfo(aSize, aFormat), aData, aStride);
 | |
|   if (!mSurface) {
 | |
|     return false;
 | |
|   }
 | |
| 
 | |
|   mSize = aSize;
 | |
|   mFormat = aFormat;
 | |
|   mCanvas = mSurface->getCanvas();
 | |
|   SetPermitSubpixelAA(IsOpaque(mFormat));
 | |
|   return true;
 | |
| }
 | |
| 
 | |
| void
 | |
| DrawTargetSkia::SetTransform(const Matrix& aTransform)
 | |
| {
 | |
|   SkMatrix mat;
 | |
|   GfxMatrixToSkiaMatrix(aTransform, mat);
 | |
|   mCanvas->setMatrix(mat);
 | |
|   mTransform = aTransform;
 | |
| }
 | |
| 
 | |
| void*
 | |
| DrawTargetSkia::GetNativeSurface(NativeSurfaceType aType)
 | |
| {
 | |
| #ifdef USE_SKIA_GPU
 | |
|   if (aType == NativeSurfaceType::OPENGL_TEXTURE && mSurface) {
 | |
|     GrBackendObject handle = mSurface->getTextureHandle(SkSurface::kFlushRead_BackendHandleAccess);
 | |
|     if (handle) {
 | |
|       return (void*)(uintptr_t)reinterpret_cast<GrGLTextureInfo *>(handle)->fID;
 | |
|     }
 | |
|   }
 | |
| #endif
 | |
|   return nullptr;
 | |
| }
 | |
| 
 | |
| 
 | |
| already_AddRefed<PathBuilder>
 | |
| DrawTargetSkia::CreatePathBuilder(FillRule aFillRule) const
 | |
| {
 | |
|   return MakeAndAddRef<PathBuilderSkia>(aFillRule);
 | |
| }
 | |
| 
 | |
| void
 | |
| DrawTargetSkia::ClearRect(const Rect &aRect)
 | |
| {
 | |
|   MarkChanged();
 | |
|   mCanvas->save();
 | |
|   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());
 | |
| }
 | |
| 
 | |
| 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)
 | |
| {
 | |
|   PushedLayer layer(GetPermitSubpixelAA(), aMask);
 | |
|   mPushedLayers.push_back(layer);
 | |
| 
 | |
|   SkPaint paint;
 | |
| 
 | |
|   paint.setAlpha(ColorFloatToByte(aOpacity));
 | |
|   paint.setBlendMode(GfxOpToSkiaOp(aCompositionOp));
 | |
| 
 | |
|   // aBounds is supplied in device space, but SaveLayerRec wants local space.
 | |
|   SkRect bounds = IntRectToSkRect(aBounds);
 | |
|   if (!bounds.isEmpty()) {
 | |
|     SkMatrix inverseCTM;
 | |
|     if (mCanvas->getTotalMatrix().invert(&inverseCTM)) {
 | |
|       inverseCTM.mapRect(&bounds);
 | |
|     } else {
 | |
|       bounds.setEmpty();
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   sk_sp<SkImage> clipImage = aMask ? GetSkImageForSurface(aMask) : nullptr;
 | |
|   SkMatrix clipMatrix;
 | |
|   GfxMatrixToSkiaMatrix(aMaskTransform, clipMatrix);
 | |
|   SkCanvas::SaveLayerRec saveRec(aBounds.IsEmpty() ? nullptr : &bounds,
 | |
|                                  &paint,
 | |
|                                  nullptr,
 | |
|                                  clipImage.get(),
 | |
|                                  &clipMatrix,
 | |
|                                  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()
 | |
| {
 | |
|   MarkChanged();
 | |
| 
 | |
|   MOZ_ASSERT(mPushedLayers.size());
 | |
|   const PushedLayer& layer = mPushedLayers.back();
 | |
| 
 | |
|   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 gfx
 | |
| } // namespace mozilla
 |