Skip to content

Integrate backgroundImageStrip into BackgroundPainter #44228

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 1 commit into
base: main
Choose a base branch
from

Conversation

kohler
Copy link
Contributor

@kohler kohler commented Apr 17, 2025

Integrate backgroundImageStrip into BackgroundPainter
https://bugs.webkit.org/show_bug.cgi?id=287503

Reviewed by NOBODY (OOPS!).

There are several cases in CSS where a image sized for one
box should be painted clipped to another box. One case is
a background for an inline element that spans multiple
lines (the background image should be sized to the union of
the line-boxes, but painted into the line-boxes individually).
Another case is the background for table rows and table
sections (the background image should be sized to the row
and/or section, but painted into the individual cells).
But previous code handled these two cases differently. Here
we unify the implementations in a BackgroundPainter member,
imageRect, which defines the sizing box for the background
image. Both inline backgrounds and row/section backgrounds
call BackgroundPainter::setImageRect to set the sizing box.
This does not change behavior on its own, but is an
important simplification towards fixing bug 287503.

This commit also removes unnecessary graphics state saving
and clipping from RenderTableCell::paintBackgroundsBehindCell.
It suffices to shrink the rectangle being painted; additional
clipping is redundant.

* Source/WebCore/rendering/BackgroundPainter.cpp:
(WebCore::BackgroundPainter::paintFillLayers const):
(WebCore::BackgroundPainter::paintFillLayer const):
(WebCore::BackgroundPainter::calculateBackgroundImageGeometry):
The BackgroundPainter::imageRect member, if set, defines the
dimensions of a background image.
* Source/WebCore/rendering/BackgroundPainter.h:
(WebCore::BackgroundPainter::setImageRect): New function.
(WebCore::BackgroundPainter::setOverrideOrigin): Deleted.
(WebCore::BackgroundPainter::paintFillLayer): Deleted.
* Source/WebCore/rendering/InlineBoxPainter.cpp:
(WebCore::InlineBoxPainter::paintFillLayer): Use setImageRect.
* Source/WebCore/rendering/RenderTableCell.cpp:
(WebCore::RenderTableCell::paintBackgroundsBehindCell): Use
setImageRect.

302e2b2

Misc iOS, visionOS, tvOS & watchOS macOS Linux Windows
✅ 🧪 style ✅ 🛠 ios ✅ 🛠 mac ✅ 🛠 wpe ✅ 🛠 win
✅ 🧪 bindings ✅ 🛠 ios-sim ✅ 🛠 mac-AS-debug ✅ 🧪 wpe-wk2 ⏳ 🧪 win-tests
✅ 🧪 webkitperl ❌ 🧪 ios-wk2 ✅ 🧪 api-mac ✅ 🧪 api-wpe
✅ 🧪 ios-wk2-wpt ✅ 🧪 mac-wk1 ✅ 🛠 wpe-cairo
✅ 🧪 api-ios ✅ 🧪 mac-wk2 ✅ 🛠 gtk
✅ 🛠 vision ✅ 🧪 mac-AS-debug-wk2 ✅ 🧪 gtk-wk2
✅ 🛠 vision-sim ✅ 🧪 mac-wk2-stress ✅ 🧪 api-gtk
✅ 🧪 vision-wk2 ✅ 🧪 mac-intel-wk2 🛠 playstation
✅ 🛠 tv ✅ 🛠 mac-safer-cpp
✅ 🛠 tv-sim
✅ 🛠 watch
✅ 🛠 watch-sim

@Ahmad-S792
Copy link
Contributor

@kohler - can you write more in commit message on why you are introducing this change (e.g., This patch do ABC to fix eventually webkit.org/b/xxx and this is to help in xyz way)?

@kohler
Copy link
Contributor Author

kohler commented Apr 17, 2025

I was asked by @nt1m to split up #40428 into multiple changes, including a cleanup change that has no semantic difference. This is a draft PR for the cleanup change.

