/* -*- 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/. */
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.
*/ staticvoid ReleaseTemporarySurface(constvoid* aPixels, void* aContext) {
DataSourceSurface* surf = static_cast<DataSourceSurface*>(aContext); if (surf) {
surf->Release();
}
}
staticbool VerifyRGBXFormat(uint8_t* aData, const IntSize& aSize, const int32_t aStride, SurfaceFormat aFormat) { if (aFormat != SurfaceFormat::B8G8R8X8 || aSize.IsEmpty()) { returntrue;
} // 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;
// Since checking every pixel is expensive, this only checks the four corners // and center of a surface that their alpha value is 0xFF. staticbool 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()) { returntrue;
}
if (aSurface->GetType() == SurfaceType::SKIA) { returnstatic_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)(constvoid*, 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;
}
// 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";
}
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;
}
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);
}
staticinline 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);
}
// TODO: Can we set greyscale somehow? if (aOptions.mAntialiasMode != AntialiasMode::NONE) {
mPaint.setAntiAlias(true);
} else {
mPaint.setAntiAlias(false);
}
// 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;
};
// 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.
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();
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;
}
}
}
staticDouble 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;
}
// 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);
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;
}
}
/*** * 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.
*/ staticbool 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())); returntrue;
} // 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 (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();
}
}
staticbool CanDrawFont(ScaledFont* aFont) { switch (aFont->GetType()) { case FontType::FREETYPE: case FontType::FONTCONFIG: case FontType::MAC: case FontType::GDI: case FontType::DWRITE: returntrue; default: returnfalse;
}
}
if (aShader) {
paint.mPaint.setShader(sk_ref_sp(aShader));
}
// 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);
}
}
// This shader overrides the luminance color used to generate the preblend // tables for glyphs, without actually changing the rasterized color. This is // necesary for subpixel AA blending which requires both the mask and color // as separate inputs. class GlyphMaskShader : public SkEmptyShader { public: explicit GlyphMaskShader(const DeviceColor& aColor)
: mColor({aColor.r, aColor.g, aColor.b, aColor.a}) {}
void DrawTargetSkia::DrawGlyphMask(ScaledFont* aFont, const GlyphBuffer& aBuffer, const DeviceColor& aColor, const StrokeOptions* aStrokeOptions, const DrawOptions& aOptions) { // Draw a mask using the GlyphMaskShader that can be used for subpixel AA // but that uses the gamma preblend weighting of the given color, even though // the mask itself does not use that color.
sk_sp<GlyphMaskShader> shader = sk_make_sp<GlyphMaskShader>(aColor);
DrawGlyphs(aFont, aBuffer, ColorPattern(DeviceColor(1, 1, 1, 1)),
aStrokeOptions, aOptions, shader.get());
}
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()) { returnfalse;
} // 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()) { returntrue;
} // 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) { returntrue;
}
// 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) { returnfalse;
}
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();
}
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;
}
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();
}
}
// 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();
}
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;
}
// 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) { returnfalse;
}
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()) { returnfalse;
} void* buf = sk_malloc_flags(size.value(), SK_MALLOC_ZERO_INITIALIZE); if (!buf) { returnfalse;
}
mSurface = AsRefPtr(SkSurfaces::WrapPixels(
info, buf, stride, FreeAlphaPixels, nullptr, &props));
} else {
mSurface = AsRefPtr(SkSurfaces::Raster(info, stride, &props));
} if (!mSurface) { returnfalse;
}
// 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);
}
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung ist noch experimentell.