blob: 3dd4e661eccadf2283995a0a9b3e2bd7afb7b9a5 [file] [log] [blame]
/*
* Copyright (C) 2011 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 COMPUTER, 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 "third_party/blink/renderer/core/paint/filter_effect_builder.h"
#include <algorithm>
#include "third_party/blink/public/platform/web_point.h"
#include "third_party/blink/renderer/core/style/filter_operations.h"
#include "third_party/blink/renderer/core/svg/graphics/filters/svg_filter_builder.h"
#include "third_party/blink/renderer/core/svg/svg_filter_element.h"
#include "third_party/blink/renderer/core/svg/svg_length_context.h"
#include "third_party/blink/renderer/core/svg/svg_resource.h"
#include "third_party/blink/renderer/platform/geometry/length_functions.h"
#include "third_party/blink/renderer/platform/graphics/compositor_filter_operations.h"
#include "third_party/blink/renderer/platform/graphics/filters/fe_box_reflect.h"
#include "third_party/blink/renderer/platform/graphics/filters/fe_color_matrix.h"
#include "third_party/blink/renderer/platform/graphics/filters/fe_component_transfer.h"
#include "third_party/blink/renderer/platform/graphics/filters/fe_drop_shadow.h"
#include "third_party/blink/renderer/platform/graphics/filters/fe_gaussian_blur.h"
#include "third_party/blink/renderer/platform/graphics/filters/filter.h"
#include "third_party/blink/renderer/platform/graphics/filters/filter_effect.h"
#include "third_party/blink/renderer/platform/graphics/filters/paint_filter_builder.h"
#include "third_party/blink/renderer/platform/graphics/filters/source_graphic.h"
#include "third_party/blink/renderer/platform/graphics/interpolation_space.h"
#include "third_party/blink/renderer/platform/wtf/math_extras.h"
namespace blink {
namespace {
inline void EndMatrixRow(Vector<float>& matrix) {
matrix.UncheckedAppend(0);
matrix.UncheckedAppend(0);
}
inline void LastMatrixRow(Vector<float>& matrix) {
matrix.UncheckedAppend(0);
matrix.UncheckedAppend(0);
matrix.UncheckedAppend(0);
matrix.UncheckedAppend(1);
matrix.UncheckedAppend(0);
}
Vector<float> GrayscaleMatrix(double amount) {
double one_minus_amount = clampTo(1 - amount, 0.0, 1.0);
// See https://drafts.fxtf.org/filter-effects/#grayscaleEquivalent for
// information on parameters.
Vector<float> matrix;
matrix.ReserveInitialCapacity(20);
matrix.UncheckedAppend(clampTo<float>(0.2126 + 0.7874 * one_minus_amount));
matrix.UncheckedAppend(clampTo<float>(0.7152 - 0.7152 * one_minus_amount));
matrix.UncheckedAppend(clampTo<float>(0.0722 - 0.0722 * one_minus_amount));
EndMatrixRow(matrix);
matrix.UncheckedAppend(clampTo<float>(0.2126 - 0.2126 * one_minus_amount));
matrix.UncheckedAppend(clampTo<float>(0.7152 + 0.2848 * one_minus_amount));
matrix.UncheckedAppend(clampTo<float>(0.0722 - 0.0722 * one_minus_amount));
EndMatrixRow(matrix);
matrix.UncheckedAppend(clampTo<float>(0.2126 - 0.2126 * one_minus_amount));
matrix.UncheckedAppend(clampTo<float>(0.7152 - 0.7152 * one_minus_amount));
matrix.UncheckedAppend(clampTo<float>(0.0722 + 0.9278 * one_minus_amount));
EndMatrixRow(matrix);
LastMatrixRow(matrix);
return matrix;
}
Vector<float> SepiaMatrix(double amount) {
double one_minus_amount = clampTo(1 - amount, 0.0, 1.0);
// See https://drafts.fxtf.org/filter-effects/#sepiaEquivalent for information
// on parameters.
Vector<float> matrix;
matrix.ReserveInitialCapacity(20);
matrix.UncheckedAppend(clampTo<float>(0.393 + 0.607 * one_minus_amount));
matrix.UncheckedAppend(clampTo<float>(0.769 - 0.769 * one_minus_amount));
matrix.UncheckedAppend(clampTo<float>(0.189 - 0.189 * one_minus_amount));
EndMatrixRow(matrix);
matrix.UncheckedAppend(clampTo<float>(0.349 - 0.349 * one_minus_amount));
matrix.UncheckedAppend(clampTo<float>(0.686 + 0.314 * one_minus_amount));
matrix.UncheckedAppend(clampTo<float>(0.168 - 0.168 * one_minus_amount));
EndMatrixRow(matrix);
matrix.UncheckedAppend(clampTo<float>(0.272 - 0.272 * one_minus_amount));
matrix.UncheckedAppend(clampTo<float>(0.534 - 0.534 * one_minus_amount));
matrix.UncheckedAppend(clampTo<float>(0.131 + 0.869 * one_minus_amount));
EndMatrixRow(matrix);
LastMatrixRow(matrix);
return matrix;
}
} // namespace
FilterEffectBuilder::FilterEffectBuilder(const FloatRect& reference_box,
float zoom,
const PaintFlags* fill_flags,
const PaintFlags* stroke_flags)
: reference_box_(reference_box),
zoom_(zoom),
fill_flags_(fill_flags),
stroke_flags_(stroke_flags) {}
FilterEffect* FilterEffectBuilder::BuildFilterEffect(
const FilterOperations& operations,
bool input_tainted) const {
// Create a parent filter for shorthand filters. These have already been
// scaled by the CSS code for page zoom, so scale is 1.0 here.
Filter* parent_filter = Filter::Create(1.0f);
FilterEffect* previous_effect = parent_filter->GetSourceGraphic();
if (input_tainted)
previous_effect->SetOriginTainted();
for (FilterOperation* filter_operation : operations.Operations()) {
FilterEffect* effect = nullptr;
switch (filter_operation->GetType()) {
case FilterOperation::REFERENCE: {
ReferenceFilterOperation& reference_operation =
ToReferenceFilterOperation(*filter_operation);
Filter* reference_filter =
BuildReferenceFilter(reference_operation, previous_effect);
if (reference_filter) {
effect = reference_filter->LastEffect();
// TODO(fs): This is essentially only needed for the
// side-effects (mapRect). The filter differs from the one
// computed just above in what the SourceGraphic is, and how
// it's connected to the filter-chain.
reference_filter = BuildReferenceFilter(reference_operation, nullptr);
}
reference_operation.SetFilter(reference_filter);
break;
}
case FilterOperation::GRAYSCALE: {
Vector<float> input_parameters = GrayscaleMatrix(
ToBasicColorMatrixFilterOperation(filter_operation)->Amount());
effect = FEColorMatrix::Create(parent_filter, FECOLORMATRIX_TYPE_MATRIX,
input_parameters);
break;
}
case FilterOperation::SEPIA: {
Vector<float> input_parameters = SepiaMatrix(
ToBasicColorMatrixFilterOperation(filter_operation)->Amount());
effect = FEColorMatrix::Create(parent_filter, FECOLORMATRIX_TYPE_MATRIX,
input_parameters);
break;
}
case FilterOperation::SATURATE: {
Vector<float> input_parameters;
input_parameters.push_back(clampTo<float>(
ToBasicColorMatrixFilterOperation(filter_operation)->Amount()));
effect = FEColorMatrix::Create(
parent_filter, FECOLORMATRIX_TYPE_SATURATE, input_parameters);
break;
}
case FilterOperation::HUE_ROTATE: {
Vector<float> input_parameters;
input_parameters.push_back(clampTo<float>(
ToBasicColorMatrixFilterOperation(filter_operation)->Amount()));
effect = FEColorMatrix::Create(
parent_filter, FECOLORMATRIX_TYPE_HUEROTATE, input_parameters);
break;
}
case FilterOperation::INVERT: {
BasicComponentTransferFilterOperation* component_transfer_operation =
ToBasicComponentTransferFilterOperation(filter_operation);
ComponentTransferFunction transfer_function;
transfer_function.type = FECOMPONENTTRANSFER_TYPE_TABLE;
Vector<float> transfer_parameters;
transfer_parameters.push_back(
clampTo<float>(component_transfer_operation->Amount()));
transfer_parameters.push_back(
clampTo<float>(1 - component_transfer_operation->Amount()));
transfer_function.table_values = transfer_parameters;
ComponentTransferFunction null_function;
effect = FEComponentTransfer::Create(parent_filter, transfer_function,
transfer_function,
transfer_function, null_function);
break;
}
case FilterOperation::OPACITY: {
ComponentTransferFunction transfer_function;
transfer_function.type = FECOMPONENTTRANSFER_TYPE_TABLE;
Vector<float> transfer_parameters;
transfer_parameters.push_back(0);
transfer_parameters.push_back(clampTo<float>(
ToBasicComponentTransferFilterOperation(filter_operation)
->Amount()));
transfer_function.table_values = transfer_parameters;
ComponentTransferFunction null_function;
effect = FEComponentTransfer::Create(parent_filter, null_function,
null_function, null_function,
transfer_function);
break;
}
case FilterOperation::BRIGHTNESS: {
ComponentTransferFunction transfer_function;
transfer_function.type = FECOMPONENTTRANSFER_TYPE_LINEAR;
transfer_function.slope = clampTo<float>(
ToBasicComponentTransferFilterOperation(filter_operation)
->Amount());
transfer_function.intercept = 0;
ComponentTransferFunction null_function;
effect = FEComponentTransfer::Create(parent_filter, transfer_function,
transfer_function,
transfer_function, null_function);
break;
}
case FilterOperation::CONTRAST: {
ComponentTransferFunction transfer_function;
transfer_function.type = FECOMPONENTTRANSFER_TYPE_LINEAR;
float amount = clampTo<float>(
ToBasicComponentTransferFilterOperation(filter_operation)
->Amount());
transfer_function.slope = amount;
transfer_function.intercept = -0.5 * amount + 0.5;
ComponentTransferFunction null_function;
effect = FEComponentTransfer::Create(parent_filter, transfer_function,
transfer_function,
transfer_function, null_function);
break;
}
case FilterOperation::BLUR: {
float std_deviation = FloatValueForLength(
ToBlurFilterOperation(filter_operation)->StdDeviation(), 0);
effect =
FEGaussianBlur::Create(parent_filter, std_deviation, std_deviation);
break;
}
case FilterOperation::DROP_SHADOW: {
const ShadowData& shadow =
ToDropShadowFilterOperation(*filter_operation).Shadow();
effect = FEDropShadow::Create(parent_filter, shadow.Blur(),
shadow.Blur(), shadow.X(), shadow.Y(),
shadow.GetColor().GetColor(), 1);
break;
}
case FilterOperation::BOX_REFLECT: {
BoxReflectFilterOperation* box_reflect_operation =
ToBoxReflectFilterOperation(filter_operation);
effect = FEBoxReflect::Create(parent_filter,
box_reflect_operation->Reflection());
break;
}
default:
break;
}
if (effect) {
if (filter_operation->GetType() != FilterOperation::REFERENCE) {
// Unlike SVG, filters applied here should not clip to their primitive
// subregions.
effect->SetClipsToBounds(false);
effect->SetOperatingInterpolationSpace(kInterpolationSpaceSRGB);
effect->InputEffects().push_back(previous_effect);
}
if (previous_effect->OriginTainted())
effect->SetOriginTainted();
previous_effect = effect;
}
}
return previous_effect;
}
CompositorFilterOperations FilterEffectBuilder::BuildFilterOperations(
const FilterOperations& operations) const {
InterpolationSpace current_interpolation_space = kInterpolationSpaceSRGB;
CompositorFilterOperations filters;
for (FilterOperation* op : operations.Operations()) {
switch (op->GetType()) {
case FilterOperation::REFERENCE: {
ReferenceFilterOperation& reference_operation =
ToReferenceFilterOperation(*op);
Filter* reference_filter =
BuildReferenceFilter(reference_operation, nullptr);
if (reference_filter && reference_filter->LastEffect()) {
paint_filter_builder::PopulateSourceGraphicImageFilters(
reference_filter->GetSourceGraphic(), nullptr,
current_interpolation_space);
FilterEffect* filter_effect = reference_filter->LastEffect();
current_interpolation_space =
filter_effect->OperatingInterpolationSpace();
auto paint_filter = paint_filter_builder::Build(
filter_effect, current_interpolation_space);
if (!paint_filter)
continue;
filters.AppendReferenceFilter(std::move(paint_filter));
}
reference_operation.SetFilter(reference_filter);
break;
}
case FilterOperation::GRAYSCALE:
case FilterOperation::SEPIA:
case FilterOperation::SATURATE:
case FilterOperation::HUE_ROTATE: {
float amount = ToBasicColorMatrixFilterOperation(*op).Amount();
switch (op->GetType()) {
case FilterOperation::GRAYSCALE:
filters.AppendGrayscaleFilter(amount);
break;
case FilterOperation::SEPIA:
filters.AppendSepiaFilter(amount);
break;
case FilterOperation::SATURATE:
filters.AppendSaturateFilter(amount);
break;
case FilterOperation::HUE_ROTATE:
filters.AppendHueRotateFilter(amount);
break;
default:
NOTREACHED();
}
break;
}
case FilterOperation::INVERT:
case FilterOperation::OPACITY:
case FilterOperation::BRIGHTNESS:
case FilterOperation::CONTRAST: {
float amount = ToBasicComponentTransferFilterOperation(*op).Amount();
switch (op->GetType()) {
case FilterOperation::INVERT:
filters.AppendInvertFilter(amount);
break;
case FilterOperation::OPACITY:
filters.AppendOpacityFilter(amount);
break;
case FilterOperation::BRIGHTNESS:
filters.AppendBrightnessFilter(amount);
break;
case FilterOperation::CONTRAST:
filters.AppendContrastFilter(amount);
break;
default:
NOTREACHED();
}
break;
}
case FilterOperation::BLUR: {
float pixel_radius =
ToBlurFilterOperation(*op).StdDeviation().GetFloatValue();
filters.AppendBlurFilter(pixel_radius);
break;
}
case FilterOperation::DROP_SHADOW: {
const ShadowData& shadow = ToDropShadowFilterOperation(*op).Shadow();
filters.AppendDropShadowFilter(FlooredIntPoint(shadow.Location()),
shadow.Blur(),
shadow.GetColor().GetColor());
break;
}
case FilterOperation::BOX_REFLECT: {
// TODO(jbroman): Consider explaining box reflect to the compositor,
// instead of calling this a "reference filter".
const auto& reflection = ToBoxReflectFilterOperation(*op).Reflection();
filters.AppendReferenceFilter(
paint_filter_builder::BuildBoxReflectFilter(reflection, nullptr));
break;
}
case FilterOperation::NONE:
break;
}
}
if (current_interpolation_space != kInterpolationSpaceSRGB) {
// Transform to device color space at the end of processing, if required.
sk_sp<PaintFilter> filter =
paint_filter_builder::TransformInterpolationSpace(
nullptr, current_interpolation_space, kInterpolationSpaceSRGB);
filters.AppendReferenceFilter(std::move(filter));
}
if (!filters.IsEmpty())
filters.SetReferenceBox(reference_box_);
return filters;
}
Filter* FilterEffectBuilder::BuildReferenceFilter(
const ReferenceFilterOperation& reference_operation,
FilterEffect* previous_effect) const {
SVGResource* resource = reference_operation.Resource();
if (auto* filter =
ToSVGFilterElementOrNull(resource ? resource->Target() : nullptr))
return BuildReferenceFilter(*filter, previous_effect);
return nullptr;
}
Filter* FilterEffectBuilder::BuildReferenceFilter(
SVGFilterElement& filter_element,
FilterEffect* previous_effect,
SVGFilterGraphNodeMap* node_map) const {
FloatRect filter_region =
SVGLengthContext::ResolveRectangle<SVGFilterElement>(
&filter_element,
filter_element.filterUnits()->CurrentValue()->EnumValue(),
reference_box_);
// TODO(fs): We rely on the presence of a node map here to opt-in to the
// check for an empty filter region. The reason for this is that we lack a
// viewport to resolve against for HTML content. This is crbug.com/512453.
if (node_map && filter_region.IsEmpty())
return nullptr;
bool primitive_bounding_box_mode =
filter_element.primitiveUnits()->CurrentValue()->EnumValue() ==
SVGUnitTypes::kSvgUnitTypeObjectboundingbox;
Filter::UnitScaling unit_scaling =
primitive_bounding_box_mode ? Filter::kBoundingBox : Filter::kUserSpace;
Filter* result =
Filter::Create(reference_box_, filter_region, zoom_, unit_scaling);
if (!previous_effect)
previous_effect = result->GetSourceGraphic();
SVGFilterBuilder builder(previous_effect, node_map, fill_flags_,
stroke_flags_);
builder.BuildGraph(result, filter_element, reference_box_);
result->SetLastEffect(builder.LastEffect());
return result;
}
} // namespace blink