blob: 94c8d01005f6402998232662bdde8f89925007d8 [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/aiks/canvas.h"
#include <memory>
#include <optional>
#include <utility>
#include "flutter/fml/logging.h"
#include "flutter/fml/trace_event.h"
#include "impeller/aiks/color_source.h"
#include "impeller/aiks/image_filter.h"
#include "impeller/aiks/paint_pass_delegate.h"
#include "impeller/entity/contents/atlas_contents.h"
#include "impeller/entity/contents/clip_contents.h"
#include "impeller/entity/contents/color_source_contents.h"
#include "impeller/entity/contents/filters/filter_contents.h"
#include "impeller/entity/contents/solid_rrect_blur_contents.h"
#include "impeller/entity/contents/text_contents.h"
#include "impeller/entity/contents/texture_contents.h"
#include "impeller/entity/contents/vertices_contents.h"
#include "impeller/entity/geometry/geometry.h"
#include "impeller/geometry/color.h"
#include "impeller/geometry/constants.h"
#include "impeller/geometry/path_builder.h"
namespace impeller {
namespace {
static std::shared_ptr<Contents> CreateContentsForGeometryWithFilters(
const Paint& paint,
std::shared_ptr<Geometry> geometry) {
std::shared_ptr<ColorSourceContents> contents =
paint.color_source.GetContents(paint);
// 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.
bool needs_color_filter = paint.HasColorFilter();
if (needs_color_filter) {
auto color_filter = paint.GetColorFilter();
if (contents->ApplyColorFilter(color_filter->GetCPUColorFilterProc())) {
needs_color_filter = false;
}
}
bool can_apply_mask_filter = geometry->CanApplyMaskFilter();
contents->SetGeometry(std::move(geometry));
if (can_apply_mask_filter && paint.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 paint.mask_blur_descriptor->CreateMaskBlur(
contents, needs_color_filter ? paint.GetColorFilter() : nullptr);
}
std::shared_ptr<Contents> contents_copy = std::move(contents);
// Image input types will directly set their color filter,
// if any. See `TiledTextureContents.SetColorFilter`.
if (needs_color_filter &&
paint.color_source.GetType() != ColorSource::Type::kImage) {
std::shared_ptr<ColorFilter> color_filter = paint.GetColorFilter();
contents_copy = color_filter->WrapWithGPUColorFilter(
FilterInput::Make(std::move(contents_copy)),
ColorFilterContents::AbsorbOpacity::kYes);
}
if (paint.image_filter) {
std::shared_ptr<FilterContents> filter = paint.image_filter->WrapInput(
FilterInput::Make(std::move(contents_copy)));
filter->SetRenderingMode(Entity::RenderingMode::kDirect);
return filter;
}
return contents_copy;
}
struct GetTextureColorSourceDataVisitor {
GetTextureColorSourceDataVisitor() {}
std::optional<ImageData> operator()(const LinearGradientData& data) {
return std::nullopt;
}
std::optional<ImageData> operator()(const RadialGradientData& data) {
return std::nullopt;
}
std::optional<ImageData> operator()(const ConicalGradientData& data) {
return std::nullopt;
}
std::optional<ImageData> operator()(const SweepGradientData& data) {
return std::nullopt;
}
std::optional<ImageData> operator()(const ImageData& data) { return data; }
std::optional<ImageData> operator()(const RuntimeEffectData& data) {
return std::nullopt;
}
std::optional<ImageData> operator()(const std::monostate& data) {
return std::nullopt;
}
#if IMPELLER_ENABLE_3D
std::optional<ImageData> operator()(const SceneData& data) {
return std::nullopt;
}
#endif // IMPELLER_ENABLE_3D
};
static std::optional<ImageData> GetImageColorSourceData(
const ColorSource& color_source) {
return std::visit(GetTextureColorSourceDataVisitor{}, color_source.GetData());
}
static std::shared_ptr<Contents> CreatePathContentsWithFilters(
const Paint& paint,
const Path& path) {
std::shared_ptr<Geometry> geometry;
switch (paint.style) {
case Paint::Style::kFill:
geometry = Geometry::MakeFillPath(path);
break;
case Paint::Style::kStroke:
geometry =
Geometry::MakeStrokePath(path, paint.stroke_width, paint.stroke_miter,
paint.stroke_cap, paint.stroke_join);
break;
}
return CreateContentsForGeometryWithFilters(paint, std::move(geometry));
}
static std::shared_ptr<Contents> CreateCoverContentsWithFilters(
const Paint& paint) {
return CreateContentsForGeometryWithFilters(paint, Geometry::MakeCover());
}
} // namespace
Canvas::Canvas() {
Initialize(std::nullopt);
}
Canvas::Canvas(Rect cull_rect) {
Initialize(cull_rect);
}
Canvas::Canvas(IRect cull_rect) {
Initialize(Rect::MakeLTRB(cull_rect.GetLeft(), cull_rect.GetTop(),
cull_rect.GetRight(), cull_rect.GetBottom()));
}
Canvas::~Canvas() = default;
void Canvas::Initialize(std::optional<Rect> cull_rect) {
initial_cull_rect_ = cull_rect;
base_pass_ = std::make_unique<EntityPass>();
base_pass_->SetNewClipDepth(++current_depth_);
current_pass_ = base_pass_.get();
transform_stack_.emplace_back(CanvasStackEntry{.cull_rect = cull_rect});
FML_DCHECK(GetSaveCount() == 1u);
FML_DCHECK(base_pass_->GetSubpassesDepth() == 1u);
}
void Canvas::Reset() {
base_pass_ = nullptr;
current_pass_ = nullptr;
current_depth_ = 0u;
transform_stack_ = {};
}
void Canvas::Save() {
Save(false);
}
namespace {
class MipCountVisitor : public ImageFilterVisitor {
public:
virtual void Visit(const BlurImageFilter& filter) {
required_mip_count_ = FilterContents::kBlurFilterRequiredMipCount;
}
virtual void Visit(const LocalMatrixImageFilter& filter) {
required_mip_count_ = 1;
}
virtual void Visit(const DilateImageFilter& filter) {
required_mip_count_ = 1;
}
virtual void Visit(const ErodeImageFilter& filter) {
required_mip_count_ = 1;
}
virtual void Visit(const MatrixImageFilter& filter) {
required_mip_count_ = 1;
}
virtual void Visit(const ComposeImageFilter& filter) {
required_mip_count_ = 1;
}
virtual void Visit(const ColorImageFilter& filter) {
required_mip_count_ = 1;
}
int32_t GetRequiredMipCount() const { return required_mip_count_; }
private:
int32_t required_mip_count_ = -1;
};
} // namespace
void Canvas::Save(bool create_subpass,
BlendMode blend_mode,
const std::shared_ptr<ImageFilter>& backdrop_filter) {
auto entry = CanvasStackEntry{};
entry.transform = transform_stack_.back().transform;
entry.cull_rect = transform_stack_.back().cull_rect;
entry.clip_height = transform_stack_.back().clip_height;
if (create_subpass) {
entry.rendering_mode = Entity::RenderingMode::kSubpass;
auto subpass = std::make_unique<EntityPass>();
subpass->SetEnableOffscreenCheckerboard(
debug_options.offscreen_texture_checkerboard);
if (backdrop_filter) {
EntityPass::BackdropFilterProc backdrop_filter_proc =
[backdrop_filter = backdrop_filter->Clone()](
const FilterInput::Ref& input, const Matrix& effect_transform,
Entity::RenderingMode rendering_mode) {
auto filter = backdrop_filter->WrapInput(input);
filter->SetEffectTransform(effect_transform);
filter->SetRenderingMode(rendering_mode);
return filter;
};
subpass->SetBackdropFilter(backdrop_filter_proc);
MipCountVisitor mip_count_visitor;
backdrop_filter->Visit(mip_count_visitor);
current_pass_->SetRequiredMipCount(
std::max(current_pass_->GetRequiredMipCount(),
mip_count_visitor.GetRequiredMipCount()));
}
subpass->SetBlendMode(blend_mode);
current_pass_ = GetCurrentPass().AddSubpass(std::move(subpass));
current_pass_->SetTransform(transform_stack_.back().transform);
current_pass_->SetClipDepth(transform_stack_.back().clip_height);
}
transform_stack_.emplace_back(entry);
}
bool Canvas::Restore() {
FML_DCHECK(transform_stack_.size() > 0);
if (transform_stack_.size() == 1) {
return false;
}
size_t num_clips = transform_stack_.back().num_clips;
current_pass_->PopClips(num_clips, current_depth_);
if (transform_stack_.back().rendering_mode ==
Entity::RenderingMode::kSubpass) {
current_pass_->SetNewClipDepth(++current_depth_);
current_pass_ = GetCurrentPass().GetSuperpass();
FML_DCHECK(current_pass_);
}
transform_stack_.pop_back();
if (num_clips > 0) {
RestoreClip();
}
return true;
}
void Canvas::Concat(const Matrix& transform) {
transform_stack_.back().transform = GetCurrentTransform() * transform;
}
void Canvas::PreConcat(const Matrix& transform) {
transform_stack_.back().transform = transform * GetCurrentTransform();
}
void Canvas::ResetTransform() {
transform_stack_.back().transform = {};
}
void Canvas::Transform(const Matrix& transform) {
Concat(transform);
}
const Matrix& Canvas::GetCurrentTransform() const {
return transform_stack_.back().transform;
}
const std::optional<Rect> Canvas::GetCurrentLocalCullingBounds() const {
auto cull_rect = transform_stack_.back().cull_rect;
if (cull_rect.has_value()) {
Matrix inverse = transform_stack_.back().transform.Invert();
cull_rect = cull_rect.value().TransformBounds(inverse);
}
return cull_rect;
}
void Canvas::Translate(const Vector3& offset) {
Concat(Matrix::MakeTranslation(offset));
}
void Canvas::Scale(const Vector2& scale) {
Concat(Matrix::MakeScale(scale));
}
void Canvas::Scale(const Vector3& scale) {
Concat(Matrix::MakeScale(scale));
}
void Canvas::Skew(Scalar sx, Scalar sy) {
Concat(Matrix::MakeSkew(sx, sy));
}
void Canvas::Rotate(Radians radians) {
Concat(Matrix::MakeRotationZ(radians));
}
size_t Canvas::GetSaveCount() const {
return transform_stack_.size();
}
void Canvas::RestoreToCount(size_t count) {
while (GetSaveCount() > count) {
if (!Restore()) {
return;
}
}
}
void Canvas::DrawPath(const Path& path, const Paint& paint) {
Entity entity;
entity.SetTransform(GetCurrentTransform());
entity.SetClipDepth(GetClipHeight());
entity.SetBlendMode(paint.blend_mode);
entity.SetContents(CreatePathContentsWithFilters(paint, path));
AddEntityToCurrentPass(std::move(entity));
}
void Canvas::DrawPaint(const Paint& paint) {
Entity entity;
entity.SetTransform(GetCurrentTransform());
entity.SetClipDepth(GetClipHeight());
entity.SetBlendMode(paint.blend_mode);
entity.SetContents(CreateCoverContentsWithFilters(paint));
AddEntityToCurrentPass(std::move(entity));
}
bool Canvas::AttemptDrawBlurredRRect(const Rect& rect,
Size corner_radii,
const Paint& paint) {
if (paint.color_source.GetType() != ColorSource::Type::kColor ||
paint.style != Paint::Style::kFill) {
return false;
}
if (!paint.mask_blur_descriptor.has_value()) {
return false;
}
// A blur sigma that is not positive enough should not result in a blur.
if (paint.mask_blur_descriptor->sigma.sigma <= kEhCloseEnough) {
return false;
}
// For symmetrically mask blurred solid RRects, absorb the mask blur and use
// a faster SDF approximation.
Color rrect_color =
paint.HasColorFilter()
// Absorb the color filter, if any.
? paint.GetColorFilter()->GetCPUColorFilterProc()(paint.color)
: paint.color;
Paint rrect_paint = {.mask_blur_descriptor = paint.mask_blur_descriptor};
// In some cases, we need to render the mask blur to a separate layer.
//
// 1. If the blur style is normal, we'll be drawing using one draw call and
// no clips. And so we can just wrap the RRect contents with the
// ImageFilter, which will get applied to the result as per usual.
//
// 2. If the blur style is solid, we combine the non-blurred RRect with the
// blurred RRect via two separate draw calls, and so we need to defer any
// fancy blending, translucency, or image filtering until after these two
// draws have been combined in a separate layer.
//
// 3. If the blur style is outer or inner, we apply the blur style via a
// clip. The ImageFilter needs to be applied to the mask blurred result.
// And so if there's an ImageFilter, we need to defer applying it until
// after the clipped RRect blur has been drawn to a separate texture.
// However, since there's only one draw call that produces color, we
// don't need to worry about the blend mode or translucency (unlike with
// BlurStyle::kSolid).
//
if ((paint.mask_blur_descriptor->style !=
FilterContents::BlurStyle::kNormal &&
paint.image_filter) ||
(paint.mask_blur_descriptor->style == FilterContents::BlurStyle::kSolid &&
(!rrect_color.IsOpaque() ||
paint.blend_mode != BlendMode::kSourceOver))) {
// Defer the alpha, blend mode, and image filter to a separate layer.
SaveLayer({.color = Color::White().WithAlpha(rrect_color.alpha),
.blend_mode = paint.blend_mode,
.image_filter = paint.image_filter});
rrect_paint.color = rrect_color.WithAlpha(1);
} else {
rrect_paint.color = rrect_color;
rrect_paint.blend_mode = paint.blend_mode;
rrect_paint.image_filter = paint.image_filter;
Save();
}
auto draw_blurred_rrect = [this, &rect, &corner_radii, &rrect_paint]() {
auto contents = std::make_shared<SolidRRectBlurContents>();
contents->SetColor(rrect_paint.color);
contents->SetSigma(rrect_paint.mask_blur_descriptor->sigma);
contents->SetRRect(rect, corner_radii);
Entity blurred_rrect_entity;
blurred_rrect_entity.SetTransform(GetCurrentTransform());
blurred_rrect_entity.SetClipDepth(GetClipHeight());
blurred_rrect_entity.SetBlendMode(rrect_paint.blend_mode);
rrect_paint.mask_blur_descriptor = std::nullopt;
blurred_rrect_entity.SetContents(
rrect_paint.WithFilters(std::move(contents)));
AddEntityToCurrentPass(std::move(blurred_rrect_entity));
};
switch (rrect_paint.mask_blur_descriptor->style) {
case FilterContents::BlurStyle::kNormal: {
draw_blurred_rrect();
break;
}
case FilterContents::BlurStyle::kSolid: {
// First, draw the blurred RRect.
draw_blurred_rrect();
// Then, draw the non-blurred RRect on top.
Entity entity;
entity.SetTransform(GetCurrentTransform());
entity.SetClipDepth(GetClipHeight());
entity.SetBlendMode(rrect_paint.blend_mode);
entity.SetContents(CreateContentsForGeometryWithFilters(
rrect_paint, Geometry::MakeRoundRect(rect, corner_radii)));
AddEntityToCurrentPass(std::move(entity));
break;
}
case FilterContents::BlurStyle::kOuter: {
ClipRRect(rect, corner_radii, Entity::ClipOperation::kDifference);
draw_blurred_rrect();
break;
}
case FilterContents::BlurStyle::kInner: {
ClipRRect(rect, corner_radii, Entity::ClipOperation::kIntersect);
draw_blurred_rrect();
break;
}
}
Restore();
return true;
}
void Canvas::DrawLine(const Point& p0, const Point& p1, const Paint& paint) {
Entity entity;
entity.SetTransform(GetCurrentTransform());
entity.SetClipDepth(GetClipHeight());
entity.SetBlendMode(paint.blend_mode);
entity.SetContents(CreateContentsForGeometryWithFilters(
paint, Geometry::MakeLine(p0, p1, paint.stroke_width, paint.stroke_cap)));
AddEntityToCurrentPass(std::move(entity));
}
void Canvas::DrawRect(const Rect& rect, const Paint& paint) {
if (paint.style == Paint::Style::kStroke) {
DrawPath(PathBuilder{}.AddRect(rect).TakePath(), paint);
return;
}
if (AttemptDrawBlurredRRect(rect, {}, paint)) {
return;
}
Entity entity;
entity.SetTransform(GetCurrentTransform());
entity.SetClipDepth(GetClipHeight());
entity.SetBlendMode(paint.blend_mode);
entity.SetContents(
CreateContentsForGeometryWithFilters(paint, Geometry::MakeRect(rect)));
AddEntityToCurrentPass(std::move(entity));
}
void Canvas::DrawOval(const Rect& rect, const Paint& paint) {
if (rect.IsSquare()) {
// Circles have slightly less overhead and can do stroking
DrawCircle(rect.GetCenter(), rect.GetWidth() * 0.5f, paint);
return;
}
if (paint.style == Paint::Style::kStroke) {
// No stroked ellipses yet
DrawPath(PathBuilder{}.AddOval(rect).TakePath(), paint);
return;
}
if (AttemptDrawBlurredRRect(rect, rect.GetSize() * 0.5f, paint)) {
return;
}
Entity entity;
entity.SetTransform(GetCurrentTransform());
entity.SetClipDepth(GetClipHeight());
entity.SetBlendMode(paint.blend_mode);
entity.SetContents(
CreateContentsForGeometryWithFilters(paint, Geometry::MakeOval(rect)));
AddEntityToCurrentPass(std::move(entity));
}
void Canvas::DrawRRect(const Rect& rect,
const Size& corner_radii,
const Paint& paint) {
if (AttemptDrawBlurredRRect(rect, corner_radii, paint)) {
return;
}
if (paint.style == Paint::Style::kFill) {
Entity entity;
entity.SetTransform(GetCurrentTransform());
entity.SetClipDepth(GetClipHeight());
entity.SetBlendMode(paint.blend_mode);
entity.SetContents(CreateContentsForGeometryWithFilters(
paint, Geometry::MakeRoundRect(rect, corner_radii)));
AddEntityToCurrentPass(std::move(entity));
return;
}
auto path = PathBuilder{}
.SetConvexity(Convexity::kConvex)
.AddRoundedRect(rect, corner_radii)
.SetBounds(rect)
.TakePath();
DrawPath(path, paint);
}
void Canvas::DrawCircle(const Point& center,
Scalar radius,
const Paint& paint) {
Size half_size(radius, radius);
if (AttemptDrawBlurredRRect(
Rect::MakeOriginSize(center - half_size, half_size * 2),
{radius, radius}, paint)) {
return;
}
Entity entity;
entity.SetTransform(GetCurrentTransform());
entity.SetClipDepth(GetClipHeight());
entity.SetBlendMode(paint.blend_mode);
auto geometry =
paint.style == Paint::Style::kStroke
? Geometry::MakeStrokedCircle(center, radius, paint.stroke_width)
: Geometry::MakeCircle(center, radius);
entity.SetContents(
CreateContentsForGeometryWithFilters(paint, std::move(geometry)));
AddEntityToCurrentPass(std::move(entity));
}
void Canvas::ClipPath(const Path& path, Entity::ClipOperation clip_op) {
auto bounds = path.GetBoundingBox();
ClipGeometry(Geometry::MakeFillPath(path), clip_op);
if (clip_op == Entity::ClipOperation::kIntersect) {
if (bounds.has_value()) {
IntersectCulling(bounds.value());
}
}
}
void Canvas::ClipRect(const Rect& rect, Entity::ClipOperation clip_op) {
auto geometry = Geometry::MakeRect(rect);
auto& cull_rect = transform_stack_.back().cull_rect;
if (clip_op == Entity::ClipOperation::kIntersect && //
cull_rect.has_value() && //
geometry->CoversArea(transform_stack_.back().transform, *cull_rect) //
) {
return; // This clip will do nothing, so skip it.
}
ClipGeometry(geometry, clip_op);
switch (clip_op) {
case Entity::ClipOperation::kIntersect:
IntersectCulling(rect);
break;
case Entity::ClipOperation::kDifference:
SubtractCulling(rect);
break;
}
}
void Canvas::ClipOval(const Rect& bounds, Entity::ClipOperation clip_op) {
auto geometry = Geometry::MakeOval(bounds);
auto& cull_rect = transform_stack_.back().cull_rect;
if (clip_op == Entity::ClipOperation::kIntersect && //
cull_rect.has_value() && //
geometry->CoversArea(transform_stack_.back().transform, *cull_rect) //
) {
return; // This clip will do nothing, so skip it.
}
ClipGeometry(geometry, clip_op);
switch (clip_op) {
case Entity::ClipOperation::kIntersect:
IntersectCulling(bounds);
break;
case Entity::ClipOperation::kDifference:
break;
}
}
void Canvas::ClipRRect(const Rect& rect,
const Size& corner_radii,
Entity::ClipOperation clip_op) {
// Does the rounded rect have a flat part on the top/bottom or left/right?
bool flat_on_TB = corner_radii.width * 2 < rect.GetWidth();
bool flat_on_LR = corner_radii.height * 2 < rect.GetHeight();
auto geometry = Geometry::MakeRoundRect(rect, corner_radii);
auto& cull_rect = transform_stack_.back().cull_rect;
if (clip_op == Entity::ClipOperation::kIntersect && //
cull_rect.has_value() && //
geometry->CoversArea(transform_stack_.back().transform, *cull_rect) //
) {
return; // This clip will do nothing, so skip it.
}
ClipGeometry(geometry, clip_op);
switch (clip_op) {
case Entity::ClipOperation::kIntersect:
IntersectCulling(rect);
break;
case Entity::ClipOperation::kDifference:
if (corner_radii.IsEmpty()) {
SubtractCulling(rect);
} else {
// We subtract the inner "tall" and "wide" rectangle pieces
// that fit inside the corners which cover the greatest area
// without involving the curved corners
// Since this is a subtract operation, we can subtract each
// rectangle piece individually without fear of interference.
if (flat_on_TB) {
SubtractCulling(rect.Expand(Size{-corner_radii.width, 0.0}));
}
if (flat_on_LR) {
SubtractCulling(rect.Expand(Size{0.0, -corner_radii.height}));
}
}
break;
}
}
void Canvas::ClipGeometry(const std::shared_ptr<Geometry>& geometry,
Entity::ClipOperation clip_op) {
auto contents = std::make_shared<ClipContents>();
contents->SetGeometry(geometry);
contents->SetClipOperation(clip_op);
Entity entity;
entity.SetTransform(GetCurrentTransform());
entity.SetContents(std::move(contents));
entity.SetClipDepth(GetClipHeight());
GetCurrentPass().PushClip(std::move(entity));
++transform_stack_.back().clip_height;
++transform_stack_.back().num_clips;
}
void Canvas::IntersectCulling(Rect clip_rect) {
clip_rect = clip_rect.TransformBounds(GetCurrentTransform());
std::optional<Rect>& cull_rect = transform_stack_.back().cull_rect;
if (cull_rect.has_value()) {
cull_rect = cull_rect
.value() //
.Intersection(clip_rect) //
.value_or(Rect{});
} else {
cull_rect = clip_rect;
}
}
void Canvas::SubtractCulling(Rect clip_rect) {
std::optional<Rect>& cull_rect = transform_stack_.back().cull_rect;
if (cull_rect.has_value()) {
clip_rect = clip_rect.TransformBounds(GetCurrentTransform());
cull_rect = cull_rect
.value() //
.Cutout(clip_rect) //
.value_or(Rect{});
}
// else (no cull) diff (any clip) is non-rectangular
}
void Canvas::RestoreClip() {
Entity entity;
entity.SetTransform(GetCurrentTransform());
// This path is empty because ClipRestoreContents just generates a quad that
// takes up the full render target.
auto clip_restore = std::make_shared<ClipRestoreContents>();
clip_restore->SetRestoreHeight(GetClipHeight());
entity.SetContents(std::move(clip_restore));
AddEntityToCurrentPass(std::move(entity));
}
void Canvas::DrawPoints(std::vector<Point> points,
Scalar radius,
const Paint& paint,
PointStyle point_style) {
if (radius <= 0) {
return;
}
Entity entity;
entity.SetTransform(GetCurrentTransform());
entity.SetClipDepth(GetClipHeight());
entity.SetBlendMode(paint.blend_mode);
entity.SetContents(CreateContentsForGeometryWithFilters(
paint,
Geometry::MakePointField(std::move(points), radius,
/*round=*/point_style == PointStyle::kRound)));
AddEntityToCurrentPass(std::move(entity));
}
void Canvas::DrawImage(const std::shared_ptr<Image>& image,
Point offset,
const Paint& paint,
SamplerDescriptor sampler) {
if (!image) {
return;
}
const auto source = Rect::MakeSize(image->GetSize());
const auto dest = source.Shift(offset);
DrawImageRect(image, source, dest, paint, std::move(sampler));
}
void Canvas::DrawImageRect(const std::shared_ptr<Image>& image,
Rect source,
Rect dest,
const Paint& paint,
SamplerDescriptor sampler,
SourceRectConstraint src_rect_constraint) {
if (!image || source.IsEmpty() || dest.IsEmpty()) {
return;
}
auto size = image->GetSize();
if (size.IsEmpty()) {
return;
}
auto texture_contents = TextureContents::MakeRect(dest);
texture_contents->SetTexture(image->GetTexture());
texture_contents->SetSourceRect(source);
texture_contents->SetStrictSourceRect(src_rect_constraint ==
SourceRectConstraint::kStrict);
texture_contents->SetSamplerDescriptor(std::move(sampler));
texture_contents->SetOpacity(paint.color.alpha);
texture_contents->SetDeferApplyingOpacity(paint.HasColorFilter());
std::shared_ptr<Contents> contents = texture_contents;
if (paint.mask_blur_descriptor.has_value()) {
contents = paint.mask_blur_descriptor->CreateMaskBlur(texture_contents);
}
Entity entity;
entity.SetBlendMode(paint.blend_mode);
entity.SetClipDepth(GetClipHeight());
entity.SetContents(paint.WithFilters(contents));
entity.SetTransform(GetCurrentTransform());
AddEntityToCurrentPass(std::move(entity));
}
Picture Canvas::EndRecordingAsPicture() {
// Assign clip depths to any outstanding clip entities.
while (current_pass_ != nullptr) {
current_pass_->PopAllClips(current_depth_);
current_pass_ = current_pass_->GetSuperpass();
}
Picture picture;
picture.pass = std::move(base_pass_);
Reset();
Initialize(initial_cull_rect_);
return picture;
}
EntityPass& Canvas::GetCurrentPass() {
FML_DCHECK(current_pass_ != nullptr);
return *current_pass_;
}
size_t Canvas::GetClipHeight() const {
return transform_stack_.back().clip_height;
}
void Canvas::AddEntityToCurrentPass(Entity entity) {
entity.SetNewClipDepth(++current_depth_);
GetCurrentPass().AddEntity(std::move(entity));
}
void Canvas::SaveLayer(const Paint& paint,
std::optional<Rect> bounds,
const std::shared_ptr<ImageFilter>& backdrop_filter,
ContentBoundsPromise bounds_promise) {
TRACE_EVENT0("flutter", "Canvas::saveLayer");
Save(true, paint.blend_mode, backdrop_filter);
// The DisplayList bounds/rtree doesn't account for filters applied to parent
// layers, and so sub-DisplayLists are getting culled as if no filters are
// applied.
// See also: https://github.com/flutter/flutter/issues/139294
if (paint.image_filter) {
transform_stack_.back().cull_rect = std::nullopt;
}
auto& new_layer_pass = GetCurrentPass();
if (bounds) {
new_layer_pass.SetBoundsLimit(bounds, bounds_promise);
}
if (paint.image_filter) {
MipCountVisitor mip_count_visitor;
paint.image_filter->Visit(mip_count_visitor);
new_layer_pass.SetRequiredMipCount(mip_count_visitor.GetRequiredMipCount());
}
// Only apply opacity peephole on default blending.
if (paint.blend_mode == BlendMode::kSourceOver) {
new_layer_pass.SetDelegate(
std::make_shared<OpacityPeepholePassDelegate>(paint));
} else {
new_layer_pass.SetDelegate(std::make_shared<PaintPassDelegate>(paint));
}
}
void Canvas::DrawTextFrame(const std::shared_ptr<TextFrame>& text_frame,
Point position,
const Paint& paint) {
Entity entity;
entity.SetClipDepth(GetClipHeight());
entity.SetBlendMode(paint.blend_mode);
auto text_contents = std::make_shared<TextContents>();
text_contents->SetTextFrame(text_frame);
text_contents->SetColor(paint.color);
text_contents->SetForceTextColor(paint.mask_blur_descriptor.has_value());
entity.SetTransform(GetCurrentTransform() *
Matrix::MakeTranslation(position));
// TODO(bdero): This mask blur application is a hack. It will always wind up
// doing a gaussian blur that affects the color source itself
// instead of just the mask. The color filter text support
// needs to be reworked in order to interact correctly with
// mask filters.
// https://github.com/flutter/flutter/issues/133297
entity.SetContents(
paint.WithFilters(paint.WithMaskBlur(std::move(text_contents), true)));
AddEntityToCurrentPass(std::move(entity));
}
static bool UseColorSourceContents(
const std::shared_ptr<VerticesGeometry>& vertices,
const Paint& paint) {
// If there are no vertex color or texture coordinates. Or if there
// are vertex coordinates then only if the contents are an image or
// a solid color.
if (vertices->HasVertexColors()) {
return false;
}
if (vertices->HasTextureCoordinates() &&
(paint.color_source.GetType() == ColorSource::Type::kImage ||
paint.color_source.GetType() == ColorSource::Type::kColor)) {
return true;
}
return !vertices->HasTextureCoordinates();
}
void Canvas::DrawVertices(const std::shared_ptr<VerticesGeometry>& vertices,
BlendMode blend_mode,
const Paint& paint) {
// Override the blend mode with kDestination in order to match the behavior
// of Skia's SK_LEGACY_IGNORE_DRAW_VERTICES_BLEND_WITH_NO_SHADER flag, which
// is enabled when the Flutter engine builds Skia.
if (paint.color_source.GetType() == ColorSource::Type::kColor) {
blend_mode = BlendMode::kDestination;
}
Entity entity;
entity.SetTransform(GetCurrentTransform());
entity.SetClipDepth(GetClipHeight());
entity.SetBlendMode(paint.blend_mode);
// If there are no vertex color or texture coordinates. Or if there
// are vertex coordinates then only if the contents are an image.
if (UseColorSourceContents(vertices, paint)) {
entity.SetContents(CreateContentsForGeometryWithFilters(paint, vertices));
AddEntityToCurrentPass(std::move(entity));
return;
}
// If there is are per-vertex colors, an image, and the blend mode
// is simple we can draw without a sub-renderpass.
if (blend_mode <= BlendMode::kModulate && vertices->HasVertexColors()) {
if (std::optional<ImageData> maybe_image_data =
GetImageColorSourceData(paint.color_source)) {
const ImageData& image_data = maybe_image_data.value();
auto contents = std::make_shared<VerticesSimpleBlendContents>();
contents->SetBlendMode(blend_mode);
contents->SetAlpha(paint.color.alpha);
contents->SetGeometry(vertices);
contents->SetEffectTransform(image_data.effect_transform);
contents->SetTexture(image_data.texture);
contents->SetTileMode(image_data.x_tile_mode, image_data.y_tile_mode);
entity.SetContents(paint.WithFilters(std::move(contents)));
AddEntityToCurrentPass(std::move(entity));
return;
}
}
auto src_paint = paint;
src_paint.color = paint.color.WithAlpha(1.0);
std::shared_ptr<Contents> src_contents =
src_paint.CreateContentsForGeometry(vertices);
if (vertices->HasTextureCoordinates()) {
// If the color source has an intrinsic size, then we use that to
// create the src contents as a simplification. Otherwise we use
// the extent of the texture coordinates to determine how large
// the src contents should be. If neither has a value we fall back
// to using the geometry coverage data.
Rect src_coverage;
auto size = src_contents->GetColorSourceSize();
if (size.has_value()) {
src_coverage = Rect::MakeXYWH(0, 0, size->width, size->height);
} else {
auto cvg = vertices->GetCoverage(Matrix{});
FML_CHECK(cvg.has_value());
src_coverage =
// Covered by FML_CHECK.
// NOLINTNEXTLINE(bugprone-unchecked-optional-access)
vertices->GetTextureCoordinateCoverge().value_or(cvg.value());
}
src_contents =
src_paint.CreateContentsForGeometry(Geometry::MakeRect(src_coverage));
}
auto contents = std::make_shared<VerticesContents>();
contents->SetAlpha(paint.color.alpha);
contents->SetBlendMode(blend_mode);
contents->SetGeometry(vertices);
contents->SetSourceContents(std::move(src_contents));
entity.SetContents(paint.WithFilters(std::move(contents)));
AddEntityToCurrentPass(std::move(entity));
}
void Canvas::DrawAtlas(const std::shared_ptr<Image>& atlas,
std::vector<Matrix> transforms,
std::vector<Rect> texture_coordinates,
std::vector<Color> colors,
BlendMode blend_mode,
SamplerDescriptor sampler,
std::optional<Rect> cull_rect,
const Paint& paint) {
if (!atlas) {
return;
}
std::shared_ptr<AtlasContents> contents = std::make_shared<AtlasContents>();
contents->SetColors(std::move(colors));
contents->SetTransforms(std::move(transforms));
contents->SetTextureCoordinates(std::move(texture_coordinates));
contents->SetTexture(atlas->GetTexture());
contents->SetSamplerDescriptor(std::move(sampler));
contents->SetBlendMode(blend_mode);
contents->SetCullRect(cull_rect);
contents->SetAlpha(paint.color.alpha);
Entity entity;
entity.SetTransform(GetCurrentTransform());
entity.SetClipDepth(GetClipHeight());
entity.SetBlendMode(paint.blend_mode);
entity.SetContents(paint.WithFilters(contents));
AddEntityToCurrentPass(std::move(entity));
}
} // namespace impeller