123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584 |
- /*
- * Copyright 2012 The Android Open Source Project
- *
- * Use of this source code is governed by a BSD-style license that can be
- * found in the LICENSE file.
- */
- #include "include/core/SkImageFilter.h"
- #include "include/core/SkCanvas.h"
- #include "include/core/SkRect.h"
- #include "include/effects/SkComposeImageFilter.h"
- #include "include/private/SkSafe32.h"
- #include "src/core/SkFuzzLogging.h"
- #include "src/core/SkImageFilterCache.h"
- #include "src/core/SkLocalMatrixImageFilter.h"
- #include "src/core/SkMatrixImageFilter.h"
- #include "src/core/SkReadBuffer.h"
- #include "src/core/SkSpecialImage.h"
- #include "src/core/SkSpecialSurface.h"
- #include "src/core/SkValidationUtils.h"
- #include "src/core/SkWriteBuffer.h"
- #if SK_SUPPORT_GPU
- #include "include/gpu/GrContext.h"
- #include "include/private/GrRecordingContext.h"
- #include "src/gpu/GrColorSpaceXform.h"
- #include "src/gpu/GrContextPriv.h"
- #include "src/gpu/GrFixedClip.h"
- #include "src/gpu/GrRecordingContextPriv.h"
- #include "src/gpu/GrRenderTargetContext.h"
- #include "src/gpu/GrTextureProxy.h"
- #include "src/gpu/SkGr.h"
- #endif
- #include <atomic>
- void SkImageFilter::CropRect::applyTo(const SkIRect& imageBounds,
- const SkMatrix& ctm,
- bool embiggen,
- SkIRect* cropped) const {
- *cropped = imageBounds;
- if (fFlags) {
- SkRect devCropR;
- ctm.mapRect(&devCropR, fRect);
- SkIRect devICropR = devCropR.roundOut();
- // Compute the left/top first, in case we need to modify the right/bottom for a missing edge
- if (fFlags & kHasLeft_CropEdge) {
- if (embiggen || devICropR.fLeft > cropped->fLeft) {
- cropped->fLeft = devICropR.fLeft;
- }
- } else {
- devICropR.fRight = Sk32_sat_add(cropped->fLeft, devICropR.width());
- }
- if (fFlags & kHasTop_CropEdge) {
- if (embiggen || devICropR.fTop > cropped->fTop) {
- cropped->fTop = devICropR.fTop;
- }
- } else {
- devICropR.fBottom = Sk32_sat_add(cropped->fTop, devICropR.height());
- }
- if (fFlags & kHasWidth_CropEdge) {
- if (embiggen || devICropR.fRight < cropped->fRight) {
- cropped->fRight = devICropR.fRight;
- }
- }
- if (fFlags & kHasHeight_CropEdge) {
- if (embiggen || devICropR.fBottom < cropped->fBottom) {
- cropped->fBottom = devICropR.fBottom;
- }
- }
- }
- }
- ///////////////////////////////////////////////////////////////////////////////////////////////////
- static int32_t next_image_filter_unique_id() {
- static std::atomic<int32_t> nextID{1};
- int32_t id;
- do {
- id = nextID++;
- } while (id == 0);
- return id;
- }
- bool SkImageFilter::Common::unflatten(SkReadBuffer& buffer, int expectedCount) {
- const int count = buffer.readInt();
- if (!buffer.validate(count >= 0)) {
- return false;
- }
- if (!buffer.validate(expectedCount < 0 || count == expectedCount)) {
- return false;
- }
- SkASSERT(fInputs.empty());
- for (int i = 0; i < count; i++) {
- fInputs.push_back(buffer.readBool() ? buffer.readImageFilter() : nullptr);
- if (!buffer.isValid()) {
- return false;
- }
- }
- SkRect rect;
- buffer.readRect(&rect);
- if (!buffer.isValid() || !buffer.validate(SkIsValidRect(rect))) {
- return false;
- }
- uint32_t flags = buffer.readUInt();
- fCropRect = CropRect(rect, flags);
- return buffer.isValid();
- }
- ///////////////////////////////////////////////////////////////////////////////////////////////////
- void SkImageFilter::init(sk_sp<SkImageFilter> const* inputs,
- int inputCount,
- const CropRect* cropRect) {
- fCropRect = cropRect ? *cropRect : CropRect(SkRect(), 0x0);
- fInputs.reset(inputCount);
- for (int i = 0; i < inputCount; ++i) {
- if (!inputs[i] || inputs[i]->usesSrcInput()) {
- fUsesSrcInput = true;
- }
- fInputs[i] = inputs[i];
- }
- }
- SkImageFilter::SkImageFilter(sk_sp<SkImageFilter> const* inputs,
- int inputCount,
- const CropRect* cropRect)
- : fUsesSrcInput(false)
- , fUniqueID(next_image_filter_unique_id()) {
- this->init(inputs, inputCount, cropRect);
- }
- SkImageFilter::~SkImageFilter() {
- SkImageFilterCache::Get()->purgeByImageFilter(this);
- }
- SkImageFilter::SkImageFilter(int inputCount, SkReadBuffer& buffer)
- : fUsesSrcInput(false)
- , fCropRect(SkRect(), 0x0)
- , fUniqueID(next_image_filter_unique_id()) {
- Common common;
- if (common.unflatten(buffer, inputCount)) {
- this->init(common.inputs(), common.inputCount(), &common.cropRect());
- }
- }
- void SkImageFilter::flatten(SkWriteBuffer& buffer) const {
- buffer.writeInt(fInputs.count());
- for (int i = 0; i < fInputs.count(); i++) {
- SkImageFilter* input = this->getInput(i);
- buffer.writeBool(input != nullptr);
- if (input != nullptr) {
- buffer.writeFlattenable(input);
- }
- }
- buffer.writeRect(fCropRect.rect());
- buffer.writeUInt(fCropRect.flags());
- }
- sk_sp<SkSpecialImage> SkImageFilter::filterImage(SkSpecialImage* src, const Context& context,
- SkIPoint* offset) const {
- SkASSERT(src && offset);
- if (!context.isValid()) {
- return nullptr;
- }
- uint32_t srcGenID = fUsesSrcInput ? src->uniqueID() : 0;
- const SkIRect srcSubset = fUsesSrcInput ? src->subset() : SkIRect::MakeWH(0, 0);
- SkImageFilterCacheKey key(fUniqueID, context.ctm(), context.clipBounds(), srcGenID, srcSubset);
- if (context.cache()) {
- sk_sp<SkSpecialImage> result = context.cache()->get(key, offset);
- if (result) {
- return result;
- }
- }
- sk_sp<SkSpecialImage> result(this->onFilterImage(src, context, offset));
- #if SK_SUPPORT_GPU
- if (src->isTextureBacked() && result && !result->isTextureBacked()) {
- // Keep the result on the GPU - this is still required for some
- // image filters that don't support GPU in all cases
- auto context = src->getContext();
- result = result->makeTextureImage(context);
- }
- #endif
- if (result && context.cache()) {
- context.cache()->set(key, result.get(), *offset, this);
- }
- return result;
- }
- SkIRect SkImageFilter::filterBounds(const SkIRect& src, const SkMatrix& ctm,
- MapDirection direction, const SkIRect* inputRect) const {
- if (kReverse_MapDirection == direction) {
- SkIRect bounds = this->onFilterNodeBounds(src, ctm, direction, inputRect);
- return this->onFilterBounds(bounds, ctm, direction, &bounds);
- } else {
- SkASSERT(!inputRect);
- SkIRect bounds = this->onFilterBounds(src, ctm, direction, nullptr);
- bounds = this->onFilterNodeBounds(bounds, ctm, direction, nullptr);
- SkIRect dst;
- this->getCropRect().applyTo(bounds, ctm, this->affectsTransparentBlack(), &dst);
- return dst;
- }
- }
- SkRect SkImageFilter::computeFastBounds(const SkRect& src) const {
- if (0 == this->countInputs()) {
- return src;
- }
- SkRect combinedBounds = this->getInput(0) ? this->getInput(0)->computeFastBounds(src) : src;
- for (int i = 1; i < this->countInputs(); i++) {
- SkImageFilter* input = this->getInput(i);
- if (input) {
- combinedBounds.join(input->computeFastBounds(src));
- } else {
- combinedBounds.join(src);
- }
- }
- return combinedBounds;
- }
- bool SkImageFilter::canComputeFastBounds() const {
- if (this->affectsTransparentBlack()) {
- return false;
- }
- for (int i = 0; i < this->countInputs(); i++) {
- SkImageFilter* input = this->getInput(i);
- if (input && !input->canComputeFastBounds()) {
- return false;
- }
- }
- return true;
- }
- #if SK_SUPPORT_GPU
- sk_sp<SkSpecialImage> SkImageFilter::DrawWithFP(GrRecordingContext* context,
- std::unique_ptr<GrFragmentProcessor> fp,
- const SkIRect& bounds,
- const OutputProperties& outputProperties,
- GrProtected isProtected) {
- GrPaint paint;
- paint.addColorFragmentProcessor(std::move(fp));
- paint.setPorterDuffXPFactory(SkBlendMode::kSrc);
- sk_sp<SkColorSpace> colorSpace = sk_ref_sp(outputProperties.colorSpace());
- GrColorType colorType = SkColorTypeToGrColorType(outputProperties.colorType());
- sk_sp<GrRenderTargetContext> renderTargetContext(
- context->priv().makeDeferredRenderTargetContext(
- SkBackingFit::kApprox,
- bounds.width(),
- bounds.height(),
- colorType,
- std::move(colorSpace),
- 1,
- GrMipMapped::kNo,
- kBottomLeft_GrSurfaceOrigin,
- nullptr,
- SkBudgeted::kYes,
- isProtected));
- if (!renderTargetContext) {
- return nullptr;
- }
- SkIRect dstIRect = SkIRect::MakeWH(bounds.width(), bounds.height());
- SkRect srcRect = SkRect::Make(bounds);
- SkRect dstRect = SkRect::MakeWH(srcRect.width(), srcRect.height());
- GrFixedClip clip(dstIRect);
- renderTargetContext->fillRectToRect(clip, std::move(paint), GrAA::kNo, SkMatrix::I(), dstRect,
- srcRect);
- return SkSpecialImage::MakeDeferredFromGpu(
- context, dstIRect, kNeedNewImageUniqueID_SpecialImage,
- renderTargetContext->asTextureProxyRef(),
- renderTargetContext->colorSpaceInfo().refColorSpace());
- }
- #endif
- bool SkImageFilter::asAColorFilter(SkColorFilter** filterPtr) const {
- SkASSERT(nullptr != filterPtr);
- if (!this->isColorFilterNode(filterPtr)) {
- return false;
- }
- if (nullptr != this->getInput(0) || (*filterPtr)->affectsTransparentBlack()) {
- (*filterPtr)->unref();
- return false;
- }
- return true;
- }
- bool SkImageFilter::canHandleComplexCTM() const {
- // CropRects need to apply in the source coordinate system, but are not aware of complex CTMs
- // when performing clipping. For a simple fix, any filter with a crop rect set cannot support
- // complex CTMs until that's updated.
- if (this->cropRectIsSet() || !this->onCanHandleComplexCTM()) {
- return false;
- }
- const int count = this->countInputs();
- for (int i = 0; i < count; ++i) {
- SkImageFilter* input = this->getInput(i);
- if (input && !input->canHandleComplexCTM()) {
- return false;
- }
- }
- return true;
- }
- bool SkImageFilter::applyCropRect(const Context& ctx, const SkIRect& srcBounds,
- SkIRect* dstBounds) const {
- SkIRect tmpDst = this->onFilterNodeBounds(srcBounds, ctx.ctm(), kForward_MapDirection, nullptr);
- fCropRect.applyTo(tmpDst, ctx.ctm(), this->affectsTransparentBlack(), dstBounds);
- // Intersect against the clip bounds, in case the crop rect has
- // grown the bounds beyond the original clip. This can happen for
- // example in tiling, where the clip is much smaller than the filtered
- // primitive. If we didn't do this, we would be processing the filter
- // at the full crop rect size in every tile.
- return dstBounds->intersect(ctx.clipBounds());
- }
- #if SK_SUPPORT_GPU
- sk_sp<SkSpecialImage> SkImageFilter::ImageToColorSpace(SkSpecialImage* src,
- const OutputProperties& outProps) {
- // There are several conditions that determine if we actually need to convert the source to the
- // destination's color space. Rather than duplicate that logic here, just try to make an xform
- // object. If that produces something, then both are tagged, and the source is in a different
- // gamut than the dest. There is some overhead to making the xform, but those are cached, and
- // if we get one back, that means we're about to use it during the conversion anyway.
- auto colorSpaceXform = GrColorSpaceXform::Make(src->getColorSpace(), src->alphaType(),
- outProps.colorSpace(), kPremul_SkAlphaType);
- if (!colorSpaceXform) {
- // No xform needed, just return the original image
- return sk_ref_sp(src);
- }
- sk_sp<SkSpecialSurface> surf(src->makeSurface(outProps,
- SkISize::Make(src->width(), src->height())));
- if (!surf) {
- return sk_ref_sp(src);
- }
- SkCanvas* canvas = surf->getCanvas();
- SkASSERT(canvas);
- SkPaint p;
- p.setBlendMode(SkBlendMode::kSrc);
- src->draw(canvas, 0, 0, &p);
- return surf->makeImageSnapshot();
- }
- #endif
- // Return a larger (newWidth x newHeight) copy of 'src' with black padding
- // around it.
- static sk_sp<SkSpecialImage> pad_image(SkSpecialImage* src,
- const SkImageFilter::OutputProperties& outProps,
- int newWidth, int newHeight, int offX, int offY) {
- // We would like to operate in the source's color space (so that we return an "identical"
- // image, other than the padding. To achieve that, we'd create new output properties:
- //
- // SkImageFilter::OutputProperties outProps(src->getColorSpace());
- //
- // That fails in at least two ways. For formats that are texturable but not renderable (like
- // F16 on some ES implementations), we can't create a surface to do the work. For sRGB, images
- // may be tagged with an sRGB color space (which leads to an sRGB config in makeSurface). But
- // the actual config of that sRGB image on a device with no sRGB support is non-sRGB.
- //
- // Rather than try to special case these situations, we execute the image padding in the
- // destination color space. This should not affect the output of the DAG in (almost) any case,
- // because the result of this call is going to be used as an input, where it would have been
- // switched to the destination space anyway. The one exception would be a filter that expected
- // to consume unclamped F16 data, but the padded version of the image is pre-clamped to 8888.
- // We can revisit this logic if that ever becomes an actual problem.
- sk_sp<SkSpecialSurface> surf(src->makeSurface(outProps, SkISize::Make(newWidth, newHeight)));
- if (!surf) {
- return nullptr;
- }
- SkCanvas* canvas = surf->getCanvas();
- SkASSERT(canvas);
- canvas->clear(0x0);
- src->draw(canvas, offX, offY, nullptr);
- return surf->makeImageSnapshot();
- }
- sk_sp<SkSpecialImage> SkImageFilter::applyCropRectAndPad(const Context& ctx,
- SkSpecialImage* src,
- SkIPoint* srcOffset,
- SkIRect* bounds) const {
- const SkIRect srcBounds = SkIRect::MakeXYWH(srcOffset->x(), srcOffset->y(),
- src->width(), src->height());
- if (!this->applyCropRect(ctx, srcBounds, bounds)) {
- return nullptr;
- }
- if (srcBounds.contains(*bounds)) {
- return sk_sp<SkSpecialImage>(SkRef(src));
- } else {
- sk_sp<SkSpecialImage> img(pad_image(src, ctx.outputProperties(),
- bounds->width(), bounds->height(),
- Sk32_sat_sub(srcOffset->x(), bounds->x()),
- Sk32_sat_sub(srcOffset->y(), bounds->y())));
- *srcOffset = SkIPoint::Make(bounds->x(), bounds->y());
- return img;
- }
- }
- SkIRect SkImageFilter::onFilterBounds(const SkIRect& src, const SkMatrix& ctm,
- MapDirection dir, const SkIRect* inputRect) const {
- if (this->countInputs() < 1) {
- return src;
- }
- SkIRect totalBounds;
- for (int i = 0; i < this->countInputs(); ++i) {
- SkImageFilter* filter = this->getInput(i);
- SkIRect rect = filter ? filter->filterBounds(src, ctm, dir, inputRect) : src;
- if (0 == i) {
- totalBounds = rect;
- } else {
- totalBounds.join(rect);
- }
- }
- return totalBounds;
- }
- SkIRect SkImageFilter::onFilterNodeBounds(const SkIRect& src, const SkMatrix&,
- MapDirection, const SkIRect*) const {
- return src;
- }
- SkImageFilter::Context SkImageFilter::mapContext(const Context& ctx) const {
- SkIRect clipBounds = this->onFilterNodeBounds(ctx.clipBounds(), ctx.ctm(),
- MapDirection::kReverse_MapDirection,
- &ctx.clipBounds());
- return Context(ctx.ctm(), clipBounds, ctx.cache(), ctx.outputProperties());
- }
- sk_sp<SkImageFilter> SkImageFilter::MakeMatrixFilter(const SkMatrix& matrix,
- SkFilterQuality filterQuality,
- sk_sp<SkImageFilter> input) {
- return SkMatrixImageFilter::Make(matrix, filterQuality, std::move(input));
- }
- sk_sp<SkImageFilter> SkImageFilter::makeWithLocalMatrix(const SkMatrix& matrix) const {
- return SkLocalMatrixImageFilter::Make(matrix, this->refMe());
- }
- sk_sp<SkSpecialImage> SkImageFilter::filterInput(int index,
- SkSpecialImage* src,
- const Context& ctx,
- SkIPoint* offset) const {
- SkImageFilter* input = this->getInput(index);
- if (!input) {
- return sk_sp<SkSpecialImage>(SkRef(src));
- }
- sk_sp<SkSpecialImage> result(input->filterImage(src, this->mapContext(ctx), offset));
- SkASSERT(!result || src->isTextureBacked() == result->isTextureBacked());
- return result;
- }
- void SkImageFilter::PurgeCache() {
- SkImageFilterCache::Get()->purge();
- }
- // In repeat mode, when we are going to sample off one edge of the srcBounds we require the
- // opposite side be preserved.
- SkIRect SkImageFilter::DetermineRepeatedSrcBound(const SkIRect& srcBounds,
- const SkIVector& filterOffset,
- const SkISize& filterSize,
- const SkIRect& originalSrcBounds) {
- SkIRect tmp = srcBounds;
- tmp.adjust(-filterOffset.fX, -filterOffset.fY,
- filterSize.fWidth - filterOffset.fX, filterSize.fHeight - filterOffset.fY);
- if (tmp.fLeft < originalSrcBounds.fLeft || tmp.fRight > originalSrcBounds.fRight) {
- tmp.fLeft = originalSrcBounds.fLeft;
- tmp.fRight = originalSrcBounds.fRight;
- }
- if (tmp.fTop < originalSrcBounds.fTop || tmp.fBottom > originalSrcBounds.fBottom) {
- tmp.fTop = originalSrcBounds.fTop;
- tmp.fBottom = originalSrcBounds.fBottom;
- }
- return tmp;
- }
- /////////////////////////////////////////////////////////////////////////////////////////////////
- static sk_sp<SkImageFilter> apply_ctm_to_filter(sk_sp<SkImageFilter> input, const SkMatrix& ctm,
- SkMatrix* remainder, bool asBackdrop) {
- if (ctm.isScaleTranslate() || input->canHandleComplexCTM()) {
- // The filter supports the CTM, so leave it as-is and 'remainder' stores the whole CTM
- *remainder = ctm;
- return input;
- }
- // We have a complex CTM and a filter that can't support them, so it needs to use the matrix
- // transform filter that resamples the image contents. Decompose the simple portion of the ctm
- // into 'remainder'
- SkMatrix ctmToEmbed;
- SkSize scale;
- if (ctm.decomposeScale(&scale, &ctmToEmbed)) {
- // decomposeScale splits ctm into scale * ctmToEmbed, so bake ctmToEmbed into DAG
- // with a matrix filter and return scale as the remaining matrix for the real CTM.
- remainder->setScale(scale.fWidth, scale.fHeight);
- // ctmToEmbed is passed to SkMatrixImageFilter, which performs its transforms as if it were
- // a pre-transformation before applying the image-filter context's CTM. In this case, we
- // need ctmToEmbed to be a post-transformation (i.e. after the scale matrix since
- // decomposeScale produces ctm = ctmToEmbed * scale). Giving scale^-1 * ctmToEmbed * scale
- // to the matrix filter achieves this effect.
- // TODO (michaelludwig) - When the original root node of a filter can be drawn directly to a
- // device using ctmToEmbed, this abuse of SkMatrixImageFilter can go away.
- ctmToEmbed.preScale(scale.fWidth, scale.fHeight);
- ctmToEmbed.postScale(1.f / scale.fWidth, 1.f / scale.fHeight);
- } else {
- // Unable to decompose
- // FIXME Ideally we'd embed the entire CTM as part of the matrix image filter, but
- // the device <-> src bounds calculations for filters are very brittle under perspective,
- // and can easily run into precision issues (wrong bounds that clip), or performance issues
- // (producing large source-space images where 80% of the image is compressed into a few
- // device pixels). A longer term solution for perspective-space image filtering is needed
- // see skbug.com/9074
- if (ctm.hasPerspective()) {
- *remainder = ctm;
- return input;
- }
- ctmToEmbed = ctm;
- remainder->setIdentity();
- }
- if (asBackdrop) {
- // In the backdrop case we also have to transform the existing device-space buffer content
- // into the source coordinate space prior to the filtering. Non-backdrop filter inputs are
- // already in the source space because of how the layer is drawn by SkCanvas.
- SkMatrix invEmbed;
- if (ctmToEmbed.invert(&invEmbed)) {
- input = SkComposeImageFilter::Make(std::move(input),
- SkMatrixImageFilter::Make(invEmbed, kLow_SkFilterQuality, nullptr));
- }
- }
- return SkMatrixImageFilter::Make(ctmToEmbed, kLow_SkFilterQuality, std::move(input));
- }
- sk_sp<SkImageFilter> SkApplyCTMToFilter(const SkImageFilter* filter, const SkMatrix& ctm,
- SkMatrix* remainder) {
- return apply_ctm_to_filter(sk_ref_sp(filter), ctm, remainder, false);
- }
- sk_sp<SkImageFilter> SkApplyCTMToBackdropFilter(const SkImageFilter* filter, const SkMatrix& ctm,
- SkMatrix* remainder) {
- return apply_ctm_to_filter(sk_ref_sp(filter), ctm, remainder, true);
- }
- bool SkIsSameFilter(const SkImageFilter* a, const SkImageFilter* b) {
- if (!a || !b) {
- // The filters are the "same" if they're both null
- return !a && !b;
- } else {
- return a->fUniqueID == b->fUniqueID;
- }
- }
- SkIRect SkFilterNodeBounds(const SkImageFilter* filter, const SkIRect& srcRect, const SkMatrix& ctm,
- SkImageFilter::MapDirection dir, const SkIRect* inputRect) {
- return filter->onFilterNodeBounds(srcRect, ctm, dir, inputRect);
- }
|