From b1ec7d8ce2fc1c960935055a427cdbd17f00c92b Mon Sep 17 00:00:00 2001 From: Leonardo da Silva Date: Fri, 31 Jul 2026 22:58:16 -0300 Subject: [PATCH 1/3] Strict source rect for sub-rect image draws (flutter/flutter#67881) Canvas::drawImageRect always passed SrcRectConstraint::kFast, so atlas cell draws could sample neighbouring cells. Pass kStrict whenever the source rect is a strict sub-rect of the image; full-image draws keep kFast semantics. Also forward the constraint in the Impeller dispatcher, which received it and dropped it (the canvas + TextureContents strict pipeline already existed, unwired). Validated on iOS sim + macOS: sentinel-atlas bleed 0/56 configs on both Skia and Impeller at FilterQuality.none and .low (stock: 3-30/56 none, 56/56 low). --- impeller/display_list/dl_dispatcher.cc | 14 ++++++++------ lib/ui/painting/canvas.cc | 7 ++++++- 2 files changed, 14 insertions(+), 7 deletions(-) diff --git a/impeller/display_list/dl_dispatcher.cc b/impeller/display_list/dl_dispatcher.cc index ffdd0c6eca9f8..d9cfdad0d19e7 100644 --- a/impeller/display_list/dl_dispatcher.cc +++ b/impeller/display_list/dl_dispatcher.cc @@ -735,12 +735,14 @@ void DlDispatcherBase::drawImageRect( AUTO_DEPTH_WATCHER(1u); GetCanvas().DrawImageRect( - image->impeller_texture(), // image - src, // source rect - dst, // destination rect - render_with_attributes ? paint_ : Paint(), // paint - skia_conversions::ToSamplerDescriptor(sampling) // sampling - ); + image->impeller_texture(), // image + src, // source rect + dst, // destination rect + render_with_attributes ? paint_ : Paint(), // paint + skia_conversions::ToSamplerDescriptor(sampling), // sampling + constraint == SrcRectConstraint::kStrict + ? SourceRectConstraint::kStrict + : SourceRectConstraint::kFast); } // |flutter::DlOpReceiver| diff --git a/lib/ui/painting/canvas.cc b/lib/ui/painting/canvas.cc index 1da304df6583c..c792fee9a1169 100644 --- a/lib/ui/painting/canvas.cc +++ b/lib/ui/painting/canvas.cc @@ -445,8 +445,13 @@ Dart_Handle Canvas::drawImageRect(const CanvasImage* image, DlPaint dl_paint; const DlPaint* opt_paint = paint.paint(dl_paint, kDrawImageRectWithPaintFlags); + // Sub-rect sources sample strictly so atlas cells cannot bleed neighbours + // (flutter/flutter#67881). Full-image draws keep kFast semantics. + const bool full_src = src.contains( + SkRect::MakeIWH(dl_image->width(), dl_image->height())); builder()->DrawImageRect(dl_image, src, dst, sampling, opt_paint, - DlCanvas::SrcRectConstraint::kFast); + full_src ? DlCanvas::SrcRectConstraint::kFast + : DlCanvas::SrcRectConstraint::kStrict); } return Dart_Null(); } From 8c72862070b1acb774ee58207dfa8fcbc6962498 Mon Sep 17 00:00:00 2001 From: Leonardo da Silva Date: Sat, 1 Aug 2026 10:05:40 -0300 Subject: [PATCH 2/3] Impeller: keep texture draw color filters when a stale image color source is set DrawImageRect renders the given texture; the paint's color source is irrelevant to it. Dispatchers persist paint attributes across ops and image draws do not re-sync the color source, so a preceding ImageShader draw leaves paint.color_source set. Paint::WithColorFilter's image-source early-return then assumes the color filter was applied by TiledTextureContents and drops it silently. Repro: draw a rect with an ImageShader paint, then drawImageRect with a ColorFilter - the filter is ignored (e.g. mask recolors render raw pixels). Fix: clear the color source on a local paint copy before building the texture entity. Upstream master retains the same early-return; recording-order changes currently mask it there. --- impeller/display_list/canvas.cc | 19 ++++++++++++++----- 1 file changed, 14 insertions(+), 5 deletions(-) diff --git a/impeller/display_list/canvas.cc b/impeller/display_list/canvas.cc index d14db69dc0d69..6617a9f9e30a8 100644 --- a/impeller/display_list/canvas.cc +++ b/impeller/display_list/canvas.cc @@ -734,20 +734,29 @@ void Canvas::DrawImageRect(const std::shared_ptr& image, texture_contents->SetOpacity(paint.color.alpha); texture_contents->SetDeferApplyingOpacity(paint.HasColorFilter()); + // The texture is the source for this draw. The paint's color source is + // irrelevant here, but if left set (dispatchers persist attributes across + // ops and image draws don't re-sync it), Paint::WithColorFilter mistakes + // it for an image color source that already applied the color filter and + // skips the filter entirely. + Paint texture_paint = paint; + texture_paint.color_source = nullptr; + Entity entity; - entity.SetBlendMode(paint.blend_mode); + entity.SetBlendMode(texture_paint.blend_mode); entity.SetTransform(GetCurrentTransform()); - if (!paint.mask_blur_descriptor.has_value()) { - entity.SetContents(paint.WithFilters(std::move(texture_contents))); + if (!texture_paint.mask_blur_descriptor.has_value()) { + entity.SetContents(texture_paint.WithFilters(std::move(texture_contents))); AddRenderEntityToCurrentPass(entity); return; } RectGeometry out_rect(Rect{}); - entity.SetContents(paint.WithFilters( - paint.mask_blur_descriptor->CreateMaskBlur(texture_contents, &out_rect))); + entity.SetContents(texture_paint.WithFilters( + texture_paint.mask_blur_descriptor->CreateMaskBlur(texture_contents, + &out_rect))); AddRenderEntityToCurrentPass(entity); } From 04faabf9fb042b8a0d5b6531d4699830d3720555 Mon Sep 17 00:00:00 2001 From: Leonardo da Silva Date: Sat, 1 Aug 2026 10:07:23 -0300 Subject: [PATCH 3/3] style: apply clang-format to fork changes --- impeller/display_list/dl_dispatcher.cc | 5 ++--- lib/ui/painting/canvas.cc | 4 ++-- 2 files changed, 4 insertions(+), 5 deletions(-) diff --git a/impeller/display_list/dl_dispatcher.cc b/impeller/display_list/dl_dispatcher.cc index d9cfdad0d19e7..edf80ed5892c2 100644 --- a/impeller/display_list/dl_dispatcher.cc +++ b/impeller/display_list/dl_dispatcher.cc @@ -740,9 +740,8 @@ void DlDispatcherBase::drawImageRect( dst, // destination rect render_with_attributes ? paint_ : Paint(), // paint skia_conversions::ToSamplerDescriptor(sampling), // sampling - constraint == SrcRectConstraint::kStrict - ? SourceRectConstraint::kStrict - : SourceRectConstraint::kFast); + constraint == SrcRectConstraint::kStrict ? SourceRectConstraint::kStrict + : SourceRectConstraint::kFast); } // |flutter::DlOpReceiver| diff --git a/lib/ui/painting/canvas.cc b/lib/ui/painting/canvas.cc index c792fee9a1169..cb05f9fbd2a4c 100644 --- a/lib/ui/painting/canvas.cc +++ b/lib/ui/painting/canvas.cc @@ -447,8 +447,8 @@ Dart_Handle Canvas::drawImageRect(const CanvasImage* image, paint.paint(dl_paint, kDrawImageRectWithPaintFlags); // Sub-rect sources sample strictly so atlas cells cannot bleed neighbours // (flutter/flutter#67881). Full-image draws keep kFast semantics. - const bool full_src = src.contains( - SkRect::MakeIWH(dl_image->width(), dl_image->height())); + const bool full_src = + src.contains(SkRect::MakeIWH(dl_image->width(), dl_image->height())); builder()->DrawImageRect(dl_image, src, dst, sampling, opt_paint, full_src ? DlCanvas::SrcRectConstraint::kFast : DlCanvas::SrcRectConstraint::kStrict);