@kohler kohler force-pushed the eng/background-image-rect branch from 0daa300 to 6625eca Compare April 18, 2025 02:22
@kohler kohler marked this pull request as ready for review April 18, 2025 02:24
@kohler
Copy link
Contributor Author

kohler commented Apr 18, 2025

@Ahmad-S792 @nt1m This change is ready for review!

Copy link
Contributor

@smfr smfr left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The commit message is oddly formatted (don't indent).

Is this well tested by reference tests?

@kohler
Copy link
Contributor Author

kohler commented Apr 18, 2025

@smfr Sorry, I hand-edited the comment at the head of this PR. Is the actual commit message badly formatted?

I think this is well-tested by reference tests. Tests I added for #31975 test that the background images on rows & sections still span those dimensions. LayoutTests/fast/inline/inline-background-clip-text-multiline.html (at least) tests dimensions on background images for inlines (and the result is good).

@kohler
Copy link
Contributor Author

kohler commented Apr 19, 2025

Also, tests included in fast/table/border-collapsing test backgrounds in border-collapse tables, validating that the call to fillRect.contract() is a suitable replacement for the prior clipping logic in RenderTableCell::paintBackgroundsBehindCell.

@kohler
Copy link
Contributor Author

kohler commented Apr 25, 2025

Ping on this? @smfr

@kohler kohler force-pushed the eng/background-image-rect branch from 6625eca to ca52497 Compare May 2, 2025 19:55
@kohler kohler force-pushed the eng/background-image-rect branch from ca52497 to a026905 Compare May 22, 2025 13:15
@kohler
Copy link
Contributor Author

kohler commented May 22, 2025

This PR remains ready for review and I'd love to know how to move it forward.

@kohler kohler force-pushed the eng/background-image-rect branch from a026905 to 699d448 Compare June 13, 2025 16:31
Copy link
Contributor

@smfr smfr left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Commit message is still oddly formatted.

auto imageRect = backgroundImageStrip.isEmpty() ? scrolledPaintRect : backgroundImageStrip;
auto paintOffset = backgroundImageStrip.isEmpty() ? rect.location() : backgroundImageStrip.location();
auto geometry = calculateBackgroundImageGeometry(m_renderer, m_paintInfo.paintContainer, bgLayer, paintOffset, imageRect, m_overrideOrigin);
// m_imageRect, if provided, is used for sizing the image. It is set for table rows and for inline boxes (multiline inline boxes paint like the image was one long strip spanning lines).
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This comment should be next to the member variable declaration.

@webkit-ews-buildbot webkit-ews-buildbot added the merging-blocked Applied to prevent a change from being merged label Jun 13, 2025
@Ahmad-S792 Ahmad-S792 added the Layout and Rendering For bugs with layout and rendering of Web pages. label Jun 13, 2025
@kohler kohler force-pushed the eng/background-image-rect branch from 699d448 to 621f14f Compare June 13, 2025 21:05
@kohler
Copy link
Contributor Author

kohler commented Jun 13, 2025

@smfr Thanks for the review!! I re-pushed, moving the comment you indicated next to the member variable declaration.

Sorry for being thick, but how should the commit message be formatted? Is it wrapped to too narrow a width? I've made some changes to the commit message in the latest push; the result looks like other commit messages in recent history.

@nt1m
Copy link
Member

nt1m commented Jun 13, 2025

@smfr Thanks for the review!! I re-pushed, moving the comment you indicated next to the member variable declaration.

Sorry for being thick, but how should the commit message be formatted? Is it wrapped to too narrow a width? I've made some changes to the commit message in the latest push; the result looks like other commit messages in recent history.

We usually don't wrap the commit message fwiw, but honestly it doesn't matter too much

@Ahmad-S792
Copy link
Contributor

@nt1m & @smfr - Should we add safe-merge-queue or since last run was 'green', we go ahead with 'merge-queue'?

@Ahmad-S792 Ahmad-S792 removed the merging-blocked Applied to prevent a change from being merged label Jun 13, 2025
@webkit-ews-buildbot webkit-ews-buildbot added the merging-blocked Applied to prevent a change from being merged label Jun 14, 2025
@kohler
Copy link
Contributor Author

kohler commented Jun 14, 2025

Buildbot blocked the merge because of two 0.01%-off pixel tests on iOS WPT. The failing tests involve table row backgrounds, which is relevant for this commit. The same tests pass on Mac, and in buildbot's iOS results, they also pass on the third try (step 42). I've looked at code differences in WebCore/rendering between the current version and the last version with an all-green test run (a026905), and nothing relevant jumped out. Any advice?

@nt1m
Copy link
Member

nt1m commented Jun 14, 2025

@kohler It was all green 3 weeks ago, because the tests that are now failing were imported just 2 weeks ago: 717f9ad

@kohler
Copy link
Contributor Author

kohler commented Jun 16, 2025

I've spent some time tracking this down and would appreciate some guidance (see end).

The problem isn’t visible to my eye. The diff reports small pixel differences (maxDifference=2) exclusively on the edges of some table cells.

I focused on this line, in BackgroundPainter.cpp, which clips the painted geometry to the relevant cell boundary: Line 489

The failing version looks like this.

        auto paintOffset = m_imageRect ? m_imageRect->location() : rect.location();
        auto geometry = calculateBackgroundImageGeometry(m_renderer, m_paintInfo.paintContainer, bgLayer, paintOffset, scrolledPaintRect, m_imageRect);

        auto& clientForBackgroundImage = backgroundObject ? *backgroundObject : m_renderer;
        bgImage->setContainerContextForRenderer(clientForBackgroundImage, geometry.tileSizeWithoutPixelSnapping, m_renderer.style().usedZoom());

        geometry.clip(LayoutRect(pixelSnappedRect)); // ***********

        RefPtr<Image> image;
        bool isFirstLine = inlineBoxIterator && inlineBoxIterator->lineBox()->isFirst();
        if (!geometry.destinationRect.isEmpty() && (image = bgImage->image(backgroundObject ? backgroundObject : &m_renderer, geometry.tileSize, isFirstLine))) {

But this clipping has a slightly different effect than clipping in the graphics context (i.e., basically at the pixel level) and painting a larger geometry. Apparently, the WPT test expects the more expensive graphics-context clip.

I tried a couple versions of the highlighted line that pass the table-backgrounds-bs-row-001 test. This simple one paints a possibly-much-larger geometry:

        if (m_imageRect) {
            backgroundClipStateSaver.ensureSave();
            context.clip(LayoutRect(pixelSnappedRect));
        } else
            geometry.clip(LayoutRect(pixelSnappedRect));

This one makes the most sense to me; it paints a slightly-larger geometry:

        geometry.clip(LayoutRect(pixelSnappedRect));
        if (m_imageRect) {
            backgroundClipStateSaver.ensureSave();
            context.clip(LayoutRect(pixelSnappedRect));
            geometry.destinationRect.inflate(1_lu);
        }

I'm checking in that version so other tests kick off.

So the original patch, which clipped only the painted geometry, seems like it should work, but it subtly violates a WPT test because of something like sub-pixel bleeding at cell edges. Expanding the painted geometry, as the patch above does, passes the test. Is that a satisfying solution?

@kohler kohler force-pushed the eng/background-image-rect branch from 621f14f to c8d7ece Compare June 16, 2025 19:03
@kohler kohler force-pushed the eng/background-image-rect branch from c8d7ece to 0ee1625 Compare June 16, 2025 19:06
https://bugs.webkit.org/show_bug.cgi?id=287503

Reviewed by Simon Fraser.

There are several cases in CSS where a image sized for one box should be painted clipped
to another box. One case is a background for an inline element that spans multiple lines
(the background image should be sized to the union of the line-boxes, but painted into
the line-boxes individually). Another case is the background for table rows and table
sections (the background image should be sized to the row and/or section, but painted
into the individual cells). But previous code handled these two cases differently. Here
we unify the implementations in a BackgroundPainter member, imageRect, which defines the
sizing box for the background image. Both inline backgrounds and row/section backgrounds
call BackgroundPainter::setImageRect to set the sizing box. This does not change
behavior on its own, but is an important simplification towards fixing bug 287503.

This commit also removes unnecessary graphics state saving and clipping from
RenderTableCell::paintBackgroundsBehindCell. It suffices to shrink the rectangle being
painted; additional clipping is redundant.

* Source/WebCore/platform/graphics/GraphicsContextStateSaver.h:
(WebCore::GraphicsContextStateSaver::ensureSave):
  New function.
* Source/WebCore/rendering/BackgroundPainter.cpp:
(WebCore::BackgroundPainter::paintFillLayers const):
(WebCore::BackgroundPainter::paintFillLayer const):
(WebCore::BackgroundPainter::calculateBackgroundImageGeometry):
  The BackgroundPainter::imageRect member, if set, defines the dimensions of a background image.
* Source/WebCore/rendering/BackgroundPainter.h:
(WebCore::BackgroundPainter::setImageRect): New function.
(WebCore::BackgroundPainter::setOverrideOrigin): Deleted.
(WebCore::BackgroundPainter::paintFillLayer): Deleted.
* Source/WebCore/rendering/InlineBoxPainter.cpp:
(WebCore::InlineBoxPainter::paintFillLayer):
  Use setImageRect.
* Source/WebCore/rendering/RenderTableCell.cpp:
(WebCore::RenderTableCell::paintBackgroundsBehindCell):
  Use setImageRect.
@kohler kohler force-pushed the eng/background-image-rect branch from 0ee1625 to 302e2b2 Compare June 17, 2025 15:24
@kohler
Copy link
Contributor Author

kohler commented Jun 18, 2025

The current commit, 0ee1625, passes all tests. (The clipped-filter failure seems unrelated; it has been failing on ios-wk2 on recent commits.)

The relevant part of BackgroundPainter::paintFillLayer:

        LayoutRect clipRect(pixelSnappedRect);
        if (m_imageRect) {
            backgroundClipStateSaver.ensureSave();
            context.clip(clipRect);
            clipRect.inflate(1_lu);
        }
        geometry.clip(clipRect);

I added a GraphicsContextStateSaver::ensureSave() function because BackgroundPainter::paintFillLayer introduces a context state saver, but does not always activate it. Rather than add a new boolean variable to track whether backgroundClipStateSaver.save() should be called, it seemed cleaner to add ensureSave(). This could be done other ways though (such as by introducing yet another GraphicsContextStateSaver object).

However, I think that it would be better to use the original code instead (which simply does geometry.clip(LayoutRect(pixelSnappedRect))), and add <meta name="fuzzy"> tags to the previously-failing WPT tests (imported/w3c/web-platform-tests/css/CSS2/tables/table-backgrounds-bs-row-001.xht, imported/w3c/web-platform-tests/css/CSS2/tables/table-backgrounds-bs-rowgroup-001.xht).

Advice appreciated! @nt1m @smfr


auto& clientForBackgroundImage = backgroundObject ? *backgroundObject : m_renderer;
bgImage->setContainerContextForRenderer(clientForBackgroundImage, geometry.tileSizeWithoutPixelSnapping, m_renderer.style().usedZoom());

geometry.clip(LayoutRect(pixelSnappedRect));
LayoutRect clipRect(pixelSnappedRect);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is wrong; pixelSnappedRect is a FloatRect for painting, and should never be cast back to a LayoutRect (which in theory is lossy).

Copy link
Contributor Author

@kohler kohler Jun 18, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

OK, but as you can see in the diff, the old code clipped the geometry to pixelSnappedRect.

I can trace that code back to 2014 (a06542c).

context.clip(clipRect);
clipRect.inflate(1_lu);
}
geometry.clip(clipRect);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

BackgroundImageGeometry contains layout (unsnapped) geometry; it's wrong to clip to a paint-snapped rect here. Maybe this should clip to rect ?

Copy link
Contributor Author

@kohler kohler Jun 18, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

BackgroundImageGeometry actually contains snapped geometry. The pixelSnapBackgroundImageGeometryForPainting function snaps the geometry to pixels. Every constructed BackgroundImageGeometry object was first pixel-snapped by that function.

@kohler
Copy link
Contributor Author

kohler commented Jun 19, 2025

@smfr asked about clipping to a non-snapped rectangle—rect instead of pixelSnappedRect.

The code currently checked in,

        LayoutRect clipRect(pixelSnappedRect);
        if (m_imageRect) {
            backgroundClipStateSaver.ensureSave();
            context.clip(clipRect);
            clipRect.inflate(1_lu);
        }
        geometry.clip(clipRect);

passes the tests in fast/table and imported/w3c/web-patform-tests/css/CSS2/tables on iphone-simulator.


This code,

        geometry.clip(rect);

fails

  imported/w3c/web-platform-tests/css/CSS2/tables/table-backgrounds-bc-row-001.xht [ ImageOnlyFailure ]
  imported/w3c/web-platform-tests/css/CSS2/tables/table-backgrounds-bc-rowgroup-001.xht [ ImageOnlyFailure ]
  imported/w3c/web-platform-tests/css/CSS2/tables/table-backgrounds-bs-row-001.xht [ ImageOnlyFailure ]
  imported/w3c/web-platform-tests/css/CSS2/tables/table-backgrounds-bs-rowgroup-001.xht [ ImageOnlyFailure ]

The failures on the -bs- tests are small, same as for the original geometry.clip(LayoutRect(pixelSnappedRect)). But the failures on the -bc- tests are significant.


This code,

        LayoutRect clipRect(rect);
        if (m_imageRect) {
            backgroundClipStateSaver.ensureSave();
            context.clip(clipRect);
            clipRect.inflate(1_lu);
        }
        geometry.clip(clipRect);

badly fails

  imported/w3c/web-platform-tests/css/CSS2/tables/table-backgrounds-bc-row-001.xht [ ImageOnlyFailure ]
  imported/w3c/web-platform-tests/css/CSS2/tables/table-backgrounds-bc-rowgroup-001.xht [ ImageOnlyFailure ]

So the code currently in this patch passes these tests. Using a non-snapped rectangle reliably fails some tests with significant visual differences.

It seems best to either use the code in the patch, or use geometry.clip(LayoutRect(pixelSnappedRect)) (which matches code previous to this patch) and add some fuzzy meta tags to the table-backgrounds-bs-row* tests (which otherwise would fail).

@kohler
Copy link
Contributor Author

kohler commented Jun 19, 2025

This meta tag allows the problematic table-backgrounds-bs-row/rowgroup tests to pass with the simpler geometry.clip(LayoutRect(pixelSnappedRect)) clipping.

	<meta name="fuzzy" content="maxDifference=0-2; totalPixels=0-2000" />

@kohler
Copy link
Contributor Author

kohler commented Jun 24, 2025

@nt1m @smfr Ping.

I think a simpler clipping code path, plus meta fuzzy tags on the two failing tests, is a better solution than the current PR, but feel like I'd need permission to add those tags.

If the more complex clipping code path is preferred, I think the patch is ready.

@nt1m
Copy link
Member

nt1m commented Jun 24, 2025

(Consider reaching out on webkit.slack.com , people should be more responsive there)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Layout and Rendering For bugs with layout and rendering of Web pages. merging-blocked Applied to prevent a change from being merged
Projects
None yet
Development

Successfully merging this pull request may close these issues.

6 participants