blob: 0305a4a88e85390c64b82f536c2b3993dedf2a52 [file] [log] [blame]
// Copyright 2013 The Flutter Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "impeller/display_list/paint.h"
#include <memory>
#include "flutter/display_list/effects/dl_color_filter.h"
#include "flutter/display_list/effects/dl_color_sources.h"
#include "flutter/display_list/geometry/dl_path.h"
#include "fml/logging.h"
#include "impeller/display_list/color_filter.h"
#include "impeller/display_list/skia_conversions.h"
#include "impeller/entity/contents/color_source_contents.h"
#include "impeller/entity/contents/conical_gradient_contents.h"
#include "impeller/entity/contents/filters/color_filter_contents.h"
#include "impeller/entity/contents/filters/filter_contents.h"
#include "impeller/entity/contents/filters/gaussian_blur_filter_contents.h"
#include "impeller/entity/contents/linear_gradient_contents.h"
#include "impeller/entity/contents/radial_gradient_contents.h"
#include "impeller/entity/contents/runtime_effect_contents.h"
#include "impeller/entity/contents/solid_color_contents.h"
#include "impeller/entity/contents/sweep_gradient_contents.h"
#include "impeller/entity/contents/tiled_texture_contents.h"
#include "impeller/entity/geometry/geometry.h"
#include "impeller/entity/geometry/rect_geometry.h"
namespace impeller {
using DlScalar = flutter::DlScalar;
using DlPoint = flutter::DlPoint;
using DlRect = flutter::DlRect;
using DlIRect = flutter::DlIRect;
using DlPath = flutter::DlPath;
std::shared_ptr<ColorSourceContents> Paint::CreateContents() const {
if (color_source == nullptr) {
auto contents = std::make_shared<SolidColorContents>();
contents->SetColor(color);
return contents;
}
switch (color_source->type()) {
case flutter::DlColorSourceType::kLinearGradient: {
const flutter::DlLinearGradientColorSource* linear =
color_source->asLinearGradient();
FML_DCHECK(linear);
auto start_point = linear->start_point();
auto end_point = linear->end_point();
std::vector<Color> colors;
std::vector<float> stops;
skia_conversions::ConvertStops(linear, colors, stops);
auto tile_mode = static_cast<Entity::TileMode>(linear->tile_mode());
auto effect_transform = linear->matrix();
auto contents = std::make_shared<LinearGradientContents>();
contents->SetOpacityFactor(color.alpha);
contents->SetColors(std::move(colors));
contents->SetStops(std::move(stops));
contents->SetEndPoints(start_point, end_point);
contents->SetTileMode(tile_mode);
contents->SetEffectTransform(effect_transform);
std::array<Point, 2> bounds{start_point, end_point};
auto intrinsic_size = Rect::MakePointBounds(bounds.begin(), bounds.end());
if (intrinsic_size.has_value()) {
contents->SetColorSourceSize(intrinsic_size->GetSize());
}
return contents;
}
case flutter::DlColorSourceType::kRadialGradient: {
const flutter::DlRadialGradientColorSource* radialGradient =
color_source->asRadialGradient();
FML_DCHECK(radialGradient);
auto center = radialGradient->center();
auto radius = radialGradient->radius();
std::vector<Color> colors;
std::vector<float> stops;
skia_conversions::ConvertStops(radialGradient, colors, stops);
auto tile_mode =
static_cast<Entity::TileMode>(radialGradient->tile_mode());
auto effect_transform = radialGradient->matrix();
auto contents = std::make_shared<RadialGradientContents>();
contents->SetOpacityFactor(color.alpha);
contents->SetColors(std::move(colors));
contents->SetStops(std::move(stops));
contents->SetCenterAndRadius(center, radius);
contents->SetTileMode(tile_mode);
contents->SetEffectTransform(effect_transform);
auto radius_pt = Point(radius, radius);
std::array<Point, 2> bounds{center + radius_pt, center - radius_pt};
auto intrinsic_size = Rect::MakePointBounds(bounds.begin(), bounds.end());
if (intrinsic_size.has_value()) {
contents->SetColorSourceSize(intrinsic_size->GetSize());
}
return contents;
}
case flutter::DlColorSourceType::kConicalGradient: {
const flutter::DlConicalGradientColorSource* conical_gradient =
color_source->asConicalGradient();
FML_DCHECK(conical_gradient);
Point center = conical_gradient->end_center();
DlScalar radius = conical_gradient->end_radius();
Point focus_center = conical_gradient->start_center();
DlScalar focus_radius = conical_gradient->start_radius();
std::vector<Color> colors;
std::vector<float> stops;
skia_conversions::ConvertStops(conical_gradient, colors, stops);
auto tile_mode =
static_cast<Entity::TileMode>(conical_gradient->tile_mode());
auto effect_transform = conical_gradient->matrix();
std::shared_ptr<ConicalGradientContents> contents =
std::make_shared<ConicalGradientContents>();
contents->SetOpacityFactor(color.alpha);
contents->SetColors(std::move(colors));
contents->SetStops(std::move(stops));
contents->SetCenterAndRadius(center, radius);
contents->SetTileMode(tile_mode);
contents->SetEffectTransform(effect_transform);
contents->SetFocus(focus_center, focus_radius);
auto radius_pt = Point(radius, radius);
std::array<Point, 2> bounds{center + radius_pt, center - radius_pt};
auto intrinsic_size = Rect::MakePointBounds(bounds.begin(), bounds.end());
if (intrinsic_size.has_value()) {
contents->SetColorSourceSize(intrinsic_size->GetSize());
}
return contents;
}
case flutter::DlColorSourceType::kSweepGradient: {
const flutter::DlSweepGradientColorSource* sweepGradient =
color_source->asSweepGradient();
FML_DCHECK(sweepGradient);
auto center = sweepGradient->center();
auto start_angle = Degrees(sweepGradient->start());
auto end_angle = Degrees(sweepGradient->end());
std::vector<Color> colors;
std::vector<float> stops;
skia_conversions::ConvertStops(sweepGradient, colors, stops);
auto tile_mode =
static_cast<Entity::TileMode>(sweepGradient->tile_mode());
auto effect_transform = sweepGradient->matrix();
auto contents = std::make_shared<SweepGradientContents>();
contents->SetOpacityFactor(color.alpha);
contents->SetCenterAndAngles(center, start_angle, end_angle);
contents->SetColors(std::move(colors));
contents->SetStops(std::move(stops));
contents->SetTileMode(tile_mode);
contents->SetEffectTransform(effect_transform);
return contents;
}
case flutter::DlColorSourceType::kImage: {
const flutter::DlImageColorSource* image_color_source =
color_source->asImage();
FML_DCHECK(image_color_source &&
image_color_source->image()->impeller_texture());
auto texture = image_color_source->image()->impeller_texture();
auto x_tile_mode = static_cast<Entity::TileMode>(
image_color_source->horizontal_tile_mode());
auto y_tile_mode = static_cast<Entity::TileMode>(
image_color_source->vertical_tile_mode());
auto sampler_descriptor =
skia_conversions::ToSamplerDescriptor(image_color_source->sampling());
auto effect_transform = image_color_source->matrix();
auto contents = std::make_shared<TiledTextureContents>();
contents->SetOpacityFactor(color.alpha);
contents->SetTexture(texture);
contents->SetTileModes(x_tile_mode, y_tile_mode);
contents->SetSamplerDescriptor(sampler_descriptor);
contents->SetEffectTransform(effect_transform);
if (color_filter || invert_colors) {
TiledTextureContents::ColorFilterProc filter_proc =
[color_filter = color_filter,
invert_colors = invert_colors](const FilterInput::Ref& input) {
if (invert_colors && color_filter) {
std::shared_ptr<FilterContents> color_filter_output =
WrapWithGPUColorFilter(
color_filter, input,
ColorFilterContents::AbsorbOpacity::kNo);
return WrapWithInvertColors(
FilterInput::Make(color_filter_output),
ColorFilterContents::AbsorbOpacity::kNo);
}
if (color_filter) {
return WrapWithGPUColorFilter(
color_filter, input,
ColorFilterContents::AbsorbOpacity::kNo);
}
return WrapWithInvertColors(
input, ColorFilterContents::AbsorbOpacity::kNo);
};
contents->SetColorFilter(filter_proc);
}
contents->SetColorSourceSize(Size::Ceil(texture->GetSize()));
return contents;
}
case flutter::DlColorSourceType::kRuntimeEffect: {
const flutter::DlRuntimeEffectColorSource* runtime_effect_color_source =
color_source->asRuntimeEffect();
auto runtime_stage =
runtime_effect_color_source->runtime_effect()->runtime_stage();
auto uniform_data = runtime_effect_color_source->uniform_data();
auto samplers = runtime_effect_color_source->samplers();
std::vector<RuntimeEffectContents::TextureInput> texture_inputs;
for (auto& sampler : samplers) {
if (sampler == nullptr) {
return nullptr;
}
auto* image = sampler->asImage();
if (!sampler->asImage()) {
return nullptr;
}
FML_DCHECK(image->image()->impeller_texture());
texture_inputs.push_back({
.sampler_descriptor =
skia_conversions::ToSamplerDescriptor(image->sampling()),
.texture = image->image()->impeller_texture(),
});
}
auto contents = std::make_shared<RuntimeEffectContents>();
contents->SetOpacityFactor(color.alpha);
contents->SetRuntimeStage(std::move(runtime_stage));
contents->SetUniformData(std::move(uniform_data));
contents->SetTextureInputs(std::move(texture_inputs));
return contents;
}
}
FML_UNREACHABLE();
}
std::shared_ptr<Contents> Paint::WithFilters(
std::shared_ptr<Contents> input) const {
input = WithColorFilter(input, ColorFilterContents::AbsorbOpacity::kYes);
auto image_filter =
WithImageFilter(input, Matrix(), Entity::RenderingMode::kDirect);
if (image_filter) {
input = image_filter;
}
return input;
}
std::shared_ptr<Contents> Paint::WithFiltersForSubpassTarget(
std::shared_ptr<Contents> input,
const Matrix& effect_transform) const {
auto image_filter =
WithImageFilter(input, effect_transform,
Entity::RenderingMode::kSubpassPrependSnapshotTransform);
if (image_filter) {
input = image_filter;
}
input = WithColorFilter(input, ColorFilterContents::AbsorbOpacity::kYes);
return input;
}
std::shared_ptr<Contents> Paint::WithMaskBlur(std::shared_ptr<Contents> input,
bool is_solid_color,
const Matrix& ctm) const {
if (mask_blur_descriptor.has_value()) {
input = mask_blur_descriptor->CreateMaskBlur(FilterInput::Make(input),
is_solid_color, ctm);
}
return input;
}
std::shared_ptr<FilterContents> Paint::WithImageFilter(
const FilterInput::Variant& input,
const Matrix& effect_transform,
Entity::RenderingMode rendering_mode) const {
if (!image_filter) {
return nullptr;
}
auto filter = WrapInput(image_filter, FilterInput::Make(input));
filter->SetRenderingMode(rendering_mode);
filter->SetEffectTransform(effect_transform);
return filter;
}
std::shared_ptr<Contents> Paint::WithColorFilter(
std::shared_ptr<Contents> input,
ColorFilterContents::AbsorbOpacity absorb_opacity) const {
// Image input types will directly set their color filter,
// if any. See `TiledTextureContents.SetColorFilter`.
if (color_source &&
color_source->type() == flutter::DlColorSourceType::kImage) {
return input;
}
if (!color_filter && !invert_colors) {
return input;
}
// Attempt to apply the color filter on the CPU first.
// Note: This is not just an optimization; some color sources rely on
// CPU-applied color filters to behave properly.
if (input->ApplyColorFilter([&](Color color) -> Color {
if (color_filter) {
color = GetCPUColorFilterProc(color_filter)(color);
}
if (invert_colors) {
color = color.ApplyColorMatrix(kColorInversion);
}
return color;
})) {
return input;
}
if (color_filter) {
input = WrapWithGPUColorFilter(color_filter, FilterInput::Make(input),
absorb_opacity);
}
if (invert_colors) {
input = WrapWithInvertColors(FilterInput::Make(input), absorb_opacity);
}
return input;
}
std::shared_ptr<FilterContents> Paint::MaskBlurDescriptor::CreateMaskBlur(
std::shared_ptr<TextureContents> texture_contents,
RectGeometry* rect_geom) const {
Scalar expand_amount = GaussianBlurFilterContents::CalculateBlurRadius(
GaussianBlurFilterContents::ScaleSigma(sigma.sigma));
texture_contents->SetSourceRect(
texture_contents->GetSourceRect().Expand(expand_amount, expand_amount));
auto mask = std::make_shared<SolidColorContents>();
mask->SetColor(Color::White());
std::optional<Rect> coverage = texture_contents->GetCoverage({});
Geometry* geometry = nullptr;
if (coverage) {
texture_contents->SetDestinationRect(
coverage.value().Expand(expand_amount, expand_amount));
*rect_geom = RectGeometry(coverage.value());
geometry = rect_geom;
}
mask->SetGeometry(geometry);
auto descriptor = texture_contents->GetSamplerDescriptor();
texture_contents->SetSamplerDescriptor(descriptor);
std::shared_ptr<FilterContents> blurred_mask =
FilterContents::MakeGaussianBlur(FilterInput::Make(mask), sigma, sigma,
Entity::TileMode::kDecal, style,
geometry);
return ColorFilterContents::MakeBlend(
BlendMode::kSourceIn,
{FilterInput::Make(blurred_mask), FilterInput::Make(texture_contents)});
}
std::shared_ptr<FilterContents> Paint::MaskBlurDescriptor::CreateMaskBlur(
std::shared_ptr<ColorSourceContents> color_source_contents,
const flutter::DlColorFilter* color_filter,
bool invert_colors,
RectGeometry* rect_geom) const {
// If it's a solid color then we can just get away with doing one Gaussian
// blur. The color filter will always be applied on the CPU.
if (color_source_contents->IsSolidColor()) {
return FilterContents::MakeGaussianBlur(
FilterInput::Make(color_source_contents), sigma, sigma,
Entity::TileMode::kDecal, style, color_source_contents->GetGeometry());
}
/// 1. Create an opaque white mask of the original geometry.
auto mask = std::make_shared<SolidColorContents>();
mask->SetColor(Color::White());
mask->SetGeometry(color_source_contents->GetGeometry());
/// 2. Blur the mask.
auto blurred_mask = FilterContents::MakeGaussianBlur(
FilterInput::Make(mask), sigma, sigma, Entity::TileMode::kDecal, style,
color_source_contents->GetGeometry());
/// 3. Replace the geometry of the original color source with a rectangle that
/// covers the full region of the blurred mask. Note that geometry is in
/// local bounds.
auto expanded_local_bounds = blurred_mask->GetCoverage({});
if (!expanded_local_bounds.has_value()) {
expanded_local_bounds = Rect();
}
*rect_geom = RectGeometry(expanded_local_bounds.value());
color_source_contents->SetGeometry(rect_geom);
std::shared_ptr<Contents> color_contents = color_source_contents;
/// 4. Apply the user set color filter on the GPU, if applicable.
if (color_filter) {
color_contents =
WrapWithGPUColorFilter(color_filter, FilterInput::Make(color_contents),
ColorFilterContents::AbsorbOpacity::kYes);
}
if (invert_colors) {
color_contents =
WrapWithInvertColors(FilterInput::Make(color_contents),
ColorFilterContents::AbsorbOpacity::kYes);
}
/// 5. Composite the color source with the blurred mask.
return ColorFilterContents::MakeBlend(
BlendMode::kSourceIn,
{FilterInput::Make(blurred_mask), FilterInput::Make(color_contents)});
}
std::shared_ptr<FilterContents> Paint::MaskBlurDescriptor::CreateMaskBlur(
const FilterInput::Ref& input,
bool is_solid_color,
const Matrix& ctm) const {
Vector2 blur_sigma(sigma.sigma, sigma.sigma);
if (!respect_ctm) {
blur_sigma /=
Vector2(ctm.GetBasisX().GetLength(), ctm.GetBasisY().GetLength());
}
if (is_solid_color) {
return FilterContents::MakeGaussianBlur(input, Sigma(blur_sigma.x),
Sigma(blur_sigma.y),
Entity::TileMode::kDecal, style);
}
return FilterContents::MakeBorderMaskBlur(input, Sigma(blur_sigma.x),
Sigma(blur_sigma.y), style);
}
bool Paint::HasColorFilter() const {
return color_filter || invert_colors;
}
} // namespace impeller