/* * Copyright 2015 Google Inc. * * Use of this source code is governed by a BSD-style license that can be * found in the LICENSE file. */ #include "GrBlurUtils.h" #include "GrDrawContext.h" #include "GrContext.h" #include "effects/GrSimpleTextureEffect.h" #include "GrStrokeInfo.h" #include "GrTexture.h" #include "GrTextureProvider.h" #include "SkDraw.h" #include "SkGr.h" #include "SkMaskFilter.h" #include "SkPaint.h" static bool clip_bounds_quick_reject(const SkIRect& clipBounds, const SkIRect& rect) { return clipBounds.isEmpty() || rect.isEmpty() || !SkIRect::Intersects(clipBounds, rect); } // Draw a mask using the supplied paint. Since the coverage/geometry // is already burnt into the mask this boils down to a rect draw. // Return true if the mask was successfully drawn. static bool draw_mask(GrDrawContext* drawContext, GrRenderTarget* rt, const GrClip& clip, const SkMatrix& viewMatrix, const SkRect& maskRect, GrPaint* grp, GrTexture* mask) { SkMatrix matrix; matrix.setTranslate(-maskRect.fLeft, -maskRect.fTop); matrix.postIDiv(mask->width(), mask->height()); grp->addCoverageProcessor(GrSimpleTextureEffect::Create(mask, matrix, kDevice_GrCoordSet))->unref(); SkMatrix inverse; if (!viewMatrix.invert(&inverse)) { return false; } drawContext->drawNonAARectWithLocalMatrix(rt, clip, *grp, SkMatrix::I(), maskRect, inverse); return true; } static bool draw_with_mask_filter(GrDrawContext* drawContext, GrTextureProvider* textureProvider, GrRenderTarget* rt, const GrClip& clipData, const SkMatrix& viewMatrix, const SkPath& devPath, SkMaskFilter* filter, const SkIRect& clipBounds, GrPaint* grp, SkPaint::Style style) { SkMask srcM, dstM; if (!SkDraw::DrawToMask(devPath, &clipBounds, filter, &viewMatrix, &srcM, SkMask::kComputeBoundsAndRenderImage_CreateMode, style)) { return false; } SkAutoMaskFreeImage autoSrc(srcM.fImage); if (!filter->filterMask(&dstM, srcM, viewMatrix, NULL)) { return false; } // this will free-up dstM when we're done (allocated in filterMask()) SkAutoMaskFreeImage autoDst(dstM.fImage); if (clip_bounds_quick_reject(clipBounds, dstM.fBounds)) { return false; } // we now have a device-aligned 8bit mask in dstM, ready to be drawn using // the current clip (and identity matrix) and GrPaint settings GrSurfaceDesc desc; desc.fWidth = dstM.fBounds.width(); desc.fHeight = dstM.fBounds.height(); desc.fConfig = kAlpha_8_GrPixelConfig; SkAutoTUnref texture(textureProvider->refScratchTexture( desc, GrTextureProvider::kApprox_ScratchTexMatch)); if (!texture) { return false; } texture->writePixels(0, 0, desc.fWidth, desc.fHeight, desc.fConfig, dstM.fImage, dstM.fRowBytes); SkRect maskRect = SkRect::Make(dstM.fBounds); return draw_mask(drawContext, rt, clipData, viewMatrix, maskRect, grp, texture); } // Create a mask of 'devPath' and place the result in 'mask'. static GrTexture* create_mask_GPU(GrContext* context, const SkRect& maskRect, const SkPath& devPath, const GrStrokeInfo& strokeInfo, bool doAA, int sampleCnt) { GrSurfaceDesc desc; desc.fFlags = kRenderTarget_GrSurfaceFlag; desc.fWidth = SkScalarCeilToInt(maskRect.width()); desc.fHeight = SkScalarCeilToInt(maskRect.height()); desc.fSampleCnt = doAA ? sampleCnt : 0; // We actually only need A8, but it often isn't supported as a // render target so default to RGBA_8888 desc.fConfig = kRGBA_8888_GrPixelConfig; if (context->isConfigRenderable(kAlpha_8_GrPixelConfig, desc.fSampleCnt > 0)) { desc.fConfig = kAlpha_8_GrPixelConfig; } GrTexture* mask = context->textureProvider()->refScratchTexture( desc, GrTextureProvider::kApprox_ScratchTexMatch); if (NULL == mask) { return NULL; } SkRect clipRect = SkRect::MakeWH(maskRect.width(), maskRect.height()); GrDrawContext* drawContext = context->drawContext(); if (!drawContext) { return NULL; } drawContext->clear(mask->asRenderTarget(), NULL, 0x0, true); GrPaint tempPaint; tempPaint.setAntiAlias(doAA); tempPaint.setCoverageSetOpXPFactory(SkRegion::kReplace_Op); // setup new clip GrClip clip(clipRect); // Draw the mask into maskTexture with the path's top-left at the origin using tempPaint. SkMatrix translate; translate.setTranslate(-maskRect.fLeft, -maskRect.fTop); drawContext->drawPath(mask->asRenderTarget(), clip, tempPaint, translate, devPath, strokeInfo); return mask; } void GrBlurUtils::drawPathWithMaskFilter(GrContext* context, GrDrawContext* drawContext, GrRenderTarget* renderTarget, const GrClip& clip, const SkPath& origSrcPath, const SkPaint& paint, const SkMatrix& origViewMatrix, const SkMatrix* prePathMatrix, const SkIRect& clipBounds, bool pathIsMutable) { SkASSERT(!pathIsMutable || origSrcPath.isVolatile()); GrStrokeInfo strokeInfo(paint); // If we have a prematrix, apply it to the path, optimizing for the case // where the original path can in fact be modified in place (even though // its parameter type is const). SkPath* pathPtr = const_cast(&origSrcPath); SkTLazy tmpPath; SkTLazy effectPath; SkPathEffect* pathEffect = paint.getPathEffect(); SkMatrix viewMatrix = origViewMatrix; if (prePathMatrix) { // stroking, path effects, and blurs are supposed to be applied *after* the prePathMatrix. // The pre-path-matrix also should not affect shading. if (NULL == paint.getMaskFilter() && NULL == pathEffect && NULL == paint.getShader() && (strokeInfo.isFillStyle() || strokeInfo.isHairlineStyle())) { viewMatrix.preConcat(*prePathMatrix); } else { SkPath* result = pathPtr; if (!pathIsMutable) { result = tmpPath.init(); result->setIsVolatile(true); pathIsMutable = true; } // should I push prePathMatrix on our MV stack temporarily, instead // of applying it here? See SkDraw.cpp pathPtr->transform(*prePathMatrix, result); pathPtr = result; } } // at this point we're done with prePathMatrix SkDEBUGCODE(prePathMatrix = (const SkMatrix*)0x50FF8001;) GrPaint grPaint; if (!SkPaint2GrPaint(context, renderTarget, paint, viewMatrix, true, &grPaint)) { return; } const SkRect* cullRect = NULL; // TODO: what is our bounds? if (!strokeInfo.isDashed() && pathEffect && pathEffect->filterPath(effectPath.init(), *pathPtr, &strokeInfo, cullRect)) { pathPtr = effectPath.get(); pathIsMutable = true; } if (paint.getMaskFilter()) { if (!strokeInfo.isHairlineStyle()) { SkPath* strokedPath = pathIsMutable ? pathPtr : tmpPath.init(); if (strokeInfo.isDashed()) { if (pathEffect->filterPath(strokedPath, *pathPtr, &strokeInfo, cullRect)) { pathPtr = strokedPath; pathIsMutable = true; } strokeInfo.removeDash(); } if (strokeInfo.applyToPath(strokedPath, *pathPtr)) { pathPtr = strokedPath; pathIsMutable = true; strokeInfo.setFillStyle(); } } // avoid possibly allocating a new path in transform if we can SkPath* devPathPtr = pathIsMutable ? pathPtr : tmpPath.init(); if (!pathIsMutable) { devPathPtr->setIsVolatile(true); } // transform the path into device space pathPtr->transform(viewMatrix, devPathPtr); SkRect maskRect; if (paint.getMaskFilter()->canFilterMaskGPU(devPathPtr->getBounds(), clipBounds, viewMatrix, &maskRect)) { SkIRect finalIRect; maskRect.roundOut(&finalIRect); if (clip_bounds_quick_reject(clipBounds, finalIRect)) { // clipped out return; } if (paint.getMaskFilter()->directFilterMaskGPU(context, renderTarget, &grPaint, clip, viewMatrix, strokeInfo, *devPathPtr)) { // the mask filter was able to draw itself directly, so there's nothing // left to do. return; } SkAutoTUnref mask(create_mask_GPU(context, maskRect, *devPathPtr, strokeInfo, grPaint.isAntiAlias(), renderTarget->numSamples())); if (mask) { GrTexture* filtered; if (paint.getMaskFilter()->filterMaskGPU(mask, viewMatrix, maskRect, &filtered, true)) { // filterMaskGPU gives us ownership of a ref to the result SkAutoTUnref atu(filtered); if (draw_mask(drawContext, renderTarget, clip, viewMatrix, maskRect, &grPaint, filtered)) { // This path is completely drawn return; } } } } // draw the mask on the CPU - this is a fallthrough path in case the // GPU path fails SkPaint::Style style = strokeInfo.isHairlineStyle() ? SkPaint::kStroke_Style : SkPaint::kFill_Style; draw_with_mask_filter(drawContext, context->textureProvider(), renderTarget, clip, viewMatrix, *devPathPtr, paint.getMaskFilter(), clipBounds, &grPaint, style); return; } drawContext->drawPath(renderTarget, clip, grPaint, viewMatrix, *pathPtr, strokeInfo); }