| /* |
| * Copyright (C) 2011-2017 Apple Inc. All rights reserved. |
| * Copyright (C) 2013 Google Inc. All rights reserved. |
| * |
| * Redistribution and use in source and binary forms, with or without |
| * modification, are permitted provided that the following conditions |
| * are met: |
| * 1. Redistributions of source code must retain the above copyright |
| * notice, this list of conditions and the following disclaimer. |
| * 2. Redistributions in binary form must reproduce the above copyright |
| * notice, this list of conditions and the following disclaimer in the |
| * documentation and/or other materials provided with the distribution. |
| * |
| * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY |
| * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE |
| * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR |
| * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR |
| * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, |
| * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, |
| * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR |
| * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY |
| * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT |
| * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE |
| * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
| */ |
| |
| #include "config.h" |
| #include "CSSFilter.h" |
| |
| #include "CachedSVGDocument.h" |
| #include "CachedSVGDocumentReference.h" |
| #include "ElementIterator.h" |
| #include "FEColorMatrix.h" |
| #include "FEComponentTransfer.h" |
| #include "FEDropShadow.h" |
| #include "FEGaussianBlur.h" |
| #include "FEMerge.h" |
| #include "Logging.h" |
| #include "RenderLayer.h" |
| #include "SVGElement.h" |
| #include "SVGFilterBuilder.h" |
| #include "SVGFilterPrimitiveStandardAttributes.h" |
| #include "SourceAlpha.h" |
| #include "SourceGraphic.h" |
| #include <algorithm> |
| #include <wtf/MathExtras.h> |
| |
| #if USE(DIRECT2D) |
| #include <d2d1.h> |
| #endif |
| |
| namespace WebCore { |
| |
| static inline void endMatrixRow(Vector<float>& parameters) |
| { |
| parameters.append(0); |
| parameters.append(0); |
| } |
| |
| static inline void lastMatrixRow(Vector<float>& parameters) |
| { |
| parameters.append(0); |
| parameters.append(0); |
| parameters.append(0); |
| parameters.append(1); |
| parameters.append(0); |
| } |
| |
| Ref<CSSFilter> CSSFilter::create() |
| { |
| return adoptRef(*new CSSFilter); |
| } |
| |
| CSSFilter::CSSFilter() |
| : Filter(FloatSize { 1, 1 }) |
| , m_sourceGraphic(SourceGraphic::create(*this)) |
| { |
| } |
| |
| CSSFilter::~CSSFilter() = default; |
| |
| GraphicsContext* CSSFilter::inputContext() |
| { |
| return sourceImage() ? &sourceImage()->context() : nullptr; |
| } |
| |
| RefPtr<FilterEffect> CSSFilter::buildReferenceFilter(RenderElement& renderer, FilterEffect& previousEffect, ReferenceFilterOperation& filterOperation) |
| { |
| auto* cachedSVGDocumentReference = filterOperation.cachedSVGDocumentReference(); |
| auto* cachedSVGDocument = cachedSVGDocumentReference ? cachedSVGDocumentReference->document() : nullptr; |
| |
| // If we have an SVG document, this is an external reference. Otherwise |
| // we look up the referenced node in the current document. |
| Document* document; |
| if (!cachedSVGDocument) |
| document = &renderer.document(); |
| else { |
| document = cachedSVGDocument->document(); |
| if (!document) |
| return nullptr; |
| } |
| |
| auto* filter = document->getElementById(filterOperation.fragment()); |
| if (!filter) { |
| // Although we did not find the referenced filter, it might exist later in the document. |
| // FIXME: This skips anonymous RenderObjects. <https://webkit.org/b/131085> |
| if (auto* element = renderer.element()) |
| document->accessSVGExtensions().addPendingResource(filterOperation.fragment(), *element); |
| return nullptr; |
| } |
| |
| RefPtr<FilterEffect> effect; |
| |
| auto builder = std::make_unique<SVGFilterBuilder>(&previousEffect); |
| m_sourceAlpha = builder->getEffectById(SourceAlpha::effectName()); |
| |
| for (auto& effectElement : childrenOfType<SVGFilterPrimitiveStandardAttributes>(*filter)) { |
| effect = effectElement.build(builder.get(), *this); |
| if (!effect) |
| continue; |
| |
| effectElement.setStandardAttributes(effect.get()); |
| if (effectElement.renderer()) |
| effect->setOperatingColorSpace(effectElement.renderer()->style().svgStyle().colorInterpolationFilters() == ColorInterpolation::LinearRGB ? ColorSpaceLinearRGB : ColorSpaceSRGB); |
| |
| builder->add(effectElement.result(), effect); |
| m_effects.append(*effect); |
| } |
| return effect; |
| } |
| |
| bool CSSFilter::build(RenderElement& renderer, const FilterOperations& operations, FilterConsumer consumer) |
| { |
| m_hasFilterThatMovesPixels = operations.hasFilterThatMovesPixels(); |
| m_hasFilterThatShouldBeRestrictedBySecurityOrigin = operations.hasFilterThatShouldBeRestrictedBySecurityOrigin(); |
| if (m_hasFilterThatMovesPixels) |
| m_outsets = operations.outsets(); |
| |
| m_effects.clear(); |
| |
| RefPtr<FilterEffect> previousEffect = m_sourceGraphic.ptr(); |
| for (auto& operation : operations.operations()) { |
| RefPtr<FilterEffect> effect; |
| auto& filterOperation = *operation; |
| switch (filterOperation.type()) { |
| case FilterOperation::REFERENCE: { |
| auto& referenceOperation = downcast<ReferenceFilterOperation>(filterOperation); |
| effect = buildReferenceFilter(renderer, *previousEffect, referenceOperation); |
| break; |
| } |
| case FilterOperation::GRAYSCALE: { |
| auto& colorMatrixOperation = downcast<BasicColorMatrixFilterOperation>(filterOperation); |
| Vector<float> inputParameters; |
| double oneMinusAmount = clampTo(1 - colorMatrixOperation.amount(), 0.0, 1.0); |
| |
| // See https://dvcs.w3.org/hg/FXTF/raw-file/tip/filters/index.html#grayscaleEquivalent |
| // for information on parameters. |
| |
| inputParameters.append(narrowPrecisionToFloat(0.2126 + 0.7874 * oneMinusAmount)); |
| inputParameters.append(narrowPrecisionToFloat(0.7152 - 0.7152 * oneMinusAmount)); |
| inputParameters.append(narrowPrecisionToFloat(0.0722 - 0.0722 * oneMinusAmount)); |
| endMatrixRow(inputParameters); |
| |
| inputParameters.append(narrowPrecisionToFloat(0.2126 - 0.2126 * oneMinusAmount)); |
| inputParameters.append(narrowPrecisionToFloat(0.7152 + 0.2848 * oneMinusAmount)); |
| inputParameters.append(narrowPrecisionToFloat(0.0722 - 0.0722 * oneMinusAmount)); |
| endMatrixRow(inputParameters); |
| |
| inputParameters.append(narrowPrecisionToFloat(0.2126 - 0.2126 * oneMinusAmount)); |
| inputParameters.append(narrowPrecisionToFloat(0.7152 - 0.7152 * oneMinusAmount)); |
| inputParameters.append(narrowPrecisionToFloat(0.0722 + 0.9278 * oneMinusAmount)); |
| endMatrixRow(inputParameters); |
| |
| lastMatrixRow(inputParameters); |
| |
| effect = FEColorMatrix::create(*this, FECOLORMATRIX_TYPE_MATRIX, inputParameters); |
| break; |
| } |
| case FilterOperation::SEPIA: { |
| auto& colorMatrixOperation = downcast<BasicColorMatrixFilterOperation>(filterOperation); |
| Vector<float> inputParameters; |
| double oneMinusAmount = clampTo(1 - colorMatrixOperation.amount(), 0.0, 1.0); |
| |
| // See https://dvcs.w3.org/hg/FXTF/raw-file/tip/filters/index.html#sepiaEquivalent |
| // for information on parameters. |
| |
| inputParameters.append(narrowPrecisionToFloat(0.393 + 0.607 * oneMinusAmount)); |
| inputParameters.append(narrowPrecisionToFloat(0.769 - 0.769 * oneMinusAmount)); |
| inputParameters.append(narrowPrecisionToFloat(0.189 - 0.189 * oneMinusAmount)); |
| endMatrixRow(inputParameters); |
| |
| inputParameters.append(narrowPrecisionToFloat(0.349 - 0.349 * oneMinusAmount)); |
| inputParameters.append(narrowPrecisionToFloat(0.686 + 0.314 * oneMinusAmount)); |
| inputParameters.append(narrowPrecisionToFloat(0.168 - 0.168 * oneMinusAmount)); |
| endMatrixRow(inputParameters); |
| |
| inputParameters.append(narrowPrecisionToFloat(0.272 - 0.272 * oneMinusAmount)); |
| inputParameters.append(narrowPrecisionToFloat(0.534 - 0.534 * oneMinusAmount)); |
| inputParameters.append(narrowPrecisionToFloat(0.131 + 0.869 * oneMinusAmount)); |
| endMatrixRow(inputParameters); |
| |
| lastMatrixRow(inputParameters); |
| |
| effect = FEColorMatrix::create(*this, FECOLORMATRIX_TYPE_MATRIX, inputParameters); |
| break; |
| } |
| case FilterOperation::SATURATE: { |
| auto& colorMatrixOperation = downcast<BasicColorMatrixFilterOperation>(filterOperation); |
| Vector<float> inputParameters; |
| inputParameters.append(narrowPrecisionToFloat(colorMatrixOperation.amount())); |
| effect = FEColorMatrix::create(*this, FECOLORMATRIX_TYPE_SATURATE, inputParameters); |
| break; |
| } |
| case FilterOperation::HUE_ROTATE: { |
| auto& colorMatrixOperation = downcast<BasicColorMatrixFilterOperation>(filterOperation); |
| Vector<float> inputParameters; |
| inputParameters.append(narrowPrecisionToFloat(colorMatrixOperation.amount())); |
| effect = FEColorMatrix::create(*this, FECOLORMATRIX_TYPE_HUEROTATE, inputParameters); |
| break; |
| } |
| case FilterOperation::INVERT: { |
| auto& componentTransferOperation = downcast<BasicComponentTransferFilterOperation>(filterOperation); |
| ComponentTransferFunction transferFunction; |
| transferFunction.type = FECOMPONENTTRANSFER_TYPE_TABLE; |
| Vector<float> transferParameters; |
| transferParameters.append(narrowPrecisionToFloat(componentTransferOperation.amount())); |
| transferParameters.append(narrowPrecisionToFloat(1 - componentTransferOperation.amount())); |
| transferFunction.tableValues = transferParameters; |
| |
| ComponentTransferFunction nullFunction; |
| effect = FEComponentTransfer::create(*this, transferFunction, transferFunction, transferFunction, nullFunction); |
| break; |
| } |
| case FilterOperation::APPLE_INVERT_LIGHTNESS: |
| ASSERT_NOT_REACHED(); // APPLE_INVERT_LIGHTNESS is only used in -apple-color-filter. |
| break; |
| case FilterOperation::OPACITY: { |
| auto& componentTransferOperation = downcast<BasicComponentTransferFilterOperation>(filterOperation); |
| ComponentTransferFunction transferFunction; |
| transferFunction.type = FECOMPONENTTRANSFER_TYPE_TABLE; |
| Vector<float> transferParameters; |
| transferParameters.append(0); |
| transferParameters.append(narrowPrecisionToFloat(componentTransferOperation.amount())); |
| transferFunction.tableValues = transferParameters; |
| |
| ComponentTransferFunction nullFunction; |
| effect = FEComponentTransfer::create(*this, nullFunction, nullFunction, nullFunction, transferFunction); |
| break; |
| } |
| case FilterOperation::BRIGHTNESS: { |
| auto& componentTransferOperation = downcast<BasicComponentTransferFilterOperation>(filterOperation); |
| ComponentTransferFunction transferFunction; |
| transferFunction.type = FECOMPONENTTRANSFER_TYPE_LINEAR; |
| transferFunction.slope = narrowPrecisionToFloat(componentTransferOperation.amount()); |
| transferFunction.intercept = 0; |
| |
| ComponentTransferFunction nullFunction; |
| effect = FEComponentTransfer::create(*this, transferFunction, transferFunction, transferFunction, nullFunction); |
| break; |
| } |
| case FilterOperation::CONTRAST: { |
| auto& componentTransferOperation = downcast<BasicComponentTransferFilterOperation>(filterOperation); |
| ComponentTransferFunction transferFunction; |
| transferFunction.type = FECOMPONENTTRANSFER_TYPE_LINEAR; |
| float amount = narrowPrecisionToFloat(componentTransferOperation.amount()); |
| transferFunction.slope = amount; |
| transferFunction.intercept = -0.5 * amount + 0.5; |
| |
| ComponentTransferFunction nullFunction; |
| effect = FEComponentTransfer::create(*this, transferFunction, transferFunction, transferFunction, nullFunction); |
| break; |
| } |
| case FilterOperation::BLUR: { |
| auto& blurOperation = downcast<BlurFilterOperation>(filterOperation); |
| float stdDeviation = floatValueForLength(blurOperation.stdDeviation(), 0); |
| effect = FEGaussianBlur::create(*this, stdDeviation, stdDeviation, consumer == FilterConsumer::FilterProperty ? EDGEMODE_NONE : EDGEMODE_DUPLICATE); |
| break; |
| } |
| case FilterOperation::DROP_SHADOW: { |
| auto& dropShadowOperation = downcast<DropShadowFilterOperation>(filterOperation); |
| effect = FEDropShadow::create(*this, dropShadowOperation.stdDeviation(), dropShadowOperation.stdDeviation(), |
| dropShadowOperation.x(), dropShadowOperation.y(), dropShadowOperation.color(), 1); |
| break; |
| } |
| default: |
| break; |
| } |
| |
| if (effect) { |
| // Unlike SVG Filters and CSSFilterImages, filter functions on the filter |
| // property applied here should not clip to their primitive subregions. |
| effect->setClipsToBounds(consumer == FilterConsumer::FilterFunction); |
| effect->setOperatingColorSpace(ColorSpaceSRGB); |
| |
| if (filterOperation.type() != FilterOperation::REFERENCE) { |
| effect->inputEffects().append(WTFMove(previousEffect)); |
| m_effects.append(*effect); |
| } |
| previousEffect = WTFMove(effect); |
| } |
| } |
| |
| // If we didn't make any effects, tell our caller we are not valid. |
| if (m_effects.isEmpty()) |
| return false; |
| |
| setMaxEffectRects(m_sourceDrawingRegion); |
| return true; |
| } |
| |
| bool CSSFilter::updateBackingStoreRect(const FloatRect& filterRect) |
| { |
| if (filterRect.isEmpty() || ImageBuffer::sizeNeedsClamping(filterRect.size())) |
| return false; |
| |
| if (filterRect == sourceImageRect()) |
| return false; |
| |
| setSourceImageRect(filterRect); |
| return true; |
| } |
| |
| void CSSFilter::allocateBackingStoreIfNeeded(const GraphicsContext& targetContext) |
| { |
| // At this point the effect chain has been built, and the |
| // source image sizes set. We just need to attach the graphic |
| // buffer if we have not yet done so. |
| |
| if (m_graphicsBufferAttached) |
| return; |
| |
| IntSize logicalSize { m_sourceDrawingRegion.size() }; |
| if (!sourceImage() || sourceImage()->logicalSize() != logicalSize) { |
| #if USE(DIRECT2D) |
| setSourceImage(ImageBuffer::create(logicalSize, renderingMode(), &targetContext, filterScale())); |
| #else |
| UNUSED_PARAM(targetContext); |
| setSourceImage(ImageBuffer::create(logicalSize, renderingMode(), filterScale())); |
| #endif |
| } |
| m_graphicsBufferAttached = true; |
| } |
| |
| void CSSFilter::determineFilterPrimitiveSubregion() |
| { |
| auto& lastEffect = m_effects.last().get(); |
| lastEffect.determineFilterPrimitiveSubregion(); |
| FloatRect subRegion = lastEffect.maxEffectRect(); |
| // At least one FilterEffect has a too big image size, recalculate the effect sizes with new scale factors. |
| FloatSize scale; |
| if (ImageBuffer::sizeNeedsClamping(subRegion.size(), scale)) { |
| setFilterResolution(scale); |
| lastEffect.determineFilterPrimitiveSubregion(); |
| } |
| } |
| |
| void CSSFilter::clearIntermediateResults() |
| { |
| m_sourceGraphic->clearResult(); |
| if (m_sourceAlpha) |
| m_sourceAlpha->clearResult(); |
| for (auto& effect : m_effects) |
| effect->clearResult(); |
| } |
| |
| void CSSFilter::apply() |
| { |
| auto& effect = m_effects.last().get(); |
| effect.apply(); |
| effect.transformResultColorSpace(ColorSpaceSRGB); |
| } |
| |
| LayoutRect CSSFilter::computeSourceImageRectForDirtyRect(const LayoutRect& filterBoxRect, const LayoutRect& dirtyRect) |
| { |
| // The result of this function is the area in the "filterBoxRect" that needs to be repainted, so that we fully cover the "dirtyRect". |
| auto rectForRepaint = dirtyRect; |
| if (hasFilterThatMovesPixels()) { |
| // Note that the outsets are reversed here because we are going backwards -> we have the dirty rect and |
| // need to find out what is the rectangle that might influence the result inside that dirty rect. |
| rectForRepaint.move(-m_outsets.right(), -m_outsets.bottom()); |
| rectForRepaint.expand(m_outsets.left() + m_outsets.right(), m_outsets.top() + m_outsets.bottom()); |
| } |
| rectForRepaint.intersect(filterBoxRect); |
| return rectForRepaint; |
| } |
| |
| ImageBuffer* CSSFilter::output() const |
| { |
| return m_effects.last()->imageBufferResult(); |
| } |
| |
| void CSSFilter::setSourceImageRect(const FloatRect& sourceImageRect) |
| { |
| m_sourceDrawingRegion = sourceImageRect; |
| setMaxEffectRects(sourceImageRect); |
| setFilterRegion(sourceImageRect); |
| m_graphicsBufferAttached = false; |
| } |
| |
| |
| void CSSFilter::setMaxEffectRects(const FloatRect& effectRect) |
| { |
| for (auto& effect : m_effects) |
| effect->setMaxEffectRect(effectRect); |
| } |
| |
| IntRect CSSFilter::outputRect() const |
| { |
| auto& lastEffect = m_effects.last().get(); |
| if (!lastEffect.hasResult()) |
| return { }; |
| return lastEffect.requestedRegionOfInputImageData(IntRect { m_filterRegion }); |
| } |
| |
| } // namespace WebCore |