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