⚠ Archived content — this site is no longer maintained.   Current WebKit documentation is at docs.webkit.org.

Changeset 284888 in webkit


Ignore:
Timestamp:
Oct 26, 2021, 12:42:43 PM (5 years ago)
Author:
timothy_horton@apple.com
Message:

DisplayList::Recorder's clipBounds() becomes empty if a flip is applied to the CTM
https://bugs.webkit.org/show_bug.cgi?id=232134

Reviewed by Darin Adler.

Source/WebCore:

New test: BifurcatedGraphicsContextTests.TransformedClip

  • platform/graphics/displaylists/DisplayListRecorder.cpp:

(WebCore::DisplayList::Recorder::clip):
(WebCore::DisplayList::Recorder::clipPath):
(WebCore::DisplayList::Recorder::clipBounds const):
Instead of updating clipBounds any time the CTM changes, store
clipBounds in base coordinates and map through the CTM when retrieved.

This matches CG's behavior and makes the clipBounds much sturdier.
For example, previously, applying a scale(1, -1) to the context
would immediately result in the clipBounds' height becoming negative,
making the bounds empty and confusing anything that reads from it.

(WebCore::DisplayList::Recorder::ContextState::translate):
(WebCore::DisplayList::Recorder::ContextState::rotate):
(WebCore::DisplayList::Recorder::ContextState::scale):
(WebCore::DisplayList::Recorder::ContextState::setCTM):
(WebCore::DisplayList::Recorder::ContextState::concatCTM):
Stop updating the clipBounds when the CTM changes, this is no longer necessary.

  • platform/graphics/displaylists/DisplayListRecorderImpl.cpp:

(WebCore::DisplayList::RecorderImpl::extentFromLocalBounds):
Since the clipBounds is now in base space, map the display list
item bounds to base space /before/ intersecting it with clipBounds.

  • platform/graphics/displaylists/DisplayListRecorder.h:

Drive-by add a default parameter so getCTM can be called on the subclass the same way it can on GraphicsContext.

  • platform/graphics/transforms/AffineTransform.h: Fix a typo.

Tools:

  • TestWebKitAPI/Tests/WebCore/cg/BifurcatedGraphicsContextTestsCG.cpp:

(TestWebKitAPI::TEST):
Add a test ensuring that our clipBounds and CG's match through a series
of simple transforms.

Location:
trunk
Files:
7 edited

Legend:

