| // 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/aiks/paint.h" |
| |
| #include <memory> |
| |
| #include "impeller/entity/contents/color_source_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/solid_color_contents.h" |
| #include "impeller/entity/geometry/geometry.h" |
| |
| namespace impeller { |
| |
| /// A color matrix which inverts colors. |
| // clang-format off |
| constexpr ColorMatrix kColorInversion = { |
| .array = { |
| -1.0, 0, 0, 1.0, 0, // |
| 0, -1.0, 0, 1.0, 0, // |
| 0, 0, -1.0, 1.0, 0, // |
| 1.0, 1.0, 1.0, 1.0, 0 // |
| } |
| }; |
| // clang-format on |
| |
| std::shared_ptr<Contents> Paint::CreateContentsForGeometry( |
| const std::shared_ptr<Geometry>& geometry) const { |
| auto contents = color_source.GetContents(*this); |
| |
| // 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. |
| auto color_filter = GetColorFilter(); |
| bool needs_color_filter = !!color_filter; |
| if (color_filter && |
| contents->ApplyColorFilter(color_filter->GetCPUColorFilterProc())) { |
| needs_color_filter = false; |
| } |
| |
| contents->SetGeometry(geometry); |
| if (mask_blur_descriptor.has_value()) { |
| // If there's a mask blur and we need to apply the color filter on the GPU, |
| // we need to be careful to only apply the color filter to the source |
| // colors. CreateMaskBlur is able to handle this case. |
| return mask_blur_descriptor->CreateMaskBlur( |
| contents, needs_color_filter ? color_filter : nullptr); |
| } |
| |
| return contents; |
| } |
| |
| 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::kSubpass); |
| 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 { |
| if (mask_blur_descriptor.has_value()) { |
| input = mask_blur_descriptor->CreateMaskBlur(FilterInput::Make(input), |
| is_solid_color); |
| } |
| 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 = image_filter->WrapInput(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.GetType() == ColorSource::Type::kImage) { |
| return input; |
| } |
| |
| auto color_filter = GetColorFilter(); |
| if (!color_filter) { |
| 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_filter->GetCPUColorFilterProc())) { |
| return input; |
| } |
| return color_filter->WrapWithGPUColorFilter(FilterInput::Make(input), |
| absorb_opacity); |
| } |
| |
| std::shared_ptr<FilterContents> Paint::MaskBlurDescriptor::CreateMaskBlur( |
| std::shared_ptr<TextureContents> texture_contents) 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({}); |
| std::shared_ptr<Geometry> geometry; |
| if (coverage) { |
| texture_contents->SetDestinationRect( |
| coverage.value().Expand(expand_amount, expand_amount)); |
| geometry = Geometry::MakeRect(coverage.value()); |
| } |
| 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 std::shared_ptr<ColorFilter>& color_filter) const { |
| // If it's a solid color and there is no color filter, then we can just get |
| // away with doing one Gaussian blur. |
| if (color_source_contents->IsSolidColor() && !color_filter) { |
| 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(); |
| } |
| color_source_contents->SetGeometry( |
| Geometry::MakeRect(*expanded_local_bounds)); |
| 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 = color_filter->WrapWithGPUColorFilter( |
| FilterInput::Make(color_source_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 { |
| if (is_solid_color) { |
| return FilterContents::MakeGaussianBlur(input, sigma, sigma, |
| Entity::TileMode::kDecal, style); |
| } |
| return FilterContents::MakeBorderMaskBlur(input, sigma, sigma, style); |
| } |
| |
| std::shared_ptr<ColorFilter> Paint::GetColorFilter() const { |
| if (invert_colors && color_filter) { |
| auto filter = ColorFilter::MakeMatrix(kColorInversion); |
| return ColorFilter::MakeComposed(filter, color_filter); |
| } |
| if (invert_colors) { |
| return ColorFilter::MakeMatrix(kColorInversion); |
| } |
| if (color_filter) { |
| return color_filter; |
| } |
| return nullptr; |
| } |
| |
| bool Paint::HasColorFilter() const { |
| return !!color_filter || invert_colors; |
| } |
| |
| } // namespace impeller |