| /* |
| * 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/SkColorFilter.h" |
| #include "include/core/SkImage.h" |
| #include "include/core/SkImageInfo.h" |
| #include "include/core/SkMatrix.h" |
| #include "include/core/SkPoint.h" |
| #include "include/core/SkRect.h" |
| #include "include/core/SkTypes.h" |
| #include "include/private/base/SkTArray.h" |
| #include "include/private/base/SkTemplates.h" |
| #include "src/core/SkImageFilterCache.h" |
| #include "src/core/SkImageFilterTypes.h" |
| #include "src/core/SkImageFilter_Base.h" |
| #include "src/core/SkLocalMatrixImageFilter.h" |
| #include "src/core/SkPicturePriv.h" |
| #include "src/core/SkReadBuffer.h" |
| #include "src/core/SkRectPriv.h" |
| #include "src/core/SkSpecialImage.h" |
| #include "src/core/SkValidationUtils.h" |
| #include "src/core/SkWriteBuffer.h" |
| #include "src/effects/colorfilters/SkColorFilterBase.h" |
| |
| #include <algorithm> |
| #include <atomic> |
| #include <cstdint> |
| #include <optional> |
| #include <utility> |
| |
| /////////////////////////////////////////////////////////////////////////////////////////////////// |
| // SkImageFilter - A number of the public APIs on SkImageFilter downcast to SkImageFilter_Base |
| // in order to perform their actual work. |
| /////////////////////////////////////////////////////////////////////////////////////////////////// |
| |
| /** |
| * Returns the number of inputs this filter will accept (some inputs can |
| * be NULL). |
| */ |
| int SkImageFilter::countInputs() const { return as_IFB(this)->fInputs.count(); } |
| |
| /** |
| * Returns the input filter at a given index, or NULL if no input is |
| * connected. The indices used are filter-specific. |
| */ |
| const SkImageFilter* SkImageFilter::getInput(int i) const { |
| SkASSERT(i < this->countInputs()); |
| return as_IFB(this)->fInputs[i].get(); |
| } |
| |
| bool SkImageFilter::isColorFilterNode(SkColorFilter** filterPtr) const { |
| return as_IFB(this)->onIsColorFilterNode(filterPtr); |
| } |
| |
| SkIRect SkImageFilter::filterBounds(const SkIRect& src, const SkMatrix& ctm, |
| MapDirection direction, const SkIRect* inputRect) const { |
| // The old filterBounds() function uses SkIRects that are defined in layer space so, while |
| // we still are supporting it, bypass SkIF_B's new public filter bounds functions and go right |
| // to the internal layer-space calculations. |
| skif::Mapping mapping{ctm}; |
| if (kReverse_MapDirection == direction) { |
| skif::LayerSpace<SkIRect> targetOutput(src); |
| std::optional<skif::LayerSpace<SkIRect>> content; |
| if (inputRect) { |
| content = skif::LayerSpace<SkIRect>(*inputRect); |
| } |
| return SkIRect(as_IFB(this)->onGetInputLayerBounds(mapping, targetOutput, content)); |
| } else { |
| SkASSERT(!inputRect); |
| skif::LayerSpace<SkIRect> content(src); |
| auto output = as_IFB(this)->onGetOutputLayerBounds(mapping, content); |
| return output ? SkIRect(*output) : SkRectPriv::MakeILarge(); |
| } |
| } |
| |
| 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++) { |
| const SkImageFilter* input = this->getInput(i); |
| if (input) { |
| combinedBounds.join(input->computeFastBounds(src)); |
| } else { |
| combinedBounds.join(src); |
| } |
| } |
| return combinedBounds; |
| } |
| |
| bool SkImageFilter::canComputeFastBounds() const { |
| return !as_IFB(this)->affectsTransparentBlack(); |
| } |
| |
| bool SkImageFilter_Base::affectsTransparentBlack() const { |
| if (this->onAffectsTransparentBlack()) { |
| return true; |
| } else if (this->ignoreInputsAffectsTransparentBlack()) { |
| // TODO(skbug.com/14611): Automatically infer this from output bounds being finite |
| return false; |
| } |
| for (int i = 0; i < this->countInputs(); i++) { |
| const SkImageFilter* input = this->getInput(i); |
| if (input && as_IFB(input)->affectsTransparentBlack()) { |
| return true; |
| } |
| } |
| return false; |
| } |
| |
| bool SkImageFilter::asAColorFilter(SkColorFilter** filterPtr) const { |
| SkASSERT(nullptr != filterPtr); |
| if (!this->isColorFilterNode(filterPtr)) { |
| return false; |
| } |
| if (nullptr != this->getInput(0) || as_CFB(*filterPtr)->affectsTransparentBlack()) { |
| (*filterPtr)->unref(); |
| return false; |
| } |
| return true; |
| } |
| |
| sk_sp<SkImageFilter> SkImageFilter::makeWithLocalMatrix(const SkMatrix& matrix) const { |
| return SkLocalMatrixImageFilter::Make(matrix, this->refMe()); |
| } |
| |
| /////////////////////////////////////////////////////////////////////////////////////////////////// |
| // SkImageFilter_Base |
| /////////////////////////////////////////////////////////////////////////////////////////////////// |
| |
| static int32_t next_image_filter_unique_id() { |
| static std::atomic<int32_t> nextID{1}; |
| |
| int32_t id; |
| do { |
| id = nextID.fetch_add(1, std::memory_order_relaxed); |
| } while (id == 0); |
| return id; |
| } |
| |
| SkImageFilter_Base::SkImageFilter_Base(sk_sp<SkImageFilter> const* inputs, |
| int inputCount, |
| std::optional<bool> usesSrc) |
| : fUsesSrcInput(usesSrc.has_value() ? *usesSrc : false) |
| , fUniqueID(next_image_filter_unique_id()) { |
| fInputs.reset(inputCount); |
| |
| for (int i = 0; i < inputCount; ++i) { |
| if (!usesSrc.has_value() && (!inputs[i] || as_IFB(inputs[i])->usesSource())) { |
| fUsesSrcInput = true; |
| } |
| fInputs[i] = inputs[i]; |
| } |
| } |
| |
| SkImageFilter_Base::~SkImageFilter_Base() { |
| SkImageFilterCache::Get()->purgeByImageFilter(this); |
| } |
| |
| std::pair<sk_sp<SkImageFilter>, std::optional<SkRect>> |
| SkImageFilter_Base::Unflatten(SkReadBuffer& buffer) { |
| Common common; |
| if (!common.unflatten(buffer, 1)) { |
| return {nullptr, std::nullopt}; |
| } else { |
| return {common.getInput(0), common.cropRect()}; |
| } |
| } |
| |
| bool SkImageFilter_Base::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; |
| } |
| |
| #if defined(SK_BUILD_FOR_FUZZER) |
| if (count > 4) { |
| return false; |
| } |
| #endif |
| |
| SkASSERT(fInputs.empty()); |
| for (int i = 0; i < count; i++) { |
| fInputs.push_back(buffer.readBool() ? buffer.readImageFilter() : nullptr); |
| if (!buffer.isValid()) { |
| return false; |
| } |
| } |
| |
| if (buffer.isVersionLT(SkPicturePriv::kRemoveDeprecatedCropRect)) { |
| static constexpr uint32_t kHasAll_CropEdge = 0x0F; |
| SkRect rect; |
| buffer.readRect(&rect); |
| if (!buffer.isValid() || !buffer.validate(SkIsValidRect(rect))) { |
| return false; |
| } |
| |
| uint32_t flags = buffer.readUInt(); |
| if (!buffer.isValid() || |
| !buffer.validate(flags == 0x0 || flags == kHasAll_CropEdge)) { |
| return false; |
| } |
| if (flags == kHasAll_CropEdge) { |
| fCropRect = rect; |
| } |
| } |
| return buffer.isValid(); |
| } |
| |
| void SkImageFilter_Base::flatten(SkWriteBuffer& buffer) const { |
| buffer.writeInt(fInputs.count()); |
| for (int i = 0; i < fInputs.count(); i++) { |
| const SkImageFilter* input = this->getInput(i); |
| buffer.writeBool(input != nullptr); |
| if (input != nullptr) { |
| buffer.writeFlattenable(input); |
| } |
| } |
| } |
| |
| skif::FilterResult SkImageFilter_Base::filterImage(const skif::Context& context) const { |
| context.markVisitedImageFilter(); |
| |
| skif::FilterResult result; |
| if (context.desiredOutput().isEmpty() || !context.mapping().layerMatrix().isFinite()) { |
| return result; |
| } |
| |
| // Some image filters that operate on the source image still affect transparent black, so if |
| // there is clipping, we may have optimized away the source image as an empty input, but still |
| // need to run the filter on it. This means `fUsesSrcInput` is not equivalent to the source |
| // being non-null. |
| const bool srcInKey = fUsesSrcInput && context.source(); |
| uint32_t srcGenID = srcInKey ? context.source().image()->uniqueID() : SK_InvalidUniqueID; |
| const SkIRect srcSubset = srcInKey ? context.source().image()->subset() : SkIRect::MakeWH(0, 0); |
| |
| SkImageFilterCacheKey key(fUniqueID, |
| context.mapping().layerMatrix(), |
| SkIRect(context.desiredOutput()), |
| srcGenID, srcSubset); |
| if (context.backend()->cache() && context.backend()->cache()->get(key, &result)) { |
| context.markCacheHit(); |
| return result; |
| } |
| |
| result = this->onFilterImage(context); |
| |
| if (context.backend()->cache()) { |
| context.backend()->cache()->set(key, this, result); |
| } |
| |
| return result; |
| } |
| |
| sk_sp<SkImage> SkImageFilter_Base::makeImageWithFilter(sk_sp<skif::Backend> backend, |
| sk_sp<SkImage> src, |
| const SkIRect& subset, |
| const SkIRect& clipBounds, |
| SkIRect* outSubset, |
| SkIPoint* offset) const { |
| if (!outSubset || !offset || !src->bounds().contains(subset)) { |
| return nullptr; |
| } |
| |
| auto srcSpecialImage = backend->makeImage(subset, src); |
| if (!srcSpecialImage) { |
| return nullptr; |
| } |
| |
| skif::Stats stats; |
| const skif::Context context{std::move(backend), |
| skif::Mapping(SkMatrix::I()), |
| skif::LayerSpace<SkIRect>(clipBounds), |
| skif::FilterResult(std::move(srcSpecialImage), |
| skif::LayerSpace<SkIPoint>(subset.topLeft())), |
| src->imageInfo().colorSpace(), |
| &stats}; |
| |
| sk_sp<SkSpecialImage> result = this->filterImage(context).imageAndOffset(context, offset); |
| stats.reportStats(); |
| |
| if (!result) { |
| return nullptr; |
| } |
| |
| SkASSERT(clipBounds.contains(SkIRect::MakeXYWH(offset->fX, offset->fY, |
| result->width(), result->height()))); |
| *outSubset = result->subset(); |
| return result->asImage(); |
| } |
| |
| skif::LayerSpace<SkIRect> SkImageFilter_Base::getInputBounds( |
| const skif::Mapping& mapping, |
| const skif::DeviceSpace<SkIRect>& desiredOutput, |
| std::optional<skif::ParameterSpace<SkRect>> knownContentBounds) const { |
| // Map both the device-space desired coverage area and the known content bounds to layer space |
| skif::LayerSpace<SkIRect> desiredBounds = mapping.deviceToLayer(desiredOutput); |
| |
| // If we have no known content bounds, leave 'contentBounds' uninstantiated to represent |
| // infinite possible content. |
| std::optional<skif::LayerSpace<SkIRect>> contentBounds; |
| if (knownContentBounds) { |
| contentBounds = mapping.paramToLayer(*knownContentBounds).roundOut(); |
| } |
| |
| // Process the layer-space desired output with the filter DAG to determine required input |
| return this->onGetInputLayerBounds(mapping, desiredBounds, contentBounds); |
| } |
| |
| std::optional<skif::DeviceSpace<SkIRect>> SkImageFilter_Base::getOutputBounds( |
| const skif::Mapping& mapping, |
| const skif::ParameterSpace<SkRect>& contentBounds) const { |
| // Map the input content into the layer space where filtering will occur |
| skif::LayerSpace<SkRect> layerContent = mapping.paramToLayer(contentBounds); |
| // Determine the filter DAGs output bounds in layer space |
| std::optional<skif::LayerSpace<SkIRect>> filterOutput = |
| this->onGetOutputLayerBounds(mapping, layerContent.roundOut()); |
| if (filterOutput) { |
| // Map all the way to device space |
| return mapping.layerToDevice(*filterOutput); |
| } else { |
| // Infinite layer output is infinite device-space output too |
| return {}; |
| } |
| } |
| |
| SkImageFilter_Base::MatrixCapability SkImageFilter_Base::getCTMCapability() const { |
| MatrixCapability result = this->onGetCTMCapability(); |
| const int count = this->countInputs(); |
| for (int i = 0; i < count; ++i) { |
| if (const SkImageFilter_Base* input = as_IFB(this->getInput(i))) { |
| result = std::min(result, input->getCTMCapability()); |
| } |
| } |
| return result; |
| } |
| |
| skif::LayerSpace<SkIRect> SkImageFilter_Base::getChildInputLayerBounds( |
| int index, |
| const skif::Mapping& mapping, |
| const skif::LayerSpace<SkIRect>& desiredOutput, |
| std::optional<skif::LayerSpace<SkIRect>> contentBounds) const { |
| // The required input for childFilter filter, or 'contentBounds' intersected with |
| // 'desiredOutput' if the filter is null and the source image is used (i.e. the identity filter) |
| const SkImageFilter* childFilter = this->getInput(index); |
| if (childFilter) { |
| return as_IFB(childFilter)->onGetInputLayerBounds(mapping, desiredOutput, contentBounds); |
| } else { |
| // NOTE: We don't calculate the intersection between content and root desired output because |
| // the desired output can expand or contract as it propagates through the filter graph to |
| // the leaves that would actually sample from the source content. |
| skif::LayerSpace<SkIRect> visibleContent = desiredOutput; |
| if (contentBounds && !visibleContent.intersect(*contentBounds)) { |
| return skif::LayerSpace<SkIRect>::Empty(); |
| } else { |
| // This will be equal to 'desiredOutput' if the contentBounds are unknown. |
| return visibleContent; |
| } |
| } |
| } |
| |
| std::optional<skif::LayerSpace<SkIRect>> SkImageFilter_Base::getChildOutputLayerBounds( |
| int index, |
| const skif::Mapping& mapping, |
| std::optional<skif::LayerSpace<SkIRect>> contentBounds) const { |
| // The output for just childFilter filter, or 'contentBounds' if the filter is null and |
| // the source image is used (i.e. the identity filter applied to the source). |
| const SkImageFilter* childFilter = this->getInput(index); |
| return childFilter ? as_IFB(childFilter)->onGetOutputLayerBounds(mapping, contentBounds) |
| : contentBounds; |
| } |
| |
| skif::FilterResult SkImageFilter_Base::getChildOutput(int index, const skif::Context& ctx) const { |
| const SkImageFilter* input = this->getInput(index); |
| return input ? as_IFB(input)->filterImage(ctx) : ctx.source(); |
| } |
| |
| void SkImageFilter_Base::PurgeCache() { |
| auto cache = SkImageFilterCache::Get(SkImageFilterCache::CreateIfNecessary::kNo); |
| if (cache) { |
| cache->purge(); |
| } |
| } |