Unmodified
Added
Removed
  • trunk/Source/WebCore/ChangeLog

    r284883 r284888  
     12021-10-26  Tim Horton  <timothy_horton@apple.com>
     2
     3        DisplayList::Recorder's clipBounds() becomes empty if a flip is applied to the CTM
     4        https://bugs.webkit.org/show_bug.cgi?id=232134
     5
     6        Reviewed by Darin Adler.
     7
     8        New test: BifurcatedGraphicsContextTests.TransformedClip
     9
     10        * platform/graphics/displaylists/DisplayListRecorder.cpp:
     11        (WebCore::DisplayList::Recorder::clip):
     12        (WebCore::DisplayList::Recorder::clipPath):
     13        (WebCore::DisplayList::Recorder::clipBounds const):
     14        Instead of updating clipBounds any time the CTM changes, store
     15        clipBounds in base coordinates and map through the CTM when retrieved.
     16
     17        This matches CG's behavior and makes the clipBounds much sturdier.
     18        For example, previously, applying a `scale(1, -1)` to the context
     19        would immediately result in the clipBounds' height becoming negative,
     20        making the bounds empty and confusing anything that reads from it.
     21
     22        (WebCore::DisplayList::Recorder::ContextState::translate):
     23        (WebCore::DisplayList::Recorder::ContextState::rotate):
     24        (WebCore::DisplayList::Recorder::ContextState::scale):
     25        (WebCore::DisplayList::Recorder::ContextState::setCTM):
     26        (WebCore::DisplayList::Recorder::ContextState::concatCTM):
     27        Stop updating the clipBounds when the CTM changes, this is no longer necessary.
     28
     29        * platform/graphics/displaylists/DisplayListRecorderImpl.cpp:
     30        (WebCore::DisplayList::RecorderImpl::extentFromLocalBounds):
     31        Since the clipBounds is now in base space, map the display list
     32        item bounds to base space /before/ intersecting it with clipBounds.
     33
     34        * platform/graphics/displaylists/DisplayListRecorder.h:
     35        Drive-by add a default parameter so getCTM can be called on the subclass the same way it can on GraphicsContext.
     36
     37        * platform/graphics/transforms/AffineTransform.h: Fix a typo.
     38
    1392021-10-26  Chris Dumez  <cdumez@apple.com>
    240
  • trunk/Source/WebCore/platform/graphics/displaylists/DisplayListRecorder.cpp

    r284003 r284888  
    425425void Recorder::clip(const FloatRect& rect)
    426426{
    427     currentState().clipBounds.intersect(rect);
     427    currentState().clipBounds.intersect(currentState().ctm.mapRect(rect));
    428428    recordClip(rect);
    429429}
     
    441441void Recorder::clipPath(const Path& path, WindRule windRule)
    442442{
    443     currentState().clipBounds.intersect(path.fastBoundingRect());
     443    currentState().clipBounds.intersect(currentState().ctm.mapRect(path.fastBoundingRect()));
    444444    recordClipPath(path, windRule);
    445445}
     
    447447IntRect Recorder::clipBounds() const
    448448{
     449    if (auto inverse = currentState().ctm.inverse())
     450        return enclosingIntRect(inverse->mapRect(currentState().clipBounds));
     451
     452    // If the CTM is not invertible, return the original rect.
     453    // This matches CGRectApplyInverseAffineTransform behavior.
    449454    return enclosingIntRect(currentState().clipBounds);
    450455}
     
    522527{
    523528    ctm.translate(x, y);
    524     clipBounds.move(-x, -y);
    525529}
    526530
     
    532536    AffineTransform rotation;
    533537    rotation.rotate(angleInDegrees);
    534 
    535     if (std::optional<AffineTransform> inverse = rotation.inverse())
    536         clipBounds = inverse.value().mapRect(clipBounds);
    537538}
    538539
     
    540541{
    541542    ctm.scale(size);
    542     clipBounds.scale(1 / size.width(), 1 / size.height());
    543543}
    544544
    545545void Recorder::ContextState::setCTM(const AffineTransform& matrix)
    546546{
    547     std::optional<AffineTransform> inverseTransformForClipBounds;
    548     if (auto originalCTMInverse = ctm.inverse())
    549         inverseTransformForClipBounds = originalCTMInverse->multiply(matrix).inverse();
    550 
    551547    ctm = matrix;
    552 
    553     if (inverseTransformForClipBounds)
    554         clipBounds = inverseTransformForClipBounds->mapRect(clipBounds);
    555548}
    556549
     
    558551{
    559552    ctm *= matrix;
    560 
    561     if (std::optional<AffineTransform> inverse = matrix.inverse())
    562         clipBounds = inverse.value().mapRect(clipBounds);
    563553}
    564554
  • trunk/Source/WebCore/platform/graphics/displaylists/DisplayListRecorder.h

    r284858 r284888  
    256256    WEBCORE_EXPORT void concatCTM(const AffineTransform&) final;
    257257    WEBCORE_EXPORT void setCTM(const AffineTransform&) final;
    258     WEBCORE_EXPORT AffineTransform getCTM(GraphicsContext::IncludeDeviceScale) const final;
     258    WEBCORE_EXPORT AffineTransform getCTM(GraphicsContext::IncludeDeviceScale = PossiblyIncludeDeviceScale) const final;
    259259
    260260    WEBCORE_EXPORT void beginTransparencyLayer(float opacity) final;
  • trunk/Source/WebCore/platform/graphics/displaylists/DisplayListRecorderImpl.cpp

    r284858 r284888  
    459459    }
    460460
    461     FloatRect clippedExtent = intersection(state.clipBounds, bounds);
    462     return state.ctm.mapRect(clippedExtent);
     461    return intersection(state.clipBounds, state.ctm.mapRect(bounds));
    463462}
    464463
  • trunk/Source/WebCore/platform/graphics/transforms/AffineTransform.h

    r284600 r284888  
    133133    WEBCORE_EXPORT double yScale() const;
    134134
    135     bool isInvertible() const; // If you call this this, you're probably doing it wrong.
     135    bool isInvertible() const; // If you call this, you're probably doing it wrong.
    136136    WEBCORE_EXPORT std::optional<AffineTransform> inverse() const;
    137137
  • trunk/Tools/ChangeLog

    r284887 r284888  
     12021-10-26  Tim Horton  <timothy_horton@apple.com>
     2
     3        DisplayList::Recorder's clipBounds() becomes empty if a flip is applied to the CTM
     4        https://bugs.webkit.org/show_bug.cgi?id=232134
     5
     6        Reviewed by Darin Adler.
     7
     8        * TestWebKitAPI/Tests/WebCore/cg/BifurcatedGraphicsContextTestsCG.cpp:
     9        (TestWebKitAPI::TEST):
     10        Add a test ensuring that our clipBounds and CG's match through a series
     11        of simple transforms.
     12
    1132021-10-26  Brady Eidson  <beidson@apple.com>
    214
  • trunk/Tools/TestWebKitAPI/Tests/WebCore/cg/BifurcatedGraphicsContextTestsCG.cpp

    r284003 r284888  
    220220}
    221221
     222TEST(BifurcatedGraphicsContextTests, TransformedClip)
     223{
     224    auto colorSpace = DestinationColorSpace::SRGB();
     225    auto primaryCGContext = adoptCF(CGBitmapContextCreate(nullptr, 100, 100, 8, 4 * 100, colorSpace.platformColorSpace(), kCGImageAlphaPremultipliedLast));
     226
     227    GraphicsContextCG primaryContextCG(primaryCGContext.get());
     228    GraphicsContext& primaryContext = primaryContextCG;
     229
     230    InMemoryDisplayList displayList;
     231    RecorderImpl secondaryContextDL(displayList, { }, FloatRect(0, 0, 100, 100), { });
     232    GraphicsContext& secondaryContext = secondaryContextDL;
     233
     234    BifurcatedGraphicsContext ctx(primaryContext, secondaryContext);
     235
     236    ctx.clip(FloatRect(25, 25, 50, 50));
     237
     238    EXPECT_EQ(primaryContext.clipBounds(), secondaryContext.clipBounds());
     239    EXPECT_EQ(primaryContext.clipBounds(), FloatRect(25, 25, 50, 50));
     240
     241    ctx.scale({ 1, -1 });
     242    ctx.translate(0, -100);
     243
     244    EXPECT_EQ(primaryContext.clipBounds(), secondaryContext.clipBounds());
     245    EXPECT_EQ(primaryContext.clipBounds(), FloatRect(25, 25, 50, 50));
     246
     247    ctx.scale(2);
     248
     249    EXPECT_EQ(primaryContext.clipBounds(), secondaryContext.clipBounds());
     250    EXPECT_EQ(primaryContext.clipBounds(), FloatRect(12, 12, 26, 26));
     251
     252    {
     253        GraphicsContextStateSaver saver(ctx);
     254
     255        ctx.translate(12, 12);
     256
     257        EXPECT_EQ(primaryContext.clipBounds(), secondaryContext.clipBounds());
     258        EXPECT_EQ(primaryContext.clipBounds(), FloatRect(0, 0, 26, 26));
     259
     260        ctx.clip(FloatRect(0, 0, 10, 10));
     261
     262        EXPECT_EQ(primaryContext.clipBounds(), secondaryContext.clipBounds());
     263        EXPECT_EQ(primaryContext.clipBounds(), FloatRect(0, 0, 10, 10));
     264
     265        ctx.rotate(M_PI / 6);
     266
     267        EXPECT_EQ(primaryContext.clipBounds(), secondaryContext.clipBounds());
     268        EXPECT_EQ(primaryContext.clipBounds(), FloatRect(0, -5, 14, 14));
     269    }
     270
     271    EXPECT_EQ(primaryContext.clipBounds(), secondaryContext.clipBounds());
     272    EXPECT_EQ(primaryContext.clipBounds(), FloatRect(12, 12, 26, 26));
     273
     274    // Make the CTM non-invertible.
     275    ctx.scale({ 0, 1 });
     276
     277    EXPECT_EQ(primaryContext.clipBounds(), secondaryContext.clipBounds());
     278    EXPECT_EQ(primaryContext.clipBounds(), FloatRect(25, 25, 50, 50));
     279}
     280
    222281} // namespace TestWebKitAPI
    223282
Note: See TracChangeset for help on using the changeset viewer.